diff --git a/doc/Identifiers.docx b/doc/Identifiers.docx deleted file mode 100644 index 87a1863..0000000 Binary files a/doc/Identifiers.docx and /dev/null differ 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/reference/algorithms.md b/doc/reference/algorithms.md new file mode 100644 index 0000000..e6e56db --- /dev/null +++ b/doc/reference/algorithms.md @@ -0,0 +1,96 @@ +# 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`. + + + +## `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 `` + +```c++ +template (edge_reference_t)>, + class Visitor = empty_visitor, + 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 std::ranges::range_value_t(1); }, + Visitor&& visitor = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()); +``` + +*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](./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)`): + * 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/reference/customization_points.md b/doc/reference/customization_points.md new file mode 100644 index 0000000..c4a59bc --- /dev/null +++ b/doc/reference/customization_points.md @@ -0,0 +1,173 @@ +# 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`. + +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. + +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 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 + + 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`. + +#### 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`. + 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)` + +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, and + * `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)` + +### `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/reference/utilities.md b/doc/reference/utilities.md new file mode 100644 index 0000000..75b4bcb --- /dev/null +++ b/doc/reference/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 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` +}; + +} +``` diff --git a/doc/reference/views.md b/doc/reference/views.md new file mode 100644 index 0000000..85dd8cd --- /dev/null +++ b/doc/reference/views.md @@ -0,0 +1,259 @@ +# 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. + + +## The "info" classes + +TODO: describe `vertex_info` and `edge_info`. + +## Breadth-first search views + +Header `` defines view factories in namespace `graph`: + +```c++ +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, source)) {} +for (auto [vid, uv, val] : edges_breadth_first_search(g, source, 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. +They differ by the element type they produce. + +Breadth-first traversal offers the following properties in terms of rendered vertices: + + * 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 + * there is no edge from `u` to `w`, + + then `v` will be rendered before `w`. + + +```c++ +template VI, + typename Alloc = std::allocator>> +std::ranges::input_range auto& +vertices_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); +``` + + +*Parameters:* + + * `g` – the graph representation, + * `source` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +*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 `source` is not an element in the returned range. + + +```c++ +template VI, + std::invocable&> VVF, + typename Alloc = std::allocator>> +std::ranges::input_range auto& +vertices_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `source` – the initial vertex, + * `vvf` – vertex value function, + * `alloc` – allocator to be used for the internal storage. + +*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&, std::invoke_result_t&>>`. + The third argument is the result of invoking `vvf` with the vertex reference. + +*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& source, Alloc alloc = Alloc()); +``` + +Parameters: + + * `g` – the graph representation, + * `source` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +*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 + `edge_info, false, edge_reference_t, void>`. + + +```c++ +template VI, + std::invocable> EVF, + typename Alloc = std::allocator>> +std::ranges::input_range auto& +edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); +``` + +*Parameters:* + + * `g` – the graph representation, + * `source` – the initial vertex, + * `evf` – edge value function, + * `alloc` – allocator to be used for the internal storage. + +*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 + `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::allocator>> +std::ranges::input_range auto& +sourced_edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); +``` + +*Parameters:* + + * `g` – the graph representation, + * `source` – the initial vertex, + * `alloc` – allocator to be used for the internal storage. + +*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 + `edge_info, true, edge_reference_t, void>`. + + +```c++ +template VI, + std::invocable> EVF, + typename Alloc = std::allocator>> +std::ranges::input_range auto& +sourced_edges_breadth_first_search(G&& g, const VI& source, Alloc alloc = Alloc()); +``` + +*Parameters:* + + * `g` – the graph representation, + * `source` – the initial vertex, + * `evf` – edge value function, + * `alloc` – allocator to be used for the internal storage. + +*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 + `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. + + + +## Incidence view + +Header `` defines view factories in namespace `graph`: + +```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. + +```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>`. + + + +## TODO: document other views \ No newline at end of file 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 new file mode 100644 index 0000000..55163fe --- /dev/null +++ b/doc/tutorial/algorithms.md @@ -0,0 +1,115 @@ +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, natively 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). + + +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 diff --git a/doc/tutorial/graph_container_interface.md b/doc/tutorial/graph_container_interface.md new file mode 100644 index 0000000..038469a --- /dev/null +++ b/doc/tutorial/graph_container_interface.md @@ -0,0 +1,57 @@ +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* (GCI). + + +```c++ + std::vector> g { // + /*0*/ {1}, // + /*1*/ {0} // (0) ----- (1) + }; // + + 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 + assert(id0 == 0); + + 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); // get the id of the vertex on the other side of the edge + assert(id1 == 1); + + 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 diff --git a/doc/tutorial/tutorial_other.md b/doc/tutorial/tutorial_other.md new file mode 100644 index 0000000..30f164e --- /dev/null +++ b/doc/tutorial/tutorial_other.md @@ -0,0 +1,169 @@ +# Tutorial + +Note: this part is under development... + +## 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 requirements 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 represented 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})); +} +``` + + + +## 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