From aab8534836ee409575d6ea526e3e03b0f8826225 Mon Sep 17 00:00:00 2001 From: Arnaud Becheler <8360330+Becheler@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:15:07 +0200 Subject: [PATCH] feature: adding stateful visitor for dijkstra and astar --- .../shortest_paths/astar_search.adoc | 2 +- .../dijkstra_shortest_paths.adoc | 9 +- .../prim_minimum_spanning_tree.adoc | 9 +- include/boost/graph/astar_search.hpp | 60 +++-- .../boost/graph/dijkstra_shortest_paths.hpp | 36 +-- test/astar_search_test.cpp | 211 ++++++++++++++++++ test/dijkstra_no_color_map_compare.cpp | 95 ++++++++ 7 files changed, 373 insertions(+), 49 deletions(-) diff --git a/doc/modules/ROOT/pages/algorithms/shortest_paths/astar_search.adoc b/doc/modules/ROOT/pages/algorithms/shortest_paths/astar_search.adoc index 16cc1c0b9..8fe9d4951 100644 --- a/doc/modules/ROOT/pages/algorithms/shortest_paths/astar_search.adoc +++ b/doc/modules/ROOT/pages/algorithms/shortest_paths/astar_search.adoc @@ -541,4 +541,4 @@ See link:../example/astar-cities.cpp[`example/astar-cities.cpp`] for an example == Notes -[#1]#[1]# Since the visitor parameter is passed by value, if your visitor contains state then any changes to the state during the algorithm will be made to a copy of the visitor object, not the visitor object passed in. Therefore you may want the visitor to hold this state by pointer or reference. +[#1]#[1]# The visitor is taken by value, so the algorithm works on a copy. To keep state, give the visitor ordinary data members and pass it with `std::ref`. The algorithm then operates on the referenced object and its state survives the call. diff --git a/doc/modules/ROOT/pages/algorithms/shortest_paths/dijkstra_shortest_paths.adoc b/doc/modules/ROOT/pages/algorithms/shortest_paths/dijkstra_shortest_paths.adoc index d2afc907b..8bc5bcb02 100644 --- a/doc/modules/ROOT/pages/algorithms/shortest_paths/dijkstra_shortest_paths.adoc +++ b/doc/modules/ROOT/pages/algorithms/shortest_paths/dijkstra_shortest_paths.adoc @@ -432,8 +432,7 @@ putting all _V - S_ vertices in the priority queue at once, but instead only those vertices in _V - S_ that are discovered and therefore have a distance less than infinity. -[#2]#[2]# Since the visitor parameter is passed by value, if -your visitor contains state then any changes to the state during the -algorithm will be made to a copy of the visitor object, not the visitor -object passed in. Therefore you may want the visitor to hold this state -by pointer or reference. +[#2]#[2]# The visitor is taken by value, so the algorithm works on a copy. +To keep state, give the visitor ordinary data members and pass it with +`std::ref`. The algorithm then operates on the referenced object and its +state survives the call. diff --git a/doc/modules/ROOT/pages/algorithms/spanning_trees/prim_minimum_spanning_tree.adoc b/doc/modules/ROOT/pages/algorithms/spanning_trees/prim_minimum_spanning_tree.adoc index 31c0da4de..1f8afe1d1 100644 --- a/doc/modules/ROOT/pages/algorithms/spanning_trees/prim_minimum_spanning_tree.adoc +++ b/doc/modules/ROOT/pages/algorithms/spanning_trees/prim_minimum_spanning_tree.adoc @@ -319,8 +319,7 @@ for an example of using Prim's algorithm. == Notes -[#1]#[1]# Since the visitor parameter is passed by value, if -your visitor contains state then any changes to the state during the -algorithm will be made to a copy of the visitor object, not the visitor -object passed in. Therefore you may want the visitor to hold this state -by pointer or reference. +[#1]#[1]# The visitor is taken by value, so the algorithm works on a copy. +To keep state, give the visitor ordinary data members and pass it with +`std::ref`. The algorithm then operates on the referenced object and its +state survives the call. diff --git a/include/boost/graph/astar_search.hpp b/include/boost/graph/astar_search.hpp index 02f4b1cb4..30e555e52 100644 --- a/include/boost/graph/astar_search.hpp +++ b/include/boost/graph/astar_search.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,7 @@ template < class Graph, class CostType > class astar_heuristic typedef Vertex argument_type; typedef CostType result_type; astar_heuristic() {} - CostType operator()(Vertex u) { return static_cast< CostType >(0); } + CostType operator()(Vertex /*u*/) { return static_cast< CostType >(0); } }; template < class Visitor, class Graph > struct AStarVisitorConcept @@ -92,11 +93,11 @@ class astar_visitor : public bfs_visitor< Visitors > } private: - template < class Edge, class Graph > void tree_edge(Edge e, const Graph& g) + template < class Edge, class Graph > void tree_edge(Edge /*e*/, const Graph& /*g*/) { } template < class Edge, class Graph > - void non_tree_edge(Edge e, const Graph& g) + void non_tree_edge(Edge /*e*/, const Graph& /*g*/) { } }; @@ -144,29 +145,29 @@ namespace detail template < class Vertex, class Graph > void initialize_vertex(Vertex u, const Graph& g) { - m_vis.initialize_vertex(u, g); + user_vis().initialize_vertex(u, g); } template < class Vertex, class Graph > void discover_vertex(Vertex u, const Graph& g) { - m_vis.discover_vertex(u, g); + user_vis().discover_vertex(u, g); } template < class Vertex, class Graph > void examine_vertex(Vertex u, const Graph& g) { - m_vis.examine_vertex(u, g); + user_vis().examine_vertex(u, g); } template < class Vertex, class Graph > void finish_vertex(Vertex u, const Graph& g) { - m_vis.finish_vertex(u, g); + user_vis().finish_vertex(u, g); } template < class Edge, class Graph > void examine_edge(Edge e, const Graph& g) { if (m_compare(get(m_weight, e), m_zero)) BOOST_THROW_EXCEPTION(negative_edge()); - m_vis.examine_edge(e, g); + user_vis().examine_edge(e, g); } template < class Edge, class Graph > void non_tree_edge(Edge, const Graph&) @@ -182,13 +183,13 @@ namespace detail if (m_decreased) { - m_vis.edge_relaxed(e, g); + user_vis().edge_relaxed(e, g); put(m_cost, target(e, g), m_combine( get(m_distance, target(e, g)), m_h(target(e, g)))); } else - m_vis.edge_not_relaxed(e, g); + user_vis().edge_not_relaxed(e, g); } template < class Edge, class Graph > @@ -204,10 +205,10 @@ namespace detail m_combine( get(m_distance, target(e, g)), m_h(target(e, g)))); m_Q.update(target(e, g)); - m_vis.edge_relaxed(e, g); + user_vis().edge_relaxed(e, g); } else - m_vis.edge_not_relaxed(e, g); + user_vis().edge_not_relaxed(e, g); } template < class Edge, class Graph > @@ -219,16 +220,21 @@ namespace detail if (m_decreased) { - m_vis.edge_relaxed(e, g); + user_vis().edge_relaxed(e, g); put(m_cost, target(e, g), m_combine( get(m_distance, target(e, g)), m_h(target(e, g)))); m_Q.push(target(e, g)); put(m_color, target(e, g), Color::gray()); - m_vis.black_target(e, g); + user_vis().black_target(e, g); } else - m_vis.edge_not_relaxed(e, g); + user_vis().edge_not_relaxed(e, g); + } + + auto& user_vis() + { + return ::boost::graph::detail::deref_visitor(m_vis); } AStarHeuristic m_h; @@ -310,7 +316,9 @@ inline void astar_search_no_init_tree(const VertexListGraph& g, null_property_map< std::pair< Distance, Vertex >, std::size_t >(), compare); - vis.discover_vertex(s, g); + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + + vis_ref.discover_vertex(s, g); Q.push(std::make_pair(get(cost, s), s)); while (!Q.empty()) { @@ -318,11 +326,11 @@ inline void astar_search_no_init_tree(const VertexListGraph& g, Distance v_rank; boost::tie(v_rank, v) = Q.top(); Q.pop(); - vis.examine_vertex(v, g); + vis_ref.examine_vertex(v, g); BGL_FORALL_OUTEDGES_T(v, e, g, VertexListGraph) { Vertex w = target(e, g); - vis.examine_edge(e, g); + vis_ref.examine_edge(e, g); Distance e_weight = get(weight, e); if (compare(e_weight, zero)) BOOST_THROW_EXCEPTION(negative_edge()); @@ -330,18 +338,18 @@ inline void astar_search_no_init_tree(const VertexListGraph& g, = relax(e, g, weight, predecessor, distance, combine, compare); if (decreased) { - vis.edge_relaxed(e, g); + vis_ref.edge_relaxed(e, g); Distance w_rank = combine(get(distance, w), h(w)); put(cost, w, w_rank); - vis.discover_vertex(w, g); + vis_ref.discover_vertex(w, g); Q.push(std::make_pair(w_rank, w)); } else { - vis.edge_not_relaxed(e, g); + vis_ref.edge_not_relaxed(e, g); } } - vis.finish_vertex(v, g); + vis_ref.finish_vertex(v, g); } } @@ -361,6 +369,8 @@ inline void astar_search(const VertexListGraph& g, typedef typename property_traits< ColorMap >::value_type ColorValue; typedef color_traits< ColorValue > Color; + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end; for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) { @@ -368,7 +378,7 @@ inline void astar_search(const VertexListGraph& g, put(distance, *ui, inf); put(cost, *ui, inf); put(predecessor, *ui, *ui); - vis.initialize_vertex(*ui, g); + vis_ref.initialize_vertex(*ui, g); } put(distance, s, zero); put(cost, s, h(s)); @@ -390,13 +400,15 @@ inline void astar_search_tree(const VertexListGraph& g, CostZero zero) { + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end; for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) { put(distance, *ui, inf); put(cost, *ui, inf); put(predecessor, *ui, *ui); - vis.initialize_vertex(*ui, g); + vis_ref.initialize_vertex(*ui, g); } put(distance, s, zero); put(cost, s, h(s)); diff --git a/include/boost/graph/dijkstra_shortest_paths.hpp b/include/boost/graph/dijkstra_shortest_paths.hpp index afaf0302d..3f84d2a8d 100644 --- a/include/boost/graph/dijkstra_shortest_paths.hpp +++ b/include/boost/graph/dijkstra_shortest_paths.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +98,7 @@ class dijkstra_visitor : public bfs_visitor< Visitors > } private: - template < class Edge, class Graph > void tree_edge(Edge u, Graph& g) {} + template < class Edge, class Graph > void tree_edge(Edge /*u*/, Graph& /*g*/) {} }; template < class Visitors > dijkstra_visitor< Visitors > make_dijkstra_visitor(Visitors vis) @@ -136,9 +137,9 @@ namespace detail bool decreased = relax_target(e, g, m_weight, m_predecessor, m_distance, m_combine, m_compare); if (decreased) - m_vis.edge_relaxed(e, g); + user_vis().edge_relaxed(e, g); else - m_vis.edge_not_relaxed(e, g); + user_vis().edge_not_relaxed(e, g); } template < class Edge, class Graph > void gray_target(Edge e, Graph& g) { @@ -149,27 +150,27 @@ namespace detail if (decreased) { dijkstra_queue_update(m_Q, target(e, g), old_distance); - m_vis.edge_relaxed(e, g); + user_vis().edge_relaxed(e, g); } else - m_vis.edge_not_relaxed(e, g); + user_vis().edge_not_relaxed(e, g); } template < class Vertex, class Graph > void initialize_vertex(Vertex u, Graph& g) { - m_vis.initialize_vertex(u, g); + user_vis().initialize_vertex(u, g); } template < class Edge, class Graph > void non_tree_edge(Edge, Graph&) {} template < class Vertex, class Graph > void discover_vertex(Vertex u, Graph& g) { - m_vis.discover_vertex(u, g); + user_vis().discover_vertex(u, g); } template < class Vertex, class Graph > void examine_vertex(Vertex u, Graph& g) { - m_vis.examine_vertex(u, g); + user_vis().examine_vertex(u, g); } template < class Edge, class Graph > void examine_edge(Edge e, Graph& g) { @@ -204,13 +205,18 @@ namespace detail boost::throw_exception(negative_edge()); // End of test for negative-weight edges. - m_vis.examine_edge(e, g); + user_vis().examine_edge(e, g); } template < class Edge, class Graph > void black_target(Edge, Graph&) {} template < class Vertex, class Graph > void finish_vertex(Vertex u, Graph& g) { - m_vis.finish_vertex(u, g); + user_vis().finish_vertex(u, g); + } + + auto& user_vis() + { + return ::boost::graph::detail::deref_visitor(m_vis); } UniformCostVisitor m_vis; @@ -250,8 +256,8 @@ namespace detail struct vertex_property_map_generator_helper< Graph, IndexMap, Value, false > { typedef boost::vector_property_map< Value, IndexMap > type; - static type build(const Graph& g, const IndexMap& index, - std::unique_ptr< Value[] >& array_holder) + static type build(const Graph& /*g*/, const IndexMap& index, + std::unique_ptr< Value[] >& /*array_holder*/) { return boost::make_vector_property_map< Value >(index); } @@ -299,7 +305,7 @@ namespace detail typedef boost::vector_property_map< boost::two_bit_color_type, IndexMap > type; - static type build(const Graph& g, const IndexMap& index) + static type build(const Graph& /*g*/, const IndexMap& index) { return boost::make_vector_property_map< boost::two_bit_color_type >( index); @@ -448,10 +454,12 @@ inline void dijkstra_shortest_paths(const VertexListGraph& g, { typedef typename property_traits< ColorMap >::value_type ColorValue; typedef color_traits< ColorValue > Color; + auto& vis_ref = ::boost::graph::detail::deref_visitor(vis); + typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end; for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) { - vis.initialize_vertex(*ui, g); + vis_ref.initialize_vertex(*ui, g); put(distance, *ui, inf); put(predecessor, *ui, *ui); put(color, *ui, Color::white()); diff --git a/test/astar_search_test.cpp b/test/astar_search_test.cpp index 2d81e8278..e05d2e6e3 100644 --- a/test/astar_search_test.cpp +++ b/test/astar_search_test.cpp @@ -14,6 +14,9 @@ #include #include #include + +#include +#include #include #include #include @@ -76,6 +79,209 @@ class astar_goal_visitor : public boost::default_astar_visitor Vertex m_goal; }; +// state in a plain data member, so it survives only through std::ref +struct examine_tally : boost::default_astar_visitor +{ + template < class Vertex, class Graph > void examine_vertex(Vertex, Graph&) + { + ++count; + } + std::size_t count = 0; +}; + +// a zero heuristic keeps the search deterministic, A* then behaves like +// dijkstra and examines every reachable vertex once +template < class Graph > +struct zero_heuristic : boost::astar_heuristic< Graph, int > +{ + using Vertex = typename boost::graph_traits< Graph >::vertex_descriptor; + int operator()(Vertex) { return 0; } +}; + +void test_stateful_visitor_with_ref() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::undirectedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + graph_t g(3); + boost::add_edge(0, 1, 1, g); + boost::add_edge(1, 2, 1, g); + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > rank(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto rank_map = boost::make_iterator_property_map(rank.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + examine_tally tracked; + boost::astar_search(g, 0, zero_heuristic< graph_t >(), std::ref(tracked), + parent_map, rank_map, distance_map, boost::get(boost::edge_weight, g), + index_map, color_map, std::less< int >(), boost::closed_plus< int >(), + (std::numeric_limits< int >::max)(), 0); + BOOST_TEST_EQ(tracked.count, boost::num_vertices(g)); + BOOST_TEST_EQ(distance[2], 2); + + // by value the caller's visitor is left untouched + examine_tally copied; + boost::astar_search(g, 0, zero_heuristic< graph_t >(), copied, parent_map, + rank_map, distance_map, boost::get(boost::edge_weight, g), index_map, + color_map, std::less< int >(), boost::closed_plus< int >(), + (std::numeric_limits< int >::max)(), 0); + BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); +} + +// counts the two relaxation outcomes the search can reach on an edge whose +// target has already been discovered or finished +struct branch_tally : boost::default_astar_visitor +{ + template < class Edge, class Graph > void edge_not_relaxed(Edge, Graph&) + { + ++not_relaxed; + } + + template < class Edge, class Graph > void black_target(Edge, Graph&) + { + ++reopened; + } + + std::size_t not_relaxed = 0; + std::size_t reopened = 0; +}; + +// an overestimating heuristic, which is what makes A* reopen a vertex it has +// already finished +template < class Graph > +struct inconsistent_heuristic : boost::astar_heuristic< Graph, int > +{ + using Vertex = typename boost::graph_traits< Graph >::vertex_descriptor; + int operator()(Vertex u) { return u == static_cast< Vertex >(2) ? 100 : 0; } +}; + +void test_edge_not_relaxed_on_discovered_target() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::undirectedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + + // the long 1 to 2 edge never improves either endpoint + graph_t g(3); + boost::add_edge(0, 1, 1, g); + boost::add_edge(0, 2, 1, g); + boost::add_edge(1, 2, 5, g); + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > rank(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto rank_map = boost::make_iterator_property_map(rank.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + branch_tally tracked; + boost::astar_search(g, 0, zero_heuristic< graph_t >(), std::ref(tracked), + parent_map, rank_map, distance_map, boost::get(boost::edge_weight, g), + index_map, color_map, std::less< int >(), boost::closed_plus< int >(), + (std::numeric_limits< int >::max)(), 0); + + BOOST_TEST_EQ(distance[1], 1); + BOOST_TEST_EQ(distance[2], 1); + BOOST_TEST(tracked.not_relaxed > 0u); + BOOST_TEST_EQ(tracked.reopened, static_cast< std::size_t >(0)); +} + +void test_black_target_reopens_finished_vertex() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::directedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + + // vertex 3 is finished through 1 at distance 11, then the detour through + // the penalised vertex 2 offers it at distance 3 + graph_t g(4); + boost::add_edge(0, 1, 1, g); + boost::add_edge(1, 3, 10, g); + boost::add_edge(0, 2, 2, g); + boost::add_edge(2, 3, 1, g); + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > rank(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto rank_map = boost::make_iterator_property_map(rank.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + branch_tally tracked; + boost::astar_search(g, 0, inconsistent_heuristic< graph_t >(), + std::ref(tracked), parent_map, rank_map, distance_map, + boost::get(boost::edge_weight, g), index_map, color_map, + std::less< int >(), boost::closed_plus< int >(), + (std::numeric_limits< int >::max)(), 0); + + BOOST_TEST_EQ(tracked.reopened, static_cast< std::size_t >(1)); + BOOST_TEST_EQ(distance[3], 3); +} + +void test_edge_not_relaxed_on_tree_edge() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::directedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + + // two hops of 60 overshoot the infinity below, so the second hop never + // improves on it and vertex 2 stays unreached + graph_t g(3); + boost::add_edge(0, 1, 60, g); + boost::add_edge(1, 2, 60, g); + + const int infinity = 100; + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > rank(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto rank_map = boost::make_iterator_property_map(rank.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + branch_tally tracked; + boost::astar_search(g, 0, zero_heuristic< graph_t >(), std::ref(tracked), + parent_map, rank_map, distance_map, boost::get(boost::edge_weight, g), + index_map, color_map, std::less< int >(), + boost::closed_plus< int >(infinity), infinity, 0); + + BOOST_TEST_EQ(distance[1], 60); + BOOST_TEST_EQ(distance[2], infinity); + BOOST_TEST_EQ(tracked.not_relaxed, static_cast< std::size_t >(1)); + BOOST_TEST_EQ(tracked.reopened, static_cast< std::size_t >(0)); +} + int main() { // specify some types @@ -203,5 +409,10 @@ int main() } BOOST_TEST_EQ(path_weight.v, d[goal].v); + test_stateful_visitor_with_ref(); + test_edge_not_relaxed_on_discovered_target(); + test_edge_not_relaxed_on_tree_edge(); + test_black_target_reopens_finished_vertex(); + return boost::report_errors(); } diff --git a/test/dijkstra_no_color_map_compare.cpp b/test/dijkstra_no_color_map_compare.cpp index c07a7237b..5a234c9c9 100644 --- a/test/dijkstra_no_color_map_compare.cpp +++ b/test/dijkstra_no_color_map_compare.cpp @@ -89,6 +89,23 @@ struct discover_tally : boost::dijkstra_visitor<> std::size_t count = 0; }; +// the events the dijkstra_shortest_paths tests below count +struct event_tally : boost::dijkstra_visitor<> +{ + template < class Vertex, class Graph > void examine_vertex(Vertex, Graph&) + { + ++examined; + } + + template < class Edge, class Graph > void edge_not_relaxed(Edge, Graph&) + { + ++not_relaxed; + } + + std::size_t examined = 0; + std::size_t not_relaxed = 0; +}; + void test_stateful_visitor_with_ref() { using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, @@ -124,6 +141,82 @@ void test_stateful_visitor_with_ref() BOOST_TEST_EQ(copied.count, static_cast< std::size_t >(0)); } +void test_dijkstra_stateful_visitor_with_ref() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::undirectedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + graph_t g(3); + boost::add_edge(0, 1, 1, g); + boost::add_edge(1, 2, 1, g); + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + // the algorithm examines every reachable vertex once + event_tally tracked; + boost::dijkstra_shortest_paths(g, 0, parent_map, distance_map, + boost::get(boost::edge_weight, g), index_map, std::less< int >(), + boost::closed_plus< int >(), (std::numeric_limits< int >::max)(), 0, + std::ref(tracked), color_map); + BOOST_TEST_EQ(tracked.examined, boost::num_vertices(g)); + BOOST_TEST_EQ(distance[2], 2); + + // by value the caller's visitor is left untouched + event_tally copied; + boost::dijkstra_shortest_paths(g, 0, parent_map, distance_map, + boost::get(boost::edge_weight, g), index_map, std::less< int >(), + boost::closed_plus< int >(), (std::numeric_limits< int >::max)(), 0, + copied, color_map); + BOOST_TEST_EQ(copied.examined, static_cast< std::size_t >(0)); +} + +void test_edge_not_relaxed_on_tree_edge() +{ + using graph_t = boost::adjacency_list< boost::vecS, boost::vecS, + boost::directedS, boost::no_property, + boost::property< boost::edge_weight_t, int > >; + + // two hops of 60 overshoot the infinity below, so the second hop never + // improves on it and vertex 2 stays unreached + graph_t g(3); + boost::add_edge(0, 1, 60, g); + boost::add_edge(1, 2, 60, g); + + const int infinity = 100; + + std::vector< std::size_t > parent(boost::num_vertices(g)); + std::vector< int > distance(boost::num_vertices(g)); + std::vector< boost::default_color_type > color(boost::num_vertices(g)); + + auto index_map = boost::get(boost::vertex_index, g); + auto parent_map + = boost::make_iterator_property_map(parent.begin(), index_map); + auto distance_map + = boost::make_iterator_property_map(distance.begin(), index_map); + auto color_map + = boost::make_iterator_property_map(color.begin(), index_map); + + event_tally tracked; + boost::dijkstra_shortest_paths(g, 0, parent_map, distance_map, + boost::get(boost::edge_weight, g), index_map, std::less< int >(), + boost::closed_plus< int >(infinity), infinity, 0, std::ref(tracked), + color_map); + + BOOST_TEST_EQ(distance[1], 60); + BOOST_TEST_EQ(distance[2], infinity); + BOOST_TEST_EQ(tracked.not_relaxed, static_cast< std::size_t >(1)); +} + int main(int argc, char* argv[]) { using namespace boost; @@ -173,6 +266,8 @@ int main(int argc, char* argv[]) run_dijkstra_test(graph); test_stateful_visitor_with_ref(); + test_dijkstra_stateful_visitor_with_ref(); + test_edge_not_relaxed_on_tree_edge(); return boost::report_errors(); }