From 64efcc73950fad401812c8f394458ba8596a6bb0 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Thu, 5 Jun 2025 23:52:43 +0200 Subject: [PATCH 01/20] docs: initial documentation This is an initial docs page. It is very incomplete as of now, but it already offers a useful introduction to the library. --- doc/Identifiers.docx | Bin 17476 -> 0 bytes doc/algorithms.md | 88 +++++++++++++++++ doc/customization_points.md | 111 +++++++++++++++++++++ doc/overview.md | 192 ++++++++++++++++++++++++++++++++++++ 4 files changed, 391 insertions(+) delete mode 100644 doc/Identifiers.docx create mode 100644 doc/algorithms.md create mode 100644 doc/customization_points.md create mode 100644 doc/overview.md diff --git a/doc/Identifiers.docx b/doc/Identifiers.docx deleted file mode 100644 index 87a18631abaebe92a06fc255092e5e0afdeae968..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17476 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 diff --git a/doc/algorithms.md b/doc/algorithms.md new file mode 100644 index 0000000..0e215af --- /dev/null +++ b/doc/algorithms.md @@ -0,0 +1,88 @@ +# Algorithms + +## Definitions + +A graph path _p_ is a possibly empty sequence of graph edges (_e_0, _e_1, ..., _e__N_) where: + * _e__i_ β‰  _e__j_ for _i_ β‰  _j_, + * target(_e__i_) = source(_e__i_+1), + * source(_e__i_) != source(_e__j_) for _i_ β‰  _j_. + +path-source(p) = source(_e_0). path-target(p) = target(_e__N_). + +distance(p) is a sum over _i_ of weight(_e__i_). + +shortest-path(g, u, v) is a path in the set of all paths `p` in graph `g` with path-source(p) = `u` +and path-target(p) = v that has the smallest value of distance(p). + +shortest-path-distance(g, u, v) is distance(shortest-path(g, u, v)) if it exists and _infinite-distance_ otherwise. + +shortest-path-predecessor(g, u, v), in the set of all shortest paths shortest-path(g, u, v) for any `v`: + * if there exists an edge _e_ with target(_e_) = v, then it is source(_e_), + * otherwise it is `v`. + +## Visitors + +TODO: explain the _GraphVisitor_ requirements + +TODO: list and describe all possible visitation events + + +## `dijkstra_shortest_paths` (single source) + +Header `` + +```c++ +template (edge_reference_t)>, + class Visitor = empty_visitor, + class Compare = less>, + class Combine = plus>> +constexpr void dijkstra_shortest_distances( + G&& g, + 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 = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()); +``` +*Constraints:* + * `index_adjacency_list` is `true`, + * `std::ranges::random_access_range` is `true`, + * `std::ranges::sized_range` is `true`, + * `std::ranges::random_access_range` is `true`, + * `std::ranges::sized_range` is `true`, + * `std::is_arithmetic_v>` is `true`, + * `std::convertible_to, std::ranges::range_value_t>` is `true`, + * `basic_edge_weight_function, Compare, Combine>` is `true`. + +*Preconditions:* + * distances[i] == shortest_path_infinite_distance<range_value_t<Distances>>() for each i in range [`0`; `num_vertices(g)`), + * predecessor[i] == i for each i in range [`0`; `num_vertices(g)`), + * `weight` returns non-negative values. + * `visitor` adheres to the _GraphVisitor_ requirements. + +*Hardened preconditions:* + * `0 <= source && source < num_vertices(g)` is `true`, + * `std::size(distances) >= num_vertices(g)` is `true`, + * `std::size(predecessor) >= num_vertices(g)` is `true`. + +*Effects:* Supports the following visitation events: `on_initialize_vertex`, `on_discover_vertex`, + `on_examine_vertex`, `on_finish_vertex`, `on_examine_edge`, `on_edge_relaxed`, and `on_edge_not_relaxed`. + +*Postconditions:* For each i in range [`0`; `num_vertices(g)`): + * distances[i] is shortest-path-distance(g, source, i), + * predecessor[i] is shortest-path-predecessor(g, source, i). + +*Throws:* `std::bad_alloc` if memory for the internal data structures cannot be allocated. + +*Complexity:* Either π’ͺ((|_E_| + |_V_|)β‹…log |_V_|) or π’ͺ(|_E_| + |_V_|β‹…log |_V_|), depending on the implementation. + +*Remarks:* Duplicate sources do not affect the algorithm’s complexity or correctness. + +## TODO + +Document all other algorithms... diff --git a/doc/customization_points.md b/doc/customization_points.md new file mode 100644 index 0000000..239f258 --- /dev/null +++ b/doc/customization_points.md @@ -0,0 +1,111 @@ +# Customization Points + +The algorithms and views in this library operate on graph representations via _Customization Point Objects_ (CPO). +A user-defined graph representation `G` is adapted for use with this library by making sure that the necessary CPOs are _valid_ for `G`. + +Each customization point specifies individually what it takes to make it valid. +A customization point can be made valid in a number of ways. +For each customization point we provide an ordered list of ways in which it can be made valid. The order in this list matters: the match for validity is performed in order and if a given customization is determined to be valid, the subsequent ways, even if they would be valid, are ignored. + +Often, the last item from the list serves the purpose of a "fallback" or "default" customization. + +If none of the customization ways is valid for a given type, or set of types, the customization point is considered _invalid_ for this set of types. +The property or being valid or invalid can be statically tested in the program via SFINAE (like `enable_if`) tricks or `requires`-expressions. + +All the customization points in this library are defined in namespace `::graph` and brought into the program code via including header ``. + +## The list of customization points + +We use the following notation to represent the customization points: + + +| Symbol | Type | Meaning | +|--------|--------------------------------|------------------------------------------| +| `G` | | the type of the graph representation | +| `g` | `G` | the graph representation | +| `u` | `graph::vertex_reference_t` | vertex in `g` | +| `ui` | `graph::vertex_iterator_t` | iterator to a vertex in `g` | +| `uid` | `graph::vertex_id_t` | _id_ of a vertex in `g` (often an index) | +| `uv` | `graph::edge_reference_t` | an edge in `g` | + + +### `vertices` + +The CPO `vertices(g)` is used to obtain the list of all vertices, in form of a `std::ranges::random_access_range`, from the graph-representing object `g`. +We also use its return type to determine the type of the vertex: `vertex_t`. + +#### Customization + + 1. Returns `g.vertices()`, if such member function exists and returns a `std::move_constructible` type. + 2. Returns `vertices(g)`, if such function is ADL-discoverable and returns a `std::move_constructible` type. + 3. Returns `g`, if it is a `std::ranges::random_access_range`. + + +### `vertex_id` + +The CPO `vertex_id(g, ui)` is used obtain the _id_ of the vertex, given the iterator. +We also use its return type to determine the type of the vertex id: `vertex_id_t`. + +#### Coustomization + + 1. Returns `ui->vertex_id(g)`, if this expression is valid and its type is `std::move_constructible`. + 2. Returns `vertex_id(g, ui)`, if this expression is valid and its type is `std::move_constructible`. + 3. Returns static_cast<vertex-id-t<G>>(ui - begin(vertices(g))), + if `std::ranges::random_access_range>` is `true`, where vertex-id-t is defined as: + + * `I`, when the type of `G` matches pattern `ranges::forward_list>` and `I` is `std::integral`, + * `I0`, when the type of `G` matches pattern ranges::forward_list<ranges::forward_list<tuple-like<I0, ...>>> and `I0` is `std::integral`, + * `std::size_t` otherwise. + +### `find_vertex` + +TODO `find_vertex(g, uid)` + +### `edges(g, u)` + +### `edges(g, uid)` + +### `num_edges(g)` + +### `target_id(g, uv)` + +### `target_id(e)` + +### `source_id(g, uv)` + +### `source_id(e)` + +### `target(g, uv)` + +### `source(g, uv)` + +### `find_vertex_edge(g, u, vid)` + +### `find_vertex_edge(g, uid, vid)` + +### `contains_edge(g, uid, vid)` + +### `partition_id(g, u)` + +### `partition_id(g, uid)` + +### `num_vertices(g, pid)` + +### `num_vertices(g)` + +### `degree(g, u)` + +### `degree(g, uid)` + +### `vertex_value(g, u)` + +### `edge_value(g, uv)` + +### `edge_value(e)` + +### `graph_value(g)` + +### `num_partitions(g)` + +### `has_edge(g)` + diff --git a/doc/overview.md b/doc/overview.md new file mode 100644 index 0000000..91ab4ab --- /dev/null +++ b/doc/overview.md @@ -0,0 +1,192 @@ +# Overview + +What this library has to offer: + + 1. A number of ready-to-use algoritms, like Dijkstra's shortest paths computation. + 2. Make it easier for you to write your own graph algorithms: we provide a _view_ for traversing your graph in the preferred order (depth-first, breadth-first, topological), and you specify what is processed in each traversal step. + 3. Customization of your graph representation, so that it can be used with our algorithms and views. + 4. Our own container for representing a graph. + +## Concepts + +This is a _generic_ library. Graph algorithms operate on various graph representations through a well defined interface: a concept. The primary concept in this library is `index_adjacency_list`. + +```c++ +#include // (1) + +static_assert(graph::index_adjacency_list); // (2) (3) +``` +Notes: + 1. Graph concepts are defined in header ``. + 2. All declarations reside in namespace `::graph`. + 3. Use concept `graph::index_adjacency_list` to test if your type satisfies the syntactic requirements of an index adjacency list. + +The graph representation that is most commonly used in this library is [_adjacency list_](https://en.wikipedia.org/wiki/Adjacency_list). +Very conceptually, we call it a random access range (corresponding to vertices) of forward ranges (corresponding to outgoing edges of a vertex). + +This representation allows the algorithms to: + + 1. Perform an iteration over vertices, and to count them. + 2. For each vertex, perform an iteration over its outgoing edges. + 3. For each edge to to look up its target vertex in constant time. + +Algorithms in this library express the requirements on the adjacency list representations via concept `graph::index_adjacency_list`. +This concept expresses its syntactic requirments mostly via [_customization points_](./customization_points.md). +We use the following notation to represent the constraints: + +| Symbol | Meaning | +|--------|---------------------------------------------------------| +| `G` | the type of the graph representation | +| `g` | lvalue or lvalue reference of type `G` | +| `u` | lvalue reference of type `graph::vertex_reference_t` | +| `ui` | value of type `graph::vertex_iterator_t` | +| `uid` | value of type `graph::vertex_id_t` | +| `uv` | lvalue reference of type `graph::edge_reference_t` | + +### Random access to vertices + +Customization point `graph::vertices(g)` must be valid and its return type must satisfy type-requirements `std::ranges::sized_range` and `std::ranges::random_access_range`. + +The algorithms will use it to access the vertices of graph represented by `g` in form of a random-access range. + +Customization point `graph::vertex_id(g, ui)` must be valid and its return type must satisfy type-requirements `std::integral`. +The algorithms will use this function to convert the iterator pointing to a vertex to the _id_ of the vertex. + + +### Forward access to target edges + +The following customization points must be valid and their return type shall satisfy type requirements `std::ranges::forward_range`: + + * `graph::edges(g, uid)`, + * `graph::edges(g, u)`. + +The algorithms will use this function to iterate over out edges of the vertex represended by either `uid` or `u`. + + +### Linking from target edges back to vertices + +Customization point `graph::target_id(g, uv)` must be valid and its return type must satisfy type-requirements `std::integral`. + +The algorithms will use this value to access a vertex in `graph::vertices(g)`. +Therefore we have a _semantic_ constraint: that the look up of the value returned from `graph::target_id(g, uv)` returns value `uid` that satisfies the condition +`0 <= uid && uid < graph::num_vertices(g)`. + + +### Associated types + +Based on the customization points the library provides a number of associated types in namespace `graph`: + +| Associated type | Definition | +|-----------------------------|----------------------------------------------------------| +| `vertex_range_t` | `decltype(graph::vertices(g))` | +| `vertex_iterator_t` | `std::ranges::iterator_t>` | +| `vertex_t` | `std::ranges::range_value_t>` | +| `vertex_reference_t` | `std::ranges::range_reference_t>` | +| `vertex_id_t` | `decltype(graph::vertex_id(g, ui))` | +| `vertex_edge_range_t` | `decltype(graph::edges(g, u))` | +| `vertex_edge_iterator_t` | `std::ranges::iterator_t>` | +| `edge_t` | `std::ranges::range_value_t>` | +| `edge_reference_t` | `std::ranges::range_reference_t>` | + + +## Views + +This library can help you write your own algorithms via _views_ that represent graph traversal patterns as C++ ranges. + +Suppose, your task is to compute the distance, in edges, from a given vertex _u_ in your graph _g_, to all vertices in _g_. +We will represent the adjacency list as a vector of vectors: + + +```c++ +std::vector> g { // + /*0*/ {1, 3}, // + /*1*/ {0, 2, 4}, // (0) ----- (1) ----- (2) + /*2*/ {1, 5}, // | | | + /*3*/ {0, 4}, // | | | + /*4*/ {1, 3}, // | | | + /*5*/ {2, 6}, // (3) ----- (4) (5) ----- (6) + /*6*/ {5} // +}; // +``` + +The algorithm is simple: you start by assigning value zero to the start vertex, +then go through all the graph edges in the breadth-first order and for each edge (_u_, _v_) +you will assign the value for _v_ as the value for _u_ plus 1. + +In order to do this, you can employ a depth-first search view from this library: + +```c++ +#include + +int main() +{ + std::vector distances(g.size(), 0); // fill with zeros + + for (auto const& [uid, vid, _] : graph::views::sourced_edges_breadth_first_search(g, 0)) + distances[vid] = distances[uid] + 1; + + assert((distances == std::vector{0, 1, 2, 1, 2, 3, 4})); +} +``` + +## Algorithms + +This library offers also full fledged algorithms. +However, due to the nature of graph algorithms, the way they communicate the results requires some additional code. +Let's, reuse the same graph topology, but use a different adjacency-list representation that is also able to store a weight for each edge: + +```c++ +std::vector>> g { + /*0*/ {{(1), 9.1}, {(3), 1.1} }, // 9.1 2.2 + /*1*/ {{(0), 9.1}, {(2), 2.2}, {(4), 3.5}}, // (0)-------(1)-------(2) + /*2*/ {{(1), 2.2}, {(5), 1.0} }, // | | | + /*3*/ {{(0), 1.1}, {(4), 2.0} }, // |1.1 |3.5 |1.0 + /*4*/ {{(1), 3.5}, {(3), 2.0} }, // | 2.0 | | 0.5 + /*5*/ {{(2), 1.0}, {(6), 0.5} }, // (3)-------(4) (5)-------(6) + /*6*/ {{(5), 0.5}} // +}; +``` + +Now, let's use Dijkstra's Shortest Paths algorithm to determine the distance from vertex `0` to each vertex in `g`, and also to determine these paths. The pathst are not obtained directly, but instead the list of predecessors is returned for each vertex: + +```c++ +auto weight = [](std::tuple const& uv) { + return std::get<1>(uv); +}; + +std::vector predecessors(g.size()); // we will store the predecessor of each vertex here + +std::vector distances(g.size()); // we will store the distance to each vertex here +graph::init_shortest_paths(distances); // fill with `infinity` + +graph::dijkstra_shortest_paths(g, 0, distances, predecessors, weight); // from vertex 0 + +assert((distances == std::vector{0.0, 6.6, 8.8, 1.1, 3.1, 9.8, 10.3})); +assert((predecessors == std::vector{0, 4, 1, 0, 3, 2, 5})); +``` + +If you need to know the sequence of vertices in the path from, say, `0` to `5`, you have to compute it yourself: + +```c++ +auto path = [&predecessors](int from, int to) +{ + std::vector result; + for (; to != from; to = predecessors[to]) { + assert(to < predecessors.size()); + result.push_back(to); + } + std::reverse(result.begin(), result.end()); + return result; +}; + +assert((path(0, 5) == std::vector{3, 4, 1, 2, 5})); +``` +The algorithms in this library are described in section [Algorithms](./algorithms.md). + + +------ + +TODO: + +- Adapting +- use our container From 794d9982a7d54fb9366ea2c44554c39c50c589a9 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Wed, 11 Jun 2025 01:53:25 +0200 Subject: [PATCH 02/20] docs: add graph container to the Overview --- doc/overview.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/doc/overview.md b/doc/overview.md index 91ab4ab..2dd0efa 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -184,9 +184,45 @@ assert((path(0, 5) == std::vector{3, 4, 1, 2, 5})); The algorithms in this library are described in section [Algorithms](./algorithms.md). + +## Containers + +This library comes with an efficient graph container that uses +[Compressed Sparse Row](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_%28CSR%2C_CRS_or_Yale_format%29) +(CSR) format: `compressed_graph`. + +```c++ +#include + +int main() +{ + std::vector> ve { + {0, 1, 0.8}, {0, 2, 0.4}, {1, 3, 0.1}, {2, 3, 0.2} // edges with weights + }; + + std::vector> vv { + {0, "A"}, {1, "B"}, {2, "C"}, {3, "D"} // vertices with names + }; + + using graph_t = graph::container::compressed_graph< + double, // type of edge value + std::string // type of vertex value + >; + + const graph_t g(ve, vv); + + std::vector vertex_names; + + for (graph_t::vertex_type const& u : vertices(g)) // use graph interface + vertex_names.push_back(vertex_value(g, u)); // + + assert((vertex_names == std::vector{"A", "B", "C", "D"})); +} +``` + + ------ TODO: - Adapting -- use our container From 8d82a3719d6fc5bec6e815753425c1afc199acbb Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Tue, 17 Jun 2025 11:00:23 +0300 Subject: [PATCH 03/20] use constraints in algo declaration --- doc/algorithms.md | 32 ++++++++++++++------------------ doc/overview.md | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/doc/algorithms.md b/doc/algorithms.md index 0e215af..bf425f9 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -32,32 +32,28 @@ TODO: list and describe all possible visitation events Header `` ```c++ -template (edge_reference_t)>, +template (edge_reference_t)>, class Visitor = empty_visitor, - class Compare = less>, - class Combine = plus>> + class Compare = less>, + class Combine = plus>> +requires std::is_arithmetic_v> && + std::ranges::sized_range && + std::ranges::sized_range && + convertible_to, std::ranges::range_value_t> && + basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_distances( G&& g, vertex_id_t source, Distances& distances, Predecessors& predecessor, - WF&& weight = [](edge_reference_t uv) { return range_value_t(1); }, // default weight(uv) -> 1 + WF&& weight = [](edge_reference_t uv) { return std::ranges::range_value_t(1); }, Visitor&& visitor = empty_visitor(), - Compare&& compare = less>(), - Combine&& combine = plus>()); + Compare&& compare = less>(), + Combine&& combine = plus>()); ``` -*Constraints:* - * `index_adjacency_list` is `true`, - * `std::ranges::random_access_range` is `true`, - * `std::ranges::sized_range` is `true`, - * `std::ranges::random_access_range` is `true`, - * `std::ranges::sized_range` is `true`, - * `std::is_arithmetic_v>` is `true`, - * `std::convertible_to, std::ranges::range_value_t>` is `true`, - * `basic_edge_weight_function, Compare, Combine>` is `true`. *Preconditions:* * distances[i] == shortest_path_infinite_distance<range_value_t<Distances>>() for each i in range [`0`; `num_vertices(g)`), diff --git a/doc/overview.md b/doc/overview.md index 2dd0efa..a20cfc6 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -2,7 +2,7 @@ What this library has to offer: - 1. A number of ready-to-use algoritms, like Dijkstra's shortest paths computation. + 1. A number of ready-to-use algorithms, like Dijkstra's shortest paths computation. 2. Make it easier for you to write your own graph algorithms: we provide a _view_ for traversing your graph in the preferred order (depth-first, breadth-first, topological), and you specify what is processed in each traversal step. 3. Customization of your graph representation, so that it can be used with our algorithms and views. 4. Our own container for representing a graph. @@ -31,7 +31,7 @@ This representation allows the algorithms to: 3. For each edge to to look up its target vertex in constant time. Algorithms in this library express the requirements on the adjacency list representations via concept `graph::index_adjacency_list`. -This concept expresses its syntactic requirments mostly via [_customization points_](./customization_points.md). +This concept expresses its syntactic requirements mostly via [_customization points_](./customization_points.md). We use the following notation to represent the constraints: | Symbol | Meaning | @@ -60,7 +60,7 @@ The following customization points must be valid and their return type shall sat * `graph::edges(g, uid)`, * `graph::edges(g, u)`. -The algorithms will use this function to iterate over out edges of the vertex represended by either `uid` or `u`. +The algorithms will use this function to iterate over out edges of the vertex represented by either `uid` or `u`. ### Linking from target edges back to vertices @@ -137,17 +137,17 @@ Let's, reuse the same graph topology, but use a different adjacency-list represe ```c++ std::vector>> g { - /*0*/ {{(1), 9.1}, {(3), 1.1} }, // 9.1 2.2 - /*1*/ {{(0), 9.1}, {(2), 2.2}, {(4), 3.5}}, // (0)-------(1)-------(2) - /*2*/ {{(1), 2.2}, {(5), 1.0} }, // | | | - /*3*/ {{(0), 1.1}, {(4), 2.0} }, // |1.1 |3.5 |1.0 - /*4*/ {{(1), 3.5}, {(3), 2.0} }, // | 2.0 | | 0.5 - /*5*/ {{(2), 1.0}, {(6), 0.5} }, // (3)-------(4) (5)-------(6) - /*6*/ {{(5), 0.5}} // + /*0*/ { {(1), 9.1}, {(3), 1.1} }, // 9.1 2.2 + /*1*/ { {(0), 9.1}, {(2), 2.2}, {(4), 3.5} }, // (0)-------(1)-------(2) + /*2*/ { {(1), 2.2}, {(5), 1.0} }, // | | | + /*3*/ { {(0), 1.1}, {(4), 2.0} }, // |1.1 |3.5 |1.0 + /*4*/ { {(1), 3.5}, {(3), 2.0} }, // | 2.0 | | 0.5 + /*5*/ { {(2), 1.0}, {(6), 0.5} }, // (3)-------(4) (5)-------(6) + /*6*/ { {(5), 0.5} } // }; ``` -Now, let's use Dijkstra's Shortest Paths algorithm to determine the distance from vertex `0` to each vertex in `g`, and also to determine these paths. The pathst are not obtained directly, but instead the list of predecessors is returned for each vertex: +Now, let's use Dijkstra's Shortest Paths algorithm to determine the distance from vertex `0` to each vertex in `g`, and also to determine these paths. The paths are not obtained directly, but instead the list of predecessors is returned for each vertex: ```c++ auto weight = [](std::tuple const& uv) { From ed24215e284d7e6924816f4951cf5ebb0d7ef426 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Wed, 18 Jun 2025 00:31:11 +0300 Subject: [PATCH 04/20] start documenting views --- doc/customization_points.md | 8 +- doc/views.md | 170 ++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 doc/views.md diff --git a/doc/customization_points.md b/doc/customization_points.md index 239f258..d17bb86 100644 --- a/doc/customization_points.md +++ b/doc/customization_points.md @@ -3,9 +3,13 @@ The algorithms and views in this library operate on graph representations via _Customization Point Objects_ (CPO). A user-defined graph representation `G` is adapted for use with this library by making sure that the necessary CPOs are _valid_ for `G`. +A CPO is a function object, so it can be passed as an argument to functions. + Each customization point specifies individually what it takes to make it valid. A customization point can be made valid in a number of ways. -For each customization point we provide an ordered list of ways in which it can be made valid. The order in this list matters: the match for validity is performed in order and if a given customization is determined to be valid, the subsequent ways, even if they would be valid, are ignored. +For each customization point we provide an ordered list of ways in which it can be made valid. +The order in this list matters: the match for validity is performed in order, +and if a given customization is determined to be valid, the subsequent ways, even if they would be valid, are ignored. Often, the last item from the list serves the purpose of a "fallback" or "default" customization. @@ -14,6 +18,7 @@ The property or being valid or invalid can be statically tested in the program v All the customization points in this library are defined in namespace `::graph` and brought into the program code via including header ``. + ## The list of customization points We use the following notation to represent the customization points: @@ -57,6 +62,7 @@ We also use its return type to determine the type of the vertex id: `vertex_id_t * `I0`, when the type of `G` matches pattern ranges::forward_list<ranges::forward_list<tuple-like<I0, ...>>> and `I0` is `std::integral`, * `std::size_t` otherwise. + ### `find_vertex` TODO `find_vertex(g, uid)` diff --git a/doc/views.md b/doc/views.md new file mode 100644 index 0000000..e2b4845 --- /dev/null +++ b/doc/views.md @@ -0,0 +1,170 @@ +# Views + +This library comes with a number of factory functions that allow you to represent +a graph or parts thereof as _views_ over vertices or edges. Using these views you can +define your own algorithms more conveniently. + + +## Breadth-first search views + +Header `` defines three view factories in namespace `graph`: + + * `vertices_breadth_first_search`, + * `edges_breadth_first_search`, + * `sourced_edges_breadth_first_search`. + +They all give you a view of vertices in the order of the breadth-first traversal. +They differ by the element type they produce. + +### vertices_breadth_first_search + +```c++ +template VI, + typename Alloc> +std::ranges::input_range auto& +vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + + +*Parameters:* + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. + +*Returns:* A view with the value type + `vertex_info, vertex_t&, void>`. + +*Remarks:* The vertex corresponding to vertex id `seed` is not an element in the returned range. + + +```c++ +template VI, + std::invocable&> VVF, + typename Alloc> +std::ranges::input_range auto& +vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `vvf` – vertex value function, + * `alloc` – allocator to be used for the internal storage. + +Returns: A view with the value type + `vertex_info, vertex_t&, std::invoke_result_t&>>`. + The third argument is the result of invoking `vvf` with the vertex reference. + +*Remarks:* The vertex corresponding to vertex id `seed` is not an element in the returned range. + +```c++ +template VI, + typename Alloc> +std::ranges::input_range auto& +edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +Returns: A view with the value type + `edge_info, false, edge_reference_t, void>`. + + +```c++ +template VI, + std::invocable> EVF, + typename Alloc> +std::ranges::input_range auto& +edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `evf` – edge value function, + * `alloc` – allocator to be used for the internal storage. + +Returns: A view with the value type + `edge_info, false, edge_reference_t, std::invoke_result_t>>`. + The fourth argument is the result of invoking `evf` with the edge reference. + + + +```c++ +template VI, + typename Alloc> +std::ranges::input_range auto& +sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +Returns: A view with the value type + `edge_info, true, edge_reference_t, void>`. + + +```c++ +template VI, + std::invocable> EVF, + typename Alloc> +std::ranges::input_range auto& +sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `seed` – the initial vertex, + * `evf` – edge value function, + * `alloc` – allocator to be used for the internal storage. + +Returns: A view with the value type + `edge_info, true, edge_reference_t, std::invoke_result_t>>`. + The fourth argument is the result of invoking `evf` with the edge reference. + + +### Cancellation + +Given `bfs` as any of the above breadth-first views, the following two operations +alter the view, so that some of all of the following elements are skipped: + + * `bfs.cancel(cancel_search::cancel_branch)` – skips the visitation of the current vertex. + * `bfs.cancel(cancel_search::cancel_all)` – ends the entire visitation. + +### Usage patterns + +The intended usage for the above views: + +```c++ +for (auto [vid, v] : vertices_breadth_first_search(g, seed)) {} +for (auto [vid, v, val] : vertices_breadth_first_search(g, seed, vvf)) {} + +for (auto [vid, uv] : edges_breadth_first_search(g, seed)) {} +for (auto [vid, uv, val] : edges_breadth_first_search(g, seed, evf)) {} + +for (auto [uid, vid, uv] : sourced_edges_depth_first_search(g, seed)) {} +for (auto [uid, vid, uv, val] : sourced_edges_depth_first_search(g, seed, evf)) {} +``` + +## TODO: other viwes \ No newline at end of file From 5509c7e1369eefc8588268a5810b6b7d817f26de Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Wed, 18 Jun 2025 07:23:58 +0300 Subject: [PATCH 05/20] fix the views description --- doc/views.md | 52 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/doc/views.md b/doc/views.md index e2b4845..d99372f 100644 --- a/doc/views.md +++ b/doc/views.md @@ -5,6 +5,10 @@ a graph or parts thereof as _views_ over vertices or edges. Using these views yo define your own algorithms more conveniently. +## The "info" classes + +TODO: describe `vertex_info` and `edge_info`. + ## Breadth-first search views Header `` defines three view factories in namespace `graph`: @@ -21,7 +25,7 @@ They differ by the element type they produce. ```c++ template VI, - typename Alloc> + typename Alloc = std::allocator>>> std::ranges::input_range auto& vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -47,7 +51,7 @@ vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); template VI, std::invocable&> VVF, - typename Alloc> + typename Alloc = std::allocator>> std::ranges::input_range auto& vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -58,8 +62,12 @@ Parameters: * `seed` – the initial vertex, * `vvf` – vertex value function, * `alloc` – allocator to be used for the internal storage. + +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. -Returns: A view with the value type +*Returns:* A view with the value type `vertex_info, vertex_t&, std::invoke_result_t&>>`. The third argument is the result of invoking `vvf` with the vertex reference. @@ -68,7 +76,7 @@ Returns: A view with the value type ```c++ template VI, - typename Alloc> + typename Alloc = std::allocator>>> std::ranges::input_range auto& edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -78,8 +86,12 @@ Parameters: * `g` – the graph representation, * `seed` – the initial vertex, * `alloc` – allocator to be used for the internal storage. + +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. -Returns: A view with the value type +*Returns:* A view with the value type `edge_info, false, edge_reference_t, void>`. @@ -87,19 +99,23 @@ Returns: A view with the value type template VI, std::invocable> EVF, - typename Alloc> + typename Alloc = std::allocator>>> std::ranges::input_range auto& edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` -Parameters: +*Parameters:* * `g` – the graph representation, * `seed` – the initial vertex, * `evf` – edge value function, * `alloc` – allocator to be used for the internal storage. + +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. -Returns: A view with the value type +*Returns:* A view with the value type `edge_info, false, edge_reference_t, std::invoke_result_t>>`. The fourth argument is the result of invoking `evf` with the edge reference. @@ -108,18 +124,22 @@ Returns: A view with the value type ```c++ template VI, - typename Alloc> + typename Alloc = std::allocator>>> std::ranges::input_range auto& sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` -Parameters: +*Parameters:* * `g` – the graph representation, * `seed` – the initial vertex, * `alloc` – allocator to be used for the internal storage. -Returns: A view with the value type +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. + +*Returns:* A view with the value type `edge_info, true, edge_reference_t, void>`. @@ -127,19 +147,23 @@ Returns: A view with the value type template VI, std::invocable> EVF, - typename Alloc> + typename Alloc = std::allocator>>> std::ranges::input_range auto& sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` -Parameters: +*Parameters:* * `g` – the graph representation, * `seed` – the initial vertex, * `evf` – edge value function, * `alloc` – allocator to be used for the internal storage. -Returns: A view with the value type +*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. + +*Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. + +*Returns:* A view with the value type `edge_info, true, edge_reference_t, std::invoke_result_t>>`. The fourth argument is the result of invoking `evf` with the edge reference. From 118b9a299f9099f8cb07d5115573279f3dcad73a Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Wed, 18 Jun 2025 12:00:44 +0300 Subject: [PATCH 06/20] document visitors --- doc/algorithms.md | 87 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/doc/algorithms.md b/doc/algorithms.md index bf425f9..dbf0347 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -22,9 +22,92 @@ and path-target(p) = v that has the smallest value of ## Visitors -TODO: explain the _GraphVisitor_ requirements +A number of functions in this section take a _visitor_ as an optional argument. +As different _events_, related to vertices and edges, occur during the execution of an algorithm, +a corresponding member function, if present, is called for the visitor. -TODO: list and describe all possible visitation events +### GraphVisitor requirements + +The following lists the visitation events and the corresponding visitor member functions. +For each of the events the visitor may choose to support it via making the corresponding member +function valid. + +The notation used: + +| name | type | definition | +|-------|------|-------------| +| `vis` | | the visitor | +| `G` | | the type of the graph that the algorithm is instantiated for | +| `vd` | `vertex_info, vertex_reference_t, void>` | visited vertex | +| `ed` | `edge_info, true, edge_reference_t, void>` | visited edge | + +```c++ +vis.on_discover_vertex(vd) +``` + +If valid, it is called whenever a new vertex is identified for future examination in the +course of executing an algorithm. + +(Note: the vertices provided as _seeds_ to algorithms are initially discovered.) + +```c++ +vis.on_examine_vertex(vd) +``` + +If valid, it is called whenever a previously discovered vertex is started being examined. + +(Note: examining a vertex usually triggers the discovery of other vertices and edges.) + +```c++ +vis.on_finish_vertex(vd) +``` + +If valid, it is called whenever an algorithm finishes examining the vertex. + +(Note: If the graph is unbalanced and another path to this vertex has a lower accumulated + weight, the algorithm will process `vd` again. + A consequence is that `on_examine_vertex` could be called twice (or more) on the + same vertex.) + +```c++ +vis.on_examine_edge(ed) +``` + +If valid, it is called whenever a new edge is started being examined. + + + + +```c++ +vis.on_edge_relaxed(ed) +``` + +If valid, it is called whenever an edge is _relaxed_. Relaxing an edge means reducing +the stored minimum accumulated distance found so far from the given seed to the target +of the examined edge `ed`. + + +```c++ +vis.on_edge_not_relaxed(ed) +``` + +If valid, it is called whenever a new edge `ed` is inspected but not relaxed (because +the stored accumulated distance to the target of `ed` found so far is smaller than the path via `ed`.) + +```c++ +vis.on_edge_minimized(ed) +``` + +If valid, it is called when no cycles have been detected while examining the edge `ed`. + + +```c++ +vis.on_edge_not_minimized(ed) +``` + +If valid, it is called when a cycles have been detected while examining the edge `ed`. +This happens in shortest paths algorithms that accept negative weights, and means that +no finite minimum exists. ## `dijkstra_shortest_paths` (single source) From b850d0dbb9304b0e24133f8a7f2b3df5f9bf1f76 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Wed, 18 Jun 2025 16:09:43 +0300 Subject: [PATCH 07/20] document incidence view --- doc/views.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/doc/views.md b/doc/views.md index d99372f..caba70f 100644 --- a/doc/views.md +++ b/doc/views.md @@ -20,12 +20,11 @@ Header `` defines three view factories in They all give you a view of vertices in the order of the breadth-first traversal. They differ by the element type they produce. -### vertices_breadth_first_search ```c++ template VI, - typename Alloc = std::allocator>>> + typename Alloc = std::allocator>> std::ranges::input_range auto& vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -76,7 +75,7 @@ Parameters: ```c++ template VI, - typename Alloc = std::allocator>>> + typename Alloc = std::allocator>> std::ranges::input_range auto& edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -99,7 +98,7 @@ Parameters: template VI, std::invocable> EVF, - typename Alloc = std::allocator>>> + typename Alloc = std::allocator>> std::ranges::input_range auto& edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -124,7 +123,7 @@ edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ```c++ template VI, - typename Alloc = std::allocator>>> + typename Alloc = std::allocator>> std::ranges::input_range auto& sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -147,7 +146,7 @@ sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()) template VI, std::invocable> EVF, - typename Alloc = std::allocator>>> + typename Alloc = std::allocator>> std::ranges::input_range auto& sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); ``` @@ -191,4 +190,72 @@ for (auto [uid, vid, uv] : sourced_edges_depth_first_search(g, seed)) for (auto [uid, vid, uv, val] : sourced_edges_depth_first_search(g, seed, evf)) {} ``` + +## Incidence view + +Header `` defines two view factories in namespace `graph`: + +* `incidence`, +* `basic_incidence` + +These offer a forward iteration over vertices incident with a given input vertex. + +```c++ +template VI> +std::ranges::forward_range auto incidence(G&& g, const VI& uid); +``` + +*Parameters:* + + * `g` – the graph representation, + * `uid` – the initial vertex. + +*Hardened preconditions:* `find_vertex(g, uid) != nullptr`. + +*Returns:* A view with the value type + `edge_info, false, edge_reference_t, void>`. + + +```c++ +template VI, std::invocable> EVF> +std::ranges::forward_range auto incidence(G&& g, const VI& uid, EFV evf); +``` + +*Parameters:* + + * `g` – the graph representation, + * `uid` – the initial vertex. + +*Hardened preconditions:* `find_vertex(g, uid) != nullptr`. + +*Returns:* A view with the value type + `edge_info, false, edge_reference_t, std::invoke_result_t>>`. + + +```c++ +template VI> +std::ranges::forward_range auto basic_incidence(G&& g, const VI& uid); +``` + +*Parameters:* + + * `g` – the graph representation, + * `uid` – the initial vertex. + +*Hardened preconditions:* `find_vertex(g, uid) != nullptr`. + +*Returns:* A view with the value type + `edge_info, false, void, void>`. + + +### Usage patterns + +The intended usage for the above views: + +```c++ +for (auto [vid, uv] : incidence(g, uid)) {} +for (auto [vid, uv, val] : incidence(g, uid, evf)) {} +for (auto [vid] : basic_incidence(g, uid)) {} +``` + ## TODO: other viwes \ No newline at end of file From 9e19945fa64ebfea80613bcdb5a7ceedf4fa3b83 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Thu, 19 Jun 2025 14:50:14 +0300 Subject: [PATCH 08/20] document BFS contract --- doc/views.md | 58 +++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/doc/views.md b/doc/views.md index caba70f..386e653 100644 --- a/doc/views.md +++ b/doc/views.md @@ -11,15 +11,33 @@ TODO: describe `vertex_info` and `edge_info`. ## Breadth-first search views -Header `` defines three view factories in namespace `graph`: +Header `` defines view factories in namespace `graph`: - * `vertices_breadth_first_search`, - * `edges_breadth_first_search`, - * `sourced_edges_breadth_first_search`. +```c++ +for (auto [vid, v] : vertices_breadth_first_search(g, seed)) {} +for (auto [vid, v, val] : vertices_breadth_first_search(g, seed, vvf)) {} + +for (auto [vid, uv] : edges_breadth_first_search(g, seed)) {} +for (auto [vid, uv, val] : edges_breadth_first_search(g, seed, evf)) {} + +for (auto [uid, vid, uv] : sourced_edges_depth_first_search(g, seed)) {} +for (auto [uid, vid, uv, val] : sourced_edges_depth_first_search(g, seed, evf)) {} +``` They all give you a view of vertices in the order of the breadth-first traversal. They differ by the element type they produce. +Breadth-first traversal offers the following properties in terms of rendered vertices: + + * Vertex `seed` is never rendered. + * Every vertex other than `seed` reachable from `seed` is rendered. + * For three vertices `u`, `v`, `w`, if + * `u` is rendered before `v` and `w` and + * there is an edge from `u` to `v` and + * there is no edge from `u` to `w`, + + then `v` will be rendered before `w`. + ```c++ template ` defines two view factories in namespace `graph`: +Header `` defines view factories in namespace `graph`: -* `incidence`, -* `basic_incidence` +```c++ +for (auto [vid, uv] : incidence(g, uid)) {} +for (auto [vid, uv, val] : incidence(g, uid, evf)) {} +for (auto [vid] : basic_incidence(g, uid)) {} +``` These offer a forward iteration over vertices incident with a given input vertex. @@ -248,14 +255,5 @@ std::ranges::forward_range auto basic_incidence(G&& g, const VI& uid); `edge_info, false, void, void>`. -### Usage patterns - -The intended usage for the above views: - -```c++ -for (auto [vid, uv] : incidence(g, uid)) {} -for (auto [vid, uv, val] : incidence(g, uid, evf)) {} -for (auto [vid] : basic_incidence(g, uid)) {} -``` ## TODO: other viwes \ No newline at end of file From 898c5c5f7f53c6a6b2268a74e3590441b177c622 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Fri, 20 Jun 2025 11:06:15 +0300 Subject: [PATCH 09/20] document Dijkstra --- doc/algorithms.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/algorithms.md b/doc/algorithms.md index dbf0347..0b3d64f 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -110,7 +110,23 @@ This happens in shortest paths algorithms that accept negative weights, and mean no finite minimum exists. -## `dijkstra_shortest_paths` (single source) +## `dijkstra_shortest_paths` + +The shortest paths algorithm builds on the idea that each edge in a graph has its associated _weight_. +A path _distance_ is determined by the composition of weights of edges that constitute the path. + +By default the composition of the edge weights is summation and the default weight is 1, +so the path distance is the number of edges that it comprises. + +Dijkstra's shortest paths algorithm also makes an assumption that appending an edge to a path _increases_ +the path's distance. In terms of the default composition and weight this assumption is expressed as `weight(uv) >= 0`. + +The distances of each path are returned directly vie the output function argument. +The paths themselves, if requested, are only returned indirectly by providing for each vertex +its predecessor in any shortest path. + + +### The single source version Header `` @@ -162,6 +178,7 @@ constexpr void dijkstra_shortest_distances( *Remarks:* Duplicate sources do not affect the algorithm’s complexity or correctness. + ## TODO Document all other algorithms... From 4a270f96669ee14977037397bafbf0c9ce09ec63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Krzemie=C5=84ski?= Date: Tue, 1 Jul 2025 23:34:06 +0200 Subject: [PATCH 10/20] Create utilities.md --- doc/utilities.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 doc/utilities.md diff --git a/doc/utilities.md b/doc/utilities.md new file mode 100644 index 0000000..4d8b734 --- /dev/null +++ b/doc/utilities.md @@ -0,0 +1,49 @@ +# Utilities + +Some components in this library, such as graph traversal views or visitors, +need to represent a number of properties of a graph vertes at once, like vertex id, +vertex reference, and vertex value. For this purpose class template `vertex_info` is used. + +```c++ +// header + +namespace graph { + +template +struct vertex_info { + using id_type = VId; // usually vertex_id_t + using vertex_type = V; // usually vertex_reference_t + using value_type = VV; // result of coputing the value of a vertex + + id_type id; // absent when `VId` is `void` + vertex_type vertex; // absent when `V` is `void` + value_type value; // absent when `VV` is `void` +}; + +} +``` + +This class template comes with a number of specializations which make certain data members disappear when the corresponding teplate parameter is `void`. + +Thre is an analogous utility class for representing edge information. + +```c++ +// header + +namespace graph { + +template +struct edge_info { + using source_id_type = VId; // this type is `void` when `Sourced` is `false` + using target_id_type = VId; // usually vertex_id_t + using edge_type = E; // usually edge_reference_t + using value_type = EV; // + + source_id_type source_id; // absent when `Sourced` is `false` + target_id_type target_id; // absent when `VId` is `void` + edge_type edge; // absent when `E` is `void` + value_type value; // absent when `EV` is `void` +}; + +} +``` From 76e7f43f0a5b7095c18f2e2003e49a49a9f3a79f Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Tue, 1 Jul 2025 23:35:51 +0200 Subject: [PATCH 11/20] describe graph_info.hpp --- doc/utilities.md | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/doc/utilities.md b/doc/utilities.md index 4d8b734..75b4bcb 100644 --- a/doc/utilities.md +++ b/doc/utilities.md @@ -1,49 +1,49 @@ -# Utilities - -Some components in this library, such as graph traversal views or visitors, -need to represent a number of properties of a graph vertes at once, like vertex id, -vertex reference, and vertex value. For this purpose class template `vertex_info` is used. - -```c++ -// header - -namespace graph { - -template -struct vertex_info { - using id_type = VId; // usually vertex_id_t - using vertex_type = V; // usually vertex_reference_t - using value_type = VV; // result of coputing the value of a vertex - - id_type id; // absent when `VId` is `void` - vertex_type vertex; // absent when `V` is `void` - value_type value; // absent when `VV` is `void` -}; - -} -``` - -This class template comes with a number of specializations which make certain data members disappear when the corresponding teplate parameter is `void`. - -Thre is an analogous utility class for representing edge information. - -```c++ -// header - -namespace graph { - -template -struct edge_info { - using source_id_type = VId; // this type is `void` when `Sourced` is `false` - using target_id_type = VId; // usually vertex_id_t - using edge_type = E; // usually edge_reference_t - using value_type = EV; // - - source_id_type source_id; // absent when `Sourced` is `false` - target_id_type target_id; // absent when `VId` is `void` - edge_type edge; // absent when `E` is `void` - value_type value; // absent when `EV` is `void` -}; - -} +# Utilities + +Some components in this library, such as graph traversal views or visitors, +need to represent a number of properties of a graph vertex at once, like vertex id, +vertex reference, and vertex value. For this purpose class template `vertex_info` is used. + +```c++ +// header + +namespace graph { + +template +struct vertex_info { + using id_type = VId; // usually vertex_id_t + using vertex_type = V; // usually vertex_reference_t + using value_type = VV; // result of computing the value of a vertex + + id_type id; // absent when `VId` is `void` + vertex_type vertex; // absent when `V` is `void` + value_type value; // absent when `VV` is `void` +}; + +} +``` + +This class template comes with a number of specializations which make certain data members disappear when the corresponding template parameter is `void`. + +There is an analogous utility class for representing edge information. + +```c++ +// header + +namespace graph { + +template +struct edge_info { + using source_id_type = VId; // this type is `void` when `Sourced` is `false` + using target_id_type = VId; // usually vertex_id_t + using edge_type = E; // usually edge_reference_t + using value_type = EV; // + + source_id_type source_id; // absent when `Sourced` is `false` + target_id_type target_id; // absent when `VId` is `void` + edge_type edge; // absent when `E` is `void` + value_type value; // absent when `EV` is `void` +}; + +} ``` From fe5e5102fedeeaf073939af287e999b36b6af5d8 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Thu, 3 Jul 2025 06:41:46 +0200 Subject: [PATCH 12/20] docs: spelling fixes --- doc/algorithms.md | 4 +-- doc/customization_points.md | 2 +- doc/views.md | 58 ++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/algorithms.md b/doc/algorithms.md index 0b3d64f..8f5b33c 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -48,7 +48,7 @@ vis.on_discover_vertex(vd) If valid, it is called whenever a new vertex is identified for future examination in the course of executing an algorithm. -(Note: the vertices provided as _seeds_ to algorithms are initially discovered.) +(Note: the vertices provided as _sources_ to algorithms are initially discovered.) ```c++ vis.on_examine_vertex(vd) @@ -83,7 +83,7 @@ vis.on_edge_relaxed(ed) ``` If valid, it is called whenever an edge is _relaxed_. Relaxing an edge means reducing -the stored minimum accumulated distance found so far from the given seed to the target +the stored minimum accumulated distance found so far from the given source to the target of the examined edge `ed`. diff --git a/doc/customization_points.md b/doc/customization_points.md index d17bb86..e7ec589 100644 --- a/doc/customization_points.md +++ b/doc/customization_points.md @@ -36,7 +36,7 @@ We use the following notation to represent the customization points: ### `vertices` -The CPO `vertices(g)` is used to obtain the list of all vertices, in form of a `std::ranges::random_access_range`, from the graph-representing object `g`. +The CPO `vertices(g)` is used to obtain the range of all vertices, in form of a `std::ranges::random_access_range`, from the graph-representing object `g`. We also use its return type to determine the type of the vertex: `vertex_t`. #### Customization diff --git a/doc/views.md b/doc/views.md index 386e653..85dd8cd 100644 --- a/doc/views.md +++ b/doc/views.md @@ -14,14 +14,14 @@ TODO: describe `vertex_info` and `edge_info`. Header `` defines view factories in namespace `graph`: ```c++ -for (auto [vid, v] : vertices_breadth_first_search(g, seed)) {} -for (auto [vid, v, val] : vertices_breadth_first_search(g, seed, vvf)) {} +for (auto [vid, v] : vertices_breadth_first_search(g, source)) {} +for (auto [vid, v, val] : vertices_breadth_first_search(g, source, vvf)) {} -for (auto [vid, uv] : edges_breadth_first_search(g, seed)) {} -for (auto [vid, uv, val] : edges_breadth_first_search(g, seed, evf)) {} +for (auto [vid, uv] : edges_breadth_first_search(g, source)) {} +for (auto [vid, uv, val] : edges_breadth_first_search(g, source, evf)) {} -for (auto [uid, vid, uv] : sourced_edges_depth_first_search(g, seed)) {} -for (auto [uid, vid, uv, val] : sourced_edges_depth_first_search(g, seed, evf)) {} +for (auto [uid, vid, uv] : sourced_edges_depth_first_search(g, source)) {} +for (auto [uid, vid, uv, val] : sourced_edges_depth_first_search(g, source, evf)) {} ``` They all give you a view of vertices in the order of the breadth-first traversal. @@ -29,8 +29,8 @@ They differ by the element type they produce. Breadth-first traversal offers the following properties in terms of rendered vertices: - * Vertex `seed` is never rendered. - * Every vertex other than `seed` reachable from `seed` is rendered. + * Vertex `source` is never rendered. + * Every vertex other than `source` reachable from `source` is rendered. * For three vertices `u`, `v`, `w`, if * `u` is rendered before `v` and `w` and * there is an edge from `u` to `v` and @@ -44,24 +44,24 @@ template VI, typename Alloc = std::allocator>> std::ranges::input_range auto& -vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +vertices_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` *Parameters:* * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. *Returns:* A view with the value type `vertex_info, vertex_t&, void>`. -*Remarks:* The vertex corresponding to vertex id `seed` is not an element in the returned range. +*Remarks:* The vertex corresponding to vertex id `source` is not an element in the returned range. ```c++ @@ -70,17 +70,17 @@ template &> VVF, typename Alloc = std::allocator>> std::ranges::input_range auto& -vertices_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +vertices_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` Parameters: * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `vvf` – vertex value function, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. @@ -88,23 +88,23 @@ Parameters: `vertex_info, vertex_t&, std::invoke_result_t&>>`. The third argument is the result of invoking `vvf` with the vertex reference. -*Remarks:* The vertex corresponding to vertex id `seed` is not an element in the returned range. +*Remarks:* The vertex corresponding to vertex id `source` is not an element in the returned range. ```c++ template VI, typename Alloc = std::allocator>> std::ranges::input_range auto& -edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` Parameters: * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. @@ -118,17 +118,17 @@ template > EVF, typename Alloc = std::allocator>> std::ranges::input_range auto& -edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` *Parameters:* * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `evf` – edge value function, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. @@ -143,16 +143,16 @@ template VI, typename Alloc = std::allocator>> std::ranges::input_range auto& -sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +sourced_edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` *Parameters:* * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. @@ -166,17 +166,17 @@ template > EVF, typename Alloc = std::allocator>> std::ranges::input_range auto& -sourced_edges_breadth_first_search(G&& g, const VI& seed, Alloc alloc = Alloc()); +sourced_edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); ``` *Parameters:* * `g` – the graph representation, - * `seed` – the initial vertex, + * `source` – the initial vertex, * `evf` – edge value function, * `alloc` – allocator to be used for the internal storage. -*Hardened preconditions:* `find_vertex(g, seed) != nullptr`. +*Hardened preconditions:* `find_vertex(g, source) != nullptr`. *Mandates:* `alloc` satisfies [Cpp17Allocator](https://eel.is/c++draft/allocator.requirements) requirements. @@ -256,4 +256,4 @@ std::ranges::forward_range auto basic_incidence(G&& g, const VI& uid); -## TODO: other viwes \ No newline at end of file +## TODO: document other views \ No newline at end of file From 461d13251132dcb4d0497a6d8ecddec0358b058f Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sat, 5 Jul 2025 23:45:37 +0200 Subject: [PATCH 13/20] Docs: some notes on visitors --- doc/algorithms.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/algorithms.md b/doc/algorithms.md index 8f5b33c..9b51b1d 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -26,6 +26,11 @@ A number of functions in this section take a _visitor_ as an optional argument. As different _events_, related to vertices and edges, occur during the execution of an algorithm, a corresponding member function, if present, is called for the visitor. +Each algorithm defines the events that it supports. Visitor functions corresponding to not supported events, even if present in the visitor are ignored. + +If an algorithm supports a given event but the specified visitor does not provide the corresponding valid member function, no runtime overhead related to processing this event is incurred. + + ### GraphVisitor requirements The following lists the visitation events and the corresponding visitor member functions. @@ -110,6 +115,9 @@ This happens in shortest paths algorithms that accept negative weights, and mean no finite minimum exists. +NOTE: This set of visitors is map + + ## `dijkstra_shortest_paths` The shortest paths algorithm builds on the idea that each edge in a graph has its associated _weight_. From a4d03b7b9e9854e27604b48d530b29d9e6d8c079 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sun, 6 Jul 2025 01:33:38 +0200 Subject: [PATCH 14/20] Docs: further review fixes --- doc/algorithms.md | 12 +++++++++++- doc/overview.md | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/algorithms.md b/doc/algorithms.md index 9b51b1d..bff4bfb 100644 --- a/doc/algorithms.md +++ b/doc/algorithms.md @@ -115,7 +115,17 @@ This happens in shortest paths algorithms that accept negative weights, and mean no finite minimum exists. -NOTE: This set of visitors is map +### `empty_visitor` + +This library comes with an empty class `empty_visitor`: + +```c++ +namespace graph { + struct empty_visitor{}; +} +``` + +It is used as the default visitor type for the algorithms. This visitor supports no events, and therefore triggers no runtime overhead on any event. ## `dijkstra_shortest_paths` diff --git a/doc/overview.md b/doc/overview.md index a20cfc6..a7fd030 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -154,10 +154,12 @@ auto weight = [](std::tuple const& uv) { return std::get<1>(uv); }; +std::vector distances(g.size()); // we will store the distance to each vertex here std::vector predecessors(g.size()); // we will store the predecessor of each vertex here -std::vector distances(g.size()); // we will store the distance to each vertex here -graph::init_shortest_paths(distances); // fill with `infinity` +// fill `distances` with `infinity` +// fill `predecessors` at index `i` with value `i` +graph::init_shortest_paths(distances, predecessors); graph::dijkstra_shortest_paths(g, 0, distances, predecessors, weight); // from vertex 0 From b41752be2698e54e4f4bd1fbb99a515743292a3e Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sat, 12 Jul 2025 21:44:30 +0200 Subject: [PATCH 15/20] docs: added ections tutorial and reference --- doc/README.md | 67 +++++++++++++++++++ doc/{ => reference}/algorithms.md | 0 doc/{ => reference}/customization_points.md | 0 doc/{ => reference}/utilities.md | 0 doc/{ => reference}/views.md | 0 doc/tutorial/graph_container_interface.md | 24 +++++++ .../tutorial_other.md} | 9 +-- 7 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 doc/README.md rename doc/{ => reference}/algorithms.md (100%) rename doc/{ => reference}/customization_points.md (100%) rename doc/{ => reference}/utilities.md (100%) rename doc/{ => reference}/views.md (100%) create mode 100644 doc/tutorial/graph_container_interface.md rename doc/{overview.md => tutorial/tutorial_other.md} (94%) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..784cd4b --- /dev/null +++ b/doc/README.md @@ -0,0 +1,67 @@ +# Introduction + +This is a C++20 graph library with the following capabilities: + + 1. Generic algorithms operating on graph representations. + 2. Generic views that allow the traversal of your graphs, or parts thereof, in a sequential manner. + 3. Customization of your graph representation, so that it can be used with our algorithms and views. + 4. A number of graph containers. + + +## Example + +The following example shows how one can compute for each vertex of a given graph `g` its distance, +measured in the number of edges, from the indicated vertex with index 0. + 1. We need an [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list) representation of a graph. + 2. Vertices are identified by indices of a vector. + +```c++ +#include +#include +#include + +// `vector>` is recognized as an adjacency list by this library +std::vector> g { // + /*0*/ {1, 3}, // + /*1*/ {0, 2, 4}, // (0) ----- (1) ----- (2) + /*2*/ {1, 5}, // | | | + /*3*/ {0, 4}, // | | | + /*4*/ {1, 3}, // | | | + /*5*/ {2, 6}, // (3) ----- (4) (5) ----- (6) + /*6*/ {5} // +}; // + +int main() +{ + std::vector distances(g.size(), 0); // fill with zeros + + // a view of edges as they appear in the breadth-first order, from vertex 0. + auto bfs_view = graph::views::sourced_edges_breadth_first_search(g, 0); + + for (auto const& [uid, vid, _] : bfs_view) // a directed edge (u, v) + distances[vid] = distances[uid] + 1; + + assert((distances == std::vector{0, 1, 2, 1, 2, 3, 4})); +} +``` + +## Design + +Algorithms and views in this library operate on graph representations via the [*Graph Container Interface*](./tutorial/graph_container_interface.md), +which is a set of *customization point objects* (CPO). In order to plug your graph container into this library you need to make sure that all the +necessary CPOs have been customized for your container. + +The generic algorithms and views in this library are constrained with concepts, which are expressed in terms of the above CPOs. + +This library comes with two graph containers, encoding different engineering trade-offs. Also, some sufficiently simple nested ranges are automatically considered +compliant with the Graph Container Interface, such as: + + * `vector>`, + * `vector>>`. + +The algorithms in this library do not mutate the graphs. There is not support for graph rewriting. + +# Next steps + +For a more detailed overview of the library, see section [tutorial](./tutorial).
+For a reference documentaiton, see section [reference](./reference). diff --git a/doc/algorithms.md b/doc/reference/algorithms.md similarity index 100% rename from doc/algorithms.md rename to doc/reference/algorithms.md diff --git a/doc/customization_points.md b/doc/reference/customization_points.md similarity index 100% rename from doc/customization_points.md rename to doc/reference/customization_points.md diff --git a/doc/utilities.md b/doc/reference/utilities.md similarity index 100% rename from doc/utilities.md rename to doc/reference/utilities.md diff --git a/doc/views.md b/doc/reference/views.md similarity index 100% rename from doc/views.md rename to doc/reference/views.md diff --git a/doc/tutorial/graph_container_interface.md b/doc/tutorial/graph_container_interface.md new file mode 100644 index 0000000..76cb12e --- /dev/null +++ b/doc/tutorial/graph_container_interface.md @@ -0,0 +1,24 @@ +Graph Container Interface +========================= + +Generic algorithms and views in this library require that your graph container +is represented as an [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list). +Your container is never accessed directly. Instead, the access is performed via the +*graph container interface*. + + +```c++ + std::vector> g { // + /*0*/ {1}, // + /*1*/ {0} // (0) ----- (1) + }; + + auto && vtcs = graph::vertices(g); // get the std::range representing vertices + auto vit = std::ranges::begin(vtcs); + int id0 = graph::vertex_id(g, vit); // get the id of the vertex + assert(id0 == 0); + auto && edgs = graph::edges(g, *vit); // + auto eit = std::ranges::begin(edgs); + int id1 = graph::target_id(g, *eit); + assert(id1 == 1); +``` \ No newline at end of file diff --git a/doc/overview.md b/doc/tutorial/tutorial_other.md similarity index 94% rename from doc/overview.md rename to doc/tutorial/tutorial_other.md index a7fd030..7f0fa2c 100644 --- a/doc/overview.md +++ b/doc/tutorial/tutorial_other.md @@ -1,11 +1,6 @@ -# Overview +# Tutorial -What this library has to offer: - - 1. A number of ready-to-use algorithms, like Dijkstra's shortest paths computation. - 2. Make it easier for you to write your own graph algorithms: we provide a _view_ for traversing your graph in the preferred order (depth-first, breadth-first, topological), and you specify what is processed in each traversal step. - 3. Customization of your graph representation, so that it can be used with our algorithms and views. - 4. Our own container for representing a graph. +Note: this part is under development... ## Concepts From 8e56bc49a8e494db4cc0c5f77117360e1483f485 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sat, 12 Jul 2025 21:45:52 +0200 Subject: [PATCH 16/20] fix GCI --- doc/tutorial/graph_container_interface.md | 53 ++++++++++++++++++----- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/doc/tutorial/graph_container_interface.md b/doc/tutorial/graph_container_interface.md index 76cb12e..038469a 100644 --- a/doc/tutorial/graph_container_interface.md +++ b/doc/tutorial/graph_container_interface.md @@ -4,21 +4,54 @@ Graph Container Interface Generic algorithms and views in this library require that your graph container is represented as an [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list). Your container is never accessed directly. Instead, the access is performed via the -*graph container interface*. +*graph container interface* (GCI). ```c++ - std::vector> g { // - /*0*/ {1}, // - /*1*/ {0} // (0) ----- (1) - }; + std::vector> g { // + /*0*/ {1}, // + /*1*/ {0} // (0) ----- (1) + }; // - auto && vtcs = graph::vertices(g); // get the std::range representing vertices + auto && vtcs = graph::vertices(g); // get the std::range representing graph vertices + auto vit = std::ranges::begin(vtcs); - int id0 = graph::vertex_id(g, vit); // get the id of the vertex + + int id0 = graph::vertex_id(g, vit); // get the id of the vertex assert(id0 == 0); - auto && edgs = graph::edges(g, *vit); // + + auto && edgs = graph::edges(g, *vit); // get the std::range representing the neighbors of the vertex + auto eit = std::ranges::begin(edgs); - int id1 = graph::target_id(g, *eit); + + int id1 = graph::target_id(g, *eit); // get the id of the vertex on the other side of the edge assert(id1 == 1); -``` \ No newline at end of file + + auto wit = graph::find_vertex(g, id1); // get iterator to vertex based on the vertex id + + int id1_ = graph::vertex_id(g, wit); + assert(id1_ == 1); +``` + +This set of operations may seem too rich: you need fewer operations to achieve the same +for type `vector>`. But the Graph Container Interface needs to be prepared for +very different representations of an adjacency list. + +The list of all CPOs in the Graph Container interface is bigger. +All the customization points are described in detail in section [Customization Points](../reference/customization_points.md). + + +Testing the customization +------------------------- + +In order to test if your type has the necessary customization for all the CPOs, you can use concepts defined in this library. + +The one that you will need most often is `graph::index_adjacency_list`: + +```c++ +#include + +static_assert(graph::index_adjacency_list); +``` + +In practice, you need to customize only a handful of CPOs. Most of the others have their default customizaiton. \ No newline at end of file From 2f2f9e1dc5100417d7a46a344c686042ba454b20 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sat, 12 Jul 2025 21:59:05 +0200 Subject: [PATCH 17/20] docs: extracted section tutorial.algorithms --- doc/tutorial/algorithms.md | 59 ++++++++++++++++++++++++++++++++++ doc/tutorial/tutorial_other.md | 56 -------------------------------- 2 files changed, 59 insertions(+), 56 deletions(-) create mode 100644 doc/tutorial/algorithms.md diff --git a/doc/tutorial/algorithms.md b/doc/tutorial/algorithms.md new file mode 100644 index 0000000..cd94a36 --- /dev/null +++ b/doc/tutorial/algorithms.md @@ -0,0 +1,59 @@ +Algorithms +========== + +This library offers a number of generic algorithms. +Due to the nature of graph algorithms, the way they communicate the results requires some additional code. + +Let's use the following graph representation, naively recognized by this library, +that is able to store a weight for each edge: + +```c++ +std::vector>> g { + /*0*/ { {(1), 9.1}, {(3), 1.1} }, // 9.1 2.2 + /*1*/ { {(0), 9.1}, {(2), 2.2}, {(4), 3.5} }, // (0)-------(1)-------(2) + /*2*/ { {(1), 2.2}, {(5), 1.0} }, // | | | + /*3*/ { {(0), 1.1}, {(4), 2.0} }, // |1.1 |3.5 |1.0 + /*4*/ { {(1), 3.5}, {(3), 2.0} }, // | 2.0 | | 0.5 + /*5*/ { {(2), 1.0}, {(6), 0.5} }, // (3)-------(4) (5)-------(6) + /*6*/ { {(5), 0.5} } // +}; +``` + +Now, let's use Dijkstra's Shortest Paths algorithm to determine the distance from vertex `0` to each vertex in `g`, and also to determine these paths. The paths are not obtained directly, but instead the list of predecessors is returned for each vertex: + +```c++ +auto weight = [](std::tuple const& uv) { + return std::get<1>(uv); +}; + +std::vector distances(g.size()); // we will store the distance to each vertex here +std::vector predecessors(g.size()); // we will store the predecessor of each vertex here + +// fill `distances` with `infinity` +// fill `predecessors` at index `i` with value `i` +graph::init_shortest_paths(distances, predecessors); + +graph::dijkstra_shortest_paths(g, 0, distances, predecessors, weight); // from vertex 0 + +assert((distances == std::vector{0.0, 6.6, 8.8, 1.1, 3.1, 9.8, 10.3})); +assert((predecessors == std::vector{0, 4, 1, 0, 3, 2, 5})); +``` + +If you need to know the sequence of vertices in the path from, say, `0` to `5`, you have to compute it yourself: + +```c++ +auto path = [&predecessors](int from, int to) +{ + std::vector result; + for (; to != from; to = predecessors[to]) { + assert(to < predecessors.size()); + result.push_back(to); + } + std::reverse(result.begin(), result.end()); + return result; +}; + +assert((path(0, 5) == std::vector{3, 4, 1, 2, 5})); +``` + +The algorithms from this library are described in section [Algorithms](../reference/algorithms.md). \ No newline at end of file diff --git a/doc/tutorial/tutorial_other.md b/doc/tutorial/tutorial_other.md index 7f0fa2c..30f164e 100644 --- a/doc/tutorial/tutorial_other.md +++ b/doc/tutorial/tutorial_other.md @@ -124,62 +124,6 @@ int main() } ``` -## Algorithms - -This library offers also full fledged algorithms. -However, due to the nature of graph algorithms, the way they communicate the results requires some additional code. -Let's, reuse the same graph topology, but use a different adjacency-list representation that is also able to store a weight for each edge: - -```c++ -std::vector>> g { - /*0*/ { {(1), 9.1}, {(3), 1.1} }, // 9.1 2.2 - /*1*/ { {(0), 9.1}, {(2), 2.2}, {(4), 3.5} }, // (0)-------(1)-------(2) - /*2*/ { {(1), 2.2}, {(5), 1.0} }, // | | | - /*3*/ { {(0), 1.1}, {(4), 2.0} }, // |1.1 |3.5 |1.0 - /*4*/ { {(1), 3.5}, {(3), 2.0} }, // | 2.0 | | 0.5 - /*5*/ { {(2), 1.0}, {(6), 0.5} }, // (3)-------(4) (5)-------(6) - /*6*/ { {(5), 0.5} } // -}; -``` - -Now, let's use Dijkstra's Shortest Paths algorithm to determine the distance from vertex `0` to each vertex in `g`, and also to determine these paths. The paths are not obtained directly, but instead the list of predecessors is returned for each vertex: - -```c++ -auto weight = [](std::tuple const& uv) { - return std::get<1>(uv); -}; - -std::vector distances(g.size()); // we will store the distance to each vertex here -std::vector predecessors(g.size()); // we will store the predecessor of each vertex here - -// fill `distances` with `infinity` -// fill `predecessors` at index `i` with value `i` -graph::init_shortest_paths(distances, predecessors); - -graph::dijkstra_shortest_paths(g, 0, distances, predecessors, weight); // from vertex 0 - -assert((distances == std::vector{0.0, 6.6, 8.8, 1.1, 3.1, 9.8, 10.3})); -assert((predecessors == std::vector{0, 4, 1, 0, 3, 2, 5})); -``` - -If you need to know the sequence of vertices in the path from, say, `0` to `5`, you have to compute it yourself: - -```c++ -auto path = [&predecessors](int from, int to) -{ - std::vector result; - for (; to != from; to = predecessors[to]) { - assert(to < predecessors.size()); - result.push_back(to); - } - std::reverse(result.begin(), result.end()); - return result; -}; - -assert((path(0, 5) == std::vector{3, 4, 1, 2, 5})); -``` -The algorithms in this library are described in section [Algorithms](./algorithms.md). - ## Containers From ec26ff052eb5a331851b9167f7be79a8b2c45da3 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sun, 13 Jul 2025 01:26:00 +0200 Subject: [PATCH 18/20] docs: more CPOs --- doc/reference/customization_points.md | 60 ++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/doc/reference/customization_points.md b/doc/reference/customization_points.md index e7ec589..75269b3 100644 --- a/doc/reference/customization_points.md +++ b/doc/reference/customization_points.md @@ -48,10 +48,10 @@ We also use its return type to determine the type of the vertex: `vertex_t`. ### `vertex_id` -The CPO `vertex_id(g, ui)` is used obtain the _id_ of the vertex, given the iterator. +The CPO `vertex_id(g, ui)` is used obtain the _id_ of the vertex, given the iterator.
We also use its return type to determine the type of the vertex id: `vertex_id_t`. -#### Coustomization +#### Customization 1. Returns `ui->vertex_id(g)`, if this expression is valid and its type is `std::move_constructible`. 2. Returns `vertex_id(g, ui)`, if this expression is valid and its type is `std::move_constructible`. @@ -67,22 +67,78 @@ We also use its return type to determine the type of the vertex id: `vertex_id_t TODO `find_vertex(g, uid)` + ### `edges(g, u)` +The CPO `edges(g, u)` is used to obtain the sequence of outgoing edges for a vertex +denoted by reference `u`.
+We also use its return type to determine the type of the edge type: `edge_t`. + +#### Customization + + 1. Returns `u.edges(g)`, if this expression is valid and its type is `std::move_constructible`. + 2. Returns `edges(g, u)`, if this expression is valid and its type is `std::move_constructible`. + 3. `u`, if `G` is a user-defined type and type `vertex_t` is `std::ranges::forward_range; + + ### `edges(g, uid)` +The CPO `edges(g, uid)` is used to obtain the sequence of outgoing edges for a vertex +denoted by _id_ `uid`. + +#### Customization + + 1. Returns `edges(g, uid)`, if this expression is valid and its type is `std::move_constructible`. + 2. Returns `*find_vertex(g, uid)`, if + * `vertex_t` is `std::ranges::forward_range`, and + * expression `find_vertex(g, uid)` is valid and its type is `std::move_constructible`. + + ### `num_edges(g)` +TODO + + ### `target_id(g, uv)` +The CPO `target_id(g, uv)` is used to obtain the _id_ of the target vertex of edge `uv`. + +#### Customization + + 1. Returns `uv.target_id(g)`, if this expression is valid and its type is `std::move_constructible`. + 2. Returns `target_id(g, uv)`, if this expression is valid and its type is `std::move_constructible`. + 3. Returns `uv`, if + * `G` is `std::ranges::forward_range`, and + * `std::ranges::range_value_t` is `std::ranges::forward_range`, and + * `std::ranges::range_value_t>` is `std::integral`. + 4. Returns `get<0>(uv)`, if + * `G` is `std::ranges::forward_range`, and + * `std::ranges::range_value_t` is `std::ranges::forward_range`, and + * `std::ranges::range_value_t>` is tuple-like, + * `std::tuple_element_t<0, std::ranges::range_value_t>>` is `std::integral`. + + ### `target_id(e)` ### `source_id(g, uv)` ### `source_id(e)` + ### `target(g, uv)` +CPO `target(g, uv)` is used to access the target vertex of a given edge `uv`. + +#### Customization + + 1. Returns `target(g, uv)`, if this expression is valid and its type is `std::move_constructible`. + 2. Returns `*find_vertex(g, target_id(g, uv))`, if + + * `vertex_range_t` is a `std::ranges::random_access_range`, and + * `find_vertex(g, uid)` is valid and its type is `std::move_constructible`, and + * `target_id(g, uv)` is valid and its type is `std::integral`. + + ### `source(g, uv)` ### `find_vertex_edge(g, u, vid)` From a1aed633b7e893d19b81f4923deab648f364537c Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sun, 13 Jul 2025 01:27:15 +0200 Subject: [PATCH 19/20] docs: bug fix in CPO --- doc/reference/customization_points.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/reference/customization_points.md b/doc/reference/customization_points.md index 75269b3..c4a59bc 100644 --- a/doc/reference/customization_points.md +++ b/doc/reference/customization_points.md @@ -114,7 +114,7 @@ The CPO `target_id(g, uv)` is used to obtain the _id_ of the target vertex of ed 4. Returns `get<0>(uv)`, if * `G` is `std::ranges::forward_range`, and * `std::ranges::range_value_t` is `std::ranges::forward_range`, and - * `std::ranges::range_value_t>` is tuple-like, + * `std::ranges::range_value_t>` is tuple-like, and * `std::tuple_element_t<0, std::ranges::range_value_t>>` is `std::integral`. From 76a7ddc04afbf3556445f2e75e9409dc94fe8396 Mon Sep 17 00:00:00 2001 From: Andrzej Krzemienski Date: Sun, 13 Jul 2025 17:35:11 +0200 Subject: [PATCH 20/20] docs: imrove visitors description --- doc/reference/algorithms.md | 108 +----------------------------------- doc/reference/visitors.md | 107 +++++++++++++++++++++++++++++++++++ doc/tutorial/algorithms.md | 60 +++++++++++++++++++- 3 files changed, 166 insertions(+), 109 deletions(-) create mode 100644 doc/reference/visitors.md diff --git a/doc/reference/algorithms.md b/doc/reference/algorithms.md index bff4bfb..e6e56db 100644 --- a/doc/reference/algorithms.md +++ b/doc/reference/algorithms.md @@ -20,112 +20,6 @@ and path-target(p) = v that has the smallest value of * if there exists an edge _e_ with target(_e_) = v, then it is source(_e_), * otherwise it is `v`. -## Visitors - -A number of functions in this section take a _visitor_ as an optional argument. -As different _events_, related to vertices and edges, occur during the execution of an algorithm, -a corresponding member function, if present, is called for the visitor. - -Each algorithm defines the events that it supports. Visitor functions corresponding to not supported events, even if present in the visitor are ignored. - -If an algorithm supports a given event but the specified visitor does not provide the corresponding valid member function, no runtime overhead related to processing this event is incurred. - - -### GraphVisitor requirements - -The following lists the visitation events and the corresponding visitor member functions. -For each of the events the visitor may choose to support it via making the corresponding member -function valid. - -The notation used: - -| name | type | definition | -|-------|------|-------------| -| `vis` | | the visitor | -| `G` | | the type of the graph that the algorithm is instantiated for | -| `vd` | `vertex_info, vertex_reference_t, void>` | visited vertex | -| `ed` | `edge_info, true, edge_reference_t, void>` | visited edge | - -```c++ -vis.on_discover_vertex(vd) -``` - -If valid, it is called whenever a new vertex is identified for future examination in the -course of executing an algorithm. - -(Note: the vertices provided as _sources_ to algorithms are initially discovered.) - -```c++ -vis.on_examine_vertex(vd) -``` - -If valid, it is called whenever a previously discovered vertex is started being examined. - -(Note: examining a vertex usually triggers the discovery of other vertices and edges.) - -```c++ -vis.on_finish_vertex(vd) -``` - -If valid, it is called whenever an algorithm finishes examining the vertex. - -(Note: If the graph is unbalanced and another path to this vertex has a lower accumulated - weight, the algorithm will process `vd` again. - A consequence is that `on_examine_vertex` could be called twice (or more) on the - same vertex.) - -```c++ -vis.on_examine_edge(ed) -``` - -If valid, it is called whenever a new edge is started being examined. - - - - -```c++ -vis.on_edge_relaxed(ed) -``` - -If valid, it is called whenever an edge is _relaxed_. Relaxing an edge means reducing -the stored minimum accumulated distance found so far from the given source to the target -of the examined edge `ed`. - - -```c++ -vis.on_edge_not_relaxed(ed) -``` - -If valid, it is called whenever a new edge `ed` is inspected but not relaxed (because -the stored accumulated distance to the target of `ed` found so far is smaller than the path via `ed`.) - -```c++ -vis.on_edge_minimized(ed) -``` - -If valid, it is called when no cycles have been detected while examining the edge `ed`. - - -```c++ -vis.on_edge_not_minimized(ed) -``` - -If valid, it is called when a cycles have been detected while examining the edge `ed`. -This happens in shortest paths algorithms that accept negative weights, and means that -no finite minimum exists. - - -### `empty_visitor` - -This library comes with an empty class `empty_visitor`: - -```c++ -namespace graph { - struct empty_visitor{}; -} -``` - -It is used as the default visitor type for the algorithms. This visitor supports no events, and therefore triggers no runtime overhead on any event. ## `dijkstra_shortest_paths` @@ -183,7 +77,7 @@ constexpr void dijkstra_shortest_distances( * `std::size(distances) >= num_vertices(g)` is `true`, * `std::size(predecessor) >= num_vertices(g)` is `true`. -*Effects:* Supports the following visitation events: `on_initialize_vertex`, `on_discover_vertex`, +*Effects:* Supports the following [visitation](./visitors.md) events: `on_initialize_vertex`, `on_discover_vertex`, `on_examine_vertex`, `on_finish_vertex`, `on_examine_edge`, `on_edge_relaxed`, and `on_edge_not_relaxed`. *Postconditions:* For each i in range [`0`; `num_vertices(g)`): diff --git a/doc/reference/visitors.md b/doc/reference/visitors.md new file mode 100644 index 0000000..5548f92 --- /dev/null +++ b/doc/reference/visitors.md @@ -0,0 +1,107 @@ +# Visitors + +A number of functions in this section take a _visitor_ as an optional argument. +As different _events_, related to vertices and edges, occur during the execution of an algorithm, +a corresponding member function, if present, is called for the visitor. + +Each algorithm defines the events that it supports. Visitor functions corresponding to not +supported events, even if present in the visitor are ignored. + +If an algorithm supports a given event but the specified visitor does not provide the corresponding valid member function, no runtime overhead related to processing this event is incurred. + + +## GraphVisitor requirements + +The following lists the visitation events and the corresponding visitor member functions. +For each of the events the visitor may choose to support it via making the corresponding member +function valid. + +The notation used: + +| name | type | definition | +|-------|------|-------------| +| `vis` | | the visitor | +| `G` | | the type of the graph that the algorithm is instantiated for | +| `vd` | `vertex_info, vertex_reference_t, void>` | visited vertex | +| `ed` | `edge_info, true, edge_reference_t, void>` | visited edge | + +```c++ +vis.on_discover_vertex(vd) +``` + +If valid, it is called whenever a new vertex is identified for future examination in the +course of executing an algorithm. + +(Note: the vertices provided as _sources_ to algorithms are initially discovered.) + +```c++ +vis.on_examine_vertex(vd) +``` + +If valid, it is called whenever a previously discovered vertex is started being examined. + +(Note: examining a vertex usually triggers the discovery of other vertices and edges.) + +```c++ +vis.on_finish_vertex(vd) +``` + +If valid, it is called whenever an algorithm finishes examining the vertex. + +(Note: If the graph is unbalanced and another path to this vertex has a lower accumulated + weight, the algorithm will process `vd` again. + A consequence is that `on_examine_vertex` could be called twice (or more) on the + same vertex.) + +```c++ +vis.on_examine_edge(ed) +``` + +If valid, it is called whenever a new edge is started being examined. + + + + +```c++ +vis.on_edge_relaxed(ed) +``` + +If valid, it is called whenever an edge is _relaxed_. Relaxing an edge means reducing +the stored minimum accumulated distance found so far from the given source to the target +of the examined edge `ed`. + + +```c++ +vis.on_edge_not_relaxed(ed) +``` + +If valid, it is called whenever a new edge `ed` is inspected but not relaxed (because +the stored accumulated distance to the target of `ed` found so far is smaller than the path via `ed`.) + +```c++ +vis.on_edge_minimized(ed) +``` + +If valid, it is called when no cycles have been detected while examining the edge `ed`. + + +```c++ +vis.on_edge_not_minimized(ed) +``` + +If valid, it is called when a cycles have been detected while examining the edge `ed`. +This happens in shortest paths algorithms that accept negative weights, and means that +no finite minimum exists. + + +## `empty_visitor` + +This library comes with an empty class `empty_visitor`: + +```c++ +namespace graph { + struct empty_visitor{}; +} +``` + +It is used as the default visitor type for the algorithms. This visitor supports no events, and therefore triggers no runtime overhead on any event. diff --git a/doc/tutorial/algorithms.md b/doc/tutorial/algorithms.md index cd94a36..55163fe 100644 --- a/doc/tutorial/algorithms.md +++ b/doc/tutorial/algorithms.md @@ -4,7 +4,7 @@ Algorithms This library offers a number of generic algorithms. Due to the nature of graph algorithms, the way they communicate the results requires some additional code. -Let's use the following graph representation, naively recognized by this library, +Let's use the following graph representation, natively recognized by this library, that is able to store a weight for each edge: ```c++ @@ -56,4 +56,60 @@ auto path = [&predecessors](int from, int to) assert((path(0, 5) == std::vector{3, 4, 1, 2, 5})); ``` -The algorithms from this library are described in section [Algorithms](../reference/algorithms.md). \ No newline at end of file +The algorithms from this library are described in section [Algorithms](../reference/algorithms.md). + + +Index-based access +------------------ + +You will notice that a lot of function arguments passed to the algorithms takes +vectors as "output" arguments, such as `predecessors` or `distances` in the above examples. +They do not have to be vectors, but they need to be something that is *indexable* by an integral +number. This is how the algorithms are able to fill in some data associated with a given vertex. +Vertices are here represented by a *vertex index*. This requires that graph representations +that interact with the algorithms need to be able to provide an *index* uniquely identifying each +vertex. This requirement is encoded in the concept `index_adjacency_list`. + + +Visitors +-------- + +Sometimes, when working with the algorithms, there is a need to get more than just the result. +For troubleshooting or debugging reasons we may want to know what indices and edges are processed, +and in what order. We might also want to display the progress of the algorithm (like, "25% done") +before the algorithm finishes. + +To address this need some of the algorithms provide a notion of a *visitor*. A visitor is +like a set of callbacks that you can pass as an optional parameter to an algorithm. Whenever +an *event* occurs during the execution of the algorithm – such as "new vertex discovered" +or "a step through an edge taken" – a corresponding callback is invoked. + +for illustration, consider that in the above example, we want the algorithm to display the +progress to `STDOUT` upon every third vertex processed. We would have to create our custom +visitor, tell it which event we are interested in intercepting, and what we want to do then. + +```c++ +class ShowProgress +{ + using G = std::vector>>; + using VD = graph::vertex_info, graph::vertex_reference_t, void>; + int vertex_count = 0; + +public: + void on_discover_vertex(VD const& v) + { + if (vertex_count % 3 == 0) + std::cout << "processing vertex " << v.id << "\n"; + ++vertex_count; + } +}; +``` + +And then pass it to the algorithm: + +```c++ +graph::dijkstra_shortest_paths(g, 0, distances, predecessors, weight, ShowProgress{}); +``` + +Name `on_discover_vertex` is one of the event handlers in visitors. For a comprehensive +list, see section [Visitors](../reference/visitors.md). \ No newline at end of file