Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed doc/Identifiers.docx
Binary file not shown.
67 changes: 67 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -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 <cassert>
#include <vector>
#include <graph/views/breadth_first_search.hpp>

// `vector<vector<int>>` is recognized as an adjacency list by this library
std::vector<std::vector<int>> 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<int> 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<int>>`,
* `vector<vector<tuple<int, ...>>>`.

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). <br>
For a reference documentaiton, see section [reference](./reference).
96 changes: 96 additions & 0 deletions doc/reference/algorithms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Algorithms

## Definitions

A graph path _p_ is a possibly empty sequence of graph edges (_e_<sub>0</sub>, _e_<sub>1</sub>, ..., _e_<sub>_N_</sub>) where:
* _e_<sub>_i_</sub> ≠ _e_<sub>_j_</sub> for _i_ ≠ _j_,
* target(_e_<sub>_i_</sub>) = source(_e_<sub>_i_+1</sub>),
* source(_e_<sub>_i_</sub>) != source(_e_<sub>_j_</sub>) for _i_ ≠ _j_.

<code><i>path-source</i>(<i>p</i>)</code> = source(_e_<sub>0</sub>). <code><i>path-target</i>(<i>p</i>)</code> = target(_e_<sub>_N_</sub>).

<code><i>distance(p)</i></code> is a sum over _i_ of <code>weight</code>(_e_<sub>_i_</sub>).

<code><i>shortest-path</i>(g, u, v)</code> is a path in the set of all paths `p` in graph `g` with <code><i>path-source</i>(<i>p</i>)</code> = `u`
and <code><i>path-target</i>(<i>p</i>)</code> = v that has the smallest value of <code><i>distance(p)</i></code>.

<code><i>shortest-path-distance</i>(g, u, v)</code> is <code><i>distance</i>(<i>shortest-path</i>(g, u, v))</code> if it exists and _infinite-distance_ otherwise.

<code><i>shortest-path-predecessor</i>(g, u, v)</code>, in the set of all shortest paths <code><i>shortest-path</i>(g, u, v)</code> 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 `<graph/algorithm/dijkstra_shortest_paths.hpp>`

```c++
template <index_adjacency_list G,
std::ranges::random_access_range Distances,
std::ranges::random_access_range Predecessors,
class WF = function<std::ranges::range_value_t<Distances>(edge_reference_t<G>)>,
class Visitor = empty_visitor,
class Compare = less<std::ranges::range_value_t<Distances>>,
class Combine = plus<std::ranges::range_value_t<Distances>>>
requires std::is_arithmetic_v<std::ranges::range_value_t<Distances>> &&
std::ranges::sized_range<Distances> &&
std::ranges::sized_range<Predecessors> &&
convertible_to<vertex_id_t<G>, std::ranges::range_value_t<Predecessors>> &&
basic_edge_weight_function<G, WF, std::ranges::range_value_t<Distances>, Compare, Combine>
constexpr void dijkstra_shortest_distances(
G&& g,
vertex_id_t<G> source,
Distances& distances,
Predecessors& predecessor,
WF&& weight = [](edge_reference_t<G> uv) { return std::ranges::range_value_t<Distances>(1); },
Visitor&& visitor = empty_visitor(),
Compare&& compare = less<std::ranges::range_value_t<Distances>>(),
Combine&& combine = plus<std::ranges::range_value_t<Distances>>());
```

*Preconditions:*
* <code>distances[<i>i</i>] == shortest_path_infinite_distance&lt;range_value_t&lt;Distances&gt;&gt;()</code> for each <code><i>i</i></code> in range [`0`; `num_vertices(g)`),
* <code>predecessor[<i>i</i>] == <i>i</i></code> for each <code><i>i</i></code> 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 <code><i>i</i></code> in range [`0`; `num_vertices(g)`):
* <code>distances[<i>i</i>]</code> is <code><i>shortest-path-distance</i>(g, source, <i>i</i>)</code>,
* <code>predecessor[<i>i</i>]</code> is <code><i>shortest-path-predecessor</i>(g, source, <i>i</i>)</code>.

*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...
173 changes: 173 additions & 0 deletions doc/reference/customization_points.md
Original file line number Diff line number Diff line change
@@ -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 `<graph/graph.hpp>`.


## 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<G>` | vertex in `g` |
| `ui` | `graph::vertex_iterator_t<G>` | iterator to a vertex in `g` |
| `uid` | `graph::vertex_id_t<G>` | _id_ of a vertex in `g` (often an index) |
| `uv` | `graph::edge_reference_t<G>` | 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<G>`.

#### 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. <br>
We also use its return type to determine the type of the vertex id: `vertex_id_t<G>`.

#### 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 <code>static_cast&lt;<em>vertex-id-t</em>&lt;G&gt;&gt;(ui - begin(vertices(g)))</code>,
if `std::ranges::random_access_range<vertex_range_t<G>>` is `true`, where <code><em>vertex-id-t</em></code> is defined as:

* `I`, when the type of `G` matches pattern `ranges::forward_list<ranges::forward_list<I>>` and `I` is `std::integral`,
* `I0`, when the type of `G` matches pattern <code>ranges::forward_list&lt;ranges::forward_list&lt;<em>tuple-like</em>&lt;I0, ...&gt;&gt;&gt;</code> 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`. <br>
We also use its return type to determine the type of the edge type: `edge_t<G>`.

#### 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<G>` 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<G>` 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<G>` is `std::ranges::forward_range`, and
* `std::ranges::range_value_t<std::ranges::range_value_t<G>>` is `std::integral`.
4. Returns `get<0>(uv)`, if
* `G` is `std::ranges::forward_range`, and
* `std::ranges::range_value_t<G>` is `std::ranges::forward_range`, and
* `std::ranges::range_value_t<std::ranges::range_value_t<G>>` is <code><em>tuple-like</em></code>, and
* `std::tuple_element_t<0, std::ranges::range_value_t<std::ranges::range_value_t<G>>>` 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<G>` 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)`

49 changes: 49 additions & 0 deletions doc/reference/utilities.md
Original file line number Diff line number Diff line change
@@ -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 <graph/graph_info.hpp>

namespace graph {

template <class VId, class V, class VV>
struct vertex_info {
using id_type = VId; // usually vertex_id_t<G>
using vertex_type = V; // usually vertex_reference_t<G>
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 <graph/graph_info.hpp>

namespace graph {

template <class VId, bool Sourced, class E, class EV>
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<G>
using edge_type = E; // usually edge_reference_t<G>
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`
};

}
```
Loading
Loading