79 Commits
Author SHA1 Message Date
warrence 0cd965db4f Merge pull request 'Update package version' (#10) from release-0.3.1 into main
Reviewed-on: #10
2026-10-16 20:36:17 +02:00
warrence 6f557a92aa Update package version 2026-10-16 20:35:54 +02:00
warrence 536a39c82b Merge pull request 'Common neighbors' (#9) from common-neighbors into main
Reviewed-on: #9
2026-10-16 20:30:31 +02:00
warrence 6403862977 Merge branch 'fixes' into common-neighbors 2026-10-16 20:29:11 +02:00
warrence 3ae0aa9b67 Update generators unit tests to cover generate_as and better validate Petersen graph 2026-10-16 20:28:48 +02:00
warrence 5b6ad13f0e Add generators::generate_as 2026-10-16 20:25:40 +02:00
warrence f41b77903f Add docs for MakeTestGraph generator methods 2026-10-16 20:23:58 +02:00
warrence 3fb3e67e8e Add GraphTopology::common_neighbors 2026-10-16 20:19:08 +02:00
warrence 8c57f5d2c4 Fix import order 2026-10-15 20:09:35 +02:00
warrence b75da01af3 Merge pull request 'Release 0.3.0' (#8) from release-0.3.0 into main
Reviewed-on: #8
2026-10-15 10:06:12 +02:00
warrence 1d1ca3bd6a Update package version 2026-10-15 10:03:18 +02:00
warrence 90a2bd9e9e Add GraphTopologyConversion tests related to addition and deletion 2026-10-15 10:01:54 +02:00
warrence 7196871a96 Add ElementMap conversion tests via new GraphIsomorphism 2026-10-15 09:32:15 +02:00
warrence dd0ae64984 Add unit tests for ElementMap::remap 2026-10-15 09:06:21 +02:00
warrence 9ae841288b Add conversion test for empty graph 2026-10-15 08:46:47 +02:00
warrence eab5a63e6c Fix loop patterns 2026-10-15 08:45:18 +02:00
warrence 653cd39279 Fix doc links 2026-10-13 21:38:44 +02:00
warrence 3d1e8246a8 Fix clippy findings and related issues 2026-10-13 21:25:50 +02:00
warrence 7cfee9cce5 Remove function calls inside of 'expect' 2026-10-12 21:52:51 +02:00
warrence b124e58b38 Change GraphTopologyConversion::convert return value to include the new GraphIsomorphism
This allows to convert maps for the converted graph.
2026-10-12 21:33:49 +02:00
warrence 1979d33649 Add reserve calls to the test fixtures 2026-10-12 20:23:27 +02:00
warrence 3d7dda6f7b Refactor ElementMap, removing its copy-on-write feature 2026-10-11 22:24:00 +02:00
warrence 4e20c49486 Update generators::petersen to use graph reserve methods 2026-09-29 23:22:10 +02:00
warrence b943dddf1b Add docs for generators and generators::petersen 2026-09-29 23:19:08 +02:00
warrence c8855daddf Renamed FromGraph to GraphTopologyConversion to align with existing traits and separate from From trait 2026-09-29 23:16:57 +02:00
warrence 9d43f0cb8d Move FromGrath trait within traits module 2026-09-29 00:00:13 +02:00
warrence 8bf196d1ad Fix code format 2026-09-28 23:59:30 +02:00
warrence 3478274959 Add From and FromGraph implementations for Graph and AppendGraph 2026-09-28 23:58:30 +02:00
warrence a9190f88db Add From and FromGraph tests for FrozenGraph and for GraphTopologyAddition
The new tests are analogous to the existing Clone tests. Currently, only FrozenGraph implements From and FromGraph, but not GraphTopologyAddition, so the tests relying on GraphTopologyAddition are currently unused.
2026-09-25 08:41:10 +02:00
warrence d438a4288a Extract asserts from Clone tests to be reusable 2026-09-25 08:36:30 +02:00
warrence 0dea90ceff Update Dijkstra's algorithm to take any weights that satisfy the new Weight trait 2026-09-25 08:32:56 +02:00
warrence 64c48287a7 Fix ElementMap capacity in FrozenGraph::edge_map 2026-09-19 18:07:33 +02:00
warrence 345dbc0d5d Add ElementMap::map 2026-09-19 17:59:27 +02:00
warrence 038e984c97 Add FromGraph trait, and add implementation for FrozenGraph 2026-09-19 17:53:44 +02:00
warrence e593298eba Add Clone to graph models, and tests 2026-09-19 17:19:47 +02:00
warrence 4c6a8a43e4 Refactor tests: use MakeTestGraph in model-specific tests, deconstruct vertex arrays where useful, adjust local variable names 2026-09-04 09:28:00 +02:00
warrence 016d127bd1 Update comment for pub, hidden Graph structs 2026-09-04 08:16:09 +02:00
warrence 8c3c6abc78 Updated todos 2026-09-03 20:40:43 +02:00
warrence 3ad448974e Add new assert helper function for GraphTopologyDeletion tests 2026-09-03 20:40:15 +02:00
warrence 1209d75e15 Use non-zero edge indices to reduce size of structs with Option fields 2026-09-03 20:28:41 +02:00
warrence c25a8a4fc1 Add FrozenGraph tests 2026-09-03 19:49:45 +02:00
warrence 73af8a16ca Add algorithms::bfs_find_path 2026-09-03 09:43:45 +02:00
warrence a8b6f7591c Move common test helpers to fixtures module 2026-09-03 09:10:03 +02:00
warrence 8a1d491943 Format test code 2026-09-03 09:06:22 +02:00
warrence 5825ef3b47 Change all graph tests to pub to avoid unused-warnings 2026-09-02 09:30:37 +02:00
warrence 0ff830ef48 Refactor graph trait tests to minimize macro code, and split into separate files per macro
The tests only declare a list of tests in the macro, no longer the test functions themselves. This allows better tooling support (e.g. IDE and rustfmt).
2026-08-31 19:49:42 +02:00
warrence df5170f2f5 Refactor algorithm tests to minimize macro code, and move into tests/
The tests only declare a list of tests in the macro, no longer the test functions themselves. This allows better tooling support (e.g. IDE and rustfmt).
2026-08-31 19:44:55 +02:00
warrence 6d3a206995 Hide mod testing from documentation 2026-08-31 19:44:05 +02:00
warrence 95601a7a5a Add todos 2026-08-29 22:39:45 +02:00
warrence 7fbd5eed9d Refactor graph creation in all tests into new internal MakeTestGraph trait
This large refactor allows future re-use of the existing tests for `FrozenGraph`, which does not implement `GraphTopologyAddition`, but instead has to convert from a different graph which does.

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