29 Commits

Author SHA1 Message Date
warrence 95f9b06088 Merge pull request 'Release 0.2.4' (#7) from release-0.2.4 into main
Reviewed-on: #7
2026-07-31 09:38:27 +02:00
warrence b752e3f9d4 Update package version 2026-07-31 09:34:13 +02:00
warrence 1b79809e2f Remove deprecated method calls from map tests 2026-07-31 09:33:26 +02:00
warrence 242aba09e8 Rename VertexMap/EdgeMap::extend to "expand"
This avoids the naming conflict with e.g. Vec::extend
2026-07-31 09:32:52 +02:00
warrence 1b8df19d00 Remove tests for deprecated VertexMap/EdgeMap::sync 2026-07-31 09:31:21 +02:00
warrence 517b83b328 Add warning for missing docs 2026-07-31 08:50:19 +02:00
warrence dd4e959554 Merge branch 'docs' into release-0.2.4 2026-07-31 08:44:10 +02:00
warrence cf1dafceb7 Add remaining docs for algorithms module 2026-07-28 22:57:55 +02:00
warrence 9a1e2faa9a Allow missing docs on deprecated maps methods 2026-07-28 22:56:51 +02:00
warrence 08b2c4db65 Add documentation for Dijkstra's algorithm, and rearrange related functions 2026-07-27 09:58:41 +02:00
warrence dfa8f8f0df Add EntityMap::extend and deprecate VertexMap/EdgeMap::sync 2026-07-27 08:56:58 +02:00
warrence 43c4ba45a2 Add documentation for maps module 2026-07-27 08:47:19 +02:00
warrence 99bab32f8c Add module level documentation for prelude, models, and testing 2026-07-24 20:57:19 +02:00
warrence 6e80b2fc9a Update traits module documentation 2026-07-24 20:56:39 +02:00
warrence b5cc3b1c3f Add documentation for Graph 2026-07-24 20:55:41 +02:00
warrence 21e79c660f Update documentation for AppendGraph 2026-07-24 20:55:01 +02:00
warrence 9f76ce4ce3 Fix vertex_capacity and edge_capacity in AppendGraph
Fix AppendGraph::vertex_capacity and AppendGraph::edge_capacity to use the actual capacity of the underlying Vecs.
2026-07-24 10:09:37 +02:00
warrence d9dc102993 Add documentation for models::append_graph module 2026-07-24 10:06:43 +02:00
warrence 3fa4b66981 Fix IncidenceCursor for AppendGraph and Graph, and add tests 2026-07-17 22:06:28 +02:00
warrence 8f9f0bf4c8 Add language tag to readme example code for syntax highlighting 2026-07-17 11:21:31 +02:00
warrence 5fa415841e Merge pull request 'Documentation updates' (#6) from release-0.2.3 into main
Reviewed-on: #6
2026-07-17 11:16:49 +02:00
warrence e8568a084a Fix doc example for early doc release 2026-07-17 11:15:12 +02:00
warrence e6b08d9148 Remove doc warnings for early doc release 2026-07-17 11:13:31 +02:00
warrence b62c263b08 Update package version 2026-07-17 11:12:21 +02:00
warrence 7345684f00 Add documentation for traits module 2026-07-17 11:04:08 +02:00
warrence 2c53d92943 Fix readme typo 2026-07-17 11:00:02 +02:00
warrence c131f520f4 Add crate doc front-page, and update readme 2026-07-14 20:01:08 +02:00
warrence b6c27ea605 Merge branch 'main' into docs 2026-07-14 09:54:17 +02:00
warrence 818b37ec15 Add linter warning for missing docs 2026-07-10 10:18:39 +02:00
12 changed files with 1274 additions and 92 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "grapherity"
version = "0.2.2"
version = "0.2.4"
authors = ["Stefan Müller"]
edition = "2024"
rust-version = "1.85.0"
+58 -3
View File
@@ -1,14 +1,69 @@
# grapherity
Library for graph models and algorithms.
Graph models and algorithms.
This library is still experimental and its API may therefore change frequently.
This crate provides data structures to model graphs and to perform algorithms on these data structures. The functionality presented is designed to be easy to use, flexible, and performant.
Currently supported are:
* Undirected graph types `Graph` and `AppendGraph`, built on a flat, index-based adjacency list,
* Maps to freely associate custom data with vertices and edges,
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
* Exposed traits to implement custom graph data structures or algorithms.
For more information, see the [documentation](https://docs.rs/grapherity/latest/grapherity/).
## Example
```rust
// Brings commonly used traits into scope.
use grapherity::prelude::*;
use grapherity::models::Graph;
use grapherity::algorithms;
let mut graph = Graph::new();
// Creates a map for edge weights with a default weight.
let mut weights = graph.edge_map(1);
// Adds three vertices and two edges.
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let v3 = graph.add_vertex();
graph.add_edge(v1, v2);
let e = graph.add_edge(v2, v3);
// Sets a non-default weight for the second edge.
weights[e] = 5;
// Checks the number of vertices.
assert_eq!(graph.vertex_count(), 3);
// Checks the number of edges.
assert_eq!(graph.edge_count(), 2);
// Checks that v1 and v2 are adjacent.
assert!(graph.are_adjacent(v1, v2));
// Checks that v1 and v3 are not adjacent.
assert!(!graph.are_adjacent(v1, v3));
// Checks the sum of all vertex degrees.
let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
assert_eq!(sum, 1 + 2 + 1);
// Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
assert_eq!(result.distances[v3], Some(1 + 5));
assert_eq!(result.predecessors[v3], Some(v2));
// Deletes the middle vertex and its incident edges.
graph.delete_vertex(v2);
// Calls Dijkstra's algorithm again on the now disconnected graph.
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
assert_eq!(result.distances[v3], None);
assert_eq!(result.predecessors[v3], None);
```
## License
This project is dual-licensed under the terms of both:
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit)
You may choose either license for your use of this software.
+446 -7
View File
@@ -1,3 +1,33 @@
//! Algorithms for graph topologies.
//!
//! # Dijkstra's algorithm
//!
//! [Dijkstra's algorithm] finds the shortest distances from a source vertex to all other vertices
//! in the graph. Note that for unweighted graphs it has worse time and space complexity than
//! [Breadth-first search](#breadth-first-search).
//!
//! Variants: [`dijkstra`], [`dijkstra_distances`], [`dijkstra_unweighted`],
//! [`dijkstra_distances_unweighted`].
//!
//! # Breadth-first search
//!
//! [Breadth-first search] traverses a graph topology, exploring all neighboring vertices first
//! before descending further into their neighborhoods.
//!
//! Variants: [`bfs`], [`bfs_distances`], [`bfs_find`], [`bfs_find_where`].
//!
//! # Depth-first search
//!
//! [Depth-first search] traverses a graph topology, exploring each branch path as far as possible
//! first before backtracking and exploring other branches.
//!
//! Variants: [`dfs`], [`dfs_visited`], [`dfs_find`], [`dfs_find_where`], [`dfs_find_path`],
//! [`dfs_find_path_where`].
//!
//! [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
//! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
//! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
use std::cmp::Ordering;
use std::collections::{BinaryHeap, VecDeque};
@@ -22,12 +52,45 @@ impl<V: Eq> Ord for DistanceOrderedVertex<V> {
}
}
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
pub struct DijkstraResult<V: Copy> {
/// Vertex map of minimum distances from a given `source` vertex.
pub distances: VertexMap<V, Option<u32>>,
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>,
}
// TODO: Generalize the return type of the weight function.
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors.
///
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
/// by `weight` function. Returns the distances from `source` to each vertex, and the predecessors
/// of each vertex on some shortest path from `source` to that vertex. Returns `None` for any vertex
/// not connected to `source`.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dijkstra;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// let e = graph.add_edge(source, target);
/// let mut weights = graph.edge_map(1);
/// weights[e] = 5;
///
/// let result = dijkstra(&graph, source, |e| weights[e]);
/// assert_eq!(result.distances[target], Some(5));
/// assert_eq!(result.predecessors[target], Some(source));
/// ```
///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex>
where
G: GraphTopology,
@@ -43,13 +106,34 @@ where
}
}
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
where
G: GraphTopology,
{
dijkstra(graph, source, |_| 1)
}
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances.
///
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
/// by `weight` function. Returns the distances from `source` to each vertex. Returns `None` for any
/// vertex not connected to `source`.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dijkstra_distances;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// let e = graph.add_edge(source, target);
/// let mut weights = graph.edge_map(1);
/// weights[e] = 5;
///
/// let distances = dijkstra_distances(&graph, source, |e| weights[e]);
/// assert_eq!(distances[target], Some(5));
/// ```
///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra_distances<G, W>(
graph: &G,
source: G::Vertex,
@@ -62,6 +146,70 @@ where
dijkstra_impl(graph, source, weight, |_, _| {})
}
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances and predecessors.
///
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
/// the distances from `source` to each vertex, and the predecessors of each vertex on some shortest
/// path from `source` to that vertex. Returns `None` for any vertex not connected to `source`.
///
/// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dijkstra_unweighted;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let result = dijkstra_unweighted(&graph, source);
/// assert_eq!(result.distances[target], Some(1));
/// assert_eq!(result.predecessors[target], Some(source));
/// ```
///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
where
G: GraphTopology,
{
dijkstra(graph, source, |_| 1)
}
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances.
///
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
/// the distances from `source` to each vertex. Returns `None` for any vertex not connected to
/// `source`.
///
/// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dijkstra_distances_unweighted;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let distances = dijkstra_distances_unweighted(&graph, source);
/// assert_eq!(distances[target], Some(1));
/// ```
///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra_distances_unweighted<G>(
graph: &G,
source: G::Vertex,
@@ -112,11 +260,44 @@ where
distances
}
/// Return data type for [`bfs`].
pub struct BfsResult<V: Copy> {
/// Vertex map of minimum distances from a given `source` vertex.
pub distances: VertexMap<V, Option<u32>>,
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>,
}
/// [Breadth-first search] traversal from `source`, returns distances and predecessors.
///
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
/// further. Returns the distances from `source` to each vertex, and the predecessors of each vertex
/// on some shortest path from `source` to that vertex. Returns `None` for any vertex not connected
/// to `source`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::bfs;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let result = bfs(&graph, source);
/// assert_eq!(result.distances[target], Some(1));
/// assert_eq!(result.predecessors[target], Some(source));
/// ```
///
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs<G>(graph: &G, source: G::Vertex) -> BfsResult<G::Vertex>
where
G: GraphTopology,
@@ -132,6 +313,34 @@ where
}
}
/// [Breadth-first search] traversal from `source`, returns distances.
///
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
/// further. Returns the distances from `source` to each vertex. Returns `None` for any vertex not
/// connected to `source`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::bfs_distances;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let distances = bfs_distances(&graph, source);
/// assert_eq!(distances[target], Some(1));
/// ```
///
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Option<u32>>
where
G: GraphTopology,
@@ -139,6 +348,33 @@ where
bfs_impl(graph, source, |_, _| true).distances
}
/// [Breadth-first search] from `source` for `target`, returns the distance.
///
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring all
/// neighbors before descending further. Returns the minimum distance from `source` to `target` if
/// `target` is a valid vertex of `graph` connected to `source`, or `None` otherwise.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::bfs_find;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// assert_eq!(bfs_find(&graph, source, target), Some(1));
/// ```
///
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
where
G: GraphTopology,
@@ -146,6 +382,35 @@ where
bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance)
}
/// [Breadth-first search] from `source` for a vertex matching `predicate`, returns the vertex and
/// its distance.
///
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
/// found, exploring all neighbors before descending further. Returns the first vertex satisfying
/// `predicate` and its minimum distance from `source`, or `None` if no such vertex is connected to
/// `source`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::bfs_find_where;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// assert_eq!(bfs_find_where(&graph, source, |v| v == target), Some((target, 1)));
/// ```
///
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)>
where
G: GraphTopology,
@@ -194,11 +459,44 @@ where
}
}
/// Return data type for [`dfs`].
pub struct DfsResult<V: Copy> {
/// Vertex map indicating which vertices were visited during the search.
pub visited: VertexMap<V, bool>,
/// Vertex map of predecessors on the DFS tree path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>,
}
/// [Depth-first search] traversal from `source`, returns visited vertices and predecessors.
///
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
/// before backtracking. Returns for each vertex whether it was visited, and the predecessors of
/// each vertex on the DFS tree path from `source` to that vertex. Any vertex not connected to
/// `source` is marked unvisited and has no predecessor.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let result = dfs(&graph, source);
/// assert!(result.visited[target]);
/// assert_eq!(result.predecessors[target], Some(source));
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs<G>(graph: &G, source: G::Vertex) -> DfsResult<G::Vertex>
where
G: GraphTopology,
@@ -214,6 +512,34 @@ where
}
}
/// [Depth-first search] traversal from `source`, returns visited vertices.
///
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
/// before backtracking. Returns a map indicating which vertices were visited, i.e. which vertices
/// are connected to `source`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs_visited;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// let visited = dfs_visited(&graph, source);
/// assert!(visited[target]);
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool>
where
G: GraphTopology,
@@ -221,6 +547,33 @@ where
dfs_impl(graph, source, |_, _| true).visited
}
/// [Depth-first search] from `source` for `target`, returns whether it was found.
///
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
/// branch as far as possible before backtracking. Returns `true` if `target` is a valid vertex of
/// `graph` connected to `source`, or `false` otherwise.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs_find;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// assert!(dfs_find(&graph, source, target));
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
where
G: GraphTopology,
@@ -228,6 +581,33 @@ where
dfs_find_where(graph, source, |v| v == target).is_some()
}
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the vertex.
///
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
/// found, exploring each branch as far as possible before backtracking. Returns the first vertex
/// satisfying `predicate`, or `None` if no such vertex is connected to `source`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs_find_where;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// graph.add_edge(source, target);
///
/// assert_eq!(dfs_find_where(&graph, source, |v| v == target), Some(target));
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<G::Vertex>
where
G: GraphTopology,
@@ -275,6 +655,35 @@ where
}
}
/// [Depth-first search] from `source` for `target`, returns the path as a sequence of edges.
///
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
/// branch as far as possible before backtracking. Returns some path from `source` to `target` as a
/// sequence of edges in traversal order if `target` is a valid vertex of `graph` connected to
/// `source`, or `None` otherwise. The returned path is empty if and only if `source` equals
/// `target`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs_find_path;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// let e = graph.add_edge(source, target);
///
/// assert_eq!(dfs_find_path(&graph, source, target), Some(vec![e]));
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
where
G: GraphTopology,
@@ -282,6 +691,36 @@ where
dfs_find_path_where(graph, source, |v| v == target)
}
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the path as a
/// sequence of edges.
///
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
/// found, exploring each branch as far as possible before backtracking. Returns some path from
/// `source` to the first vertex satisfying `predicate` as a sequence of edges in traversal order,
/// or `None` if no such vertex is connected to `source`. The returned path is empty if and only if
/// `source` satisfies `predicate`.
///
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
///
/// # Panics
///
/// Panics if `source` is not a valid vertex of `graph`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::algorithms::dfs_find_path_where;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let source = graph.add_vertex();
/// let target = graph.add_vertex();
/// let e = graph.add_edge(source, target);
///
/// assert_eq!(dfs_find_path_where(&graph, source, |v| v == target), Some(vec![e]));
/// ```
///
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
where
G: GraphTopology,
+132
View File
@@ -1,8 +1,140 @@
//! Graph models and algorithms.
//!
//! This crate provides data structures to model graphs and to perform algorithms on these data
//! structures. The functionality presented is designed to be easy to use, flexible, and performant.
//!
//! Currently supported are:
//!
//! * Undirected graph types [`Graph`] and [`AppendGraph`] built on a flat, index-based adjacency
//! list,
//! * [`VertexMap`] and [`EdgeMap`] to freely associate custom data with vertices and edges,
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
//! variants,
//! * Exposed traits to implement custom graph data structures or algorithms.
//!
//! # Usage example
//!
//! This example demonstrates some of this library's features.
//!
//! ```
//! // Brings commonly used traits into scope.
//! use grapherity::prelude::*;
//! use grapherity::models::Graph;
//! use grapherity::algorithms;
//!
//! let mut graph = Graph::new();
//! // Creates a map for edge weights with a default weight.
//! let mut weights = graph.edge_map(1);
//! // Adds three vertices and two edges.
//! let v1 = graph.add_vertex();
//! let v2 = graph.add_vertex();
//! let v3 = graph.add_vertex();
//! graph.add_edge(v1, v2);
//! let e = graph.add_edge(v2, v3);
//! // Sets a non-default weight for the second edge.
//! weights[e] = 5;
//!
//! // Checks the number of vertices.
//! assert_eq!(graph.vertex_count(), 3);
//! // Checks the number of edges.
//! assert_eq!(graph.edge_count(), 2);
//! // Checks that v1 and v2 are adjacent.
//! assert!(graph.are_adjacent(v1, v2));
//! // Checks that v1 and v3 are not adjacent.
//! assert!(!graph.are_adjacent(v1, v3));
//! // Checks the sum of all vertex degrees.
//! let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
//! assert_eq!(sum, 1 + 2 + 1);
//!
//! // Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
//! assert_eq!(result.distances[v3], Some(1 + 5));
//! assert_eq!(result.predecessors[v3], Some(v2));
//!
//! // Deletes the middle vertex and its incident edges.
//! graph.delete_vertex(v2);
//!
//! // Calls Dijkstra's algorithm again on the now disconnected graph.
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
//! assert_eq!(result.distances[v3], None);
//! assert_eq!(result.predecessors[v3], None);
//! ```
//!
//! # Graph types
//!
//! [`Graph`] and [`AppendGraph`] both implement [`GraphTopology`] using a flat, index-based adjacency
//! list to store edges, which means that vertex and edge insertions happen in `O(1)`. Degree
//! lookups are also `O(1)`.
//!
//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
//! vertices and edges.
//!
//! [`AppendGraph`] does not support deletion, vertices and edges can only be added, allowing it to
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
//! tracking needed for stable handles after deletion.
//!
//! # Vertices and edges
//!
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
//! to denote vertices and edges. This means that vertices and edges returned by graph functions
//! never change, even if the underlying graph data structure is mutated. However, vertices and
//! edges can become invalid when the denoted element is deleted. Code using an invalid vertex or
//! edge in a graph method will panic.
//!
//! ```should_panic
//! # use grapherity::prelude::*;
//! # use grapherity::models::Graph;
//! let mut graph = Graph::new();
//! let v = graph.add_vertex();
//! graph.delete_vertex(v);
//! // This will panic because 'v' was deleted and is not valid anymore.
//! graph.degree(v);
//! ```
//!
//! [`VertexMap`] and [`EdgeMap`] are not required to panic if provided with an invalid
//! vertex or edge.
//!
//! Vertices and edges are provided by the graph and not intended to be generated by users.
//!
//! # Loops and multi-edges
//!
//! Loops and multi edges are supported as well. In the following example, `v1` will have two loops
//! and two edges to `v2`.
//!
//! ```
//! # use grapherity::prelude::*;
//! # use grapherity::models::Graph;
//! let mut graph = Graph::new();
//! let v1 = graph.add_vertex();
//! let v2 = graph.add_vertex();
//! for _ in 0..2 {
//! graph.add_edge(v1, v1);
//! graph.add_edge(v1, v2);
//! }
//! assert_eq!(graph.edge_count(), 4);
//! assert_eq!(graph.degree(v1), 6);
//! ```
//!
//! [BFS]: crate::algorithms::bfs
//! [Dijkstra's algorithm]: crate::algorithms::dijkstra
//! [DFS]: crate::algorithms::dfs
//! [`EdgeMap`]: crate::maps::EdgeMap
//! [`VertexMap`]: crate::maps::VertexMap
//! [`AppendGraph`]: crate::models::AppendGraph
//! [`Graph`]: crate::models::Graph
//! [`GraphTopology`]: crate::traits::GraphTopology
//! [`GraphTopology::Edge`]: crate::traits::GraphTopology::Edge
//! [`GraphTopology::Vertex`]: crate::traits::GraphTopology::Vertex
//! [`GraphTopologyDeletion`]: crate::traits::GraphTopologyDeletion
#![warn(missing_docs)]
pub mod algorithms;
pub mod maps;
pub mod models;
pub mod traits;
/// Convenience re-exports of graph topology traits for common use.
pub mod prelude {
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
}
+67 -6
View File
@@ -1,31 +1,65 @@
//! Provides maps to associate custom data to graph vertices and edges.
//!
//! [`VertexMap`] and [`EdgeMap`] provide copy-on-write, [`Vec`]-backed maps to associate data to
//! all vertices or all edges in a graph, respectively.
use std::ops::{Index, IndexMut};
use crate::traits::GraphTopology;
/// A map to associate custom data to graph vertices.
///
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph
/// vertices. The implementation uses a [`Vec`], allocating contiguous slots for the data, which
/// means that the provided index conversion function should map vertices to contiguous indices, or
/// indices with relatively few gaps.
///
/// Data allocation happens as copy-on-write, i.e. the backing [`Vec`] is only resized if a value
/// beyond current capacity is written, but the `default` value can transparently be read. No bound
/// or validity checks are performed by the map on the provided vertex handles.
///
/// Use [`GraphTopology::vertex_map`] to obtain a `VertexMap`.
pub struct VertexMap<V: Copy, T: Clone> {
inner: EntityMap<V, T>,
}
impl<V: Copy, T: Clone> VertexMap<V, T> {
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
/// and an initial `capacity`.
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
Self {
inner: EntityMap::new(default, to_index, capacity),
}
}
// Reads beyond 'capacity' are valid and return the default value.
/// Returns the total number of data entries the map can write without reallocating. Reads
/// beyond `capacity` are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.capacity()
}
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
/// capacity is already sufficient.
///
/// Use this before writing data for new graph vertices to avoid incremental growth on the first
/// write to each new vertex.
pub fn expand(&mut self, capacity: usize) {
self.inner.expand(capacity);
}
#[deprecated(
since = "0.2.4",
note = "use 'expand(graph.vertex_capacity())' instead"
)]
#[allow(missing_docs)]
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
self.inner.resize(graph.vertex_capacity());
self.inner.expand(graph.vertex_capacity());
}
}
@@ -43,29 +77,56 @@ impl<V: Copy, T: Clone> IndexMut<V> for VertexMap<V, T> {
}
}
/// A map to associate custom data to graph edges.
///
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph edges.
/// The implementation uses a [`Vec`], allocating contiguous slots for the data, which means that
/// the provided index conversion function should map edges to contiguous indices, or indices with
/// relatively few gaps.
///
/// Data allocation happens as copy-on-write, i.e. the backing [`Vec`] is only resized if a value
/// beyond current capacity is written, but the `default` value can transparently be read. No bound
/// or validity checks are performed by the map on the provided edge handles.
///
/// Use [`GraphTopology::edge_map`] to obtain an `EdgeMap`.
pub struct EdgeMap<E: Copy, T: Clone> {
inner: EntityMap<E, T>,
}
impl<E: Copy, T: Clone> EdgeMap<E, T> {
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
/// and an initial `capacity`.
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
Self {
inner: EntityMap::new(default, to_index, capacity),
}
}
// Reads beyond 'capacity' are valid and return the default value.
/// Returns the total number of data entries the map can write without reallocating. Reads
/// beyond `capacity` are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.capacity()
}
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
/// capacity is already sufficient.
///
/// Use this before writing data for new graph edges to avoid incremental growth on the first
/// write to each new edge.
pub fn expand(&mut self, capacity: usize) {
self.inner.expand(capacity);
}
#[deprecated(since = "0.2.4", note = "use 'expand(graph.edge_capacity())' instead")]
#[allow(missing_docs)]
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
self.inner.resize(graph.edge_capacity());
self.inner.expand(graph.edge_capacity());
}
}
@@ -102,8 +163,8 @@ impl<E: Copy, T: Clone> EntityMap<E, T> {
self.data.len()
}
pub fn resize(&mut self, capacity: usize) {
if capacity > self.data.len() {
pub fn expand(&mut self, capacity: usize) {
if capacity > self.data.capacity() {
self.data.resize(capacity, self.default.clone());
}
}
+4
View File
@@ -1,5 +1,9 @@
//! Concrete graph topology models.
pub mod append_graph;
pub mod graph;
// TODO: Compressed-sparse-row graph model.
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
+56 -5
View File
@@ -1,13 +1,26 @@
//! [`AppendGraph`], an undirected graph topology supporting addition only.
use crate::maps::{EdgeMap, VertexMap};
use crate::traits::{GraphTopology, IncidenceCursor};
/// An opaque handle identifying a vertex in an [`AppendGraph`].
///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Vertex(usize);
/// An opaque handle identifying an edge in an [`AppendGraph`].
///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Edge(usize);
/// A [`VertexMap`] for [`AppendGraph`] vertices.
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
/// An [`EdgeMap`] for [`AppendGraph`] edges.
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
impl Edge {
@@ -16,6 +29,7 @@ impl Edge {
}
}
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
struct VertexIncidenceHeader {
incidence_count: usize,
first_incidence: Option<Edge>,
@@ -27,6 +41,9 @@ struct IncidenceEntry {
adjacent: Vertex,
}
/// A resumable cursor over the incidences of a single vertex in an [`AppendGraph`].
///
/// Obtain via [`AppendGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
#[derive(Copy, Clone)]
pub struct AppendGraphIncidenceCursor {
incidence: Option<Edge>,
@@ -34,16 +51,50 @@ pub struct AppendGraphIncidenceCursor {
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
graph.step_incidence(&mut self.incidence)
graph
.step_incidence(&mut self.incidence)
.map(|(v, e)| (v, e.normalize()))
}
}
/// An undirected graph that supports adding vertices and edges, but not deleting them.
///
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it
/// repeatedly. [`Vertex`] and [`Edge`] handles are never invalidated. Use [`Graph`] instead if you
/// need to remove vertices or edges.
///
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
/// vertex neighborhood traversals result in scattered index jumps.
///
/// # Examples
///
/// ```
/// use grapherity::prelude::*;
/// use grapherity::models::AppendGraph;
///
/// let mut graph = AppendGraph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let e = graph.add_edge(v1, v2);
/// assert!(graph.are_adjacent(v1, v2));
/// ```
///
/// # Time and space complexity
///
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
/// time since vertex degrees are stored. Space complexity is *O(|V| + |E|)*.
///
/// [`add_vertex`]: Self::add_vertex
/// [`add_edge`]: Self::add_edge
/// [`degree`]: Self::degree
/// [`Graph`]: crate::models::Graph
pub struct AppendGraph {
vertices: Vec<VertexIncidenceHeader>,
incidences: Vec<IncidenceEntry>,
}
impl AppendGraph {
/// Creates an empty graph instance with no vertices or edges.
pub fn new() -> Self {
Self {
vertices: vec![],
@@ -51,8 +102,8 @@ impl AppendGraph {
}
}
// Adds a single incidence of an edge, which is composed by two such incidences, to the
// incidences vector.
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences vector.
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
self.incidences.push(IncidenceEntry {
next: self.vertices[v1.0].first_incidence.take(),
@@ -91,7 +142,7 @@ impl GraphTopology for AppendGraph {
}
fn vertex_capacity(&self) -> usize {
self.vertices.len()
self.vertices.capacity()
}
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
@@ -103,7 +154,7 @@ impl GraphTopology for AppendGraph {
}
fn edge_capacity(&self) -> usize {
self.incidences.len() / 2
self.incidences.capacity() / 2
}
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
+88 -11
View File
@@ -1,12 +1,26 @@
//! [`Graph`], an undirected graph topology supporting addition and deletion.
use typed_generational_arena::{Arena, Index};
use crate::maps::{EdgeMap, VertexMap};
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
/// An opaque handle identifying a vertex in a [`Graph`].
///
/// Handles remain valid until the vertex is explicitly deleted. Obtain via graph methods like
/// [`Graph::add_vertex`] and [`Graph::vertices`].
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
/// An opaque handle identifying an edge in a [`Graph`].
///
/// Handles remain valid until the edge is explicitly deleted, or one of its endpoint vertices is
/// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`].
pub type Edge = Index<IncidenceEntry, usize, usize>;
/// A [`VertexMap`] for [`Graph`] vertices.
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
/// An [`EdgeMap`] for [`Graph`] edges.
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -15,11 +29,18 @@ struct VertexSlot(usize);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct IncidenceSlot(usize);
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not
/// intended for direct external use.
#[doc(hidden)]
pub struct VertexIncidenceHeader {
incidence_count: usize,
first_incidence: Option<IncidenceSlot>,
}
/// `pub` because [`Edge`] references it as a type parameter of the underlying arena. Not intended
/// for direct external use.
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct IncidenceEntry {
next: Option<IncidenceSlot>,
@@ -36,6 +57,9 @@ impl IncidentEdgeCursor {
}
}
/// A resumable cursor over the incidences of a single vertex in a [`Graph`].
///
/// Obtain via [`Graph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
#[derive(Copy, Clone)]
pub struct GraphIncidenceCursor {
incidence: Option<IncidenceSlot>,
@@ -43,12 +67,58 @@ pub struct GraphIncidenceCursor {
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
graph
.step_incidence(&mut self.incidence)
.map(|(vs, e)| (graph.vertices.get_idx(vs.0).unwrap(), e))
graph.step_incidence(&mut self.incidence).map(|(vs, e)| {
(
graph.vertices.get_idx(vs.0).unwrap(),
graph.normalize_edge(e),
)
})
}
}
/// An undirected graph that supports adding vertices and edges, and deleting them.
///
/// `Graph` is suited for workloads that need to modify the graph structure over time, i.e. adding
/// amd removing vertices and edges. [`Vertex`] and [`Edge`] handles remain valid until the vertex
/// or edge they identify is explicitly deleted. Use [`AppendGraph`] instead if you only need to
/// append vertices and edges.
///
/// Incidences are stored as interleaved adjacency lists in a generational [`Arena`]. In general,
/// vertex neighborhood traversals result in scattered memory accesses.
///
/// # Examples
///
/// ```
/// use grapherity::prelude::*;
/// use grapherity::models::Graph;
///
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let _e = graph.add_edge(v1, v2);
/// assert!(graph.are_adjacent(v1, v2));
/// graph.delete_vertex(v1);
/// assert_eq!(graph.degree(v2), 0);
/// ```
///
/// # Time and space complexity
///
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
/// time since vertex degrees are stored. [`delete_vertex`] runs in *O(degree(v))* time.
/// [`delete_edge`] runs in *O(degree(u) + degree(v))* time, where *u* and *v* are the edge's
/// endpoints.
///
/// Space complexity is *O(|V| + |E|)*, but freed slots for vertices and edges are not compacted and
/// their allocation is never reclaimed. Capacity only grows. If additions and deletions are both
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
/// potentially avoiding reallocation.
///
/// [`add_vertex`]: Self::add_vertex
/// [`add_edge`]: Self::add_edge
/// [`degree`]: Self::degree
/// [`delete_vertex`]: Self::delete_vertex
/// [`delete_edge`]: Self::delete_edge
/// [`AppendGraph`]: crate::models::AppendGraph
pub struct Graph {
// TODO: Arena index and generation types could be externalized to Graph.
vertices: Arena<VertexIncidenceHeader, usize, usize>,
@@ -56,6 +126,7 @@ pub struct Graph {
}
impl Graph {
/// Creates an empty graph instance with no vertices or edges.
pub fn new() -> Self {
Self {
vertices: Arena::new(),
@@ -63,8 +134,8 @@ impl Graph {
}
}
// Adds a single incidence of an edge, which is composed by two such incidences, to the
// incidences arena, and returns its index.
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences arena, and returns its index.
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
let edge = self.incidences.insert(IncidenceEntry {
next: self.vertices[v1].first_incidence.take(),
@@ -91,8 +162,9 @@ impl Graph {
(f, e_entry, f_entry)
}
// Updates the source vertex incidence list after the incidence "e" was deleted from the
// incidence arena. "next" is the next incidence after "e" in the source vertex incidence list.
/// Updates the `source` vertex incidence list after the incidence `e` was deleted from the
/// incidence arena. `next` is the next incidence after `e` in the `source` vertex incidence
/// list.
fn update_incidence_list(
&mut self,
e: Edge,
@@ -140,7 +212,12 @@ impl Graph {
}
fn normalize_edge(&self, e: Edge) -> Edge {
self.incidences.get_idx(e.arr_idx() & !1).unwrap()
let i = e.arr_idx();
if i & 1 == 0 {
e
} else {
self.incidences.get_idx(i ^ 1).unwrap()
}
}
}
@@ -267,10 +344,10 @@ impl GraphTopologyDeletion for Graph {
}
}
// The incidence entries are removed before patching the linked lists. This is safe because
// update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences
// the predecessor, never the removed entries themselves.
fn delete_edge(&mut self, e: Self::Edge) {
// The incidence entries are removed before patching the linked lists. This is safe because
// `update_incidence_list` searches by raw slot index (IncidenceSlot.0) and only
// dereferences the predecessor, never the removed entries themselves.
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
if e_entry.adjacent != f_entry.adjacent {
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
+2
View File
@@ -1,3 +1,5 @@
//! Test fixture and test macros for graph topology and algorithm implementations.
pub(crate) mod bfs_testing;
pub(crate) mod dfs_testing;
pub(crate) mod dijkstra_testing;
+197 -15
View File
@@ -298,7 +298,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
vertices.iter().filter(|&x| *x == v).count(),
1,
"unexpected vertex {v:?} from the iterator"
"unexpected vertex {v:?} from iterator"
);
}
}
@@ -339,7 +339,7 @@ macro_rules! graph_topology_tests {
.iter()
.position(|w| *w == v)
.expect(&format!(
"unexpected adjacent vertex {v:?} of {:?} from the iterator",
"unexpected adjacent vertex {v:?} of {:?} from iterator",
vertices[4]
));
expected_adjacency.swap_remove(i);
@@ -347,7 +347,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
expected_adjacency.len(),
0,
"expected adjacent vertices {:?} of {:?} were not matched by the iterator",
"expected adjacent vertices {:?} of {:?} were not matched by iterator",
expected_adjacency,
vertices[4]
);
@@ -484,7 +484,7 @@ macro_rules! graph_topology_tests {
assert_eq!(
edges.iter().filter(|&&(f, _, _)| f == e).count(),
1,
"unexpected edge {e:?} from the iterator"
"unexpected edge {e:?} from iterator"
);
}
}
@@ -518,14 +518,14 @@ macro_rules! graph_topology_tests {
.iter()
.position(|f| *f == e)
.expect(&format!(
"unexpected incident edge {e:?} of vertex {:?} from the iterator",
"unexpected incident edge {e:?} of vertex {:?} from iterator",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -572,7 +572,7 @@ macro_rules! graph_topology_tests {
}
assert!(
expected.is_empty(),
"expected incident edges {:?} of vertex {:?} were not matched by the iterator",
"expected incident edges {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -608,14 +608,14 @@ macro_rules! graph_topology_tests {
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from the iterator",
"unexpected incidence {incidence:?} of vertex {:?} from iterator",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} were not matched by the iterator",
"expected incidences {:?} of vertex {:?} were not matched by iterator",
expected,
vertices[i]
);
@@ -710,6 +710,131 @@ macro_rules! graph_topology_tests {
}
}
#[test]
fn incidence_cursor_empty() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let mut graph = <$T>::new();
let v = graph.add_vertex();
let mut cursor = graph.incidence_cursor(v);
assert_eq!(
cursor.next(&graph),
None,
"incidence cursor of vertex with degree 0 should immediately be exhausted"
);
}
#[test]
fn incidence_cursor() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let (graph, vertices, _, incidences) = make_test_graph();
for i in 0..10 {
let mut expected = incidences[i].clone();
let mut cursor = graph.incidence_cursor(vertices[i]);
while let Some(incidence) = cursor.next(&graph) {
let pos = expected
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor",
vertices[i]
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} were not matched by cursor",
expected,
vertices[i]
);
}
}
#[test]
fn incidence_cursor_loop_edge() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let mut graph = <$T>::new();
let v = graph.add_vertex();
let e = graph.add_edge(v, v);
let mut cursor = graph.incidence_cursor(v);
assert_eq!(
cursor.next(&graph),
Some((v, e)),
"vertex should be adjacent to itself"
);
assert_eq!(
cursor.next(&graph),
Some((v, e)),
"vertex should be adjacent to itself twice"
);
assert_eq!(
cursor.next(&graph),
None,
"too many incidences from cursor"
);
}
#[test]
fn incidence_cursor_multiple_edges() {
use $crate::traits::{GraphTopology, IncidenceCursor};
let k = 3;
let mut graph = <$T>::new();
let vertices = [graph.add_vertex(), graph.add_vertex()];
let mut edges = Vec::new();
for _ in 0..k {
edges.push(graph.add_edge(vertices[0], vertices[1]));
}
for i in 0..2 {
let mut cursor = graph.incidence_cursor(vertices[i]);
for j in 0..k {
let current = cursor.next(&graph).expect(&format!(
"incidence {j} missing, expected {k} incidences for vertex {:?}",
vertices[i]
));
assert_eq!(
current.0,
vertices[1 - i],
"unexpected adjacent vertex of vertex {:?} in incidence {j}",
vertices[i]
);
assert_eq!(
edges.iter().filter(|e| **e == current.1).count(),
1,
"unexpected incident edge {:?} of vertex {:?}",
current.1,
vertices[i],
);
}
assert_eq!(
cursor.next(&graph),
None,
"too many incidences of {:?} from cursor",
vertices[i]
);
}
}
#[test]
fn incidence_cursor_copy() {
use $crate::traits::{GraphTopology, IncidenceCursor};
// Constructs a graph with two vertices connected to `v`.
let mut graph = <$T>::new();
let v = graph.add_vertex();
for _ in 0..2 {
let u = graph.add_vertex();
graph.add_edge(u, v);
}
let mut c1 = graph.incidence_cursor(v);
assert!(c1.next(&graph).is_some(), "expected first incidence");
// Copies cursor mid-traversal.
let mut c2 = c1;
// Continues iteration with original cursor.
assert!(c1.next(&graph).is_some(), "expected second incidence from original cursor");
assert!(c1.next(&graph).is_none(), "expected original cursor to be exhausted");
// Replays from the copy point with the copied cursor.
assert!(c2.next(&graph).is_some(), "expected second incidence from copied cursor");
assert!(c2.next(&graph).is_none(), "expected copied cursor to be exhausted");
}
#[test]
fn incident_vertices_incidences_consistency() {
use $crate::traits::GraphTopology;
@@ -1103,8 +1228,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 {
let mut expected: Vec<_> = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.map(|(_, e)| *e)
.filter_map(|(_, e)| (*e != edges[2].0).then_some(*e))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1137,8 +1261,7 @@ macro_rules! graph_topology_deletion_tests {
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let mut expected: Vec<_> = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.map(|(_, e)| *e)
.filter_map(|(v, e)| (*v != vertices[2]).then_some(*e))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1213,14 +1336,73 @@ macro_rules! graph_topology_deletion_tests {
.iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} after delete",
"unexpected incidence {incidence:?} of vertex {:?} from iterator after delete",
v
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} not matched after delete",
"expected incidences {:?} of vertex {:?} not matched by iterator after delete",
expected,
v
);
}
#[test]
fn incidence_cursor_after_delete_vertex() {
use $crate::traits::GraphTopologyDeletion;
let (mut graph, vertices, _, incidences) = make_test_graph();
graph.delete_vertex(vertices[2]);
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let remaining = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
}
}
#[test]
fn incidence_cursor_after_delete_edge() {
use $crate::traits::GraphTopologyDeletion;
let (mut graph, vertices, edges, incidences) = make_test_graph();
// Deletes the edge from vertices[1] to vertices[2].
graph.delete_edge(edges[2].0);
for i in 0..10 {
let remaining = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
}
}
fn assert_vertex_incidence_cursor(
graph: &$T,
v: <$T as $crate::traits::GraphTopology>::Vertex,
mut expected: Vec<(
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Edge,
)>,
) {
use $crate::traits::IncidenceCursor;
let mut cursor = graph.incidence_cursor(v);
while let Some(incidence) = cursor.next(graph) {
let pos = expected
.iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor after delete",
v
));
expected.swap_remove(pos);
}
assert!(
expected.is_empty(),
"expected incidences {:?} of vertex {:?} not matched by cursor after delete",
expected,
v
);
+10 -44
View File
@@ -48,7 +48,7 @@ macro_rules! vertex_map_tests {
}
#[test]
fn sync_expands_to_new_vertices() {
fn expand_to_new_vertices() {
use $crate::traits::GraphTopology;
let mut graph = <$T>::new();
graph.add_vertex();
@@ -59,21 +59,21 @@ macro_rules! vertex_map_tests {
}
assert!(
map.capacity() < graph.vertex_capacity(),
"precondition: map is stale before sync"
"precondition: map is stale before expand"
);
map.sync(&graph);
map.expand(graph.vertex_capacity());
assert_eq!(map.capacity(), graph.vertex_capacity());
}
#[test]
fn sync_does_not_overwrite_existing_values() {
fn expand_does_not_overwrite_existing_values() {
use $crate::traits::GraphTopology;
let mut graph = <$T>::new();
let v = graph.add_vertex();
let mut map = graph.vertex_map(0);
map[v] = 5;
graph.add_vertex();
map.sync(&graph);
map.expand(graph.vertex_capacity());
assert_eq!(map[v], 5);
}
};
@@ -97,22 +97,6 @@ macro_rules! vertex_map_deletion_tests {
assert_eq!(map[v1], 1);
}
#[test]
fn capacity_does_not_shrink_after_delete() {
use $crate::traits::GraphTopology;
use $crate::traits::GraphTopologyDeletion;
let mut graph = <$T>::new();
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let mut map = graph.vertex_map(0);
map[v1] = 5;
let capacity_before = graph.vertex_capacity();
graph.delete_vertex(v2);
map.sync(&graph);
assert_eq!(map.capacity(), capacity_before);
assert_eq!(map[v1], 5);
}
#[test]
fn reused_slot_returns_old_value() {
use $crate::traits::GraphTopology;
@@ -190,7 +174,7 @@ macro_rules! edge_map_tests {
}
#[test]
fn sync_expands_to_new_edges() {
fn expand_to_new_edges() {
use $crate::traits::GraphTopology;
let mut graph = <$T>::new();
let v1 = graph.add_vertex();
@@ -203,14 +187,14 @@ macro_rules! edge_map_tests {
}
assert!(
map.capacity() < graph.edge_capacity(),
"precondition: map is stale before sync"
"precondition: map is stale before expand"
);
map.sync(&graph);
map.expand(graph.edge_capacity());
assert_eq!(map.capacity(), graph.edge_capacity());
}
#[test]
fn sync_does_not_overwrite_existing_values() {
fn expand_does_not_overwrite_existing_values() {
use $crate::traits::GraphTopology;
let mut graph = <$T>::new();
let v1 = graph.add_vertex();
@@ -219,7 +203,7 @@ macro_rules! edge_map_tests {
let mut map = graph.edge_map(0);
map[e] = 5;
graph.add_edge(v1, v2);
map.sync(&graph);
map.expand(graph.edge_capacity());
assert_eq!(map[e], 5);
}
};
@@ -245,24 +229,6 @@ macro_rules! edge_map_deletion_tests {
assert_eq!(map[e1], 1);
}
#[test]
fn capacity_does_not_shrink_after_delete() {
use $crate::traits::GraphTopology;
use $crate::traits::GraphTopologyDeletion;
let mut graph = <$T>::new();
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let e1 = graph.add_edge(v1, v2);
let e2 = graph.add_edge(v1, v2);
let mut map = graph.edge_map(0);
map[e1] = 5;
let capacity_before = graph.edge_capacity();
graph.delete_edge(e2);
map.sync(&graph);
assert_eq!(map.capacity(), capacity_before);
assert_eq!(map[e1], 5);
}
#[test]
fn reused_slot_returns_old_value() {
use $crate::traits::GraphTopology;
+213
View File
@@ -1,36 +1,249 @@
//! Core traits for undirected graph topologies.
use crate::maps::{EdgeMap, VertexMap};
// TODO: Add functions to reserve memory for vertices and edges.
// TODO: Split out GraphTopologyAddition trait.
// TODO: Introduce an Incidence struct.
/// A trait representing an undirected graph topology.
///
/// An undirected graph is a set of vertices and undirected edges, where each edge connects either
/// exactly two vertices or one vertex with itself (loop edge). This trait provides methods for
/// querying a graph topology, iterating over vertices and edges, and adding new vertices and edges.
///
/// # Vertices and edges
///
/// Vertices and edges are identified by opaque handles ([`Vertex`] and [`Edge`]) that implement
/// [`Copy`] and [`Eq`]. Handles remain valid for the lifetime of the graph unless the graph also
/// implements [`GraphTopologyDeletion`] and the item identified by the handle is explicitly
/// deleted.
///
/// Methods accepting vertices or edges as parameters panic if the handle was invalidated by a
/// deletion, and return incorrect results if the handle was not produced by this graph instance.
///
/// # Deletion
///
/// This trait covers graph construction and querying only. To delete vertices and edges, see
/// [`GraphTopologyDeletion`].
///
/// [`Edge`]: GraphTopology::Edge
/// [`Vertex`]: GraphTopology::Vertex
pub trait GraphTopology {
/// An opaque, stable handle identifying a vertex.
type Vertex: Copy + Eq;
/// An opaque, stable handle identifying an edge.
type Edge: Copy + Eq;
/// A resumable position in the incidence list of a vertex.
///
/// Prefer [`incidences`](Self::incidences) for straightforward iteration. A cursor is useful
/// when an algorithm needs to pause traversal, perform other graph queries or mutations, and
/// then continue from where it left off. This cursor type can be obtained via
/// [`incidence_cursor`](Self::incidence_cursor).
type IncidenceCursor: IncidenceCursor<Self> + Copy;
/// Returns the number of vertices in the graph.
fn vertex_count(&self) -> usize;
/// Returns the total number of vertices the graph can hold without reallocating.
fn vertex_capacity(&self) -> usize;
/// Creates and returns a [`VertexMap`] with every slot initialised to `default`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let mut labels = graph.vertex_map("z");
/// assert_eq!(labels[v1], "z");
/// labels[v1] = "a";
/// assert_eq!(labels[v1], "a");
///
/// // A new vertex is immediately available for read and write.
/// let v2 = graph.add_vertex();
/// assert_eq!(labels[v2], "z");
/// labels[v2] = "b";
/// assert_eq!(labels[v2], "b");
/// ```
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T>;
/// Returns the number of edges in the graph.
fn edge_count(&self) -> usize;
/// Returns the total number of edges the graph can hold without reallocating.
fn edge_capacity(&self) -> usize;
/// Creates and returns an [`EdgeMap`] with every slot initialised to `default`.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let e1 = graph.add_edge(v1, v2);
/// let mut weights = graph.edge_map(5);
/// assert_eq!(weights[e1], 5);
/// weights[e1] = 1;
/// assert_eq!(weights[e1], 1);
///
/// // A new edge is immediately available for read and write.
/// let e2 = graph.add_edge(v1, v2);
/// assert_eq!(weights[e2], 5);
/// weights[e2] = 2;
/// assert_eq!(weights[e2], 2);
/// ```
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T>;
/// Returns the degree of `v`, i.e. the number of incident edges, where each loop contributes 2.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn degree(&self, v: Self::Vertex) -> usize;
/// Returns `true` if there is at least one edge between `v1` and `v2`, and `false` otherwise.
///
/// A vertex is adjacent to itself if and only if it has a loop edge.
///
/// # Panics
///
/// Panics if `v1` or `v2` is not a valid vertex of this graph.
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool;
/// Returns an iterator over all vertices in the graph.
fn vertices(&self) -> impl Iterator<Item = Self::Vertex>;
/// Returns an iterator over all vertices adjacent to `v`.
///
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the edges are not
/// required, since implementors may be able to provide an iterator faster than the trivial
/// mapping. The complexity of this method must not exceed that of
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex>;
/// Returns the two endpoints of edge `e`, which are identical if and only if `e` is a loop
/// edge.
///
/// # Panics
///
/// Panics if `e` is not a valid edge of this graph.
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex);
/// Returns an iterator over all edges in the graph.
fn edges(&self) -> impl Iterator<Item = Self::Edge>;
/// Returns an iterator over all edges incident to `v`.
///
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the vertices are not
/// required, since implementors may be able to provide an iterator faster than the trivial
/// mapping. The complexity of this method must not exceed that of
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge>;
/// Returns an iterator over all incidences of `v`, i.e. vertex-edge pairs `(u, e)` such that
/// `u` is adjacent to `v` and `e` is an edge between them.
///
/// Use [`incidence_cursor`](Self::incidence_cursor) instead for a traversal that needs to
/// suspend and resume across other state updates.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)>;
/// Returns a cursor over all incidences of `v`, analogously to
/// [`incidences`](Self::incidences), initially positioned before the first one.
///
/// See [`IncidenceCursor`](Self::IncidenceCursor) for when to prefer a cursor over
/// [`incidences`](Self::incidences).
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
/// Adds a new isolated vertex and returns its handle.
fn add_vertex(&mut self) -> Self::Vertex;
/// Adds a new edge between `v1` and `v2` and returns its handle.
///
/// # Panics
///
/// Panics if `v1` or `v2` is not a valid vertex of this graph.
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge;
}
/// A trait that adds deletion operations to an undirected graph topology.
///
/// This trait provides methods for deletion of vertices and edges in an undirected graph. These
/// operations will invalidate handles to all deleted vertices and edges. Methods accepting invalid
/// vertices or edges as parameters panic.
pub trait GraphTopologyDeletion: GraphTopology {
/// Deletes the vertex `v` and all its incident edges from the graph. Note that this also
/// invalidates the handles of all edges incident to `v`.
///
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn delete_vertex(&mut self, v: Self::Vertex);
/// Deletes the edge `e` from the graph. This operation only invalidates `e` and no other vertex
/// or edge handles.
///
/// # Panics
///
/// Panics if `e` is not a valid edge of this graph.
fn delete_edge(&mut self, e: Self::Edge);
}
/// A cursor for traversing the incidences of a vertex one step at a time.
///
/// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See
/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over
/// [`GraphTopology::incidences`].
pub trait IncidenceCursor<G: GraphTopology + ?Sized> {
/// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the
/// traversal is exhausted.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::Graph;
/// # use grapherity::traits::IncidenceCursor;
/// // Constructs a graph with two vertices connected to `v`.
/// let mut graph = Graph::new();
/// let v = graph.add_vertex();
/// for _ in 0..2 {
/// let u = graph.add_vertex();
/// graph.add_edge(u, v);
/// }
///
/// // Iterates over the incidences of `v` with a cursor.
/// let mut c1 = graph.incidence_cursor(v);
/// assert!(c1.next(&graph).is_some());
/// let mut c2 = c1;
/// // Continues iteration with original cursor.
/// assert!(c1.next(&graph).is_some());
/// assert!(c1.next(&graph).is_none());
/// // Iterates over the last incidence again with the copied cursor.
/// assert!(c2.next(&graph).is_some());
/// assert!(c2.next(&graph).is_none());
/// ```
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
}