Skip to content

Commit

Permalink
Use reflection for visitors on bellman-ford algos
Browse files Browse the repository at this point in the history
  • Loading branch information
pratzl committed Sep 12, 2024
1 parent 3cc26e4 commit cdd24ac
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 150 deletions.
114 changes: 48 additions & 66 deletions include/graph/algorithm/bellman_ford_shortest_paths.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,6 @@

namespace graph {

template <adjacency_list G>
class bellman_visitor_base {
// Types
public:
using graph_type = G;
using vertex_desc_type = vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>;
using sourced_edge_desc_type = edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, 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 <class G, class Visitor>
concept bellman_visitor = requires(Visitor& v,
remove_reference_t<Visitor>::vertex_desc_type& vdesc,
remove_reference_t<Visitor>::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.
*
Expand Down Expand Up @@ -126,23 +97,22 @@ template <index_adjacency_list G,
random_access_range Distances,
random_access_range Predecessors,
class WF = function<range_value_t<Distances>(edge_reference_t<G>)>,
class Visitor = bellman_visitor_base<G>,
class Visitor = empty_visitor,
class Compare = less<range_value_t<Distances>>,
class Combine = plus<range_value_t<Distances>>>
requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
is_arithmetic_v<range_value_t<Distances>> && //
convertible_to<vertex_id_t<G>, range_value_t<Predecessors>> && //
sized_range<Distances> && //
sized_range<Predecessors> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine> && //
bellman_visitor<G, Visitor>
requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
is_arithmetic_v<range_value_t<Distances>> && //
convertible_to<vertex_id_t<G>, range_value_t<Predecessors>> && //
sized_range<Distances> && //
sized_range<Predecessors> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine>
[[nodiscard]] constexpr optional<vertex_id_t<G>> bellman_ford_shortest_paths(
G&& g,
const Sources& sources,
Distances& distances,
Predecessors& predecessor,
WF&& weight = [](edge_reference_t<G> uv) { return range_value_t<Distances>(1); }, // default weight(uv) -> 1
Visitor&& visitor = bellman_visitor_base<G>(),
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<range_value_t<Distances>>(),
Combine&& combine = plus<range_value_t<Distances>>()) {
using id_type = vertex_id_t<G>;
Expand Down Expand Up @@ -193,18 +163,27 @@ requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> &&
fmt::format("bellman_ford_shortest_paths: source vertex id '{}' is out of range", source));
}
distances[static_cast<size_t>(source)] = zero; // mark source as discovered
if constexpr (has_on_discover_vertex<G, Visitor>) {
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<G, Visitor>) {
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<G, Visitor>) {
visitor.on_edge_relaxed({uid, vid, uv});
}
} else if constexpr (has_on_edge_not_relaxed<G, Visitor>) {
visitor.on_edge_not_relaxed({uid, vid, uv});
}
}
if (!at_least_one_edge_relaxed)
break;
Expand All @@ -214,13 +193,17 @@ requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> &&
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<Predecessors, _null_range_type>) {
predecessor[vid] = uid; // close the cycle
}
if constexpr (has_on_edge_not_minimized<G, Visitor>) {
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<G, Visitor>) {
visitor.on_edge_minimized({uid, vid, uv});
}
}
}
}
Expand All @@ -232,26 +215,26 @@ template <index_adjacency_list G,
random_access_range Distances,
random_access_range Predecessors,
class WF = function<range_value_t<Distances>(edge_reference_t<G>)>,
class Visitor = bellman_visitor_base<G>,
class Visitor = empty_visitor,
class Compare = less<range_value_t<Distances>>,
class Combine = plus<range_value_t<Distances>>>
requires is_arithmetic_v<range_value_t<Distances>> && //
convertible_to<vertex_id_t<G>, range_value_t<Predecessors>> && //
sized_range<Distances> && //
sized_range<Predecessors> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine> && //
bellman_visitor<G, Visitor>
requires is_arithmetic_v<range_value_t<Distances>> && //
convertible_to<vertex_id_t<G>, range_value_t<Predecessors>> && //
sized_range<Distances> && //
sized_range<Predecessors> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine>
[[nodiscard]] constexpr optional<vertex_id_t<G>> bellman_ford_shortest_paths(
G&& g,
const vertex_id_t<G> source,
Distances& distances,
Predecessors& predecessor,
WF&& weight = [](edge_reference_t<G> uv) { return range_value_t<Distances>(1); }, // default weight(uv) -> 1
Visitor&& visitor = bellman_visitor_base<G>(),
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<range_value_t<Distances>>(),
Combine&& combine = plus<range_value_t<Distances>>()) {
return bellman_ford_shortest_paths(g, subrange(&source, (&source + 1)), distances, predecessor, weight,
forward<Visitor>(visitor), forward<Compare>(compare), forward<Combine>(combine));
forward<Visitor>(visitor), forward<Compare>(compare),
forward<Combine>(combine));
}


Expand Down Expand Up @@ -289,42 +272,41 @@ template <index_adjacency_list G,
input_range Sources,
random_access_range Distances,
class WF = function<range_value_t<Distances>(edge_reference_t<G>)>,
class Visitor = bellman_visitor_base<G>,
class Visitor = empty_visitor,
class Compare = less<range_value_t<Distances>>,
class Combine = plus<range_value_t<Distances>>>
requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
is_arithmetic_v<range_value_t<Distances>> && //
sized_range<Distances> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine> && //
bellman_visitor<G, Visitor>
requires convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
is_arithmetic_v<range_value_t<Distances>> && //
sized_range<Distances> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine>
[[nodiscard]] constexpr optional<vertex_id_t<G>> bellman_ford_shortest_distances(
G&& g,
const Sources& sources,
Distances& distances,
WF&& weight = [](edge_reference_t<G> uv) { return range_value_t<Distances>(1); }, // default weight(uv) -> 1
Visitor&& visitor = bellman_visitor_base<G>(),
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<range_value_t<Distances>>(),
Combine&& combine = plus<range_value_t<Distances>>()) {
return bellman_ford_shortest_paths(g, sources, distances, _null_predecessors, forward<WF>(weight),
forward<Visitor>(visitor), forward<Compare>(compare), forward<Combine>(combine));
forward<Visitor>(visitor), forward<Compare>(compare),
forward<Combine>(combine));
}

template <index_adjacency_list G,
random_access_range Distances,
class WF = function<range_value_t<Distances>(edge_reference_t<G>)>,
class Visitor = bellman_visitor_base<G>,
class Visitor = empty_visitor,
class Compare = less<range_value_t<Distances>>,
class Combine = plus<range_value_t<Distances>>>
requires is_arithmetic_v<range_value_t<Distances>> && //
sized_range<Distances> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine> && //
bellman_visitor<G, Visitor>
requires is_arithmetic_v<range_value_t<Distances>> && //
sized_range<Distances> && //
basic_edge_weight_function<G, WF, range_value_t<Distances>, Compare, Combine>
[[nodiscard]] constexpr optional<vertex_id_t<G>> bellman_ford_shortest_distances(
G&& g,
const vertex_id_t<G> source,
Distances& distances,
WF&& weight = [](edge_reference_t<G> uv) { return range_value_t<Distances>(1); }, // default weight(uv) -> 1
Visitor&& visitor = bellman_visitor_base<G>(),
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<range_value_t<Distances>>(),
Combine&& combine = plus<range_value_t<Distances>>()) {
return bellman_ford_shortest_paths(g, subrange(&source, (&source + 1)), distances, _null_predecessors,
Expand Down
22 changes: 13 additions & 9 deletions include/graph/algorithm/common_shortest_paths.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,53 +77,57 @@ constexpr void init_shortest_paths(Distances& distances, Predecessors& predecess
//
// Visitor concepts and classes
//

// Vertex visitor concepts
template <class G, class Visitor>
concept has_on_initialize_vertex = //
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>& vdesc) {
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void> vdesc) {
{ v.on_initialize_vertex(vdesc) };
};
template <class G, class Visitor>
concept has_on_discover_vertex = //
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>& vdesc) {
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void> vdesc) {
{ v.on_discover_vertex(vdesc) };
};
template <class G, class Visitor>
concept has_on_examine_vertex = //
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>& vdesc) {
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void> vdesc) {
{ v.on_examine_vertex(vdesc) };
};
template <class G, class Visitor>
concept has_on_finish_vertex = //
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>& vdesc) {
requires(Visitor& v, vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void> vdesc) {
{ v.on_finish_vertex(vdesc) };
};

// Edge visitor concepts
template <class G, class Visitor>
concept has_on_examine_edge = //
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>& edesc) {
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void> edesc) {
{ v.on_examine_edge(edesc) };
};
template <class G, class Visitor>
concept has_on_edge_relaxed = //
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>& edesc) {
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void> edesc) {
{ v.on_edge_relaxed(edesc) };
};
template <class G, class Visitor>
concept has_on_edge_not_relaxed = //
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>& edesc) {
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void> edesc) {
{ v.on_edge_not_relaxed(edesc) };
};
template <class G, class Visitor>
concept has_on_edge_minimized = //
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>& edesc) {
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void> edesc) {
{ v.on_edge_minimized(edesc) };
};
template <class G, class Visitor>
concept has_on_edge_not_minimized =
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>& edesc) {
requires(Visitor& v, edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void> edesc) {
{ v.on_edge_not_minimized(edesc) };
};

// Visitor structs and classes
struct empty_visitor {};

/**
Expand Down
2 changes: 1 addition & 1 deletion include/graph/algorithm/dijkstra_shortest_paths.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ constexpr void dijkstra_shortest_paths(
Distances& distances,
Predecessors& predecessor,
WF&& weight = [](edge_reference_t<G> uv) { return range_value_t<Distances>(1); }, // default weight(uv) -> 1
Visitor&& visitor = empty_visitor,
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<range_value_t<Distances>>(),
Combine&& combine = plus<range_value_t<Distances>>()) {
using id_type = vertex_id_t<G>;
Expand Down
67 changes: 36 additions & 31 deletions tests/bellman_shortest_paths_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double, std::string>;
using routes_volf_graph_type = graph::container::dynamic_adjacency_graph<routes_volf_graph_traits>;

Expand Down Expand Up @@ -117,6 +91,37 @@ auto to_string(const Predecessors& predecessors) {
return pred;
}

template <typename G>
using visited_vertex_t = vertex_descriptor<vertex_id_t<G>, vertex_reference_t<G>, void>;
template <typename G>
using visited_edge_t = edge_descriptor<vertex_id_t<G>, true, edge_reference_t<G>, void>;

template <index_adjacency_list G>
class empty_bellman_ford_visitor {
// Types
public:
using graph_type = G;
using vertex_desc_type = visited_vertex_t<G>;
using sourced_edge_desc_type = visited_edge_t<G>;

// 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;
Expand All @@ -131,7 +136,7 @@ TEST_CASE("Bellman-Ford's Common Shortest Segments", "[csv][vofl][shortest][segm
auto weight = [](edge_reference_t<G> uv) -> double { return 1.0; };

#if 0
//using V = graph::bellman_visitor_base<G>;
//using V = graph::empty_visitor;
//static_assert(graph::bellman_visitor<G, V>, "Visitor doesn't match bellman_visitor requirements");
#endif

Expand Down Expand Up @@ -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<G> uv) -> double { return 1.0; };
auto visitor = bellman_visitor_base<G>();
auto visitor = empty_visitor();

#if 0
using Visitor = decltype(visitor);
Expand Down Expand Up @@ -574,7 +579,7 @@ TEST_CASE("Bellman-Ford's General Shortest Paths", "[csv][vofl][shortest][paths]
vector<vertex_id_t<G>> predecessors(size(vertices(g)));
init_shortest_paths(distance, predecessors);
auto weight = [&g](edge_reference_t<G> uv) -> double { return edge_value(g, uv); };
auto visitor = bellman_visitor_base<G>();
auto visitor = empty_visitor();

optional<vertex_id_t<G>> cycle_vertex_id = bellman_ford_shortest_paths(
g, frankfurt_id, distance, predecessors, weight, visitor, std::less<Distance>(), std::plus<Distance>());
Expand Down Expand Up @@ -702,7 +707,7 @@ TEST_CASE("Bellman-Ford's General Shortest Distances", "[csv][vofl][shortest][di
vector<double> distance(size(vertices(g)));
init_shortest_paths(distance);
auto weight = [&g](edge_reference_t<G> uv) -> double { return edge_value(g, uv); };
auto visitor = bellman_visitor_base<G>();
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<Distance>(), std::plus<Distance>());
Expand Down
Loading

0 comments on commit cdd24ac

Please sign in to comment.