diff --git a/Cargo.toml b/Cargo.toml index 0ccf888..513fad2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grapherity" -version = "0.2.3" +version = "0.2.4" authors = ["Stefan Müller"] edition = "2024" rust-version = "1.85.0" diff --git a/README.md b/README.md index 9954e5b..417af7c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ For more information, see the [documentation](https://docs.rs/grapherity/latest/ ## Example -``` +```rust // Brings commonly used traits into scope. use grapherity::prelude::*; use grapherity::models::Graph; diff --git a/src/algorithms.rs b/src/algorithms.rs index b974441..5c6d147 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -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 Ord for DistanceOrderedVertex { } } +/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. pub struct DijkstraResult { + /// Vertex map of minimum distances from a given `source` vertex. pub distances: VertexMap>, + /// Vertex map of predecessors on some shortest path from a given `source` vertex. pub predecessors: VertexMap>, } // 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(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult where G: GraphTopology, @@ -43,13 +106,34 @@ where } } -pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult -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( 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(graph: &G, source: G::Vertex) -> DijkstraResult +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( graph: &G, source: G::Vertex, @@ -112,11 +260,44 @@ where distances } +/// Return data type for [`bfs`]. pub struct BfsResult { + /// Vertex map of minimum distances from a given `source` vertex. pub distances: VertexMap>, + /// Vertex map of predecessors on some shortest path from a given `source` vertex. pub predecessors: VertexMap>, } +/// [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(graph: &G, source: G::Vertex) -> BfsResult 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(graph: &G, source: G::Vertex) -> VertexMap> 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(graph: &G, source: G::Vertex, target: G::Vertex) -> Option 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(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 { + /// Vertex map indicating which vertices were visited during the search. pub visited: VertexMap, + /// Vertex map of predecessors on the DFS tree path from a given `source` vertex. pub predecessors: VertexMap>, } +/// [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(graph: &G, source: G::Vertex) -> DfsResult 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(graph: &G, source: G::Vertex) -> VertexMap 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(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(graph: &G, source: G::Vertex, predicate: P) -> Option 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(graph: &G, source: G::Vertex, target: G::Vertex) -> Option> 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(graph: &G, source: G::Vertex, predicate: P) -> Option> where G: GraphTopology, diff --git a/src/lib.rs b/src/lib.rs index 3b611e5..a926085 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,11 +127,14 @@ //! [`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}; } diff --git a/src/maps.rs b/src/maps.rs index 277ceb5..baf8cf8 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -1,30 +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 { inner: EntityMap, } impl VertexMap { + /// 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>(&mut self, graph: &G) { - self.inner.resize(graph.vertex_capacity()); + self.inner.expand(graph.vertex_capacity()); } } @@ -42,29 +77,56 @@ impl IndexMut for VertexMap { } } +/// 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 { inner: EntityMap, } impl EdgeMap { + /// 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>(&mut self, graph: &G) { - self.inner.resize(graph.edge_capacity()); + self.inner.expand(graph.edge_capacity()); } } @@ -101,8 +163,8 @@ impl EntityMap { 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()); } } diff --git a/src/models.rs b/src/models.rs index d6b95a4..4eb163e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,3 +1,5 @@ +//! Concrete graph topology models. + pub mod append_graph; pub mod graph; diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 9ccfef7..dacc404 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -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 = VertexMap; + +/// An [`EdgeMap`] for [`AppendGraph`] edges. pub type AppendGraphEdgeMap = EdgeMap; 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, @@ -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, @@ -34,16 +51,50 @@ pub struct AppendGraphIncidenceCursor { impl IncidenceCursor 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, incidences: Vec, } 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(&self, default: T) -> VertexMap { @@ -103,7 +154,7 @@ impl GraphTopology for AppendGraph { } fn edge_capacity(&self) -> usize { - self.incidences.len() / 2 + self.incidences.capacity() / 2 } fn edge_map(&self, default: T) -> EdgeMap { diff --git a/src/models/graph.rs b/src/models/graph.rs index ed86205..0943153 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -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; + +/// 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; +/// A [`VertexMap`] for [`Graph`] vertices. pub type GraphVertexMap = VertexMap; + +/// An [`EdgeMap`] for [`Graph`] edges. pub type GraphEdgeMap = EdgeMap; #[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, } +/// `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, @@ -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, @@ -43,12 +67,58 @@ pub struct GraphIncidenceCursor { impl IncidenceCursor 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, @@ -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); diff --git a/src/testing.rs b/src/testing.rs index a345512..3412447 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -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; diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 400290c..cd46e13 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -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 ); diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs index e8b5362..61ce663 100644 --- a/src/testing/maps_testing.rs +++ b/src/testing/maps_testing.rs @@ -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; diff --git a/src/traits.rs b/src/traits.rs index d98b55a..3597959 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,14 +1,17 @@ +//! 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 /// /// 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