Merge pull request 'Release 0.3.0' (#8) from release-0.3.0 into main

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2026-10-15 10:06:12 +02:00
34 changed files with 5083 additions and 3039 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "grapherity" name = "grapherity"
version = "0.2.4" version = "0.3.0"
authors = ["Stefan Müller"] authors = ["Stefan Müller"]
edition = "2024" edition = "2024"
rust-version = "1.85.0" rust-version = "1.85.0"
@@ -11,4 +11,5 @@ keywords = ["graph", "graph-algorithms"]
categories = ["algorithms", "data-structures", "mathematics"] categories = ["algorithms", "data-structures", "mathematics"]
[dependencies] [dependencies]
priority-queue = "2.7.0"
typed-generational-arena = "0.2.9" typed-generational-arena = "0.2.9"
+4 -4
View File
@@ -6,8 +6,8 @@ This crate provides data structures to model graphs and to perform algorithms on
Currently supported are: Currently supported are:
* Undirected graph types `Graph` and `AppendGraph`, built on a flat, index-based adjacency list, * Undirected graph types `Graph`, `AppendGraph`, and `FrozenGraph`, built on a flat, index-based adjacency list,
* Maps to freely associate custom data with vertices and edges, * `ElementMap` to freely associate custom data with vertices and edges,
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants, * Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
* Exposed traits to implement custom graph data structures or algorithms. * Exposed traits to implement custom graph data structures or algorithms.
@@ -22,14 +22,14 @@ use grapherity::models::Graph;
use grapherity::algorithms; use grapherity::algorithms;
let mut graph = Graph::new(); 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. // Adds three vertices and two edges.
let v1 = graph.add_vertex(); let v1 = graph.add_vertex();
let v2 = graph.add_vertex(); let v2 = graph.add_vertex();
let v3 = graph.add_vertex(); let v3 = graph.add_vertex();
graph.add_edge(v1, v2); graph.add_edge(v1, v2);
let e = graph.add_edge(v2, v3); 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. // Sets a non-default weight for the second edge.
weights[e] = 5; weights[e] = 5;
+197 -66
View File
@@ -28,46 +28,60 @@
//! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search //! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
//! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm //! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
use std::cmp::Ordering; use priority_queue::PriorityQueue;
use std::collections::{BinaryHeap, VecDeque}; use std::cmp::Reverse;
use std::collections::VecDeque;
use std::hash::Hash;
use std::ops::Add;
use crate::maps::VertexMap; use crate::maps::ElementMap;
use crate::traits::{GraphTopology, IncidenceCursor}; use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
#[derive(PartialEq, Eq)] /// Trait that edge weights for [`dijkstra`] and its variants need to satisfy.
struct DistanceOrderedVertex<V> { ///
distance: u32, /// [`Default`] is used as the starting distance for `source`, so it must behave as the additive
vertex: V, /// 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<Output = Self> + Default {}
impl<V: Eq> PartialOrd for DistanceOrderedVertex<V> { impl<T: Copy + Ord + Add<Output = T> + Default> Weight for T {}
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<V: Eq> Ord for DistanceOrderedVertex<V> {
fn cmp(&self, other: &Self) -> Ordering {
other.distance.cmp(&self.distance)
}
}
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. /// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
pub struct DijkstraResult<V: Copy> { pub struct DijkstraResult<V: Copy, W: Weight> {
/// Vertex map of minimum distances from a given `source` vertex. /// Vertex map of minimum distances from a given `source` vertex.
pub distances: VertexMap<V, Option<u32>>, pub distances: ElementMap<V, Option<W>>,
/// Vertex map of predecessors on some shortest path from a given `source` vertex. /// Vertex map of predecessors on some shortest path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>, pub predecessors: ElementMap<V, Option<V>>,
} }
// 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. /// [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 /// 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 /// of each vertex on some shortest path from `source` to that vertex. Returns `None` for any vertex
/// not connected to `source`. /// not connected to `source`.
/// ///
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
///
/// # Panics /// # Panics
/// ///
/// Panics if `source` is not a valid vertex of `graph`. /// Panics if `source` is not a valid vertex of `graph`.
@@ -91,13 +105,18 @@ pub struct DijkstraResult<V: Copy> {
/// ``` /// ```
/// ///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex> pub fn dijkstra<G, W>(
graph: &G,
source: G::Vertex,
weights: impl Fn(G::Edge) -> W,
) -> DijkstraResult<G::Vertex, W>
where where
G: GraphTopology, G: GraphTopology,
W: Fn(G::Edge) -> u32, G::Vertex: Hash,
W: Weight,
{ {
let mut predecessors = graph.vertex_map(None); 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); predecessors[adjacent] = Some(predecessor);
}); });
DijkstraResult { DijkstraResult {
@@ -109,9 +128,11 @@ where
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances. /// [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 /// 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`. /// vertex not connected to `source`.
/// ///
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
///
/// # Panics /// # Panics
/// ///
/// Panics if `source` is not a valid vertex of `graph`. /// Panics if `source` is not a valid vertex of `graph`.
@@ -137,13 +158,14 @@ where
pub fn dijkstra_distances<G, W>( pub fn dijkstra_distances<G, W>(
graph: &G, graph: &G,
source: G::Vertex, source: G::Vertex,
weight: W, weights: impl Fn(G::Edge) -> W,
) -> VertexMap<G::Vertex, Option<u32>> ) -> ElementMap<G::Vertex, Option<W>>
where where
G: GraphTopology, 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. /// [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 /// 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`. /// 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. /// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity.
/// ///
/// # Panics /// # Panics
@@ -175,9 +199,10 @@ where
/// ``` /// ```
/// ///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex> pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex, u32>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash,
{ {
dijkstra(graph, source, |_| 1) dijkstra(graph, source, |_| 1)
} }
@@ -188,6 +213,8 @@ where
/// the distances from `source` to each vertex. Returns `None` for any vertex not connected to /// the distances from `source` to each vertex. Returns `None` for any vertex not connected to
/// `source`. /// `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. /// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity.
/// ///
/// # Panics /// # Panics
@@ -213,9 +240,10 @@ where
pub fn dijkstra_distances_unweighted<G>( pub fn dijkstra_distances_unweighted<G>(
graph: &G, graph: &G,
source: G::Vertex, source: G::Vertex,
) -> VertexMap<G::Vertex, Option<u32>> ) -> ElementMap<G::Vertex, Option<u32>>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash,
{ {
dijkstra_distances(graph, source, |_| 1) dijkstra_distances(graph, source, |_| 1)
} }
@@ -223,37 +251,32 @@ where
fn dijkstra_impl<G, W, F>( fn dijkstra_impl<G, W, F>(
graph: &G, graph: &G,
source: G::Vertex, source: G::Vertex,
weight: W, weights: impl Fn(G::Edge) -> W,
mut on_relax: F, mut on_relax: F,
) -> VertexMap<G::Vertex, Option<u32>> ) -> ElementMap<G::Vertex, Option<W>>
where where
G: GraphTopology, G: GraphTopology,
W: Fn(G::Edge) -> u32, G::Vertex: Hash,
W: Weight,
F: FnMut(G::Vertex, G::Vertex), F: FnMut(G::Vertex, G::Vertex),
{ {
let mut distances = graph.vertex_map(None); let mut distances = graph.vertex_map(None);
let mut heap = BinaryHeap::new(); let mut heap = PriorityQueue::new();
distances[source] = Some(0); distances[source] = Some(W::default());
heap.push(DistanceOrderedVertex { heap.push(source, Reverse(W::default()));
vertex: source,
distance: 0,
});
while let Some(v) = heap.pop() { while let Some((v, Reverse(v_distance))) = heap.pop() {
for incidence in graph.incidences(v.vertex) { for Incidence { vertex: u, edge: e } in graph.incidences(v) {
let new_distance = distances[v.vertex].unwrap() + weight(incidence.1); let new_distance = weights(e) + v_distance;
if match distances[incidence.0] { if match distances[u] {
None => true, None => true,
Some(old_distance) if old_distance > new_distance => true, Some(old_distance) if old_distance > new_distance => true,
_ => false, _ => false,
} { } {
distances[incidence.0] = Some(new_distance); distances[u] = Some(new_distance);
on_relax(incidence.0, v.vertex); on_relax(u, v);
heap.push(DistanceOrderedVertex { heap.push_increase(u, Reverse(new_distance));
vertex: incidence.0,
distance: new_distance,
});
} }
} }
} }
@@ -263,9 +286,9 @@ where
/// Return data type for [`bfs`]. /// Return data type for [`bfs`].
pub struct BfsResult<V: Copy> { pub struct BfsResult<V: Copy> {
/// Vertex map of minimum distances from a given `source` vertex. /// Vertex map of minimum distances from a given `source` vertex.
pub distances: VertexMap<V, Option<u32>>, pub distances: ElementMap<V, Option<u32>>,
/// Vertex map of predecessors on some shortest path from a given `source` vertex. /// Vertex map of predecessors on some shortest path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>, pub predecessors: ElementMap<V, Option<V>>,
} }
/// [Breadth-first search] traversal from `source`, returns distances and predecessors. /// [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 /// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Option<u32>> pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> ElementMap<G::Vertex, Option<u32>>
where where
G: GraphTopology, G: GraphTopology,
{ {
@@ -423,7 +446,7 @@ where
} }
struct BfsImplResult<V: Copy> { struct BfsImplResult<V: Copy> {
distances: VertexMap<V, Option<u32>>, distances: ElementMap<V, Option<u32>>,
found: Option<(V, u32)>, found: Option<(V, u32)>,
} }
@@ -433,10 +456,8 @@ where
F: FnMut(G::Vertex, G::Vertex) -> bool, F: FnMut(G::Vertex, G::Vertex) -> bool,
{ {
let mut distances = graph.vertex_map(None); let mut distances = graph.vertex_map(None);
let mut queue = VecDeque::new();
distances[source] = Some(0); distances[source] = Some(0);
queue.push_back(source); let mut queue = VecDeque::from([source]);
while let Some(v) = queue.pop_front() { while let Some(v) = queue.pop_front() {
for neighbor in graph.adjacent_vertices(v) { 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<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
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<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
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`]. /// Return data type for [`dfs`].
pub struct DfsResult<V: Copy> { pub struct DfsResult<V: Copy> {
/// Vertex map indicating which vertices were visited during the search. /// Vertex map indicating which vertices were visited during the search.
pub visited: VertexMap<V, bool>, pub visited: ElementMap<V, bool>,
/// Vertex map of predecessors on the DFS tree path from a given `source` vertex. /// Vertex map of predecessors on the DFS tree path from a given `source` vertex.
pub predecessors: VertexMap<V, Option<V>>, pub predecessors: ElementMap<V, Option<V>>,
} }
/// [Depth-first search] traversal from `source`, returns visited vertices and predecessors. /// [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 /// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool> pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> ElementMap<G::Vertex, bool>
where where
G: GraphTopology, G: GraphTopology,
{ {
@@ -620,7 +748,7 @@ where
} }
struct DfsImplResult<V: Copy> { struct DfsImplResult<V: Copy> {
visited: VertexMap<V, bool>, visited: ElementMap<V, bool>,
found: Option<V>, found: Option<V>,
} }
@@ -748,7 +876,10 @@ where
None => { None => {
stack.pop(); stack.pop();
} }
Some((neighbor, edge)) => { Some(Incidence {
vertex: neighbor,
edge,
}) => {
if predicate(neighbor) { if predicate(neighbor) {
let mut path: Vec<G::Edge> = let mut path: Vec<G::Edge> =
stack.iter().filter_map(|f| f.arrival_edge).collect(); stack.iter().filter_map(|f| f.arrival_edge).collect();
+79
View File
@@ -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<G>(
graph: &mut G,
) -> (
[<G as GraphTopology>::Vertex; 10],
[<G as GraphTopology>::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<G: GraphTopology>(
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"
);
}
}
+37 -24
View File
@@ -5,12 +5,12 @@
//! //!
//! Currently supported are: //! Currently supported are:
//! //!
//! * Undirected graph types [`Graph`] and [`AppendGraph`] built on a flat, index-based adjacency //! * Undirected graph types [`Graph`], [`AppendGraph`], and [`FrozenGraph`] built on a flat,
//! list, //! index-based adjacency list,
//! * [`VertexMap`] and [`EdgeMap`] to freely associate custom data with vertices and edges, //! * [`ElementMap`] to freely associate custom data with vertices and edges,
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different //! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
//! variants, //! variants,
//! * Exposed traits to implement custom graph data structures or algorithms. //! * Exposed [`traits`] to implement custom graph data structures or algorithms.
//! //!
//! # Usage example //! # Usage example
//! //!
@@ -23,14 +23,14 @@
//! use grapherity::algorithms; //! use grapherity::algorithms;
//! //!
//! let mut graph = Graph::new(); //! 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. //! // Adds three vertices and two edges.
//! let v1 = graph.add_vertex(); //! let v1 = graph.add_vertex();
//! let v2 = graph.add_vertex(); //! let v2 = graph.add_vertex();
//! let v3 = graph.add_vertex(); //! let v3 = graph.add_vertex();
//! graph.add_edge(v1, v2); //! graph.add_edge(v1, v2);
//! let e = graph.add_edge(v2, v3); //! 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. //! // Sets a non-default weight for the second edge.
//! weights[e] = 5; //! weights[e] = 5;
//! //!
@@ -62,9 +62,11 @@
//! //!
//! # Graph types //! # Graph types
//! //!
//! [`Graph`] and [`AppendGraph`] both implement [`GraphTopology`] using a flat, index-based adjacency //! [`Graph`], [`AppendGraph`], and [`FrozenGraph`] all implement [`GraphTopology`] using a flat,
//! list to store edges, which means that vertex and edge insertions happen in `O(1)`. Degree //! index-based adjacency list to store edges. Degree lookups run in `O(1)`.
//! lookups are also `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 //! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
//! vertices and edges. //! vertices and edges.
@@ -73,6 +75,10 @@
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation //! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
//! tracking needed for stable handles after deletion. //! 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 //! # Vertices and edges
//! //!
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`] //! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
@@ -91,8 +97,8 @@
//! graph.degree(v); //! graph.degree(v);
//! ``` //! ```
//! //!
//! [`VertexMap`] and [`EdgeMap`] are not required to panic if provided with an invalid //! [`ElementMap`] does not check validity of vertices or edges and might or might not panic in case
//! vertex or edge. //! an invalid handle is provided.
//! //!
//! Vertices and edges are provided by the graph and not intended to be generated by users. //! 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); //! assert_eq!(graph.degree(v1), 6);
//! ``` //! ```
//! //!
//! [BFS]: crate::algorithms::bfs //! [BFS]: algorithms::bfs
//! [Dijkstra's algorithm]: crate::algorithms::dijkstra //! [Dijkstra's algorithm]: algorithms::dijkstra
//! [DFS]: crate::algorithms::dfs //! [DFS]: algorithms::dfs
//! [`EdgeMap`]: crate::maps::EdgeMap //! [`ElementMap`]: maps::ElementMap
//! [`VertexMap`]: crate::maps::VertexMap //! [`AppendGraph`]: models::AppendGraph
//! [`AppendGraph`]: crate::models::AppendGraph //! [`FrozenGraph`]: models::FrozenGraph
//! [`Graph`]: crate::models::Graph //! [`Graph`]: models::Graph
//! [`GraphTopology`]: crate::traits::GraphTopology //! [`traits`]: traits
//! [`GraphTopology::Edge`]: crate::traits::GraphTopology::Edge //! [`GraphTopology`]: traits::GraphTopology
//! [`GraphTopology::Vertex`]: crate::traits::GraphTopology::Vertex //! [`GraphTopology::Edge`]: traits::GraphTopology::Edge
//! [`GraphTopologyDeletion`]: crate::traits::GraphTopologyDeletion //! [`GraphTopology::Vertex`]: traits::GraphTopology::Vertex
//! [`GraphTopologyAddition`]: traits::GraphTopologyAddition
//! [`GraphTopologyDeletion`]: traits::GraphTopologyDeletion
#![warn(missing_docs)] #![warn(missing_docs)]
pub mod algorithms; pub mod algorithms;
pub mod generators;
pub mod maps; pub mod maps;
pub mod models; pub mod models;
pub mod traits; pub mod traits;
/// Convenience re-exports of graph topology traits for common use. /// Convenience re-exports of graph topology traits for common use.
pub mod prelude { 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;
+345 -158
View File
@@ -1,194 +1,381 @@
//! Provides maps to associate custom data to graph vertices and edges. //! 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 //! [`ElementMap`] is a dense, [`Vec`]-backed map to associate data to either all vertices or all
//! all vertices or all edges in a graph, respectively. //! edges in a graph.
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use crate::traits::GraphTopology; /// A map to associate custom data to graph vertices or edges.
/// A map to associate custom data to graph vertices.
/// ///
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph /// This map uses raw element indices to associate homogenous custom data of type `T` to graph
/// vertices. The implementation uses a [`Vec`], allocating contiguous slots for the data, which /// vertices or edges. The implementation uses a [`Vec`], allocating contiguous slots for the data,
/// means that the provided index conversion function should map vertices to contiguous indices, or /// which means that the provided index conversion function should map elements to contiguous
/// indices with relatively few gaps. /// 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 /// Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] to obtain an `ElementMap` for
/// beyond current capacity is written, but the `default` value can transparently be read. No bound /// vertices or edges, respectively.
/// or validity checks are performed by the map on the provided vertex handles.
/// ///
/// Use [`GraphTopology::vertex_map`] to obtain a `VertexMap`. /// The map does not grow on its own when the associated graph grows. Use the graph's
pub struct VertexMap<V: Copy, T: Clone> { /// [`GraphTopologyAddition::sync_vertex_map`] or [`GraphTopologyAddition::sync_edge_map`] to extend
inner: EntityMap<V, T>, /// the map explicitly after adding vertices or edges to it.
}
impl<V: Copy, T: Clone> VertexMap<V, T> {
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
/// and an initial `capacity`.
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
Self {
inner: EntityMap::new(default, to_index, capacity),
}
}
/// Returns the total number of data entries the map can write without reallocating. Reads
/// beyond `capacity` are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.capacity()
}
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
/// capacity is already sufficient.
///
/// Use this before writing data for new graph vertices to avoid incremental growth on the first
/// write to each new vertex.
pub fn expand(&mut self, capacity: usize) {
self.inner.expand(capacity);
}
#[deprecated(
since = "0.2.4",
note = "use 'expand(graph.vertex_capacity())' instead"
)]
#[allow(missing_docs)]
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
self.inner.expand(graph.vertex_capacity());
}
}
impl<V: Copy, T: Clone> Index<V> for VertexMap<V, T> {
type Output = T;
fn index(&self, v: V) -> &T {
&self.inner[v]
}
}
impl<V: Copy, T: Clone> IndexMut<V> for VertexMap<V, T> {
fn index_mut(&mut self, v: V) -> &mut T {
&mut self.inner[v]
}
}
/// A map to associate custom data to graph edges.
/// ///
/// This map uses raw entity indices to associate homogenous custom data of type `T` to graph edges. /// No bound or validity checks are performed by the map on the provided element handles.
/// The implementation uses a [`Vec`], allocating contiguous slots for the data, which means that
/// the provided index conversion function should map edges to contiguous indices, or indices with
/// relatively few gaps.
/// ///
/// Data allocation happens as copy-on-write, i.e. the backing [`Vec`] is only resized if a value /// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map
/// beyond current capacity is written, but the `default` value can transparently be read. No bound /// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map
/// or validity checks are performed by the map on the provided edge handles. /// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
/// /// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
/// Use [`GraphTopology::edge_map`] to obtain an `EdgeMap`. pub struct ElementMap<E: Copy, T: Clone> {
pub struct EdgeMap<E: Copy, T: Clone> {
inner: EntityMap<E, T>,
}
impl<E: Copy, T: Clone> EdgeMap<E, T> {
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
/// and an initial `capacity`.
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
Self {
inner: EntityMap::new(default, to_index, capacity),
}
}
/// Returns the total number of data entries the map can write without reallocating. Reads
/// beyond `capacity` are valid and return the default value.
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[deprecated(since = "0.2.2", note = "use 'capacity' instead")]
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.capacity()
}
/// Expands the internal data storage capacity of the map to `capacity`. Does nothing if
/// capacity is already sufficient.
///
/// Use this before writing data for new graph edges to avoid incremental growth on the first
/// write to each new edge.
pub fn expand(&mut self, capacity: usize) {
self.inner.expand(capacity);
}
#[deprecated(since = "0.2.4", note = "use 'expand(graph.edge_capacity())' instead")]
#[allow(missing_docs)]
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
self.inner.expand(graph.edge_capacity());
}
}
impl<E: Copy, T: Clone> Index<E> for EdgeMap<E, T> {
type Output = T;
fn index(&self, e: E) -> &T {
&self.inner[e]
}
}
impl<E: Copy, T: Clone> IndexMut<E> for EdgeMap<E, T> {
fn index_mut(&mut self, e: E) -> &mut T {
&mut self.inner[e]
}
}
struct EntityMap<E: Copy, T: Clone> {
data: Vec<T>, data: Vec<T>,
default: T, default: T,
to_index: fn(E) -> usize, to_index: fn(E) -> usize,
} }
impl<E: Copy, T: Clone> EntityMap<E, T> { impl<E: Copy, T: Clone> ElementMap<E, T> {
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { /// 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 { Self {
data: vec![default.clone(); capacity], data: vec![default.clone(); len],
default, default,
to_index, to_index,
} }
} }
pub fn capacity(&self) -> usize { /// Grows the map so that it contains at least `new_len` slots for elements, initialising any
self.data.len() /// 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) { /// Returns a new map over the same elements with values transformed by `f`.
if capacity > self.data.capacity() { ///
self.data.resize(capacity, self.default.clone()); /// 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<usize, Option<i32>> = 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<U: Clone>(&self, f: impl Fn(&T) -> U) -> ElementMap<E, U> {
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<F: Copy + Eq>(
&self,
mapping: &ElementMap<E, F>,
template: &ElementMap<F, ()>,
) -> ElementMap<F, T> {
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<E: Copy, T: Clone> Index<E> for EntityMap<E, T> { impl<E: Copy, T: Clone> Index<E> for ElementMap<E, T> {
type Output = T; type Output = T;
fn index(&self, e: E) -> &T { fn index(&self, e: E) -> &T {
let i = (self.to_index)(e); &self.data[(self.to_index)(e)]
if i < self.data.len() {
&self.data[i]
} else {
&self.default
}
} }
} }
impl<E: Copy, T: Clone> IndexMut<E> for EntityMap<E, T> { impl<E: Copy, T: Clone> IndexMut<E> for ElementMap<E, T> {
fn index_mut(&mut self, e: E) -> &mut T { fn index_mut(&mut self, e: E) -> &mut T {
let i = (self.to_index)(e); &mut self.data[(self.to_index)(e)]
if i >= self.data.len() { }
self.data.resize(i + 1, self.default.clone()); }
}
&mut self.data[i] #[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<usize, Option<i32>> = 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<usize, Option<i32>> = 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::<usize, i32>::new(0, |e| e, 3);
source[0] = 10;
source[1] = 20;
source[2] = 30;
let mut mapping = ElementMap::<usize, u32>::new(u32::MAX, |e| e, 3);
mapping[0] = 0;
mapping[1] = 1;
mapping[2] = 2;
// Reverses the order of the slots.
let template = ElementMap::<u32, ()>::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::<i32>::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"
);
} }
} }
+43 -3
View File
@@ -1,9 +1,49 @@
//! Concrete graph topology models. //! Concrete graph topology models.
use crate::maps::ElementMap;
use crate::traits::{GraphTopology, GraphTopologyAddition};
pub mod append_graph; pub mod append_graph;
pub mod frozen_graph;
pub mod 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}; /// Builds a new graph isomorphic to `source`.
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap}; ///
/// 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<G: GraphTopologyAddition, H: GraphTopology>(
source: &H,
vertex_default: G::Vertex,
mut on_edge: impl FnMut(H::Edge, G::Edge),
) -> (G, ElementMap<H::Vertex, G::Vertex>) {
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)
}
+134 -62
View File
@@ -1,35 +1,53 @@
//! [`AppendGraph`], an undirected graph topology supporting addition only. //! [`AppendGraph`], an undirected graph topology supporting addition only.
use crate::maps::{EdgeMap, VertexMap}; use std::num::NonZeroUsize;
use crate::traits::{GraphTopology, IncidenceCursor};
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`]. /// An opaque handle identifying a vertex in an [`AppendGraph`].
/// ///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like /// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`]. /// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Vertex(usize); 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`]. /// An opaque handle identifying an edge in an [`AppendGraph`].
/// ///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like /// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`]. /// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Edge(usize); pub struct Edge(NonZeroUsize);
/// A [`VertexMap`] for [`AppendGraph`] vertices.
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
/// An [`EdgeMap`] for [`AppendGraph`] edges.
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
impl Edge { 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 { 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<T> = ElementMap<Vertex, T>;
/// An [`ElementMap`] for [`AppendGraph`] edges.
pub type AppendGraphEdgeMap<T> = ElementMap<Edge, T>;
/// An [`Incidence`] for [`AppendGraph`].
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
#[derive(Clone)]
struct VertexIncidenceHeader { struct VertexIncidenceHeader {
incidence_count: usize, incidence_count: usize,
first_incidence: Option<Edge>, first_incidence: Option<Edge>,
@@ -50,10 +68,13 @@ pub struct AppendGraphIncidenceCursor {
} }
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor { impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> { fn next(&mut self, graph: &AppendGraph) -> Option<AppendGraphIncidence> {
graph graph
.step_incidence(&mut self.incidence) .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<AppendGraph> for AppendGraphIncidenceCursor {
/// ///
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it /// `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 /// 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, /// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
/// vertex neighborhood traversals result in scattered index jumps. /// vertex neighborhood traversals result in scattered index jumps.
@@ -73,10 +95,10 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
/// use grapherity::models::AppendGraph; /// use grapherity::models::AppendGraph;
/// ///
/// let mut graph = AppendGraph::new(); /// let mut graph = AppendGraph::new();
/// let v1 = graph.add_vertex(); /// let u = graph.add_vertex();
/// let v2 = graph.add_vertex(); /// let v = graph.add_vertex();
/// let e = graph.add_edge(v1, v2); /// let e = graph.add_edge(u, v);
/// assert!(graph.are_adjacent(v1, v2)); /// assert!(graph.are_adjacent(u, v));
/// ``` /// ```
/// ///
/// # Time and space complexity /// # Time and space complexity
@@ -84,10 +106,18 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)* /// 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|)*. /// 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_vertex`]: Self::add_vertex
/// [`add_edge`]: Self::add_edge /// [`add_edge`]: Self::add_edge
/// [`degree`]: Self::degree /// [`degree`]: Self::degree
/// [`Graph`]: crate::models::Graph /// [`FrozenGraph`]: models::FrozenGraph
/// [`Graph`]: models::Graph
#[derive(Clone)]
pub struct AppendGraph { pub struct AppendGraph {
vertices: Vec<VertexIncidenceHeader>, vertices: Vec<VertexIncidenceHeader>,
incidences: Vec<IncidenceEntry>, incidences: Vec<IncidenceEntry>,
@@ -104,13 +134,13 @@ impl AppendGraph {
/// Adds a single incidence of an edge, which is composed by two such incidences, to the /// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences vector. /// incidences vector.
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { fn add_incidence(&mut self, u: Vertex, v: Vertex) {
self.incidences.push(IncidenceEntry { self.incidences.push(IncidenceEntry {
next: self.vertices[v1.0].first_incidence.take(), next: self.vertices[u.0].first_incidence.take(),
adjacent: v2, adjacent: v,
}); });
self.vertices[v1.0].incidence_count += 1; self.vertices[u.0].incidence_count += 1;
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1)); self.vertices[u.0].first_incidence = Some(Edge::new(self.incidences.len() - 1));
} }
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> { fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
@@ -120,7 +150,7 @@ impl AppendGraph {
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> { fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
let current = (*incidence)?; let current = (*incidence)?;
let entry = self.incidences[current.0]; let entry = self.incidences[current.index()];
*incidence = entry.next; *incidence = entry.next;
Some((entry.adjacent, current)) Some((entry.adjacent, current))
} }
@@ -141,32 +171,24 @@ impl GraphTopology for AppendGraph {
self.vertices.len() self.vertices.len()
} }
fn vertex_capacity(&self) -> usize { fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
self.vertices.capacity() ElementMap::new(default, |v| v.0, self.vertex_capacity())
}
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
VertexMap::new(default, |v| v.0, self.vertex_capacity())
} }
fn edge_count(&self) -> usize { fn edge_count(&self) -> usize {
self.incidences.len() / 2 self.incidences.len() / 2
} }
fn edge_capacity(&self) -> usize { fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
self.incidences.capacity() / 2 ElementMap::new(default, |e| e.index() / 2, self.edge_capacity())
}
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity())
} }
fn degree(&self, v: Self::Vertex) -> usize { fn degree(&self, v: Self::Vertex) -> usize {
self.vertices[v.0].incidence_count self.vertices[v.0].incidence_count
} }
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
self.adjacent_vertices(v1).any(|x| x == v2) self.adjacent_vertices(u).any(|x| x == v)
} }
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> { fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
@@ -178,21 +200,26 @@ impl GraphTopology for AppendGraph {
} }
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
let v2 = self.incidences[e.0].adjacent; let ei = e.index();
let v1 = self.incidences[e.0 ^ 1].adjacent; (
(v1, v2) self.incidences[ei ^ 1].adjacent,
self.incidences[ei].adjacent,
)
} }
fn edges(&self) -> impl Iterator<Item = Self::Edge> { fn edges(&self) -> impl Iterator<Item = Self::Edge> {
(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<Item = Self::Edge> { fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
self.raw_incidences(v).map(|(_, e)| e.normalize()) self.raw_incidences(v).map(|(_, e)| e.normalize())
} }
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> { fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = AppendGraphIncidence> {
self.raw_incidences(v).map(|(v, e)| (v, e.normalize())) self.raw_incidences(v).map(|(v, e)| Incidence {
vertex: v,
edge: e.normalize(),
})
} }
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
@@ -200,6 +227,24 @@ impl GraphTopology for AppendGraph {
incidence: self.vertices[v.0].first_incidence, 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 { fn add_vertex(&mut self) -> Self::Vertex {
self.vertices.push(VertexIncidenceHeader { self.vertices.push(VertexIncidenceHeader {
@@ -209,31 +254,58 @@ impl GraphTopology for AppendGraph {
Vertex(self.vertices.len() - 1) Vertex(self.vertices.len() - 1)
} }
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
self.add_incidence(v1, v2); self.add_incidence(u, v);
self.add_incidence(v2, v1); self.add_incidence(v, u);
Edge(self.incidences.len() - 2) Edge::new(self.incidences.len() - 2)
} }
} }
impl GraphTopologyConversion for AppendGraph {
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
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<H: GraphTopology> From<&H> for AppendGraph {
fn from(source: &H) -> Self {
models::convert_to_addition::<Self, _>(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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::testing::fixtures::MakeTestGraph;
crate::graph_topology_test_fixtures!(AppendGraph);
crate::graph_topology_tests!(AppendGraph);
#[test] #[test]
fn incident_vertices_paired_index() { fn incident_vertices_paired_index() {
let mut graph = AppendGraph::new(); let (graph, [v0, v1], e) = AppendGraph::single_edge();
let v1 = graph.add_vertex(); let f = Edge::new(e.index() + 1);
let v2 = graph.add_vertex(); let (u0, u1) = graph.incident_vertices(f);
let e = graph.add_edge(v1, v2);
let f = Edge(e.0 + 1);
let (u1, u2) = graph.incident_vertices(f);
assert!( assert!(
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
); );
} }
} }
+322
View File
@@ -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<T> = ElementMap<Vertex, T>;
/// An [`ElementMap`] for [`FrozenGraph`] edges.
pub type FrozenGraphEdgeMap<T> = ElementMap<Edge, T>;
/// An [`Incidence`] for [`FrozenGraph`].
pub type FrozenGraphIncidence = Incidence<Vertex, Edge>;
#[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<FrozenGraph> for FrozenGraphIncidenceCursor {
fn next(&mut self, graph: &FrozenGraph) -> Option<FrozenGraphIncidence> {
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<usize>,
incidences: Vec<IncidenceEntry>,
}
impl FrozenGraph {
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = usize> {
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<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
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<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
// "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<Item = Self::Vertex> {
(0..self.vertices.len() - 1).map(Vertex)
}
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex> {
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<Item = Self::Edge> {
self.incidences
.iter()
.enumerate()
.filter_map(|(i, x)| (i < x.opposite).then_some(Edge(i)))
}
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
self.raw_incidences(v).map(|i| self.normalize_edge(i))
}
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = FrozenGraphIncidence> {
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<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
// 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<G: GraphTopology> 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"
);
}
}
}
+158 -69
View File
@@ -1,9 +1,14 @@
//! [`Graph`], an undirected graph topology supporting addition and deletion. //! [`Graph`], an undirected graph topology supporting addition and deletion.
use std::num::NonZeroUsize;
use typed_generational_arena::{Arena, Index}; use typed_generational_arena::{Arena, Index};
use crate::maps::{EdgeMap, VertexMap}; use crate::maps::ElementMap;
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor}; use crate::models;
use crate::traits::{
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
GraphTopologyDeletion, Incidence, IncidenceCursor,
};
/// An opaque handle identifying a vertex in a [`Graph`]. /// An opaque handle identifying a vertex in a [`Graph`].
/// ///
@@ -17,29 +22,43 @@ pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
/// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`]. /// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`].
pub type Edge = Index<IncidenceEntry, usize, usize>; pub type Edge = Index<IncidenceEntry, usize, usize>;
/// A [`VertexMap`] for [`Graph`] vertices. /// An [`ElementMap`] for [`Graph`] vertices.
pub type GraphVertexMap<T> = VertexMap<Vertex, T>; pub type GraphVertexMap<T> = ElementMap<Vertex, T>;
/// An [`EdgeMap`] for [`Graph`] edges. /// An [`ElementMap`] for [`Graph`] edges.
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>; pub type GraphEdgeMap<T> = ElementMap<Edge, T>;
/// An [`Incidence`] for [`Graph`].
pub type GraphIncidence = Incidence<Vertex, Edge>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct VertexSlot(usize); 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)] #[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 impl IncidenceSlot {
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not fn new(index: usize) -> Self {
/// intended for direct external use. 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)] #[doc(hidden)]
#[derive(Clone)]
pub struct VertexIncidenceHeader { pub struct VertexIncidenceHeader {
incidence_count: usize, incidence_count: usize,
first_incidence: Option<IncidenceSlot>, first_incidence: Option<IncidenceSlot>,
} }
/// `pub` because [`Edge`] references it as a type parameter of the underlying arena. Not intended /// Referenced by [`Edge`] as a type parameter of the underlying arena. Not intended for direct
/// for direct external use. /// external use.
#[doc(hidden)] #[doc(hidden)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct IncidenceEntry { pub struct IncidenceEntry {
@@ -66,12 +85,12 @@ pub struct GraphIncidenceCursor {
} }
impl IncidenceCursor<Graph> for GraphIncidenceCursor { impl IncidenceCursor<Graph> for GraphIncidenceCursor {
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> { fn next(&mut self, graph: &Graph) -> Option<GraphIncidence> {
graph.step_incidence(&mut self.incidence).map(|(vs, e)| { graph
( .step_incidence(&mut self.incidence)
graph.vertices.get_idx(vs.0).unwrap(), .map(|(vs, e)| Incidence {
graph.normalize_edge(e), vertex: graph.vertices.get_idx(vs.0).unwrap(),
) edge: graph.normalize_edge(e),
}) })
} }
} }
@@ -113,12 +132,19 @@ impl IncidenceCursor<Graph> for GraphIncidenceCursor {
/// planned, performing deletions first allows freed slots to be reused by subsequent additions, /// planned, performing deletions first allows freed slots to be reused by subsequent additions,
/// potentially avoiding reallocation. /// 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_vertex`]: Self::add_vertex
/// [`add_edge`]: Self::add_edge /// [`add_edge`]: Self::add_edge
/// [`degree`]: Self::degree /// [`degree`]: Self::degree
/// [`delete_vertex`]: Self::delete_vertex /// [`delete_vertex`]: Self::delete_vertex
/// [`delete_edge`]: Self::delete_edge /// [`delete_edge`]: Self::delete_edge
/// [`AppendGraph`]: crate::models::AppendGraph /// [`AppendGraph`]: models::AppendGraph
#[derive(Clone)]
pub struct Graph { pub struct Graph {
// TODO: Arena index and generation types could be externalized to Graph. // TODO: Arena index and generation types could be externalized to Graph.
vertices: Arena<VertexIncidenceHeader, usize, usize>, vertices: Arena<VertexIncidenceHeader, usize, usize>,
@@ -136,13 +162,13 @@ impl Graph {
/// Adds a single incidence of an edge, which is composed by two such incidences, to the /// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences arena, and returns its index. /// 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 { let edge = self.incidences.insert(IncidenceEntry {
next: self.vertices[v1].first_incidence.take(), next: self.vertices[u].first_incidence.take(),
adjacent: VertexSlot(v2.arr_idx()), adjacent: VertexSlot(v.arr_idx()),
}); });
self.vertices[v1].incidence_count += 1; self.vertices[u].incidence_count += 1;
self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); self.vertices[u].first_incidence = Some(IncidenceSlot::new(edge.arr_idx()));
edge edge
} }
@@ -181,7 +207,7 @@ impl Graph {
let first = vertex_header let first = vertex_header
.first_incidence .first_incidence
.expect("incident vertex without incidences, corrupt internal data state"); .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; vertex_header.first_incidence = next;
} else { } else {
let graph: &Graph = self; let graph: &Graph = self;
@@ -190,7 +216,7 @@ impl Graph {
.find(|(_, f)| { .find(|(_, f)| {
graph.incidences[*f] graph.incidences[*f]
.next .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"); .expect("cannot find previous incidence, corrupt internal data state");
self.incidences[previous].next = next; self.incidences[previous].next = next;
@@ -205,7 +231,7 @@ impl Graph {
fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> { fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> {
// TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries). // TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries).
let current = (*incidence)?; 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]; let entry = self.incidences[e];
*incidence = entry.next; *incidence = entry.next;
Some((entry.adjacent, e)) Some((entry.adjacent, e))
@@ -236,32 +262,24 @@ impl GraphTopology for Graph {
self.vertices.len() self.vertices.len()
} }
fn vertex_capacity(&self) -> usize { fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
self.vertices.capacity() ElementMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
}
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
} }
fn edge_count(&self) -> usize { fn edge_count(&self) -> usize {
self.incidences.len() / 2 self.incidences.len() / 2
} }
fn edge_capacity(&self) -> usize { fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
self.incidences.capacity() / 2 ElementMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
}
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
} }
fn degree(&self, v: Self::Vertex) -> usize { fn degree(&self, v: Self::Vertex) -> usize {
self.vertices[v].incidence_count self.vertices[v].incidence_count
} }
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
self.adjacent_vertices(v1).any(|x| x == v2) self.adjacent_vertices(u).any(|x| x == v)
} }
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> { fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
@@ -274,16 +292,16 @@ impl GraphTopology for Graph {
} }
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
let v2 = self let v = self
.vertices .vertices
.get_idx(self.incidences[e].adjacent.0) .get_idx(self.incidences[e].adjacent.0)
.unwrap(); .unwrap();
let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap(); let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap();
let v1 = self let u = self
.vertices .vertices
.get_idx(self.incidences[f].adjacent.0) .get_idx(self.incidences[f].adjacent.0)
.unwrap(); .unwrap();
(v1, v2) (u, v)
} }
fn edges(&self) -> impl Iterator<Item = Self::Edge> { fn edges(&self) -> impl Iterator<Item = Self::Edge> {
@@ -297,9 +315,11 @@ impl GraphTopology for Graph {
self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e)) self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e))
} }
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> { fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = GraphIncidence> {
self.raw_incidences(v) self.raw_incidences(v).map(|(vs, e)| Incidence {
.map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) vertex: self.vertices.get_idx(vs.0).unwrap(),
edge: self.normalize_edge(e),
})
} }
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
@@ -307,6 +327,24 @@ impl GraphTopology for Graph {
incidence: self.vertices[v].first_incidence, 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 { fn add_vertex(&mut self) -> Self::Vertex {
self.vertices.insert(VertexIncidenceHeader { 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 { fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
let first = self.add_incidence(v1, v2); let first = self.add_incidence(u, v);
self.add_incidence(v2, v1); self.add_incidence(v, u);
first first
} }
} }
@@ -352,7 +390,7 @@ impl GraphTopologyDeletion for Graph {
if e_entry.adjacent != f_entry.adjacent { if e_entry.adjacent != f_entry.adjacent {
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false); self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
self.update_incidence_list(f, e_entry.adjacent, f_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); self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
} else { } else {
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true); 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<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
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<H: GraphTopology> From<&H> for Graph {
fn from(source: &H) -> Self {
models::convert_to_addition::<Self, _>(
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::testing::fixtures::MakeTestGraph;
crate::graph_topology_test_fixtures!(Graph);
crate::graph_topology_tests!(Graph);
crate::graph_topology_deletion_tests!(Graph);
#[test] #[test]
fn incident_vertices_paired_index() { fn incident_vertices_paired_index() {
let mut graph = Graph::new(); let (graph, [v0, v1], e) = Graph::single_edge();
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let e = graph.add_edge(v1, v2);
let f = graph let f = graph
.incidences .incidences
.get_idx(e.arr_idx() + 1) .get_idx(e.arr_idx() + 1)
.expect("paired index should be valid"); .expect("paired index should be valid");
let (u1, u2) = graph.incident_vertices(f); let (u0, u1) = graph.incident_vertices(f);
assert!( assert!(
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), (u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" "unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
); );
} }
#[test] #[test]
fn delete_edge_paired_index() { fn delete_edge_paired_index() {
let mut graph = Graph::new(); let (mut graph, _, e) = Graph::single_edge();
let v1 = graph.add_vertex();
let v2 = graph.add_vertex();
let e = graph.add_edge(v1, v2);
let f = graph let f = graph
.incidences .incidences
.get_idx(e.arr_idx() + 1) .get_idx(e.arr_idx() + 1)
@@ -401,9 +471,7 @@ mod tests {
#[test] #[test]
fn delete_edge_loop_paired_index() { fn delete_edge_loop_paired_index() {
let mut graph = Graph::new(); let (mut graph, _, e) = Graph::loop_edge();
let v = graph.add_vertex();
let e = graph.add_edge(v, v);
let f = graph let f = graph
.incidences .incidences
.get_idx(e.arr_idx() + 1) .get_idx(e.arr_idx() + 1)
@@ -411,4 +479,25 @@ mod tests {
graph.delete_edge(f); graph.delete_edge(f);
assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); 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"
);
}
} }
+12 -6
View File
@@ -1,8 +1,14 @@
//! Test fixture and test macros for graph topology and algorithm implementations. //! Test fixture and test macros for graph topology and algorithm implementations.
pub(crate) mod bfs_testing; pub mod fixtures;
pub(crate) mod dfs_testing; pub mod from_testing;
pub(crate) mod dijkstra_testing; pub mod graph_topology_addition_clone_testing;
pub(crate) mod find_path_testing; pub mod graph_topology_addition_conversion_testing;
pub(crate) mod graph_topology_testing; pub mod graph_topology_addition_deletion_conversion_testing;
pub(crate) mod maps_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;
-258
View File
@@ -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<u32>,
>,
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]
);
}
}
};
}
-206
View File
@@ -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"
);
}
}
};
}
-314
View File
@@ -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<u32>,
>,
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<u32>,
>,
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<u32>,
>,
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<u32>,
>,
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)
}
};
}
-124
View File
@@ -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:?}"
);
}
};
}
+493
View File
@@ -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<Incidence<Self::Vertex, Self::Edge>>; 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<const K: usize>() -> (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<G: GraphTopologyAddition> MakeTestGraph for G {
fn standard() -> (
Self,
[Self::Vertex; 10],
[(Self::Edge, Self::Vertex, Self::Vertex); 18],
[Vec<Incidence<Self::Vertex, Self::Edge>>; 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<const K: usize>() -> (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<Incidence<Self::Vertex, Self::Edge>>; 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<const K: usize>() -> (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<V, F>(actual_for: F, vertices: &[V])
where
V: Debug + Copy,
F: Fn(V) -> Option<u32>,
{
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<V: Debug + Copy + PartialEq>(
actual: &ElementMap<V, Option<V>>,
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<G: GraphTopology>(
graph: &G,
path_option: Option<Vec<G::Edge>>,
source: G::Vertex,
targets: &[G::Vertex],
) -> Vec<G::Edge>
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(&current),
"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<G: GraphTopology, H: GraphTopology>(
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<G: GraphTopologyAddition, H: GraphTopologyAddition>(
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"
);
}
+38
View File
@@ -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<G, H>()
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");
}
@@ -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<G: Clone + GraphTopologyAddition>() {
let (mut graph, vertices, _) = G::single_edge();
let mut clone = graph.clone();
fixtures::assert_mutation_independent(&mut graph, &mut clone, &vertices, |v| v);
}
@@ -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<G, H>()
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<G, H>()
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<G: GraphTopologyAddition>(graph: &mut G) -> Vec<G::Vertex> {
let capacity = graph.vertex_capacity();
let mut added = Vec::new();
while graph.vertex_capacity() == capacity {
added.push(graph.add_vertex());
}
added
}
@@ -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<G, H>()
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),
);
}
@@ -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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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");
}
@@ -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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>()
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<G: GraphTopologyAddition>()
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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<G: GraphTopologyAddition>() {
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");
}
@@ -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<G: Clone + MakeTestGraph>()
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);
}
@@ -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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>()
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<G: GraphTopologyConversion, H: MakeTestGraph>() {
// 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));
}
@@ -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<G, H>()
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),
);
}
@@ -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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
const K: usize = 2;
let (mut graph, [v0, v1], edges) = G::multiple_edges::<K>();
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<G: GraphTopologyDeletion + GraphTopologyAddition>() {
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopologyDeletion + GraphTopologyAddition>()
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<G: GraphTopology>(
graph: &G,
v: G::Vertex,
mut expected: Vec<Incidence<G::Vertex, G::Edge>>,
) 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<G: GraphTopology>(
graph: &G,
v: G::Vertex,
mut expected: Vec<Incidence<G::Vertex, G::Edge>>,
) 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<G: GraphTopology>(
graph: &G,
v: G::Vertex,
expected: &mut Vec<G::Edge>,
) 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"
);
}
File diff suppressed because it is too large Load Diff
-251
View File
@@ -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);
}
};
}
+320 -36
View File
@@ -1,15 +1,12 @@
//! Core traits for undirected graph topologies. //! 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. /// A trait representing an undirected graph topology.
/// ///
/// An undirected graph is a set of vertices and undirected edges, where each edge connects either /// 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 /// 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 /// # 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 /// 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, 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`]. /// [`GraphTopologyDeletion`].
/// ///
/// [`Edge`]: GraphTopology::Edge /// [`Edge`]: GraphTopology::Edge
@@ -41,15 +39,17 @@ pub trait GraphTopology {
/// when an algorithm needs to pause traversal, perform other graph queries or mutations, and /// 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 /// then continue from where it left off. This cursor type can be obtained via
/// [`incidence_cursor`](Self::incidence_cursor). /// [`incidence_cursor`](Self::incidence_cursor).
type IncidenceCursor: IncidenceCursor<Self> + Copy; type IncidenceCursor: IncidenceCursor<Self>;
/// Returns the number of vertices in the graph. /// Returns the number of vertices in the graph.
fn vertex_count(&self) -> usize; fn vertex_count(&self) -> usize;
/// Returns the total number of vertices the graph can hold without reallocating. /// Creates and returns an [`ElementMap`] for the vertices with every slot initialised to
fn vertex_capacity(&self) -> usize; /// `default`.
///
/// Creates and returns a [`VertexMap`] 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 /// # Examples
/// ///
@@ -58,26 +58,24 @@ pub trait GraphTopology {
/// # use grapherity::models::Graph; /// # use grapherity::models::Graph;
/// let mut graph = Graph::new(); /// let mut graph = Graph::new();
/// let v1 = graph.add_vertex(); /// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let mut labels = graph.vertex_map("z"); /// let mut labels = graph.vertex_map("z");
/// assert_eq!(labels[v1], "z"); /// assert_eq!(labels[v1], "z");
/// labels[v1] = "a"; /// labels[v1] = "a";
/// assert_eq!(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"); /// assert_eq!(labels[v2], "z");
/// labels[v2] = "b"; /// labels[v2] = "b";
/// assert_eq!(labels[v2], "b"); /// assert_eq!(labels[v2], "b");
/// ``` /// ```
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T>; fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T>;
/// Returns the number of edges in the graph. /// Returns the number of edges in the graph.
fn edge_count(&self) -> usize; fn edge_count(&self) -> usize;
/// Returns the total number of edges the graph can hold without reallocating. /// Creates and returns an [`ElementMap`] for the edges with every slot initialised to `default`.
fn edge_capacity(&self) -> usize; ///
/// The returned map only covers the edges that exist in the graph at the time of the call. Use
/// Creates and returns an [`EdgeMap`] with every slot initialised to `default`. /// [`GraphTopologyAddition::sync_edge_map`] to extend the map before accessing new edges.
/// ///
/// # Examples /// # Examples
/// ///
@@ -88,18 +86,16 @@ pub trait GraphTopology {
/// let v1 = graph.add_vertex(); /// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex(); /// let v2 = graph.add_vertex();
/// let e1 = graph.add_edge(v1, v2); /// let e1 = graph.add_edge(v1, v2);
/// let e2 = graph.add_edge(v1, v2);
/// let mut weights = graph.edge_map(5); /// let mut weights = graph.edge_map(5);
/// assert_eq!(weights[e1], 5); /// assert_eq!(weights[e1], 5);
/// weights[e1] = 1; /// weights[e1] = 1;
/// assert_eq!(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); /// assert_eq!(weights[e2], 5);
/// weights[e2] = 2; /// weights[e2] = 2;
/// assert_eq!(weights[e2], 2); /// assert_eq!(weights[e2], 2);
/// ``` /// ```
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T>; fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T>;
/// Returns the degree of `v`, i.e. the number of incident edges, where each loop contributes 2. /// 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. /// Panics if `v` is not a valid vertex of this graph.
fn degree(&self, v: Self::Vertex) -> usize; 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. /// A vertex is adjacent to itself if and only if it has a loop edge.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `v1` or `v2` is not a valid vertex of this graph. /// Panics if `u` or `v` is not a valid vertex of this graph.
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool;
/// Returns an iterator over all vertices in the graph. /// Returns an iterator over all vertices in the graph.
fn vertices(&self) -> impl Iterator<Item = Self::Vertex>; fn vertices(&self) -> impl Iterator<Item = Self::Vertex>;
@@ -164,7 +160,10 @@ pub trait GraphTopology {
/// # Panics /// # Panics
/// ///
/// Panics if `v` is not a valid vertex of this graph. /// Panics if `v` is not a valid vertex of this graph.
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)>; fn incidences(
&self,
v: Self::Vertex,
) -> impl Iterator<Item = Incidence<Self::Vertex, Self::Edge>>;
/// Returns a cursor over all incidences of `v`, analogously to /// Returns a cursor over all incidences of `v`, analogously to
/// [`incidences`](Self::incidences), initially positioned before the first one. /// [`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. /// Panics if `v` is not a valid vertex of this graph.
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor; 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. /// Adds a new isolated vertex and returns its handle.
fn add_vertex(&mut self) -> Self::Vertex; 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
/// ///
/// Panics if `v1` or `v2` is not a valid vertex of this graph. /// Panics if `u` or `v` is not a valid vertex of this graph.
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; 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<T: Clone>(&self, map: &mut ElementMap<Self::Vertex, T>) {
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<T: Clone>(&self, map: &mut ElementMap<Self::Edge, T>) {
map.grow(self.edge_capacity());
}
} }
/// A trait that adds deletion operations to an undirected graph topology. /// 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. /// 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 /// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See
/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over /// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over
/// [`GraphTopology::incidences`]. /// [`GraphTopology::incidences`].
pub trait IncidenceCursor<G: GraphTopology + ?Sized> { pub trait IncidenceCursor<G: GraphTopology + ?Sized>: Copy {
/// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the /// Advances the cursor and returns the next incidence, or `None` if the traversal is
/// traversal is exhausted. /// exhausted.
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # use grapherity::prelude::*; /// # use grapherity::prelude::*;
/// # use grapherity::models::Graph; /// # use grapherity::models::Graph;
/// # use grapherity::traits::IncidenceCursor;
/// // Constructs a graph with two vertices connected to `v`. /// // Constructs a graph with two vertices connected to `v`.
/// let mut graph = Graph::new(); /// let mut graph = Graph::new();
/// let v = graph.add_vertex(); /// let v = graph.add_vertex();
@@ -245,5 +347,187 @@ pub trait IncidenceCursor<G: GraphTopology + ?Sized> {
/// assert!(c2.next(&graph).is_some()); /// assert!(c2.next(&graph).is_some());
/// assert!(c2.next(&graph).is_none()); /// 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<Incidence<G::Vertex, G::Edge>>;
}
/// 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<V, E> {
/// 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<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>)
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<G: GraphTopology, H: GraphTopology> {
vertices: ElementMap<G::Vertex, H::Vertex>,
edges: ElementMap<G::Edge, H::Edge>,
vertex_map_template: ElementMap<H::Vertex, ()>,
edge_map_template: ElementMap<H::Edge, ()>,
}
impl<G: GraphTopology, H: GraphTopology> GraphIsomorphism<G, H> {
/// 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<G::Vertex, H::Vertex>,
edges: ElementMap<G::Edge, H::Edge>,
) -> 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<T: Clone>(
&self,
map: &ElementMap<G::Vertex, T>,
) -> ElementMap<H::Vertex, T> {
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<T: Clone>(
&self,
map: &ElementMap<G::Edge, T>,
) -> ElementMap<H::Edge, T> {
map.remap(&self.edges, &self.edge_map_template)
}
} }
+341 -6
View File
@@ -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; use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph); bfs_tests!(AppendGraph);
grapherity::bfs_tests!(AppendGraph);
} }
mod graph_tests { mod graph {
use grapherity::models::Graph; use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph); bfs_tests!(Graph);
grapherity::bfs_tests!(Graph); }
fn bfs_single_vertex<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>() {
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>() {
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<G: MakeTestGraph>() {
let (graph, [v0, v1, _]) = G::disconnected();
assert_eq!(
algorithms::bfs_find(&graph, v0, v1),
None,
"disconnected target should not be found"
);
}
fn bfs_find<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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"
);
} }
+351 -6
View File
@@ -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; use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph); dfs_tests!(AppendGraph);
grapherity::dfs_tests!(AppendGraph);
} }
mod graph_tests { mod graph {
use grapherity::models::Graph; use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph); dfs_tests!(Graph);
grapherity::dfs_tests!(Graph); }
fn dfs_single_vertex<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
where
G::Vertex: Debug,
{
let (graph, vertices, _, _) = G::standard();
let result = algorithms::dfs(&graph, vertices[0]);
assert_dfs_visited::<G>(&result.visited, &vertices);
assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices);
}
fn dfs_visited_single_vertex<G: MakeTestGraph>() {
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
where
G::Vertex: Debug,
{
let (graph, vertices, _, _) = G::standard();
let visited = algorithms::dfs_visited(&graph, vertices[0]);
assert_dfs_visited::<G>(&visited, &vertices);
}
fn dfs_find_source<G: MakeTestGraph>() {
let (graph, v) = G::single_vertex();
assert!(
algorithms::dfs_find(&graph, v, v),
"source should find itself"
);
}
fn dfs_find_disconnected<G: MakeTestGraph>() {
let (graph, [v0, v1, _]) = G::disconnected();
assert!(
!algorithms::dfs_find(&graph, v0, v1),
"disconnected target should not be found"
);
}
fn dfs_find<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: MakeTestGraph>()
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<G: GraphTopology>(
visited: &ElementMap<G::Vertex, bool>,
vertices: &[G::Vertex],
) where
G::Vertex: Debug,
{
for &v in vertices {
assert!(visited[v], "vertex {v:?} should be visited");
}
}
fn assert_dfs_predecessors<G: GraphTopology>(
graph: &G,
visited: &ElementMap<G::Vertex, bool>,
predecessors: &ElementMap<G::Vertex, Option<G::Vertex>>,
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"
);
}
} }
+290 -6
View File
@@ -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; use grapherity::models::AppendGraph;
grapherity::graph_topology_test_fixtures!(AppendGraph); dijkstra_tests!(AppendGraph);
grapherity::dijkstra_tests!(AppendGraph);
} }
mod graph_tests { mod graph {
use grapherity::models::Graph; use grapherity::models::Graph;
grapherity::graph_topology_test_fixtures!(Graph); dijkstra_tests!(Graph);
grapherity::dijkstra_tests!(Graph); }
fn dijkstra_single_vertex<G: MakeTestGraph>()
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::<G>(&result, v);
}
fn dijkstra_disconnected<G: MakeTestGraph>()
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::<G>(&result, vertices[0]);
assert_disconnected::<G>(&result, &vertices[1..3]);
}
fn dijkstra_zero_weight_loop<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, vertices, weights) = make_two_edge_path_with_zero_weight_loops::<G>();
let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]);
assert_single_vertex::<G>(&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<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, vertices, weights) = make_standard_weighted::<G>();
let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]);
assert_standard_weighted_distances_v0::<G>(&result.distances, &vertices);
assert_standard_weighted_predecessors_v0::<G>(&result.predecessors, &vertices);
}
fn dijkstra_distances<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, vertices, weights) = make_standard_weighted::<G>();
let distances = algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]);
assert_standard_weighted_distances_v0::<G>(&distances, &vertices);
}
fn dijkstra_unweighted_single_vertex<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, v) = G::single_vertex();
let result = algorithms::dijkstra_unweighted(&graph, v);
assert_single_vertex::<G>(&result, v)
}
fn dijkstra_unweighted_disconnected<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, vertices) = G::disconnected();
let result = algorithms::dijkstra_unweighted(&graph, vertices[0]);
assert_single_vertex::<G>(&result, vertices[0]);
assert_disconnected::<G>(&result, &vertices[1..3]);
}
fn dijkstra_unweighted<G: MakeTestGraph>()
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<G: MakeTestGraph>()
where
G::Vertex: Hash,
{
let (graph, v) = G::single_vertex();
let distances = algorithms::dijkstra_distances_unweighted(&graph, v);
assert_distances_single_vertex::<G>(&distances, v);
}
fn dijkstra_distances_unweighted_disconnected<G: MakeTestGraph>()
where
G::Vertex: Debug + Hash,
{
let (graph, vertices) = G::disconnected();
let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]);
assert_distances_single_vertex::<G>(&distances, vertices[0]);
assert_distances_disconnected::<G>(&distances, &vertices[1..3]);
}
fn dijkstra_distances_unweighted<G: MakeTestGraph>()
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<G: GraphTopology>(result: &DijkstraResult<G::Vertex, u32>, v: G::Vertex)
where
G::Vertex: Debug,
{
assert_distances_single_vertex::<G>(&result.distances, v);
assert_eq!(
result.predecessors[v], None,
"unexpected predecessor of source vertex",
);
}
fn assert_distances_single_vertex<G: GraphTopology>(
distances: &ElementMap<G::Vertex, Option<u32>>,
v: G::Vertex,
) {
assert_eq!(
distances[v],
Some(0),
"unexpected distance of source vertex"
);
}
fn assert_disconnected<G: GraphTopology>(
result: &DijkstraResult<G::Vertex, u32>,
vertices: &[G::Vertex],
) where
G::Vertex: Debug,
{
assert_distances_disconnected::<G>(&result.distances, vertices);
for &v in vertices {
assert_eq!(
result.predecessors[v], None,
"unexpected predecessor of disconnected vertex {v:?}",
);
}
}
fn assert_distances_disconnected<G: GraphTopology>(
distances: &ElementMap<G::Vertex, Option<u32>>,
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<G: GraphTopology>(
actual: &ElementMap<G::Vertex, Option<G::Vertex>>,
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<G: GraphTopology>(
actual: &ElementMap<G::Vertex, Option<u32>>,
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: MakeTestGraph>() -> (G, [G::Vertex; 10], ElementMap<G::Edge, u32>) {
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: MakeTestGraph>()
-> (G, [G::Vertex; 3], ElementMap<G::Edge, u32>) {
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)
} }
-13
View File
@@ -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);
}
-25
View File
@@ -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);
}