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