Compare commits
124
Commits
0.2.1
..
generators
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
783a82fc18 | ||
|
|
be7acfce1f | ||
|
|
f5bb3ad07a | ||
|
|
baa6775156 | ||
|
|
32fae0b24c | ||
|
|
026005fe46 | ||
|
|
421207a1cd | ||
|
|
9b11090e82 | ||
|
|
d1463544f7 | ||
|
|
ba3717f246 | ||
|
|
f34a7e6541 | ||
|
|
5fc586f58e | ||
|
|
0cd965db4f | ||
|
|
6f557a92aa | ||
|
|
536a39c82b | ||
|
|
6403862977 | ||
|
|
3ae0aa9b67 | ||
|
|
5b6ad13f0e | ||
|
|
f41b77903f | ||
|
|
3fb3e67e8e | ||
|
|
8c57f5d2c4 | ||
|
|
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]
|
[package]
|
||||||
name = "grapherity"
|
name = "grapherity"
|
||||||
version = "0.2.1"
|
version = "0.3.1"
|
||||||
authors = ["Stefan Müller"]
|
authors = ["Stefan Müller"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
@@ -11,4 +11,5 @@ keywords = ["graph", "graph-algorithms"]
|
|||||||
categories = ["algorithms", "data-structures", "mathematics"]
|
categories = ["algorithms", "data-structures", "mathematics"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
priority-queue = "2.7.0"
|
||||||
typed-generational-arena = "0.2.9"
|
typed-generational-arena = "0.2.9"
|
||||||
|
|||||||
@@ -1,14 +1,69 @@
|
|||||||
# grapherity
|
# 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
|
## License
|
||||||
|
|
||||||
This project is dual-licensed under the terms of both:
|
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)
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit)
|
||||||
|
|
||||||
You may choose either license for your use of this software.
|
You may choose either license for your use of this software.
|
||||||
|
|||||||
+678
-94
@@ -1,40 +1,122 @@
|
|||||||
use std::cmp::Ordering;
|
//! Algorithms for graph topologies.
|
||||||
use std::collections::{BinaryHeap, VecDeque};
|
//!
|
||||||
|
//! # 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 priority_queue::PriorityQueue;
|
||||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
use std::cmp::Reverse;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
use crate::maps::ElementMap;
|
||||||
struct DistanceOrderedVertex<V> {
|
use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
|
||||||
distance: u32,
|
|
||||||
vertex: V,
|
/// Trait that edge weights for [`dijkstra`] and its variants need to satisfy.
|
||||||
|
///
|
||||||
|
/// [`Default`] is used as the starting distance for `source`, so it must behave as the additive
|
||||||
|
/// identity (typically zero) for accumulation via [`Add`] to give correct results.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::prelude::*;
|
||||||
|
/// # use grapherity::algorithms::dijkstra;
|
||||||
|
/// # use grapherity::models::Graph;
|
||||||
|
/// use std::time::Duration;
|
||||||
|
///
|
||||||
|
/// let mut graph = Graph::new();
|
||||||
|
/// let source = graph.add_vertex();
|
||||||
|
/// let target = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(source, target);
|
||||||
|
/// let mut travel_time = graph.edge_map(Duration::ZERO);
|
||||||
|
/// travel_time[e] = Duration::from_secs(5);
|
||||||
|
///
|
||||||
|
/// let result = dijkstra(&graph, source, |e| travel_time[e]);
|
||||||
|
/// assert_eq!(result.distances[target], Some(Duration::from_secs(5)));
|
||||||
|
/// ```
|
||||||
|
pub trait Weight: Copy + Ord + Add<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> {
|
// 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.
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
/// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors.
|
||||||
Some(self.cmp(other))
|
///
|
||||||
}
|
/// 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
|
||||||
impl<V: Eq> Ord for DistanceOrderedVertex<V> {
|
/// not connected to `source`.
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
///
|
||||||
other.distance.cmp(&self.distance)
|
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||||
}
|
///
|
||||||
}
|
/// # Panics
|
||||||
|
///
|
||||||
pub struct DijkstraResult<V: Copy> {
|
/// Panics if `source` is not a valid vertex of `graph`.
|
||||||
pub distances: VertexMap<V, Option<u32>>,
|
///
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
/// # Examples
|
||||||
}
|
///
|
||||||
|
/// ```
|
||||||
// TODO: Generalize the return type of the weight function.
|
/// # use grapherity::prelude::*;
|
||||||
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult<G::Vertex>
|
/// # 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
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
W: Fn(G::Edge) -> u32,
|
G::Vertex: Hash,
|
||||||
|
W: Weight,
|
||||||
{
|
{
|
||||||
let mut predecessors = graph.vertex_map(None);
|
let mut predecessors = graph.vertex_map(None);
|
||||||
let distances = dijkstra_impl(graph, source, weight, |adjacent, predecessor| {
|
let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| {
|
||||||
predecessors[adjacent] = Some(predecessor);
|
predecessors[adjacent] = Some(predecessor);
|
||||||
});
|
});
|
||||||
DijkstraResult {
|
DijkstraResult {
|
||||||
@@ -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
|
where
|
||||||
G: GraphTopology,
|
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)
|
dijkstra(graph, source, |_| 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dijkstra_distances<G, W>(
|
/// [Dijkstra's algorithm] with unit edge weights, returns minimum distances.
|
||||||
graph: &G,
|
///
|
||||||
source: G::Vertex,
|
/// Calculates the shortest paths from `source` to all vertices in an unweighted `graph`. Returns
|
||||||
weight: W,
|
/// the distances from `source` to each vertex. Returns `None` for any vertex not connected to
|
||||||
) -> VertexMap<G::Vertex, Option<u32>>
|
/// `source`.
|
||||||
where
|
///
|
||||||
G: GraphTopology,
|
/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*.
|
||||||
W: Fn(G::Edge) -> u32,
|
///
|
||||||
{
|
/// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity.
|
||||||
dijkstra_impl(graph, source, weight, |_, _| {})
|
///
|
||||||
}
|
/// # 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>(
|
pub fn dijkstra_distances_unweighted<G>(
|
||||||
graph: &G,
|
graph: &G,
|
||||||
source: G::Vertex,
|
source: G::Vertex,
|
||||||
) -> VertexMap<G::Vertex, Option<u32>>
|
) -> ElementMap<G::Vertex, Option<u32>>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
|
G::Vertex: Hash,
|
||||||
{
|
{
|
||||||
dijkstra_distances(graph, source, |_| 1)
|
dijkstra_distances(graph, source, |_| 1)
|
||||||
}
|
}
|
||||||
@@ -75,70 +251,153 @@ where
|
|||||||
fn dijkstra_impl<G, W, F>(
|
fn dijkstra_impl<G, W, F>(
|
||||||
graph: &G,
|
graph: &G,
|
||||||
source: G::Vertex,
|
source: G::Vertex,
|
||||||
weight: W,
|
weights: impl Fn(G::Edge) -> W,
|
||||||
mut on_relax: F,
|
mut on_relax: F,
|
||||||
) -> VertexMap<G::Vertex, Option<u32>>
|
) -> ElementMap<G::Vertex, Option<W>>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
W: Fn(G::Edge) -> u32,
|
G::Vertex: Hash,
|
||||||
|
W: Weight,
|
||||||
F: FnMut(G::Vertex, G::Vertex),
|
F: FnMut(G::Vertex, G::Vertex),
|
||||||
{
|
{
|
||||||
let mut distances = graph.vertex_map(None);
|
let mut distances = graph.vertex_map(None);
|
||||||
let mut heap = BinaryHeap::new();
|
let mut heap = PriorityQueue::new();
|
||||||
|
|
||||||
distances[source] = Some(0);
|
distances[source] = Some(W::default());
|
||||||
heap.push(DistanceOrderedVertex {
|
heap.push(source, Reverse(W::default()));
|
||||||
vertex: source,
|
|
||||||
distance: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(v) = heap.pop() {
|
while let Some((v, Reverse(v_distance))) = heap.pop() {
|
||||||
for incidence in graph.incidences(v.vertex) {
|
for Incidence { vertex: u, edge: e } in graph.incidences(v) {
|
||||||
let new_distance = distances[v.vertex].unwrap() + weight(incidence.1);
|
let new_distance = weights(e) + v_distance;
|
||||||
if match distances[incidence.0] {
|
if match distances[u] {
|
||||||
None => true,
|
None => true,
|
||||||
Some(old_distance) if old_distance > new_distance => true,
|
Some(old_distance) if old_distance > new_distance => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
} {
|
} {
|
||||||
distances[incidence.0] = Some(new_distance);
|
distances[u] = Some(new_distance);
|
||||||
on_relax(incidence.0, v.vertex);
|
on_relax(u, v);
|
||||||
heap.push(DistanceOrderedVertex {
|
heap.push_increase(u, Reverse(new_distance));
|
||||||
vertex: incidence.0,
|
|
||||||
distance: new_distance,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
distances
|
distances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return data type for [`bfs`].
|
||||||
pub struct BfsResult<V: Copy> {
|
pub struct BfsResult<V: Copy> {
|
||||||
pub distances: VertexMap<V, Option<u32>>,
|
/// Vertex map of minimum distances from a given `source` vertex.
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
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>
|
pub fn bfs<G>(graph: &G, source: G::Vertex) -> BfsResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
{
|
{
|
||||||
let mut predecessors = graph.vertex_map(None);
|
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);
|
predecessors[neighbor] = Some(predecessor);
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
BfsResult {
|
BfsResult {
|
||||||
distances,
|
distances: result.distances,
|
||||||
predecessors,
|
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
|
where
|
||||||
G: GraphTopology,
|
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>
|
pub fn bfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<u32>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -146,6 +405,35 @@ where
|
|||||||
bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance)
|
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)>
|
pub fn bfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -154,23 +442,22 @@ where
|
|||||||
if predicate(source) {
|
if predicate(source) {
|
||||||
return Some((source, 0));
|
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>(
|
struct BfsImplResult<V: Copy> {
|
||||||
graph: &G,
|
distances: ElementMap<V, Option<u32>>,
|
||||||
source: G::Vertex,
|
found: Option<(V, u32)>,
|
||||||
mut on_discover: F,
|
}
|
||||||
) -> (VertexMap<G::Vertex, Option<u32>>, Option<(G::Vertex, u32)>)
|
|
||||||
|
fn bfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> BfsImplResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||||
{
|
{
|
||||||
let mut distances = graph.vertex_map(None);
|
let mut distances = graph.vertex_map(None);
|
||||||
let mut queue = VecDeque::new();
|
|
||||||
|
|
||||||
distances[source] = Some(0);
|
distances[source] = Some(0);
|
||||||
queue.push_back(source);
|
let mut queue = VecDeque::from([source]);
|
||||||
|
|
||||||
while let Some(v) = queue.pop_front() {
|
while let Some(v) = queue.pop_front() {
|
||||||
for neighbor in graph.adjacent_vertices(v) {
|
for neighbor in graph.adjacent_vertices(v) {
|
||||||
@@ -178,42 +465,243 @@ where
|
|||||||
let distance = distances[v].unwrap() + 1;
|
let distance = distances[v].unwrap() + 1;
|
||||||
distances[neighbor] = Some(distance);
|
distances[neighbor] = Some(distance);
|
||||||
if !on_discover(neighbor, v) {
|
if !on_discover(neighbor, v) {
|
||||||
return (distances, Some((neighbor, distance)));
|
return BfsImplResult {
|
||||||
|
distances,
|
||||||
|
found: Some((neighbor, distance)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
queue.push_back(neighbor);
|
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 struct DfsResult<V: Copy> {
|
||||||
pub visited: VertexMap<V, bool>,
|
/// Vertex map indicating which vertices were visited during the search.
|
||||||
pub predecessors: VertexMap<V, Option<V>>,
|
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>
|
pub fn dfs<G>(graph: &G, source: G::Vertex) -> DfsResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
{
|
{
|
||||||
let mut predecessors = graph.vertex_map(None);
|
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);
|
predecessors[neighbor] = Some(predecessor);
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
DfsResult {
|
DfsResult {
|
||||||
visited,
|
visited: result.visited,
|
||||||
predecessors,
|
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
|
where
|
||||||
G: GraphTopology,
|
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
|
pub fn dfs_find<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> bool
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -221,6 +709,33 @@ where
|
|||||||
dfs_find_where(graph, source, |v| v == target).is_some()
|
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>
|
pub fn dfs_find_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -229,14 +744,15 @@ where
|
|||||||
if predicate(source) {
|
if predicate(source) {
|
||||||
return Some(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>(
|
struct DfsImplResult<V: Copy> {
|
||||||
graph: &G,
|
visited: ElementMap<V, bool>,
|
||||||
source: G::Vertex,
|
found: Option<V>,
|
||||||
mut on_discover: F,
|
}
|
||||||
) -> (VertexMap<G::Vertex, bool>, Option<G::Vertex>)
|
|
||||||
|
fn dfs_impl<G, F>(graph: &G, source: G::Vertex, mut on_discover: F) -> DfsImplResult<G::Vertex>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||||
@@ -248,7 +764,10 @@ where
|
|||||||
while let Some((v, predecessor)) = stack.pop() {
|
while let Some((v, predecessor)) = stack.pop() {
|
||||||
if let Some(p) = predecessor {
|
if let Some(p) = predecessor {
|
||||||
if !on_discover(v, p) {
|
if !on_discover(v, p) {
|
||||||
return (visited, Some(v));
|
return DfsImplResult {
|
||||||
|
visited,
|
||||||
|
found: Some(v),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for neighbor in graph.adjacent_vertices(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>>
|
pub fn dfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -268,6 +819,36 @@ where
|
|||||||
dfs_find_path_where(graph, source, |v| v == target)
|
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>>
|
pub fn dfs_find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
|
||||||
where
|
where
|
||||||
G: GraphTopology,
|
G: GraphTopology,
|
||||||
@@ -295,7 +876,10 @@ where
|
|||||||
None => {
|
None => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
Some((neighbor, edge)) => {
|
Some(Incidence {
|
||||||
|
vertex: neighbor,
|
||||||
|
edge,
|
||||||
|
}) => {
|
||||||
if predicate(neighbor) {
|
if predicate(neighbor) {
|
||||||
let mut path: Vec<G::Edge> =
|
let mut path: Vec<G::Edge> =
|
||||||
stack.iter().filter_map(|f| f.arrival_edge).collect();
|
stack.iter().filter_map(|f| f.arrival_edge).collect();
|
||||||
|
|||||||
@@ -0,0 +1,590 @@
|
|||||||
|
//! Generators for common graphs.
|
||||||
|
|
||||||
|
use crate::models::AppendGraph;
|
||||||
|
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion};
|
||||||
|
|
||||||
|
/// Builds a graph of type `G` by running a generator on a temporary graph.
|
||||||
|
///
|
||||||
|
/// `generate_as` fills a temporary [`AppendGraph`] and converts it to `G`. It returns the handles
|
||||||
|
/// of the elements it created as handles of the resulting graph. This makes it possible to obtain
|
||||||
|
/// graph types directly that do not implement [`GraphTopologyAddition`], such as [`FrozenGraph`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
/// use grapherity::generators;
|
||||||
|
/// use grapherity::models::FrozenGraph;
|
||||||
|
///
|
||||||
|
/// // Generates a parameterless Petersen graph as a FrozenGraph model.
|
||||||
|
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph>(generators::petersen);
|
||||||
|
/// assert_eq!(graph.vertex_count(), 10);
|
||||||
|
/// assert!(graph.vertices().all(|v| graph.degree(v) == 3));
|
||||||
|
///
|
||||||
|
/// // Generates a parameterized complete graph as a FrozenGraph model.
|
||||||
|
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph>(|g| generators::complete(g, 5));
|
||||||
|
/// assert_eq!(graph.vertex_count(), 5);
|
||||||
|
/// assert!(graph.vertices().all(|v| graph.degree(v) == 4));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
||||||
|
pub fn generate_as<G>(
|
||||||
|
generator: impl FnOnce(
|
||||||
|
&mut AppendGraph,
|
||||||
|
) -> (
|
||||||
|
Vec<<AppendGraph as GraphTopology>::Vertex>,
|
||||||
|
Vec<<AppendGraph as GraphTopology>::Edge>,
|
||||||
|
),
|
||||||
|
) -> (G, Vec<G::Vertex>, Vec<G::Edge>)
|
||||||
|
where
|
||||||
|
G: GraphTopologyConversion,
|
||||||
|
{
|
||||||
|
let mut builder = AppendGraph::new();
|
||||||
|
let (vertices, edges) = generator(&mut builder);
|
||||||
|
let (graph, iso) = G::convert(&builder);
|
||||||
|
(
|
||||||
|
graph,
|
||||||
|
vertices.into_iter().map(|v| iso.vertex(v)).collect(),
|
||||||
|
edges.into_iter().map(|e| iso.edge(e)).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a path graph.
|
||||||
|
///
|
||||||
|
/// Generates a [path graph] *P(n)* with `n` vertices and `n - 1` edges in `graph` as a new
|
||||||
|
/// connected component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges
|
||||||
|
/// *e0, e1, ..., e(n - 2)* in order, i.e. *e0 = (v0, v1)*, *e1 = (v1, v2)*, etc. and *v0* and
|
||||||
|
/// *v(n-1)* being the two terminal vertices
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex and no edges are added.
|
||||||
|
///
|
||||||
|
/// [Path graph]: https://en.wikipedia.org/wiki/Path_graph
|
||||||
|
pub fn path<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
let edge_capacity = n.saturating_sub(1);
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(edge_capacity);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let edges = (0..edge_capacity)
|
||||||
|
.map(|i| graph.add_edge(vertices[i], vertices[i + 1]))
|
||||||
|
.collect();
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a cycle graph.
|
||||||
|
///
|
||||||
|
/// Generates a [cycle graph] *C(n)* with `n` vertices and `n` edges in `graph` as a new connected
|
||||||
|
/// component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges *e0, e1, ..., e(n - 1)*
|
||||||
|
/// in order, i.e. *e0 = (v0, v1), e1 = (v1, v2), ..., e(n - 1) = (v(n - 1), v0)*.
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, a single vertex with a loop is added. For
|
||||||
|
/// `n == 2`, two vertices and two edges between them (multi-edge) are added.
|
||||||
|
///
|
||||||
|
/// [Cycle graph]: https://en.wikipedia.org/wiki/Cycle_graph
|
||||||
|
pub fn cycle<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(n);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let edges = (0..n)
|
||||||
|
.map(|i| graph.add_edge(vertices[i], vertices[if i < n - 1 { i + 1 } else { 0 }]))
|
||||||
|
.collect();
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a complete graph.
|
||||||
|
///
|
||||||
|
/// Generates a [complete graph] *K(n)* with `n` vertices and `n * (n - 1) / 2` edges in `graph` as
|
||||||
|
/// a new connected component and returns the new vertices and edges.
|
||||||
|
///
|
||||||
|
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex is added.
|
||||||
|
///
|
||||||
|
/// [Complete graph]: https://en.wikipedia.org/wiki/Complete_graph
|
||||||
|
pub fn complete<G: GraphTopologyAddition>(
|
||||||
|
graph: &mut G,
|
||||||
|
n: usize,
|
||||||
|
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
let edge_capacity = n * n.saturating_sub(1) / 2;
|
||||||
|
graph.reserve_vertices(n);
|
||||||
|
graph.reserve_edges(edge_capacity);
|
||||||
|
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||||
|
let mut edges = Vec::with_capacity(edge_capacity);
|
||||||
|
for (i, &u) in vertices.iter().enumerate() {
|
||||||
|
for &v in &vertices[i + 1..] {
|
||||||
|
edges.push(graph.add_edge(u, v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: GraphTopologyAddition>(graph: &mut G) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
const N: usize = 5;
|
||||||
|
const K: usize = 2;
|
||||||
|
|
||||||
|
graph.reserve_vertices(10);
|
||||||
|
graph.reserve_edges(15);
|
||||||
|
let vertices: Vec<_> = (0..10).map(|_| graph.add_vertex()).collect();
|
||||||
|
let edges = (0..15)
|
||||||
|
.map(|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]),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(vertices, edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Spec {
|
||||||
|
Path(usize),
|
||||||
|
Cycle(usize),
|
||||||
|
Complete(usize),
|
||||||
|
Petersen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spec {
|
||||||
|
fn generate<G: GraphTopologyAddition>(
|
||||||
|
&self,
|
||||||
|
graph: &mut G,
|
||||||
|
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||||
|
match *self {
|
||||||
|
Spec::Path(n) => path(graph, n),
|
||||||
|
Spec::Cycle(n) => cycle(graph, n),
|
||||||
|
Spec::Complete(n) => complete(graph, n),
|
||||||
|
Spec::Petersen => petersen(graph),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert<G: GraphTopology>(&self, built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Spec::Path(n) => assert_path(built, n),
|
||||||
|
Spec::Cycle(n) => assert_cycle(built, n),
|
||||||
|
Spec::Complete(n) => assert_complete(built, n),
|
||||||
|
Spec::Petersen => assert_petersen(built),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Built<G: GraphTopology> {
|
||||||
|
graph: G,
|
||||||
|
vertices: Vec<G::Vertex>,
|
||||||
|
edges: Vec<G::Edge>,
|
||||||
|
initial_vertex_count: usize,
|
||||||
|
initial_edge_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the test for the `append_graph` harness for the given `Spec` expression.
|
||||||
|
macro_rules! min_harness_tests {
|
||||||
|
($spec:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn append_graph() {
|
||||||
|
run($spec, add_to_empty::<$crate::models::AppendGraph>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates one test per harness for the given `Spec` expression.
|
||||||
|
macro_rules! all_harness_tests {
|
||||||
|
($spec:expr) => {
|
||||||
|
min_harness_tests!($spec);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_graph_non_empty() {
|
||||||
|
run($spec, add_to_standard::<$crate::models::AppendGraph>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graph() {
|
||||||
|
run($spec, add_to_empty::<$crate::models::Graph>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graph_non_empty() {
|
||||||
|
run($spec, add_to_standard::<$crate::models::Graph>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frozen_graph() {
|
||||||
|
run($spec, conv_generate_as::<$crate::models::FrozenGraph>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the module tree `<group>::<case>::<harness>` for each generator case.
|
||||||
|
///
|
||||||
|
/// The `all_harnesses` cases run on every harness, the `min_harnesses` cases only on a reduced
|
||||||
|
/// set of harnesses.
|
||||||
|
macro_rules! generator_tests {
|
||||||
|
($($group:ident {
|
||||||
|
all { $($all_case:ident => $all_generator:expr),+ $(,)? }
|
||||||
|
min { $($min_case:ident => $min_generator:expr),* $(,)? }
|
||||||
|
})+) => {
|
||||||
|
$(
|
||||||
|
mod $group {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
mod $all_case {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
all_harness_tests!($all_generator);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
|
||||||
|
$(
|
||||||
|
mod $min_case {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
min_harness_tests!($min_generator);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generator_tests! {
|
||||||
|
path {
|
||||||
|
all {
|
||||||
|
small => Spec::Path(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Path(0),
|
||||||
|
single_vertex => Spec::Path(1),
|
||||||
|
single_edge => Spec::Path(2),
|
||||||
|
large => Spec::Path(21),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cycle {
|
||||||
|
all {
|
||||||
|
small_even => Spec::Cycle(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Cycle(0),
|
||||||
|
loop_edge => Spec::Cycle(1),
|
||||||
|
multi_edge => Spec::Cycle(2),
|
||||||
|
large_odd => Spec::Cycle(21),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
complete {
|
||||||
|
all {
|
||||||
|
small_even => Spec::Complete(4),
|
||||||
|
}
|
||||||
|
min {
|
||||||
|
empty => Spec::Complete(0),
|
||||||
|
single_vertex => Spec::Complete(1),
|
||||||
|
single_edge => Spec::Complete(2),
|
||||||
|
large_odd => Spec::Complete(13),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod petersen {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
all_harness_tests!(Spec::Petersen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a graph with `harness` and asserts that it matches what `spec` should produce.
|
||||||
|
fn run<G: GraphTopology>(spec: Spec, harness: impl FnOnce(&Spec) -> Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let built = harness(&spec);
|
||||||
|
spec.assert(&built);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` on an empty graph of type `G`.
|
||||||
|
fn add_to_empty<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||||
|
let mut graph = G::default();
|
||||||
|
let (vertices, edges) = spec.generate(&mut graph);
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: 0,
|
||||||
|
initial_edge_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` on a non-empty test graph of type `G`.
|
||||||
|
fn add_to_standard<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||||
|
let (mut graph, initial_vertices, initial_edges, _) = G::standard();
|
||||||
|
let (vertices, edges) = spec.generate(&mut graph);
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: initial_vertices.len(),
|
||||||
|
initial_edge_count: initial_edges.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `spec` within `generate_as` for type `G`.
|
||||||
|
fn conv_generate_as<G: GraphTopologyConversion>(spec: &Spec) -> Built<G> {
|
||||||
|
let (graph, vertices, edges) = generate_as::<G>(|graph| spec.generate(graph));
|
||||||
|
Built {
|
||||||
|
graph,
|
||||||
|
vertices,
|
||||||
|
edges,
|
||||||
|
initial_vertex_count: 0,
|
||||||
|
initial_edge_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_path<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n.saturating_sub(1));
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
assert_no_loops(built);
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
match n {
|
||||||
|
0 => {}
|
||||||
|
1 => assert_eq!(
|
||||||
|
built.graph.degree(built.vertices[0]),
|
||||||
|
0,
|
||||||
|
"path n == 1 should be an isolated vertex"
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
for (i, &v) in built.vertices.iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.degree(v),
|
||||||
|
if i == 0 || i == n - 1 { 1 } else { 2 },
|
||||||
|
"unexpected degree of vertex {v:?} in path n == {n}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_path_order(built);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_cycle<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
if n != 1 {
|
||||||
|
assert_no_loops(built);
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
built.vertices.iter().all(|&v| built.graph.degree(v) == 2),
|
||||||
|
"all new vertices should have degree 2"
|
||||||
|
);
|
||||||
|
assert_path_order(built);
|
||||||
|
// Asserts the closing edge e(n - 1) == (v(n - 1), v0).
|
||||||
|
if n > 0 {
|
||||||
|
// For n == 1, u == v and this validates the loop. For n == 2, the above asserted one of
|
||||||
|
// the two multi-edges, and this asserts the other.
|
||||||
|
let e = built.edges[n - 1];
|
||||||
|
let u = built.vertices[0];
|
||||||
|
let v = built.vertices[n - 1];
|
||||||
|
let incident_vertices = built.graph.incident_vertices(e);
|
||||||
|
assert!(
|
||||||
|
incident_vertices == (u, v) || incident_vertices == (v, u),
|
||||||
|
"edge {e:?} should have incident vertices {u:?} and {v:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_complete<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, n, n * n.saturating_sub(1) / 2);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
// The predicate in `all` is only executed if `vertices` has any elements, i.e. for n > 0.
|
||||||
|
assert!(
|
||||||
|
built
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.all(|&v| built.graph.degree(v) == n - 1),
|
||||||
|
"all new vertices should have degree {}",
|
||||||
|
n - 1
|
||||||
|
);
|
||||||
|
// Asserts that all pairs of vertices have exactly one edge between them. This also includes
|
||||||
|
// checks for loops and multi-edges. If there are no duplicates, then this implies that all
|
||||||
|
// expected edges were also matched due to the validated counts from above.
|
||||||
|
let mut seen = vec![false; n * n];
|
||||||
|
let position = |v, e| {
|
||||||
|
built
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.position(|&x| x == v)
|
||||||
|
.unwrap_or_else(|| panic!("edge {e:?} has an invalid incident vertex {v:?}"))
|
||||||
|
};
|
||||||
|
for &e in &built.edges {
|
||||||
|
let (u, v) = built.graph.incident_vertices(e);
|
||||||
|
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||||
|
let (i, j) = (position(u, e), position(v, e));
|
||||||
|
let (i, j) = (i.min(j), i.max(j));
|
||||||
|
assert!(
|
||||||
|
!seen[i * n + j],
|
||||||
|
"unexpected multi-edge {e:?} between vertices {u:?} and {v:?}"
|
||||||
|
);
|
||||||
|
seen[i * n + j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_petersen<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
assert_element_counts(built, 10, 15);
|
||||||
|
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||||
|
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||||
|
assert_no_loops(built);
|
||||||
|
assert_no_multi_edges(built);
|
||||||
|
assert!(
|
||||||
|
built.vertices.iter().all(|&v| built.graph.degree(v) == 3),
|
||||||
|
"all new vertices should have degree 3"
|
||||||
|
);
|
||||||
|
// Checks that each pair of vertices (u, v) has no common neighbor if they are adjacent, and
|
||||||
|
// exactly one common neighbor otherwise.
|
||||||
|
for (i, &v) in built.vertices.iter().enumerate() {
|
||||||
|
for &u in &built.vertices[i + 1..] {
|
||||||
|
if built.graph.are_adjacent(v, u) {
|
||||||
|
assert!(
|
||||||
|
built.graph.common_neighbors(u, v).next().is_none(),
|
||||||
|
"adjacent vertices {u:?} and {v:?} should not have a common neighbor"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.common_neighbors(u, v).count(),
|
||||||
|
1,
|
||||||
|
"non-adjacent vertices {u:?} and {v:?} should have exactly one common neighbor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_element_counts<G: GraphTopology>(
|
||||||
|
built: &Built<G>,
|
||||||
|
expected_vertex_count: usize,
|
||||||
|
expected_edge_count: usize,
|
||||||
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.vertex_count(),
|
||||||
|
built.initial_vertex_count + expected_vertex_count,
|
||||||
|
"unexpected number of added vertices"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.vertices.len(),
|
||||||
|
expected_vertex_count,
|
||||||
|
"unexpected number of returned vertices"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.graph.edge_count(),
|
||||||
|
built.initial_edge_count + expected_edge_count,
|
||||||
|
"unexpected number of added edges"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
built.edges.len(),
|
||||||
|
expected_edge_count,
|
||||||
|
"unexpected number of returned edges"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that every handle in `returned` is unique and in `all`.
|
||||||
|
fn assert_handles<T: PartialEq + Debug>(
|
||||||
|
all: impl Iterator<Item = T>,
|
||||||
|
returned: &[T],
|
||||||
|
kind: &str,
|
||||||
|
) {
|
||||||
|
let mut remaining: Vec<T> = all.collect();
|
||||||
|
for e in returned {
|
||||||
|
let Some(position) = remaining.iter().position(|x| x == e) else {
|
||||||
|
panic!("returned {kind} {e:?} is not in the graph or appears more than once");
|
||||||
|
};
|
||||||
|
remaining.swap_remove(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_no_loops<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
for &e in &built.edges {
|
||||||
|
let (u, v) = built.graph.incident_vertices(e);
|
||||||
|
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_no_multi_edges<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
for &v in &built.vertices {
|
||||||
|
let mut neighbors = Vec::new();
|
||||||
|
let mut loop_count = 0;
|
||||||
|
for u in built.graph.adjacent_vertices(v) {
|
||||||
|
if u == v {
|
||||||
|
loop_count += 1;
|
||||||
|
assert!(loop_count < 3, "unexpected multi-edge loop at vertex {v:?}");
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
!neighbors.contains(&u),
|
||||||
|
"unexpected multi-edge between vertices {v:?} and {u:?}"
|
||||||
|
);
|
||||||
|
neighbors.push(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that each returned edge *ei* connects the returned vertices *vi* and *v(i + 1)*.
|
||||||
|
fn assert_path_order<G: GraphTopology>(built: &Built<G>)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
for (w, &e) in built.vertices.windows(2).zip(&built.edges) {
|
||||||
|
let incident_vertices = built.graph.incident_vertices(e);
|
||||||
|
assert!(
|
||||||
|
incident_vertices == (w[0], w[1]) || incident_vertices == (w[1], w[0]),
|
||||||
|
"edge {e:?} should have incident vertices {:?} and {:?}",
|
||||||
|
w[0],
|
||||||
|
w[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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 algorithms;
|
||||||
|
pub mod generators;
|
||||||
pub mod maps;
|
pub mod maps;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
|
/// Convenience re-exports of graph topology traits for common use.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::traits::{GraphTopology, GraphTopologyDeletion};
|
pub use crate::traits::{
|
||||||
|
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
|
||||||
|
GraphTopologyDeletion, Incidence, IncidenceCursor,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mod testing;
|
#[doc(hidden)]
|
||||||
|
pub mod testing;
|
||||||
|
|||||||
+354
-94
@@ -1,122 +1,382 @@
|
|||||||
|
//! 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 std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::traits::GraphTopology;
|
/// A map to associate custom data to graph vertices or edges.
|
||||||
|
///
|
||||||
pub struct VertexMap<V: Copy, T: Clone> {
|
/// This map uses raw element indices to associate homogenous custom data of type `T` to graph
|
||||||
inner: EntityMap<V, T>,
|
/// 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.
|
||||||
impl<V: Copy, T: Clone> VertexMap<V, T> {
|
///
|
||||||
pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self {
|
/// Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`] to obtain an `ElementMap` for
|
||||||
Self {
|
/// vertices or edges, respectively.
|
||||||
inner: EntityMap::new(default, to_index, capacity),
|
///
|
||||||
}
|
/// 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.
|
||||||
// Reads beyond len() are valid and return the default value.
|
///
|
||||||
pub fn len(&self) -> usize {
|
/// No bound or validity checks are performed by the map on the provided element handles.
|
||||||
self.inner.len()
|
///
|
||||||
}
|
/// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map
|
||||||
|
/// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map
|
||||||
pub fn sync<G: GraphTopology<Vertex = V>>(&mut self, graph: &G) {
|
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||||
self.inner.resize(graph.vertex_capacity());
|
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||||
}
|
pub struct ElementMap<E: Copy, T: Clone> {
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
data: Vec<T>,
|
data: Vec<T>,
|
||||||
default: T,
|
default: T,
|
||||||
to_index: fn(E) -> usize,
|
to_index: fn(E) -> usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Copy, T: Clone> EntityMap<E, T> {
|
impl<E: Copy, T: Clone> ElementMap<E, T> {
|
||||||
pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self {
|
/// Creates a new map with the given `default` value, an index conversion function `to_index`,
|
||||||
|
/// and an initial `len`, i.e. the number of slots that can be read from and written to.
|
||||||
|
///
|
||||||
|
/// Since `to_index` is usually an implementation detail of the graph model, this is not meant
|
||||||
|
/// to be called directly. Use [`GraphTopology::vertex_map`] or [`GraphTopology::edge_map`]
|
||||||
|
/// instead.
|
||||||
|
///
|
||||||
|
/// [`GraphTopology::edge_map`]: crate::traits::GraphTopology::edge_map
|
||||||
|
/// [`GraphTopology::vertex_map`]: crate::traits::GraphTopology::vertex_map
|
||||||
|
pub fn new(default: T, to_index: fn(E) -> usize, len: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: vec![default.clone(); capacity],
|
data: vec![default.clone(); len],
|
||||||
default,
|
default,
|
||||||
to_index,
|
to_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
/// Grows the map so that it contains at least `new_len` slots for elements, initialising any
|
||||||
self.data.len()
|
/// newly added slots to the `default` value provided to [`new`], without modifying existing
|
||||||
|
/// data. Does nothing if the map already has at least `new_len` slots.
|
||||||
|
///
|
||||||
|
/// Since the number of elements the map can actually store depend on the index conversion
|
||||||
|
/// function, which is usually an implementation detail of the graph model, this is not meant to
|
||||||
|
/// be called directly. Use [`GraphTopologyAddition::sync_vertex_map`] or
|
||||||
|
/// [`GraphTopologyAddition::sync_edge_map`] instead.
|
||||||
|
///
|
||||||
|
/// [`new`]: ElementMap::new
|
||||||
|
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||||
|
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||||
|
pub fn grow(&mut self, new_len: usize) {
|
||||||
|
if new_len > self.data.len() {
|
||||||
|
self.data.resize(new_len, self.default.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, capacity: usize) {
|
/// Returns a new map over the same elements with values transformed by `f`.
|
||||||
if capacity > self.data.len() {
|
///
|
||||||
self.data.resize(capacity, self.default.clone());
|
/// The default value of the new map is `f` applied to the original default, so slots later added
|
||||||
|
/// to the new map by [`GraphTopologyAddition::sync_vertex_map`] or
|
||||||
|
/// [`GraphTopologyAddition::sync_edge_map`] are filled consistently with the transformation.
|
||||||
|
///
|
||||||
|
/// [`GraphTopologyAddition::sync_vertex_map`]: crate::traits::GraphTopologyAddition::sync_vertex_map
|
||||||
|
/// [`GraphTopologyAddition::sync_edge_map`]: crate::traits::GraphTopologyAddition::sync_edge_map
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use grapherity::maps::ElementMap;
|
||||||
|
/// let mut map: ElementMap<usize, Option<i32>> = ElementMap::new(None, |e| e, 2);
|
||||||
|
/// map[1] = Some(42);
|
||||||
|
///
|
||||||
|
/// let bools = map.map(|v| v.is_some());
|
||||||
|
/// assert_eq!(bools[0], false);
|
||||||
|
/// assert_eq!(bools[1], true);
|
||||||
|
/// ```
|
||||||
|
pub fn map<U: Clone>(&self, f: impl Fn(&T) -> U) -> ElementMap<E, U> {
|
||||||
|
ElementMap {
|
||||||
|
data: self.data.iter().map(&f).collect(),
|
||||||
|
default: f(&self.default),
|
||||||
|
to_index: self.to_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `ElementMap` indexed by elements of type `F`, such that for each element *e*
|
||||||
|
/// of this map that `mapping` maps to some *f*, the new map contains at *f* a clone of the
|
||||||
|
/// value this map contains at *e*.
|
||||||
|
///
|
||||||
|
/// A slot of `mapping` counts as unmapped if it holds the default value of `mapping`, which
|
||||||
|
/// must therefore never be a valid element of `F`. Unmapped slots are skipped. Slots of the new
|
||||||
|
/// map that no element is mapped to, and slots of this map beyond the length of `mapping` or
|
||||||
|
/// vice versa, are left at this map's default value. The `template` is only used to determine
|
||||||
|
/// the length and the index conversion function of the new map.
|
||||||
|
///
|
||||||
|
/// Iterates the raw slots of both maps, so it neither needs the graph nor the element handles.
|
||||||
|
pub(crate) fn remap<F: Copy + Eq>(
|
||||||
|
&self,
|
||||||
|
mapping: &ElementMap<E, F>,
|
||||||
|
template: &ElementMap<F, ()>,
|
||||||
|
) -> ElementMap<F, T> {
|
||||||
|
let mut result = ElementMap {
|
||||||
|
data: vec![self.default.clone(); template.data.len()],
|
||||||
|
default: self.default.clone(),
|
||||||
|
to_index: template.to_index,
|
||||||
|
};
|
||||||
|
for (value, &f) in self.data.iter().zip(&mapping.data) {
|
||||||
|
if f != mapping.default {
|
||||||
|
result.data[(template.to_index)(f)] = value.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Copy, T: Clone> Index<E> for EntityMap<E, T> {
|
impl<E: Copy, T: Clone> Index<E> for ElementMap<E, T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
|
|
||||||
fn index(&self, e: E) -> &T {
|
fn index(&self, e: E) -> &T {
|
||||||
let i = (self.to_index)(e);
|
&self.data[(self.to_index)(e)]
|
||||||
if i < self.data.len() {
|
|
||||||
&self.data[i]
|
|
||||||
} else {
|
|
||||||
&self.default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Copy, T: Clone> IndexMut<E> for EntityMap<E, T> {
|
impl<E: Copy, T: Clone> IndexMut<E> for ElementMap<E, T> {
|
||||||
fn index_mut(&mut self, e: E) -> &mut T {
|
fn index_mut(&mut self, e: E) -> &mut T {
|
||||||
let i = (self.to_index)(e);
|
&mut self.data[(self.to_index)(e)]
|
||||||
if i >= self.data.len() {
|
}
|
||||||
self.data.resize(i + 1, self.default.clone());
|
}
|
||||||
}
|
|
||||||
&mut self.data[i]
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[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 append_graph;
|
||||||
|
pub mod frozen_graph;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
|
|
||||||
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
|
pub use append_graph::{
|
||||||
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
|
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};
|
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
||||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
|
||||||
|
|
||||||
#[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);
|
pub struct Vertex(usize);
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
// TODO: Benchmark the cost of the index shift. An alternative could be to init AppendGraph::incidences with two dummy entries to avoid 0.
|
||||||
pub struct Edge(usize);
|
/// An opaque handle identifying an edge in an [`AppendGraph`].
|
||||||
|
///
|
||||||
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
|
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub struct Edge(NonZeroUsize);
|
||||||
|
|
||||||
impl Edge {
|
impl Edge {
|
||||||
|
fn new(index: usize) -> Self {
|
||||||
|
Edge(NonZeroUsize::new(index + 1).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(self) -> usize {
|
||||||
|
self.0.get() - 1
|
||||||
|
}
|
||||||
|
|
||||||
fn normalize(&self) -> Self {
|
fn normalize(&self) -> Self {
|
||||||
Self(self.0 & !1)
|
Self::new(self.index() & !1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An [`ElementMap`] for [`AppendGraph`] vertices.
|
||||||
|
pub type AppendGraphVertexMap<T> = ElementMap<Vertex, T>;
|
||||||
|
|
||||||
|
/// An [`ElementMap`] for [`AppendGraph`] edges.
|
||||||
|
pub type AppendGraphEdgeMap<T> = ElementMap<Edge, T>;
|
||||||
|
|
||||||
|
/// An [`Incidence`] for [`AppendGraph`].
|
||||||
|
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct VertexIncidenceHeader {
|
struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<Edge>,
|
first_incidence: Option<Edge>,
|
||||||
@@ -27,23 +59,72 @@ struct IncidenceEntry {
|
|||||||
adjacent: Vertex,
|
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)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct AppendGraphIncidenceCursor {
|
pub struct AppendGraphIncidenceCursor {
|
||||||
incidence: Option<Edge>,
|
incidence: Option<Edge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
||||||
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
|
fn next(&mut self, graph: &AppendGraph) -> Option<AppendGraphIncidence> {
|
||||||
graph.step_incidence(&mut self.incidence)
|
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 {
|
pub struct AppendGraph {
|
||||||
vertices: Vec<VertexIncidenceHeader>,
|
vertices: Vec<VertexIncidenceHeader>,
|
||||||
incidences: Vec<IncidenceEntry>,
|
incidences: Vec<IncidenceEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppendGraph {
|
impl AppendGraph {
|
||||||
|
/// Creates an empty graph instance with no vertices or edges.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: vec![],
|
vertices: vec![],
|
||||||
@@ -51,15 +132,15 @@ impl AppendGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||||
// incidences vector.
|
/// incidences vector.
|
||||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
|
fn add_incidence(&mut self, u: Vertex, v: Vertex) {
|
||||||
self.incidences.push(IncidenceEntry {
|
self.incidences.push(IncidenceEntry {
|
||||||
next: self.vertices[v1.0].first_incidence.take(),
|
next: self.vertices[u.0].first_incidence.take(),
|
||||||
adjacent: v2,
|
adjacent: v,
|
||||||
});
|
});
|
||||||
self.vertices[v1.0].incidence_count += 1;
|
self.vertices[u.0].incidence_count += 1;
|
||||||
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1));
|
self.vertices[u.0].first_incidence = Some(Edge::new(self.incidences.len() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
||||||
@@ -69,7 +150,7 @@ impl AppendGraph {
|
|||||||
|
|
||||||
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
||||||
let current = (*incidence)?;
|
let current = (*incidence)?;
|
||||||
let entry = self.incidences[current.0];
|
let entry = self.incidences[current.index()];
|
||||||
*incidence = entry.next;
|
*incidence = entry.next;
|
||||||
Some((entry.adjacent, current))
|
Some((entry.adjacent, current))
|
||||||
}
|
}
|
||||||
@@ -90,32 +171,24 @@ impl GraphTopology for AppendGraph {
|
|||||||
self.vertices.len()
|
self.vertices.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertex_capacity(&self) -> usize {
|
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
|
||||||
self.vertices.len()
|
ElementMap::new(default, |v| v.0, self.vertex_capacity())
|
||||||
}
|
|
||||||
|
|
||||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
|
||||||
VertexMap::new(default, |v| v.0, self.vertex_capacity())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_count(&self) -> usize {
|
fn edge_count(&self) -> usize {
|
||||||
self.incidences.len() / 2
|
self.incidences.len() / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_capacity(&self) -> usize {
|
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||||
self.incidences.len() / 2
|
ElementMap::new(default, |e| e.index() / 2, self.edge_capacity())
|
||||||
}
|
|
||||||
|
|
||||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
|
||||||
EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn degree(&self, v: Self::Vertex) -> usize {
|
fn degree(&self, v: Self::Vertex) -> usize {
|
||||||
self.vertices[v.0].incidence_count
|
self.vertices[v.0].incidence_count
|
||||||
}
|
}
|
||||||
|
|
||||||
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
|
||||||
self.adjacent_vertices(v1).any(|x| x == v2)
|
self.adjacent_vertices(u).any(|x| x == v)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
||||||
@@ -127,21 +200,26 @@ impl GraphTopology for AppendGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||||
let v2 = self.incidences[e.0].adjacent;
|
let ei = e.index();
|
||||||
let v1 = self.incidences[e.0 ^ 1].adjacent;
|
(
|
||||||
(v1, v2)
|
self.incidences[ei ^ 1].adjacent,
|
||||||
|
self.incidences[ei].adjacent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
||||||
(0..self.incidences.len()).step_by(2).map(Edge)
|
(0..self.incidences.len()).step_by(2).map(Edge::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
||||||
self.raw_incidences(v).map(|(_, e)| e.normalize())
|
self.raw_incidences(v).map(|(_, e)| e.normalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
|
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = AppendGraphIncidence> {
|
||||||
self.raw_incidences(v).map(|(v, e)| (v, e.normalize()))
|
self.raw_incidences(v).map(|(v, e)| Incidence {
|
||||||
|
vertex: v,
|
||||||
|
edge: e.normalize(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
||||||
@@ -149,6 +227,24 @@ impl GraphTopology for AppendGraph {
|
|||||||
incidence: self.vertices[v.0].first_incidence,
|
incidence: self.vertices[v.0].first_incidence,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphTopologyAddition for AppendGraph {
|
||||||
|
fn vertex_capacity(&self) -> usize {
|
||||||
|
self.vertices.capacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edge_capacity(&self) -> usize {
|
||||||
|
self.incidences.capacity() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_vertices(&mut self, additional: usize) {
|
||||||
|
self.vertices.reserve(additional);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_edges(&mut self, additional: usize) {
|
||||||
|
self.incidences.reserve(additional * 2);
|
||||||
|
}
|
||||||
|
|
||||||
fn add_vertex(&mut self) -> Self::Vertex {
|
fn add_vertex(&mut self) -> Self::Vertex {
|
||||||
self.vertices.push(VertexIncidenceHeader {
|
self.vertices.push(VertexIncidenceHeader {
|
||||||
@@ -158,31 +254,58 @@ impl GraphTopology for AppendGraph {
|
|||||||
Vertex(self.vertices.len() - 1)
|
Vertex(self.vertices.len() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
|
||||||
self.add_incidence(v1, v2);
|
self.add_incidence(u, v);
|
||||||
self.add_incidence(v2, v1);
|
self.add_incidence(v, u);
|
||||||
Edge(self.incidences.len() - 2)
|
Edge::new(self.incidences.len() - 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GraphTopologyConversion for AppendGraph {
|
||||||
|
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
|
||||||
|
let mut edge_map = source.edge_map(Edge(NonZeroUsize::MAX));
|
||||||
|
let (result, vertex_map) =
|
||||||
|
models::convert_to_addition(source, Vertex(usize::MAX), |e, new_e| edge_map[e] = new_e);
|
||||||
|
let iso = GraphIsomorphism::new(&result, vertex_map, edge_map);
|
||||||
|
(result, iso)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: GraphTopology> From<&H> for AppendGraph {
|
||||||
|
fn from(source: &H) -> Self {
|
||||||
|
models::convert_to_addition::<Self, _>(source, Vertex(usize::MAX), |_, _| {}).0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod trait_tests {
|
||||||
|
use super::AppendGraph;
|
||||||
|
use crate::models::Graph;
|
||||||
|
|
||||||
|
crate::graph_topology_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_addition_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_clone_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_addition_clone_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_conversion_tests!(AppendGraph, Graph);
|
||||||
|
crate::graph_topology_addition_conversion_tests!(AppendGraph, Graph);
|
||||||
|
crate::graph_topology_deletion_conversion_tests!(AppendGraph, Graph);
|
||||||
|
crate::graph_topology_addition_deletion_conversion_tests!(AppendGraph, Graph);
|
||||||
|
crate::from_tests!(AppendGraph, Graph);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
crate::graph_topology_test_fixtures!(AppendGraph);
|
|
||||||
crate::graph_topology_tests!(AppendGraph);
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn incident_vertices_paired_index() {
|
fn incident_vertices_paired_index() {
|
||||||
let mut graph = AppendGraph::new();
|
let (graph, [v0, v1], e) = AppendGraph::single_edge();
|
||||||
let v1 = graph.add_vertex();
|
let f = Edge::new(e.index() + 1);
|
||||||
let v2 = graph.add_vertex();
|
let (u0, u1) = graph.incident_vertices(f);
|
||||||
let e = graph.add_edge(v1, v2);
|
|
||||||
let f = Edge(e.0 + 1);
|
|
||||||
let (u1, u2) = graph.incident_vertices(f);
|
|
||||||
assert!(
|
assert!(
|
||||||
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
|
(u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
|
||||||
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}"
|
"unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 typed_generational_arena::{Arena, Index};
|
||||||
|
|
||||||
use crate::maps::{EdgeMap, VertexMap};
|
use crate::maps::ElementMap;
|
||||||
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
|
use crate::models;
|
||||||
|
use crate::traits::{
|
||||||
|
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
|
||||||
|
GraphTopologyDeletion, Incidence, IncidenceCursor,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An opaque handle identifying a vertex in a [`Graph`].
|
||||||
|
///
|
||||||
|
/// 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>;
|
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 Edge = Index<IncidenceEntry, usize, usize>;
|
||||||
|
|
||||||
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
|
/// An [`ElementMap`] for [`Graph`] vertices.
|
||||||
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
|
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)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
struct VertexSlot(usize);
|
struct VertexSlot(usize);
|
||||||
|
|
||||||
|
// TODO: Benchmark the cost of the index shift. An alternative could be to init Graph::incidences with two dummy entries to avoid 0.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
struct IncidenceSlot(usize);
|
struct IncidenceSlot(NonZeroUsize);
|
||||||
|
|
||||||
|
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 {
|
pub struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<IncidenceSlot>,
|
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)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct IncidenceEntry {
|
pub struct IncidenceEntry {
|
||||||
next: Option<IncidenceSlot>,
|
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)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct GraphIncidenceCursor {
|
pub struct GraphIncidenceCursor {
|
||||||
incidence: Option<IncidenceSlot>,
|
incidence: Option<IncidenceSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
|
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
|
||||||
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
|
fn next(&mut self, graph: &Graph) -> Option<GraphIncidence> {
|
||||||
graph
|
graph
|
||||||
.step_incidence(&mut self.incidence)
|
.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 {
|
pub struct Graph {
|
||||||
// TODO: Arena index and generation types could be externalized to Graph.
|
// TODO: Arena index and generation types could be externalized to Graph.
|
||||||
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
||||||
@@ -56,6 +152,7 @@ pub struct Graph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Graph {
|
impl Graph {
|
||||||
|
/// Creates an empty graph instance with no vertices or edges.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: Arena::new(),
|
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
|
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
|
||||||
// incidences arena, and returns its index.
|
/// incidences arena, and returns its index.
|
||||||
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
|
fn add_incidence(&mut self, u: Vertex, v: Vertex) -> Edge {
|
||||||
let edge = self.incidences.insert(IncidenceEntry {
|
let edge = self.incidences.insert(IncidenceEntry {
|
||||||
next: self.vertices[v1].first_incidence.take(),
|
next: self.vertices[u].first_incidence.take(),
|
||||||
adjacent: VertexSlot(v2.arr_idx()),
|
adjacent: VertexSlot(v.arr_idx()),
|
||||||
});
|
});
|
||||||
self.vertices[v1].incidence_count += 1;
|
self.vertices[u].incidence_count += 1;
|
||||||
self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx()));
|
self.vertices[u].first_incidence = Some(IncidenceSlot::new(edge.arr_idx()));
|
||||||
edge
|
edge
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +188,9 @@ impl Graph {
|
|||||||
(f, e_entry, f_entry)
|
(f, e_entry, f_entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the source vertex incidence list after the incidence "e" was deleted from the
|
/// 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.
|
/// incidence arena. `next` is the next incidence after `e` in the `source` vertex incidence
|
||||||
|
/// list.
|
||||||
fn update_incidence_list(
|
fn update_incidence_list(
|
||||||
&mut self,
|
&mut self,
|
||||||
e: Edge,
|
e: Edge,
|
||||||
@@ -109,7 +207,7 @@ impl Graph {
|
|||||||
let first = vertex_header
|
let first = vertex_header
|
||||||
.first_incidence
|
.first_incidence
|
||||||
.expect("incident vertex without incidences, corrupt internal data state");
|
.expect("incident vertex without incidences, corrupt internal data state");
|
||||||
if first.0 == e.arr_idx() {
|
if first.index() == e.arr_idx() {
|
||||||
vertex_header.first_incidence = next;
|
vertex_header.first_incidence = next;
|
||||||
} else {
|
} else {
|
||||||
let graph: &Graph = self;
|
let graph: &Graph = self;
|
||||||
@@ -118,7 +216,7 @@ impl Graph {
|
|||||||
.find(|(_, f)| {
|
.find(|(_, f)| {
|
||||||
graph.incidences[*f]
|
graph.incidences[*f]
|
||||||
.next
|
.next
|
||||||
.is_some_and(|i| i.0 == e.arr_idx())
|
.is_some_and(|i| i.index() == e.arr_idx())
|
||||||
})
|
})
|
||||||
.expect("cannot find previous incidence, corrupt internal data state");
|
.expect("cannot find previous incidence, corrupt internal data state");
|
||||||
self.incidences[previous].next = next;
|
self.incidences[previous].next = next;
|
||||||
@@ -133,14 +231,19 @@ impl Graph {
|
|||||||
fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> {
|
fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> {
|
||||||
// TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries).
|
// TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries).
|
||||||
let current = (*incidence)?;
|
let current = (*incidence)?;
|
||||||
let e = self.incidences.get_idx(current.0).unwrap();
|
let e = self.incidences.get_idx(current.index()).unwrap();
|
||||||
let entry = self.incidences[e];
|
let entry = self.incidences[e];
|
||||||
*incidence = entry.next;
|
*incidence = entry.next;
|
||||||
Some((entry.adjacent, e))
|
Some((entry.adjacent, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_edge(&self, e: Edge) -> Edge {
|
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()
|
self.vertices.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertex_capacity(&self) -> usize {
|
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
|
||||||
self.vertices.capacity()
|
ElementMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
|
||||||
}
|
|
||||||
|
|
||||||
fn vertex_map<T: Clone>(&self, default: T) -> VertexMap<Self::Vertex, T> {
|
|
||||||
VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_count(&self) -> usize {
|
fn edge_count(&self) -> usize {
|
||||||
self.incidences.len() / 2
|
self.incidences.len() / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_capacity(&self) -> usize {
|
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||||
self.incidences.capacity() / 2
|
ElementMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
|
||||||
}
|
|
||||||
|
|
||||||
fn edge_map<T: Clone>(&self, default: T) -> EdgeMap<Self::Edge, T> {
|
|
||||||
EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn degree(&self, v: Self::Vertex) -> usize {
|
fn degree(&self, v: Self::Vertex) -> usize {
|
||||||
self.vertices[v].incidence_count
|
self.vertices[v].incidence_count
|
||||||
}
|
}
|
||||||
|
|
||||||
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
fn are_adjacent(&self, u: Self::Vertex, v: Self::Vertex) -> bool {
|
||||||
self.adjacent_vertices(v1).any(|x| x == v2)
|
self.adjacent_vertices(u).any(|x| x == v)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
||||||
@@ -197,16 +292,16 @@ impl GraphTopology for Graph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||||
let v2 = self
|
let v = self
|
||||||
.vertices
|
.vertices
|
||||||
.get_idx(self.incidences[e].adjacent.0)
|
.get_idx(self.incidences[e].adjacent.0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap();
|
let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap();
|
||||||
let v1 = self
|
let u = self
|
||||||
.vertices
|
.vertices
|
||||||
.get_idx(self.incidences[f].adjacent.0)
|
.get_idx(self.incidences[f].adjacent.0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
(v1, v2)
|
(u, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
||||||
@@ -220,9 +315,11 @@ impl GraphTopology for Graph {
|
|||||||
self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e))
|
self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
|
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = GraphIncidence> {
|
||||||
self.raw_incidences(v)
|
self.raw_incidences(v).map(|(vs, e)| Incidence {
|
||||||
.map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e)))
|
vertex: self.vertices.get_idx(vs.0).unwrap(),
|
||||||
|
edge: self.normalize_edge(e),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
||||||
@@ -230,6 +327,24 @@ impl GraphTopology for Graph {
|
|||||||
incidence: self.vertices[v].first_incidence,
|
incidence: self.vertices[v].first_incidence,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphTopologyAddition for Graph {
|
||||||
|
fn vertex_capacity(&self) -> usize {
|
||||||
|
self.vertices.capacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edge_capacity(&self) -> usize {
|
||||||
|
self.incidences.capacity() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_vertices(&mut self, additional: usize) {
|
||||||
|
self.vertices.reserve(additional);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_edges(&mut self, additional: usize) {
|
||||||
|
self.incidences.reserve(additional * 2);
|
||||||
|
}
|
||||||
|
|
||||||
fn add_vertex(&mut self) -> Self::Vertex {
|
fn add_vertex(&mut self) -> Self::Vertex {
|
||||||
self.vertices.insert(VertexIncidenceHeader {
|
self.vertices.insert(VertexIncidenceHeader {
|
||||||
@@ -238,9 +353,9 @@ impl GraphTopology for Graph {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
fn add_edge(&mut self, u: Self::Vertex, v: Self::Vertex) -> Self::Edge {
|
||||||
let first = self.add_incidence(v1, v2);
|
let first = self.add_incidence(u, v);
|
||||||
self.add_incidence(v2, v1);
|
self.add_incidence(v, u);
|
||||||
first
|
first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) {
|
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);
|
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
|
||||||
if e_entry.adjacent != f_entry.adjacent {
|
if e_entry.adjacent != f_entry.adjacent {
|
||||||
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
||||||
self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false);
|
self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false);
|
||||||
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) {
|
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.index()) {
|
||||||
self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
|
self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
|
||||||
} else {
|
} else {
|
||||||
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);
|
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);
|
||||||
@@ -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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
crate::graph_topology_test_fixtures!(Graph);
|
|
||||||
crate::graph_topology_tests!(Graph);
|
|
||||||
crate::graph_topology_deletion_tests!(Graph);
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn incident_vertices_paired_index() {
|
fn incident_vertices_paired_index() {
|
||||||
let mut graph = Graph::new();
|
let (graph, [v0, v1], e) = Graph::single_edge();
|
||||||
let v1 = graph.add_vertex();
|
|
||||||
let v2 = graph.add_vertex();
|
|
||||||
let e = graph.add_edge(v1, v2);
|
|
||||||
let f = graph
|
let f = graph
|
||||||
.incidences
|
.incidences
|
||||||
.get_idx(e.arr_idx() + 1)
|
.get_idx(e.arr_idx() + 1)
|
||||||
.expect("paired index should be valid");
|
.expect("paired index should be valid");
|
||||||
let (u1, u2) = graph.incident_vertices(f);
|
let (u0, u1) = graph.incident_vertices(f);
|
||||||
assert!(
|
assert!(
|
||||||
(u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1),
|
(u0 == v0 && u1 == v1) || (u0 == v1 && u1 == v0),
|
||||||
"unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}"
|
"unexpected incident vertices {u0:?} and {u1:?} for edge {f:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_edge_paired_index() {
|
fn delete_edge_paired_index() {
|
||||||
let mut graph = Graph::new();
|
let (mut graph, _, e) = Graph::single_edge();
|
||||||
let v1 = graph.add_vertex();
|
|
||||||
let v2 = graph.add_vertex();
|
|
||||||
let e = graph.add_edge(v1, v2);
|
|
||||||
let f = graph
|
let f = graph
|
||||||
.incidences
|
.incidences
|
||||||
.get_idx(e.arr_idx() + 1)
|
.get_idx(e.arr_idx() + 1)
|
||||||
@@ -324,9 +471,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_edge_loop_paired_index() {
|
fn delete_edge_loop_paired_index() {
|
||||||
let mut graph = Graph::new();
|
let (mut graph, _, e) = Graph::loop_edge();
|
||||||
let v = graph.add_vertex();
|
|
||||||
let e = graph.add_edge(v, v);
|
|
||||||
let f = graph
|
let f = graph
|
||||||
.incidences
|
.incidences
|
||||||
.get_idx(e.arr_idx() + 1)
|
.get_idx(e.arr_idx() + 1)
|
||||||
@@ -334,4 +479,25 @@ mod tests {
|
|||||||
graph.delete_edge(f);
|
graph.delete_edge(f);
|
||||||
assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete");
|
assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reused_slot_returns_old_value() {
|
||||||
|
let (mut graph, [_, v], _) = Graph::single_edge();
|
||||||
|
let mut map = graph.vertex_map(0);
|
||||||
|
map[v] = 99;
|
||||||
|
graph.delete_vertex(v);
|
||||||
|
let u = graph.add_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
v.arr_idx(),
|
||||||
|
u.arr_idx(),
|
||||||
|
"precondition: new vertex {v:?} should reuse slot of deleted vertex {u:?}"
|
||||||
|
);
|
||||||
|
// ElementMap uses raw indices, not vertex identity. A new vertex u reusing the slot
|
||||||
|
// of previously deleted v sees the old value. Callers must reinitialize stale slots
|
||||||
|
// after deletion.
|
||||||
|
assert_eq!(
|
||||||
|
map[u], 99,
|
||||||
|
"new vertex reusing slot of deleted vertex should return old value"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -1,6 +1,14 @@
|
|||||||
pub(crate) mod bfs_testing;
|
//! Test fixture and test macros for graph topology and algorithm implementations.
|
||||||
pub(crate) mod dfs_testing;
|
|
||||||
pub(crate) mod dijkstra_testing;
|
pub mod fixtures;
|
||||||
pub(crate) mod find_path_testing;
|
pub mod from_testing;
|
||||||
pub(crate) mod graph_topology_testing;
|
pub mod graph_topology_addition_clone_testing;
|
||||||
pub(crate) mod maps_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,505 @@
|
|||||||
|
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 {
|
||||||
|
/// Returns a versatile, pre-defined standard test graph.
|
||||||
|
// 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],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Returns an empty graph.
|
||||||
|
fn empty() -> Self;
|
||||||
|
|
||||||
|
/// Returns a graph with a single vertex and no edges.
|
||||||
|
fn single_vertex() -> (Self, Self::Vertex);
|
||||||
|
|
||||||
|
/// Returns a graph with two vertices connected by a single edge.
|
||||||
|
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge);
|
||||||
|
|
||||||
|
/// Returns a graph with vertices *v0*, *v1*, and *v2*, where *v0* is isolated, and the other
|
||||||
|
/// two are connected by a single edge.
|
||||||
|
fn disconnected() -> (Self, [Self::Vertex; 3]);
|
||||||
|
|
||||||
|
/// Returns a graph with a single vertex that has a single loop edge.
|
||||||
|
fn loop_edge() -> (Self, Self::Vertex, Self::Edge);
|
||||||
|
|
||||||
|
/// Returns a graph with two vertices that are connected by `K` multi-edges.
|
||||||
|
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]);
|
||||||
|
|
||||||
|
/// Returns a graph that is a path with three vertices and two edges between them, returned in
|
||||||
|
/// order.
|
||||||
|
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]);
|
||||||
|
|
||||||
|
/// Returns a graph that is a path with three vertices *v0*, *v1*, and *v2*, and two edges
|
||||||
|
/// *e0 = (v0, v1)* and *e1 = (v1, v2)* between them, and two loops on the first two vertices
|
||||||
|
/// *e2 = (v0, v0)* and *e3 = (v1, v1)*.
|
||||||
|
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 {v:?}",
|
||||||
|
vertices[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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` for any `target` in `targets`, that its edges are pairwise incident in the
|
||||||
|
/// order given, and that it starts at `source` and ends at any `target` in `targets`.
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
+882
-1217
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+564
-15
@@ -1,36 +1,585 @@
|
|||||||
use crate::maps::{EdgeMap, VertexMap};
|
//! Core traits for undirected graph topologies.
|
||||||
|
|
||||||
// TODO: Add functions to reserve memory for vertices and edges.
|
use crate::maps::ElementMap;
|
||||||
// TODO: Split out GraphTopologyAddition trait.
|
|
||||||
|
/// 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 {
|
pub trait GraphTopology {
|
||||||
|
/// An opaque, stable handle identifying a vertex.
|
||||||
type Vertex: Copy + Eq;
|
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_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_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 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>;
|
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>;
|
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);
|
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>;
|
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 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 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;
|
/// Returns an iterator over all vertices that are adjacent to both `u` and `v`.
|
||||||
|
///
|
||||||
|
/// Every common neighbor is yielded exactly once, in unspecified order, regardless of how
|
||||||
|
/// many parallel edges connect it to `u` or `v`. This method is symmetric up to permutations,
|
||||||
|
/// i.e. swapping the arguments will yield the same vertices, but not necessarily in the same
|
||||||
|
/// order.
|
||||||
|
///
|
||||||
|
/// If `u` has a loop and is adjacent to `v`, then `u` is a common neighbor, and will be yielded
|
||||||
|
/// exactly once, and analogously for a loop at `v`.
|
||||||
|
///
|
||||||
|
/// If `u == v`, then the common neighbors are all neighbors of `u`, including `u` itself if and
|
||||||
|
/// only if it has a loop.
|
||||||
|
///
|
||||||
|
/// All neighbors of `u` are collected when this method is called, while those of `v` are
|
||||||
|
/// traversed lazily.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `u` or `v` is not a valid vertex of this graph.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::models::AppendGraph;
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
///
|
||||||
|
/// let mut graph = AppendGraph::new();
|
||||||
|
/// let [u, w, v] = core::array::from_fn(|_| graph.add_vertex());
|
||||||
|
/// // Parallel edges do not yield `w` more than once.
|
||||||
|
/// graph.add_edge(u, w);
|
||||||
|
/// graph.add_edge(u, w);
|
||||||
|
/// graph.add_edge(w, v);
|
||||||
|
/// assert!(graph.common_neighbors(u, v).eq([w]));
|
||||||
|
/// ```
|
||||||
|
// TODO: FrozenGraph could provide a sorted-merge override, if its vertices were sorted.
|
||||||
|
fn common_neighbors(
|
||||||
|
&self,
|
||||||
|
u: Self::Vertex,
|
||||||
|
v: Self::Vertex,
|
||||||
|
) -> impl Iterator<Item = Self::Vertex> {
|
||||||
|
// TODO: Instead of Vec, this could use a vertex ElementMap with marked neighbors if the product of the degrees of u and v is large compared to |V|.
|
||||||
|
// TODO: Instead of Vec, this could use a reusable, user-provided(?) "scratch-pad" vertex ElementMap, to not pay its allocation.
|
||||||
|
let mut u_neighbors: Vec<_> = self.adjacent_vertices(u).collect();
|
||||||
|
let mut v_neighbors = self.adjacent_vertices(v);
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
let w = v_neighbors.find(|w| u_neighbors.contains(w))?;
|
||||||
|
// Removes all parallel entries, so that `w` is not yielded again.
|
||||||
|
u_neighbors.retain(|x| *x != w);
|
||||||
|
Some(w)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
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);
|
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);
|
fn delete_edge(&mut self, e: Self::Edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IncidenceCursor<G: GraphTopology + ?Sized> {
|
/// A cursor for traversing the incidences of a vertex one step at a time.
|
||||||
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
|
///
|
||||||
|
/// 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;
|
use grapherity::models::AppendGraph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
bfs_tests!(AppendGraph);
|
||||||
grapherity::bfs_tests!(AppendGraph);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod graph_tests {
|
mod graph {
|
||||||
use grapherity::models::Graph;
|
use grapherity::models::Graph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(Graph);
|
bfs_tests!(Graph);
|
||||||
grapherity::bfs_tests!(Graph);
|
}
|
||||||
|
|
||||||
|
fn bfs_single_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let result = algorithms::bfs(&graph, v);
|
||||||
|
assert_eq!(
|
||||||
|
result.distances[v],
|
||||||
|
Some(0),
|
||||||
|
"unexpected distance of source vertex"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"unexpected predecessor of source vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let result = algorithms::bfs(&graph, vertices[0]);
|
||||||
|
assert_eq!(
|
||||||
|
result.distances[vertices[0]],
|
||||||
|
Some(0),
|
||||||
|
"unexpected distance of source vertex"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[vertices[0]], None,
|
||||||
|
"unexpected predecessor of source vertex"
|
||||||
|
);
|
||||||
|
for &v in &vertices[1..3] {
|
||||||
|
assert_eq!(
|
||||||
|
result.distances[v], None,
|
||||||
|
"disconnected vertex {v:?} should have no distance"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"disconnected vertex {v:?} should have no predecessor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let result = algorithms::bfs(&graph, vertices[0]);
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices);
|
||||||
|
fixtures::assert_standard_unweighted_predecessors_v0(&result.predecessors, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_distances_single_vertex<G: MakeTestGraph>() {
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let distances = algorithms::bfs_distances(&graph, v);
|
||||||
|
assert_eq!(
|
||||||
|
distances[v],
|
||||||
|
Some(0),
|
||||||
|
"unexpected distance of source vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_distances_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let distances = algorithms::bfs_distances(&graph, vertices[0]);
|
||||||
|
assert_eq!(
|
||||||
|
distances[vertices[0]],
|
||||||
|
Some(0),
|
||||||
|
"unexpected distance of source vertex"
|
||||||
|
);
|
||||||
|
for &v in &vertices[1..3] {
|
||||||
|
assert_eq!(
|
||||||
|
distances[v], None,
|
||||||
|
"disconnected vertex {v:?} should have no distance"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_distances<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let distances = algorithms::bfs_distances(&graph, vertices[0]);
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_source<G: MakeTestGraph>() {
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find(&graph, v, v),
|
||||||
|
Some(0),
|
||||||
|
"source should be found at distance 0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_disconnected<G: MakeTestGraph>() {
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find(&graph, v0, v1),
|
||||||
|
None,
|
||||||
|
"disconnected target should not be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(
|
||||||
|
|v| algorithms::bfs_find(&graph, vertices[0], v),
|
||||||
|
&vertices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_where_source_matches<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_where(&graph, v, |u| u == v),
|
||||||
|
Some((v, 0)),
|
||||||
|
"source should match with distance 0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_where_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_where(&graph, v0, |v| v == v1),
|
||||||
|
None,
|
||||||
|
"disconnected vertex {v1:?} should not be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_where_no_match<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, ..], _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_where(&graph, v0, |_| false),
|
||||||
|
None,
|
||||||
|
"no vertex should match an always-false predicate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_where_nearest<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
// vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0].
|
||||||
|
// vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9].
|
||||||
|
// BFS must return one of the distance-3 ones, not vertices[9].
|
||||||
|
let result = algorithms::bfs_find_where(&graph, vertices[0], |v| {
|
||||||
|
v == vertices[7] || v == vertices[8] || v == vertices[9]
|
||||||
|
});
|
||||||
|
assert!(result.is_some(), "expected a match");
|
||||||
|
let (found, distance) = result.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
distance, 3,
|
||||||
|
"unexpected distance to nearest matching vertex {found:?}",
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
found == vertices[7] || found == vertices[8],
|
||||||
|
"unexpected nearest match vertex {found:?}, should be {:?} or {:?}",
|
||||||
|
vertices[7],
|
||||||
|
vertices[8]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_source_equals_target<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_path(&graph, v, v),
|
||||||
|
Some(vec![]),
|
||||||
|
"path from source to itself should be empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_path(&graph, v0, v1),
|
||||||
|
None,
|
||||||
|
"no path should exist to disconnected vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_adjacent<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1], e) = G::single_edge();
|
||||||
|
let path = algorithms::bfs_find_path(&graph, v0, v1)
|
||||||
|
.expect("path should exist between adjacent vertices");
|
||||||
|
assert_eq!(path, [e], "path should contain only the connecting edge");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(
|
||||||
|
|v| {
|
||||||
|
let result = algorithms::bfs_find_path(&graph, vertices[0], v);
|
||||||
|
let path = fixtures::assert_valid_path(&graph, result, vertices[0], &[v]);
|
||||||
|
Some(u32::try_from(path.len()).unwrap())
|
||||||
|
},
|
||||||
|
&vertices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_where_source_matches<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_path_where(&graph, v, |u| u == v),
|
||||||
|
Some(vec![]),
|
||||||
|
"path from source to itself should be empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_where_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_path_where(&graph, v0, |v| v == v1),
|
||||||
|
None,
|
||||||
|
"no path should exist to disconnected vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_where_no_match<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, ..], _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::bfs_find_path_where(&graph, v0, |_| false),
|
||||||
|
None,
|
||||||
|
"no path should exist when predicate never matches"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bfs_find_path_where<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
// vertices[7] and vertices[8] are at distance 3 from vertices[0]; vertices[9] is at distance 4.
|
||||||
|
// BFS must return one shortest path, so it must lead to one of the distance-3 matches.
|
||||||
|
let result = algorithms::bfs_find_path_where(&graph, vertices[0], |v| {
|
||||||
|
v == vertices[7] || v == vertices[8] || v == vertices[9]
|
||||||
|
});
|
||||||
|
let path =
|
||||||
|
fixtures::assert_valid_path(&graph, result, vertices[0], &[vertices[7], vertices[8]]);
|
||||||
|
assert_eq!(
|
||||||
|
path.len(),
|
||||||
|
3,
|
||||||
|
"BFS should return shortest path to a matching vertex"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+351
-6
@@ -1,13 +1,358 @@
|
|||||||
mod append_graph_tests {
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use grapherity::algorithms;
|
||||||
|
use grapherity::maps::ElementMap;
|
||||||
|
use grapherity::testing::fixtures;
|
||||||
|
use grapherity::testing::fixtures::MakeTestGraph;
|
||||||
|
use grapherity::traits::GraphTopology;
|
||||||
|
|
||||||
|
macro_rules! dfs_tests {
|
||||||
|
($T:ty) => {
|
||||||
|
dfs_tests!(@wrap $T,
|
||||||
|
dfs_single_vertex,
|
||||||
|
dfs_disconnected,
|
||||||
|
dfs,
|
||||||
|
dfs_visited_single_vertex,
|
||||||
|
dfs_visited_disconnected,
|
||||||
|
dfs_visited,
|
||||||
|
dfs_find_source,
|
||||||
|
dfs_find_disconnected,
|
||||||
|
dfs_find,
|
||||||
|
dfs_find_where_source_matches,
|
||||||
|
dfs_find_where_disconnected,
|
||||||
|
dfs_find_where_no_match,
|
||||||
|
dfs_find_where_adjacent,
|
||||||
|
dfs_find_where,
|
||||||
|
dfs_find_path_source_equals_target,
|
||||||
|
dfs_find_path_disconnected,
|
||||||
|
dfs_find_path_adjacent,
|
||||||
|
dfs_find_path,
|
||||||
|
dfs_find_path_where_source_matches,
|
||||||
|
dfs_find_path_where_disconnected,
|
||||||
|
dfs_find_path_where_no_match,
|
||||||
|
dfs_find_path_where,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
super::$name::<$T>();
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod frozen_graph {
|
||||||
|
use grapherity::models::FrozenGraph;
|
||||||
|
|
||||||
|
dfs_tests!(FrozenGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod append_graph {
|
||||||
use grapherity::models::AppendGraph;
|
use grapherity::models::AppendGraph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
dfs_tests!(AppendGraph);
|
||||||
grapherity::dfs_tests!(AppendGraph);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod graph_tests {
|
mod graph {
|
||||||
use grapherity::models::Graph;
|
use grapherity::models::Graph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(Graph);
|
dfs_tests!(Graph);
|
||||||
grapherity::dfs_tests!(Graph);
|
}
|
||||||
|
|
||||||
|
fn dfs_single_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let result = algorithms::dfs(&graph, v);
|
||||||
|
assert!(result.visited[v], "source vertex should be visited");
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"unexpected predecessor of source vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let result = algorithms::dfs(&graph, vertices[0]);
|
||||||
|
assert!(
|
||||||
|
result.visited[vertices[0]],
|
||||||
|
"source vertex should be visited"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[vertices[0]], None,
|
||||||
|
"unexpected predecessor of source vertex"
|
||||||
|
);
|
||||||
|
for &v in &vertices[1..3] {
|
||||||
|
assert!(
|
||||||
|
!result.visited[v],
|
||||||
|
"disconnected vertex {v:?} should not be visited"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"disconnected vertex {v:?} should have no predecessor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let result = algorithms::dfs(&graph, vertices[0]);
|
||||||
|
assert_dfs_visited::<G>(&result.visited, &vertices);
|
||||||
|
assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_visited_single_vertex<G: MakeTestGraph>() {
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let visited = algorithms::dfs_visited(&graph, v);
|
||||||
|
assert!(visited[v], "source vertex should be visited");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_visited_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let visited = algorithms::dfs_visited(&graph, vertices[0]);
|
||||||
|
assert!(visited[vertices[0]], "source vertex should be visited");
|
||||||
|
for &v in &vertices[1..3] {
|
||||||
|
assert!(
|
||||||
|
!visited[v],
|
||||||
|
"disconnected vertex {v:?} should not be visited"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_visited<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let visited = algorithms::dfs_visited(&graph, vertices[0]);
|
||||||
|
assert_dfs_visited::<G>(&visited, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_source<G: MakeTestGraph>() {
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert!(
|
||||||
|
algorithms::dfs_find(&graph, v, v),
|
||||||
|
"source should find itself"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_disconnected<G: MakeTestGraph>() {
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert!(
|
||||||
|
!algorithms::dfs_find(&graph, v0, v1),
|
||||||
|
"disconnected target should not be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
for &v in &vertices {
|
||||||
|
assert!(
|
||||||
|
algorithms::dfs_find(&graph, vertices[0], v),
|
||||||
|
"vertex {v:?} should be reachable from {:?}",
|
||||||
|
vertices[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_where_source_matches<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_where(&graph, v, |u| u == v),
|
||||||
|
Some(v),
|
||||||
|
"source should find itself"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_where_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_where(&graph, v0, |v| v == v1),
|
||||||
|
None,
|
||||||
|
"disconnected vertex {v1:?} should not be found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_where_no_match<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, ..], _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_where(&graph, v0, |_| false),
|
||||||
|
None,
|
||||||
|
"no vertex should match an always-false predicate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_where_adjacent<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, ..], _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_where(&graph, v0, |v| v == v1),
|
||||||
|
Some(v1),
|
||||||
|
"expected to find adjacent vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_where<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]),
|
||||||
|
Some(vertices[9]),
|
||||||
|
"expected to find connected vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_source_equals_target<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_path(&graph, v, v),
|
||||||
|
Some(vec![]),
|
||||||
|
"path from source to itself should be empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_path(&graph, v0, v1),
|
||||||
|
None,
|
||||||
|
"no path should exist to disconnected vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_adjacent<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1], e) = G::single_edge();
|
||||||
|
let path = algorithms::dfs_find_path(&graph, v0, v1)
|
||||||
|
.expect("path should exist between adjacent vertices");
|
||||||
|
assert_eq!(path, [e], "path should contain only the connecting edge");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[9]);
|
||||||
|
fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_where_source_matches<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_path_where(&graph, v, |u| u == v),
|
||||||
|
Some(vec![]),
|
||||||
|
"path from source to itself should be empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_where_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, v1, _]) = G::disconnected();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_path_where(&graph, v0, |v| v == v1),
|
||||||
|
None,
|
||||||
|
"no path should exist to disconnected vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_where_no_match<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, [v0, ..], _, _) = G::standard();
|
||||||
|
assert_eq!(
|
||||||
|
algorithms::dfs_find_path_where(&graph, v0, |_| false),
|
||||||
|
None,
|
||||||
|
"no path should exist when predicate never matches"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_find_path_where<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let path = algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]);
|
||||||
|
_ = fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_dfs_visited<G: GraphTopology>(
|
||||||
|
visited: &ElementMap<G::Vertex, bool>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
for &v in vertices {
|
||||||
|
assert!(visited[v], "vertex {v:?} should be visited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_dfs_predecessors<G: GraphTopology>(
|
||||||
|
graph: &G,
|
||||||
|
visited: &ElementMap<G::Vertex, bool>,
|
||||||
|
predecessors: &ElementMap<G::Vertex, Option<G::Vertex>>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
predecessors[vertices[0]], None,
|
||||||
|
"source should have no predecessor"
|
||||||
|
);
|
||||||
|
for &v in vertices.iter().skip(1) {
|
||||||
|
let p = predecessors[v].unwrap_or_else(|| panic!("vertex {v:?} should have a predecessor"));
|
||||||
|
assert!(
|
||||||
|
visited[p],
|
||||||
|
"predecessor {p:?} of vertex {v:?} should be visited"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
graph.are_adjacent(v, p),
|
||||||
|
"predecessor {p:?} of vertex {v:?} should be adjacent"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+290
-6
@@ -1,13 +1,297 @@
|
|||||||
mod append_graph_tests {
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use grapherity::algorithms;
|
||||||
|
use grapherity::algorithms::DijkstraResult;
|
||||||
|
use grapherity::maps::ElementMap;
|
||||||
|
use grapherity::testing::fixtures;
|
||||||
|
use grapherity::testing::fixtures::MakeTestGraph;
|
||||||
|
use grapherity::traits::GraphTopology;
|
||||||
|
|
||||||
|
macro_rules! dijkstra_tests {
|
||||||
|
($T:ty) => {
|
||||||
|
dijkstra_tests!(@wrap $T,
|
||||||
|
dijkstra_single_vertex,
|
||||||
|
dijkstra_disconnected,
|
||||||
|
dijkstra_zero_weight_loop,
|
||||||
|
dijkstra,
|
||||||
|
dijkstra_distances,
|
||||||
|
dijkstra_unweighted_single_vertex,
|
||||||
|
dijkstra_unweighted_disconnected,
|
||||||
|
dijkstra_unweighted,
|
||||||
|
dijkstra_distances_unweighted_single_vertex,
|
||||||
|
dijkstra_distances_unweighted_disconnected,
|
||||||
|
dijkstra_distances_unweighted,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
super::$name::<$T>();
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod frozen_graph {
|
||||||
|
use grapherity::models::FrozenGraph;
|
||||||
|
|
||||||
|
dijkstra_tests!(FrozenGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod append_graph {
|
||||||
use grapherity::models::AppendGraph;
|
use grapherity::models::AppendGraph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
dijkstra_tests!(AppendGraph);
|
||||||
grapherity::dijkstra_tests!(AppendGraph);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod graph_tests {
|
mod graph {
|
||||||
use grapherity::models::Graph;
|
use grapherity::models::Graph;
|
||||||
|
|
||||||
grapherity::graph_topology_test_fixtures!(Graph);
|
dijkstra_tests!(Graph);
|
||||||
grapherity::dijkstra_tests!(Graph);
|
}
|
||||||
|
|
||||||
|
fn dijkstra_single_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let result = algorithms::dijkstra(&graph, v, |_| panic!("unexpected call of weight functor"));
|
||||||
|
assert_single_vertex::<G>(&result, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let result = algorithms::dijkstra(&graph, vertices[0], |_| {
|
||||||
|
panic!("unexpected call of weight functor")
|
||||||
|
});
|
||||||
|
assert_single_vertex::<G>(&result, vertices[0]);
|
||||||
|
assert_disconnected::<G>(&result, &vertices[1..3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_zero_weight_loop<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices, weights) = make_two_edge_path_with_zero_weight_loops::<G>();
|
||||||
|
let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]);
|
||||||
|
assert_single_vertex::<G>(&result, vertices[0]);
|
||||||
|
for (i, &v) in vertices.iter().enumerate().skip(1) {
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v],
|
||||||
|
Some(vertices[i - 1]),
|
||||||
|
"unexpected predecessor of vertex {v:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result.distances[v],
|
||||||
|
Some(i.try_into().unwrap()),
|
||||||
|
"unexpected distance of vertex {v:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices, weights) = make_standard_weighted::<G>();
|
||||||
|
let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]);
|
||||||
|
assert_standard_weighted_distances_v0::<G>(&result.distances, &vertices);
|
||||||
|
assert_standard_weighted_predecessors_v0::<G>(&result.predecessors, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_distances<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices, weights) = make_standard_weighted::<G>();
|
||||||
|
let distances = algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]);
|
||||||
|
assert_standard_weighted_distances_v0::<G>(&distances, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_unweighted_single_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let result = algorithms::dijkstra_unweighted(&graph, v);
|
||||||
|
assert_single_vertex::<G>(&result, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_unweighted_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let result = algorithms::dijkstra_unweighted(&graph, vertices[0]);
|
||||||
|
assert_single_vertex::<G>(&result, vertices[0]);
|
||||||
|
assert_disconnected::<G>(&result, &vertices[1..3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_unweighted<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let result = algorithms::dijkstra_unweighted(&graph, vertices[0]);
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices);
|
||||||
|
fixtures::assert_standard_unweighted_predecessors_v0(&result.predecessors, &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_distances_unweighted_single_vertex<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Hash,
|
||||||
|
{
|
||||||
|
let (graph, v) = G::single_vertex();
|
||||||
|
let distances = algorithms::dijkstra_distances_unweighted(&graph, v);
|
||||||
|
assert_distances_single_vertex::<G>(&distances, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_distances_unweighted_disconnected<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices) = G::disconnected();
|
||||||
|
let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]);
|
||||||
|
assert_distances_single_vertex::<G>(&distances, vertices[0]);
|
||||||
|
assert_distances_disconnected::<G>(&distances, &vertices[1..3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dijkstra_distances_unweighted<G: MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug + Hash,
|
||||||
|
{
|
||||||
|
let (graph, vertices, _, _) = G::standard();
|
||||||
|
let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]);
|
||||||
|
fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_single_vertex<G: GraphTopology>(result: &DijkstraResult<G::Vertex, u32>, v: G::Vertex)
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
assert_distances_single_vertex::<G>(&result.distances, v);
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"unexpected predecessor of source vertex",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_distances_single_vertex<G: GraphTopology>(
|
||||||
|
distances: &ElementMap<G::Vertex, Option<u32>>,
|
||||||
|
v: G::Vertex,
|
||||||
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
distances[v],
|
||||||
|
Some(0),
|
||||||
|
"unexpected distance of source vertex"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_disconnected<G: GraphTopology>(
|
||||||
|
result: &DijkstraResult<G::Vertex, u32>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
assert_distances_disconnected::<G>(&result.distances, vertices);
|
||||||
|
for &v in vertices {
|
||||||
|
assert_eq!(
|
||||||
|
result.predecessors[v], None,
|
||||||
|
"unexpected predecessor of disconnected vertex {v:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_distances_disconnected<G: GraphTopology>(
|
||||||
|
distances: &ElementMap<G::Vertex, Option<u32>>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
for &v in vertices {
|
||||||
|
assert_eq!(
|
||||||
|
distances[v], None,
|
||||||
|
"unexpected distance of disconnected vertex {v:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_standard_weighted_predecessors_v0<G: GraphTopology>(
|
||||||
|
actual: &ElementMap<G::Vertex, Option<G::Vertex>>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let expected = [
|
||||||
|
vec![None],
|
||||||
|
vec![Some(vertices[0])],
|
||||||
|
vec![Some(vertices[4])],
|
||||||
|
vec![Some(vertices[1])],
|
||||||
|
vec![Some(vertices[7])],
|
||||||
|
vec![Some(vertices[9])],
|
||||||
|
vec![Some(vertices[3])],
|
||||||
|
vec![Some(vertices[9])],
|
||||||
|
vec![Some(vertices[7])],
|
||||||
|
vec![Some(vertices[6])],
|
||||||
|
];
|
||||||
|
assert_eq!(vertices.len(), expected.len());
|
||||||
|
for (&v, exp) in vertices.iter().zip(&expected) {
|
||||||
|
assert!(
|
||||||
|
exp.contains(&actual[v]),
|
||||||
|
"unexpected predecessor {:?} of {v:?}",
|
||||||
|
actual[v]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_standard_weighted_distances_v0<G: GraphTopology>(
|
||||||
|
actual: &ElementMap<G::Vertex, Option<u32>>,
|
||||||
|
vertices: &[G::Vertex],
|
||||||
|
) where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
{
|
||||||
|
let expected = [
|
||||||
|
Some(0),
|
||||||
|
Some(1),
|
||||||
|
Some(65),
|
||||||
|
Some(4),
|
||||||
|
Some(58),
|
||||||
|
Some(43),
|
||||||
|
Some(14),
|
||||||
|
Some(46),
|
||||||
|
Some(145),
|
||||||
|
Some(29),
|
||||||
|
];
|
||||||
|
assert_eq!(vertices.len(), expected.len());
|
||||||
|
for (&v, &exp) in vertices.iter().zip(&expected) {
|
||||||
|
assert_eq!(
|
||||||
|
actual[v], exp,
|
||||||
|
"unexpected distance from {:?} to {v:?}",
|
||||||
|
vertices[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_standard_weighted<G: MakeTestGraph>() -> (G, [G::Vertex; 10], ElementMap<G::Edge, u32>) {
|
||||||
|
let (graph, vertices, edges, _) = G::standard();
|
||||||
|
let mut weights = graph.edge_map(99);
|
||||||
|
for i in [1, 3, 7, 10, 12, 14, 15, 17] {
|
||||||
|
weights[edges[i].0] = i.try_into().unwrap();
|
||||||
|
}
|
||||||
|
(graph, vertices, weights)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_two_edge_path_with_zero_weight_loops<G: MakeTestGraph>()
|
||||||
|
-> (G, [G::Vertex; 3], ElementMap<G::Edge, u32>) {
|
||||||
|
let (graph, vertices, edges) = G::two_edge_path_with_loops();
|
||||||
|
let mut weights = graph.edge_map(1);
|
||||||
|
weights[edges[2]] = 0;
|
||||||
|
weights[edges[3]] = 0;
|
||||||
|
(graph, vertices, weights)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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