From cdd24ac074c64c1c2aaccfc6c093d007e3076440 Mon Sep 17 00:00:00 2001 From: Phil Ratzloff Date: Thu, 12 Sep 2024 12:40:56 -0400 Subject: [PATCH] Use reflection for visitors on bellman-ford algos --- .../algorithm/bellman_ford_shortest_paths.hpp | 114 ++++++++---------- .../graph/algorithm/common_shortest_paths.hpp | 22 ++-- .../algorithm/dijkstra_shortest_paths.hpp | 2 +- tests/bellman_shortest_paths_tests.cpp | 67 +++++----- tests/dijkstra_shortest_paths_tests.cpp | 42 +------ tests/visitor_dijkstra_tests.cpp | 3 +- 6 files changed, 100 insertions(+), 150 deletions(-) diff --git a/include/graph/algorithm/bellman_ford_shortest_paths.hpp b/include/graph/algorithm/bellman_ford_shortest_paths.hpp index cd31218..e043e77 100644 --- a/include/graph/algorithm/bellman_ford_shortest_paths.hpp +++ b/include/graph/algorithm/bellman_ford_shortest_paths.hpp @@ -27,35 +27,6 @@ namespace graph { -template -class bellman_visitor_base { - // Types -public: - using graph_type = G; - using vertex_desc_type = vertex_descriptor, vertex_reference_t, void>; - using sourced_edge_desc_type = edge_descriptor, true, edge_reference_t, void>; - - // Visitor Functions -public: - // edge visitor functions - constexpr void on_examine_edge(const sourced_edge_desc_type& edesc) {} - constexpr void on_edge_relaxed(const sourced_edge_desc_type& edesc) {} - constexpr void on_edge_not_relaxed(const sourced_edge_desc_type& edesc) {} - constexpr void on_edge_minimized(const sourced_edge_desc_type& edesc) {} - constexpr void on_edge_not_minimized(const sourced_edge_desc_type& edesc) {} -}; - -template -concept bellman_visitor = requires(Visitor& v, - remove_reference_t::vertex_desc_type& vdesc, - remove_reference_t::sourced_edge_desc_type& edesc) { - { v.on_examine_edge(edesc) }; - { v.on_edge_relaxed(edesc) }; - { v.on_edge_not_relaxed(edesc) }; - { v.on_edge_minimized(edesc) }; - { v.on_edge_not_minimized(edesc) }; -}; - /** * @brief Get the vertex ids in a negative weight cycle. * @@ -126,23 +97,22 @@ template (edge_reference_t)>, - class Visitor = bellman_visitor_base, + class Visitor = empty_visitor, class Compare = less>, class Combine = plus>> -requires convertible_to, vertex_id_t> && // - is_arithmetic_v> && // - convertible_to, range_value_t> && // - sized_range && // - sized_range && // - basic_edge_weight_function, Compare, Combine> && // - bellman_visitor +requires convertible_to, vertex_id_t> && // + is_arithmetic_v> && // + convertible_to, range_value_t> && // + sized_range && // + sized_range && // + basic_edge_weight_function, Compare, Combine> [[nodiscard]] constexpr optional> bellman_ford_shortest_paths( G&& g, const Sources& sources, Distances& distances, Predecessors& predecessor, WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 - Visitor&& visitor = bellman_visitor_base(), + Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>()) { using id_type = vertex_id_t; @@ -193,18 +163,27 @@ requires convertible_to, vertex_id_t> && fmt::format("bellman_ford_shortest_paths: source vertex id '{}' is out of range", source)); } distances[static_cast(source)] = zero; // mark source as discovered + if constexpr (has_on_discover_vertex) { + visitor.on_discover_vertex({source, vertex_value(g, source)}); + } } + // Evalute the shortest paths bool at_least_one_edge_relaxed = false; for (id_type k = 0; k < N; ++k) { at_least_one_edge_relaxed = false; for (auto&& [uid, vid, uv, w] : views::edgelist(g, weight)) { - visitor.on_examine_edge({uid, vid, uv}); + if constexpr (has_on_examine_edge) { + visitor.on_examine_edge({uid, vid, uv}); + } if (relax_target(uv, uid, w)) { at_least_one_edge_relaxed = true; - visitor.on_edge_relaxed({uid, vid, uv}); - } else + if constexpr (has_on_edge_relaxed) { + visitor.on_edge_relaxed({uid, vid, uv}); + } + } else if constexpr (has_on_edge_not_relaxed) { visitor.on_edge_not_relaxed({uid, vid, uv}); + } } if (!at_least_one_edge_relaxed) break; @@ -214,13 +193,17 @@ requires convertible_to, vertex_id_t> && if (at_least_one_edge_relaxed) { for (auto&& [uid, vid, uv, w] : views::edgelist(g, weight)) { if (compare(combine(distances[uid], w), distances[vid])) { - visitor.on_edge_not_minimized({uid, vid, uv}); if constexpr (!is_same_v) { predecessor[vid] = uid; // close the cycle } + if constexpr (has_on_edge_not_minimized) { + visitor.on_edge_not_minimized({uid, vid, uv}); + } return return_type(uid); } else { - visitor.on_edge_minimized({uid, vid, uv}); + if constexpr (has_on_edge_minimized) { + visitor.on_edge_minimized({uid, vid, uv}); + } } } } @@ -232,26 +215,26 @@ template (edge_reference_t)>, - class Visitor = bellman_visitor_base, + class Visitor = empty_visitor, class Compare = less>, class Combine = plus>> -requires is_arithmetic_v> && // - convertible_to, range_value_t> && // - sized_range && // - sized_range && // - basic_edge_weight_function, Compare, Combine> && // - bellman_visitor +requires is_arithmetic_v> && // + convertible_to, range_value_t> && // + sized_range && // + sized_range && // + basic_edge_weight_function, Compare, Combine> [[nodiscard]] constexpr optional> bellman_ford_shortest_paths( G&& g, const vertex_id_t source, Distances& distances, Predecessors& predecessor, WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 - Visitor&& visitor = bellman_visitor_base(), + Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>()) { return bellman_ford_shortest_paths(g, subrange(&source, (&source + 1)), distances, predecessor, weight, - forward(visitor), forward(compare), forward(combine)); + forward(visitor), forward(compare), + forward(combine)); } @@ -289,42 +272,41 @@ template (edge_reference_t)>, - class Visitor = bellman_visitor_base, + class Visitor = empty_visitor, class Compare = less>, class Combine = plus>> -requires convertible_to, vertex_id_t> && // - is_arithmetic_v> && // - sized_range && // - basic_edge_weight_function, Compare, Combine> && // - bellman_visitor +requires convertible_to, vertex_id_t> && // + is_arithmetic_v> && // + sized_range && // + basic_edge_weight_function, Compare, Combine> [[nodiscard]] constexpr optional> bellman_ford_shortest_distances( G&& g, const Sources& sources, Distances& distances, WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 - Visitor&& visitor = bellman_visitor_base(), + Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>()) { return bellman_ford_shortest_paths(g, sources, distances, _null_predecessors, forward(weight), - forward(visitor), forward(compare), forward(combine)); + forward(visitor), forward(compare), + forward(combine)); } template (edge_reference_t)>, - class Visitor = bellman_visitor_base, + class Visitor = empty_visitor, class Compare = less>, class Combine = plus>> -requires is_arithmetic_v> && // - sized_range && // - basic_edge_weight_function, Compare, Combine> && // - bellman_visitor +requires is_arithmetic_v> && // + sized_range && // + basic_edge_weight_function, Compare, Combine> [[nodiscard]] constexpr optional> bellman_ford_shortest_distances( G&& g, const vertex_id_t source, Distances& distances, WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 - Visitor&& visitor = bellman_visitor_base(), + Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>()) { return bellman_ford_shortest_paths(g, subrange(&source, (&source + 1)), distances, _null_predecessors, diff --git a/include/graph/algorithm/common_shortest_paths.hpp b/include/graph/algorithm/common_shortest_paths.hpp index 1a6a3d0..e39817e 100644 --- a/include/graph/algorithm/common_shortest_paths.hpp +++ b/include/graph/algorithm/common_shortest_paths.hpp @@ -77,53 +77,57 @@ constexpr void init_shortest_paths(Distances& distances, Predecessors& predecess // // Visitor concepts and classes // + +// Vertex visitor concepts template concept has_on_initialize_vertex = // - requires(Visitor& v, vertex_descriptor, vertex_reference_t, void>& vdesc) { + requires(Visitor& v, vertex_descriptor, vertex_reference_t, void> vdesc) { { v.on_initialize_vertex(vdesc) }; }; template concept has_on_discover_vertex = // - requires(Visitor& v, vertex_descriptor, vertex_reference_t, void>& vdesc) { + requires(Visitor& v, vertex_descriptor, vertex_reference_t, void> vdesc) { { v.on_discover_vertex(vdesc) }; }; template concept has_on_examine_vertex = // - requires(Visitor& v, vertex_descriptor, vertex_reference_t, void>& vdesc) { + requires(Visitor& v, vertex_descriptor, vertex_reference_t, void> vdesc) { { v.on_examine_vertex(vdesc) }; }; template concept has_on_finish_vertex = // - requires(Visitor& v, vertex_descriptor, vertex_reference_t, void>& vdesc) { + requires(Visitor& v, vertex_descriptor, vertex_reference_t, void> vdesc) { { v.on_finish_vertex(vdesc) }; }; +// Edge visitor concepts template concept has_on_examine_edge = // - requires(Visitor& v, edge_descriptor, true, edge_reference_t, void>& edesc) { + requires(Visitor& v, edge_descriptor, true, edge_reference_t, void> edesc) { { v.on_examine_edge(edesc) }; }; template concept has_on_edge_relaxed = // - requires(Visitor& v, edge_descriptor, true, edge_reference_t, void>& edesc) { + requires(Visitor& v, edge_descriptor, true, edge_reference_t, void> edesc) { { v.on_edge_relaxed(edesc) }; }; template concept has_on_edge_not_relaxed = // - requires(Visitor& v, edge_descriptor, true, edge_reference_t, void>& edesc) { + requires(Visitor& v, edge_descriptor, true, edge_reference_t, void> edesc) { { v.on_edge_not_relaxed(edesc) }; }; template concept has_on_edge_minimized = // - requires(Visitor& v, edge_descriptor, true, edge_reference_t, void>& edesc) { + requires(Visitor& v, edge_descriptor, true, edge_reference_t, void> edesc) { { v.on_edge_minimized(edesc) }; }; template concept has_on_edge_not_minimized = - requires(Visitor& v, edge_descriptor, true, edge_reference_t, void>& edesc) { + requires(Visitor& v, edge_descriptor, true, edge_reference_t, void> edesc) { { v.on_edge_not_minimized(edesc) }; }; +// Visitor structs and classes struct empty_visitor {}; /** diff --git a/include/graph/algorithm/dijkstra_shortest_paths.hpp b/include/graph/algorithm/dijkstra_shortest_paths.hpp index d762394..c446acf 100644 --- a/include/graph/algorithm/dijkstra_shortest_paths.hpp +++ b/include/graph/algorithm/dijkstra_shortest_paths.hpp @@ -76,7 +76,7 @@ constexpr void dijkstra_shortest_paths( Distances& distances, Predecessors& predecessor, WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 - Visitor&& visitor = empty_visitor, + Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>()) { using id_type = vertex_id_t; diff --git a/tests/bellman_shortest_paths_tests.cpp b/tests/bellman_shortest_paths_tests.cpp index 15c8b69..c550199 100644 --- a/tests/bellman_shortest_paths_tests.cpp +++ b/tests/bellman_shortest_paths_tests.cpp @@ -31,35 +31,9 @@ using std::plus; using std::is_arithmetic_v; using std::optional; -using graph::index_adjacency_list; -using graph::edge_weight_function; -using graph::basic_edge_weight_function; - -using graph::vertex_t; -using graph::vertex_id_t; -using graph::vertex_edge_range_t; -using graph::edge_t; -using graph::edge_value_t; - -using graph::vertices; -using graph::find_vertex; -using graph::vertex_value; -using graph::edges; -using graph::target_id; -using graph::target; -using graph::edge_value; -using graph::num_vertices; -using graph::vertex_reference_t; -using graph::edge_reference_t; - +using namespace graph; using graph::views::vertexlist; -using graph::shortest_path_infinite_distance; -using graph::init_shortest_paths; -using graph::bellman_ford_shortest_paths; -using graph::bellman_ford_shortest_distances; -using graph::bellman_visitor_base; - using routes_volf_graph_traits = graph::container::vofl_graph_traits; using routes_volf_graph_type = graph::container::dynamic_adjacency_graph; @@ -117,6 +91,37 @@ auto to_string(const Predecessors& predecessors) { return pred; } +template +using visited_vertex_t = vertex_descriptor, vertex_reference_t, void>; +template +using visited_edge_t = edge_descriptor, true, edge_reference_t, void>; + +template +class empty_bellman_ford_visitor { + // Types +public: + using graph_type = G; + using vertex_desc_type = visited_vertex_t; + using sourced_edge_desc_type = visited_edge_t; + + // Visitor Functions +public: + empty_bellman_ford_visitor() = default; + + // vertex visitor functions + constexpr void on_initialize_vertex(vertex_desc_type& vdesc) {} + constexpr void on_discover_vertex(vertex_desc_type& vdesc) {} + constexpr void on_examine_vertex(vertex_desc_type& vdesc) {} + constexpr void on_finish_vertex(vertex_desc_type& vdesc) {} + + // edge visitor functions + constexpr void on_examine_edge(sourced_edge_desc_type& edesc) {} + constexpr void on_edge_relaxed(sourced_edge_desc_type& edesc) {} + constexpr void on_edge_not_relaxed(sourced_edge_desc_type& edesc) {} + constexpr void on_edge_minimized(sourced_edge_desc_type& edesc) {} + constexpr void on_edge_not_minimized(sourced_edge_desc_type& edesc) {} +}; + TEST_CASE("Bellman-Ford's Common Shortest Segments", "[csv][vofl][shortest][segments][bellman][common]") { init_console(); using G = routes_volf_graph_type; @@ -131,7 +136,7 @@ TEST_CASE("Bellman-Ford's Common Shortest Segments", "[csv][vofl][shortest][segm auto weight = [](edge_reference_t uv) -> double { return 1.0; }; #if 0 - //using V = graph::bellman_visitor_base; + //using V = graph::empty_visitor; //static_assert(graph::bellman_visitor, "Visitor doesn't match bellman_visitor requirements"); #endif @@ -433,7 +438,7 @@ TEST_CASE("Bellman-Ford's General Shortest Segments", "[csv][vofl][shortest][seg Predecessors predecessors(size(vertices(g))); init_shortest_paths(distance, predecessors); auto weight = [](edge_reference_t uv) -> double { return 1.0; }; - auto visitor = bellman_visitor_base(); + auto visitor = empty_visitor(); #if 0 using Visitor = decltype(visitor); @@ -574,7 +579,7 @@ TEST_CASE("Bellman-Ford's General Shortest Paths", "[csv][vofl][shortest][paths] vector> predecessors(size(vertices(g))); init_shortest_paths(distance, predecessors); auto weight = [&g](edge_reference_t uv) -> double { return edge_value(g, uv); }; - auto visitor = bellman_visitor_base(); + auto visitor = empty_visitor(); optional> cycle_vertex_id = bellman_ford_shortest_paths( g, frankfurt_id, distance, predecessors, weight, visitor, std::less(), std::plus()); @@ -702,7 +707,7 @@ TEST_CASE("Bellman-Ford's General Shortest Distances", "[csv][vofl][shortest][di vector distance(size(vertices(g))); init_shortest_paths(distance); auto weight = [&g](edge_reference_t uv) -> double { return edge_value(g, uv); }; - auto visitor = bellman_visitor_base(); + auto visitor = empty_visitor(); // This test case just tests that these will compile without error. The distances will be the same as before. //bellman_ford_shortest_distances(g, frankfurt_id, distance, std::less(), std::plus()); diff --git a/tests/dijkstra_shortest_paths_tests.cpp b/tests/dijkstra_shortest_paths_tests.cpp index 66dacf4..4da2da2 100644 --- a/tests/dijkstra_shortest_paths_tests.cpp +++ b/tests/dijkstra_shortest_paths_tests.cpp @@ -30,49 +30,9 @@ using std::less; using std::plus; using std::is_arithmetic_v; -using graph::index_adjacency_list; -using graph::edge_weight_function; -using graph::basic_edge_weight_function; - -using graph::vertex_t; -using graph::vertex_id_t; -using graph::vertex_edge_range_t; -using graph::edge_t; -using graph::edge_value_t; - -using graph::vertices; -using graph::find_vertex; -using graph::vertex_value; -using graph::edges; -using graph::target_id; -using graph::target; -using graph::edge_value; -using graph::num_vertices; -using graph::vertex_reference_t; -using graph::edge_reference_t; - +using namespace graph; using graph::views::vertexlist; -using graph::shortest_path_infinite_distance; -using graph::init_shortest_paths; -using graph::dijkstra_shortest_paths; -using graph::dijkstra_shortest_distances; - -using graph::vertex_descriptor; -using graph::edge_descriptor; - -using graph::empty_visitor; -using graph::has_on_initialize_vertex; -using graph::has_on_discover_vertex; -using graph::has_on_examine_vertex; -using graph::has_on_finish_vertex; - -using graph::has_on_examine_edge; -using graph::has_on_edge_relaxed; -using graph::has_on_edge_not_relaxed; -using graph::has_on_edge_minimized; -using graph::has_on_edge_not_minimized; - using routes_volf_graph_traits = graph::container::vofl_graph_traits; using routes_volf_graph_type = graph::container::dynamic_adjacency_graph; diff --git a/tests/visitor_dijkstra_tests.cpp b/tests/visitor_dijkstra_tests.cpp index 79fbc8b..1fc77ec 100644 --- a/tests/visitor_dijkstra_tests.cpp +++ b/tests/visitor_dijkstra_tests.cpp @@ -41,7 +41,6 @@ using graph::edge_value; using graph::degree; using graph::find_vertex; using graph::find_vertex_edge; -using graph::experimental::dijkstra_visitor_base; using graph::experimental::dijkstra_with_visitor; using graph::experimental::init_shortest_paths; @@ -62,7 +61,7 @@ auto find_frankfurt(G&& g) { return find_city(g, "Frankf\xC3\xBCrt"); } -struct my_dijkstra_visitor : dijkstra_visitor_base { +struct my_dijkstra_visitor : graph::experimental::dijkstra_visitor_base { using G = routes_vol_graph_type; using base_t = dijkstra_visitor_base;