Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b75da01af3 | ||
|
|
1d1ca3bd6a | ||
|
|
90a2bd9e9e | ||
|
|
7196871a96 | ||
|
|
dd0ae64984 | ||
|
|
9ae841288b | ||
|
|
eab5a63e6c | ||
|
|
653cd39279 | ||
|
|
3d1e8246a8 | ||
|
|
7cfee9cce5 | ||
|
|
b124e58b38 | ||
|
|
1979d33649 | ||
|
|
3d7dda6f7b | ||
|
|
4e20c49486 | ||
|
|
b943dddf1b | ||
|
|
c8855daddf | ||
|
|
9d43f0cb8d | ||
|
|
8bf196d1ad | ||
|
|
3478274959 | ||
|
|
a9190f88db | ||
|
|
d438a4288a | ||
|
|
0dea90ceff | ||
|
|
64c48287a7 | ||
|
|
345dbc0d5d | ||
|
|
038e984c97 | ||
|
|
e593298eba | ||
|
|
4c6a8a43e4 | ||
|
|
016d127bd1 | ||
|
|
8c3c6abc78 | ||
|
|
3ad448974e | ||
|
|
1209d75e15 | ||
|
|
c25a8a4fc1 | ||
|
|
73af8a16ca | ||
|
|
a8b6f7591c | ||
|
|
8a1d491943 | ||
|
|
5825ef3b47 | ||
|
|
0ff830ef48 | ||
|
|
df5170f2f5 | ||
|
|
6d3a206995 | ||
|
|
95601a7a5a | ||
|
|
7fbd5eed9d | ||
|
|
1185e3b667 | ||
|
|
cbabb8f960 | ||
|
|
a89086253a | ||
|
|
5085b7795b | ||
|
|
cd1d2cddee | ||
|
|
3d99501d4b | ||
|
|
b4374174a9 | ||
|
|
59d9c2b8a7 | ||
|
|
a7346bd782 | ||
|
|
7d8c9aaa10 | ||
|
|
10d0c382cc | ||
|
|
2851cf60fd | ||
|
|
110e6310aa | ||
|
|
53a6bed932 | ||
|
|
afaf161344 | ||
|
|
0169343ac8 | ||
|
|
95f9b06088 | ||
|
|
b752e3f9d4 | ||
|
|
1b79809e2f | ||
|
|
242aba09e8 | ||
|
|
1b8df19d00 | ||
|
|
517b83b328 | ||
|
|
dd4e959554 | ||
|
|
cf1dafceb7 | ||
|
|
9a1e2faa9a | ||
|
|
08b2c4db65 | ||
|
|
dfa8f8f0df | ||
|
|
43c4ba45a2 | ||
|
|
99bab32f8c | ||
|
|
6e80b2fc9a | ||
|
|
b5cc3b1c3f | ||
|
|
21e79c660f | ||
|
|
399b36bb16 | ||
|
|
9f76ce4ce3 | ||
|
|
d9dc102993 | ||
|
|
3708c1e0d1 | ||
|
|
9608796f23 | ||
|
|
c253e4a840 | ||
|
|
d711e68577 | ||
|
|
8db7a86187 | ||
|
|
3fa4b66981 | ||
|
|
ad9850800d | ||
|
|
a14202effc | ||
|
|
8f9f0bf4c8 | ||
|
|
5fa415841e | ||
|
|
e8568a084a | ||
|
|
e6b08d9148 | ||
|
|
b62c263b08 | ||
|
|
7345684f00 | ||
|
|
2c53d92943 | ||
|
|
913a9e0baa | ||
|
|
8c6c98859c | ||
|
|
24c08438d6 | ||
|
|
bf6a7142f3 | ||
|
|
c131f520f4 | ||
|
|
b6c27ea605 | ||
|
|
5b30151010 | ||
|
|
2f549cc506 | ||
|
|
cf31ee2f01 | ||
|
|
e58261ba47 | ||
|
|
fb9dfe8280 | ||
|
|
818b37ec15 |
+2
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grapherity"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Stefan Müller"]
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
@@ -11,4 +11,5 @@ keywords = ["graph", "graph-algorithms"]
|
||||
categories = ["algorithms", "data-structures", "mathematics"]
|
||||
|
||||
[dependencies]
|
||||
priority-queue = "2.7.0"
|
||||
typed-generational-arena = "0.2.9"
|
||||
|
||||
@@ -1,14 +1,69 @@
|
||||
# grapherity
|
||||
|
||||
Library for graph models and algorithms.
|
||||
Graph models and algorithms.
|
||||
|
||||
This library is still experimental and its API may therefore change frequently.
|
||||
This crate provides data structures to model graphs and to perform algorithms on these data structures. The functionality presented is designed to be easy to use, flexible, and performant.
|
||||
|
||||
Currently supported are:
|
||||
|
||||
* Undirected graph types `Graph`, `AppendGraph`, and `FrozenGraph`, built on a flat, index-based adjacency list,
|
||||
* `ElementMap` to freely associate custom data with vertices and edges,
|
||||
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
|
||||
* Exposed traits to implement custom graph data structures or algorithms.
|
||||
|
||||
For more information, see the [documentation](https://docs.rs/grapherity/latest/grapherity/).
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
// Brings commonly used traits into scope.
|
||||
use grapherity::prelude::*;
|
||||
use grapherity::models::Graph;
|
||||
use grapherity::algorithms;
|
||||
|
||||
let mut graph = Graph::new();
|
||||
// Adds three vertices and two edges.
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let v3 = graph.add_vertex();
|
||||
graph.add_edge(v1, v2);
|
||||
let e = graph.add_edge(v2, v3);
|
||||
// Creates a map for edge weights with a default weight, after creating the edge.
|
||||
let mut weights = graph.edge_map(1);
|
||||
// Sets a non-default weight for the second edge.
|
||||
weights[e] = 5;
|
||||
|
||||
// Checks the number of vertices.
|
||||
assert_eq!(graph.vertex_count(), 3);
|
||||
// Checks the number of edges.
|
||||
assert_eq!(graph.edge_count(), 2);
|
||||
// Checks that v1 and v2 are adjacent.
|
||||
assert!(graph.are_adjacent(v1, v2));
|
||||
// Checks that v1 and v3 are not adjacent.
|
||||
assert!(!graph.are_adjacent(v1, v3));
|
||||
// Checks the sum of all vertex degrees.
|
||||
let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
|
||||
assert_eq!(sum, 1 + 2 + 1);
|
||||
|
||||
// Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
|
||||
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
|
||||
assert_eq!(result.distances[v3], Some(1 + 5));
|
||||
assert_eq!(result.predecessors[v3], Some(v2));
|
||||
|
||||
// Deletes the middle vertex and its incident edges.
|
||||
graph.delete_vertex(v2);
|
||||
|
||||
// Calls Dijkstra's algorithm again on the now disconnected graph.
|
||||
let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
|
||||
assert_eq!(result.distances[v3], None);
|
||||
assert_eq!(result.predecessors[v3], None);
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is dual-licensed under the terms of both:
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit)
|
||||
|
||||
You may choose either license for your use of this software.
|
||||
|
||||
+678
-94
@@ -1,40 +1,122 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BinaryHeap, VecDeque};
|
||||
//! Algorithms for graph topologies.
|
||||
//!
|
||||
//! # Dijkstra's algorithm
|
||||
//!
|
||||
//! [Dijkstra's algorithm] finds the shortest distances from a source vertex to all other vertices
|
||||
//! in the graph. Note that for unweighted graphs it has worse time and space complexity than
|
||||
//! [Breadth-first search](#breadth-first-search).
|
||||
//!
|
||||
//! Variants: [`dijkstra`], [`dijkstra_distances`], [`dijkstra_unweighted`],
|
||||
//! [`dijkstra_distances_unweighted`].
|
||||
//!
|
||||
//! # Breadth-first search
|
||||
//!
|
||||
//! [Breadth-first search] traverses a graph topology, exploring all neighboring vertices first
|
||||
//! before descending further into their neighborhoods.
|
||||
//!
|
||||
//! Variants: [`bfs`], [`bfs_distances`], [`bfs_find`], [`bfs_find_where`].
|
||||
//!
|
||||
//! # Depth-first search
|
||||
//!
|
||||
//! [Depth-first search] traverses a graph topology, exploring each branch path as far as possible
|
||||
//! first before backtracking and exploring other branches.
|
||||
//!
|
||||
//! Variants: [`dfs`], [`dfs_visited`], [`dfs_find`], [`dfs_find_where`], [`dfs_find_path`],
|
||||
//! [`dfs_find_path_where`].
|
||||
//!
|
||||
//! [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
//! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
//! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
|
||||
use crate::maps::VertexMap;
|
||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
||||
use priority_queue::PriorityQueue;
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Add;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct DistanceOrderedVertex<V> {
|
||||
distance: u32,
|
||||
vertex: V,
|
||||
use crate::maps::ElementMap;
|
||||
use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
|
||||
|
||||
/// Trait that edge weights for [`dijkstra`] and its variants need to satisfy.
|
||||
///
|
||||
/// [`Default`] is used as the starting distance for `source`, so it must behave as the additive
|
||||
/// identity (typically zero) for accumulation via [`Add`] to give correct results.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dijkstra;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
/// let mut travel_time = graph.edge_map(Duration::ZERO);
|
||||
/// travel_time[e] = Duration::from_secs(5);
|
||||
///
|
||||
/// let result = dijkstra(&graph, source, |e| travel_time[e]);
|
||||
/// assert_eq!(result.distances[target], Some(Duration::from_secs(5)));
|
||||
/// ```
|
||||
pub trait Weight: Copy + Ord + Add<Output = Self> + Default {}
|
||||
|
||||
impl<T: Copy + Ord + Add<Output = T> + Default> Weight for T {}
|
||||
|
||||
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
|
||||
pub struct DijkstraResult<V: Copy, W: Weight> {
|
||||
/// Vertex map of minimum distances from a given `source` vertex.
|
||||
pub distances: ElementMap<V, Option<W>>,
|
||||
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
|
||||
pub predecessors: ElementMap<V, Option<V>>,
|
||||
}
|
||||
|
||||
impl<V: Eq> PartialOrd for DistanceOrderedVertex<V> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DijkstraResult<V: Copy> {
|
||||
pub distances: VertexMap<V, Option<u32>>,
|
||||
pub predecessors: VertexMap<V, Option<V>>,
|
||||
}
|
||||
|
||||
// TODO: Generalize the return type of the weight function.
|
||||
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex>
|
||||
// TODO: A Fibonacci heap would lower complexity to O(|E| + |V| log |V|) by making decrease-key O(1) amortized instead of O(log |V|). No standard Rust implementation exists; high constant factors may negate the asymptotic gain in practice.
|
||||
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors.
|
||||
///
|
||||
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
|
||||
/// by `weights` function. Returns the distances from `source` to each vertex, and the predecessors
|
||||
/// of each vertex on some shortest path from `source` to that vertex. Returns `None` for any vertex
|
||||
/// not connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dijkstra;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
/// let mut weights = graph.edge_map(1);
|
||||
/// weights[e] = 5;
|
||||
///
|
||||
/// let result = dijkstra(&graph, source, |e| weights[e]);
|
||||
/// assert_eq!(result.distances[target], Some(5));
|
||||
/// assert_eq!(result.predecessors[target], Some(source));
|
||||
/// ```
|
||||
///
|
||||
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
pub fn dijkstra<G, W>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weights: impl Fn(G::Edge) -> W,
|
||||
) -> DijkstraResult<G::Vertex, W>
|
||||
where
|
||||
G: GraphTopology,
|
||||
W: Fn(G::Edge) -> u32,
|
||||
G::Vertex: Hash,
|
||||
W: Weight,
|
||||
{
|
||||
let mut predecessors = graph.vertex_map(None);
|
||||
let distances = dijkstra_impl(graph, source, weight, |adjacent, predecessor| {
|
||||
let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| {
|
||||
predecessors[adjacent] = Some(predecessor);
|
||||
});
|
||||
DijkstraResult {
|
||||
@@ -43,31 +125,125 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
|
||||
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances.
|
||||
///
|
||||
/// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given
|
||||
/// by `weights` function. Returns the distances from `source` to each vertex. Returns `None` for any
|
||||
/// vertex not connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dijkstra_distances;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
/// let mut weights = graph.edge_map(1);
|
||||
/// weights[e] = 5;
|
||||
///
|
||||
/// let distances = dijkstra_distances(&graph, source, |e| weights[e]);
|
||||
/// assert_eq!(distances[target], Some(5));
|
||||
/// ```
|
||||
///
|
||||
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
pub fn dijkstra_distances<G, W>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weights: impl Fn(G::Edge) -> W,
|
||||
) -> ElementMap<G::Vertex, Option<W>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
G::Vertex: Hash,
|
||||
W: Weight,
|
||||
{
|
||||
dijkstra_impl(graph, source, weights, |_, _| {})
|
||||
}
|
||||
|
||||
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances and predecessors.
|
||||
///
|
||||
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
|
||||
/// the distances from `source` to each vertex, and the predecessors of each vertex on some shortest
|
||||
/// path from `source` to that vertex. Returns `None` for any vertex not connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dijkstra_unweighted;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let result = dijkstra_unweighted(&graph, source);
|
||||
/// assert_eq!(result.distances[target], Some(1));
|
||||
/// assert_eq!(result.predecessors[target], Some(source));
|
||||
/// ```
|
||||
///
|
||||
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex, u32>
|
||||
where
|
||||
G: GraphTopology,
|
||||
G::Vertex: Hash,
|
||||
{
|
||||
dijkstra(graph, source, |_| 1)
|
||||
}
|
||||
|
||||
pub fn dijkstra_distances<G, W>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weight: W,
|
||||
) -> VertexMap<G::Vertex, Option<u32>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
W: Fn(G::Edge) -> u32,
|
||||
{
|
||||
dijkstra_impl(graph, source, weight, |_, _| {})
|
||||
}
|
||||
|
||||
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances.
|
||||
///
|
||||
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
|
||||
/// the distances from `source` to each vertex. Returns `None` for any vertex not connected to
|
||||
/// `source`.
|
||||
///
|
||||
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dijkstra_distances_unweighted;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let distances = dijkstra_distances_unweighted(&graph, source);
|
||||
/// assert_eq!(distances[target], Some(1));
|
||||
/// ```
|
||||
///
|
||||
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
||||
pub fn dijkstra_distances_unweighted<G>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
) -> VertexMap<G::Vertex, Option<u32>>
|
||||
) -> ElementMap<G::Vertex, Option<u32>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
G::Vertex: Hash,
|
||||
{
|
||||
dijkstra_distances(graph, source, |_| 1)
|
||||
}
|
||||
@@ -75,70 +251,153 @@ where
|
||||
fn dijkstra_impl<G, W, F>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weight: W,
|
||||
weights: impl Fn(G::Edge) -> W,
|
||||
mut on_relax: F,
|
||||
) -> VertexMap<G::Vertex, Option<u32>>
|
||||
) -> ElementMap<G::Vertex, Option<W>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
W: Fn(G::Edge) -> u32,
|
||||
G::Vertex: Hash,
|
||||
W: Weight,
|
||||
F: FnMut(G::Vertex, G::Vertex),
|
||||
{
|
||||
let mut distances = graph.vertex_map(None);
|
||||
let mut heap = BinaryHeap::new();
|
||||
let mut heap = PriorityQueue::new();
|
||||
|
||||
distances[source] = Some(0);
|
||||
heap.push(DistanceOrderedVertex {
|
||||
vertex: source,
|
||||
distance: 0,
|
||||
});
|
||||
distances[source] = Some(W::default());
|
||||
heap.push(source, Reverse(W::default()));
|
||||
|
||||
while let Some(v) = heap.pop() {
|
||||
for incidence in graph.incidences(v.vertex) {
|
||||
let new_distance = distances[v.vertex].unwrap() + weight(incidence.1);
|
||||
if match distances[incidence.0] {
|
||||
while let Some((v, Reverse(v_distance))) = heap.pop() {
|
||||
for Incidence { vertex: u, edge: e } in graph.incidences(v) {
|
||||
let new_distance = weights(e) + v_distance;
|
||||
if match distances[u] {
|
||||
None => true,
|
||||
Some(old_distance) if old_distance > new_distance => true,
|
||||
_ => false,
|
||||
} {
|
||||
distances[incidence.0] = Some(new_distance);
|
||||
on_relax(incidence.0, v.vertex);
|
||||
heap.push(DistanceOrderedVertex {
|
||||
vertex: incidence.0,
|
||||
distance: new_distance,
|
||||
});
|
||||
distances[u] = Some(new_distance);
|
||||
on_relax(u, v);
|
||||
heap.push_increase(u, Reverse(new_distance));
|
||||
}
|
||||
}
|
||||
}
|
||||
distances
|
||||
}
|
||||
|
||||
/// Return data type for [`bfs`].
|
||||
pub struct BfsResult<V: Copy> {
|
||||
pub distances: VertexMap<V, Option<u32>>,
|
||||
pub predecessors: VertexMap<V, Option<V>>,
|
||||
/// Vertex map of minimum distances from a given `source` vertex.
|
||||
pub distances: ElementMap<V, Option<u32>>,
|
||||
/// Vertex map of predecessors on some shortest path from a given `source` vertex.
|
||||
pub predecessors: ElementMap<V, Option<V>>,
|
||||
}
|
||||
|
||||
/// [Breadth-first search] traversal from `source`, returns distances and predecessors.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
|
||||
/// further. Returns the distances from `source` to each vertex, and the predecessors of each vertex
|
||||
/// on some shortest path from `source` to that vertex. Returns `None` for any vertex not connected
|
||||
/// to `source`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let result = bfs(&graph, source);
|
||||
/// assert_eq!(result.distances[target], Some(1));
|
||||
/// assert_eq!(result.predecessors[target], Some(source));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs<G>(graph: &G, source: G::Vertex) -> BfsResult<G::Vertex>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
let mut predecessors = graph.vertex_map(None);
|
||||
let (distances, _) = bfs_impl(graph, source, |neighbor, predecessor| {
|
||||
let result = bfs_impl(graph, source, |neighbor, predecessor| {
|
||||
predecessors[neighbor] = Some(predecessor);
|
||||
true
|
||||
});
|
||||
BfsResult {
|
||||
distances,
|
||||
distances: result.distances,
|
||||
predecessors,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, Option<u32>>
|
||||
/// [Breadth-first search] traversal from `source`, returns distances.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source`, exploring all neighbors before descending
|
||||
/// further. Returns the distances from `source` to each vertex. Returns `None` for any vertex not
|
||||
/// connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs_distances;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let distances = bfs_distances(&graph, source);
|
||||
/// assert_eq!(distances[target], Some(1));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs_distances<G>(graph: &G, source: G::Vertex) -> ElementMap<G::Vertex, Option<u32>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
bfs_impl(graph, source, |_, _| true).0
|
||||
bfs_impl(graph, source, |_, _| true).distances
|
||||
}
|
||||
|
||||
/// [Breadth-first search] from `source` for `target`, returns the distance.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring all
|
||||
/// neighbors before descending further. Returns the minimum distance from `source` to `target` if
|
||||
/// `target` is a valid vertex of `graph` connected to `source`, or `None` otherwise.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs_find;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(bfs_find(&graph, source, target), Some(1));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -146,6 +405,35 @@ where
|
||||
bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance)
|
||||
}
|
||||
|
||||
/// [Breadth-first search] from `source` for a vertex matching `predicate`, returns the vertex and
|
||||
/// its distance.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||
/// found, exploring all neighbors before descending further. Returns the first vertex satisfying
|
||||
/// `predicate` and its minimum distance from `source`, or `None` if no such vertex is connected to
|
||||
/// `source`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs_find_where;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(bfs_find_where(&graph, source, |v| v == target), Some((target, 1)));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)>
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -154,23 +442,22 @@ where
|
||||
if predicate(source) {
|
||||
return Some((source, 0));
|
||||
}
|
||||
bfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1
|
||||
bfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).found
|
||||
}
|
||||
|
||||
fn bfs_impl<G, F>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
mut on_discover: F,
|
||||
) -> (VertexMap<G::Vertex, Option<u32>>, Option<(G::Vertex, u32)>)
|
||||
struct BfsImplResult<V: Copy> {
|
||||
distances: ElementMap<V, Option<u32>>,
|
||||
found: Option<(V, u32)>,
|
||||
}
|
||||
|
||||
fn bfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> BfsImplResult<G::Vertex>
|
||||
where
|
||||
G: GraphTopology,
|
||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||
{
|
||||
let mut distances = graph.vertex_map(None);
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
distances[source] = Some(0);
|
||||
queue.push_back(source);
|
||||
let mut queue = VecDeque::from([source]);
|
||||
|
||||
while let Some(v) = queue.pop_front() {
|
||||
for neighbor in graph.adjacent_vertices(v) {
|
||||
@@ -178,42 +465,243 @@ where
|
||||
let distance = distances[v].unwrap() + 1;
|
||||
distances[neighbor] = Some(distance);
|
||||
if !on_discover(neighbor, v) {
|
||||
return (distances, Some((neighbor, distance)));
|
||||
return BfsImplResult {
|
||||
distances,
|
||||
found: Some((neighbor, distance)),
|
||||
};
|
||||
}
|
||||
queue.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
(distances, None)
|
||||
BfsImplResult {
|
||||
distances,
|
||||
found: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// [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`].
|
||||
pub struct DfsResult<V: Copy> {
|
||||
pub visited: VertexMap<V, bool>,
|
||||
pub predecessors: VertexMap<V, Option<V>>,
|
||||
/// Vertex map indicating which vertices were visited during the search.
|
||||
pub visited: ElementMap<V, bool>,
|
||||
/// Vertex map of predecessors on the DFS tree path from a given `source` vertex.
|
||||
pub predecessors: ElementMap<V, Option<V>>,
|
||||
}
|
||||
|
||||
/// [Depth-first search] traversal from `source`, returns visited vertices and predecessors.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
|
||||
/// before backtracking. Returns for each vertex whether it was visited, and the predecessors of
|
||||
/// each vertex on the DFS tree path from `source` to that vertex. Any vertex not connected to
|
||||
/// `source` is marked unvisited and has no predecessor.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let result = dfs(&graph, source);
|
||||
/// assert!(result.visited[target]);
|
||||
/// assert_eq!(result.predecessors[target], Some(source));
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs<G>(graph: &G, source: G::Vertex) -> DfsResult<G::Vertex>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
let mut predecessors = graph.vertex_map(None);
|
||||
let (visited, _) = dfs_impl(graph, source, |neighbor, predecessor| {
|
||||
let result = dfs_impl(graph, source, |neighbor, predecessor| {
|
||||
predecessors[neighbor] = Some(predecessor);
|
||||
true
|
||||
});
|
||||
DfsResult {
|
||||
visited,
|
||||
visited: result.visited,
|
||||
predecessors,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> VertexMap<G::Vertex, bool>
|
||||
/// [Depth-first search] traversal from `source`, returns visited vertices.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source`, exploring each branch as far as possible
|
||||
/// before backtracking. Returns a map indicating which vertices were visited, i.e. which vertices
|
||||
/// are connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs_visited;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// let visited = dfs_visited(&graph, source);
|
||||
/// assert!(visited[target]);
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs_visited<G>(graph: &G, source: G::Vertex) -> ElementMap<G::Vertex, bool>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
dfs_impl(graph, source, |_, _| true).0
|
||||
dfs_impl(graph, source, |_, _| true).visited
|
||||
}
|
||||
|
||||
/// [Depth-first search] from `source` for `target`, returns whether it was found.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
|
||||
/// branch as far as possible before backtracking. Returns `true` if `target` is a valid vertex of
|
||||
/// `graph` connected to `source`, or `false` otherwise.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs_find;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// assert!(dfs_find(&graph, source, target));
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -221,6 +709,33 @@ where
|
||||
dfs_find_where(graph, source, |v| v == target).is_some()
|
||||
}
|
||||
|
||||
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the vertex.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||
/// found, exploring each branch as far as possible before backtracking. Returns the first vertex
|
||||
/// satisfying `predicate`, or `None` if no such vertex is connected to `source`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs_find_where;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(dfs_find_where(&graph, source, |v| v == target), Some(target));
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<G::Vertex>
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -229,14 +744,15 @@ where
|
||||
if predicate(source) {
|
||||
return Some(source);
|
||||
}
|
||||
dfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1
|
||||
dfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).found
|
||||
}
|
||||
|
||||
fn dfs_impl<G, F>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
mut on_discover: F,
|
||||
) -> (VertexMap<G::Vertex, bool>, Option<G::Vertex>)
|
||||
struct DfsImplResult<V: Copy> {
|
||||
visited: ElementMap<V, bool>,
|
||||
found: Option<V>,
|
||||
}
|
||||
|
||||
fn dfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> DfsImplResult<G::Vertex>
|
||||
where
|
||||
G: GraphTopology,
|
||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||
@@ -248,7 +764,10 @@ where
|
||||
while let Some((v, predecessor)) = stack.pop() {
|
||||
if let Some(p) = predecessor {
|
||||
if !on_discover(v, p) {
|
||||
return (visited, Some(v));
|
||||
return DfsImplResult {
|
||||
visited,
|
||||
found: Some(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
for neighbor in graph.adjacent_vertices(v) {
|
||||
@@ -258,9 +777,41 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
(visited, None)
|
||||
DfsImplResult {
|
||||
visited,
|
||||
found: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// [Depth-first search] from `source` for `target`, returns the path as a sequence of edges.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring each
|
||||
/// branch as far as possible before backtracking. Returns some path from `source` to `target` as a
|
||||
/// sequence of edges in traversal order if `target` is a valid vertex of `graph` connected to
|
||||
/// `source`, or `None` otherwise. The returned path is empty if and only if `source` equals
|
||||
/// `target`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs_find_path;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(dfs_find_path(&graph, source, target), Some(vec![e]));
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -268,6 +819,36 @@ where
|
||||
dfs_find_path_where(graph, source, |v| v == target)
|
||||
}
|
||||
|
||||
/// [Depth-first search] from `source` for a vertex matching `predicate`, returns the path as a
|
||||
/// sequence of edges.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||
/// found, exploring each branch as far as possible before backtracking. Returns some path from
|
||||
/// `source` to the first vertex satisfying `predicate` as a sequence of edges in traversal order,
|
||||
/// or `None` if no such vertex is connected to `source`. The returned path is empty if and only if
|
||||
/// `source` satisfies `predicate`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::dfs_find_path_where;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(dfs_find_path_where(&graph, source, |v| v == target), Some(vec![e]));
|
||||
/// ```
|
||||
///
|
||||
/// [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search
|
||||
pub fn dfs_find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
@@ -295,7 +876,10 @@ where
|
||||
None => {
|
||||
stack.pop();
|
||||
}
|
||||
Some((neighbor, edge)) => {
|
||||
Some(Incidence {
|
||||
vertex: neighbor,
|
||||
edge,
|
||||
}) => {
|
||||
if predicate(neighbor) {
|
||||
let mut path: Vec<G::Edge> =
|
||||
stack.iter().filter_map(|f| f.arrival_edge).collect();
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
+147
-2
@@ -1,10 +1,155 @@
|
||||
//! Graph models and algorithms.
|
||||
//!
|
||||
//! This crate provides data structures to model graphs and to perform algorithms on these data
|
||||
//! structures. The functionality presented is designed to be easy to use, flexible, and performant.
|
||||
//!
|
||||
//! Currently supported are:
|
||||
//!
|
||||
//! * Undirected graph types [`Graph`], [`AppendGraph`], and [`FrozenGraph`] built on a flat,
|
||||
//! index-based adjacency list,
|
||||
//! * [`ElementMap`] to freely associate custom data with vertices and edges,
|
||||
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
|
||||
//! variants,
|
||||
//! * Exposed [`traits`] to implement custom graph data structures or algorithms.
|
||||
//!
|
||||
//! # Usage example
|
||||
//!
|
||||
//! This example demonstrates some of this library's features.
|
||||
//!
|
||||
//! ```
|
||||
//! // Brings commonly used traits into scope.
|
||||
//! use grapherity::prelude::*;
|
||||
//! use grapherity::models::Graph;
|
||||
//! use grapherity::algorithms;
|
||||
//!
|
||||
//! let mut graph = Graph::new();
|
||||
//! // Adds three vertices and two edges.
|
||||
//! let v1 = graph.add_vertex();
|
||||
//! let v2 = graph.add_vertex();
|
||||
//! let v3 = graph.add_vertex();
|
||||
//! graph.add_edge(v1, v2);
|
||||
//! let e = graph.add_edge(v2, v3);
|
||||
//! // Creates a map for edge weights with a default weight, after creating the edges.
|
||||
//! let mut weights = graph.edge_map(1);
|
||||
//! // Sets a non-default weight for the second edge.
|
||||
//! weights[e] = 5;
|
||||
//!
|
||||
//! // Checks the number of vertices.
|
||||
//! assert_eq!(graph.vertex_count(), 3);
|
||||
//! // Checks the number of edges.
|
||||
//! assert_eq!(graph.edge_count(), 2);
|
||||
//! // Checks that v1 and v2 are adjacent.
|
||||
//! assert!(graph.are_adjacent(v1, v2));
|
||||
//! // Checks that v1 and v3 are not adjacent.
|
||||
//! assert!(!graph.are_adjacent(v1, v3));
|
||||
//! // Checks the sum of all vertex degrees.
|
||||
//! let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum();
|
||||
//! assert_eq!(sum, 1 + 2 + 1);
|
||||
//!
|
||||
//! // Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3.
|
||||
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
|
||||
//! assert_eq!(result.distances[v3], Some(1 + 5));
|
||||
//! assert_eq!(result.predecessors[v3], Some(v2));
|
||||
//!
|
||||
//! // Deletes the middle vertex and its incident edges.
|
||||
//! graph.delete_vertex(v2);
|
||||
//!
|
||||
//! // Calls Dijkstra's algorithm again on the now disconnected graph.
|
||||
//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]);
|
||||
//! assert_eq!(result.distances[v3], None);
|
||||
//! assert_eq!(result.predecessors[v3], None);
|
||||
//! ```
|
||||
//!
|
||||
//! # Graph types
|
||||
//!
|
||||
//! [`Graph`], [`AppendGraph`], and [`FrozenGraph`] all implement [`GraphTopology`] using a flat,
|
||||
//! index-based adjacency list to store edges. Degree lookups run in `O(1)`.
|
||||
//!
|
||||
//! [`Graph`] and [`AppendGraph`] implement [`GraphTopologyAddition`], which provides functionality
|
||||
//! for iterative graph construction. Vertex and edge insertions happen in `O(1)`.
|
||||
//!
|
||||
//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
|
||||
//! vertices and edges.
|
||||
//!
|
||||
//! [`AppendGraph`] does not support deletion, vertices and edges can only be added, allowing it to
|
||||
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
|
||||
//! tracking needed for stable handles after deletion.
|
||||
//!
|
||||
//! [`FrozenGraph`] uses a memory layout that is more cache-friendly for traversals than
|
||||
//! [`AppendGraph`], but the trade-off is that its topology must be constructed during its
|
||||
//! initialization. It cannot be modified afterward.
|
||||
//!
|
||||
//! # Vertices and edges
|
||||
//!
|
||||
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
|
||||
//! to denote vertices and edges. This means that vertices and edges returned by graph functions
|
||||
//! never change, even if the underlying graph data structure is mutated. However, vertices and
|
||||
//! edges can become invalid when the denoted element is deleted. Code using an invalid vertex or
|
||||
//! edge in a graph method will panic.
|
||||
//!
|
||||
//! ```should_panic
|
||||
//! # use grapherity::prelude::*;
|
||||
//! # use grapherity::models::Graph;
|
||||
//! let mut graph = Graph::new();
|
||||
//! let v = graph.add_vertex();
|
||||
//! graph.delete_vertex(v);
|
||||
//! // This will panic because 'v' was deleted and is not valid anymore.
|
||||
//! graph.degree(v);
|
||||
//! ```
|
||||
//!
|
||||
//! [`ElementMap`] does not check validity of vertices or edges and might or might not panic in case
|
||||
//! an invalid handle is provided.
|
||||
//!
|
||||
//! Vertices and edges are provided by the graph and not intended to be generated by users.
|
||||
//!
|
||||
//! # Loops and multi-edges
|
||||
//!
|
||||
//! Loops and multi edges are supported as well. In the following example, `v1` will have two loops
|
||||
//! and two edges to `v2`.
|
||||
//!
|
||||
//! ```
|
||||
//! # use grapherity::prelude::*;
|
||||
//! # use grapherity::models::Graph;
|
||||
//! let mut graph = Graph::new();
|
||||
//! let v1 = graph.add_vertex();
|
||||
//! let v2 = graph.add_vertex();
|
||||
//! for _ in 0..2 {
|
||||
//! graph.add_edge(v1, v1);
|
||||
//! graph.add_edge(v1, v2);
|
||||
//! }
|
||||
//! assert_eq!(graph.edge_count(), 4);
|
||||
//! assert_eq!(graph.degree(v1), 6);
|
||||
//! ```
|
||||
//!
|
||||
//! [BFS]: algorithms::bfs
|
||||
//! [Dijkstra's algorithm]: algorithms::dijkstra
|
||||
//! [DFS]: algorithms::dfs
|
||||
//! [`ElementMap`]: maps::ElementMap
|
||||
//! [`AppendGraph`]: models::AppendGraph
|
||||
//! [`FrozenGraph`]: models::FrozenGraph
|
||||
//! [`Graph`]: models::Graph
|
||||
//! [`traits`]: traits
|
||||
//! [`GraphTopology`]: traits::GraphTopology
|
||||
//! [`GraphTopology::Edge`]: traits::GraphTopology::Edge
|
||||
//! [`GraphTopology::Vertex`]: traits::GraphTopology::Vertex
|
||||
//! [`GraphTopologyAddition`]: traits::GraphTopologyAddition
|
||||
//! [`GraphTopologyDeletion`]: traits::GraphTopologyDeletion
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod algorithms;
|
||||
pub mod generators;
|
||||
pub mod maps;
|
||||
pub mod models;
|
||||
pub mod traits;
|
||||
|
||||
/// Convenience re-exports of graph topology traits for common use.
|
||||
pub mod prelude {
|
||||
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
|
||||
pub use crate::traits::{
|
||||
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
|
||||
GraphTopologyDeletion, Incidence, IncidenceCursor,
|
||||
};
|
||||
}
|
||||
|
||||
mod testing;
|
||||
#[doc(hidden)]
|
||||
pub mod testing;
|
||||
|
||||
+353
-94
@@ -1,122 +1,381 @@
|
||||
//! Provides maps to associate custom data to graph vertices and edges.
|
||||
//!
|
||||
//! [`ElementMap`] is a dense, [`Vec`]-backed map to associate data to either all vertices or all
|
||||
//! edges in a graph.
|
||||
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::traits::GraphTopology;
|
||||
|
||||
pub struct VertexMap<V: Copy, T: Clone> {
|
||||
inner: EntityMap<V, T>,
|
||||
}
|
||||
|
||||
impl<V: Copy, T: Clone> VertexMap<V, T> {
|
||||
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: EntityMap::new(default, to_index, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Reads beyond len() are valid and return the default value.
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
|
||||
self.inner.resize(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]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EdgeMap<E: Copy, T: Clone> {
|
||||
inner: EntityMap<E, T>,
|
||||
}
|
||||
|
||||
impl<E: Copy, T: Clone> EdgeMap<E, T> {
|
||||
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: EntityMap::new(default, to_index, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Reads beyond len() are valid and return the default value.
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn sync<G: GraphTopology<Edge = E>>(&mut self, graph: &G) {
|
||||
self.inner.resize(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> {
|
||||
/// A map to associate custom data to graph vertices or edges.
|
||||
///
|
||||
/// This map uses raw element indices to associate homogenous custom data of type `T` to graph
|
||||
/// vertices or edges. The implementation uses a [`Vec`], allocating contiguous slots for the data,
|
||||
/// which means that the provided index conversion function should map elements to contiguous
|
||||
/// indices, or indices with relatively few gaps.
|
||||
///
|
||||
/// Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] to obtain an `ElementMap` for
|
||||
/// vertices or edges, respectively.
|
||||
///
|
||||
/// The map does not grow on its own when the associated graph grows. Use the graph's
|
||||
/// [`GraphTopologyAddition::sync_vertex_map`] or [`GraphTopologyAddition::sync_edge_map`] to extend
|
||||
/// the map explicitly after adding vertices or edges to it.
|
||||
///
|
||||
/// No bound or validity checks are performed by the map on the provided element handles.
|
||||
///
|
||||
/// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map
|
||||
/// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map
|
||||
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||
pub struct ElementMap<E: Copy, T: Clone> {
|
||||
data: Vec<T>,
|
||||
default: T,
|
||||
to_index: fn(E) -> usize,
|
||||
}
|
||||
|
||||
impl<E: Copy, T: Clone> EntityMap<E, T> {
|
||||
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
|
||||
impl<E: Copy, T: Clone> ElementMap<E, T> {
|
||||
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
|
||||
/// and an initial `len`, i.e. the number of slots that can be read from and written to.
|
||||
///
|
||||
/// Since `to_index` is usually an implementation detail of the graph model, this is not meant
|
||||
/// to be called directly. Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`]
|
||||
/// instead.
|
||||
///
|
||||
/// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map
|
||||
/// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map
|
||||
pub fn new(default: T, to_index: fn(E) -> usize, len: usize) -> Self {
|
||||
Self {
|
||||
data: vec![default.clone(); capacity],
|
||||
data: vec![default.clone(); len],
|
||||
default,
|
||||
to_index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
/// Grows the map so that it contains at least `new_len` slots for elements, initialising any
|
||||
/// newly added slots to the `default` value provided to [`new`], without modifying existing
|
||||
/// data. Does nothing if the map already has at least `new_len` slots.
|
||||
///
|
||||
/// Since the number of elements the map can actually store depend on the index conversion
|
||||
/// function, which is usually an implementation detail of the graph model, this is not meant to
|
||||
/// be called directly. Use [`GraphTopologyAddition::sync_vertex_map`] or
|
||||
/// [`GraphTopologyAddition::sync_edge_map`] instead.
|
||||
///
|
||||
/// [`new`]: ElementMap::new
|
||||
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||
pub fn grow(&mut self, new_len: usize) {
|
||||
if new_len > self.data.len() {
|
||||
self.data.resize(new_len, self.default.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, capacity: usize) {
|
||||
if capacity > self.data.len() {
|
||||
self.data.resize(capacity, self.default.clone());
|
||||
/// Returns a new map over the same elements with values transformed by `f`.
|
||||
///
|
||||
/// The default value of the new map is `f` applied to the original default, so slots later added
|
||||
/// to the new map by [`GraphTopologyAddition::sync_vertex_map`] or
|
||||
/// [`GraphTopologyAddition::sync_edge_map`] are filled consistently with the transformation.
|
||||
///
|
||||
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::maps::ElementMap;
|
||||
/// let mut map: ElementMap<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;
|
||||
|
||||
fn index(&self, e: E) -> &T {
|
||||
let i = (self.to_index)(e);
|
||||
if i < self.data.len() {
|
||||
&self.data[i]
|
||||
} else {
|
||||
&self.default
|
||||
}
|
||||
&self.data[(self.to_index)(e)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<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 {
|
||||
let i = (self.to_index)(e);
|
||||
if i >= self.data.len() {
|
||||
self.data.resize(i + 1, self.default.clone());
|
||||
}
|
||||
&mut self.data[i]
|
||||
&mut self.data[(self.to_index)(e)]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn initial_values_are_default() {
|
||||
let map = ElementMap::new(42, |e| e, 6);
|
||||
assert_eq!(map[0], 42, "initial read at 0 should be default");
|
||||
assert_eq!(map[5], 42, "initial read at 5 should be default");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn read_out_of_bounds() {
|
||||
let map = ElementMap::new(42, |e| e, 3);
|
||||
let _ = map[3];
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn write_out_of_bounds() {
|
||||
let mut map = ElementMap::new(0, |e| e, 3);
|
||||
map[3] = 1;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_and_read() {
|
||||
let mut map = ElementMap::new(0, |e| e, 2);
|
||||
map[1] = 7;
|
||||
assert_eq!(map[1], 7, "unexpected read value after write");
|
||||
assert_eq!(map[0], 0, "initial read should be default");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow() {
|
||||
let mut map = ElementMap::new(0, |e| e, 2);
|
||||
assert_eq!(
|
||||
map.data.len(),
|
||||
2,
|
||||
"precondition: unexpected data length before grow"
|
||||
);
|
||||
map.grow(10);
|
||||
assert_eq!(map.data.len(), 10, "unexpected data length after grow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow_extends_with_default() {
|
||||
let mut map = ElementMap::new(17, |e| e, 3);
|
||||
map.grow(6);
|
||||
assert_eq!(map[5], 17, "read of newly grown slot should be default");
|
||||
map[5] = 9;
|
||||
assert_eq!(
|
||||
map[5], 9,
|
||||
"unexpected value after write in newly grown slot"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow_preserves_existing_values() {
|
||||
let mut map = ElementMap::new(22, |e| e, 3);
|
||||
map[0] = 5;
|
||||
map.grow(10);
|
||||
assert_eq!(map[0], 5, "unexpected data after grow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grow_to_smaller_len() {
|
||||
let mut map = ElementMap::new(23, |e| e, 6);
|
||||
map[5] = 9;
|
||||
map.grow(3);
|
||||
assert_eq!(
|
||||
map[5], 9,
|
||||
"grow to a smaller length should not discard data"
|
||||
);
|
||||
assert_eq!(map.data.len(), 6, "unexpected data length");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map() {
|
||||
let mut source: ElementMap<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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+46
-2
@@ -1,5 +1,49 @@
|
||||
//! Concrete graph topology models.
|
||||
|
||||
use crate::maps::ElementMap;
|
||||
use crate::traits::{GraphTopology, GraphTopologyAddition};
|
||||
|
||||
pub mod append_graph;
|
||||
pub mod frozen_graph;
|
||||
pub mod graph;
|
||||
|
||||
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
|
||||
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
|
||||
pub use append_graph::{
|
||||
AppendGraph, AppendGraphEdgeMap, AppendGraphIncidence, AppendGraphVertexMap,
|
||||
};
|
||||
pub use frozen_graph::{
|
||||
FrozenGraph, FrozenGraphEdgeMap, FrozenGraphIncidence, FrozenGraphVertexMap,
|
||||
};
|
||||
pub use graph::{Graph, GraphEdgeMap, GraphIncidence, GraphVertexMap};
|
||||
|
||||
/// Builds a new graph isomorphic to `source`.
|
||||
///
|
||||
/// The resulting graph type `G` implements [`GraphTopologyAddition`] which is used to add
|
||||
/// vertices and edges. `on_edge` is called for each edge in `source` together with the
|
||||
/// corresponding new edge of the new graph, which callers can use to construct an edge mapping.
|
||||
///
|
||||
/// Used by the [`GraphTopologyAddition`] models' `From<&H>` and [`GraphTopologyConversion`]
|
||||
/// implementations.
|
||||
///
|
||||
/// [`GraphTopologyConversion`]: crate::traits::GraphTopologyConversion
|
||||
pub(crate) fn convert_to_addition<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)
|
||||
}
|
||||
|
||||
+177
-54
@@ -1,21 +1,53 @@
|
||||
use crate::maps::{EdgeMap, VertexMap};
|
||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
||||
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::maps::ElementMap;
|
||||
use crate::models;
|
||||
use crate::traits::{
|
||||
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence,
|
||||
IncidenceCursor,
|
||||
};
|
||||
|
||||
/// An opaque handle identifying a vertex in an [`AppendGraph`].
|
||||
///
|
||||
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||
/// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`].
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Vertex(usize);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Edge(usize);
|
||||
|
||||
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
|
||||
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
|
||||
// TODO: Benchmark the cost of the index shift. An alternative could be to init AppendGraph::incidences with two dummy entries to avoid 0.
|
||||
/// An opaque handle identifying an edge in an [`AppendGraph`].
|
||||
///
|
||||
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Edge(NonZeroUsize);
|
||||
|
||||
impl Edge {
|
||||
fn new(index: usize) -> Self {
|
||||
Edge(NonZeroUsize::new(index + 1).unwrap())
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0.get() - 1
|
||||
}
|
||||
|
||||
fn normalize(&self) -> Self {
|
||||
Self(self.0 & !1)
|
||||
Self::new(self.index() & !1)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
incidence_count: usize,
|
||||
first_incidence: Option<Edge>,
|
||||
@@ -27,23 +59,72 @@ struct IncidenceEntry {
|
||||
adjacent: Vertex,
|
||||
}
|
||||
|
||||
/// A resumable cursor over the incidences of a single vertex in an [`AppendGraph`].
|
||||
///
|
||||
/// Obtain via [`AppendGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AppendGraphIncidenceCursor {
|
||||
incidence: Option<Edge>,
|
||||
}
|
||||
|
||||
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
||||
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
|
||||
graph.step_incidence(&mut self.incidence)
|
||||
fn next(&mut self, graph: &AppendGraph) -> Option<AppendGraphIncidence> {
|
||||
graph
|
||||
.step_incidence(&mut self.incidence)
|
||||
.map(|(v, e)| Incidence {
|
||||
vertex: v,
|
||||
edge: e.normalize(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An undirected graph that supports adding vertices and edges, but not deleting them.
|
||||
///
|
||||
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it
|
||||
/// repeatedly. [`Vertex`] and [`Edge`] handles are never invalidated. Use [`Graph`] instead if you
|
||||
/// need to remove vertices or edges. Use [`FrozenGraph`] if you do not need incremental
|
||||
/// construction.
|
||||
///
|
||||
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
|
||||
/// vertex neighborhood traversals result in scattered index jumps.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use grapherity::prelude::*;
|
||||
/// use grapherity::models::AppendGraph;
|
||||
///
|
||||
/// let mut graph = AppendGraph::new();
|
||||
/// let u = graph.add_vertex();
|
||||
/// let v = graph.add_vertex();
|
||||
/// let e = graph.add_edge(u, v);
|
||||
/// assert!(graph.are_adjacent(u, v));
|
||||
/// ```
|
||||
///
|
||||
/// # Time and space complexity
|
||||
///
|
||||
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
||||
/// time since vertex degrees are stored. Space complexity is *O(|V| + |E|)*.
|
||||
///
|
||||
/// # Cloning
|
||||
///
|
||||
/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or
|
||||
/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect
|
||||
/// each other.
|
||||
///
|
||||
/// [`add_vertex`]: Self::add_vertex
|
||||
/// [`add_edge`]: Self::add_edge
|
||||
/// [`degree`]: Self::degree
|
||||
/// [`FrozenGraph`]: models::FrozenGraph
|
||||
/// [`Graph`]: models::Graph
|
||||
#[derive(Clone)]
|
||||
pub struct AppendGraph {
|
||||
vertices: Vec<VertexIncidenceHeader>,
|
||||
incidences: Vec<IncidenceEntry>,
|
||||
}
|
||||
|
||||
impl AppendGraph {
|
||||
/// Creates an empty graph instance with no vertices or edges.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vertices: vec![],
|
||||
@@ -51,15 +132,15 @@ impl AppendGraph {
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||
// incidences vector.
|
||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
|
||||
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||
/// incidences vector.
|
||||
fn add_incidence(&mut self, u: Vertex, v: Vertex) {
|
||||
self.incidences.push(IncidenceEntry {
|
||||
next: self.vertices[v1.0].first_incidence.take(),
|
||||
adjacent: v2,
|
||||
next: self.vertices[u.0].first_incidence.take(),
|
||||
adjacent: v,
|
||||
});
|
||||
self.vertices[v1.0].incidence_count += 1;
|
||||
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1));
|
||||
self.vertices[u.0].incidence_count += 1;
|
||||
self.vertices[u.0].first_incidence = Some(Edge::new(self.incidences.len() - 1));
|
||||
}
|
||||
|
||||
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
||||
@@ -69,7 +150,7 @@ impl AppendGraph {
|
||||
|
||||
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
||||
let current = (*incidence)?;
|
||||
let entry = self.incidences[current.0];
|
||||
let entry = self.incidences[current.index()];
|
||||
*incidence = entry.next;
|
||||
Some((entry.adjacent, current))
|
||||
}
|
||||
@@ -90,32 +171,24 @@ impl GraphTopology for AppendGraph {
|
||||
self.vertices.len()
|
||||
}
|
||||
|
||||
fn vertex_capacity(&self) -> usize {
|
||||
self.vertices.len()
|
||||
}
|
||||
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
||||
VertexMap::new(default, |v| v.0, self.vertex_capacity())
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
|
||||
ElementMap::new(default, |v| v.0, self.vertex_capacity())
|
||||
}
|
||||
|
||||
fn edge_count(&self) -> usize {
|
||||
self.incidences.len() / 2
|
||||
}
|
||||
|
||||
fn edge_capacity(&self) -> usize {
|
||||
self.incidences.len() / 2
|
||||
}
|
||||
|
||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
||||
EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity())
|
||||
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||
ElementMap::new(default, |e| e.index() / 2, self.edge_capacity())
|
||||
}
|
||||
|
||||
fn degree(&self, v: Self::Vertex) -> usize {
|
||||
self.vertices[v.0].incidence_count
|
||||
}
|
||||
|
||||
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
||||
self.adjacent_vertices(v1).any(|x| x == v2)
|
||||
fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
|
||||
self.adjacent_vertices(u).any(|x| x == v)
|
||||
}
|
||||
|
||||
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
||||
@@ -127,21 +200,26 @@ impl GraphTopology for AppendGraph {
|
||||
}
|
||||
|
||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||
let v2 = self.incidences[e.0].adjacent;
|
||||
let v1 = self.incidences[e.0 ^ 1].adjacent;
|
||||
(v1, v2)
|
||||
let ei = e.index();
|
||||
(
|
||||
self.incidences[ei ^ 1].adjacent,
|
||||
self.incidences[ei].adjacent,
|
||||
)
|
||||
}
|
||||
|
||||
fn edges(&self) -> impl Iterator<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> {
|
||||
self.raw_incidences(v).map(|(_, e)| e.normalize())
|
||||
}
|
||||
|
||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
|
||||
self.raw_incidences(v).map(|(v, e)| (v, e.normalize()))
|
||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = AppendGraphIncidence> {
|
||||
self.raw_incidences(v).map(|(v, e)| Incidence {
|
||||
vertex: v,
|
||||
edge: e.normalize(),
|
||||
})
|
||||
}
|
||||
|
||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
||||
@@ -149,6 +227,24 @@ impl GraphTopology for AppendGraph {
|
||||
incidence: self.vertices[v.0].first_incidence,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphTopologyAddition for AppendGraph {
|
||||
fn vertex_capacity(&self) -> usize {
|
||||
self.vertices.capacity()
|
||||
}
|
||||
|
||||
fn edge_capacity(&self) -> usize {
|
||||
self.incidences.capacity() / 2
|
||||
}
|
||||
|
||||
fn reserve_vertices(&mut self, additional: usize) {
|
||||
self.vertices.reserve(additional);
|
||||
}
|
||||
|
||||
fn reserve_edges(&mut self, additional: usize) {
|
||||
self.incidences.reserve(additional * 2);
|
||||
}
|
||||
|
||||
fn add_vertex(&mut self) -> Self::Vertex {
|
||||
self.vertices.push(VertexIncidenceHeader {
|
||||
@@ -158,31 +254,58 @@ impl GraphTopology for AppendGraph {
|
||||
Vertex(self.vertices.len() - 1)
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
||||
self.add_incidence(v1, v2);
|
||||
self.add_incidence(v2, v1);
|
||||
Edge(self.incidences.len() - 2)
|
||||
fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
|
||||
self.add_incidence(u, v);
|
||||
self.add_incidence(v, u);
|
||||
Edge::new(self.incidences.len() - 2)
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphTopologyConversion for AppendGraph {
|
||||
fn convert<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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
crate::graph_topology_test_fixtures!(AppendGraph);
|
||||
crate::graph_topology_tests!(AppendGraph);
|
||||
use crate::testing::fixtures::MakeTestGraph;
|
||||
|
||||
#[test]
|
||||
fn incident_vertices_paired_index() {
|
||||
let mut graph = AppendGraph::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let e = graph.add_edge(v1, v2);
|
||||
let f = Edge(e.0 + 1);
|
||||
let (u1, u2) = graph.incident_vertices(f);
|
||||
let (graph, [v0, v1], e) = AppendGraph::single_edge();
|
||||
let f = Edge::new(e.index() + 1);
|
||||
let (u0, u1) = graph.incident_vertices(f);
|
||||
assert!(
|
||||
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
|
||||
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}"
|
||||
(u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
|
||||
"unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+231
-65
@@ -1,25 +1,65 @@
|
||||
//! [`Graph`], an undirected graph topology supporting addition and deletion.
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use typed_generational_arena::{Arena, Index};
|
||||
|
||||
use crate::maps::{EdgeMap, VertexMap};
|
||||
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
|
||||
use crate::maps::ElementMap;
|
||||
use crate::models;
|
||||
use crate::traits::{
|
||||
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
|
||||
GraphTopologyDeletion, Incidence, IncidenceCursor,
|
||||
};
|
||||
|
||||
/// An opaque handle identifying a vertex in a [`Graph`].
|
||||
///
|
||||
/// Handles remain valid until the vertex is explicitly deleted. Obtain via graph methods like
|
||||
/// [`Graph::add_vertex`] and [`Graph::vertices`].
|
||||
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
|
||||
|
||||
/// An opaque handle identifying an edge in a [`Graph`].
|
||||
///
|
||||
/// Handles remain valid until the edge is explicitly deleted, or one of its endpoint vertices is
|
||||
/// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`].
|
||||
pub type Edge = Index<IncidenceEntry, usize, usize>;
|
||||
|
||||
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
|
||||
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
|
||||
/// An [`ElementMap`] for [`Graph`] vertices.
|
||||
pub type GraphVertexMap<T> = ElementMap<Vertex, T>;
|
||||
|
||||
/// An [`ElementMap`] for [`Graph`] edges.
|
||||
pub type GraphEdgeMap<T> = ElementMap<Edge, T>;
|
||||
|
||||
/// An [`Incidence`] for [`Graph`].
|
||||
pub type GraphIncidence = Incidence<Vertex, Edge>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct VertexSlot(usize);
|
||||
|
||||
// TODO: Benchmark the cost of the index shift. An alternative could be to init Graph::incidences with two dummy entries to avoid 0.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct IncidenceSlot(usize);
|
||||
struct IncidenceSlot(NonZeroUsize);
|
||||
|
||||
impl IncidenceSlot {
|
||||
fn new(index: usize) -> Self {
|
||||
IncidenceSlot(NonZeroUsize::new(index + 1).unwrap())
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0.get() - 1
|
||||
}
|
||||
}
|
||||
|
||||
/// Referenced by [`Vertex`] as a type parameter of the underlying arena. Not intended for direct
|
||||
/// external use.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct VertexIncidenceHeader {
|
||||
incidence_count: usize,
|
||||
first_incidence: Option<IncidenceSlot>,
|
||||
}
|
||||
|
||||
/// Referenced by [`Edge`] as a type parameter of the underlying arena. Not intended for direct
|
||||
/// external use.
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct IncidenceEntry {
|
||||
next: Option<IncidenceSlot>,
|
||||
@@ -36,19 +76,75 @@ impl IncidentEdgeCursor {
|
||||
}
|
||||
}
|
||||
|
||||
/// A resumable cursor over the incidences of a single vertex in a [`Graph`].
|
||||
///
|
||||
/// Obtain via [`Graph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct GraphIncidenceCursor {
|
||||
incidence: Option<IncidenceSlot>,
|
||||
}
|
||||
|
||||
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.vertices.get_idx(vs.0).unwrap(), e))
|
||||
.map(|(vs, e)| Incidence {
|
||||
vertex: graph.vertices.get_idx(vs.0).unwrap(),
|
||||
edge: graph.normalize_edge(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An undirected graph that supports adding vertices and edges, and deleting them.
|
||||
///
|
||||
/// `Graph` is suited for workloads that need to modify the graph structure over time, i.e. adding
|
||||
/// amd removing vertices and edges. [`Vertex`] and [`Edge`] handles remain valid until the vertex
|
||||
/// or edge they identify is explicitly deleted. Use [`AppendGraph`] instead if you only need to
|
||||
/// append vertices and edges.
|
||||
///
|
||||
/// Incidences are stored as interleaved adjacency lists in a generational [`Arena`]. In general,
|
||||
/// vertex neighborhood traversals result in scattered memory accesses.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use grapherity::prelude::*;
|
||||
/// use grapherity::models::Graph;
|
||||
///
|
||||
/// let mut graph = Graph::new();
|
||||
/// let v1 = graph.add_vertex();
|
||||
/// let v2 = graph.add_vertex();
|
||||
/// let _e = graph.add_edge(v1, v2);
|
||||
/// assert!(graph.are_adjacent(v1, v2));
|
||||
/// graph.delete_vertex(v1);
|
||||
/// assert_eq!(graph.degree(v2), 0);
|
||||
/// ```
|
||||
///
|
||||
/// # Time and space complexity
|
||||
///
|
||||
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
||||
/// time since vertex degrees are stored. [`delete_vertex`] runs in *O(degree(v))* time.
|
||||
/// [`delete_edge`] runs in *O(degree(u) + degree(v))* time, where *u* and *v* are the edge's
|
||||
/// endpoints.
|
||||
///
|
||||
/// Space complexity is *O(|V| + |E|)*, but freed slots for vertices and edges are not compacted and
|
||||
/// their allocation is never reclaimed. Capacity only grows. If additions and deletions are both
|
||||
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
|
||||
/// potentially avoiding reallocation.
|
||||
///
|
||||
/// # Cloning
|
||||
///
|
||||
/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or
|
||||
/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect
|
||||
/// each other.
|
||||
///
|
||||
/// [`add_vertex`]: Self::add_vertex
|
||||
/// [`add_edge`]: Self::add_edge
|
||||
/// [`degree`]: Self::degree
|
||||
/// [`delete_vertex`]: Self::delete_vertex
|
||||
/// [`delete_edge`]: Self::delete_edge
|
||||
/// [`AppendGraph`]: models::AppendGraph
|
||||
#[derive(Clone)]
|
||||
pub struct Graph {
|
||||
// TODO: Arena index and generation types could be externalized to Graph.
|
||||
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
||||
@@ -56,6 +152,7 @@ pub struct Graph {
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
/// Creates an empty graph instance with no vertices or edges.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vertices: Arena::new(),
|
||||
@@ -63,15 +160,15 @@ impl Graph {
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||
// incidences arena, and returns its index.
|
||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
|
||||
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||
/// incidences arena, and returns its index.
|
||||
fn add_incidence(&mut self, u: Vertex, v: Vertex) -> Edge {
|
||||
let edge = self.incidences.insert(IncidenceEntry {
|
||||
next: self.vertices[v1].first_incidence.take(),
|
||||
adjacent: VertexSlot(v2.arr_idx()),
|
||||
next: self.vertices[u].first_incidence.take(),
|
||||
adjacent: VertexSlot(v.arr_idx()),
|
||||
});
|
||||
self.vertices[v1].incidence_count += 1;
|
||||
self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx()));
|
||||
self.vertices[u].incidence_count += 1;
|
||||
self.vertices[u].first_incidence = Some(IncidenceSlot::new(edge.arr_idx()));
|
||||
edge
|
||||
}
|
||||
|
||||
@@ -91,8 +188,9 @@ impl Graph {
|
||||
(f, e_entry, f_entry)
|
||||
}
|
||||
|
||||
// Updates the source vertex incidence list after the incidence "e" was deleted from the
|
||||
// incidence arena. "next" is the next incidence after "e" in the source vertex incidence list.
|
||||
/// Updates the `source` vertex incidence list after the incidence `e` was deleted from the
|
||||
/// incidence arena. `next` is the next incidence after `e` in the `source` vertex incidence
|
||||
/// list.
|
||||
fn update_incidence_list(
|
||||
&mut self,
|
||||
e: Edge,
|
||||
@@ -109,7 +207,7 @@ impl Graph {
|
||||
let first = vertex_header
|
||||
.first_incidence
|
||||
.expect("incident vertex without incidences, corrupt internal data state");
|
||||
if first.0 == e.arr_idx() {
|
||||
if first.index() == e.arr_idx() {
|
||||
vertex_header.first_incidence = next;
|
||||
} else {
|
||||
let graph: &Graph = self;
|
||||
@@ -118,7 +216,7 @@ impl Graph {
|
||||
.find(|(_, f)| {
|
||||
graph.incidences[*f]
|
||||
.next
|
||||
.is_some_and(|i| i.0 == e.arr_idx())
|
||||
.is_some_and(|i| i.index() == e.arr_idx())
|
||||
})
|
||||
.expect("cannot find previous incidence, corrupt internal data state");
|
||||
self.incidences[previous].next = next;
|
||||
@@ -133,14 +231,19 @@ impl Graph {
|
||||
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).
|
||||
let current = (*incidence)?;
|
||||
let e = self.incidences.get_idx(current.0).unwrap();
|
||||
let e = self.incidences.get_idx(current.index()).unwrap();
|
||||
let entry = self.incidences[e];
|
||||
*incidence = entry.next;
|
||||
Some((entry.adjacent, e))
|
||||
}
|
||||
|
||||
fn normalize_edge(&self, e: Edge) -> Edge {
|
||||
self.incidences.get_idx(e.arr_idx() & !1).unwrap()
|
||||
let i = e.arr_idx();
|
||||
if i & 1 == 0 {
|
||||
e
|
||||
} else {
|
||||
self.incidences.get_idx(i ^ 1).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,32 +262,24 @@ impl GraphTopology for Graph {
|
||||
self.vertices.len()
|
||||
}
|
||||
|
||||
fn vertex_capacity(&self) -> usize {
|
||||
self.vertices.capacity()
|
||||
}
|
||||
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
||||
VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
|
||||
ElementMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
|
||||
}
|
||||
|
||||
fn edge_count(&self) -> usize {
|
||||
self.incidences.len() / 2
|
||||
}
|
||||
|
||||
fn edge_capacity(&self) -> usize {
|
||||
self.incidences.capacity() / 2
|
||||
}
|
||||
|
||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
||||
EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
|
||||
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||
ElementMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
|
||||
}
|
||||
|
||||
fn degree(&self, v: Self::Vertex) -> usize {
|
||||
self.vertices[v].incidence_count
|
||||
}
|
||||
|
||||
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
||||
self.adjacent_vertices(v1).any(|x| x == v2)
|
||||
fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
|
||||
self.adjacent_vertices(u).any(|x| x == v)
|
||||
}
|
||||
|
||||
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
||||
@@ -197,16 +292,16 @@ impl GraphTopology for Graph {
|
||||
}
|
||||
|
||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||
let v2 = self
|
||||
let v = self
|
||||
.vertices
|
||||
.get_idx(self.incidences[e].adjacent.0)
|
||||
.unwrap();
|
||||
let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap();
|
||||
let v1 = self
|
||||
let u = self
|
||||
.vertices
|
||||
.get_idx(self.incidences[f].adjacent.0)
|
||||
.unwrap();
|
||||
(v1, v2)
|
||||
(u, v)
|
||||
}
|
||||
|
||||
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
||||
@@ -220,9 +315,11 @@ impl GraphTopology for Graph {
|
||||
self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e))
|
||||
}
|
||||
|
||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
|
||||
self.raw_incidences(v)
|
||||
.map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e)))
|
||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = GraphIncidence> {
|
||||
self.raw_incidences(v).map(|(vs, e)| Incidence {
|
||||
vertex: self.vertices.get_idx(vs.0).unwrap(),
|
||||
edge: self.normalize_edge(e),
|
||||
})
|
||||
}
|
||||
|
||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
||||
@@ -230,6 +327,24 @@ impl GraphTopology for Graph {
|
||||
incidence: self.vertices[v].first_incidence,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphTopologyAddition for Graph {
|
||||
fn vertex_capacity(&self) -> usize {
|
||||
self.vertices.capacity()
|
||||
}
|
||||
|
||||
fn edge_capacity(&self) -> usize {
|
||||
self.incidences.capacity() / 2
|
||||
}
|
||||
|
||||
fn reserve_vertices(&mut self, additional: usize) {
|
||||
self.vertices.reserve(additional);
|
||||
}
|
||||
|
||||
fn reserve_edges(&mut self, additional: usize) {
|
||||
self.incidences.reserve(additional * 2);
|
||||
}
|
||||
|
||||
fn add_vertex(&mut self) -> Self::Vertex {
|
||||
self.vertices.insert(VertexIncidenceHeader {
|
||||
@@ -238,9 +353,9 @@ impl GraphTopology for Graph {
|
||||
})
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
||||
let first = self.add_incidence(v1, v2);
|
||||
self.add_incidence(v2, v1);
|
||||
fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
|
||||
let first = self.add_incidence(u, v);
|
||||
self.add_incidence(v, u);
|
||||
first
|
||||
}
|
||||
}
|
||||
@@ -267,15 +382,15 @@ impl GraphTopologyDeletion for Graph {
|
||||
}
|
||||
}
|
||||
|
||||
// The incidence entries are removed before patching the linked lists. This is safe because
|
||||
// update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences
|
||||
// the predecessor, never the removed entries themselves.
|
||||
fn delete_edge(&mut self, e: Self::Edge) {
|
||||
// The incidence entries are removed before patching the linked lists. This is safe because
|
||||
// `update_incidence_list` searches by raw slot index (IncidenceSlot.0) and only
|
||||
// dereferences the predecessor, never the removed entries themselves.
|
||||
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
|
||||
if e_entry.adjacent != f_entry.adjacent {
|
||||
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
||||
self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false);
|
||||
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) {
|
||||
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.index()) {
|
||||
self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
|
||||
} else {
|
||||
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);
|
||||
@@ -283,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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
crate::graph_topology_test_fixtures!(Graph);
|
||||
crate::graph_topology_tests!(Graph);
|
||||
crate::graph_topology_deletion_tests!(Graph);
|
||||
use crate::testing::fixtures::MakeTestGraph;
|
||||
|
||||
#[test]
|
||||
fn incident_vertices_paired_index() {
|
||||
let mut graph = Graph::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let e = graph.add_edge(v1, v2);
|
||||
let (graph, [v0, v1], e) = Graph::single_edge();
|
||||
let f = graph
|
||||
.incidences
|
||||
.get_idx(e.arr_idx() + 1)
|
||||
.expect("paired index should be valid");
|
||||
let (u1, u2) = graph.incident_vertices(f);
|
||||
let (u0, u1) = graph.incident_vertices(f);
|
||||
assert!(
|
||||
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
|
||||
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}"
|
||||
(u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
|
||||
"unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_edge_paired_index() {
|
||||
let mut graph = Graph::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let e = graph.add_edge(v1, v2);
|
||||
let (mut graph, _, e) = Graph::single_edge();
|
||||
let f = graph
|
||||
.incidences
|
||||
.get_idx(e.arr_idx() + 1)
|
||||
@@ -324,9 +471,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn delete_edge_loop_paired_index() {
|
||||
let mut graph = Graph::new();
|
||||
let v = graph.add_vertex();
|
||||
let e = graph.add_edge(v, v);
|
||||
let (mut graph, _, e) = Graph::loop_edge();
|
||||
let f = graph
|
||||
.incidences
|
||||
.get_idx(e.arr_idx() + 1)
|
||||
@@ -334,4 +479,25 @@ mod tests {
|
||||
graph.delete_edge(f);
|
||||
assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reused_slot_returns_old_value() {
|
||||
let (mut graph, [_, v], _) = Graph::single_edge();
|
||||
let mut map = graph.vertex_map(0);
|
||||
map[v] = 99;
|
||||
graph.delete_vertex(v);
|
||||
let u = graph.add_vertex();
|
||||
assert_eq!(
|
||||
v.arr_idx(),
|
||||
u.arr_idx(),
|
||||
"precondition: new vertex {v:?} should reuse slot of deleted vertex {u:?}"
|
||||
);
|
||||
// ElementMap uses raw indices, not vertex identity. A new vertex u reusing the slot
|
||||
// of previously deleted v sees the old value. Callers must reinitialize stale slots
|
||||
// after deletion.
|
||||
assert_eq!(
|
||||
map[u], 99,
|
||||
"new vertex reusing slot of deleted vertex should return old value"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -1,6 +1,14 @@
|
||||
pub(crate) mod bfs_testing;
|
||||
pub(crate) mod dfs_testing;
|
||||
pub(crate) mod dijkstra_testing;
|
||||
pub(crate) mod find_path_testing;
|
||||
pub(crate) mod graph_topology_testing;
|
||||
pub(crate) mod maps_testing;
|
||||
//! Test fixture and test macros for graph topology and algorithm implementations.
|
||||
|
||||
pub mod fixtures;
|
||||
pub mod from_testing;
|
||||
pub mod graph_topology_addition_clone_testing;
|
||||
pub mod graph_topology_addition_conversion_testing;
|
||||
pub mod graph_topology_addition_deletion_conversion_testing;
|
||||
pub mod graph_topology_addition_deletion_testing;
|
||||
pub mod graph_topology_addition_testing;
|
||||
pub mod graph_topology_clone_testing;
|
||||
pub mod graph_topology_conversion_testing;
|
||||
pub mod graph_topology_deletion_conversion_testing;
|
||||
pub mod graph_topology_deletion_testing;
|
||||
pub mod graph_topology_testing;
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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:?}"
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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(¤t),
|
||||
"path should end at a target vertex ({targets:?}), but ended at {current:?}"
|
||||
);
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/// Asserts that the two graphs are isomorphic.
|
||||
///
|
||||
/// `vertices` must contain all vertices of the `original` graph, and `vertex_map` must for any
|
||||
/// vertex in the `original` graph return the corresponding vertex in the `other` graph.
|
||||
///
|
||||
/// `edges` must contain all edges and their endpoints of the `original` graph, and `edge_map` must
|
||||
/// for any edge in the `original` graph return the corresponding edge in the `other` graph.
|
||||
pub fn assert_isomorphic<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"
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -1,285 +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 sync_expands_to_new_vertices() {
|
||||
use $crate::traits::GraphTopology;
|
||||
let mut graph = <$T>::new();
|
||||
graph.add_vertex();
|
||||
let mut map = graph.vertex_map(42);
|
||||
let initial_len = map.len();
|
||||
while graph.vertex_capacity() <= initial_len {
|
||||
graph.add_vertex();
|
||||
}
|
||||
assert!(
|
||||
map.len() < graph.vertex_capacity(),
|
||||
"precondition: map is stale before sync"
|
||||
);
|
||||
map.sync(&graph);
|
||||
assert_eq!(map.len(), graph.vertex_capacity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_does_not_overwrite_existing_values() {
|
||||
use $crate::traits::GraphTopology;
|
||||
let mut graph = <$T>::new();
|
||||
let v = graph.add_vertex();
|
||||
let mut map = graph.vertex_map(0);
|
||||
map[v] = 5;
|
||||
graph.add_vertex();
|
||||
map.sync(&graph);
|
||||
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 capacity_does_not_shrink_after_delete() {
|
||||
use $crate::traits::GraphTopology;
|
||||
use $crate::traits::GraphTopologyDeletion;
|
||||
let mut graph = <$T>::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let mut map = graph.vertex_map(0);
|
||||
map[v1] = 5;
|
||||
let capacity_before = graph.vertex_capacity();
|
||||
graph.delete_vertex(v2);
|
||||
map.sync(&graph);
|
||||
assert_eq!(map.len(), capacity_before);
|
||||
assert_eq!(map[v1], 5);
|
||||
}
|
||||
|
||||
#[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 sync_expands_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 initial_len = map.len();
|
||||
while graph.edge_capacity() <= initial_len {
|
||||
graph.add_edge(v1, v2);
|
||||
}
|
||||
assert!(
|
||||
map.len() < graph.edge_capacity(),
|
||||
"precondition: map is stale before sync"
|
||||
);
|
||||
map.sync(&graph);
|
||||
assert_eq!(map.len(), graph.edge_capacity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_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.sync(&graph);
|
||||
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 capacity_does_not_shrink_after_delete() {
|
||||
use $crate::traits::GraphTopology;
|
||||
use $crate::traits::GraphTopologyDeletion;
|
||||
let mut graph = <$T>::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let e1 = graph.add_edge(v1, v2);
|
||||
let e2 = graph.add_edge(v1, v2);
|
||||
let mut map = graph.edge_map(0);
|
||||
map[e1] = 5;
|
||||
let capacity_before = graph.edge_capacity();
|
||||
graph.delete_edge(e2);
|
||||
map.sync(&graph);
|
||||
assert_eq!(map.len(), capacity_before);
|
||||
assert_eq!(map[e1], 5);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
};
|
||||
}
|
||||
+512
-15
@@ -1,36 +1,533 @@
|
||||
use crate::maps::{EdgeMap, VertexMap};
|
||||
//! Core traits for undirected graph topologies.
|
||||
|
||||
// TODO: Add functions to reserve memory for vertices and edges.
|
||||
// TODO: Split out GraphTopologyAddition trait.
|
||||
use crate::maps::ElementMap;
|
||||
|
||||
/// A trait representing an undirected graph topology.
|
||||
///
|
||||
/// An undirected graph is a set of vertices and undirected edges, where each edge connects either
|
||||
/// exactly two vertices or one vertex with itself (loop edge). This trait provides methods for
|
||||
/// querying a graph topology, and iterating over vertices and edges.
|
||||
///
|
||||
/// # Vertices and edges
|
||||
///
|
||||
/// Vertices and edges are identified by opaque handles ([`Vertex`] and [`Edge`]) that implement
|
||||
/// [`Copy`] and [`Eq`]. Handles remain valid for the lifetime of the graph unless the graph also
|
||||
/// implements [`GraphTopologyDeletion`] and the item identified by the handle is explicitly
|
||||
/// deleted.
|
||||
///
|
||||
/// Methods accepting vertices or edges as parameters panic if the handle was invalidated by a
|
||||
/// deletion, and return incorrect results if the handle was not produced by this graph instance.
|
||||
///
|
||||
/// # Addition and deletion
|
||||
///
|
||||
/// This trait covers graph querying only. To construct a graph incrementally by adding vertices and
|
||||
/// edges, see [`GraphTopologyAddition`]. To delete vertices and edges, see
|
||||
/// [`GraphTopologyDeletion`].
|
||||
///
|
||||
/// [`Edge`]: GraphTopology::Edge
|
||||
/// [`Vertex`]: GraphTopology::Vertex
|
||||
pub trait GraphTopology {
|
||||
/// An opaque, stable handle identifying a vertex.
|
||||
type Vertex: Copy + Eq;
|
||||
type Edge: Copy + Eq;
|
||||
type IncidenceCursor: IncidenceCursor<Self> + Copy;
|
||||
|
||||
/// An opaque, stable handle identifying an edge.
|
||||
type Edge: Copy + Eq;
|
||||
|
||||
/// A resumable position in the incidence list of a vertex.
|
||||
///
|
||||
/// Prefer [`incidences`](Self::incidences) for straightforward iteration. A cursor is useful
|
||||
/// when an algorithm needs to pause traversal, perform other graph queries or mutations, and
|
||||
/// then continue from where it left off. This cursor type can be obtained via
|
||||
/// [`incidence_cursor`](Self::incidence_cursor).
|
||||
type IncidenceCursor: IncidenceCursor<Self>;
|
||||
|
||||
/// Returns the number of vertices in the graph.
|
||||
fn vertex_count(&self) -> usize;
|
||||
fn vertex_capacity(&self) -> usize;
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T>;
|
||||
|
||||
/// Creates and returns an [`ElementMap`] for the vertices with every slot initialised to
|
||||
/// `default`.
|
||||
///
|
||||
/// The returned map only covers the vertices that exist in the graph at the time of the call.
|
||||
/// Use [`GraphTopologyAddition::sync_vertex_map`] to extend the map before accessing new
|
||||
/// vertices.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let v1 = graph.add_vertex();
|
||||
/// let v2 = graph.add_vertex();
|
||||
/// let mut labels = graph.vertex_map("z");
|
||||
/// assert_eq!(labels[v1], "z");
|
||||
/// labels[v1] = "a";
|
||||
/// assert_eq!(labels[v1], "a");
|
||||
/// assert_eq!(labels[v2], "z");
|
||||
/// labels[v2] = "b";
|
||||
/// assert_eq!(labels[v2], "b");
|
||||
/// ```
|
||||
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T>;
|
||||
|
||||
/// Returns the number of edges in the graph.
|
||||
fn edge_count(&self) -> usize;
|
||||
fn edge_capacity(&self) -> usize;
|
||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T>;
|
||||
|
||||
/// Creates and returns an [`ElementMap`] for the edges with every slot initialised to `default`.
|
||||
///
|
||||
/// The returned map only covers the edges that exist in the graph at the time of the call. Use
|
||||
/// [`GraphTopologyAddition::sync_edge_map`] to extend the map before accessing new edges.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let v1 = graph.add_vertex();
|
||||
/// let v2 = graph.add_vertex();
|
||||
/// let e1 = graph.add_edge(v1, v2);
|
||||
/// let e2 = graph.add_edge(v1, v2);
|
||||
/// let mut weights = graph.edge_map(5);
|
||||
/// assert_eq!(weights[e1], 5);
|
||||
/// weights[e1] = 1;
|
||||
/// assert_eq!(weights[e1], 1);
|
||||
/// assert_eq!(weights[e2], 5);
|
||||
/// weights[e2] = 2;
|
||||
/// assert_eq!(weights[e2], 2);
|
||||
/// ```
|
||||
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.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn degree(&self, v: Self::Vertex) -> usize;
|
||||
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool;
|
||||
|
||||
/// Returns `true` if there is at least one edge between `u` and `v`, and `false` otherwise.
|
||||
///
|
||||
/// A vertex is adjacent to itself if and only if it has a loop edge.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `u` or `v` is not a valid vertex of this graph.
|
||||
fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool;
|
||||
|
||||
/// Returns an iterator over all vertices in the graph.
|
||||
fn vertices(&self) -> impl Iterator<Item = Self::Vertex>;
|
||||
|
||||
/// Returns an iterator over all vertices adjacent to `v`.
|
||||
///
|
||||
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the edges are not
|
||||
/// required, since implementors may be able to provide an iterator faster than the trivial
|
||||
/// mapping. The complexity of this method must not exceed that of
|
||||
/// [`incidences`](Self::incidences).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex>;
|
||||
|
||||
/// Returns the two endpoints of edge `e`, which are identical if and only if `e` is a loop
|
||||
/// edge.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `e` is not a valid edge of this graph.
|
||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex);
|
||||
|
||||
/// Returns an iterator over all edges in the graph.
|
||||
fn edges(&self) -> impl Iterator<Item = Self::Edge>;
|
||||
|
||||
/// Returns an iterator over all edges incident to `v`.
|
||||
///
|
||||
/// Algorithms may prefer this over [`incidences`](Self::incidences) if the vertices are not
|
||||
/// required, since implementors may be able to provide an iterator faster than the trivial
|
||||
/// mapping. The complexity of this method must not exceed that of
|
||||
/// [`incidences`](Self::incidences).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge>;
|
||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)>;
|
||||
|
||||
/// Returns an iterator over all incidences of `v`, i.e. vertex-edge pairs `(u, e)` such that
|
||||
/// `u` is adjacent to `v` and `e` is an edge between them.
|
||||
///
|
||||
/// Use [`incidence_cursor`](Self::incidence_cursor) instead for a traversal that needs to
|
||||
/// suspend and resume across other state updates.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn incidences(
|
||||
&self,
|
||||
v: Self::Vertex,
|
||||
) -> impl Iterator<Item = Incidence<Self::Vertex, Self::Edge>>;
|
||||
|
||||
/// Returns a cursor over all incidences of `v`, analogously to
|
||||
/// [`incidences`](Self::incidences), initially positioned before the first one.
|
||||
///
|
||||
/// See [`IncidenceCursor`](Self::IncidenceCursor) for when to prefer a cursor over
|
||||
/// [`incidences`](Self::incidences).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
|
||||
fn add_vertex(&mut self) -> Self::Vertex;
|
||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge;
|
||||
}
|
||||
|
||||
/// A trait that adds construction operations to an undirected graph topology.
|
||||
///
|
||||
/// This trait provides methods for addition of vertices and edges, and capacity management in an
|
||||
/// undirected graph.
|
||||
pub trait GraphTopologyAddition: GraphTopology + Default {
|
||||
/// Returns the total number of vertices the graph can hold without reallocating.
|
||||
///
|
||||
/// For every valid [`Vertex`] handle, the index produced by the conversion function used in
|
||||
/// [`GraphTopology::vertex_map`] must be strictly less than this capacity. Implementors of
|
||||
/// [`GraphTopology::vertex_map`] must size the returned vertex [`ElementMap`] to this capacity.
|
||||
///
|
||||
/// [`Vertex`]: GraphTopology::Vertex
|
||||
fn vertex_capacity(&self) -> usize;
|
||||
|
||||
/// Returns the total number of edges the graph can hold without reallocating.
|
||||
///
|
||||
/// For every valid [`Edge`] handle, the index produced by the conversion function used in
|
||||
/// [`GraphTopology::edge_map`] must be strictly less than this capacity. Implementors of
|
||||
/// [`GraphTopology::edge_map`] must size the returned edge [`ElementMap`] to this capacity.
|
||||
///
|
||||
/// [`Edge`]: GraphTopology::Edge
|
||||
fn edge_capacity(&self) -> usize;
|
||||
|
||||
/// Reserves capacity for at least `additional` more vertices. Does nothing if capacity is
|
||||
/// already sufficient.
|
||||
///
|
||||
/// Use this before adding multiple vertices to avoid incremental growth on each call to
|
||||
/// [`add_vertex`].
|
||||
///
|
||||
/// [`add_vertex`]: GraphTopologyAddition::add_vertex
|
||||
fn reserve_vertices(&mut self, additional: usize);
|
||||
|
||||
/// Reserves capacity for at least `additional` more edges. Does nothing if capacity is already
|
||||
/// sufficient.
|
||||
///
|
||||
/// Use this before adding multiple edges to avoid incremental growth on each call to
|
||||
/// [`add_edge`].
|
||||
///
|
||||
/// [`add_edge`]: GraphTopologyAddition::add_edge
|
||||
fn reserve_edges(&mut self, additional: usize);
|
||||
|
||||
/// Adds a new isolated vertex and returns its handle.
|
||||
fn add_vertex(&mut self) -> Self::Vertex;
|
||||
|
||||
/// Adds a new edge between `u` and `v` and returns its handle.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `u` or `v` is not a valid vertex of this graph.
|
||||
fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge;
|
||||
|
||||
/// Grows `map` so that it covers a number of vertices up to at least [`vertex_capacity`].
|
||||
///
|
||||
/// The data for any newly covered vertices is initialised to the map's original default value.
|
||||
/// Does nothing if `map` already covers enough vertices.
|
||||
///
|
||||
/// An [`ElementMap`] does not grow on its own, so call this after [`reserve_vertices`] (or
|
||||
/// after vertices have already been added) and before accessing `map` for any newly added
|
||||
/// vertices.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let mut labels = graph.vertex_map("z");
|
||||
/// graph.reserve_vertices(1);
|
||||
/// graph.sync_vertex_map(&mut labels);
|
||||
/// let v = graph.add_vertex();
|
||||
/// labels[v] = "a";
|
||||
/// assert_eq!(labels[v], "a");
|
||||
/// ```
|
||||
///
|
||||
/// [`reserve_vertices`]: Self::reserve_vertices
|
||||
/// [`vertex_capacity`]: Self::vertex_capacity
|
||||
fn sync_vertex_map<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.
|
||||
///
|
||||
/// This trait provides methods for deletion of vertices and edges in an undirected graph. These
|
||||
/// operations will invalidate handles to all deleted vertices and edges. Methods accepting invalid
|
||||
/// vertices or edges as parameters panic.
|
||||
pub trait GraphTopologyDeletion: GraphTopology {
|
||||
/// Deletes the vertex `v` and all its incident edges from the graph. Note that this also
|
||||
/// invalidates the handles of all edges incident to `v`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn delete_vertex(&mut self, v: Self::Vertex);
|
||||
|
||||
/// Deletes the edge `e` from the graph. This operation only invalidates `e` and no other vertex
|
||||
/// or edge handles.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `e` is not a valid edge of this graph.
|
||||
fn delete_edge(&mut self, e: Self::Edge);
|
||||
}
|
||||
|
||||
pub trait IncidenceCursor<G: GraphTopology + ?Sized> {
|
||||
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
|
||||
/// A cursor for traversing the incidences of a vertex one step at a time.
|
||||
///
|
||||
/// Because the cursor is [`Copy`], its state can be saved and restored to replay or branch a
|
||||
/// traversal.
|
||||
///
|
||||
/// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See
|
||||
/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over
|
||||
/// [`GraphTopology::incidences`].
|
||||
pub trait IncidenceCursor<G: GraphTopology + ?Sized>: Copy {
|
||||
/// Advances the cursor and returns the next incidence, or `None` if the traversal is
|
||||
/// exhausted.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// // Constructs a graph with two vertices connected to `v`.
|
||||
/// let mut graph = Graph::new();
|
||||
/// let v = graph.add_vertex();
|
||||
/// for _ in 0..2 {
|
||||
/// let u = graph.add_vertex();
|
||||
/// graph.add_edge(u, v);
|
||||
/// }
|
||||
///
|
||||
/// // Iterates over the incidences of `v` with a cursor.
|
||||
/// let mut c1 = graph.incidence_cursor(v);
|
||||
/// assert!(c1.next(&graph).is_some());
|
||||
/// let mut c2 = c1;
|
||||
/// // Continues iteration with original cursor.
|
||||
/// assert!(c1.next(&graph).is_some());
|
||||
/// assert!(c1.next(&graph).is_none());
|
||||
/// // Iterates over the last incidence again with the copied cursor.
|
||||
/// assert!(c2.next(&graph).is_some());
|
||||
/// assert!(c2.next(&graph).is_none());
|
||||
/// ```
|
||||
// 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
@@ -1,13 +1,348 @@
|
||||
mod append_graph_tests {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use grapherity::algorithms;
|
||||
use grapherity::testing::fixtures;
|
||||
use grapherity::testing::fixtures::MakeTestGraph;
|
||||
|
||||
macro_rules! bfs_tests {
|
||||
($T:ty) => {
|
||||
bfs_tests!(@wrap $T,
|
||||
bfs_single_vertex,
|
||||
bfs_disconnected,
|
||||
bfs,
|
||||
bfs_distances_single_vertex,
|
||||
bfs_distances_disconnected,
|
||||
bfs_distances,
|
||||
bfs_find_source,
|
||||
bfs_find_disconnected,
|
||||
bfs_find,
|
||||
bfs_find_where_source_matches,
|
||||
bfs_find_where_disconnected,
|
||||
bfs_find_where_no_match,
|
||||
bfs_find_where_nearest,
|
||||
bfs_find_path_source_equals_target,
|
||||
bfs_find_path_disconnected,
|
||||
bfs_find_path_adjacent,
|
||||
bfs_find_path,
|
||||
bfs_find_path_where_source_matches,
|
||||
bfs_find_path_where_disconnected,
|
||||
bfs_find_path_where_no_match,
|
||||
bfs_find_path_where,
|
||||
);
|
||||
};
|
||||
|
||||
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
super::$name::<$T>();
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
mod frozen_graph {
|
||||
use grapherity::models::FrozenGraph;
|
||||
|
||||
bfs_tests!(FrozenGraph);
|
||||
}
|
||||
|
||||
mod append_graph {
|
||||
use grapherity::models::AppendGraph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
||||
grapherity::bfs_tests!(AppendGraph);
|
||||
bfs_tests!(AppendGraph);
|
||||
}
|
||||
|
||||
mod graph_tests {
|
||||
mod graph {
|
||||
use grapherity::models::Graph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(Graph);
|
||||
grapherity::bfs_tests!(Graph);
|
||||
bfs_tests!(Graph);
|
||||
}
|
||||
|
||||
fn bfs_single_vertex<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
@@ -1,13 +1,358 @@
|
||||
mod append_graph_tests {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use grapherity::algorithms;
|
||||
use grapherity::maps::ElementMap;
|
||||
use grapherity::testing::fixtures;
|
||||
use grapherity::testing::fixtures::MakeTestGraph;
|
||||
use grapherity::traits::GraphTopology;
|
||||
|
||||
macro_rules! dfs_tests {
|
||||
($T:ty) => {
|
||||
dfs_tests!(@wrap $T,
|
||||
dfs_single_vertex,
|
||||
dfs_disconnected,
|
||||
dfs,
|
||||
dfs_visited_single_vertex,
|
||||
dfs_visited_disconnected,
|
||||
dfs_visited,
|
||||
dfs_find_source,
|
||||
dfs_find_disconnected,
|
||||
dfs_find,
|
||||
dfs_find_where_source_matches,
|
||||
dfs_find_where_disconnected,
|
||||
dfs_find_where_no_match,
|
||||
dfs_find_where_adjacent,
|
||||
dfs_find_where,
|
||||
dfs_find_path_source_equals_target,
|
||||
dfs_find_path_disconnected,
|
||||
dfs_find_path_adjacent,
|
||||
dfs_find_path,
|
||||
dfs_find_path_where_source_matches,
|
||||
dfs_find_path_where_disconnected,
|
||||
dfs_find_path_where_no_match,
|
||||
dfs_find_path_where,
|
||||
);
|
||||
};
|
||||
|
||||
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
super::$name::<$T>();
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
mod frozen_graph {
|
||||
use grapherity::models::FrozenGraph;
|
||||
|
||||
dfs_tests!(FrozenGraph);
|
||||
}
|
||||
|
||||
mod append_graph {
|
||||
use grapherity::models::AppendGraph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
||||
grapherity::dfs_tests!(AppendGraph);
|
||||
dfs_tests!(AppendGraph);
|
||||
}
|
||||
|
||||
mod graph_tests {
|
||||
mod graph {
|
||||
use grapherity::models::Graph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(Graph);
|
||||
grapherity::dfs_tests!(Graph);
|
||||
dfs_tests!(Graph);
|
||||
}
|
||||
|
||||
fn dfs_single_vertex<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
@@ -1,13 +1,297 @@
|
||||
mod append_graph_tests {
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
use grapherity::algorithms;
|
||||
use grapherity::algorithms::DijkstraResult;
|
||||
use grapherity::maps::ElementMap;
|
||||
use grapherity::testing::fixtures;
|
||||
use grapherity::testing::fixtures::MakeTestGraph;
|
||||
use grapherity::traits::GraphTopology;
|
||||
|
||||
macro_rules! dijkstra_tests {
|
||||
($T:ty) => {
|
||||
dijkstra_tests!(@wrap $T,
|
||||
dijkstra_single_vertex,
|
||||
dijkstra_disconnected,
|
||||
dijkstra_zero_weight_loop,
|
||||
dijkstra,
|
||||
dijkstra_distances,
|
||||
dijkstra_unweighted_single_vertex,
|
||||
dijkstra_unweighted_disconnected,
|
||||
dijkstra_unweighted,
|
||||
dijkstra_distances_unweighted_single_vertex,
|
||||
dijkstra_distances_unweighted_disconnected,
|
||||
dijkstra_distances_unweighted,
|
||||
);
|
||||
};
|
||||
|
||||
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
super::$name::<$T>();
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
mod frozen_graph {
|
||||
use grapherity::models::FrozenGraph;
|
||||
|
||||
dijkstra_tests!(FrozenGraph);
|
||||
}
|
||||
|
||||
mod append_graph {
|
||||
use grapherity::models::AppendGraph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
||||
grapherity::dijkstra_tests!(AppendGraph);
|
||||
dijkstra_tests!(AppendGraph);
|
||||
}
|
||||
|
||||
mod graph_tests {
|
||||
mod graph {
|
||||
use grapherity::models::Graph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(Graph);
|
||||
grapherity::dijkstra_tests!(Graph);
|
||||
dijkstra_tests!(Graph);
|
||||
}
|
||||
|
||||
fn dijkstra_single_vertex<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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user