diff --git a/Cargo.toml b/Cargo.toml index 513fad2..15834cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grapherity" -version = "0.2.4" +version = "0.3.0" authors = ["Stefan Müller"] edition = "2024" rust-version = "1.85.0" @@ -11,4 +11,5 @@ keywords = ["graph", "graph-algorithms"] categories = ["algorithms", "data-structures", "mathematics"] [dependencies] +priority-queue = "2.7.0" typed-generational-arena = "0.2.9" diff --git a/README.md b/README.md index 417af7c..a47f8b8 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ This crate provides data structures to model graphs and to perform algorithms on 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, +* Undirected graph types `Graph`, `AppendGraph`, and `FrozenGraph`, built on a flat, index-based adjacency list, +* `ElementMap` 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. @@ -22,14 +22,14 @@ 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); +// Creates a map for edge weights with a default weight, after creating the edge. +let mut weights = graph.edge_map(1); // Sets a non-default weight for the second edge. weights[e] = 5; diff --git a/src/algorithms.rs b/src/algorithms.rs index 5c6d147..19ff46d 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -28,46 +28,60 @@ //! [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}; +use priority_queue::PriorityQueue; +use std::cmp::Reverse; +use std::collections::VecDeque; +use std::hash::Hash; +use std::ops::Add; -use crate::maps::VertexMap; -use crate::traits::{GraphTopology, IncidenceCursor}; +use crate::maps::ElementMap; +use crate::traits::{GraphTopology, Incidence, IncidenceCursor}; -#[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { - distance: u32, - vertex: V, -} +/// Trait that edge weights for [`dijkstra`] and its variants need to satisfy. +/// +/// [`Default`] is used as the starting distance for `source`, so it must behave as the additive +/// identity (typically zero) for accumulation via [`Add`] to give correct results. +/// +/// # Examples +/// +/// ``` +/// # use grapherity::prelude::*; +/// # use grapherity::algorithms::dijkstra; +/// # use grapherity::models::Graph; +/// use std::time::Duration; +/// +/// let mut graph = Graph::new(); +/// let source = graph.add_vertex(); +/// let target = graph.add_vertex(); +/// let e = graph.add_edge(source, target); +/// let mut travel_time = graph.edge_map(Duration::ZERO); +/// travel_time[e] = Duration::from_secs(5); +/// +/// let result = dijkstra(&graph, source, |e| travel_time[e]); +/// assert_eq!(result.distances[target], Some(Duration::from_secs(5))); +/// ``` +pub trait Weight: Copy + Ord + Add + Default {} -impl PartialOrd for DistanceOrderedVertex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for DistanceOrderedVertex { - fn cmp(&self, other: &Self) -> Ordering { - other.distance.cmp(&self.distance) - } -} +impl + Default> Weight for T {} /// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. -pub struct DijkstraResult { +pub struct DijkstraResult { /// Vertex map of minimum distances from a given `source` vertex. - pub distances: VertexMap>, + pub distances: ElementMap>, /// Vertex map of predecessors on some shortest path from a given `source` vertex. - pub predecessors: VertexMap>, + pub predecessors: ElementMap>, } -// TODO: Generalize the return type of the weight function. +// TODO: A Fibonacci heap would lower complexity to O(|E| + |V| log |V|) by making decrease-key O(1) amortized instead of O(log |V|). No standard Rust implementation exists; high constant factors may negate the asymptotic gain in practice. /// [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 +/// by `weights` 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`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// # Panics /// /// Panics if `source` is not a valid vertex of `graph`. @@ -91,13 +105,18 @@ pub struct DijkstraResult { /// ``` /// /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -pub fn dijkstra(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult +pub fn dijkstra( + graph: &G, + source: G::Vertex, + weights: impl Fn(G::Edge) -> W, +) -> DijkstraResult where G: GraphTopology, - W: Fn(G::Edge) -> u32, + G::Vertex: Hash, + W: Weight, { let mut predecessors = graph.vertex_map(None); - let distances = dijkstra_impl(graph, source, weight, |adjacent, predecessor| { + let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| { predecessors[adjacent] = Some(predecessor); }); DijkstraResult { @@ -109,9 +128,11 @@ where /// [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 +/// by `weights` function. Returns the distances from `source` to each vertex. Returns `None` for any /// vertex not connected to `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// # Panics /// /// Panics if `source` is not a valid vertex of `graph`. @@ -137,13 +158,14 @@ where pub fn dijkstra_distances( graph: &G, source: G::Vertex, - weight: W, -) -> VertexMap> + weights: impl Fn(G::Edge) -> W, +) -> ElementMap> where G: GraphTopology, - W: Fn(G::Edge) -> u32, + G::Vertex: Hash, + W: Weight, { - dijkstra_impl(graph, source, weight, |_, _| {}) + dijkstra_impl(graph, source, weights, |_, _| {}) } /// [Dijkstra's algorithm] with unit edge weights, returns minimum distances and predecessors. @@ -152,6 +174,8 @@ where /// 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|) log |V|)*, space complexity is *O(|V|)*. +/// /// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity. /// /// # Panics @@ -175,9 +199,10 @@ where /// ``` /// /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult +pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, + G::Vertex: Hash, { dijkstra(graph, source, |_| 1) } @@ -188,6 +213,8 @@ where /// the distances from `source` to each vertex. Returns `None` for any vertex not connected to /// `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity. /// /// # Panics @@ -213,9 +240,10 @@ where pub fn dijkstra_distances_unweighted( graph: &G, source: G::Vertex, -) -> VertexMap> +) -> ElementMap> where G: GraphTopology, + G::Vertex: Hash, { dijkstra_distances(graph, source, |_| 1) } @@ -223,37 +251,32 @@ where fn dijkstra_impl( graph: &G, source: G::Vertex, - weight: W, + weights: impl Fn(G::Edge) -> W, mut on_relax: F, -) -> VertexMap> +) -> ElementMap> where G: GraphTopology, - W: Fn(G::Edge) -> u32, + G::Vertex: Hash, + W: Weight, F: FnMut(G::Vertex, G::Vertex), { let mut distances = graph.vertex_map(None); - let mut heap = BinaryHeap::new(); + let mut heap = PriorityQueue::new(); - distances[source] = Some(0); - heap.push(DistanceOrderedVertex { - vertex: source, - distance: 0, - }); + distances[source] = Some(W::default()); + heap.push(source, Reverse(W::default())); - while let Some(v) = heap.pop() { - for incidence in graph.incidences(v.vertex) { - let new_distance = distances[v.vertex].unwrap() + weight(incidence.1); - if match distances[incidence.0] { + while let Some((v, Reverse(v_distance))) = heap.pop() { + for Incidence { vertex: u, edge: e } in graph.incidences(v) { + let new_distance = weights(e) + v_distance; + if match distances[u] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[incidence.0] = Some(new_distance); - on_relax(incidence.0, v.vertex); - heap.push(DistanceOrderedVertex { - vertex: incidence.0, - distance: new_distance, - }); + distances[u] = Some(new_distance); + on_relax(u, v); + heap.push_increase(u, Reverse(new_distance)); } } } @@ -263,9 +286,9 @@ where /// Return data type for [`bfs`]. pub struct BfsResult { /// Vertex map of minimum distances from a given `source` vertex. - pub distances: VertexMap>, + pub distances: ElementMap>, /// Vertex map of predecessors on some shortest path from a given `source` vertex. - pub predecessors: VertexMap>, + pub predecessors: ElementMap>, } /// [Breadth-first search] traversal from `source`, returns distances and predecessors. @@ -341,7 +364,7 @@ where /// ``` /// /// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search -pub fn bfs_distances(graph: &G, source: G::Vertex) -> VertexMap> +pub fn bfs_distances(graph: &G, source: G::Vertex) -> ElementMap> where G: GraphTopology, { @@ -423,7 +446,7 @@ where } struct BfsImplResult { - distances: VertexMap>, + distances: ElementMap>, found: Option<(V, u32)>, } @@ -433,10 +456,8 @@ where F: FnMut(G::Vertex, G::Vertex) -> bool, { let mut distances = graph.vertex_map(None); - let mut queue = VecDeque::new(); - distances[source] = Some(0); - queue.push_back(source); + let mut queue = VecDeque::from([source]); while let Some(v) = queue.pop_front() { for neighbor in graph.adjacent_vertices(v) { @@ -459,12 +480,119 @@ where } } +/// [Breadth-first search] from `source` for `target`, returns some shortest path as a sequence of +/// edges. +/// +/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring all +/// neighbors before descending further. Returns some shortest 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::bfs_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!(bfs_find_path(&graph, source, target), Some(vec![e])); +/// ``` +/// +/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search +pub fn bfs_find_path(graph: &G, source: G::Vertex, target: G::Vertex) -> Option> +where + G: GraphTopology, +{ + bfs_find_path_where(graph, source, |v| v == target) +} + +/// [Breadth-first search] from `source` for a vertex matching `predicate`, returns some shortest +/// path as a sequence of edges. +/// +/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is +/// found, exploring all neighbors before descending further. Returns some shortest 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::bfs_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!(bfs_find_path_where(&graph, source, |v| v == target), Some(vec![e])); +/// ``` +/// +/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search +pub fn bfs_find_path_where(graph: &G, source: G::Vertex, predicate: P) -> Option> +where + G: GraphTopology, + P: Fn(G::Vertex) -> bool, +{ + if predicate(source) { + return Some(vec![]); + } + + let mut visited = graph.vertex_map(false); + visited[source] = true; + let mut predecessor = graph.vertex_map(None); + let mut queue = VecDeque::from([source]); + + while let Some(v) = queue.pop_front() { + for Incidence { + vertex: neighbor, + edge, + } in graph.incidences(v) + { + if !visited[neighbor] { + visited[neighbor] = true; + predecessor[neighbor] = Some((v, edge)); + if predicate(neighbor) { + let mut path = vec![edge]; + let mut current = v; + while let Some((prev, e)) = predecessor[current] { + path.push(e); + current = prev; + } + path.reverse(); + return Some(path); + } + queue.push_back(neighbor); + } + } + } + None +} + /// Return data type for [`dfs`]. pub struct DfsResult { /// Vertex map indicating which vertices were visited during the search. - pub visited: VertexMap, + pub visited: ElementMap, /// Vertex map of predecessors on the DFS tree path from a given `source` vertex. - pub predecessors: VertexMap>, + pub predecessors: ElementMap>, } /// [Depth-first search] traversal from `source`, returns visited vertices and predecessors. @@ -540,7 +668,7 @@ where /// ``` /// /// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search -pub fn dfs_visited(graph: &G, source: G::Vertex) -> VertexMap +pub fn dfs_visited(graph: &G, source: G::Vertex) -> ElementMap where G: GraphTopology, { @@ -620,7 +748,7 @@ where } struct DfsImplResult { - visited: VertexMap, + visited: ElementMap, found: Option, } @@ -748,7 +876,10 @@ where None => { stack.pop(); } - Some((neighbor, edge)) => { + Some(Incidence { + vertex: neighbor, + edge, + }) => { if predicate(neighbor) { let mut path: Vec = stack.iter().filter_map(|f| f.arrival_edge).collect(); diff --git a/src/generators.rs b/src/generators.rs new file mode 100644 index 0000000..4457fea --- /dev/null +++ b/src/generators.rs @@ -0,0 +1,79 @@ +//! Generators for common graphs. + +use crate::traits::{GraphTopology, GraphTopologyAddition}; + +/// Generates a Petersen graph. +/// +/// Generates a [Petersen graph] in `graph` as a new connected component and returns the new +/// vertices and edges. This graph is also the Kneser graph *KG(5, 2)*. +/// +/// [Petersen graph]: https://en.wikipedia.org/wiki/Petersen_graph +pub fn petersen( + graph: &mut G, +) -> ( + [::Vertex; 10], + [::Edge; 15], +) +where + G: GraphTopologyAddition, +{ + const N: usize = 5; + const K: usize = 2; + + graph.reserve_vertices(10); + graph.reserve_edges(15); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + let edges = core::array::from_fn(|j| { + let i = j / 3; + match j % 3 { + 0 => { + let outer = if i < N - 1 { i + 1 } else { 0 }; + graph.add_edge(vertices[i], vertices[outer]) + } + 1 => { + let inner = if i < N - K { i + N + K } else { i + K }; + graph.add_edge(vertices[i + N], vertices[inner]) + } + _ => graph.add_edge(vertices[i], vertices[i + N]), + } + }); + (vertices, edges) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::AppendGraph; + + #[test] + fn petersen() { + let mut graph = AppendGraph::new(); + let (vertices, edges) = super::petersen(&mut graph); + assert_eq!( + graph.vertex_count(), + 10, + "unexpected number of added vertices" + ); + assert_eq!(graph.edge_count(), 15, "unexpected number of added edges"); + assert!( + graph.vertices().all(|v| graph.degree(v) == 3), + "all vertices should have degree 3" + ); + assert_handles_in_graph(&graph, &vertices, &edges); + } + + fn assert_handles_in_graph( + graph: &G, + vertices: &[G::Vertex], + edges: &[G::Edge], + ) { + assert!( + vertices.iter().all(|&v| graph.vertices().any(|u| u == v)), + "all returned vertices should be in the graph" + ); + assert!( + edges.iter().all(|&e| graph.edges().any(|f| f == e)), + "all returned edges should be in the graph" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index a926085..51f83a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,12 @@ //! //! 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, +//! * Undirected graph types [`Graph`], [`AppendGraph`], and [`FrozenGraph`] built on a flat, +//! index-based adjacency list, +//! * [`ElementMap`] 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. +//! variants, +//! * Exposed [`traits`] to implement custom graph data structures or algorithms. //! //! # Usage example //! @@ -23,14 +23,14 @@ //! 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); +//! // Creates a map for edge weights with a default weight, after creating the edges. +//! let mut weights = graph.edge_map(1); //! // Sets a non-default weight for the second edge. //! weights[e] = 5; //! @@ -62,9 +62,11 @@ //! //! # 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`], [`AppendGraph`], and [`FrozenGraph`] all implement [`GraphTopology`] using a flat, +//! index-based adjacency list to store edges. Degree lookups run in `O(1)`. +//! +//! [`Graph`] and [`AppendGraph`] implement [`GraphTopologyAddition`], which provides functionality +//! for iterative graph construction. Vertex and edge insertions happen in `O(1)`. //! //! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of //! vertices and edges. @@ -73,6 +75,10 @@ //! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation //! tracking needed for stable handles after deletion. //! +//! [`FrozenGraph`] uses a memory layout that is more cache-friendly for traversals than +//! [`AppendGraph`], but the trade-off is that its topology must be constructed during its +//! initialization. It cannot be modified afterward. +//! //! # Vertices and edges //! //! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`] @@ -91,8 +97,8 @@ //! graph.degree(v); //! ``` //! -//! [`VertexMap`] and [`EdgeMap`] are not required to panic if provided with an invalid -//! vertex or edge. +//! [`ElementMap`] does not check validity of vertices or edges and might or might not panic in case +//! an invalid handle is provided. //! //! Vertices and edges are provided by the graph and not intended to be generated by users. //! @@ -115,28 +121,35 @@ //! 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 +//! [BFS]: algorithms::bfs +//! [Dijkstra's algorithm]: algorithms::dijkstra +//! [DFS]: algorithms::dfs +//! [`ElementMap`]: maps::ElementMap +//! [`AppendGraph`]: models::AppendGraph +//! [`FrozenGraph`]: models::FrozenGraph +//! [`Graph`]: models::Graph +//! [`traits`]: traits +//! [`GraphTopology`]: traits::GraphTopology +//! [`GraphTopology::Edge`]: traits::GraphTopology::Edge +//! [`GraphTopology::Vertex`]: traits::GraphTopology::Vertex +//! [`GraphTopologyAddition`]: traits::GraphTopologyAddition +//! [`GraphTopologyDeletion`]: traits::GraphTopologyDeletion #![warn(missing_docs)] pub mod algorithms; +pub mod generators; 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}; + pub use crate::traits::{ + GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, + GraphTopologyDeletion, Incidence, IncidenceCursor, + }; } -mod testing; +#[doc(hidden)] +pub mod testing; diff --git a/src/maps.rs b/src/maps.rs index baf8cf8..136b815 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -1,194 +1,381 @@ //! 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. +//! [`ElementMap`] is a dense, [`Vec`]-backed map to associate data to either all vertices or all +//! edges in a graph. use std::ops::{Index, IndexMut}; -use crate::traits::GraphTopology; - -/// A map to associate custom data to graph vertices. +/// A map to associate custom data to graph vertices or edges. /// -/// 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. +/// This map uses raw element indices to associate homogenous custom data of type `T` to graph +/// vertices or edges. The implementation uses a [`Vec`], allocating contiguous slots for the data, +/// which means that the provided index conversion function should map elements 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`] or [`GraphTopology::edge_map`] to obtain an `ElementMap` for +/// vertices or edges, respectively. /// -/// 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), - } - } - - /// 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.expand(graph.vertex_capacity()); - } -} - -impl Index for VertexMap { - type Output = T; - - fn index(&self, v: V) -> &T { - &self.inner[v] - } -} - -impl IndexMut for VertexMap { - fn index_mut(&mut self, v: V) -> &mut T { - &mut self.inner[v] - } -} - -/// A map to associate custom data to graph edges. +/// The map does not grow on its own when the associated graph grows. Use the graph's +/// [`GraphTopologyAddition::sync_vertex_map`] or [`GraphTopologyAddition::sync_edge_map`] to extend +/// the map explicitly after adding vertices or edges to it. /// -/// 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. +/// No bound or validity checks are performed by the map on the provided element handles. /// -/// 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), - } - } - - /// 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.expand(graph.edge_capacity()); - } -} - -impl Index for EdgeMap { - type Output = T; - - fn index(&self, e: E) -> &T { - &self.inner[e] - } -} - -impl IndexMut for EdgeMap { - fn index_mut(&mut self, e: E) -> &mut T { - &mut self.inner[e] - } -} - -struct EntityMap { +/// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map +/// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map +/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map +/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map +pub struct ElementMap { data: Vec, default: T, to_index: fn(E) -> usize, } -impl EntityMap { - pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { +impl ElementMap { + /// Creates a new map with the given `default` value, an index conversion function `to_index`, + /// and an initial `len`, i.e. the number of slots that can be read from and written to. + /// + /// Since `to_index` is usually an implementation detail of the graph model, this is not meant + /// to be called directly. Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] + /// instead. + /// + /// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map + /// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map + pub fn new(default: T, to_index: fn(E) -> usize, len: usize) -> Self { Self { - data: vec![default.clone(); capacity], + data: vec![default.clone(); len], default, to_index, } } - pub fn capacity(&self) -> usize { - self.data.len() + /// Grows the map so that it contains at least `new_len` slots for elements, initialising any + /// newly added slots to the `default` value provided to [`new`], without modifying existing + /// data. Does nothing if the map already has at least `new_len` slots. + /// + /// Since the number of elements the map can actually store depend on the index conversion + /// function, which is usually an implementation detail of the graph model, this is not meant to + /// be called directly. Use [`GraphTopologyAddition::sync_vertex_map`] or + /// [`GraphTopologyAddition::sync_edge_map`] instead. + /// + /// [`new`]: ElementMap::new + /// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map + /// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map + pub fn grow(&mut self, new_len: usize) { + if new_len > self.data.len() { + self.data.resize(new_len, self.default.clone()); + } } - pub fn expand(&mut self, capacity: usize) { - if capacity > self.data.capacity() { - self.data.resize(capacity, self.default.clone()); + /// Returns a new map over the same elements with values transformed by `f`. + /// + /// The default value of the new map is `f` applied to the original default, so slots later added + /// to the new map by [`GraphTopologyAddition::sync_vertex_map`] or + /// [`GraphTopologyAddition::sync_edge_map`] are filled consistently with the transformation. + /// + /// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map + /// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map + /// + /// # Examples + /// + /// ``` + /// # use grapherity::maps::ElementMap; + /// let mut map: ElementMap> = ElementMap::new(None, |e| e, 2); + /// map[1] = Some(42); + /// + /// let bools = map.map(|v| v.is_some()); + /// assert_eq!(bools[0], false); + /// assert_eq!(bools[1], true); + /// ``` + pub fn map(&self, f: impl Fn(&T) -> U) -> ElementMap { + ElementMap { + data: self.data.iter().map(&f).collect(), + default: f(&self.default), + to_index: self.to_index, } } + + /// Creates a new `ElementMap` indexed by elements of type `F`, such that for each element *e* + /// of this map that `mapping` maps to some *f*, the new map contains at *f* a clone of the + /// value this map contains at *e*. + /// + /// A slot of `mapping` counts as unmapped if it holds the default value of `mapping`, which + /// must therefore never be a valid element of `F`. Unmapped slots are skipped. Slots of the new + /// map that no element is mapped to, and slots of this map beyond the length of `mapping` or + /// vice versa, are left at this map's default value. The `template` is only used to determine + /// the length and the index conversion function of the new map. + /// + /// Iterates the raw slots of both maps, so it neither needs the graph nor the element handles. + pub(crate) fn remap( + &self, + mapping: &ElementMap, + template: &ElementMap, + ) -> ElementMap { + let mut result = ElementMap { + data: vec![self.default.clone(); template.data.len()], + default: self.default.clone(), + to_index: template.to_index, + }; + for (value, &f) in self.data.iter().zip(&mapping.data) { + if f != mapping.default { + result.data[(template.to_index)(f)] = value.clone(); + } + } + result + } } -impl Index for EntityMap { +impl Index for ElementMap { type Output = T; fn index(&self, e: E) -> &T { - let i = (self.to_index)(e); - if i < self.data.len() { - &self.data[i] - } else { - &self.default - } + &self.data[(self.to_index)(e)] } } -impl IndexMut for EntityMap { +impl IndexMut for ElementMap { fn index_mut(&mut self, e: E) -> &mut T { - let i = (self.to_index)(e); - if i >= self.data.len() { - self.data.resize(i + 1, self.default.clone()); - } - &mut self.data[i] + &mut self.data[(self.to_index)(e)] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::rc::Rc; + + #[test] + fn initial_values_are_default() { + let map = ElementMap::new(42, |e| e, 6); + assert_eq!(map[0], 42, "initial read at 0 should be default"); + assert_eq!(map[5], 42, "initial read at 5 should be default"); + } + + #[test] + #[should_panic] + fn read_out_of_bounds() { + let map = ElementMap::new(42, |e| e, 3); + let _ = map[3]; + } + + #[test] + #[should_panic] + fn write_out_of_bounds() { + let mut map = ElementMap::new(0, |e| e, 3); + map[3] = 1; + } + + #[test] + fn write_and_read() { + let mut map = ElementMap::new(0, |e| e, 2); + map[1] = 7; + assert_eq!(map[1], 7, "unexpected read value after write"); + assert_eq!(map[0], 0, "initial read should be default"); + } + + #[test] + fn grow() { + let mut map = ElementMap::new(0, |e| e, 2); + assert_eq!( + map.data.len(), + 2, + "precondition: unexpected data length before grow" + ); + map.grow(10); + assert_eq!(map.data.len(), 10, "unexpected data length after grow"); + } + + #[test] + fn grow_extends_with_default() { + let mut map = ElementMap::new(17, |e| e, 3); + map.grow(6); + assert_eq!(map[5], 17, "read of newly grown slot should be default"); + map[5] = 9; + assert_eq!( + map[5], 9, + "unexpected value after write in newly grown slot" + ); + } + + #[test] + fn grow_preserves_existing_values() { + let mut map = ElementMap::new(22, |e| e, 3); + map[0] = 5; + map.grow(10); + assert_eq!(map[0], 5, "unexpected data after grow"); + } + + #[test] + fn grow_to_smaller_len() { + let mut map = ElementMap::new(23, |e| e, 6); + map[5] = 9; + map.grow(3); + assert_eq!( + map[5], 9, + "grow to a smaller length should not discard data" + ); + assert_eq!(map.data.len(), 6, "unexpected data length"); + } + + #[test] + fn map() { + let mut source: ElementMap> = ElementMap::new(None, |e| e, 3); + source[1] = Some(42); + source[2] = None; + let mapped = source.map(|v| v.is_some()); + assert!(!mapped[0], "unset entry should map to false"); + assert!(mapped[1], "Some entry should map to true"); + assert!(!mapped[2], "explicit None entry should map to false"); + } + + #[test] + fn map_grow() { + let mut source: ElementMap> = ElementMap::new(None, |e| e, 2); + source[1] = Some(42); + let mut mapped = source.map(|v| v.is_some()); + mapped.grow(4); + assert!( + !mapped[3], + "slot added by grow should hold the mapped default" + ); + } + + #[test] + fn remap() { + let mut source = ElementMap::new('-', |e| e, 3); + source[0] = 'a'; + source[1] = 'b'; + source[2] = 'c'; + let mut mapping = ElementMap::new(usize::MAX, |e| e, 3); + mapping[0] = 2; + mapping[1] = 0; + mapping[2] = 1; + let template = ElementMap::new((), |f| f, 3); + let result = source.remap(&mapping, &template); + assert_eq!(result[0], 'b', "unexpected value at slot 0"); + assert_eq!(result[1], 'c', "unexpected value at slot 1"); + assert_eq!(result[2], 'a', "unexpected value at slot 2"); + assert_eq!( + result.default, '-', + "default should be taken from the source map" + ); + } + + #[test] + fn remap_skips_default() { + let mut source = ElementMap::new(0, |e| e, 3); + source[0] = 1; + source[1] = 2; + source[2] = 3; + // Slot 1 stays unmapped, which would be out of bounds for the template if it was used. + let mut mapping = ElementMap::new(usize::MAX, |e| e, 3); + mapping[0] = 1; + mapping[2] = 0; + let template = ElementMap::new((), |f| f, 2); + let result = source.remap(&mapping, &template); + assert_eq!(result[0], 3, "unexpected value at slot 0"); + assert_eq!(result[1], 1, "unexpected value at slot 1"); + } + + #[test] + fn remap_unmapped_slots() { + let mut source = ElementMap::new(9, |e| e, 2); + source[0] = 1; + source[1] = 2; + let mut mapping = ElementMap::new(usize::MAX, |e| e, 2); + mapping[0] = 0; + mapping[1] = 2; + let template = ElementMap::new((), |f| f, 4); + let result = source.remap(&mapping, &template); + assert_eq!( + result.data, + vec![1, 9, 2, 9], + "should have the template's length, with unmapped slots holding the source's default" + ); + } + + #[test] + fn remap_index_function() { + let mut source = ElementMap::::new(0, |e| e, 3); + source[0] = 10; + source[1] = 20; + source[2] = 30; + let mut mapping = ElementMap::::new(u32::MAX, |e| e, 3); + mapping[0] = 0; + mapping[1] = 1; + mapping[2] = 2; + // Reverses the order of the slots. + let template = ElementMap::::new((), |f| 2 - f as usize, 3); + let result = source.remap(&mapping, &template); + assert_eq!( + result.data, + vec![30, 20, 10], + "values should be written at the slots given by the template's index function" + ); + assert_eq!( + (result.to_index)(0), + 2, + "index function should be taken from the template" + ); + } + + #[test] + fn remap_shorter_source() { + let mut source = ElementMap::new(9, |e| e, 2); + source[0] = 5; + source[1] = 6; + let mut mapping = ElementMap::new(usize::MAX, |e| e, 4); + for i in 0..4 { + mapping[i] = i; + } + let template = ElementMap::new((), |f| f, 4); + let result = source.remap(&mapping, &template); + assert_eq!(result.data, vec![5, 6, 9, 9], "unexpected data"); + } + + #[test] + fn remap_longer_source() { + let mut source = ElementMap::new(0, |e| e, 4); + for i in 0..4 { + source[i] = i + 1; + } + let mut mapping = ElementMap::new(usize::MAX, |e| e, 2); + mapping[0] = 1; + mapping[1] = 0; + let template = ElementMap::new((), |f| f, 2); + let result = source.remap(&mapping, &template); + assert_eq!(result.data, vec![2, 1], "unexpected data"); + } + + #[test] + fn remap_empty() { + let source = ElementMap::new(0, |e| e, 0); + let mapping = ElementMap::new(usize::MAX, |e| e, 0); + let template = ElementMap::new((), |f| f, 0); + let result = source.remap(&mapping, &template); + assert_eq!(result.data, Vec::::new(), "should be empty"); + } + + #[test] + fn remap_clones_values() { + let value = Rc::new(1); + let mut source = ElementMap::new(Rc::new(0), |e| e, 1); + source[0] = Rc::clone(&value); + let mut mapping = ElementMap::new(usize::MAX, |e| e, 1); + mapping[0] = 0; + let template = ElementMap::new((), |f| f, 1); + let _result = source.remap(&mapping, &template); + // The local variable, the source slot and the result slot each hold one reference. + assert_eq!( + Rc::strong_count(&value), + 3, + "result should hold its own clone of the value" + ); + drop(source); + assert_eq!( + Rc::strong_count(&value), + 2, + "result should keep the value alive on its own" + ); } } diff --git a/src/models.rs b/src/models.rs index 4eb163e..5274bbc 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,9 +1,49 @@ //! Concrete graph topology models. +use crate::maps::ElementMap; +use crate::traits::{GraphTopology, GraphTopologyAddition}; + pub mod append_graph; +pub mod frozen_graph; pub mod graph; -// TODO: Compressed-sparse-row graph model. +pub use append_graph::{ + AppendGraph, AppendGraphEdgeMap, AppendGraphIncidence, AppendGraphVertexMap, +}; +pub use frozen_graph::{ + FrozenGraph, FrozenGraphEdgeMap, FrozenGraphIncidence, FrozenGraphVertexMap, +}; +pub use graph::{Graph, GraphEdgeMap, GraphIncidence, GraphVertexMap}; -pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap}; -pub use graph::{Graph, GraphEdgeMap, GraphVertexMap}; +/// Builds a new graph isomorphic to `source`. +/// +/// The resulting graph type `G` implements [`GraphTopologyAddition`] which is used to add +/// vertices and edges. `on_edge` is called for each edge in `source` together with the +/// corresponding new edge of the new graph, which callers can use to construct an edge mapping. +/// +/// Used by the [`GraphTopologyAddition`] models' `From<&H>` and [`GraphTopologyConversion`] +/// implementations. +/// +/// [`GraphTopologyConversion`]: crate::traits::GraphTopologyConversion +pub(crate) fn convert_to_addition( + source: &H, + vertex_default: G::Vertex, + mut on_edge: impl FnMut(H::Edge, G::Edge), +) -> (G, ElementMap) { + let mut result = G::default(); + result.reserve_vertices(source.vertex_count()); + result.reserve_edges(source.edge_count()); + + let mut vertex_map = source.vertex_map(vertex_default); + for v in source.vertices() { + vertex_map[v] = result.add_vertex(); + } + + for e in source.edges() { + let (u, v) = source.incident_vertices(e); + let new_edge = result.add_edge(vertex_map[u], vertex_map[v]); + on_edge(e, new_edge); + } + + (result, vertex_map) +} diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index dacc404..a872e6a 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,35 +1,53 @@ //! [`AppendGraph`], an undirected graph topology supporting addition only. -use crate::maps::{EdgeMap, VertexMap}; -use crate::traits::{GraphTopology, IncidenceCursor}; +use std::num::NonZeroUsize; + +use crate::maps::ElementMap; +use crate::models; +use crate::traits::{ + GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence, + 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)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Vertex(usize); +// TODO: Benchmark the cost of the index shift. An alternative could be to init AppendGraph::incidences with two dummy entries to avoid 0. /// 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; +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct Edge(NonZeroUsize); impl Edge { + fn new(index: usize) -> Self { + Edge(NonZeroUsize::new(index + 1).unwrap()) + } + + fn index(self) -> usize { + self.0.get() - 1 + } + fn normalize(&self) -> Self { - Self(self.0 & !1) + Self::new(self.index() & !1) } } -// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173 +/// An [`ElementMap`] for [`AppendGraph`] vertices. +pub type AppendGraphVertexMap = ElementMap; + +/// An [`ElementMap`] for [`AppendGraph`] edges. +pub type AppendGraphEdgeMap = ElementMap; + +/// An [`Incidence`] for [`AppendGraph`]. +pub type AppendGraphIncidence = Incidence; + +#[derive(Clone)] struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, @@ -50,10 +68,13 @@ pub struct AppendGraphIncidenceCursor { } impl IncidenceCursor for AppendGraphIncidenceCursor { - fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> { + fn next(&mut self, graph: &AppendGraph) -> Option { graph .step_incidence(&mut self.incidence) - .map(|(v, e)| (v, e.normalize())) + .map(|(v, e)| Incidence { + vertex: v, + edge: e.normalize(), + }) } } @@ -61,7 +82,8 @@ impl IncidenceCursor for AppendGraphIncidenceCursor { /// /// `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. +/// need to remove vertices or edges. Use [`FrozenGraph`] if you do not need incremental +/// construction. /// /// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general, /// vertex neighborhood traversals result in scattered index jumps. @@ -73,10 +95,10 @@ impl IncidenceCursor for AppendGraphIncidenceCursor { /// 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)); +/// let u = graph.add_vertex(); +/// let v = graph.add_vertex(); +/// let e = graph.add_edge(u, v); +/// assert!(graph.are_adjacent(u, v)); /// ``` /// /// # Time and space complexity @@ -84,10 +106,18 @@ impl IncidenceCursor for AppendGraphIncidenceCursor { /// 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|)*. /// +/// # Cloning +/// +/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or +/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect +/// each other. +/// /// [`add_vertex`]: Self::add_vertex /// [`add_edge`]: Self::add_edge /// [`degree`]: Self::degree -/// [`Graph`]: crate::models::Graph +/// [`FrozenGraph`]: models::FrozenGraph +/// [`Graph`]: models::Graph +#[derive(Clone)] pub struct AppendGraph { vertices: Vec, incidences: Vec, @@ -104,13 +134,13 @@ impl AppendGraph { /// 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) { + fn add_incidence(&mut self, u: Vertex, v: Vertex) { self.incidences.push(IncidenceEntry { - next: self.vertices[v1.0].first_incidence.take(), - adjacent: v2, + next: self.vertices[u.0].first_incidence.take(), + adjacent: v, }); - self.vertices[v1.0].incidence_count += 1; - self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1)); + self.vertices[u.0].incidence_count += 1; + self.vertices[u.0].first_incidence = Some(Edge::new(self.incidences.len() - 1)); } fn raw_incidences(&self, v: Vertex) -> impl Iterator { @@ -120,7 +150,7 @@ impl AppendGraph { fn step_incidence(&self, incidence: &mut Option) -> Option<(Vertex, Edge)> { let current = (*incidence)?; - let entry = self.incidences[current.0]; + let entry = self.incidences[current.index()]; *incidence = entry.next; Some((entry.adjacent, current)) } @@ -141,32 +171,24 @@ impl GraphTopology for AppendGraph { self.vertices.len() } - fn vertex_capacity(&self) -> usize { - self.vertices.capacity() - } - - fn vertex_map(&self, default: T) -> VertexMap { - VertexMap::new(default, |v| v.0, self.vertex_capacity()) + fn vertex_map(&self, default: T) -> ElementMap { + ElementMap::new(default, |v| v.0, self.vertex_capacity()) } fn edge_count(&self) -> usize { self.incidences.len() / 2 } - fn edge_capacity(&self) -> usize { - self.incidences.capacity() / 2 - } - - fn edge_map(&self, default: T) -> EdgeMap { - EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity()) + fn edge_map(&self, default: T) -> ElementMap { + ElementMap::new(default, |e| e.index() / 2, self.edge_capacity()) } fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v.0].incidence_count } - fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.adjacent_vertices(v1).any(|x| x == v2) + fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool { + self.adjacent_vertices(u).any(|x| x == v) } fn vertices(&self) -> impl Iterator { @@ -178,21 +200,26 @@ impl GraphTopology for AppendGraph { } fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { - let v2 = self.incidences[e.0].adjacent; - let v1 = self.incidences[e.0 ^ 1].adjacent; - (v1, v2) + let ei = e.index(); + ( + self.incidences[ei ^ 1].adjacent, + self.incidences[ei].adjacent, + ) } fn edges(&self) -> impl Iterator { - (0..self.incidences.len()).step_by(2).map(Edge) + (0..self.incidences.len()).step_by(2).map(Edge::new) } fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { self.raw_incidences(v).map(|(_, e)| e.normalize()) } - fn incidences(&self, v: Self::Vertex) -> impl Iterator { - self.raw_incidences(v).map(|(v, e)| (v, e.normalize())) + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|(v, e)| Incidence { + vertex: v, + edge: e.normalize(), + }) } fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { @@ -200,6 +227,24 @@ impl GraphTopology for AppendGraph { incidence: self.vertices[v.0].first_incidence, } } +} + +impl GraphTopologyAddition for AppendGraph { + fn vertex_capacity(&self) -> usize { + self.vertices.capacity() + } + + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } + + fn reserve_vertices(&mut self, additional: usize) { + self.vertices.reserve(additional); + } + + fn reserve_edges(&mut self, additional: usize) { + self.incidences.reserve(additional * 2); + } fn add_vertex(&mut self) -> Self::Vertex { self.vertices.push(VertexIncidenceHeader { @@ -209,31 +254,58 @@ impl GraphTopology for AppendGraph { Vertex(self.vertices.len() - 1) } - fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { - self.add_incidence(v1, v2); - self.add_incidence(v2, v1); - Edge(self.incidences.len() - 2) + fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge { + self.add_incidence(u, v); + self.add_incidence(v, u); + Edge::new(self.incidences.len() - 2) } } +impl GraphTopologyConversion for AppendGraph { + fn convert(source: &G) -> (Self, GraphIsomorphism) { + let mut edge_map = source.edge_map(Edge(NonZeroUsize::MAX)); + let (result, vertex_map) = + models::convert_to_addition(source, Vertex(usize::MAX), |e, new_e| edge_map[e] = new_e); + let iso = GraphIsomorphism::new(&result, vertex_map, edge_map); + (result, iso) + } +} + +impl From<&H> for AppendGraph { + fn from(source: &H) -> Self { + models::convert_to_addition::(source, Vertex(usize::MAX), |_, _| {}).0 + } +} + +#[cfg(test)] +mod trait_tests { + use super::AppendGraph; + use crate::models::Graph; + + crate::graph_topology_tests!(AppendGraph); + crate::graph_topology_addition_tests!(AppendGraph); + crate::graph_topology_clone_tests!(AppendGraph); + crate::graph_topology_addition_clone_tests!(AppendGraph); + crate::graph_topology_conversion_tests!(AppendGraph, Graph); + crate::graph_topology_addition_conversion_tests!(AppendGraph, Graph); + crate::graph_topology_deletion_conversion_tests!(AppendGraph, Graph); + crate::graph_topology_addition_deletion_conversion_tests!(AppendGraph, Graph); + crate::from_tests!(AppendGraph, Graph); +} + #[cfg(test)] mod tests { use super::*; - - crate::graph_topology_test_fixtures!(AppendGraph); - crate::graph_topology_tests!(AppendGraph); + use crate::testing::fixtures::MakeTestGraph; #[test] fn incident_vertices_paired_index() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - let f = Edge(e.0 + 1); - let (u1, u2) = graph.incident_vertices(f); + let (graph, [v0, v1], e) = AppendGraph::single_edge(); + let f = Edge::new(e.index() + 1); + let (u0, u1) = graph.incident_vertices(f); assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}" ); } } diff --git a/src/models/frozen_graph.rs b/src/models/frozen_graph.rs new file mode 100644 index 0000000..3215a67 --- /dev/null +++ b/src/models/frozen_graph.rs @@ -0,0 +1,322 @@ +//! [`FrozenGraph`], an undirected graph topology supporting only one-time construction. + +use std::ops::IndexMut; + +use crate::maps::ElementMap; +use crate::traits::{ + GraphIsomorphism, GraphTopology, GraphTopologyConversion, Incidence, IncidenceCursor, +}; + +/// An opaque handle identifying a vertex in a [`FrozenGraph`]. +/// +/// Handles are stable for the lifetime of the graph. Obtain via graph methods like +/// [`FrozenGraph::vertices`]. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct Vertex(usize); + +/// An opaque handle identifying an edge in a [`FrozenGraph`]. +/// +/// Handles are stable for the lifetime of the graph. Obtain via graph methods like +/// [`FrozenGraph::edges`]. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct Edge(usize); + +/// An [`ElementMap`] for [`FrozenGraph`] vertices. +pub type FrozenGraphVertexMap = ElementMap; + +/// An [`ElementMap`] for [`FrozenGraph`] edges. +pub type FrozenGraphEdgeMap = ElementMap; + +/// An [`Incidence`] for [`FrozenGraph`]. +pub type FrozenGraphIncidence = Incidence; + +#[derive(Copy, Clone)] +struct IncidenceEntry { + adjacent: Vertex, + opposite: usize, +} + +/// A resumable cursor over the incidences of a single vertex in a [`FrozenGraph`]. +/// +/// Obtain via [`FrozenGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance. +#[derive(Copy, Clone)] +pub struct FrozenGraphIncidenceCursor { + current: usize, + end: usize, +} + +impl IncidenceCursor for FrozenGraphIncidenceCursor { + fn next(&mut self, graph: &FrozenGraph) -> Option { + if self.current < self.end { + let incidence = graph.normalize_incidence(self.current); + self.current += 1; + Some(incidence) + } else { + None + } + } +} + +/// An undirected graph that supports only one-time construction, and no further modification. +/// +/// `FrozenGraph` is optimised for workloads that repeatedly query a graph, but do not require +/// changing it once it is constructed. [`Vertex`] and [`Edge`] handles are never invalidated. Use +/// [`AppendGraph`] instead if you need to incrementally add vertices or edges, or [`Graph`] if you +/// need to remove vertices or edges after initial construction. +/// +/// `FrozenGraph` uses a compressed-sparse data representation. Incidences are stored as contiguous +/// adjacency lists per vertex in a single flat [`Vec`]. Vertex neighborhood traversals are +/// therefore cache-friendly without index jumps. +/// +/// # Examples +/// +/// ``` +/// use grapherity::prelude::*; +/// use grapherity::models::{FrozenGraph, AppendGraph}; +/// +/// // Constructs a FrozenGraph instance via AppendGraph. +/// let mut graph = AppendGraph::new(); +/// let u = graph.add_vertex(); +/// let v = graph.add_vertex(); +/// let e = graph.add_edge(u, v); +/// let (graph, isomorphism) = FrozenGraph::convert(&graph); +/// +/// // Queries the FrozenGraph instance via the returned map. +/// assert!(graph.are_adjacent(isomorphism.vertex(u), isomorphism.vertex(v))); +/// ``` +/// +/// # Time and space complexity +/// +/// [`degree`] runs in *O(1)* time. Space complexity is *O(|V| + |E|)*. +/// +/// # Cloning +/// +/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or +/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect +/// each other. +/// +/// [`degree`]: Self::degree +/// [`AppendGraph`]: crate::models::AppendGraph +/// [`Graph`]: crate::models::Graph +#[derive(Clone)] +pub struct FrozenGraph { + vertices: Vec, + incidences: Vec, +} + +impl FrozenGraph { + fn raw_incidences(&self, v: Vertex) -> impl Iterator { + self.vertices[v.0]..self.vertices[v.0 + 1] + } + + fn normalize_edge(&self, e: usize) -> Edge { + let f = self.incidences[e].opposite; + Edge(if e < f { e } else { f }) + } + + fn normalize_incidence(&self, i: usize) -> FrozenGraphIncidence { + let entry = &self.incidences[i]; + Incidence { + vertex: entry.adjacent, + edge: Edge(if i < entry.opposite { + i + } else { + entry.opposite + }), + } + } +} + +impl GraphTopology for FrozenGraph { + type Vertex = Vertex; + type Edge = Edge; + type IncidenceCursor = FrozenGraphIncidenceCursor; + + fn vertex_count(&self) -> usize { + self.vertices.len() - 1 + } + + fn vertex_map(&self, default: T) -> ElementMap { + ElementMap::new(default, |v| v.0, self.vertex_count()) + } + + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + + // TODO: Find a way to reduce the needed capacity for the edge ElementMap. This could be improved by finding a small vertex cover and store those vertices first. See https://en.wikipedia.org/wiki/Vertex_cover#Approximate_evaluation + fn edge_map(&self, default: T) -> ElementMap { + // "incidences.len() - 1" would underflow for an edge-free graph. + ElementMap::new(default, |e| e.0, self.incidences.len().saturating_sub(1)) + } + + fn degree(&self, v: Self::Vertex) -> usize { + self.vertices[v.0 + 1] - self.vertices[v.0] + } + + fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool { + self.adjacent_vertices(u).any(|x| x == v) + } + + fn vertices(&self) -> impl Iterator { + (0..self.vertices.len() - 1).map(Vertex) + } + + fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|i| self.incidences[i].adjacent) + } + + fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { + let entry = &self.incidences[e.0]; + (self.incidences[entry.opposite].adjacent, entry.adjacent) + } + + fn edges(&self) -> impl Iterator { + self.incidences + .iter() + .enumerate() + .filter_map(|(i, x)| (i < x.opposite).then_some(Edge(i))) + } + + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|i| self.normalize_edge(i)) + } + + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|i| self.normalize_incidence(i)) + } + + fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { + FrozenGraphIncidenceCursor { + current: self.vertices[v.0], + end: self.vertices[v.0 + 1], + } + } +} + +impl GraphTopologyConversion for FrozenGraph { + fn convert(source: &G) -> (Self, GraphIsomorphism) { + // Builds vertex list with sentinel and vertex map. + let mut vertex_map = source.vertex_map(Vertex(usize::MAX)); + let mut vertices = Vec::with_capacity(source.vertex_count() + 1); + let mut offset = 0; + for (i, v) in source.vertices().enumerate() { + vertex_map[v] = Vertex(i); + vertices.push(offset); + offset += source.degree(v); + } + vertices.push(offset); + + // Builds incidence list and edge map. Uses "usize::MAX" as "unset" here: an edge can never + // be normalized to "usize::MAX", since it must be the smaller index of two incidences. + const UNSET: usize = usize::MAX; + let mut edge_map = source.edge_map(Edge(UNSET)); + let mut incidences = Vec::with_capacity(source.edge_count() * 2); + for v in source.vertices() { + for Incidence { vertex: u, edge: e } in source.incidences(v) { + let em = edge_map.index_mut(e); + if em.0 == UNSET { + // Adds the first incidence for the edge, but we don't know the opposite, yet. + *em = Edge(incidences.len()); + incidences.push(IncidenceEntry { + adjacent: vertex_map[u], + opposite: 0, + }); + } else { + // Adds the second incidence for the edge and fills the opposite for the first. + incidences[em.0].opposite = incidences.len(); + incidences.push(IncidenceEntry { + adjacent: vertex_map[u], + opposite: em.0, + }); + } + } + } + + let result = Self { + vertices, + incidences, + }; + let iso = GraphIsomorphism::new(&result, vertex_map, edge_map); + (result, iso) + } +} + +impl From<&G> for FrozenGraph { + fn from(graph: &G) -> Self { + Self::convert(graph).0 + } +} + +#[cfg(test)] +mod trait_tests { + use super::FrozenGraph; + use crate::models::{AppendGraph, Graph}; + + crate::graph_topology_tests!(FrozenGraph); + crate::graph_topology_clone_tests!(FrozenGraph); + crate::graph_topology_conversion_tests!(FrozenGraph, AppendGraph); + crate::graph_topology_deletion_conversion_tests!(FrozenGraph, Graph); + crate::graph_topology_addition_deletion_conversion_tests!(FrozenGraph, Graph); + crate::from_tests!(FrozenGraph, AppendGraph); +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::testing::fixtures::MakeTestGraph; + use crate::traits::GraphTopology; + + #[test] + fn incident_vertices_opposite_handle() { + let (graph, [v0, v1], e) = FrozenGraph::single_edge(); + let f = Edge(graph.incidences[e.0].opposite); + let (u0, u1) = graph.incident_vertices(f); + assert!( + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}" + ); + } + + #[test] + fn csr_sentinel_and_offsets_standard() { + let (graph, ..) = FrozenGraph::standard(); + assert_eq!( + *graph.vertices.last().unwrap(), + graph.incidences.len(), + "sentinel should equal total number of incidences" + ); + assert!( + graph.vertices.windows(2).all(|w| w[0] <= w[1]), + "incidence offsets of vertices should be monotonically non-decreasing" + ); + } + + #[test] + fn csr_sentinel_and_offsets_empty() { + let graph = FrozenGraph::empty(); + assert_eq!( + graph.vertices, + vec![0], + "empty graph should have a single sentinel entry of 0" + ); + assert!( + graph.incidences.is_empty(), + "empty graph should have no incidences" + ); + } + + #[test] + fn opposite_symmetry() { + let (graph, ..) = FrozenGraph::standard(); + for (i, inc) in graph.incidences.iter().enumerate() { + let opp = inc.opposite; + assert_ne!(opp, i, "incidence should not equal its own opposite"); + assert_eq!( + graph.incidences[opp].opposite, i, + "opposite of opposite of incidence should equal the incidence" + ); + } + } +} diff --git a/src/models/graph.rs b/src/models/graph.rs index 0943153..ae225ed 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,9 +1,14 @@ //! [`Graph`], an undirected graph topology supporting addition and deletion. +use std::num::NonZeroUsize; use typed_generational_arena::{Arena, Index}; -use crate::maps::{EdgeMap, VertexMap}; -use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor}; +use crate::maps::ElementMap; +use crate::models; +use crate::traits::{ + GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, + GraphTopologyDeletion, Incidence, IncidenceCursor, +}; /// An opaque handle identifying a vertex in a [`Graph`]. /// @@ -17,29 +22,43 @@ pub type Vertex = Index; /// 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 [`ElementMap`] for [`Graph`] vertices. +pub type GraphVertexMap = ElementMap; -/// An [`EdgeMap`] for [`Graph`] edges. -pub type GraphEdgeMap = EdgeMap; +/// An [`ElementMap`] for [`Graph`] edges. +pub type GraphEdgeMap = ElementMap; + +/// An [`Incidence`] for [`Graph`]. +pub type GraphIncidence = Incidence; #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct VertexSlot(usize); +// TODO: Benchmark the cost of the index shift. An alternative could be to init Graph::incidences with two dummy entries to avoid 0. #[derive(Copy, Clone, PartialEq, Eq, Debug)] -struct IncidenceSlot(usize); +struct IncidenceSlot(NonZeroUsize); -// 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. +impl IncidenceSlot { + fn new(index: usize) -> Self { + IncidenceSlot(NonZeroUsize::new(index + 1).unwrap()) + } + + fn index(self) -> usize { + self.0.get() - 1 + } +} + +/// Referenced by [`Vertex`] as a type parameter of the underlying arena. Not intended for direct +/// external use. #[doc(hidden)] +#[derive(Clone)] 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. +/// Referenced by [`Edge`] as a type parameter of the underlying arena. Not intended for direct +/// external use. #[doc(hidden)] #[derive(Copy, Clone)] pub struct IncidenceEntry { @@ -66,13 +85,13 @@ 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(), - graph.normalize_edge(e), - ) - }) + fn next(&mut self, graph: &Graph) -> Option { + graph + .step_incidence(&mut self.incidence) + .map(|(vs, e)| Incidence { + vertex: graph.vertices.get_idx(vs.0).unwrap(), + edge: graph.normalize_edge(e), + }) } } @@ -113,12 +132,19 @@ impl IncidenceCursor for GraphIncidenceCursor { /// planned, performing deletions first allows freed slots to be reused by subsequent additions, /// potentially avoiding reallocation. /// +/// # Cloning +/// +/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or +/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect +/// each other. +/// /// [`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 +/// [`AppendGraph`]: models::AppendGraph +#[derive(Clone)] pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -136,13 +162,13 @@ impl Graph { /// 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 { + fn add_incidence(&mut self, u: Vertex, v: Vertex) -> Edge { let edge = self.incidences.insert(IncidenceEntry { - next: self.vertices[v1].first_incidence.take(), - adjacent: VertexSlot(v2.arr_idx()), + next: self.vertices[u].first_incidence.take(), + adjacent: VertexSlot(v.arr_idx()), }); - self.vertices[v1].incidence_count += 1; - self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); + self.vertices[u].incidence_count += 1; + self.vertices[u].first_incidence = Some(IncidenceSlot::new(edge.arr_idx())); edge } @@ -181,7 +207,7 @@ impl Graph { let first = vertex_header .first_incidence .expect("incident vertex without incidences, corrupt internal data state"); - if first.0 == e.arr_idx() { + if first.index() == e.arr_idx() { vertex_header.first_incidence = next; } else { let graph: &Graph = self; @@ -190,7 +216,7 @@ impl Graph { .find(|(_, f)| { graph.incidences[*f] .next - .is_some_and(|i| i.0 == e.arr_idx()) + .is_some_and(|i| i.index() == e.arr_idx()) }) .expect("cannot find previous incidence, corrupt internal data state"); self.incidences[previous].next = next; @@ -205,7 +231,7 @@ impl Graph { fn step_incidence(&self, incidence: &mut Option) -> Option<(VertexSlot, Edge)> { // TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries). let current = (*incidence)?; - let e = self.incidences.get_idx(current.0).unwrap(); + let e = self.incidences.get_idx(current.index()).unwrap(); let entry = self.incidences[e]; *incidence = entry.next; Some((entry.adjacent, e)) @@ -236,32 +262,24 @@ impl GraphTopology for Graph { self.vertices.len() } - fn vertex_capacity(&self) -> usize { - self.vertices.capacity() - } - - fn vertex_map(&self, default: T) -> VertexMap { - VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) + fn vertex_map(&self, default: T) -> ElementMap { + ElementMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) } fn edge_count(&self) -> usize { self.incidences.len() / 2 } - fn edge_capacity(&self) -> usize { - self.incidences.capacity() / 2 - } - - fn edge_map(&self, default: T) -> EdgeMap { - EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) + fn edge_map(&self, default: T) -> ElementMap { + ElementMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) } fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].incidence_count } - fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.adjacent_vertices(v1).any(|x| x == v2) + fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool { + self.adjacent_vertices(u).any(|x| x == v) } fn vertices(&self) -> impl Iterator { @@ -274,16 +292,16 @@ impl GraphTopology for Graph { } fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { - let v2 = self + let v = self .vertices .get_idx(self.incidences[e].adjacent.0) .unwrap(); let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap(); - let v1 = self + let u = self .vertices .get_idx(self.incidences[f].adjacent.0) .unwrap(); - (v1, v2) + (u, v) } fn edges(&self) -> impl Iterator { @@ -297,9 +315,11 @@ impl GraphTopology for Graph { self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e)) } - fn incidences(&self, v: Self::Vertex) -> impl Iterator { - self.raw_incidences(v) - .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|(vs, e)| Incidence { + vertex: self.vertices.get_idx(vs.0).unwrap(), + edge: self.normalize_edge(e), + }) } fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { @@ -307,6 +327,24 @@ impl GraphTopology for Graph { incidence: self.vertices[v].first_incidence, } } +} + +impl GraphTopologyAddition for Graph { + fn vertex_capacity(&self) -> usize { + self.vertices.capacity() + } + + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } + + fn reserve_vertices(&mut self, additional: usize) { + self.vertices.reserve(additional); + } + + fn reserve_edges(&mut self, additional: usize) { + self.incidences.reserve(additional * 2); + } fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { @@ -315,9 +353,9 @@ impl GraphTopology for Graph { }) } - fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { - let first = self.add_incidence(v1, v2); - self.add_incidence(v2, v1); + fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge { + let first = self.add_incidence(u, v); + self.add_incidence(v, u); first } } @@ -352,7 +390,7 @@ impl GraphTopologyDeletion for Graph { if e_entry.adjacent != f_entry.adjacent { self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false); self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false); - } else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) { + } else if f_entry.next.is_some_and(|i| e.arr_idx() == i.index()) { self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true); } else { self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true); @@ -360,37 +398,69 @@ impl GraphTopologyDeletion for Graph { } } +impl GraphTopologyConversion for Graph { + fn convert(source: &G) -> (Self, GraphIsomorphism) { + let mut edge_map = source.edge_map(Edge::new(usize::MAX, usize::MAX)); + let (result, vertex_map) = + models::convert_to_addition(source, Vertex::new(usize::MAX, usize::MAX), |e, new_e| { + edge_map[e] = new_e + }); + let iso = GraphIsomorphism::new(&result, vertex_map, edge_map); + (result, iso) + } +} + +impl From<&H> for Graph { + fn from(source: &H) -> Self { + models::convert_to_addition::( + source, + Vertex::new(usize::MAX, usize::MAX), + |_, _| {}, + ) + .0 + } +} + +#[cfg(test)] +mod trait_tests { + use super::Graph; + use crate::models::AppendGraph; + + crate::graph_topology_tests!(Graph); + crate::graph_topology_addition_tests!(Graph); + crate::graph_topology_deletion_tests!(Graph); + crate::graph_topology_addition_deletion_tests!(Graph); + crate::graph_topology_clone_tests!(Graph); + crate::graph_topology_addition_clone_tests!(Graph); + crate::graph_topology_conversion_tests!(Graph, AppendGraph); + crate::graph_topology_addition_conversion_tests!(Graph, AppendGraph); + crate::graph_topology_deletion_conversion_tests!(Graph, Graph); + crate::graph_topology_addition_deletion_conversion_tests!(Graph, Graph); + crate::from_tests!(Graph, AppendGraph); +} + #[cfg(test)] mod tests { use super::*; - - crate::graph_topology_test_fixtures!(Graph); - crate::graph_topology_tests!(Graph); - crate::graph_topology_deletion_tests!(Graph); + use crate::testing::fixtures::MakeTestGraph; #[test] fn incident_vertices_paired_index() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); + let (graph, [v0, v1], e) = Graph::single_edge(); let f = graph .incidences .get_idx(e.arr_idx() + 1) .expect("paired index should be valid"); - let (u1, u2) = graph.incident_vertices(f); + let (u0, u1) = graph.incident_vertices(f); assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}" ); } #[test] fn delete_edge_paired_index() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); + let (mut graph, _, e) = Graph::single_edge(); let f = graph .incidences .get_idx(e.arr_idx() + 1) @@ -401,9 +471,7 @@ mod tests { #[test] fn delete_edge_loop_paired_index() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); + let (mut graph, _, e) = Graph::loop_edge(); let f = graph .incidences .get_idx(e.arr_idx() + 1) @@ -411,4 +479,25 @@ mod tests { graph.delete_edge(f); assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); } + + #[test] + fn reused_slot_returns_old_value() { + let (mut graph, [_, v], _) = Graph::single_edge(); + let mut map = graph.vertex_map(0); + map[v] = 99; + graph.delete_vertex(v); + let u = graph.add_vertex(); + assert_eq!( + v.arr_idx(), + u.arr_idx(), + "precondition: new vertex {v:?} should reuse slot of deleted vertex {u:?}" + ); + // ElementMap uses raw indices, not vertex identity. A new vertex u reusing the slot + // of previously deleted v sees the old value. Callers must reinitialize stale slots + // after deletion. + assert_eq!( + map[u], 99, + "new vertex reusing slot of deleted vertex should return old value" + ); + } } diff --git a/src/testing.rs b/src/testing.rs index 3412447..0c39578 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,8 +1,14 @@ //! 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; -pub(crate) mod find_path_testing; -pub(crate) mod graph_topology_testing; -pub(crate) mod maps_testing; +pub mod fixtures; +pub mod from_testing; +pub mod graph_topology_addition_clone_testing; +pub mod graph_topology_addition_conversion_testing; +pub mod graph_topology_addition_deletion_conversion_testing; +pub mod graph_topology_addition_deletion_testing; +pub mod graph_topology_addition_testing; +pub mod graph_topology_clone_testing; +pub mod graph_topology_conversion_testing; +pub mod graph_topology_deletion_conversion_testing; +pub mod graph_topology_deletion_testing; +pub mod graph_topology_testing; diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs deleted file mode 100644 index 709ac9f..0000000 --- a/src/testing/bfs_testing.rs +++ /dev/null @@ -1,258 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! bfs_tests { - ($T:ty) => { - #[test] - fn bfs_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let result = $crate::algorithms::bfs(&graph, v); - assert_eq!( - result.distances[v], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex" - ); - } - - #[test] - fn bfs_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let result = $crate::algorithms::bfs(&graph, vertices[0]); - assert_eq!( - result.distances[vertices[0]], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[vertices[0]], None, - "unexpected predecessor of source vertex" - ); - for &v in &vertices[1..3] { - assert_eq!( - result.distances[v], None, - "disconnected vertex {v:?} should have no distance" - ); - assert_eq!( - result.predecessors[v], None, - "disconnected vertex {v:?} should have no predecessor" - ); - } - } - - #[test] - fn bfs() { - let (graph, vertices, _, _) = make_test_graph(); - let result = $crate::algorithms::bfs(&graph, vertices[0]); - assert_bfs_distances(&result.distances, &vertices); - assert_bfs_predecessors(&result.predecessors, &vertices); - } - - #[test] - fn bfs_distances_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let distances = $crate::algorithms::bfs_distances(&graph, v); - assert_eq!( - distances[v], - Some(0), - "unexpected distance of source vertex" - ); - } - - #[test] - fn bfs_distances_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let distances = $crate::algorithms::bfs_distances(&graph, vertices[0]); - assert_eq!( - distances[vertices[0]], - Some(0), - "unexpected distance of source vertex" - ); - for &v in &vertices[1..3] { - assert_eq!( - distances[v], None, - "disconnected vertex {v:?} should have no distance" - ); - } - } - - #[test] - fn bfs_distances() { - let (graph, vertices, _, _) = make_test_graph(); - let distances = $crate::algorithms::bfs_distances(&graph, vertices[0]); - assert_bfs_distances(&distances, &vertices); - } - - #[test] - fn bfs_find_source() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - $crate::algorithms::bfs_find(&graph, v, v), - Some(0), - "source should be found at distance 0" - ); - } - - #[test] - fn bfs_find_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert_eq!( - $crate::algorithms::bfs_find(&graph, vertices[0], vertices[1]), - None, - "disconnected target should not be found" - ); - } - - #[test] - fn bfs_find() { - let (graph, vertices, _, _) = make_test_graph(); - let expected_distances = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - $crate::algorithms::bfs_find(&graph, vertices[0], vertices[i]), - expected_distances[i], - "unexpected distance from {:?} to {:?}", - vertices[0], - vertices[i] - ); - } - } - - #[test] - fn bfs_find_where_source_matches() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, v, |u| u == v), - Some((v, 0)), - "source should match with distance 0" - ); - } - - #[test] - fn bfs_find_where_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "disconnected vertex {:?} should not be found", - vertices[1] - ); - } - - #[test] - fn bfs_find_where_no_match() { - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!( - $crate::algorithms::bfs_find_where(&graph, vertices[0], |_| false), - None, - "no vertex should match an always-false predicate" - ); - } - - #[test] - fn bfs_find_where_nearest() { - let (graph, vertices, _, _) = make_test_graph(); - // vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0]. - // vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9]. - // BFS must return one of the distance-3 ones, not vertices[9]. - let result = $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| { - v == vertices[7] || v == vertices[8] || v == vertices[9] - }); - assert!(result.is_some(), "expected a match"); - let (found, distance) = result.unwrap(); - assert_eq!( - distance, 3, - "unexpected distance to nearest matching vertex {found:?}", - ); - assert!( - found == vertices[7] || found == vertices[8], - "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", - vertices[7], - vertices[8] - ); - } - - fn assert_bfs_distances( - distances: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected[i], - "unexpected BFS distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - fn assert_bfs_predecessors( - predecessors: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option<<$T as $crate::traits::GraphTopology>::Vertex>, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_eq!( - predecessors[vertices[0]], None, - "source should have no predecessor" - ); - // Each non-source vertex's predecessor must be adjacent and at distance one less. - let expected_predecessors = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[2])], - vec![Some(vertices[2]), Some(vertices[3])], - vec![Some(vertices[4])], - vec![Some(vertices[4])], - vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], - ]; - for i in 1..10 { - assert!( - expected_predecessors[i].contains(&predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - predecessors[vertices[i]], - vertices[i] - ); - } - } - }; -} diff --git a/src/testing/dfs_testing.rs b/src/testing/dfs_testing.rs deleted file mode 100644 index 78238c8..0000000 --- a/src/testing/dfs_testing.rs +++ /dev/null @@ -1,206 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! dfs_tests { - ($T:ty) => { - #[test] - fn dfs_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let result = $crate::algorithms::dfs(&graph, v); - assert!(result.visited[v], "source vertex should be visited"); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex" - ); - } - - #[test] - fn dfs_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let result = $crate::algorithms::dfs(&graph, vertices[0]); - assert!( - result.visited[vertices[0]], - "source vertex should be visited" - ); - assert_eq!( - result.predecessors[vertices[0]], None, - "unexpected predecessor of source vertex" - ); - for &v in &vertices[1..3] { - assert!( - !result.visited[v], - "disconnected vertex {v:?} should not be visited" - ); - assert_eq!( - result.predecessors[v], None, - "disconnected vertex {v:?} should have no predecessor" - ); - } - } - - #[test] - fn dfs() { - let (graph, vertices, _, _) = make_test_graph(); - let result = $crate::algorithms::dfs(&graph, vertices[0]); - assert_dfs_visited(&result.visited, &vertices); - assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices); - } - - #[test] - fn dfs_visited_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let visited = $crate::algorithms::dfs_visited(&graph, v); - assert!(visited[v], "source vertex should be visited"); - } - - #[test] - fn dfs_visited_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let visited = $crate::algorithms::dfs_visited(&graph, vertices[0]); - assert!(visited[vertices[0]], "source vertex should be visited"); - for &v in &vertices[1..3] { - assert!( - !visited[v], - "disconnected vertex {v:?} should not be visited" - ); - } - } - - #[test] - fn dfs_visited() { - let (graph, vertices, _, _) = make_test_graph(); - let visited = $crate::algorithms::dfs_visited(&graph, vertices[0]); - assert_dfs_visited(&visited, &vertices); - } - - #[test] - fn dfs_find_source() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert!( - $crate::algorithms::dfs_find(&graph, v, v), - "source should find itself" - ); - } - - #[test] - fn dfs_find_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert!( - !$crate::algorithms::dfs_find(&graph, vertices[0], vertices[1]), - "disconnected target should not be found" - ); - } - - #[test] - fn dfs_find() { - let (graph, vertices, _, _) = make_test_graph(); - for i in 0..10 { - assert!( - $crate::algorithms::dfs_find(&graph, vertices[0], vertices[i]), - "vertex {:?} should be reachable from {:?}", - vertices[i], - vertices[0] - ); - } - } - - #[test] - fn dfs_find_where_source_matches() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, v, |u| u == v), - Some(v), - "source should find itself" - ); - } - - #[test] - fn dfs_find_where_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "disconnected vertex {:?} should not be found", - vertices[1] - ); - } - - #[test] - fn dfs_find_where_no_match() { - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |_| false), - None, - "no vertex should match an always-false predicate" - ); - } - - #[test] - fn dfs_find_where_adjacent() { - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), - Some(vertices[1]), - "expected to find adjacent vertex" - ); - } - - #[test] - fn dfs_find_where() { - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!( - $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), - Some(vertices[9]), - "expected to find connected vertex" - ); - } - - fn assert_dfs_visited( - visited: &$crate::maps::VertexMap<<$T as $crate::traits::GraphTopology>::Vertex, bool>, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - for i in 0..10 { - assert!( - visited[vertices[i]], - "vertex {:?} should be visited", - vertices[i] - ); - } - } - - fn assert_dfs_predecessors( - graph: &$T, - visited: &$crate::maps::VertexMap<<$T as $crate::traits::GraphTopology>::Vertex, bool>, - predecessors: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option<<$T as $crate::traits::GraphTopology>::Vertex>, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - use $crate::traits::GraphTopology; - assert_eq!( - predecessors[vertices[0]], None, - "source should have no predecessor" - ); - for i in 1..10 { - let v = vertices[i]; - let p = predecessors[v].expect(&format!("vertex {v:?} should have a predecessor")); - assert!( - visited[p], - "predecessor {p:?} of vertex {v:?} should be visited" - ); - assert!( - graph.are_adjacent(v, p), - "predecessor {p:?} of vertex {v:?} should be adjacent" - ); - } - } - }; -} diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs deleted file mode 100644 index 3815c4e..0000000 --- a/src/testing/dijkstra_testing.rs +++ /dev/null @@ -1,314 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! dijkstra_tests { - ($T:ty) => { - #[test] - fn dijkstra_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let result = $crate::algorithms::dijkstra(&graph, v, |_| { - panic!("unexpected call of weight functor") - }); - assert_single_vertex(&result, v); - } - - #[test] - fn dijkstra_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |_| { - panic!("unexpected call of weight functor") - }); - assert_single_vertex(&result, vertices[0]); - assert_disconnected(&result, &vertices[1..3]); - } - - #[test] - fn dijkstra_zero_weight_loop() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 3] = - core::array::from_fn(|_| graph.add_vertex()); - let e1 = graph.add_edge(vertices[0], vertices[0]); - let e2 = graph.add_edge(vertices[1], vertices[1]); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - let mut weights = graph.edge_map(1); - weights[e1] = 0; - weights[e2] = 0; - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); - assert_single_vertex(&result, vertices[0]); - for i in 1..3 { - assert_eq!( - result.predecessors[vertices[i]], - Some(vertices[i - 1]), - "unexpected predecessor of vertex {:?}", - vertices[i] - ); - assert_eq!( - result.distances[vertices[i]], - Some(i.try_into().unwrap()), - "unexpected distance of vertex {:?}", - vertices[i] - ); - } - } - - #[test] - fn dijkstra() { - let (graph, vertices, edges, _, weights) = make_test_graph_weighted(); - let result = $crate::algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); - assert_test_graph(&result, &vertices); - } - - #[test] - fn dijkstra_distances() { - let (graph, vertices, edges, _, weights) = make_test_graph_weighted(); - let distances = - $crate::algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]); - assert_distances_test_graph(&distances, &vertices); - } - - #[test] - fn dijkstra_unweighted_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, v); - assert_single_vertex(&result, v) - } - - #[test] - fn dijkstra_unweighted_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); - assert_single_vertex(&result, vertices[0]); - assert_disconnected(&result, &vertices[1..3]); - } - - #[test] - fn dijkstra_unweighted() { - let (graph, vertices, _, _) = make_test_graph(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); - assert_unweighted_test_graph(&result, &vertices); - } - - #[test] - fn dijkstra_distances_unweighted_single_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v); - assert_distances_single_vertex(&distances, v); - } - - #[test] - fn dijkstra_distances_unweighted_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - assert_distances_single_vertex(&distances, vertices[0]); - assert_distances_disconnected(&distances, &vertices[1..3]); - } - - #[test] - fn dijkstra_distances_unweighted() { - let (graph, vertices, _, _) = make_test_graph(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - assert_distances_unweighted_test_graph(&distances, &vertices); - } - - fn assert_single_vertex( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - v: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - assert_distances_single_vertex(&result.distances, v); - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of source vertex", - ); - } - - fn assert_distances_single_vertex( - distances: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - v: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - assert_eq!( - distances[v], - Some(0), - "unexpected distance of source vertex" - ); - } - - fn assert_disconnected( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_disconnected(&result.distances, &vertices); - for &v in vertices { - assert_eq!( - result.predecessors[v], None, - "unexpected predecessor of disconnected vertex {v:?}", - ); - } - } - - fn assert_distances_disconnected( - distances: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - for &v in vertices { - assert_eq!( - distances[v], None, - "unexpected distance of disconnected vertex {v:?}", - ); - } - } - - fn assert_test_graph( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_test_graph(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[4])], - vec![Some(vertices[1])], - vec![Some(vertices[7])], - vec![Some(vertices[9])], - vec![Some(vertices[3])], - vec![Some(vertices[9])], - vec![Some(vertices[7])], - vec![Some(vertices[6])], - ]; - for i in 0..10 { - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } - } - - fn assert_distances_test_graph( - distances: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected_distances_from_v0 = [ - Some(0), - Some(1), - Some(65), - Some(4), - Some(58), - Some(43), - Some(14), - Some(46), - Some(145), - Some(29), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - fn assert_unweighted_test_graph( - result: &$crate::algorithms::DijkstraResult< - <$T as $crate::traits::GraphTopology>::Vertex, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - assert_distances_unweighted_test_graph(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ - vec![None], - vec![Some(vertices[0])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[1])], - vec![Some(vertices[2])], - vec![Some(vertices[2]), Some(vertices[3])], - vec![Some(vertices[4])], - vec![Some(vertices[4])], - vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], - ]; - for i in 0..10 { - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } - } - - fn assert_distances_unweighted_test_graph( - distances: &$crate::maps::VertexMap< - <$T as $crate::traits::GraphTopology>::Vertex, - Option, - >, - vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], - ) { - let expected_distances_from_v0 = [ - Some(0), - Some(1), - Some(2), - Some(2), - Some(2), - Some(3), - Some(3), - Some(3), - Some(3), - Some(4), - ]; - for i in 0..10 { - assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } - } - - fn make_test_graph_weighted() -> ( - $T, - [<$T as $crate::traits::GraphTopology>::Vertex; 10], - [( - <$T as $crate::traits::GraphTopology>::Edge, - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Vertex, - ); 18], - [Vec<( - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Edge, - )>; 10], - $crate::maps::EdgeMap<<$T as $crate::traits::GraphTopology>::Edge, u32>, - ) { - use $crate::traits::GraphTopology; - let (graph, vertices, edges, incidences) = make_test_graph(); - let mut weights = graph.edge_map(99_u32); - for i in [1, 3, 7, 10, 12, 14, 15, 17] { - weights[edges[i].0] = i.try_into().unwrap(); - } - (graph, vertices, edges, incidences, weights) - } - }; -} diff --git a/src/testing/find_path_testing.rs b/src/testing/find_path_testing.rs deleted file mode 100644 index c9c4e42..0000000 --- a/src/testing/find_path_testing.rs +++ /dev/null @@ -1,124 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! find_path_tests { - ($T:ty) => { - #[test] - fn find_path_source_equals_target() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_path(&graph, v, v), - Some(vec![]), - "path from source to itself should be empty" - ); - } - - #[test] - fn find_path_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[1]), - None, - "no path should exist to disconnected vertex" - ); - } - - #[test] - fn find_path_adjacent() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - let path = $crate::algorithms::dfs_find_path(&graph, v1, v2) - .expect("path should exist between adjacent vertices"); - assert_eq!( - path.len(), - 1, - "unexpected path length between adjacent vertices" - ); - assert_eq!(path[0], e, "path should use the connecting edge"); - } - - #[test] - fn find_path() { - let (graph, vertices, _, _) = make_test_graph(); - let path = $crate::algorithms::dfs_find_path(&graph, vertices[0], vertices[9]) - .expect(&format!( - "path should exist between connected vertices {:?} and {:?}", - vertices[0], vertices[9] - )); - assert_valid_path(&graph, &path, vertices[0], vertices[9]); - } - - #[test] - fn find_path_where_source_matches() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, v, |u| u == v), - Some(vec![]), - "path from source to itself should be empty" - ); - } - - #[test] - fn find_path_where_disconnected() { - let (graph, vertices) = make_test_graph_disconnected(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]), - None, - "no path should exist to disconnected vertex" - ); - } - - #[test] - fn find_path_where_no_match() { - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!( - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |_| false), - None, - "no path should exist when predicate never matches" - ); - } - - #[test] - fn find_path_where() { - let (graph, vertices, _, _) = make_test_graph(); - let path = - $crate::algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]) - .expect(&format!( - "path should exist between connected vertices {:?} and {:?}", - vertices[0], vertices[9] - )); - assert_valid_path(&graph, &path, vertices[0], vertices[9]); - } - - fn assert_valid_path( - graph: &$T, - path: &[<$T as $crate::traits::GraphTopology>::Edge], - source: <$T as $crate::traits::GraphTopology>::Vertex, - target: <$T as $crate::traits::GraphTopology>::Vertex, - ) { - use $crate::traits::GraphTopology; - assert!(!path.is_empty(), "path should be non-empty"); - // Walks the path: tracks current vertex, confirm each edge is incident to it. - let mut current = source; - for (i, &e) in path.iter().enumerate() { - let (v1, v2) = graph.incident_vertices(e); - assert_ne!(v1, v2, "path should not contain loop edge {e:?}"); - assert!( - v1 == current || v2 == current, - "path edge {e:?} (index {i}, from {v1:?} to {v2:?}) is not incident to current path vertex {current:?}" - ); - current = if v1 == current { v2 } else { v1 }; - } - assert_eq!( - current, target, - "path should end at target {target:?}, but ended at {current:?}" - ); - } - }; -} diff --git a/src/testing/fixtures.rs b/src/testing/fixtures.rs new file mode 100644 index 0000000..b118fe7 --- /dev/null +++ b/src/testing/fixtures.rs @@ -0,0 +1,493 @@ +use std::fmt::Debug; + +use crate::maps::ElementMap; +use crate::models::{AppendGraph, FrozenGraph}; +use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence}; + +pub trait MakeTestGraph: GraphTopology + Sized { + // Since this is only a test fixture, the complex return type here is deemed acceptable. + #[allow(clippy::type_complexity)] + fn standard() -> ( + Self, + [Self::Vertex; 10], + [(Self::Edge, Self::Vertex, Self::Vertex); 18], + [Vec>; 10], + ); + + fn empty() -> Self; + + fn single_vertex() -> (Self, Self::Vertex); + + fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge); + + fn disconnected() -> (Self, [Self::Vertex; 3]); + + fn loop_edge() -> (Self, Self::Vertex, Self::Edge); + + fn multiple_edges() -> (Self, [Self::Vertex; 2], [Self::Edge; K]); + + fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]); + + fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]); +} + +impl MakeTestGraph for G { + fn standard() -> ( + Self, + [Self::Vertex; 10], + [(Self::Edge, Self::Vertex, Self::Vertex); 18], + [Vec>; 10], + ) { + let mut graph = Self::default(); + graph.reserve_vertices(10); + graph.reserve_edges(18); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + let edges = [ + (vertices[0], vertices[1]), + (vertices[0], vertices[1]), + (vertices[1], vertices[2]), + (vertices[1], vertices[3]), + (vertices[1], vertices[4]), + (vertices[2], vertices[2]), + (vertices[2], vertices[4]), + (vertices[2], vertices[4]), + (vertices[2], vertices[5]), + (vertices[2], vertices[6]), + (vertices[3], vertices[6]), + (vertices[4], vertices[4]), + (vertices[4], vertices[7]), + (vertices[4], vertices[8]), + (vertices[5], vertices[9]), + (vertices[6], vertices[9]), + (vertices[7], vertices[8]), + (vertices[7], vertices[9]), + ] + .map(|(u, v)| (graph.add_edge(u, v), u, v)); + let i = |vertex, edge| Incidence { vertex, edge }; + let incidences = [ + vec![i(vertices[1], edges[0].0), i(vertices[1], edges[1].0)], + vec![ + i(vertices[0], edges[0].0), + i(vertices[0], edges[1].0), + i(vertices[2], edges[2].0), + i(vertices[3], edges[3].0), + i(vertices[4], edges[4].0), + ], + vec![ + i(vertices[2], edges[5].0), + i(vertices[2], edges[5].0), + i(vertices[1], edges[2].0), + i(vertices[4], edges[6].0), + i(vertices[4], edges[7].0), + i(vertices[5], edges[8].0), + i(vertices[6], edges[9].0), + ], + vec![i(vertices[1], edges[3].0), i(vertices[6], edges[10].0)], + vec![ + i(vertices[1], edges[4].0), + i(vertices[2], edges[6].0), + i(vertices[2], edges[7].0), + i(vertices[4], edges[11].0), + i(vertices[4], edges[11].0), + i(vertices[7], edges[12].0), + i(vertices[8], edges[13].0), + ], + vec![i(vertices[2], edges[8].0), i(vertices[9], edges[14].0)], + vec![ + i(vertices[2], edges[9].0), + i(vertices[3], edges[10].0), + i(vertices[9], edges[15].0), + ], + vec![ + i(vertices[4], edges[12].0), + i(vertices[8], edges[16].0), + i(vertices[9], edges[17].0), + ], + vec![i(vertices[4], edges[13].0), i(vertices[7], edges[16].0)], + vec![ + i(vertices[5], edges[14].0), + i(vertices[6], edges[15].0), + i(vertices[7], edges[17].0), + ], + ]; + (graph, vertices, edges, incidences) + } + + fn empty() -> Self { + Self::default() + } + + fn single_vertex() -> (Self, Self::Vertex) { + let mut graph = Self::default(); + let v = graph.add_vertex(); + (graph, v) + } + + fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge) { + let mut graph = Self::default(); + graph.reserve_vertices(2); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + let e = graph.add_edge(vertices[0], vertices[1]); + (graph, vertices, e) + } + + fn disconnected() -> (Self, [Self::Vertex; 3]) { + let mut graph = Self::default(); + graph.reserve_vertices(3); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[1], vertices[2]); + (graph, vertices) + } + + fn loop_edge() -> (Self, Self::Vertex, Self::Edge) { + let mut graph = Self::default(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + (graph, v, e) + } + + fn multiple_edges() -> (Self, [Self::Vertex; 2], [Self::Edge; K]) { + let mut graph = Self::default(); + graph.reserve_vertices(2); + graph.reserve_edges(K); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + let edges = core::array::from_fn(|_| graph.add_edge(vertices[0], vertices[1])); + (graph, vertices, edges) + } + + fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]) { + let mut graph = Self::default(); + graph.reserve_vertices(3); + graph.reserve_edges(2); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + let edges = core::array::from_fn(|i| graph.add_edge(vertices[i], vertices[i + 1])); + (graph, vertices, edges) + } + + fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]) { + let mut graph = Self::default(); + graph.reserve_vertices(3); + graph.reserve_edges(4); + let vertices = core::array::from_fn(|_| graph.add_vertex()); + // Path edges. + let e0 = graph.add_edge(vertices[0], vertices[1]); + let e1 = graph.add_edge(vertices[1], vertices[2]); + // Loops. + let e2 = graph.add_edge(vertices[0], vertices[0]); + let e3 = graph.add_edge(vertices[1], vertices[1]); + (graph, vertices, [e0, e1, e2, e3]) + } +} + +impl MakeTestGraph for FrozenGraph { + fn standard() -> ( + Self, + [Self::Vertex; 10], + [(Self::Edge, Self::Vertex, Self::Vertex); 18], + [Vec>; 10], + ) { + let (build, vertices, edges, incidences) = AppendGraph::standard(); + let (graph, iso) = FrozenGraph::convert(&build); + ( + graph, + vertices.map(|v| iso.vertex(v)), + edges.map(|(e, u, v)| (iso.edge(e), iso.vertex(u), iso.vertex(v))), + incidences.map(|vec| { + vec.iter() + .map(|i| Incidence { + vertex: iso.vertex(i.vertex), + edge: iso.edge(i.edge), + }) + .collect() + }), + ) + } + + fn empty() -> Self { + FrozenGraph::from(&AppendGraph::empty()) + } + + fn single_vertex() -> (Self, Self::Vertex) { + let (build, v) = AppendGraph::single_vertex(); + let (graph, iso) = FrozenGraph::convert(&build); + (graph, iso.vertex(v)) + } + + fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge) { + let (build, vertices, e) = AppendGraph::single_edge(); + let (graph, iso) = FrozenGraph::convert(&build); + (graph, vertices.map(|v| iso.vertex(v)), iso.edge(e)) + } + + fn disconnected() -> (Self, [Self::Vertex; 3]) { + let (build, vertices) = AppendGraph::disconnected(); + let (graph, iso) = FrozenGraph::convert(&build); + (graph, vertices.map(|v| iso.vertex(v))) + } + + fn loop_edge() -> (Self, Self::Vertex, Self::Edge) { + let (build, v, e) = AppendGraph::loop_edge(); + let (graph, iso) = FrozenGraph::convert(&build); + (graph, iso.vertex(v), iso.edge(e)) + } + + fn multiple_edges() -> (Self, [Self::Vertex; 2], [Self::Edge; K]) { + let (build, vertices, edges) = AppendGraph::multiple_edges(); + let (graph, iso) = FrozenGraph::convert(&build); + ( + graph, + vertices.map(|v| iso.vertex(v)), + edges.map(|e| iso.edge(e)), + ) + } + + fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]) { + let (build, vertices, edges) = AppendGraph::two_edge_path(); + let (graph, iso) = FrozenGraph::convert(&build); + ( + graph, + vertices.map(|v| iso.vertex(v)), + edges.map(|e| iso.edge(e)), + ) + } + + fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]) { + let (build, vertices, edges) = AppendGraph::two_edge_path_with_loops(); + let (graph, iso) = FrozenGraph::convert(&build); + ( + graph, + vertices.map(|v| iso.vertex(v)), + edges.map(|e| iso.edge(e)), + ) + } +} + +pub fn assert_standard_unweighted_distances_v0(actual_for: F, vertices: &[V]) +where + V: Debug + Copy, + F: Fn(V) -> Option, +{ + let expected = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4]; + for (i, v) in vertices.iter().enumerate() { + assert_eq!( + actual_for(*v), + Some(expected[i]), + "unexpected distance from vertex {:?} to vertex {:?}", + vertices[0], + vertices[i] + ); + } +} + +pub fn assert_standard_unweighted_predecessors_v0( + actual: &ElementMap>, + vertices: &[V], +) { + let expected = [ + vec![None], + vec![Some(vertices[0])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[1])], + vec![Some(vertices[2])], + vec![Some(vertices[2]), Some(vertices[3])], + vec![Some(vertices[4])], + vec![Some(vertices[4])], + vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], + ]; + assert_eq!(vertices.len(), expected.len()); + for (&v, exp) in vertices.iter().zip(&expected) { + assert!( + exp.contains(&actual[v]), + "unexpected predecessor {:?} of vertex {v:?}", + actual[v] + ); + } +} + +/// Asserts that the path given as `path_option` is a valid path in `graph` +/// +/// Walks the path and asserts that it is `Some`, that it is empty if and only if +/// `source == target`, that its edges are pairwise incident in the order given, and that it starts +/// at `source` and ends at `target`. +pub fn assert_valid_path( + graph: &G, + path_option: Option>, + source: G::Vertex, + targets: &[G::Vertex], +) -> Vec +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let path = path_option.unwrap_or_else(|| { + panic!( + "path should exist between source vertex {source:?} and connected targets {targets:?}" + ) + }); + + if targets.contains(&source) { + assert!( + path.is_empty(), + "path from source to itself should be empty" + ); + } else { + assert!(!path.is_empty(), "path should not be empty"); + // Walks the path: tracks current vertex, confirms each edge is incident to it. + let mut current = source; + for (i, &e) in path.iter().enumerate() { + let (u, v) = graph.incident_vertices(e); + assert_ne!(u, v, "path should not contain loop edge {e:?}"); + assert!( + u == current || v == current, + "path edge {e:?} (index {i}, vertices {u:?} to {v:?}) not incident to vertex {current:?}" + ); + current = if u == current { v } else { u }; + } + assert!( + targets.contains(¤t), + "path should end at a target vertex ({targets:?}), but ended at {current:?}" + ); + } + path +} + +/// Asserts that the two graphs are isomorphic. +/// +/// `vertices` must contain all vertices of the `original` graph, and `vertex_map` must for any +/// vertex in the `original` graph return the corresponding vertex in the `other` graph. +/// +/// `edges` must contain all edges and their endpoints of the `original` graph, and `edge_map` must +/// for any edge in the `original` graph return the corresponding edge in the `other` graph. +pub fn assert_isomorphic( + original: &G, + other: &H, + vertices: &[G::Vertex], + vertex_map: impl Fn(G::Vertex) -> H::Vertex, + edges: &[(G::Edge, G::Vertex, G::Vertex)], + edge_map: impl Fn(G::Edge) -> H::Edge, +) where + G::Vertex: Debug, + G::Edge: Debug, + H::Vertex: Debug, + H::Edge: Debug, +{ + assert_eq!( + original.vertex_count(), + vertices.len(), + "precondition: all vertices of the original must be provided" + ); + assert_eq!( + original.edge_count(), + edges.len(), + "precondition: all edges of the original must be provided" + ); + assert_eq!( + other.vertex_count(), + original.vertex_count(), + "other graph should have the same vertex count as the original" + ); + assert_eq!( + other.edge_count(), + original.edge_count(), + "other graph should have the same edge count as the original" + ); + for &v in vertices { + assert_eq!( + other.degree(vertex_map(v)), + original.degree(v), + "vertex {v:?} should have the same degree in the other graph as in the original" + ); + let mut expected: Vec<_> = original.incidences(v).collect(); + for incidence in other.incidences(vertex_map(v)) { + let pos = expected + .iter() + .position(|x| { + vertex_map(x.vertex) == incidence.vertex && edge_map(x.edge) == incidence.edge + }) + .unwrap_or_else(|| { + panic!("unexpected incidence {incidence:?} of vertex {v:?} in the other graph") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {expected:?} of vertex {v:?} are missing in the other graph" + ); + } + for &(e, u, v) in edges { + assert_eq!( + other.incident_vertices(edge_map(e)), + (vertex_map(u), vertex_map(v)), + "edge {e:?} should have the same incident vertices in the other graph as in the original" + ); + } +} + +/// Asserts that mutation of the two graphs happens independently. +/// +/// `vertices` must contain all vertices of the `original` graph, which must not be empty, and +/// `vertex_map` must for any vertex in the `original` graph return the corresponding vertex in the +/// `other` graph. +pub fn assert_mutation_independent( + original: &mut G, + other: &mut H, + vertices: &[G::Vertex], + vertex_map: impl Fn(G::Vertex) -> H::Vertex, +) { + let edge_count = original.edge_count(); + assert_eq!( + original.vertex_count(), + vertices.len(), + "precondition: all vertices of the original must be provided" + ); + assert!( + !vertices.is_empty(), + "precondition: at least one vertex must be provided" + ); + let u = other.add_vertex(); + other.add_edge(vertex_map(vertices[0]), u); + assert_eq!( + other.vertex_count(), + vertices.len() + 1, + "precondition: vertex count of other graph" + ); + assert_eq!( + other.edge_count(), + edge_count + 1, + "precondition: edge count of other graph" + ); + assert_eq!( + original.vertex_count(), + vertices.len(), + "mutating the other graph should not affect the vertex count of the original" + ); + assert_eq!( + original.edge_count(), + edge_count, + "mutating the other graph should not affect the edge count of the original" + ); + + let v = original.add_vertex(); + original.add_edge(vertices[0], v); + assert_eq!( + original.vertex_count(), + vertices.len() + 1, + "precondition: vertex count of original" + ); + assert_eq!( + original.edge_count(), + edge_count + 1, + "precondition: edge count of original" + ); + assert_eq!( + other.vertex_count(), + vertices.len() + 1, + "mutating the original should not affect the vertex count of the other graph" + ); + assert_eq!( + other.edge_count(), + edge_count + 1, + "mutating the original should not affect the edge count of the other graph" + ); +} diff --git a/src/testing/from_testing.rs b/src/testing/from_testing.rs new file mode 100644 index 0000000..2253fd7 --- /dev/null +++ b/src/testing/from_testing.rs @@ -0,0 +1,38 @@ +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::GraphTopology; + +#[doc(hidden)] +#[macro_export] +macro_rules! from_tests { + ($T:ty, $U:ty) => { + $crate::from_tests!(@wrap $T, $U, + from, + ); + }; + + (@wrap $T:ty, $U:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::from_testing::$name::<$T, $U>(); + } + )* + }; +} + +pub fn from() +where + G: for<'a> From<&'a H> + GraphTopology, + H: MakeTestGraph, +{ + let (source, _, _, _) = H::standard(); + let graph = G::from(&source); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 18, "unexpected edge count"); + + let mut expected: Vec<_> = source.vertices().map(|v| source.degree(v)).collect(); + let mut actual: Vec<_> = graph.vertices().map(|v| graph.degree(v)).collect(); + expected.sort(); + actual.sort(); + assert_eq!(actual, expected, "degree sequence should match"); +} diff --git a/src/testing/graph_topology_addition_clone_testing.rs b/src/testing/graph_topology_addition_clone_testing.rs new file mode 100644 index 0000000..2f360eb --- /dev/null +++ b/src/testing/graph_topology_addition_clone_testing.rs @@ -0,0 +1,28 @@ +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::GraphTopologyAddition; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_addition_clone_tests { + ($T:ty) => { + $crate::graph_topology_addition_clone_tests!(@wrap $T, + clone_mutation_independent, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_addition_clone_testing::$name::<$T>(); + } + )* + }; +} + +pub fn clone_mutation_independent() { + let (mut graph, vertices, _) = G::single_edge(); + let mut clone = graph.clone(); + fixtures::assert_mutation_independent(&mut graph, &mut clone, &vertices, |v| v); +} diff --git a/src/testing/graph_topology_addition_conversion_testing.rs b/src/testing/graph_topology_addition_conversion_testing.rs new file mode 100644 index 0000000..6d2310c --- /dev/null +++ b/src/testing/graph_topology_addition_conversion_testing.rs @@ -0,0 +1,65 @@ +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{GraphTopologyAddition, GraphTopologyConversion}; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_addition_conversion_tests { + ($T:ty, $U:ty) => { + $crate::graph_topology_addition_conversion_tests!(@wrap $T, $U, + convert_mutation_independent, + convert_map_stale_source_map, + ); + }; + + (@wrap $T:ty, $U:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_addition_conversion_testing::$name::<$T, $U>(); + } + )* + }; +} + +pub fn convert_mutation_independent() +where + G: GraphTopologyConversion + GraphTopologyAddition, + H: MakeTestGraph + GraphTopologyAddition, +{ + let (mut source, vertices, _) = H::single_edge(); + let (mut graph, iso) = G::convert(&source); + fixtures::assert_mutation_independent(&mut source, &mut graph, &vertices, |v| iso.vertex(v)); +} + +pub fn convert_map_stale_source_map() +where + G: GraphTopologyConversion + GraphTopologyAddition, + H: MakeTestGraph + GraphTopologyAddition, +{ + let (mut source, [u, v], _) = H::single_edge(); + let mut source_map = source.vertex_map(9); + source_map[u] = 1; + source_map[v] = 2; + let added = add_vertices_until_capacity_grows(&mut source); + let (_, iso) = G::convert(&source); + let map = iso.convert_vertex_map(&source_map); + assert_eq!(map[iso.vertex(u)], 1, "unexpected value for first vertex"); + assert_eq!(map[iso.vertex(v)], 2, "unexpected value for second vertex"); + for w in added { + assert_eq!( + map[iso.vertex(w)], + 9, + "vertex not covered by the source map should hold the default" + ); + } +} + +fn add_vertices_until_capacity_grows(graph: &mut G) -> Vec { + let capacity = graph.vertex_capacity(); + let mut added = Vec::new(); + while graph.vertex_capacity() == capacity { + added.push(graph.add_vertex()); + } + added +} diff --git a/src/testing/graph_topology_addition_deletion_conversion_testing.rs b/src/testing/graph_topology_addition_deletion_conversion_testing.rs new file mode 100644 index 0000000..aa8e2d4 --- /dev/null +++ b/src/testing/graph_topology_addition_deletion_conversion_testing.rs @@ -0,0 +1,56 @@ +use std::fmt::Debug; + +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{GraphTopologyAddition, GraphTopologyConversion, GraphTopologyDeletion}; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_addition_deletion_conversion_tests { + ($T:ty, $U:ty) => { + $crate::graph_topology_addition_deletion_conversion_tests!(@wrap $T, $U, + convert_after_delete_add_vertex, + ); + }; + + (@wrap $T:ty, $U:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_addition_deletion_conversion_testing::$name::<$T, $U>(); + } + )* + }; +} + +pub fn convert_after_delete_add_vertex() +where + G: GraphTopologyConversion, + H: MakeTestGraph + GraphTopologyDeletion + GraphTopologyAddition, + G::Vertex: Debug, + G::Edge: Debug, + H::Vertex: Debug, + H::Edge: Debug, +{ + let (mut source, vertices, edges, _) = H::standard(); + let del = vertices[2]; + source.delete_vertex(del); + // Depending on the graph type, the new vertex may reuse the slot of the deleted one. + let added = source.add_vertex(); + let mut vertices: Vec<_> = vertices.into_iter().filter(|&v| v != del).collect(); + vertices.push(added); + let edges: Vec<_> = edges + .into_iter() + .filter(|&(_, u, v)| u != del && v != del) + .collect(); + + let (graph, iso) = G::convert(&source); + fixtures::assert_isomorphic( + &source, + &graph, + &vertices, + |v| iso.vertex(v), + &edges, + |e| iso.edge(e), + ); +} diff --git a/src/testing/graph_topology_addition_deletion_testing.rs b/src/testing/graph_topology_addition_deletion_testing.rs new file mode 100644 index 0000000..a6e6643 --- /dev/null +++ b/src/testing/graph_topology_addition_deletion_testing.rs @@ -0,0 +1,62 @@ +use std::fmt::Debug; + +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{GraphTopologyAddition, GraphTopologyDeletion}; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_addition_deletion_tests { + ($T:ty) => { + $crate::graph_topology_addition_deletion_tests!(@wrap $T, + delete_vertex_add_vertex, + delete_edge_add_edge, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_addition_deletion_testing::$name::<$T>(); + } + )* + }; +} + +pub fn delete_vertex_add_vertex() +where + G::Vertex: Debug, +{ + let (mut graph, v) = G::single_vertex(); + graph.delete_vertex(v); + assert_eq!( + graph.vertex_count(), + 0, + "unexpected vertex count after delete" + ); + assert_ne!( + graph.add_vertex(), + v, + "unexpected duplicate vertex after delete" + ); + assert_eq!( + graph.vertex_count(), + 1, + "unexpected vertex count after re-add" + ); +} + +pub fn delete_edge_add_edge() +where + G::Edge: Debug, +{ + let (mut graph, [v0, v1], e) = G::single_edge(); + graph.delete_edge(e); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + assert_ne!( + graph.add_edge(v0, v1), + e, + "unexpected duplicate edge after re-add" + ); + assert_eq!(graph.edge_count(), 1, "unexpected edge count after re-add"); +} diff --git a/src/testing/graph_topology_addition_testing.rs b/src/testing/graph_topology_addition_testing.rs new file mode 100644 index 0000000..c15d0e6 --- /dev/null +++ b/src/testing/graph_topology_addition_testing.rs @@ -0,0 +1,193 @@ +use std::fmt::Debug; + +use crate::traits::GraphTopologyAddition; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_addition_tests { + ($T:ty) => { + $crate::graph_topology_addition_tests!(@wrap $T, + reserve_vertices_increases_capacity, + reserve_vertices_prevents_reallocation_on_add, + reserve_vertices_does_not_affect_edge_capacity, + reserve_edges_increases_capacity, + reserve_edges_prevents_reallocation_on_add, + reserve_edges_does_not_affect_vertex_capacity, + add_vertex, + add_edge, + sync_vertex_map_after_add, + sync_edge_map_after_add, + sync_vertex_map_preserves_values, + sync_edge_map_preserves_values, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_addition_testing::$name::<$T>(); + } + )* + }; +} + +pub fn reserve_vertices_increases_capacity() { + let mut graph = G::default(); + let capacity_before = graph.vertex_capacity(); + graph.reserve_vertices(capacity_before + 10); + assert!( + graph.vertex_capacity() > capacity_before, + "expected sufficient capacity increase after reserve" + ); +} + +pub fn reserve_vertices_prevents_reallocation_on_add() { + const N: usize = 10; + let mut graph = G::default(); + graph.reserve_vertices(N); + let capacity_before = graph.vertex_capacity(); + for _ in 0..N { + graph.add_vertex(); + } + assert_eq!( + graph.vertex_capacity(), + capacity_before, + "writes within reserved capacity should not reallocate" + ); +} + +pub fn reserve_vertices_does_not_affect_edge_capacity() { + let mut graph = G::default(); + let capacity_before = graph.edge_capacity(); + graph.reserve_vertices(10); + assert_eq!( + graph.edge_capacity(), + capacity_before, + "reserve_vertices must not change edge capacity" + ); +} + +pub fn reserve_edges_increases_capacity() { + let mut graph = G::default(); + let capacity_before = graph.edge_capacity(); + graph.reserve_edges(capacity_before + 10); + assert!( + graph.edge_capacity() > capacity_before, + "expected sufficient capacity increase after reserve" + ); +} + +pub fn reserve_edges_prevents_reallocation_on_add() { + const N: usize = 10; + let mut graph = G::default(); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + graph.reserve_edges(N); + let capacity_before = graph.edge_capacity(); + for _ in 0..N { + graph.add_edge(u, v); + } + assert_eq!( + graph.edge_capacity(), + capacity_before, + "writes within reserved capacity should not reallocate" + ); +} + +pub fn reserve_edges_does_not_affect_vertex_capacity() { + let mut graph = G::default(); + let capacity_before = graph.vertex_capacity(); + graph.reserve_edges(10); + assert_eq!( + graph.vertex_capacity(), + capacity_before, + "reserve_edges must not change vertex capacity" + ); +} + +pub fn add_vertex() +where + G::Vertex: Debug, +{ + let mut graph = G::default(); + let v = graph.add_vertex(); + assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); +} + +pub fn add_edge() +where + G::Edge: Debug, +{ + let mut graph = G::default(); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + let e = graph.add_edge(u, v); + assert_ne!(graph.add_edge(u, v), e, "unexpected duplicate edge"); +} + +pub fn sync_vertex_map_after_add() { + let mut graph = G::default(); + let mut map = graph.vertex_map(27); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + graph.sync_vertex_map(&mut map); + assert_eq!( + map[u], 27, + "unexpected value from default map read for new vertex" + ); + map[u] = 9; + assert_eq!( + map[u], 9, + "unexpected value from map read after write for new vertex" + ); + assert_eq!( + map[v], 27, + "unexpected value from default map read for second new vertex" + ); +} + +pub fn sync_edge_map_after_add() { + let mut graph = G::default(); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + let mut map = graph.edge_map(28); + let e = graph.add_edge(u, v); + let f = graph.add_edge(u, v); + graph.sync_edge_map(&mut map); + assert_eq!( + map[e], 28, + "unexpected value from default map read for new edge" + ); + map[e] = 8; + assert_eq!( + map[e], 8, + "unexpected value from map read after write for new edge" + ); + assert_eq!( + map[f], 28, + "unexpected value from default map read for second new edge" + ); +} + +pub fn sync_vertex_map_preserves_values() { + let mut graph = G::default(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.reserve_vertices(20); + graph.sync_vertex_map(&mut map); + assert_eq!(map[v], 5, "sync should not change existing map values"); +} + +pub fn sync_edge_map_preserves_values() { + let mut graph = G::default(); + let u = graph.add_vertex(); + let v = graph.add_vertex(); + let e = graph.add_edge(u, v); + let mut map = graph.edge_map(0); + map[e] = 6; + graph.reserve_edges(20); + graph.sync_edge_map(&mut map); + assert_eq!(map[e], 6, "sync should not change existing map values"); +} diff --git a/src/testing/graph_topology_clone_testing.rs b/src/testing/graph_topology_clone_testing.rs new file mode 100644 index 0000000..b4ffd1e --- /dev/null +++ b/src/testing/graph_topology_clone_testing.rs @@ -0,0 +1,36 @@ +use std::fmt::Debug; + +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_clone_tests { + ($T:ty) => { + $crate::graph_topology_clone_tests!(@wrap $T, + clone_handles_interchangeable, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_clone_testing::$name::<$T>(); + } + )* + }; +} + +/// Queries the clone using vertex and edge handles obtained from the original graph, and asserts +/// that the results are identical to querying the original with those same handles. This is only +/// meaningful because Vertex and Edge handles are not tied to a particular graph instance. +pub fn clone_handles_interchangeable() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, edges, _) = G::standard(); + let clone = graph.clone(); + fixtures::assert_isomorphic(&graph, &clone, &vertices, |v| v, &edges, |e| e); +} diff --git a/src/testing/graph_topology_conversion_testing.rs b/src/testing/graph_topology_conversion_testing.rs new file mode 100644 index 0000000..6478ed9 --- /dev/null +++ b/src/testing/graph_topology_conversion_testing.rs @@ -0,0 +1,154 @@ +use std::fmt::Debug; + +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::GraphTopologyConversion; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_conversion_tests { + ($T:ty, $U:ty) => { + $crate::graph_topology_conversion_tests!(@wrap $T, $U, + convert, + convert_empty, + convert_vertex_map, + convert_edge_map, + convert_vertex_map_defaults, + convert_edge_map_defaults, + convert_map_empty, + ); + }; + + (@wrap $T:ty, $U:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_conversion_testing::$name::<$T, $U>(); + } + )* + }; +} + +pub fn convert() +where + G::Vertex: Debug, + G::Edge: Debug, + H::Vertex: Debug, + H::Edge: Debug, +{ + let (source, vertices, edges, _) = H::standard(); + let (graph, iso) = G::convert(&source); + fixtures::assert_isomorphic( + &source, + &graph, + &vertices, + |v| iso.vertex(v), + &edges, + |e| iso.edge(e), + ); +} + +pub fn convert_empty() +where + G::Vertex: Debug, + G::Edge: Debug, + H::Vertex: Debug, + H::Edge: Debug, +{ + let source = H::empty(); + let (graph, iso) = G::convert(&source); + fixtures::assert_isomorphic( + &source, + &graph, + &[], + |v| iso.vertex(v), + &[], + |e| iso.edge(e), + ); +} + +pub fn convert_vertex_map() +where + H::Vertex: Debug, +{ + let (source, vertices, _, _) = H::standard(); + let (_, iso) = G::convert(&source); + let mut source_map = source.vertex_map(None); + for (i, &v) in vertices.iter().enumerate() { + source_map[v] = Some(i); + } + let map = iso.convert_vertex_map(&source_map); + for (i, &v) in vertices.iter().enumerate() { + assert_eq!( + map[iso.vertex(v)], + Some(i), + "unexpected converted value for vertex {v:?}" + ); + } +} + +pub fn convert_edge_map() +where + H::Edge: Debug, +{ + let (source, _, edges, _) = H::standard(); + let (_, iso) = G::convert(&source); + let mut source_map = source.edge_map(None); + for (i, &(e, _, _)) in edges.iter().enumerate() { + source_map[e] = Some(i); + } + let map = iso.convert_edge_map(&source_map); + for (i, &(e, _, _)) in edges.iter().enumerate() { + assert_eq!( + map[iso.edge(e)], + Some(i), + "unexpected converted value for edge {e:?}" + ); + } +} + +pub fn convert_vertex_map_defaults() +where + G::Vertex: Debug, +{ + let (source, vertices, _, _) = H::standard(); + let (graph, iso) = G::convert(&source); + + for &v in &vertices { + let mut source_map = source.vertex_map(8); + source_map[v] = 1; + let map = iso.convert_vertex_map(&source_map); + let mapped = iso.vertex(v); + assert_eq!(map[mapped], 1, "unexpected value for set vertex {mapped:?}"); + for u in graph.vertices().filter(|&x| x != mapped) { + assert_eq!(map[u], 8, "unset vertex {u:?} should hold the default"); + } + } +} + +pub fn convert_edge_map_defaults() +where + G::Edge: Debug, +{ + let (source, _, edges, _) = H::standard(); + let (graph, iso) = G::convert(&source); + + for &(e, _, _) in &edges { + let mut source_map = source.edge_map(8); + source_map[e] = 1; + let map = iso.convert_edge_map(&source_map); + let mapped = iso.edge(e); + assert_eq!(map[mapped], 1, "unexpected value for set edge {mapped:?}"); + for f in graph.edges().filter(|&x| x != mapped) { + assert_eq!(map[f], 8, "unset edge {f:?} should hold the default"); + } + } +} + +pub fn convert_map_empty() { + // Checks that converting maps of a graph without vertices and edges does not panic. + let source = H::empty(); + let (_, iso) = G::convert(&source); + iso.convert_vertex_map(&source.vertex_map(0)); + iso.convert_edge_map(&source.edge_map(0)); +} diff --git a/src/testing/graph_topology_deletion_conversion_testing.rs b/src/testing/graph_topology_deletion_conversion_testing.rs new file mode 100644 index 0000000..31aea56 --- /dev/null +++ b/src/testing/graph_topology_deletion_conversion_testing.rs @@ -0,0 +1,55 @@ +use std::fmt::Debug; + +use crate::testing::fixtures; +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{GraphTopologyConversion, GraphTopologyDeletion}; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_deletion_conversion_tests { + ($T:ty, $U:ty) => { + $crate::graph_topology_deletion_conversion_tests!(@wrap $T, $U, + convert_after_deletion, + ); + }; + + (@wrap $T:ty, $U:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_deletion_conversion_testing::$name::<$T, $U>(); + } + )* + }; +} + +pub fn convert_after_deletion() +where + G: GraphTopologyConversion, + H: MakeTestGraph + GraphTopologyDeletion, + G::Vertex: Debug, + G::Edge: Debug, + H::Vertex: Debug, + H::Edge: Debug, +{ + let (mut source, vertices, edges, _) = H::standard(); + + // Deletes a vertex together with its incident edges, which leaves gaps in the source's slots. + let del = vertices[2]; + source.delete_vertex(del); + let vertices: Vec<_> = vertices.into_iter().filter(|&v| v != del).collect(); + let edges: Vec<_> = edges + .into_iter() + .filter(|&(_, u, v)| u != del && v != del) + .collect(); + + let (graph, iso) = G::convert(&source); + fixtures::assert_isomorphic( + &source, + &graph, + &vertices, + |v| iso.vertex(v), + &edges, + |e| iso.edge(e), + ); +} diff --git a/src/testing/graph_topology_deletion_testing.rs b/src/testing/graph_topology_deletion_testing.rs new file mode 100644 index 0000000..7ad8edf --- /dev/null +++ b/src/testing/graph_topology_deletion_testing.rs @@ -0,0 +1,505 @@ +use std::fmt::Debug; + +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{ + GraphTopology, GraphTopologyAddition, GraphTopologyDeletion, Incidence, IncidenceCursor, +}; + +#[doc(hidden)] +#[macro_export] +macro_rules! graph_topology_deletion_tests { + ($T:ty) => { + $crate::graph_topology_deletion_tests!(@wrap $T, + delete_vertex, + delete_vertex_loop, + delete_vertex_invalid_index, + delete_vertex_connected, + delete_edge, + delete_edge_loop, + delete_edge_multiple, + delete_edge_invalid_index, + vertices_after_delete, + incident_vertices_after_delete_edge, + edges_after_delete, + incident_edges_after_delete_edge, + incident_edges_after_delete_vertex, + incidences_after_delete_vertex, + incidences_after_delete_edge, + incidence_cursor_after_delete_vertex, + incidence_cursor_after_delete_edge, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_deletion_testing::$name::<$T>(); + } + )* + }; +} + +pub fn delete_vertex() { + let (mut graph, v) = G::single_vertex(); + assert_eq!( + graph.vertex_count(), + 1, + "unexpected vertex count before delete" + ); + graph.delete_vertex(v); + assert_eq!( + graph.vertex_count(), + 0, + "unexpected vertex count after delete" + ); +} + +pub fn delete_vertex_loop() { + let (mut graph, v, _) = G::loop_edge(); + assert_eq!( + graph.vertex_count(), + 1, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v, v), + "expected vertex to be self-adjacent before delete" + ); + assert_eq!(graph.degree(v), 2, "unexpected vertex degree before delete"); + graph.delete_vertex(v); + assert_eq!( + graph.vertex_count(), + 0, + "unexpected vertex count after delete" + ); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); +} + +pub fn delete_vertex_invalid_index() { + let (mut graph, v) = G::single_vertex(); + graph.delete_vertex(v); + let result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || graph.delete_vertex(v))); + assert!(result.is_err(), "second deletion should panic"); +} + +pub fn delete_vertex_connected() +where + G::Vertex: Debug, +{ + let (mut graph, vertices, _, _) = G::standard(); + assert_eq!( + graph.vertex_count(), + 10, + "unexpected vertex count before delete" + ); + assert_eq!( + graph.edge_count(), + 18, + "unexpected edge count before delete" + ); + graph.delete_vertex(vertices[2]); + assert_eq!( + graph.vertex_count(), + 9, + "unexpected vertex count after delete" + ); + assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); + let expected_edges = [ + (vertices[0], vertices[1]), + (vertices[1], vertices[3]), + (vertices[1], vertices[4]), + (vertices[3], vertices[6]), + (vertices[4], vertices[4]), + (vertices[4], vertices[7]), + (vertices[4], vertices[8]), + (vertices[5], vertices[9]), + (vertices[6], vertices[9]), + (vertices[7], vertices[8]), + (vertices[7], vertices[9]), + ]; + for (v, u) in expected_edges { + assert!( + graph.are_adjacent(v, u), + "expected {v:?} and {u:?} to be adjacent after delete" + ); + } + for v in [vertices[1], vertices[4], vertices[5], vertices[6]] { + assert!( + !graph.adjacent_vertices(v).any(|u| u == vertices[2]), + "unexpected adjacency of {v:?} to deleted vertex {:?}", + vertices[2] + ); + } +} + +pub fn delete_edge() { + let (mut graph, [v0, v1], e) = G::single_edge(); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v0, v1), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v0), + 1, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v1), + 1, + "unexpected vertex degree before delete" + ); + graph.delete_edge(e); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count after delete" + ); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + assert!( + !graph.are_adjacent(v0, v1), + "unexpected adjacency after delete" + ); + assert_eq!(graph.degree(v0), 0, "unexpected vertex degree after delete"); + assert_eq!(graph.degree(v1), 0, "unexpected vertex degree after delete"); +} + +pub fn delete_edge_loop() { + let (mut graph, v, e) = G::loop_edge(); + assert_eq!( + graph.vertex_count(), + 1, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v, v), + "expected vertex to be self-adjacent before delete" + ); + assert_eq!(graph.degree(v), 2, "unexpected vertex degree before delete"); + graph.delete_edge(e); + assert_eq!( + graph.vertex_count(), + 1, + "unexpected vertex count after delete" + ); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + assert!( + !graph.are_adjacent(v, v), + "unexpected adjacency after delete" + ); + assert_eq!(graph.degree(v), 0, "unexpected vertex degree after delete"); +} + +pub fn delete_edge_multiple() { + const K: usize = 2; + let (mut graph, [v0, v1], edges) = G::multiple_edges::(); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), K, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v0, v1), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v0), + K, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v1), + K, + "unexpected vertex degree before delete" + ); + graph.delete_edge(edges[0]); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count after delete" + ); + assert_eq!( + graph.edge_count(), + K - 1, + "unexpected edge count after delete" + ); + assert!( + graph.are_adjacent(v0, v1), + "expected vertices to be adjacent after delete" + ); + assert_eq!( + graph.degree(v0), + K - 1, + "unexpected vertex degree after delete" + ); + assert_eq!( + graph.degree(v1), + K - 1, + "unexpected vertex degree after delete" + ); +} + +pub fn delete_edge_invalid_index() { + let (mut graph, _, e) = G::single_edge(); + graph.delete_edge(e); + let result = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || graph.delete_edge(e))); + assert!(result.is_err(), "second deletion should panic"); +} + +pub fn vertices_after_delete() +where + G::Vertex: Debug, +{ + let (mut graph, vertices, _, _) = G::standard(); + let del = vertices[2]; + graph.delete_vertex(del); + assert_eq!( + graph.vertex_count(), + 9, + "unexpected vertex count after delete" + ); + assert_eq!( + graph.vertices().count(), + 9, + "unexpected vertex iterator count after delete" + ); + for v in graph.vertices() { + assert_ne!(v, del, "deleted vertex {del:?} appeared in iterator"); + } + for &v in vertices.iter().filter(|&&u| u != del) { + assert!( + graph.vertices().any(|u| u == v), + "expected vertex {v:?} missing from iterator after delete" + ); + } +} + +pub fn incident_vertices_after_delete_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, _, edges, _) = G::standard(); + let del = edges[2].0; + graph.delete_edge(del); + for &(f, v0, v1) in edges.iter().filter(|&&(f, _, _)| f != del) { + let (u0, u1) = graph.incident_vertices(f); + assert!( + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?} after delete" + ); + } +} + +pub fn edges_after_delete() +where + G::Edge: Debug, +{ + let (mut graph, vertices, edges, _) = G::standard(); + graph.delete_vertex(vertices[2]); + assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); + assert_eq!( + graph.edges().count(), + 12, + "unexpected edge iterator count after delete" + ); + for i in [2, 5, 6, 7, 8, 9] { + assert!( + !graph.edges().any(|e| e == edges[i].0), + "deleted edge {:?} appeared in iterator", + edges[i].0 + ); + } + for i in [0, 1, 3, 4, 10, 11, 12, 13, 14, 15, 16, 17] { + assert!( + graph.edges().any(|e| e == edges[i].0), + "expected edge {:?} missing from iterator after delete", + edges[i].0 + ); + } +} + +pub fn incident_edges_after_delete_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, edges, incidences) = G::standard(); + let del = edges[2].0; + graph.delete_edge(del); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + let mut expected = incs + .iter() + .filter_map(|x| (x.edge != del).then_some(x.edge)) + .collect(); + assert_incident_edges_after_delete(&graph, v, &mut expected); + } +} + +pub fn incident_edges_after_delete_vertex() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, _, incidences) = G::standard(); + let del = vertices[2]; + graph.delete_vertex(del); + for (&v, incs) in vertices.iter().zip(&incidences).filter(|&(&u, _)| u != del) { + let mut expected = incs + .iter() + .filter_map(|x| (x.vertex != del).then_some(x.edge)) + .collect(); + assert_incident_edges_after_delete(&graph, v, &mut expected); + } +} + +pub fn incidences_after_delete_vertex() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, _, incidences) = G::standard(); + let del = vertices[2]; + graph.delete_vertex(del); + for (&v, incs) in vertices.iter().zip(&incidences).filter(|&(&u, _)| u != del) { + let remaining = incs.iter().filter(|x| x.vertex != del).cloned().collect(); + assert_vertex_incidences(&graph, v, remaining); + } +} + +pub fn incidences_after_delete_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, edges, incidences) = G::standard(); + let del = edges[2].0; + graph.delete_edge(del); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + let remaining = incs.iter().filter(|x| x.edge != del).cloned().collect(); + assert_vertex_incidences(&graph, v, remaining); + } +} + +pub fn incidence_cursor_after_delete_vertex() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, _, incidences) = G::standard(); + let del = vertices[2]; + graph.delete_vertex(del); + for (&v, incs) in vertices.iter().zip(&incidences).filter(|&(&u, _)| u != del) { + let remaining = incs.iter().filter(|x| x.vertex != del).cloned().collect(); + assert_vertex_incidence_cursor(&graph, v, remaining); + } +} + +pub fn incidence_cursor_after_delete_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (mut graph, vertices, edges, incidences) = G::standard(); + let del = edges[2].0; + graph.delete_edge(del); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + let remaining = incs.iter().filter(|x| x.edge != del).cloned().collect(); + assert_vertex_incidence_cursor(&graph, v, remaining); + } +} + +fn assert_vertex_incidences( + graph: &G, + v: G::Vertex, + mut expected: Vec>, +) where + G::Vertex: Debug, + G::Edge: Debug, +{ + assert_eq!( + graph.incidences(v).count(), + expected.len(), + "unexpected incidence count for vertex {v:?} after delete" + ); + for incidence in graph.incidences(v) { + let pos = expected + .iter() + .position(|&x| x == incidence) + .unwrap_or_else(|| { + panic!( + "unexpected incidence {incidence:?} of vertex {v:?} from iterator after delete" + ) + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {expected:?} of vertex {v:?} not matched by iterator after delete" + ); +} + +fn assert_vertex_incidence_cursor( + graph: &G, + v: G::Vertex, + mut expected: Vec>, +) where + G::Vertex: Debug, + G::Edge: Debug, +{ + let mut cursor = graph.incidence_cursor(v); + while let Some(incidence) = cursor.next(graph) { + let pos = expected + .iter() + .position(|&x| x == incidence) + .unwrap_or_else(|| { + panic!( + "unexpected incidence {incidence:?} of vertex {v:?} from cursor after delete" + ) + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {expected:?} of vertex {v:?} not matched by cursor after delete" + ); +} + +fn assert_incident_edges_after_delete( + graph: &G, + v: G::Vertex, + expected: &mut Vec, +) where + G::Vertex: Debug, + G::Edge: Debug, +{ + assert_eq!( + graph.incident_edges(v).count(), + expected.len(), + "unexpected incident edge count for vertex {v:?} after delete" + ); + for e in graph.incident_edges(v) { + let pos = expected.iter().position(|&f| f == e).unwrap_or_else(|| { + panic!("unexpected incident edge {e:?} of vertex {v:?} after delete") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {expected:?} of vertex {v:?} not matched after delete" + ); +} diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index cd46e13..2c4c394 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -1,1411 +1,773 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! graph_topology_test_fixtures { - ($T:ty) => { - fn make_test_graph() -> ( - $T, - [<$T as $crate::traits::GraphTopology>::Vertex; 10], - [( - <$T as $crate::traits::GraphTopology>::Edge, - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Vertex, - ); 18], - [Vec<( - <$T as $crate::traits::GraphTopology>::Vertex, - <$T as $crate::traits::GraphTopology>::Edge, - )>; 10], - ) { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = - core::array::from_fn(|_| graph.add_vertex()); - let edges = [ - (vertices[0], vertices[1]), - (vertices[0], vertices[1]), - (vertices[1], vertices[2]), - (vertices[1], vertices[3]), - (vertices[1], vertices[4]), - (vertices[2], vertices[2]), - (vertices[2], vertices[4]), - (vertices[2], vertices[4]), - (vertices[2], vertices[5]), - (vertices[2], vertices[6]), - (vertices[3], vertices[6]), - (vertices[4], vertices[4]), - (vertices[4], vertices[7]), - (vertices[4], vertices[8]), - (vertices[5], vertices[9]), - (vertices[6], vertices[9]), - (vertices[7], vertices[8]), - (vertices[7], vertices[9]), - ] - .map(|(v1, v2)| (graph.add_edge(v1, v2), v1, v2)); - let incidences: [Vec<_>; 10] = [ - vec![(vertices[1], edges[0].0), (vertices[1], edges[1].0)], - vec![ - (vertices[0], edges[0].0), - (vertices[0], edges[1].0), - (vertices[2], edges[2].0), - (vertices[3], edges[3].0), - (vertices[4], edges[4].0), - ], - vec![ - (vertices[2], edges[5].0), - (vertices[2], edges[5].0), - (vertices[1], edges[2].0), - (vertices[4], edges[6].0), - (vertices[4], edges[7].0), - (vertices[5], edges[8].0), - (vertices[6], edges[9].0), - ], - vec![(vertices[1], edges[3].0), (vertices[6], edges[10].0)], - vec![ - (vertices[1], edges[4].0), - (vertices[2], edges[6].0), - (vertices[2], edges[7].0), - (vertices[4], edges[11].0), - (vertices[4], edges[11].0), - (vertices[7], edges[12].0), - (vertices[8], edges[13].0), - ], - vec![(vertices[2], edges[8].0), (vertices[9], edges[14].0)], - vec![ - (vertices[2], edges[9].0), - (vertices[3], edges[10].0), - (vertices[9], edges[15].0), - ], - vec![ - (vertices[4], edges[12].0), - (vertices[8], edges[16].0), - (vertices[9], edges[17].0), - ], - vec![(vertices[4], edges[13].0), (vertices[7], edges[16].0)], - vec![ - (vertices[5], edges[14].0), - (vertices[6], edges[15].0), - (vertices[7], edges[17].0), - ], - ]; - (graph, vertices, edges, incidences) - } +use std::fmt::Debug; - #[allow(dead_code)] - fn make_test_graph_disconnected() - -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 3] = - core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[1], vertices[2]); - (graph, vertices) - } - }; -} +use crate::testing::fixtures::MakeTestGraph; +use crate::traits::{Incidence, IncidenceCursor}; #[doc(hidden)] #[macro_export] macro_rules! graph_topology_tests { ($T:ty) => { - #[test] - fn add_vertex() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); - } + $crate::graph_topology_tests!(@wrap $T, + vertex_count_empty, + vertex_count, + vertex_map, + edge_count_empty, + edge_count, + edge_map, + degree_zero, + degree, + loop_edge, + multiple_edges, + are_adjacent_vertex_self, + are_adjacent_single_edge, + are_adjacent, + vertices_empty, + vertices, + adjacent_vertices_empty, + adjacent_vertices, + adjacent_vertices_loop_edge, + adjacent_vertices_multiple_edges, + incident_vertices_single_edge, + incident_vertices_loop_edge, + incident_vertices_multiple_edges, + incident_vertices, + edges_empty, + edges, + incident_edges_empty, + incident_edges, + incident_edges_loop_edge, + incident_edges_multiple_edges, + incidences_empty, + incidences, + incidences_edge_consistency, + incidences_loop_edge, + incidences_multiple_edges, + incidence_cursor_empty, + incidence_cursor, + incidence_cursor_loop_edge, + incidence_cursor_multiple_edges, + incidence_cursor_copy, + incident_vertices_incidences_consistency, + incident_edges_incidences_consistency, + ); + }; - #[test] - fn vertex_count_empty() { - use $crate::traits::GraphTopology; - let graph = <$T>::new(); - assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); - } - - #[test] - fn vertex_count() { - use $crate::traits::GraphTopology; - let (graph, _, _, _) = make_test_graph(); - assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); - } - - #[test] - fn add_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); - } - - #[test] - fn edge_count_empty() { - use $crate::traits::GraphTopology; - let graph = <$T>::new(); - assert_eq!(graph.edge_count(), 0, "unexpected edge count"); - } - - #[test] - fn edge_count() { - use $crate::traits::GraphTopology; - let (graph, _, _, _) = make_test_graph(); - assert_eq!(graph.edge_count(), 18, "unexpected edge count"); - } - - #[test] - fn degree_zero() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); - } - - #[test] - fn degree() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, _) = make_test_graph(); - let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; - for i in 0..graph.vertex_count() { - assert_eq!( - graph.degree(vertices[i]), - expected_degrees[i], - "unexpected degree of {:?}", - vertices[i] - ); + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + $crate::testing::graph_topology_testing::$name::<$T>(); } - } - - #[test] - fn loop_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert!( - graph.are_adjacent(v, v), - "vertex with loop edge should be adjacent to itself" - ); - } - - #[test] - fn multiple_edges() { - use $crate::traits::GraphTopology; - let k = 3; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - for _ in 0..k { - graph.add_edge(v1, v2); - } - assert_eq!(graph.vertex_count(), 2, "unexpected vertex count"); - assert_eq!(graph.edge_count(), k, "unexpected edge count"); - assert_eq!(graph.degree(v1), k, "unexpected degree of vertex {v1:?}"); - assert_eq!(graph.degree(v2), k, "unexpected degree of vertex {v2:?}"); - assert!(graph.are_adjacent(v1, v2), "should be adjacent"); - } - - #[test] - fn are_adjacent_vertex_self() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert!( - !graph.are_adjacent(v, v), - "should not be adjacent to itself" - ); - } - - #[test] - fn are_adjacent_single_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); - assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); - graph.add_edge(v1, v2); - assert!(graph.are_adjacent(v1, v2), "should be adjacent"); - assert!(graph.are_adjacent(v2, v1), "should be adjacent"); - } - - #[test] - fn are_adjacent() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, _) = make_test_graph(); - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[1] - ); - assert!( - graph.are_adjacent(vertices[9], vertices[5]), - "expected {:?} and {:?} to be adjacent", - vertices[9], - vertices[5] - ); - assert!( - !graph.are_adjacent(vertices[9], vertices[3]), - "unexpected adjacency of {:?} and {:?}", - vertices[9], - vertices[3] - ); - - for i in 0..graph.vertex_count() { - let exp = match i { - 1 | 2 | 4 | 5 | 6 => true, - _ => false, - }; - assert_eq!( - graph.are_adjacent(vertices[2], vertices[i]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[2], - vertices[i] - ); - assert_eq!( - graph.are_adjacent(vertices[i], vertices[2]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[i], - vertices[2] - ); - } - } - - #[test] - fn vertices_empty() { - use $crate::traits::GraphTopology; - let graph = <$T>::new(); - assert_eq!( - graph.vertices().count(), - 0, - "vertex iterator of empty graph should have no elements" - ); - } - - #[test] - fn vertices() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, _) = make_test_graph(); - assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); - - // Expects each vertex to appear exactly once. - for v in graph.vertices() { - assert_eq!( - vertices.iter().filter(|&x| *x == v).count(), - 1, - "unexpected vertex {v:?} from iterator" - ); - } - } - - #[test] - fn adjacent_vertices_empty() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - graph.adjacent_vertices(v).count(), - 0, - "adjacent vertex iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn adjacent_vertices() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, _) = make_test_graph(); - // Checks adjacency of vertex 4. - assert_eq!( - graph.adjacent_vertices(vertices[4]).count(), - 7, - "unexpected adjacency count" - ); - let mut expected_adjacency = vec![ - vertices[1], - vertices[2], - vertices[2], - vertices[4], - vertices[4], - vertices[7], - vertices[8], - ]; - for v in graph.adjacent_vertices(vertices[4]) { - let i = expected_adjacency - .iter() - .position(|w| *w == v) - .expect(&format!( - "unexpected adjacent vertex {v:?} of {:?} from iterator", - vertices[4] - )); - expected_adjacency.swap_remove(i); - } - assert_eq!( - expected_adjacency.len(), - 0, - "expected adjacent vertices {:?} of {:?} were not matched by iterator", - expected_adjacency, - vertices[4] - ); - } - - #[test] - fn adjacent_vertices_loop_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - let mut iter = graph.adjacent_vertices(v); - assert_eq!(iter.next(), Some(v), "vertex should be adjacent to itself"); - assert_eq!( - iter.next(), - Some(v), - "vertex should be adjacent to itself twice" - ); - assert_eq!( - iter.next(), - None, - "too many adjacent vertices from iterator" - ); - } - - #[test] - fn adjacent_vertices_multiple_edges() { - use $crate::traits::GraphTopology; - let k = 3; - let mut graph = <$T>::new(); - let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..k { - graph.add_edge(vertices[0], vertices[1]); - } - for i in 0..2 { - let mut iter = graph.adjacent_vertices(vertices[i]); - for j in 0..k { - assert_eq!( - iter.next(), - Some(vertices[1 - i]), - "unexpected adjacent vertex {j} of vertex {:?}", - vertices[i] - ); - } - assert_eq!( - iter.next(), - None, - "too many adjacent vertices of vertex {:?} from iterator", - vertices[i] - ); - } - } - - #[test] - fn incident_vertices_single_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - let (u1, u2) = graph.incident_vertices(e); - assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edge {e:?}" - ); - } - - #[test] - fn incident_vertices_loop_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); - assert_eq!( - graph.incident_vertices(e), - (v, v), - "unexpected incident vertices for loop edge {e:?}" - ); - } - - #[test] - fn incident_vertices_multiple_edges() { - use $crate::traits::GraphTopology; - 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); - assert_ne!(e1, e2, "edges should be distinct"); - let (u1, u2) = graph.incident_vertices(e1); - assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for first multi-edge {e1:?}" - ); - let (u1, u2) = graph.incident_vertices(e2); - assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for second multi-edge {e2:?}" - ); - } - - #[test] - fn incident_vertices() { - use $crate::traits::GraphTopology; - let (graph, _, edges, _) = make_test_graph(); - for &(e, v1, v2) in edges.iter() { - let (u1, u2) = graph.incident_vertices(e); - assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edge {e:?}" - ); - } - } - - #[test] - fn edges_empty() { - use $crate::traits::GraphTopology; - let graph = <$T>::new(); - assert_eq!( - graph.edges().count(), - 0, - "edge iterator of empty graph should have no elements" - ); - } - - #[test] - fn edges() { - use $crate::traits::GraphTopology; - let (graph, _, edges, _) = make_test_graph(); - assert_eq!(graph.edges().count(), 18, "unexpected edge count"); - - // Expects each edge to appear exactly once. - for e in graph.edges() { - assert_eq!( - edges.iter().filter(|&&(f, _, _)| f == e).count(), - 1, - "unexpected edge {e:?} from iterator" - ); - } - } - - #[test] - fn incident_edges_empty() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - graph.incident_edges(v).count(), - 0, - "incident edge iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn incident_edges() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, incidences) = make_test_graph(); - for i in 0..10 { - assert_eq!( - graph.incident_edges(vertices[i]).count(), - incidences[i].len(), - "unexpected incident edge count for vertex {:?}", - vertices[i] - ); - let mut expected: Vec<_> = incidences[i].iter().map(|(_, e)| *e).collect(); - for e in graph.incident_edges(vertices[i]) { - let pos = expected - .iter() - .position(|f| *f == e) - .expect(&format!( - "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 iterator", - expected, - vertices[i] - ); - } - } - - #[test] - fn incident_edges_loop_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); - let mut iter = graph.incident_edges(v); - assert_eq!(iter.next(), Some(e), "loop edge should appear in incident edges"); - assert_eq!( - iter.next(), - Some(e), - "loop edge should appear twice in incident edges" - ); - assert_eq!(iter.next(), None, "too many incident edges from iterator"); - } - - #[test] - fn incident_edges_multiple_edges() { - use $crate::traits::GraphTopology; - 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 expected = edges.clone(); - for e in graph.incident_edges(vertices[i]) { - let pos = expected - .iter() - .position(|f| *f == e) - .expect(&format!( - "unexpected incident edge {e:?} of vertex {:?}", - vertices[i] - )); - expected.swap_remove(pos); - } - assert!( - expected.is_empty(), - "expected incident edges {:?} of vertex {:?} were not matched by iterator", - expected, - vertices[i] - ); - } - } - - #[test] - fn incidences_empty() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - assert_eq!( - graph.incidences(v).count(), - 0, - "incidence iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn incidences() { - use $crate::traits::GraphTopology; - let (graph, vertices, _, incidences) = make_test_graph(); - for i in 0..10 { - assert_eq!( - graph.incidences(vertices[i]).count(), - incidences[i].len(), - "unexpected incidence count for vertex {:?}", - vertices[i] - ); - let mut expected = incidences[i].clone(); - for incidence in graph.incidences(vertices[i]) { - let pos = expected - .iter() - .position(|(v, e)| *v == incidence.0 && *e == incidence.1) - .expect(&format!( - "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 iterator", - expected, - vertices[i] - ); - } - } - - #[test] - fn incidences_edge_consistency() { - use $crate::traits::GraphTopology; - // For each incidence (v, e) of u, the same edge e must appear in the incidences of v. - // For loop edges (u == v), the edge must appear exactly twice in the incidences of u. - let (graph, _, _, _) = make_test_graph(); - for u in graph.vertices() { - for (v, e) in graph.incidences(u) { - if u == v { - assert_eq!( - graph.incidences(u).filter(|(_, f)| *f == e).count(), - 2, - "loop edge {e:?} should appear exactly twice in incidences of vertex {u:?}" - ); - } else { - assert!( - graph.incidences(v).any(|(w, f)| w == u && f == e), - "edge {e:?} from incidences of vertex {u:?} missing in incidences of adjacent vertex {v:?}" - ); - } - } - } - } - - #[test] - fn incidences_loop_edge() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); - let mut iter = graph.incidences(v); - assert_eq!( - iter.next(), - Some((v, e)), - "vertex should be adjacent to itself" - ); - assert_eq!( - iter.next(), - Some((v, e)), - "vertex should be adjacent to itself twice" - ); - assert_eq!( - iter.next(), - None, - "too many adjacent vertices from iterator" - ); - } - - #[test] - fn incidences_multiple_edges() { - use $crate::traits::GraphTopology; - 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 iter = graph.incidences(vertices[i]); - for j in 0..k { - let current = iter.next().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!( - iter.next(), - None, - "too many adjacent vertices of {:?} from iterator", - vertices[i] - ); - } - } - - #[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; - let (graph, _, _, _) = make_test_graph(); - for u in graph.vertices() { - for (v, e) in graph.incidences(u) { - let (w1, w2) = graph.incident_vertices(e); - assert!( - (w1 == u && w2 == v) || (w1 == v && w2 == u), - "incident vertices {w1:?} and {w2:?} of edge {e:?} are inconsistent with incidence ({v:?}, {e:?}) of vertex {u:?}" - ); - } - } - } - - #[test] - fn incident_edges_incidences_consistency() { - use $crate::traits::GraphTopology; - let (graph, _, _, _) = make_test_graph(); - for u in graph.vertices() { - let mut expected: Vec<_> = graph.incidences(u).map(|(_, e)| e).collect(); - for e in graph.incident_edges(u) { - let pos = expected - .iter() - .position(|f| *f == e) - .expect(&format!( - "incident edge {e:?} of vertex {u:?} missing from incidences" - )); - expected.swap_remove(pos); - } - assert!( - expected.is_empty(), - "incidence edges {:?} of vertex {u:?} not matched by incident_edges", - expected - ); - } - } + )* }; } -#[doc(hidden)] -#[macro_export] -macro_rules! graph_topology_deletion_tests { - ($T:ty) => { - #[test] - fn delete_vertex() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v = graph.add_vertex(); - assert_eq!( - graph.vertex_count(), - 1, - "unexpected vertex count before delete" - ); - graph.delete_vertex(v); - assert_eq!( - graph.vertex_count(), - 0, - "unexpected vertex count after delete" - ); - assert_ne!( - graph.add_vertex(), - v, - "unexpected duplicate vertex after delete" - ); - } - - #[test] - fn delete_vertex_loop() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - assert_eq!( - graph.vertex_count(), - 1, - "unexpected vertex count before delete" - ); - assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); - assert!( - graph.are_adjacent(v, v), - "expected vertex to be self-adjacent before delete" - ); - assert_eq!(graph.degree(v), 2, "unexpected vertex degree before delete"); - graph.delete_vertex(v); - assert_eq!( - graph.vertex_count(), - 0, - "unexpected vertex count after delete" - ); - assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); - assert_ne!( - graph.add_vertex(), - v, - "unexpected duplicate vertex after delete" - ); - } - - #[test] - fn delete_vertex_invalid_index() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v = graph.add_vertex(); - graph.delete_vertex(v); - let result = std::panic::catch_unwind(move || graph.delete_vertex(v)); - assert!(result.is_err(), "second deletion should panic"); - } - - #[test] - fn delete_vertex_connected() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let (mut graph, vertices, _, _) = make_test_graph(); - assert_eq!( - graph.vertex_count(), - 10, - "unexpected vertex count before delete" - ); - assert_eq!( - graph.edge_count(), - 18, - "unexpected edge count before delete" - ); - graph.delete_vertex(vertices[2]); - assert_eq!( - graph.vertex_count(), - 9, - "unexpected vertex count after delete" - ); - assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); - let expected_edges = [ - (vertices[0], vertices[1]), - (vertices[1], vertices[3]), - (vertices[1], vertices[4]), - (vertices[3], vertices[6]), - (vertices[4], vertices[4]), - (vertices[4], vertices[7]), - (vertices[4], vertices[8]), - (vertices[5], vertices[9]), - (vertices[6], vertices[9]), - (vertices[7], vertices[8]), - (vertices[7], vertices[9]), - ]; - for (v, u) in expected_edges { - assert!( - graph.are_adjacent(v, u), - "expected {v:?} and {u:?} to be adjacent after delete" - ); - } - for v in [vertices[1], vertices[4], vertices[5], vertices[6]] { - assert!( - !graph.adjacent_vertices(v).any(|u| u == vertices[2]), - "unexpected adjacency of {v:?} to deleted vertex {:?}", - vertices[2] - ); - } - } - - #[test] - fn delete_edge() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - assert_eq!( - graph.vertex_count(), - 2, - "unexpected vertex count before delete" - ); - assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); - assert!( - graph.are_adjacent(v1, v2), - "expected vertices to be adjacent before delete" - ); - assert_eq!( - graph.degree(v1), - 1, - "unexpected vertex degree before delete" - ); - assert_eq!( - graph.degree(v2), - 1, - "unexpected vertex degree before delete" - ); - graph.delete_edge(e); - assert_eq!( - graph.vertex_count(), - 2, - "unexpected vertex count after delete" - ); - assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); - assert!( - !graph.are_adjacent(v1, v2), - "unexpected adjacency after delete" - ); - assert_eq!(graph.degree(v1), 0, "unexpected vertex degree after delete"); - assert_eq!(graph.degree(v2), 0, "unexpected vertex degree after delete"); - assert_ne!( - graph.add_edge(v1, v2), - e, - "unexpected duplicate edge after delete" - ); - } - - #[test] - fn delete_edge_loop() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); - assert_eq!( - graph.vertex_count(), - 1, - "unexpected vertex count before delete" - ); - assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); - assert!( - graph.are_adjacent(v, v), - "expected vertex to be self-adjacent before delete" - ); - assert_eq!(graph.degree(v), 2, "unexpected vertex degree before delete"); - graph.delete_edge(e); - assert_eq!( - graph.vertex_count(), - 1, - "unexpected vertex count after delete" - ); - assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); - assert!( - !graph.are_adjacent(v, v), - "unexpected adjacency after delete" - ); - assert_eq!(graph.degree(v), 0, "unexpected vertex degree after delete"); - assert_ne!( - graph.add_edge(v, v), - e, - "unexpected duplicate edge after delete" - ); - } - - #[test] - fn delete_edge_multiple() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - graph.add_edge(v1, v2); - assert_eq!( - graph.vertex_count(), - 2, - "unexpected vertex count before delete" - ); - assert_eq!(graph.edge_count(), 2, "unexpected edge count before delete"); - assert!( - graph.are_adjacent(v1, v2), - "expected vertices to be adjacent before delete" - ); - assert_eq!( - graph.degree(v1), - 2, - "unexpected vertex degree before delete" - ); - assert_eq!( - graph.degree(v2), - 2, - "unexpected vertex degree before delete" - ); - graph.delete_edge(e); - assert_eq!( - graph.vertex_count(), - 2, - "unexpected vertex count after delete" - ); - assert_eq!(graph.edge_count(), 1, "unexpected edge count after delete"); - assert!( - graph.are_adjacent(v1, v2), - "expected vertices to be adjacent after delete" - ); - assert_eq!(graph.degree(v1), 1, "unexpected vertex degree after delete"); - assert_eq!(graph.degree(v2), 1, "unexpected vertex degree after delete"); - assert_ne!( - graph.add_edge(v1, v2), - e, - "unexpected duplicate edge after delete" - ); - } - - #[test] - fn delete_edge_invalid_index() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - graph.delete_edge(e); - let result = std::panic::catch_unwind(move || graph.delete_edge(e)); - assert!(result.is_err(), "second deletion should panic"); - } - - #[test] - fn vertices_after_delete() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let (mut graph, vertices, _, _) = make_test_graph(); - graph.delete_vertex(vertices[2]); - assert_eq!( - graph.vertex_count(), - 9, - "unexpected vertex count after delete" - ); - assert_eq!( - graph.vertices().count(), - 9, - "unexpected vertex iterator count after delete" - ); - for v in graph.vertices() { - assert_ne!( - v, vertices[2], - "deleted vertex {:?} appeared in iterator", - vertices[2] - ); - } - for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { - assert!( - graph.vertices().any(|v| v == vertices[i]), - "expected vertex {:?} missing from iterator after delete", - vertices[i] - ); - } - } - - #[test] - fn incident_vertices_after_delete_edge() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let (mut graph, _, edges, _) = make_test_graph(); - let e = edges[2].0; - graph.delete_edge(e); - for &(f, v1, v2) in edges.iter().filter(|&&(f, _, _)| f != e) { - let (u1, u2) = graph.incident_vertices(f); - assert!( - (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?} after delete" - ); - } - } - - #[test] - fn edges_after_delete() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let (mut graph, vertices, edges, _) = make_test_graph(); - graph.delete_vertex(vertices[2]); - assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); - assert_eq!( - graph.edges().count(), - 12, - "unexpected edge iterator count after delete" - ); - for i in [2, 5, 6, 7, 8, 9] { - assert!( - !graph.edges().any(|e| e == edges[i].0), - "deleted edge {:?} appeared in iterator", - edges[i].0 - ); - } - for i in [0, 1, 3, 4, 10, 11, 12, 13, 14, 15, 16, 17] { - assert!( - graph.edges().any(|e| e == edges[i].0), - "expected edge {:?} missing from iterator after delete", - edges[i].0 - ); - } - } - - #[test] - fn incident_edges_after_delete_edge() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let (mut graph, vertices, edges, incidences) = make_test_graph(); - graph.delete_edge(edges[2].0); - for i in 0..10 { - let mut expected: Vec<_> = incidences[i] - .iter() - .filter_map(|(_, e)| (*e != edges[2].0).then_some(*e)) - .collect(); - assert_eq!( - graph.incident_edges(vertices[i]).count(), - expected.len(), - "unexpected incident edge count for vertex {:?} after delete", - vertices[i] - ); - for e in graph.incident_edges(vertices[i]) { - let pos = expected.iter().position(|f| *f == e).expect(&format!( - "unexpected incident edge {e:?} of vertex {:?} after delete", - vertices[i] - )); - expected.swap_remove(pos); - } - assert!( - expected.is_empty(), - "expected incident edges {:?} of vertex {:?} not matched after delete", - expected, - vertices[i] - ); - } - } - - #[test] - fn incident_edges_after_delete_vertex() { - use $crate::traits::GraphTopology; - 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 mut expected: Vec<_> = incidences[i] - .iter() - .filter_map(|(v, e)| (*v != vertices[2]).then_some(*e)) - .collect(); - assert_eq!( - graph.incident_edges(vertices[i]).count(), - expected.len(), - "unexpected incident edge count for vertex {:?} after delete", - vertices[i] - ); - for e in graph.incident_edges(vertices[i]) { - let pos = expected.iter().position(|f| *f == e).expect(&format!( - "unexpected incident edge {e:?} of vertex {:?} after delete", - vertices[i] - )); - expected.swap_remove(pos); - } - assert!( - expected.is_empty(), - "expected incident edges {:?} of vertex {:?} not matched after delete", - expected, - vertices[i] - ); - } - } - - #[test] - fn incidences_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_incidences(&graph, vertices[i], remaining); - } - } - - #[test] - fn incidences_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_incidences(&graph, vertices[i], remaining); - } - } - - fn assert_vertex_incidences( - 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::GraphTopology; - assert_eq!( - graph.incidences(v).count(), - expected.len(), - "unexpected incidence count for vertex {:?} after delete", - v - ); - for incidence in graph.incidences(v) { - let pos = expected - .iter() - .position(|(u, e)| *u == incidence.0 && *e == incidence.1) - .expect(&format!( - "unexpected incidence {incidence:?} of vertex {:?} from iterator after delete", - v - )); - expected.swap_remove(pos); - } - assert!( - expected.is_empty(), - "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 - ); - } - }; +pub fn vertex_count_empty() { + let graph = G::empty(); + assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); +} + +pub fn vertex_count() { + let (graph, _, _, _) = G::standard(); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); +} + +pub fn vertex_map() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let mut map = graph.vertex_map(88); + for (i, &v) in vertices.iter().enumerate() { + assert_eq!( + map[v], 88, + "unexpected value from default map read for vertex {v:?}" + ); + map[v] = i; + } + for (i, &v) in vertices.iter().enumerate() { + assert_eq!(map[v], i, "unexpected value after write for vertex {v:?}"); + } +} + +pub fn edge_count_empty() { + let graph = G::empty(); + assert_eq!(graph.edge_count(), 0, "unexpected edge count"); +} + +pub fn edge_count() { + let (graph, _, _, _) = G::standard(); + assert_eq!(graph.edge_count(), 18, "unexpected edge count"); +} + +pub fn edge_map() +where + G::Edge: Debug, +{ + let (graph, _, edges, _) = G::standard(); + let mut map = graph.edge_map(99); + for (i, &(e, _, _)) in edges.iter().enumerate() { + assert_eq!( + map[e], 99, + "unexpected value from default map read for edge {e:?}" + ); + map[e] = i; + } + for (i, &(e, _, _)) in edges.iter().enumerate() { + assert_eq!(map[e], i, "unexpected value after write for edge {e:?}"); + } +} + +pub fn degree_zero() { + let (graph, v) = G::single_vertex(); + assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); +} + +pub fn degree() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let expected = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; + assert_eq!(vertices.len(), expected.len()); + for (&v, &exp) in vertices.iter().zip(&expected) { + assert_eq!(graph.degree(v), exp, "unexpected degree of {v:?}"); + } +} + +pub fn loop_edge() { + let (graph, v, _) = G::loop_edge(); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert!( + graph.are_adjacent(v, v), + "vertex with loop edge should be adjacent to itself" + ); +} + +pub fn multiple_edges() +where + G::Vertex: Debug, +{ + const K: usize = 3; + let (graph, [v0, v1], _) = G::multiple_edges::(); + assert_eq!(graph.vertex_count(), 2, "unexpected vertex count"); + assert_eq!(graph.edge_count(), K, "unexpected edge count"); + assert_eq!(graph.degree(v0), K, "unexpected degree of vertex {v0:?}"); + assert_eq!(graph.degree(v1), K, "unexpected degree of vertex {v1:?}",); + assert!(graph.are_adjacent(v0, v1), "should be adjacent"); +} + +pub fn are_adjacent_vertex_self() { + let (graph, v) = G::single_vertex(); + assert!( + !graph.are_adjacent(v, v), + "should not be adjacent to itself" + ); +} + +pub fn are_adjacent_single_edge() { + let (graph, [v0, v1], _) = G::single_edge(); + assert!(graph.are_adjacent(v0, v1), "should be adjacent"); + assert!(graph.are_adjacent(v1, v0), "should be adjacent"); +} + +pub fn are_adjacent() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[1] + ); + assert!( + graph.are_adjacent(vertices[9], vertices[5]), + "expected {:?} and {:?} to be adjacent", + vertices[9], + vertices[5] + ); + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); + for (i, &v) in vertices.iter().enumerate() { + let exp = matches!(i, 1 | 2 | 4 | 5 | 6); + assert_eq!( + graph.are_adjacent(vertices[2], v), + exp, + "unexpected adjacency of {:?} and {v:?}", + vertices[2] + ); + assert_eq!( + graph.are_adjacent(v, vertices[2]), + exp, + "unexpected adjacency of {v:?} and {:?}", + vertices[2] + ); + } +} + +pub fn vertices_empty() { + let graph = G::empty(); + assert_eq!( + graph.vertices().count(), + 0, + "vertex iterator of empty graph should have no elements" + ); +} + +pub fn vertices() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); + for v in graph.vertices() { + assert_eq!( + vertices.iter().filter(|&x| *x == v).count(), + 1, + "unexpected vertex {v:?} from iterator" + ); + } +} + +pub fn adjacent_vertices_empty() { + let (graph, v) = G::single_vertex(); + assert_eq!( + graph.adjacent_vertices(v).count(), + 0, + "adjacent vertex iterator of vertex with degree 0 should have no elements" + ); +} + +pub fn adjacent_vertices() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + graph.adjacent_vertices(vertices[4]).count(), + 7, + "unexpected adjacency count" + ); + let mut expected_adjacency = vec![ + vertices[1], + vertices[2], + vertices[2], + vertices[4], + vertices[4], + vertices[7], + vertices[8], + ]; + for v in graph.adjacent_vertices(vertices[4]) { + let i = expected_adjacency + .iter() + .position(|w| *w == v) + .unwrap_or_else(|| { + panic!( + "unexpected adjacent vertex {v:?} of {:?} from iterator", + vertices[4] + ) + }); + expected_adjacency.swap_remove(i); + } + assert_eq!( + expected_adjacency.len(), + 0, + "expected adjacent vertices {expected_adjacency:?} of {:?} were not matched by iterator", + vertices[4] + ); +} + +pub fn adjacent_vertices_loop_edge() +where + G::Vertex: Debug, +{ + let (graph, v, _) = G::loop_edge(); + let mut iter = graph.adjacent_vertices(v); + assert_eq!(iter.next(), Some(v), "vertex should be adjacent to itself"); + assert_eq!( + iter.next(), + Some(v), + "vertex should be adjacent to itself twice" + ); + assert_eq!( + iter.next(), + None, + "too many adjacent vertices from iterator" + ); +} + +pub fn adjacent_vertices_multiple_edges() +where + G::Vertex: Debug, +{ + const K: usize = 3; + let (graph, vertices, _) = G::multiple_edges::(); + for (i, &v) in vertices.iter().enumerate() { + let mut iter = graph.adjacent_vertices(v); + for j in 0..K { + assert_eq!( + iter.next(), + Some(vertices[1 - i]), + "unexpected adjacent vertex {j} of vertex {v:?}" + ); + } + assert_eq!( + iter.next(), + None, + "too many adjacent vertices of vertex {v:?} from iterator" + ); + } +} + +pub fn incident_vertices_single_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, [v0, v1], e) = G::single_edge(); + let (u0, u1) = graph.incident_vertices(e); + assert!( + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {e:?}" + ); +} + +pub fn incident_vertices_loop_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, v, e) = G::loop_edge(); + assert_eq!( + graph.incident_vertices(e), + (v, v), + "unexpected incident vertices for loop edge {e:?}" + ); +} + +pub fn incident_vertices_multiple_edges() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, [v0, v1], edges) = G::multiple_edges::<2>(); + assert_ne!(edges[0], edges[1], "edges should be distinct"); + for (i, &e) in edges.iter().enumerate() { + let (u0, u1) = graph.incident_vertices(e); + assert!( + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for multi-edge {e:?} (index {i})" + ); + } +} + +pub fn incident_vertices() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, _, edges, _) = G::standard(); + for &(e, v0, v1) in edges.iter() { + let (u0, u1) = graph.incident_vertices(e); + assert!( + (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0), + "unexpected incident vertices {u0:?} and {u1:?} for edge {e:?}" + ); + } +} + +pub fn edges_empty() { + let graph = G::empty(); + assert_eq!( + graph.edges().count(), + 0, + "edge iterator of empty graph should have no elements" + ); +} + +pub fn edges() +where + G::Edge: Debug, +{ + let (graph, _, edges, _) = G::standard(); + assert_eq!(graph.edges().count(), 18, "unexpected edge count"); + for e in graph.edges() { + assert_eq!( + edges.iter().filter(|&&(f, _, _)| f == e).count(), + 1, + "unexpected edge {e:?} from iterator" + ); + } +} + +pub fn incident_edges_empty() { + let (graph, v) = G::single_vertex(); + assert_eq!( + graph.incident_edges(v).count(), + 0, + "incident edge iterator of vertex with degree 0 should have no elements" + ); +} + +pub fn incident_edges() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, incidences) = G::standard(); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + assert_eq!( + graph.incident_edges(v).count(), + incs.len(), + "unexpected incident edge count for vertex {v:?}" + ); + let mut expected: Vec<_> = incs.iter().map(|x| x.edge).collect(); + for e in graph.incident_edges(v) { + let pos = expected.iter().position(|&f| f == e).unwrap_or_else(|| { + panic!("unexpected incident edge {e:?} of vertex {v:?} from iterator") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {expected:?} of vertex {v:?} were not matched by iterator" + ); + } +} + +pub fn incident_edges_loop_edge() +where + G::Edge: Debug, +{ + let (graph, v, e) = G::loop_edge(); + let mut iter = graph.incident_edges(v); + assert_eq!( + iter.next(), + Some(e), + "loop edge should appear in incident edges" + ); + assert_eq!( + iter.next(), + Some(e), + "loop edge should appear twice in incident edges" + ); + assert_eq!(iter.next(), None, "too many incident edges from iterator"); +} + +pub fn incident_edges_multiple_edges() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + const K: usize = 3; + let (graph, vertices, edges) = G::multiple_edges::(); + for &v in &vertices { + let mut expected = edges.to_vec(); + for e in graph.incident_edges(v) { + let pos = expected + .iter() + .position(|&f| f == e) + .unwrap_or_else(|| panic!("unexpected incident edge {e:?} of vertex {v:?}")); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {expected:?} of vertex {v:?} were not matched by iterator" + ); + } +} + +pub fn incidences_empty() { + let (graph, v) = G::single_vertex(); + assert_eq!( + graph.incidences(v).count(), + 0, + "incidence iterator of vertex with degree 0 should have no elements" + ); +} + +pub fn incidences() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, incidences) = G::standard(); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + assert_eq!( + graph.incidences(v).count(), + incs.len(), + "unexpected incidence count for vertex {v:?}" + ); + let mut expected = incs.clone(); + for incidence in graph.incidences(v) { + let pos = expected + .iter() + .position(|&x| x == incidence) + .unwrap_or_else(|| { + panic!("unexpected incidence {incidence:?} of vertex {v:?} from iterator") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {expected:?} of vertex {v:?} were not matched by iterator" + ); + } +} + +pub fn incidences_edge_consistency() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, _, _, _) = G::standard(); + for u in graph.vertices() { + for incidence in graph.incidences(u) { + if u == incidence.vertex { + assert_eq!( + graph + .incidences(u) + .filter(|x| x.edge == incidence.edge) + .count(), + 2, + "loop edge {:?} should appear exactly twice in incidences of vertex {u:?}", + incidence.edge + ); + } else { + assert!( + graph + .incidences(incidence.vertex) + .any(|x| x.vertex == u && x.edge == incidence.edge), + "edge {:?} from incidences of vertex {u:?} missing in incidences of \ + adjacent vertex {:?}", + incidence.edge, + incidence.vertex + ); + } + } + } +} + +pub fn incidences_loop_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, v, e) = G::loop_edge(); + let mut iter = graph.incidences(v); + assert_eq!( + iter.next(), + Some(Incidence { vertex: v, edge: e }), + "vertex should be adjacent to itself" + ); + assert_eq!( + iter.next(), + Some(Incidence { vertex: v, edge: e }), + "vertex should be adjacent to itself twice" + ); + assert_eq!( + iter.next(), + None, + "too many adjacent vertices from iterator" + ); +} + +pub fn incidences_multiple_edges() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + const K: usize = 3; + let (graph, vertices, edges) = G::multiple_edges::(); + for (i, &v) in vertices.iter().enumerate() { + let mut iter = graph.incidences(v); + for j in 0..K { + let current = iter.next().unwrap_or_else(|| { + panic!("incidence {j} missing, expected {K} incidences for vertex {v:?}") + }); + assert_eq!( + current.vertex, + vertices[1 - i], + "unexpected adjacent vertex of vertex {v:?} in incidence {j}" + ); + assert_eq!( + edges.iter().filter(|&&e| e == current.edge).count(), + 1, + "unexpected incident edge {:?} of vertex {v:?}", + current.edge + ); + } + assert_eq!( + iter.next(), + None, + "too many adjacent vertices of {v:?} from iterator" + ); + } +} + +pub fn incidence_cursor_empty() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, v) = G::single_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" + ); +} + +pub fn incidence_cursor() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, incidences) = G::standard(); + assert_eq!(vertices.len(), incidences.len()); + for (&v, incs) in vertices.iter().zip(&incidences) { + let mut expected = incs.clone(); + let mut cursor = graph.incidence_cursor(v); + while let Some(incidence) = cursor.next(&graph) { + let pos = expected + .iter() + .position(|&x| x == incidence) + .unwrap_or_else(|| { + panic!("unexpected incidence {incidence:?} of vertex {v:?} from cursor") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {expected:?} of vertex {v:?} were not matched by cursor" + ); + } +} + +pub fn incidence_cursor_loop_edge() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, v, e) = G::loop_edge(); + let mut cursor = graph.incidence_cursor(v); + assert_eq!( + cursor.next(&graph), + Some(Incidence { vertex: v, edge: e }), + "vertex should be adjacent to itself" + ); + assert_eq!( + cursor.next(&graph), + Some(Incidence { vertex: v, edge: e }), + "vertex should be adjacent to itself twice" + ); + assert_eq!(cursor.next(&graph), None, "too many incidences from cursor"); +} + +pub fn incidence_cursor_multiple_edges() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + const K: usize = 3; + let (graph, vertices, edges) = G::multiple_edges::(); + for (i, &v) in vertices.iter().enumerate() { + let mut cursor = graph.incidence_cursor(v); + for j in 0..K { + let current = cursor.next(&graph).unwrap_or_else(|| { + panic!("incidence {j} missing, expected {K} incidences for vertex {v:?}") + }); + assert_eq!( + current.vertex, + vertices[1 - i], + "unexpected adjacent vertex of vertex {v:?} in incidence {j}" + ); + assert_eq!( + edges.iter().filter(|&&e| e == current.edge).count(), + 1, + "unexpected incident edge {:?} of vertex {v:?}", + current.edge + ); + } + assert_eq!( + cursor.next(&graph), + None, + "too many incidences of {v:?} from cursor" + ); + } +} + +pub fn incidence_cursor_copy() { + let (graph, [_, v, _], _) = G::two_edge_path(); + let mut c1 = graph.incidence_cursor(v); + assert!(c1.next(&graph).is_some(), "expected first incidence"); + let mut c2 = c1; + assert!( + c1.next(&graph).is_some(), + "expected second incidence from original cursor" + ); + assert!( + c1.next(&graph).is_none(), + "expected original cursor to be exhausted" + ); + assert!( + c2.next(&graph).is_some(), + "expected second incidence from copied cursor" + ); + assert!( + c2.next(&graph).is_none(), + "expected copied cursor to be exhausted" + ); +} + +pub fn incident_vertices_incidences_consistency() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, _, _, _) = G::standard(); + for u in graph.vertices() { + for incidence in graph.incidences(u) { + let (w1, w2) = graph.incident_vertices(incidence.edge); + assert!( + (w1 == u && w2 == incidence.vertex) || (w1 == incidence.vertex && w2 == u), + "edge endpoints {w1:?} and {w2:?} are inconsistent with incidence \ + ({:?}, {:?}) of vertex {u:?}", + incidence.vertex, + incidence.edge + ); + } + } +} + +pub fn incident_edges_incidences_consistency() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, _, _, _) = G::standard(); + for u in graph.vertices() { + let mut expected: Vec<_> = graph.incidences(u).map(|x| x.edge).collect(); + for e in graph.incident_edges(u) { + let pos = expected.iter().position(|&f| f == e).unwrap_or_else(|| { + panic!("incident edge {e:?} of vertex {u:?} missing from incidences") + }); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "incidence edges {expected:?} of vertex {u:?} not matched by incident_edges" + ); + } } diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs deleted file mode 100644 index 61ce663..0000000 --- a/src/testing/maps_testing.rs +++ /dev/null @@ -1,251 +0,0 @@ -#[doc(hidden)] -#[macro_export] -macro_rules! vertex_map_tests { - ($T:ty) => { - #[test] - fn initial_values_are_default() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let map = graph.vertex_map(42); - assert_eq!(map[v1], 42); - assert_eq!(map[v2], 42); - } - - #[test] - fn write_and_read() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 7; - assert_eq!(map[v1], 7); - assert_eq!(map[v2], 0); - } - - #[test] - fn lazy_growth_on_read() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - graph.add_vertex(); - let map = graph.vertex_map(99); - let v = graph.add_vertex(); - assert_eq!(map[v], 99); - } - - #[test] - fn lazy_growth_on_write() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let v2 = graph.add_vertex(); - map[v2] = 7; - assert_eq!(map[v1], 0); - assert_eq!(map[v2], 7); - } - - #[test] - fn expand_to_new_vertices() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - graph.add_vertex(); - let mut map = graph.vertex_map(42); - let capacity_before = map.capacity(); - while graph.vertex_capacity() <= capacity_before { - graph.add_vertex(); - } - assert!( - map.capacity() < graph.vertex_capacity(), - "precondition: map is stale before expand" - ); - map.expand(graph.vertex_capacity()); - assert_eq!(map.capacity(), graph.vertex_capacity()); - } - - #[test] - 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.expand(graph.vertex_capacity()); - assert_eq!(map[v], 5); - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! vertex_map_deletion_tests { - ($T:ty) => { - #[test] - fn surviving_vertex_readable_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] = 1; - map[v2] = 2; - graph.delete_vertex(v2); - assert_eq!(map[v1], 1); - } - - #[test] - fn reused_slot_returns_old_value() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = <$T>::new(); - graph.add_vertex(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 99; - graph.delete_vertex(v1); - let v2 = graph.add_vertex(); - // VertexMap uses raw indices, not vertex identity. A new vertex v2 reusing the slot - // of previously deleted v1 sees the old value. Callers must reinitialize stale slots - // after deletion. - assert_eq!(map[v2], 99); - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! edge_map_tests { - ($T:ty) => { - #[test] - fn initial_values_are_default() { - use $crate::traits::GraphTopology; - 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 map = graph.edge_map(42); - assert_eq!(map[e1], 42); - assert_eq!(map[e2], 42); - } - - #[test] - fn write_and_read() { - use $crate::traits::GraphTopology; - 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] = 7; - assert_eq!(map[e1], 7); - assert_eq!(map[e2], 0); - } - - #[test] - fn lazy_growth_on_read() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - graph.add_edge(v1, v2); - let map = graph.edge_map(99); - let e = graph.add_edge(v1, v2); - assert_eq!(map[e], 99); - } - - #[test] - fn lazy_growth_on_write() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e1 = graph.add_edge(v1, v2); - let mut map = graph.edge_map(0); - let e2 = graph.add_edge(v1, v2); - map[e2] = 7; - assert_eq!(map[e1], 0); - assert_eq!(map[e2], 7); - } - - #[test] - fn expand_to_new_edges() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - graph.add_edge(v1, v2); - let mut map = graph.edge_map(42); - let capacity_before = map.capacity(); - while graph.edge_capacity() <= capacity_before { - graph.add_edge(v1, v2); - } - assert!( - map.capacity() < graph.edge_capacity(), - "precondition: map is stale before expand" - ); - map.expand(graph.edge_capacity()); - assert_eq!(map.capacity(), graph.edge_capacity()); - } - - #[test] - fn expand_does_not_overwrite_existing_values() { - use $crate::traits::GraphTopology; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - let mut map = graph.edge_map(0); - map[e] = 5; - graph.add_edge(v1, v2); - map.expand(graph.edge_capacity()); - assert_eq!(map[e], 5); - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! edge_map_deletion_tests { - ($T:ty) => { - #[test] - fn surviving_edge_readable_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] = 1; - map[e2] = 2; - graph.delete_edge(e2); - assert_eq!(map[e1], 1); - } - - #[test] - fn reused_slot_returns_old_value() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - graph.add_edge(v1, v2); - let e1 = graph.add_edge(v1, v2); - let mut map = graph.edge_map(0); - map[e1] = 99; - graph.delete_edge(e1); - let e2 = graph.add_edge(v1, v2); - // EdgeMap uses raw indices, not edge identity. Because to_index uses arr_idx/2, - // both halves of a deleted edge pair map to the same index, so a new edge reusing - // either slot sees the old value. Callers must reinitialize stale slots after deletion. - assert_eq!(map[e2], 99); - } - }; -} diff --git a/src/traits.rs b/src/traits.rs index 3597959..7f27523 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,15 +1,12 @@ //! Core traits for undirected graph topologies. -use crate::maps::{EdgeMap, VertexMap}; +use crate::maps::ElementMap; -// 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. +/// querying a graph topology, and iterating over vertices and edges. /// /// # Vertices and edges /// @@ -21,9 +18,10 @@ use crate::maps::{EdgeMap, VertexMap}; /// 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 +/// # Addition and deletion /// -/// This trait covers graph construction and querying only. To delete vertices and edges, see +/// This trait covers graph querying only. To construct a graph incrementally by adding vertices and +/// edges, see [`GraphTopologyAddition`]. To delete vertices and edges, see /// [`GraphTopologyDeletion`]. /// /// [`Edge`]: GraphTopology::Edge @@ -41,15 +39,17 @@ pub trait GraphTopology { /// 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 + Copy; + type IncidenceCursor: IncidenceCursor; /// 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`. + /// Creates and returns an [`ElementMap`] for the vertices with every slot initialised to + /// `default`. + /// + /// The returned map only covers the vertices that exist in the graph at the time of the call. + /// Use [`GraphTopologyAddition::sync_vertex_map`] to extend the map before accessing new + /// vertices. /// /// # Examples /// @@ -58,26 +58,24 @@ pub trait GraphTopology { /// # use grapherity::models::Graph; /// let mut graph = Graph::new(); /// let v1 = graph.add_vertex(); + /// let v2 = 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(&self, default: T) -> VertexMap; + fn vertex_map(&self, default: T) -> ElementMap; /// 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`. + /// Creates and returns an [`ElementMap`] for the edges with every slot initialised to `default`. + /// + /// The returned map only covers the edges that exist in the graph at the time of the call. Use + /// [`GraphTopologyAddition::sync_edge_map`] to extend the map before accessing new edges. /// /// # Examples /// @@ -88,18 +86,16 @@ pub trait GraphTopology { /// 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 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(&self, default: T) -> EdgeMap; + fn edge_map(&self, default: T) -> ElementMap; /// Returns the degree of `v`, i.e. the number of incident edges, where each loop contributes 2. /// @@ -108,14 +104,14 @@ pub trait GraphTopology { /// 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. + /// Returns `true` if there is at least one edge between `u` and `v`, 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; + /// Panics if `u` or `v` is not a valid vertex of this graph. + fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool; /// Returns an iterator over all vertices in the graph. fn vertices(&self) -> impl Iterator; @@ -164,7 +160,10 @@ pub trait GraphTopology { /// # Panics /// /// Panics if `v` is not a valid vertex of this graph. - fn incidences(&self, v: Self::Vertex) -> impl Iterator; + fn incidences( + &self, + v: Self::Vertex, + ) -> impl Iterator>; /// Returns a cursor over all incidences of `v`, analogously to /// [`incidences`](Self::incidences), initially positioned before the first one. @@ -176,16 +175,117 @@ pub trait GraphTopology { /// /// Panics if `v` is not a valid vertex of this graph. fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor; +} + +/// A trait that adds construction operations to an undirected graph topology. +/// +/// This trait provides methods for addition of vertices and edges, and capacity management in an +/// undirected graph. +pub trait GraphTopologyAddition: GraphTopology + Default { + /// Returns the total number of vertices the graph can hold without reallocating. + /// + /// For every valid [`Vertex`] handle, the index produced by the conversion function used in + /// [`GraphTopology::vertex_map`] must be strictly less than this capacity. Implementors of + /// [`GraphTopology::vertex_map`] must size the returned vertex [`ElementMap`] to this capacity. + /// + /// [`Vertex`]: GraphTopology::Vertex + fn vertex_capacity(&self) -> usize; + + /// Returns the total number of edges the graph can hold without reallocating. + /// + /// For every valid [`Edge`] handle, the index produced by the conversion function used in + /// [`GraphTopology::edge_map`] must be strictly less than this capacity. Implementors of + /// [`GraphTopology::edge_map`] must size the returned edge [`ElementMap`] to this capacity. + /// + /// [`Edge`]: GraphTopology::Edge + fn edge_capacity(&self) -> usize; + + /// Reserves capacity for at least `additional` more vertices. Does nothing if capacity is + /// already sufficient. + /// + /// Use this before adding multiple vertices to avoid incremental growth on each call to + /// [`add_vertex`]. + /// + /// [`add_vertex`]: GraphTopologyAddition::add_vertex + fn reserve_vertices(&mut self, additional: usize); + + /// Reserves capacity for at least `additional` more edges. Does nothing if capacity is already + /// sufficient. + /// + /// Use this before adding multiple edges to avoid incremental growth on each call to + /// [`add_edge`]. + /// + /// [`add_edge`]: GraphTopologyAddition::add_edge + fn reserve_edges(&mut self, additional: usize); /// 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. + /// Adds a new edge between `u` and `v` 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; + /// Panics if `u` or `v` is not a valid vertex of this graph. + fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge; + + /// Grows `map` so that it covers a number of vertices up to at least [`vertex_capacity`]. + /// + /// The data for any newly covered vertices is initialised to the map's original default value. + /// Does nothing if `map` already covers enough vertices. + /// + /// An [`ElementMap`] does not grow on its own, so call this after [`reserve_vertices`] (or + /// after vertices have already been added) and before accessing `map` for any newly added + /// vertices. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let mut labels = graph.vertex_map("z"); + /// graph.reserve_vertices(1); + /// graph.sync_vertex_map(&mut labels); + /// let v = graph.add_vertex(); + /// labels[v] = "a"; + /// assert_eq!(labels[v], "a"); + /// ``` + /// + /// [`reserve_vertices`]: Self::reserve_vertices + /// [`vertex_capacity`]: Self::vertex_capacity + fn sync_vertex_map(&self, map: &mut ElementMap) { + map.grow(self.vertex_capacity()); + } + + /// Grows `map` so that it covers a number of edges up to at least [`edge_capacity`]. + /// + /// The data for any newly covered edges is initialised to the map's original default value. + /// Does nothing if `map` already covers enough edges. + /// + /// An [`ElementMap`] does not grow on its own, so call this after [`reserve_edges`] (or after + /// edges have already been added) and before accessing `map` for any newly added edges. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let mut weights = graph.edge_map(1); + /// let v1 = graph.add_vertex(); + /// let v2 = graph.add_vertex(); + /// graph.reserve_edges(1); + /// graph.sync_edge_map(&mut weights); + /// let e = graph.add_edge(v1, v2); + /// weights[e] = 5; + /// assert_eq!(weights[e], 5); + /// ``` + /// + /// [`reserve_edges`]: Self::reserve_edges + /// [`edge_capacity`]: Self::edge_capacity + fn sync_edge_map(&self, map: &mut ElementMap) { + map.grow(self.edge_capacity()); + } } /// A trait that adds deletion operations to an undirected graph topology. @@ -213,19 +313,21 @@ pub trait GraphTopologyDeletion: GraphTopology { /// A cursor for traversing the incidences of a vertex one step at a time. /// +/// Because the cursor is [`Copy`], its state can be saved and restored to replay or branch a +/// traversal. +/// /// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See /// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over /// [`GraphTopology::incidences`]. -pub trait IncidenceCursor { - /// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the - /// traversal is exhausted. +pub trait IncidenceCursor: Copy { + /// Advances the cursor and returns the next incidence, 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(); @@ -245,5 +347,187 @@ pub trait IncidenceCursor { /// assert!(c2.next(&graph).is_some()); /// assert!(c2.next(&graph).is_none()); /// ``` - fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>; + // TODO: Replace the above example graph with a path generator function. + fn next(&mut self, graph: &G) -> Option>; +} + +/// An incidence of some vertex *v* in a graph, i.e. a vertex-edge pair *(u, e)* such that *u* is +/// adjacent to *v* and *e* is an edge between them. +/// +/// Return type of [`GraphTopology::incidences`] and [`IncidenceCursor::next`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Incidence { + /// The vertex adjacent to *v*. + pub vertex: V, + /// The edge between *v* and [`vertex`](Self::vertex). + pub edge: E, +} + +/// A trait for constructing an undirected graph topology by converting another graph +/// implementation to this one. +pub trait GraphTopologyConversion: GraphTopology { + /// Creates a new graph instance that is isomorphic to `source`. + /// + /// Returns the converted graph instance and a [`GraphIsomorphism`] instance that maps the + /// vertices and edges of `source` to the corresponding vertices and edges of the returned + /// graph. + /// + /// # Examples + /// + /// ``` + /// use grapherity::prelude::*; + /// use grapherity::models::{AppendGraph, FrozenGraph}; + /// + /// let mut source = AppendGraph::new(); + /// let u = source.add_vertex(); + /// let v = source.add_vertex(); + /// source.add_edge(u, v); + /// let (graph, iso) = FrozenGraph::convert(&source); + /// + /// assert!(graph.are_adjacent(iso.vertex(u), iso.vertex(v))); + /// ``` + /// + /// The isomorphism can also convert [`ElementMap`] instances defined on `source` so that they + /// can be indexed by vertex or edge handles of the new graph. + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::{AppendGraph, FrozenGraph}; + /// + /// # let mut source = AppendGraph::new(); + /// # let u = source.add_vertex(); + /// # let v = source.add_vertex(); + /// # let (graph, isomorphism) = FrozenGraph::convert(&source); + /// + /// let mut source_map = source.vertex_map(0); + /// source_map[u] = 7; + /// let map = isomorphism.convert_vertex_map(&source_map); + /// + /// assert_eq!(map[isomorphism.vertex(u)], 7); + /// ``` + fn convert(source: &G) -> (Self, GraphIsomorphism) + where + Self: Sized; +} + +/// An explicit isomorphism between a source graph of type `G` and a graph of type `H` converted +/// from it. +/// +/// Returned by [`GraphTopologyConversion::convert`]. It maps each vertex and edge handle of the +/// source graph to the corresponding handle of the converted graph, and can transfer +/// [`ElementMap`] instances defined on the source over to the converted graph. +/// +/// The isomorphism describes the source as it was when it was converted. Handle lookups stay +/// correct for handles that were valid then. If the source graph type reuses storage after +/// deletions (see [`GraphTopologyDeletion`]), do not delete vertices or edges from the source +/// before converting maps, since a new element could take the place of a deleted one and be paired +/// with it. +pub struct GraphIsomorphism { + vertices: ElementMap, + edges: ElementMap, + vertex_map_template: ElementMap, + edge_map_template: ElementMap, +} + +impl GraphIsomorphism { + /// Creates an isomorphism from the given handle mappings, for use by implementations of + /// [`GraphTopologyConversion::convert`]. + /// + /// `vertices` and `edges` must map every vertex and edge of the source to the corresponding + /// vertex and edge of `target`, respectively. Their default values mark unmapped slots, so they + /// must be handles that are never valid in `target`, and must not have been assigned to any + /// mapped slot. Passing mappings that are not an isomorphism results in unspecified, but + /// memory-safe, behaviour. + pub fn new( + target: &H, + vertices: ElementMap, + edges: ElementMap, + ) -> Self { + Self { + vertices, + edges, + vertex_map_template: target.vertex_map(()), + edge_map_template: target.edge_map(()), + } + } + + /// Returns the vertex of the converted graph that corresponds to the source vertex `v`. + /// + /// `v` must have been a vertex of the source graph when it was converted. Otherwise, the + /// returned handle may be invalid, or this method may panic. + pub fn vertex(&self, v: G::Vertex) -> H::Vertex { + self.vertices[v] + } + + /// Returns the edge of the converted graph that corresponds to the source edge `e`. + /// + /// `e` must have been an edge of the source graph when it was converted. Otherwise, the + /// returned handle may be invalid, or this method may panic. + pub fn edge(&self, e: G::Edge) -> H::Edge { + self.edges[e] + } + + /// Converts a vertex map of the source into an equivalent vertex map of the converted graph. + /// + /// For each source vertex *v*, the returned map holds a clone of `map[v]` at `vertex(v)`. It + /// has the same default value as `map`, and it covers the vertices of the converted graph at + /// the time of conversion. Use [`GraphTopologyAddition::sync_vertex_map`] before accessing + /// vertices added to the converted graph afterwards. Missing slots of `map`, for instance + /// because `map` was created before the source grew, are treated as holding the default value. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::{AppendGraph, FrozenGraph}; + /// let mut source = AppendGraph::new(); + /// let u = source.add_vertex(); + /// let v = source.add_vertex(); + /// let (graph, iso) = FrozenGraph::convert(&source); + /// + /// let mut labels = source.vertex_map("none"); + /// labels[u] = "start"; + /// let converted = iso.convert_vertex_map(&labels); + /// assert_eq!(converted[iso.vertex(u)], "start"); + /// assert_eq!(converted[iso.vertex(v)], "none"); + /// ``` + pub fn convert_vertex_map( + &self, + map: &ElementMap, + ) -> ElementMap { + map.remap(&self.vertices, &self.vertex_map_template) + } + + /// Converts an edge map of the source into an equivalent edge map of the converted graph. + /// + /// For each source edge *e*, the returned map holds a clone of `map[e]` at `edge(e)`. It has + /// the same default value as `map`, and it covers the edges of the converted graph at the time + /// of conversion. Use [`GraphTopologyAddition::sync_edge_map`] before accessing edges added to + /// the converted graph afterwards. Missing slots of `map`, for instance because `map` was + /// created before the source grew, are treated as holding the default value. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::{AppendGraph, FrozenGraph}; + /// let mut source = AppendGraph::new(); + /// let u = source.add_vertex(); + /// let v = source.add_vertex(); + /// let e = source.add_edge(u, v); + /// let f = source.add_edge(u, v); + /// let (graph, iso) = FrozenGraph::convert(&source); + /// + /// let mut weights = source.edge_map(1); + /// weights[e] = 5; + /// let converted = iso.convert_edge_map(&weights); + /// assert_eq!(converted[iso.edge(e)], 5); + /// assert_eq!(converted[iso.edge(f)], 1); + /// ``` + pub fn convert_edge_map( + &self, + map: &ElementMap, + ) -> ElementMap { + map.remap(&self.edges, &self.edge_map_template) + } } diff --git a/tests/bfs.rs b/tests/bfs.rs index 293ea12..72cec83 100644 --- a/tests/bfs.rs +++ b/tests/bfs.rs @@ -1,13 +1,348 @@ -mod append_graph_tests { +use std::fmt::Debug; + +use grapherity::algorithms; +use grapherity::testing::fixtures; +use grapherity::testing::fixtures::MakeTestGraph; + +macro_rules! bfs_tests { + ($T:ty) => { + bfs_tests!(@wrap $T, + bfs_single_vertex, + bfs_disconnected, + bfs, + bfs_distances_single_vertex, + bfs_distances_disconnected, + bfs_distances, + bfs_find_source, + bfs_find_disconnected, + bfs_find, + bfs_find_where_source_matches, + bfs_find_where_disconnected, + bfs_find_where_no_match, + bfs_find_where_nearest, + bfs_find_path_source_equals_target, + bfs_find_path_disconnected, + bfs_find_path_adjacent, + bfs_find_path, + bfs_find_path_where_source_matches, + bfs_find_path_where_disconnected, + bfs_find_path_where_no_match, + bfs_find_path_where, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod frozen_graph { + use grapherity::models::FrozenGraph; + + bfs_tests!(FrozenGraph); +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::graph_topology_test_fixtures!(AppendGraph); - grapherity::bfs_tests!(AppendGraph); + bfs_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::graph_topology_test_fixtures!(Graph); - grapherity::bfs_tests!(Graph); + bfs_tests!(Graph); +} + +fn bfs_single_vertex() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::bfs(&graph, v); + assert_eq!( + result.distances[v], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex" + ); +} + +fn bfs_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::bfs(&graph, vertices[0]); + assert_eq!( + result.distances[vertices[0]], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.predecessors[vertices[0]], None, + "unexpected predecessor of source vertex" + ); + for &v in &vertices[1..3] { + assert_eq!( + result.distances[v], None, + "disconnected vertex {v:?} should have no distance" + ); + assert_eq!( + result.predecessors[v], None, + "disconnected vertex {v:?} should have no predecessor" + ); + } +} + +fn bfs() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::bfs(&graph, vertices[0]); + fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices); + fixtures::assert_standard_unweighted_predecessors_v0(&result.predecessors, &vertices); +} + +fn bfs_distances_single_vertex() { + let (graph, v) = G::single_vertex(); + let distances = algorithms::bfs_distances(&graph, v); + assert_eq!( + distances[v], + Some(0), + "unexpected distance of source vertex" + ); +} + +fn bfs_distances_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let distances = algorithms::bfs_distances(&graph, vertices[0]); + assert_eq!( + distances[vertices[0]], + Some(0), + "unexpected distance of source vertex" + ); + for &v in &vertices[1..3] { + assert_eq!( + distances[v], None, + "disconnected vertex {v:?} should have no distance" + ); + } +} + +fn bfs_distances() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let distances = algorithms::bfs_distances(&graph, vertices[0]); + fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); +} + +fn bfs_find_source() { + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find(&graph, v, v), + Some(0), + "source should be found at distance 0" + ); +} + +fn bfs_find_disconnected() { + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::bfs_find(&graph, v0, v1), + None, + "disconnected target should not be found" + ); +} + +fn bfs_find() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + fixtures::assert_standard_unweighted_distances_v0( + |v| algorithms::bfs_find(&graph, vertices[0], v), + &vertices, + ); +} + +fn bfs_find_where_source_matches() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find_where(&graph, v, |u| u == v), + Some((v, 0)), + "source should match with distance 0" + ); +} + +fn bfs_find_where_disconnected() +where + G::Vertex: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::bfs_find_where(&graph, v0, |v| v == v1), + None, + "disconnected vertex {v1:?} should not be found" + ); +} + +fn bfs_find_where_no_match() +where + G::Vertex: Debug, +{ + let (graph, [v0, ..], _, _) = G::standard(); + assert_eq!( + algorithms::bfs_find_where(&graph, v0, |_| false), + None, + "no vertex should match an always-false predicate" + ); +} + +fn bfs_find_where_nearest() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + // vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0]. + // vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9]. + // BFS must return one of the distance-3 ones, not vertices[9]. + let result = algorithms::bfs_find_where(&graph, vertices[0], |v| { + v == vertices[7] || v == vertices[8] || v == vertices[9] + }); + assert!(result.is_some(), "expected a match"); + let (found, distance) = result.unwrap(); + assert_eq!( + distance, 3, + "unexpected distance to nearest matching vertex {found:?}", + ); + assert!( + found == vertices[7] || found == vertices[8], + "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", + vertices[7], + vertices[8] + ); +} + +fn bfs_find_path_source_equals_target() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find_path(&graph, v, v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn bfs_find_path_disconnected() +where + G::Edge: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::bfs_find_path(&graph, v0, v1), + None, + "no path should exist to disconnected vertex" + ); +} + +fn bfs_find_path_adjacent() +where + G::Edge: Debug, +{ + let (graph, [v0, v1], e) = G::single_edge(); + let path = algorithms::bfs_find_path(&graph, v0, v1) + .expect("path should exist between adjacent vertices"); + assert_eq!(path, [e], "path should contain only the connecting edge"); +} + +fn bfs_find_path() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + fixtures::assert_standard_unweighted_distances_v0( + |v| { + let result = algorithms::bfs_find_path(&graph, vertices[0], v); + let path = fixtures::assert_valid_path(&graph, result, vertices[0], &[v]); + Some(u32::try_from(path.len()).unwrap()) + }, + &vertices, + ); +} + +fn bfs_find_path_where_source_matches() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::bfs_find_path_where(&graph, v, |u| u == v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn bfs_find_path_where_disconnected() +where + G::Edge: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::bfs_find_path_where(&graph, v0, |v| v == v1), + None, + "no path should exist to disconnected vertex" + ); +} + +fn bfs_find_path_where_no_match() +where + G::Edge: Debug, +{ + let (graph, [v0, ..], _, _) = G::standard(); + assert_eq!( + algorithms::bfs_find_path_where(&graph, v0, |_| false), + None, + "no path should exist when predicate never matches" + ); +} + +fn bfs_find_path_where() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + // vertices[7] and vertices[8] are at distance 3 from vertices[0]; vertices[9] is at distance 4. + // BFS must return one shortest path, so it must lead to one of the distance-3 matches. + let result = algorithms::bfs_find_path_where(&graph, vertices[0], |v| { + v == vertices[7] || v == vertices[8] || v == vertices[9] + }); + let path = + fixtures::assert_valid_path(&graph, result, vertices[0], &[vertices[7], vertices[8]]); + assert_eq!( + path.len(), + 3, + "BFS should return shortest path to a matching vertex" + ); } diff --git a/tests/dfs.rs b/tests/dfs.rs index ec7e039..0875463 100644 --- a/tests/dfs.rs +++ b/tests/dfs.rs @@ -1,13 +1,358 @@ -mod append_graph_tests { +use std::fmt::Debug; + +use grapherity::algorithms; +use grapherity::maps::ElementMap; +use grapherity::testing::fixtures; +use grapherity::testing::fixtures::MakeTestGraph; +use grapherity::traits::GraphTopology; + +macro_rules! dfs_tests { + ($T:ty) => { + dfs_tests!(@wrap $T, + dfs_single_vertex, + dfs_disconnected, + dfs, + dfs_visited_single_vertex, + dfs_visited_disconnected, + dfs_visited, + dfs_find_source, + dfs_find_disconnected, + dfs_find, + dfs_find_where_source_matches, + dfs_find_where_disconnected, + dfs_find_where_no_match, + dfs_find_where_adjacent, + dfs_find_where, + dfs_find_path_source_equals_target, + dfs_find_path_disconnected, + dfs_find_path_adjacent, + dfs_find_path, + dfs_find_path_where_source_matches, + dfs_find_path_where_disconnected, + dfs_find_path_where_no_match, + dfs_find_path_where, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod frozen_graph { + use grapherity::models::FrozenGraph; + + dfs_tests!(FrozenGraph); +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::graph_topology_test_fixtures!(AppendGraph); - grapherity::dfs_tests!(AppendGraph); + dfs_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::graph_topology_test_fixtures!(Graph); - grapherity::dfs_tests!(Graph); + dfs_tests!(Graph); +} + +fn dfs_single_vertex() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dfs(&graph, v); + assert!(result.visited[v], "source vertex should be visited"); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex" + ); +} + +fn dfs_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dfs(&graph, vertices[0]); + assert!( + result.visited[vertices[0]], + "source vertex should be visited" + ); + assert_eq!( + result.predecessors[vertices[0]], None, + "unexpected predecessor of source vertex" + ); + for &v in &vertices[1..3] { + assert!( + !result.visited[v], + "disconnected vertex {v:?} should not be visited" + ); + assert_eq!( + result.predecessors[v], None, + "disconnected vertex {v:?} should have no predecessor" + ); + } +} + +fn dfs() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::dfs(&graph, vertices[0]); + assert_dfs_visited::(&result.visited, &vertices); + assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices); +} + +fn dfs_visited_single_vertex() { + let (graph, v) = G::single_vertex(); + let visited = algorithms::dfs_visited(&graph, v); + assert!(visited[v], "source vertex should be visited"); +} + +fn dfs_visited_disconnected() +where + G::Vertex: Debug, +{ + let (graph, vertices) = G::disconnected(); + let visited = algorithms::dfs_visited(&graph, vertices[0]); + assert!(visited[vertices[0]], "source vertex should be visited"); + for &v in &vertices[1..3] { + assert!( + !visited[v], + "disconnected vertex {v:?} should not be visited" + ); + } +} + +fn dfs_visited() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let visited = algorithms::dfs_visited(&graph, vertices[0]); + assert_dfs_visited::(&visited, &vertices); +} + +fn dfs_find_source() { + let (graph, v) = G::single_vertex(); + assert!( + algorithms::dfs_find(&graph, v, v), + "source should find itself" + ); +} + +fn dfs_find_disconnected() { + let (graph, [v0, v1, _]) = G::disconnected(); + assert!( + !algorithms::dfs_find(&graph, v0, v1), + "disconnected target should not be found" + ); +} + +fn dfs_find() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + for &v in &vertices { + assert!( + algorithms::dfs_find(&graph, vertices[0], v), + "vertex {v:?} should be reachable from {:?}", + vertices[0] + ); + } +} + +fn dfs_find_where_source_matches() +where + G::Vertex: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_where(&graph, v, |u| u == v), + Some(v), + "source should find itself" + ); +} + +fn dfs_find_where_disconnected() +where + G::Vertex: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_where(&graph, v0, |v| v == v1), + None, + "disconnected vertex {v1:?} should not be found" + ); +} + +fn dfs_find_where_no_match() +where + G::Vertex: Debug, +{ + let (graph, [v0, ..], _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, v0, |_| false), + None, + "no vertex should match an always-false predicate" + ); +} + +fn dfs_find_where_adjacent() +where + G::Vertex: Debug, +{ + let (graph, [v0, v1, ..], _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, v0, |v| v == v1), + Some(v1), + "expected to find adjacent vertex" + ); +} + +fn dfs_find_where() +where + G::Vertex: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), + Some(vertices[9]), + "expected to find connected vertex" + ); +} + +fn dfs_find_path_source_equals_target() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_path(&graph, v, v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn dfs_find_path_disconnected() +where + G::Edge: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_path(&graph, v0, v1), + None, + "no path should exist to disconnected vertex" + ); +} + +fn dfs_find_path_adjacent() +where + G::Edge: Debug, +{ + let (graph, [v0, v1], e) = G::single_edge(); + let path = algorithms::dfs_find_path(&graph, v0, v1) + .expect("path should exist between adjacent vertices"); + assert_eq!(path, [e], "path should contain only the connecting edge"); +} + +fn dfs_find_path() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[9]); + fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]); +} + +fn dfs_find_path_where_source_matches() +where + G::Edge: Debug, +{ + let (graph, v) = G::single_vertex(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, v, |u| u == v), + Some(vec![]), + "path from source to itself should be empty" + ); +} + +fn dfs_find_path_where_disconnected() +where + G::Edge: Debug, +{ + let (graph, [v0, v1, _]) = G::disconnected(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, v0, |v| v == v1), + None, + "no path should exist to disconnected vertex" + ); +} + +fn dfs_find_path_where_no_match() +where + G::Edge: Debug, +{ + let (graph, [v0, ..], _, _) = G::standard(); + assert_eq!( + algorithms::dfs_find_path_where(&graph, v0, |_| false), + None, + "no path should exist when predicate never matches" + ); +} + +fn dfs_find_path_where() +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let (graph, vertices, _, _) = G::standard(); + let path = algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]); + _ = fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]); +} + +fn assert_dfs_visited( + visited: &ElementMap, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + for &v in vertices { + assert!(visited[v], "vertex {v:?} should be visited"); + } +} + +fn assert_dfs_predecessors( + graph: &G, + visited: &ElementMap, + predecessors: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + assert_eq!( + predecessors[vertices[0]], None, + "source should have no predecessor" + ); + for &v in vertices.iter().skip(1) { + let p = predecessors[v].unwrap_or_else(|| panic!("vertex {v:?} should have a predecessor")); + assert!( + visited[p], + "predecessor {p:?} of vertex {v:?} should be visited" + ); + assert!( + graph.are_adjacent(v, p), + "predecessor {p:?} of vertex {v:?} should be adjacent" + ); + } } diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index b495546..a2f5559 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,13 +1,297 @@ -mod append_graph_tests { +use std::fmt::Debug; +use std::hash::Hash; + +use grapherity::algorithms; +use grapherity::algorithms::DijkstraResult; +use grapherity::maps::ElementMap; +use grapherity::testing::fixtures; +use grapherity::testing::fixtures::MakeTestGraph; +use grapherity::traits::GraphTopology; + +macro_rules! dijkstra_tests { + ($T:ty) => { + dijkstra_tests!(@wrap $T, + dijkstra_single_vertex, + dijkstra_disconnected, + dijkstra_zero_weight_loop, + dijkstra, + dijkstra_distances, + dijkstra_unweighted_single_vertex, + dijkstra_unweighted_disconnected, + dijkstra_unweighted, + dijkstra_distances_unweighted_single_vertex, + dijkstra_distances_unweighted_disconnected, + dijkstra_distances_unweighted, + ); + }; + + (@wrap $T:ty, $($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + super::$name::<$T>(); + } + )* + }; +} + +mod frozen_graph { + use grapherity::models::FrozenGraph; + + dijkstra_tests!(FrozenGraph); +} + +mod append_graph { use grapherity::models::AppendGraph; - grapherity::graph_topology_test_fixtures!(AppendGraph); - grapherity::dijkstra_tests!(AppendGraph); + dijkstra_tests!(AppendGraph); } -mod graph_tests { +mod graph { use grapherity::models::Graph; - grapherity::graph_topology_test_fixtures!(Graph); - grapherity::dijkstra_tests!(Graph); + dijkstra_tests!(Graph); +} + +fn dijkstra_single_vertex() +where + G::Vertex: Debug + Hash, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dijkstra(&graph, v, |_| panic!("unexpected call of weight functor")); + assert_single_vertex::(&result, v); +} + +fn dijkstra_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dijkstra(&graph, vertices[0], |_| { + panic!("unexpected call of weight functor") + }); + assert_single_vertex::(&result, vertices[0]); + assert_disconnected::(&result, &vertices[1..3]); +} + +fn dijkstra_zero_weight_loop() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, weights) = make_two_edge_path_with_zero_weight_loops::(); + let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); + assert_single_vertex::(&result, vertices[0]); + for (i, &v) in vertices.iter().enumerate().skip(1) { + assert_eq!( + result.predecessors[v], + Some(vertices[i - 1]), + "unexpected predecessor of vertex {v:?}" + ); + assert_eq!( + result.distances[v], + Some(i.try_into().unwrap()), + "unexpected distance of vertex {v:?}" + ); + } +} + +fn dijkstra() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, weights) = make_standard_weighted::(); + let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); + assert_standard_weighted_distances_v0::(&result.distances, &vertices); + assert_standard_weighted_predecessors_v0::(&result.predecessors, &vertices); +} + +fn dijkstra_distances() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, weights) = make_standard_weighted::(); + let distances = algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]); + assert_standard_weighted_distances_v0::(&distances, &vertices); +} + +fn dijkstra_unweighted_single_vertex() +where + G::Vertex: Debug + Hash, +{ + let (graph, v) = G::single_vertex(); + let result = algorithms::dijkstra_unweighted(&graph, v); + assert_single_vertex::(&result, v) +} + +fn dijkstra_unweighted_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); + assert_single_vertex::(&result, vertices[0]); + assert_disconnected::(&result, &vertices[1..3]); +} + +fn dijkstra_unweighted() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _) = G::standard(); + let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); + fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices); + fixtures::assert_standard_unweighted_predecessors_v0(&result.predecessors, &vertices); +} + +fn dijkstra_distances_unweighted_single_vertex() +where + G::Vertex: Hash, +{ + let (graph, v) = G::single_vertex(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, v); + assert_distances_single_vertex::(&distances, v); +} + +fn dijkstra_distances_unweighted_disconnected() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices) = G::disconnected(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + assert_distances_single_vertex::(&distances, vertices[0]); + assert_distances_disconnected::(&distances, &vertices[1..3]); +} + +fn dijkstra_distances_unweighted() +where + G::Vertex: Debug + Hash, +{ + let (graph, vertices, _, _) = G::standard(); + let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); +} + +fn assert_single_vertex(result: &DijkstraResult, v: G::Vertex) +where + G::Vertex: Debug, +{ + assert_distances_single_vertex::(&result.distances, v); + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of source vertex", + ); +} + +fn assert_distances_single_vertex( + distances: &ElementMap>, + v: G::Vertex, +) { + assert_eq!( + distances[v], + Some(0), + "unexpected distance of source vertex" + ); +} + +fn assert_disconnected( + result: &DijkstraResult, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + assert_distances_disconnected::(&result.distances, vertices); + for &v in vertices { + assert_eq!( + result.predecessors[v], None, + "unexpected predecessor of disconnected vertex {v:?}", + ); + } +} + +fn assert_distances_disconnected( + distances: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + for &v in vertices { + assert_eq!( + distances[v], None, + "unexpected distance of disconnected vertex {v:?}", + ); + } +} + +fn assert_standard_weighted_predecessors_v0( + actual: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + let expected = [ + vec![None], + vec![Some(vertices[0])], + vec![Some(vertices[4])], + vec![Some(vertices[1])], + vec![Some(vertices[7])], + vec![Some(vertices[9])], + vec![Some(vertices[3])], + vec![Some(vertices[9])], + vec![Some(vertices[7])], + vec![Some(vertices[6])], + ]; + assert_eq!(vertices.len(), expected.len()); + for (&v, exp) in vertices.iter().zip(&expected) { + assert!( + exp.contains(&actual[v]), + "unexpected predecessor {:?} of {v:?}", + actual[v] + ); + } +} + +fn assert_standard_weighted_distances_v0( + actual: &ElementMap>, + vertices: &[G::Vertex], +) where + G::Vertex: Debug, +{ + let expected = [ + Some(0), + Some(1), + Some(65), + Some(4), + Some(58), + Some(43), + Some(14), + Some(46), + Some(145), + Some(29), + ]; + assert_eq!(vertices.len(), expected.len()); + for (&v, &exp) in vertices.iter().zip(&expected) { + assert_eq!( + actual[v], exp, + "unexpected distance from {:?} to {v:?}", + vertices[0] + ); + } +} + +fn make_standard_weighted() -> (G, [G::Vertex; 10], ElementMap) { + let (graph, vertices, edges, _) = G::standard(); + let mut weights = graph.edge_map(99); + for i in [1, 3, 7, 10, 12, 14, 15, 17] { + weights[edges[i].0] = i.try_into().unwrap(); + } + (graph, vertices, weights) +} + +fn make_two_edge_path_with_zero_weight_loops() +-> (G, [G::Vertex; 3], ElementMap) { + let (graph, vertices, edges) = G::two_edge_path_with_loops(); + let mut weights = graph.edge_map(1); + weights[edges[2]] = 0; + weights[edges[3]] = 0; + (graph, vertices, weights) } diff --git a/tests/find_path.rs b/tests/find_path.rs deleted file mode 100644 index 7f296c8..0000000 --- a/tests/find_path.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod append_graph_tests { - use grapherity::models::AppendGraph; - - grapherity::graph_topology_test_fixtures!(AppendGraph); - grapherity::find_path_tests!(AppendGraph); -} - -mod graph_tests { - use grapherity::models::Graph; - - grapherity::graph_topology_test_fixtures!(Graph); - grapherity::find_path_tests!(Graph); -} diff --git a/tests/maps.rs b/tests/maps.rs deleted file mode 100644 index b936db9..0000000 --- a/tests/maps.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod append_graph_vertex_map_tests { - use grapherity::models::AppendGraph; - - grapherity::vertex_map_tests!(AppendGraph); -} - -mod append_graph_edge_map_tests { - use grapherity::models::AppendGraph; - - grapherity::edge_map_tests!(AppendGraph); -} - -mod graph_vertex_map_tests { - use grapherity::models::Graph; - - grapherity::vertex_map_tests!(Graph); - grapherity::vertex_map_deletion_tests!(Graph); -} - -mod graph_edge_map_tests { - use grapherity::models::Graph; - - grapherity::edge_map_tests!(Graph); - grapherity::edge_map_deletion_tests!(Graph); -}