From e07389aed819d8c91d013d79788c79f497cd78fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 3 Oct 2025 17:46:05 +0200 Subject: [PATCH 001/102] Add cargo init files --- Cargo.toml | 6 ++++++ src/main.rs | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b338f02 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "grapherity" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} -- 2.43.0 From 2e72de2281034b69a7133533e12713f4243cd020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 4 Oct 2025 22:23:08 +0200 Subject: [PATCH 002/102] Add experimental Graph implementation --- src/main.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/main.rs b/src/main.rs index e7a11a9..bff4b7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,76 @@ +#[derive(Copy, Clone, PartialEq, Eq)] +struct Vertex(usize); + +#[derive(Copy, Clone, PartialEq, Eq)] +struct Incidence(usize); + +struct VertexIncidenceHeader { + incidence_count: usize, + first_incidence: Option, +} + +// TODO: Visibility of Graph members. +struct Graph { + // TODO: Index 'vertex_incidence_headers' by a 'Vertex' instead of 'Vertex.0'? + vertex_incidence_headers: Vec, + next_incidences: Vec>, + incidence_vertices: Vec, +} + +// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. +impl Graph { + fn vertex_count(&self) -> usize { + self.vertex_incidence_headers.len() + } + + fn edge_count(&self) -> usize { + self.next_incidences.len() / 2 + } + + fn degree(&self, v: Vertex) -> usize { + self.vertex_incidence_headers[v.0].incidence_count + } + + // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. + fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { + let Some(next) = self.vertex_incidence_headers[v1.0].first_incidence else { + return false; + }; + loop { + if self.incidence_vertices[next.0] == v2 { + return true; + } + let Some(next) = self.next_incidences[next.0] else { + return false; + }; + } + } + + // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. + // TODO: 'fn add_vertex()' needs variants for custom vertex data. + fn add_vertex(&mut self) -> Vertex { + self.vertex_incidence_headers.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + Vertex(self.vertex_incidence_headers.len() - 1) + } + + // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. + // TODO: 'fn add_edge()' needs variants for custom edge data. + fn add_edge(&mut self, v1: Vertex, v2: Vertex) { + self.add_incidence(v1, v2); + self.add_incidence(v2, v1); + } + + fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { + self.incidence_vertices.push(v2); + self.next_incidences.push(self.vertex_incidence_headers[v1.0].first_incidence.take()); + self.vertex_incidence_headers[v1.0].incidence_count += 1; + self.vertex_incidence_headers[v1.0].first_incidence = Some(Incidence(self.incidence_vertices.len() - 1)); + } +} + fn main() { println!("Hello, world!"); } -- 2.43.0 From b85edb12875d4358afcb6fefc0da0b99a43f1f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 4 Oct 2025 22:23:42 +0200 Subject: [PATCH 003/102] Add RustRover files --- .idea/.gitignore | 8 ++++++++ .idea/grapherity.iml | 11 +++++++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 7 +++++++ 4 files changed, 34 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/grapherity.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/grapherity.iml b/.idea/grapherity.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/grapherity.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e8807bb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..8306744 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file -- 2.43.0 From 58f330ef4635285c33ac3d683a2e36834f818cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sun, 5 Oct 2025 23:41:27 +0200 Subject: [PATCH 004/102] Fix are_adjacent() method Next incidence was not updated correctly, which could lead to bad results and infinite loops. --- src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index bff4b7a..6768cac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,16 +33,17 @@ impl Graph { // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - let Some(next) = self.vertex_incidence_headers[v1.0].first_incidence else { + let Some(mut incidence) = self.vertex_incidence_headers[v1.0].first_incidence else { return false; }; loop { - if self.incidence_vertices[next.0] == v2 { + if self.incidence_vertices[incidence.0] == v2 { return true; } - let Some(next) = self.next_incidences[next.0] else { + let Some(next) = self.next_incidences[incidence.0] else { return false; }; + incidence = next; } } -- 2.43.0 From 5a1080783cf156967c2c4b096334a4f9fda38837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sun, 5 Oct 2025 23:53:06 +0200 Subject: [PATCH 005/102] Add Graph example test --- src/main.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6768cac..5eb5c7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] struct Vertex(usize); #[derive(Copy, Clone, PartialEq, Eq)] @@ -73,5 +73,56 @@ impl Graph { } fn main() { - println!("Hello, world!"); + // TODO: Move this graph example into one or more tests. + let mut graph = Graph{ + // TODO: The initializations should happen in an initializer or constructor + vertex_incidence_headers: vec![], + next_incidences: vec![], + incidence_vertices: vec![], + }; + let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + + assert_eq!(graph.vertex_count(), 10, "vertex count"); + assert_eq!(graph.edge_count(), 14, "edge count"); + let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + for i in 0..graph.vertex_count() { + assert_eq!(graph.degree(vertices[i]), expected_degrees[i], + "degree of {:?}", vertices[i]); + } + + assert_eq!(graph.are_adjacent(vertices[0], vertices[1]), true, + "adjacency of {:?} and {:?}", vertices[0], vertices[1]); + assert_eq!(graph.are_adjacent(vertices[9], vertices[5]), true, + "adjacency of {:?} and {:?}", vertices[0], vertices[5]); + assert_eq!(graph.are_adjacent(vertices[9], vertices[3]), false, + "adjacency of {:?} and {:?}", vertices[9], vertices[3]); + + for i in 0..vertices.len() { + let exp = match i { + 2 => continue, + 1 | 4 | 5 | 6 => true, + _ => false, + }; + assert_eq!(graph.are_adjacent(vertices[2], vertices[i]), exp, + "adjacency of {:?} and {:?}", vertices[2], vertices[i]); + assert_eq!(graph.are_adjacent(vertices[i], vertices[2]), exp, + "adjacency of {:?} and {:?}", vertices[i], vertices[2]); + } + + // TODO: test Dijkstra's algorithm starting at 0 and at another vertex. + let expected_distances_to_v0 = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4]; } -- 2.43.0 From e518d72a9ec5b64b2fddecb555cb8616fad7a6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 6 Oct 2025 10:41:04 +0200 Subject: [PATCH 006/102] Rename Graph.vertex_incidence_headers to "incidence_headers" --- src/main.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5eb5c7f..5ba7885 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,8 @@ struct VertexIncidenceHeader { // TODO: Visibility of Graph members. struct Graph { - // TODO: Index 'vertex_incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - vertex_incidence_headers: Vec, + // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? + incidence_headers: Vec, next_incidences: Vec>, incidence_vertices: Vec, } @@ -20,7 +20,7 @@ struct Graph { // TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. impl Graph { fn vertex_count(&self) -> usize { - self.vertex_incidence_headers.len() + self.incidence_headers.len() } fn edge_count(&self) -> usize { @@ -28,12 +28,12 @@ impl Graph { } fn degree(&self, v: Vertex) -> usize { - self.vertex_incidence_headers[v.0].incidence_count + self.incidence_headers[v.0].incidence_count } // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - let Some(mut incidence) = self.vertex_incidence_headers[v1.0].first_incidence else { + let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { return false; }; loop { @@ -50,11 +50,11 @@ impl Graph { // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. // TODO: 'fn add_vertex()' needs variants for custom vertex data. fn add_vertex(&mut self) -> Vertex { - self.vertex_incidence_headers.push(VertexIncidenceHeader { + self.incidence_headers.push(VertexIncidenceHeader { incidence_count: 0, first_incidence: None, }); - Vertex(self.vertex_incidence_headers.len() - 1) + Vertex(self.incidence_headers.len() - 1) } // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. @@ -66,9 +66,9 @@ impl Graph { fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { self.incidence_vertices.push(v2); - self.next_incidences.push(self.vertex_incidence_headers[v1.0].first_incidence.take()); - self.vertex_incidence_headers[v1.0].incidence_count += 1; - self.vertex_incidence_headers[v1.0].first_incidence = Some(Incidence(self.incidence_vertices.len() - 1)); + self.next_incidences.push(self.incidence_headers[v1.0].first_incidence.take()); + self.incidence_headers[v1.0].incidence_count += 1; + self.incidence_headers[v1.0].first_incidence = Some(Incidence(self.incidence_vertices.len() - 1)); } } @@ -76,7 +76,7 @@ fn main() { // TODO: Move this graph example into one or more tests. let mut graph = Graph{ // TODO: The initializations should happen in an initializer or constructor - vertex_incidence_headers: vec![], + incidence_headers: vec![], next_incidences: vec![], incidence_vertices: vec![], }; -- 2.43.0 From 04c9150172de067014d88acf766a7d492ca09f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 6 Oct 2025 10:59:49 +0200 Subject: [PATCH 007/102] Reformat code with rustfmt --- src/main.rs | 61 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5ba7885..e839bf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,15 +66,17 @@ impl Graph { fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { self.incidence_vertices.push(v2); - self.next_incidences.push(self.incidence_headers[v1.0].first_incidence.take()); + self.next_incidences + .push(self.incidence_headers[v1.0].first_incidence.take()); self.incidence_headers[v1.0].incidence_count += 1; - self.incidence_headers[v1.0].first_incidence = Some(Incidence(self.incidence_vertices.len() - 1)); + self.incidence_headers[v1.0].first_incidence = + Some(Incidence(self.incidence_vertices.len() - 1)); } } fn main() { // TODO: Move this graph example into one or more tests. - let mut graph = Graph{ + let mut graph = Graph { // TODO: The initializations should happen in an initializer or constructor incidence_headers: vec![], next_incidences: vec![], @@ -100,16 +102,35 @@ fn main() { assert_eq!(graph.edge_count(), 14, "edge count"); let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { - assert_eq!(graph.degree(vertices[i]), expected_degrees[i], - "degree of {:?}", vertices[i]); + assert_eq!( + graph.degree(vertices[i]), + expected_degrees[i], + "degree of {:?}", + vertices[i] + ); } - assert_eq!(graph.are_adjacent(vertices[0], vertices[1]), true, - "adjacency of {:?} and {:?}", vertices[0], vertices[1]); - assert_eq!(graph.are_adjacent(vertices[9], vertices[5]), true, - "adjacency of {:?} and {:?}", vertices[0], vertices[5]); - assert_eq!(graph.are_adjacent(vertices[9], vertices[3]), false, - "adjacency of {:?} and {:?}", vertices[9], vertices[3]); + assert_eq!( + graph.are_adjacent(vertices[0], vertices[1]), + true, + "adjacency of {:?} and {:?}", + vertices[0], + vertices[1] + ); + assert_eq!( + graph.are_adjacent(vertices[9], vertices[5]), + true, + "adjacency of {:?} and {:?}", + vertices[0], + vertices[5] + ); + assert_eq!( + graph.are_adjacent(vertices[9], vertices[3]), + false, + "adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); for i in 0..vertices.len() { let exp = match i { @@ -117,10 +138,20 @@ fn main() { 1 | 4 | 5 | 6 => true, _ => false, }; - assert_eq!(graph.are_adjacent(vertices[2], vertices[i]), exp, - "adjacency of {:?} and {:?}", vertices[2], vertices[i]); - assert_eq!(graph.are_adjacent(vertices[i], vertices[2]), exp, - "adjacency of {:?} and {:?}", vertices[i], vertices[2]); + assert_eq!( + graph.are_adjacent(vertices[2], vertices[i]), + exp, + "adjacency of {:?} and {:?}", + vertices[2], + vertices[i] + ); + assert_eq!( + graph.are_adjacent(vertices[i], vertices[2]), + exp, + "adjacency of {:?} and {:?}", + vertices[i], + vertices[2] + ); } // TODO: test Dijkstra's algorithm starting at 0 and at another vertex. -- 2.43.0 From 2a35c47ad41510dbdc04dba1b2ed74d8718ea65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 6 Oct 2025 18:34:17 +0200 Subject: [PATCH 008/102] Add basic Dijkstra algorithm with test in main() --- src/main.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index e839bf5..8217e97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +use std::cmp::Ordering; +use std::collections::BinaryHeap; + #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct Vertex(usize); @@ -74,6 +77,70 @@ impl Graph { } } +#[derive(PartialEq, Eq)] +struct DistanceOrderedVertex { + distance: u32, + vertex: Vertex, +} + +impl PartialOrd for DistanceOrderedVertex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.distance.cmp(&other.distance)) + } +} + +impl Ord for DistanceOrderedVertex { + fn cmp(&self, other: &Self) -> Ordering { + other.distance.cmp(&self.distance) + } +} + +// TODO: Maybe introduce a return struct type for Dijkstra's algorithm? +fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { + let mut distances = vec![None; graph.vertex_count()]; + let mut predecessors = vec![None; graph.vertex_count()]; + let mut heap = BinaryHeap::new(); + + distances[source.0] = Some(0); + heap.push(DistanceOrderedVertex { + vertex: source, + distance: 0, + }); + while let Some(v) = heap.pop() { + // TODO: Simplify with a neighbor iterator. + // Finds the first neighbor of v. + let Some(mut incidence) = graph.incidence_headers[v.vertex.0].first_incidence else { + break; + }; + loop { + // Processes one neighbor of v. + let neighbor = graph.incidence_vertices[incidence.0]; + // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. + let edge_weight = 1; + let new_distance = distances[v.vertex.0].unwrap() + edge_weight; + if match distances[neighbor.0] { + None => true, + Some(old_distance) if old_distance > new_distance => true, + _ => false, + } { + distances[neighbor.0] = Some(new_distance); + predecessors[neighbor.0] = Some(v.vertex); + heap.push(DistanceOrderedVertex { + vertex: neighbor, + distance: new_distance, + }); + } + + // Finds next neighbor of v. + let Some(next) = graph.next_incidences[incidence.0] else { + break; + }; + incidence = next; + } + } + (distances, predecessors) +} + fn main() { // TODO: Move this graph example into one or more tests. let mut graph = Graph { @@ -154,6 +221,52 @@ fn main() { ); } - // TODO: test Dijkstra's algorithm starting at 0 and at another vertex. - let expected_distances_to_v0 = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4]; + let (distances, predecessors) = dijkstra(&graph, vertices[0]); + 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), + ]; + 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])], + ]; + assert_eq!( + distances.len(), + graph.vertex_count(), + "distances count from Dijkstra's algorithm must equal vertex count" + ); + assert_eq!( + predecessors.len(), + graph.vertex_count(), + "predecessors count from Dijkstra's algorithm must equal vertex count" + ); + for i in 0..graph.vertex_count() { + assert_eq!( + distances[i], expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?} from Dijkstra's algorithm", + vertices[0], vertices[i] + ); + assert!( + expected_predecessors_from_v0[i].contains(&predecessors[i]), + "unexpected predecessor {:?} of {:?} from Dijkstra's algorithm", + predecessors[i], + vertices[i] + ); + } } -- 2.43.0 From 5bf723928bf20e0d7a8680e70b9429255a7d225e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 6 Oct 2025 18:43:37 +0200 Subject: [PATCH 009/102] Update test assertions in main() --- src/main.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8217e97..5fc6937 100644 --- a/src/main.rs +++ b/src/main.rs @@ -165,41 +165,38 @@ fn main() { graph.add_edge(vertices[7], vertices[8]); graph.add_edge(vertices[7], vertices[9]); - assert_eq!(graph.vertex_count(), 10, "vertex count"); - assert_eq!(graph.edge_count(), 14, "edge count"); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 14, "unexpected edge count"); let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { assert_eq!( graph.degree(vertices[i]), expected_degrees[i], - "degree of {:?}", + "unexpected degree of {:?}", vertices[i] ); } - assert_eq!( + assert!( graph.are_adjacent(vertices[0], vertices[1]), - true, - "adjacency of {:?} and {:?}", + "expected {:?} and {:?} to be adjacent", vertices[0], vertices[1] ); - assert_eq!( + assert!( graph.are_adjacent(vertices[9], vertices[5]), - true, - "adjacency of {:?} and {:?}", + "expected {:?} and {:?} to be adjacent", vertices[0], vertices[5] ); - assert_eq!( - graph.are_adjacent(vertices[9], vertices[3]), - false, - "adjacency of {:?} and {:?}", + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", vertices[9], vertices[3] ); - for i in 0..vertices.len() { + for i in 0..graph.vertex_count() { let exp = match i { 2 => continue, 1 | 4 | 5 | 6 => true, @@ -208,14 +205,14 @@ fn main() { assert_eq!( graph.are_adjacent(vertices[2], vertices[i]), exp, - "adjacency of {:?} and {:?}", + "unexpected adjacency of {:?} and {:?}", vertices[2], vertices[i] ); assert_eq!( graph.are_adjacent(vertices[i], vertices[2]), exp, - "adjacency of {:?} and {:?}", + "unexpected adjacency of {:?} and {:?}", vertices[i], vertices[2] ); -- 2.43.0 From e879316a4c84e893fce207e43ba5e2ca6a865c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 6 Oct 2025 19:25:20 +0200 Subject: [PATCH 010/102] Update todo --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 5fc6937..1b99b6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,6 +96,7 @@ impl Ord for DistanceOrderedVertex { } // TODO: Maybe introduce a return struct type for Dijkstra's algorithm? +// TODO: Maybe variant of Dijkstra's algorithm without predecessors? fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { let mut distances = vec![None; graph.vertex_count()]; let mut predecessors = vec![None; graph.vertex_count()]; -- 2.43.0 From 43974d675472b658a80e8db165a155f1426c6367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 13:42:50 +0200 Subject: [PATCH 011/102] Add todos --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 1b99b6a..bb0f212 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,8 @@ struct Graph { incidence_vertices: Vec, } +// TODO: Add iterator of vertices. +// TODO: Add iterator of edges. // TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. impl Graph { fn vertex_count(&self) -> usize { -- 2.43.0 From 46675a1246b0985135e077c62b980f4038b8c119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 14:30:14 +0200 Subject: [PATCH 012/102] Add success message at the end of main() --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index bb0f212..45f0f33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -269,4 +269,5 @@ fn main() { vertices[i] ); } + println!("Example test success!"); } -- 2.43.0 From 720f497b648536463cd6de30f5736082644c6c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 14:32:52 +0200 Subject: [PATCH 013/102] Add "algorithms" and "models" modules for existing code and related todos --- src/algorithms.rs | 70 ++++++++++++++++++++++ src/main.rs | 150 ++-------------------------------------------- src/models.rs | 81 +++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 145 deletions(-) create mode 100644 src/algorithms.rs create mode 100644 src/models.rs diff --git a/src/algorithms.rs b/src/algorithms.rs new file mode 100644 index 0000000..f263163 --- /dev/null +++ b/src/algorithms.rs @@ -0,0 +1,70 @@ +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +use crate::models::Graph; +use crate::models::Vertex; + +#[derive(PartialEq, Eq)] +struct DistanceOrderedVertex { + distance: u32, + vertex: Vertex, +} + +impl PartialOrd for DistanceOrderedVertex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.distance.cmp(&other.distance)) + } +} + +impl Ord for DistanceOrderedVertex { + fn cmp(&self, other: &Self) -> Ordering { + other.distance.cmp(&self.distance) + } +} + +// TODO: Maybe introduce a return struct type for Dijkstra's algorithm? +// TODO: Maybe variant of Dijkstra's algorithm without predecessors? +pub fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { + let mut distances = vec![None; graph.vertex_count()]; + let mut predecessors = vec![None; graph.vertex_count()]; + let mut heap = BinaryHeap::new(); + + distances[source.0] = Some(0); + heap.push(DistanceOrderedVertex { + vertex: source, + distance: 0, + }); + while let Some(v) = heap.pop() { + // TODO: Simplify with a neighbor iterator. + // Finds the first neighbor of v. + let Some(mut incidence) = graph.incidence_headers[v.vertex.0].first_incidence else { + break; + }; + loop { + // Processes one neighbor of v. + let neighbor = graph.incidence_vertices[incidence.0]; + // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. + let edge_weight = 1; + let new_distance = distances[v.vertex.0].unwrap() + edge_weight; + if match distances[neighbor.0] { + None => true, + Some(old_distance) if old_distance > new_distance => true, + _ => false, + } { + distances[neighbor.0] = Some(new_distance); + predecessors[neighbor.0] = Some(v.vertex); + heap.push(DistanceOrderedVertex { + vertex: neighbor, + distance: new_distance, + }); + } + + // Finds next neighbor of v. + let Some(next) = graph.next_incidences[incidence.0] else { + break; + }; + incidence = next; + } + } + (distances, predecessors) +} diff --git a/src/main.rs b/src/main.rs index 45f0f33..bc1306a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,148 +1,8 @@ -use std::cmp::Ordering; -use std::collections::BinaryHeap; +mod algorithms; +mod models; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -struct Vertex(usize); - -#[derive(Copy, Clone, PartialEq, Eq)] -struct Incidence(usize); - -struct VertexIncidenceHeader { - incidence_count: usize, - first_incidence: Option, -} - -// TODO: Visibility of Graph members. -struct Graph { - // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - incidence_headers: Vec, - next_incidences: Vec>, - incidence_vertices: Vec, -} - -// TODO: Add iterator of vertices. -// TODO: Add iterator of edges. -// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. -impl Graph { - fn vertex_count(&self) -> usize { - self.incidence_headers.len() - } - - fn edge_count(&self) -> usize { - self.next_incidences.len() / 2 - } - - fn degree(&self, v: Vertex) -> usize { - self.incidence_headers[v.0].incidence_count - } - - // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. - fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { - return false; - }; - loop { - if self.incidence_vertices[incidence.0] == v2 { - return true; - } - let Some(next) = self.next_incidences[incidence.0] else { - return false; - }; - incidence = next; - } - } - - // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. - // TODO: 'fn add_vertex()' needs variants for custom vertex data. - fn add_vertex(&mut self) -> Vertex { - self.incidence_headers.push(VertexIncidenceHeader { - incidence_count: 0, - first_incidence: None, - }); - Vertex(self.incidence_headers.len() - 1) - } - - // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. - // TODO: 'fn add_edge()' needs variants for custom edge data. - fn add_edge(&mut self, v1: Vertex, v2: Vertex) { - self.add_incidence(v1, v2); - self.add_incidence(v2, v1); - } - - fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { - self.incidence_vertices.push(v2); - self.next_incidences - .push(self.incidence_headers[v1.0].first_incidence.take()); - self.incidence_headers[v1.0].incidence_count += 1; - self.incidence_headers[v1.0].first_incidence = - Some(Incidence(self.incidence_vertices.len() - 1)); - } -} - -#[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { - distance: u32, - vertex: Vertex, -} - -impl PartialOrd for DistanceOrderedVertex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.distance.cmp(&other.distance)) - } -} - -impl Ord for DistanceOrderedVertex { - fn cmp(&self, other: &Self) -> Ordering { - other.distance.cmp(&self.distance) - } -} - -// TODO: Maybe introduce a return struct type for Dijkstra's algorithm? -// TODO: Maybe variant of Dijkstra's algorithm without predecessors? -fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { - let mut distances = vec![None; graph.vertex_count()]; - let mut predecessors = vec![None; graph.vertex_count()]; - let mut heap = BinaryHeap::new(); - - distances[source.0] = Some(0); - heap.push(DistanceOrderedVertex { - vertex: source, - distance: 0, - }); - while let Some(v) = heap.pop() { - // TODO: Simplify with a neighbor iterator. - // Finds the first neighbor of v. - let Some(mut incidence) = graph.incidence_headers[v.vertex.0].first_incidence else { - break; - }; - loop { - // Processes one neighbor of v. - let neighbor = graph.incidence_vertices[incidence.0]; - // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. - let edge_weight = 1; - let new_distance = distances[v.vertex.0].unwrap() + edge_weight; - if match distances[neighbor.0] { - None => true, - Some(old_distance) if old_distance > new_distance => true, - _ => false, - } { - distances[neighbor.0] = Some(new_distance); - predecessors[neighbor.0] = Some(v.vertex); - heap.push(DistanceOrderedVertex { - vertex: neighbor, - distance: new_distance, - }); - } - - // Finds next neighbor of v. - let Some(next) = graph.next_incidences[incidence.0] else { - break; - }; - incidence = next; - } - } - (distances, predecessors) -} +use models::Graph; +use models::Vertex; fn main() { // TODO: Move this graph example into one or more tests. @@ -221,7 +81,7 @@ fn main() { ); } - let (distances, predecessors) = dijkstra(&graph, vertices[0]); + let (distances, predecessors) = algorithms::dijkstra(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), Some(1), diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..4847121 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,81 @@ +// TODO: Vertex value must not be public. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Vertex(pub usize); + +// TODO: Incidence value must not be public. +// TODO: Incidence must not be public. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Incidence(pub usize); + +// TODO: VertexIncidenceHeader and its fields must not be public, but without iterators there is currently no other way to access vertex incidences. +pub struct VertexIncidenceHeader { + incidence_count: usize, + pub first_incidence: Option, +} + +// TODO: Graph fields must not be public. +pub struct Graph { + // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? + pub incidence_headers: Vec, + pub next_incidences: Vec>, + pub incidence_vertices: Vec, +} + +// TODO: Add iterator of vertices. +// TODO: Add iterator of edges. +// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. +impl Graph { + pub fn vertex_count(&self) -> usize { + self.incidence_headers.len() + } + + pub fn edge_count(&self) -> usize { + self.next_incidences.len() / 2 + } + + pub fn degree(&self, v: Vertex) -> usize { + self.incidence_headers[v.0].incidence_count + } + + // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. + pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { + let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { + return false; + }; + loop { + if self.incidence_vertices[incidence.0] == v2 { + return true; + } + let Some(next) = self.next_incidences[incidence.0] else { + return false; + }; + incidence = next; + } + } + + // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. + // TODO: 'fn add_vertex()' needs variants for custom vertex data. + pub fn add_vertex(&mut self) -> Vertex { + self.incidence_headers.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + Vertex(self.incidence_headers.len() - 1) + } + + // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. + // TODO: 'fn add_edge()' needs variants for custom edge data. + pub fn add_edge(&mut self, v1: Vertex, v2: Vertex) { + self.add_incidence(v1, v2); + self.add_incidence(v2, v1); + } + + fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { + self.incidence_vertices.push(v2); + self.next_incidences + .push(self.incidence_headers[v1.0].first_incidence.take()); + self.incidence_headers[v1.0].incidence_count += 1; + self.incidence_headers[v1.0].first_incidence = + Some(Incidence(self.incidence_vertices.len() - 1)); + } +} -- 2.43.0 From 679b597f928983531b3b93f0d266545ab0437ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 14:38:28 +0200 Subject: [PATCH 014/102] Add todo --- src/models.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models.rs b/src/models.rs index 4847121..ba56b93 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,3 +1,4 @@ +// TODO: Vertex (and Incidence?) should be references of Graph data, not plain indices into the internal (Vec) data structures. // TODO: Vertex value must not be public. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Vertex(pub usize); -- 2.43.0 From 66e1498c76afed3f589214b9a44aa51b87d75a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 8 Oct 2025 17:04:25 +0200 Subject: [PATCH 015/102] Add Graph::new() --- src/main.rs | 7 +------ src/models.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index bc1306a..49ba617 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,7 @@ use models::Vertex; fn main() { // TODO: Move this graph example into one or more tests. - let mut graph = Graph { - // TODO: The initializations should happen in an initializer or constructor - incidence_headers: vec![], - next_incidences: vec![], - incidence_vertices: vec![], - }; + let mut graph = Graph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); diff --git a/src/models.rs b/src/models.rs index ba56b93..96ef011 100644 --- a/src/models.rs +++ b/src/models.rs @@ -26,6 +26,14 @@ pub struct Graph { // TODO: Add iterator of edges. // TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. impl Graph { + pub fn new() -> Graph { + Graph { + incidence_headers: vec![], + next_incidences: vec![], + incidence_vertices: vec![], + } + } + pub fn vertex_count(&self) -> usize { self.incidence_headers.len() } -- 2.43.0 From a4ed7ea44e618683633a22e91ec8d171393bd027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 11 Oct 2025 21:44:20 +0200 Subject: [PATCH 016/102] Update todos --- src/models.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models.rs b/src/models.rs index 96ef011..e54ba13 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,4 +1,3 @@ -// TODO: Vertex (and Incidence?) should be references of Graph data, not plain indices into the internal (Vec) data structures. // TODO: Vertex value must not be public. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Vertex(pub usize); @@ -14,7 +13,10 @@ pub struct VertexIncidenceHeader { pub first_incidence: Option, } +// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. // TODO: Graph fields must not be public. +// TODO: Add functions to reserve memory for vertices and edges. +// TODO: Add function to delete edges. pub struct Graph { // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? pub incidence_headers: Vec, @@ -62,7 +64,6 @@ impl Graph { } } - // TODO: 'fn add_vertex()' should not be publicly accessible for all graph implementations. // TODO: 'fn add_vertex()' needs variants for custom vertex data. pub fn add_vertex(&mut self) -> Vertex { self.incidence_headers.push(VertexIncidenceHeader { @@ -72,7 +73,6 @@ impl Graph { Vertex(self.incidence_headers.len() - 1) } - // TODO: 'fn add_edge()' should not be publicly accessible for all graph implementations. // TODO: 'fn add_edge()' needs variants for custom edge data. pub fn add_edge(&mut self, v1: Vertex, v2: Vertex) { self.add_incidence(v1, v2); -- 2.43.0 From bff8ca685779aff2a6f5a5d78e17aeaba699b027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 15 Oct 2025 17:09:11 +0200 Subject: [PATCH 017/102] Move main() code into tests and remove binary crate --- .idea/grapherity.iml | 1 + src/lib.rs | 2 + src/models.rs | 96 ++++++++++++++++++++++++++++ src/main.rs => tests/dijkstra.rs | 104 ++++++++----------------------- 4 files changed, 125 insertions(+), 78 deletions(-) create mode 100644 src/lib.rs rename src/main.rs => tests/dijkstra.rs (57%) diff --git a/.idea/grapherity.iml b/.idea/grapherity.iml index cf84ae4..bbe0a70 100644 --- a/.idea/grapherity.iml +++ b/.idea/grapherity.iml @@ -3,6 +3,7 @@ + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..67722ce --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod algorithms; +pub mod models; diff --git a/src/models.rs b/src/models.rs index e54ba13..a9d59ab 100644 --- a/src/models.rs +++ b/src/models.rs @@ -88,3 +88,99 @@ impl Graph { Some(Incidence(self.incidence_vertices.len() - 1)); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn vertex_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + } + + #[test] + fn edge_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.edge_count(), 14, "unexpected edge count"); + } + + #[test] + fn degrees() { + let (graph, vertices) = make_test_graph(); + let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + for i in 0..graph.vertex_count() { + assert_eq!( + graph.degree(vertices[i]), + expected_degrees[i], + "unexpected degree of {:?}", + vertices[i] + ); + } + } + + #[test] + fn adjacencies() { + let (graph, vertices) = make_test_graph(); + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[1] + ); + assert!( + graph.are_adjacent(vertices[9], vertices[5]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[5] + ); + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); + + for i in 0..graph.vertex_count() { + let exp = match i { + 2 => continue, + 1 | 4 | 5 | 6 => true, + _ => false, + }; + assert_eq!( + graph.are_adjacent(vertices[2], vertices[i]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[2], + vertices[i] + ); + assert_eq!( + graph.are_adjacent(vertices[i], vertices[2]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[i], + vertices[2] + ); + } + } + + fn make_test_graph() -> (Graph, [Vertex; 10]) { + let mut graph = Graph::new(); + let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + (graph, vertices) + } +} diff --git a/src/main.rs b/tests/dijkstra.rs similarity index 57% rename from src/main.rs rename to tests/dijkstra.rs index 49ba617..b540223 100644 --- a/src/main.rs +++ b/tests/dijkstra.rs @@ -1,81 +1,10 @@ -mod algorithms; -mod models; - -use models::Graph; -use models::Vertex; - -fn main() { - // TODO: Move this graph example into one or more tests. - let mut graph = Graph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - - assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 14, "unexpected edge count"); - let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; - for i in 0..graph.vertex_count() { - assert_eq!( - graph.degree(vertices[i]), - expected_degrees[i], - "unexpected degree of {:?}", - vertices[i] - ); - } - - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[1] - ); - assert!( - graph.are_adjacent(vertices[9], vertices[5]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[5] - ); - assert!( - !graph.are_adjacent(vertices[9], vertices[3]), - "unexpected adjacency of {:?} and {:?}", - vertices[9], - vertices[3] - ); - - for i in 0..graph.vertex_count() { - let exp = match i { - 2 => continue, - 1 | 4 | 5 | 6 => true, - _ => false, - }; - assert_eq!( - graph.are_adjacent(vertices[2], vertices[i]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[2], - vertices[i] - ); - assert_eq!( - graph.are_adjacent(vertices[i], vertices[2]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[i], - vertices[2] - ); - } +use grapherity::algorithms; +use grapherity::models::Graph; +use grapherity::models::Vertex; +#[test] +fn dijkstra() { + let (graph, vertices) = make_test_graph(); let (distances, predecessors) = algorithms::dijkstra(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), @@ -124,5 +53,24 @@ fn main() { vertices[i] ); } - println!("Example test success!"); +} + +fn make_test_graph() -> (Graph, [Vertex; 10]) { + let mut graph = Graph::new(); + let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + (graph, vertices) } -- 2.43.0 From 0826e140b83e947e25289e60cc936f1033d79083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 21 Oct 2025 15:57:48 +0200 Subject: [PATCH 018/102] Add iterator over vertices Graph::iter() --- src/models.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index a9d59ab..ba8ad71 100644 --- a/src/models.rs +++ b/src/models.rs @@ -24,7 +24,6 @@ pub struct Graph { pub incidence_vertices: Vec, } -// TODO: Add iterator of vertices. // TODO: Add iterator of edges. // TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. impl Graph { @@ -87,6 +86,10 @@ impl Graph { self.incidence_headers[v1.0].first_incidence = Some(Incidence(self.incidence_vertices.len() - 1)); } + + pub fn iter(&self) -> impl Iterator { + (0..self.incidence_headers.len()).map(Vertex) + } } #[cfg(test)] -- 2.43.0 From dd76355a70b72d23546c62ad51300bed44a71ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 21 Oct 2025 16:36:53 +0200 Subject: [PATCH 019/102] Add iterator over neighbors of a vertex Graph::neighbors() --- src/models.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/models.rs b/src/models.rs index ba8ad71..1163b97 100644 --- a/src/models.rs +++ b/src/models.rs @@ -13,10 +13,26 @@ pub struct VertexIncidenceHeader { pub first_incidence: Option, } +pub struct VertexNeighborIterator<'a> { + graph: &'a Graph, + incidence: Option, +} + +impl<'a> Iterator for VertexNeighborIterator<'a> { + type Item = Vertex; + + fn next(&mut self) -> Option { + let incidence = self.incidence?; + self.incidence = self.graph.next_incidences[incidence.0]; + Some(self.graph.incidence_vertices[incidence.0]) + } +} + // TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. // TODO: Graph fields must not be public. // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add function to delete edges. +// TODO: Add iterator of edges. pub struct Graph { // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? pub incidence_headers: Vec, @@ -24,8 +40,6 @@ pub struct Graph { pub incidence_vertices: Vec, } -// TODO: Add iterator of edges. -// TODO: Add iterator of vertex neighbors, see 'fn add_adjacent()'. impl Graph { pub fn new() -> Graph { Graph { @@ -90,6 +104,13 @@ impl Graph { pub fn iter(&self) -> impl Iterator { (0..self.incidence_headers.len()).map(Vertex) } + + pub fn neighbors(&self, v: Vertex) -> impl Iterator { + VertexNeighborIterator { + graph: self, + incidence: self.incidence_headers[v.0].first_incidence, + } + } } #[cfg(test)] -- 2.43.0 From 72d3c06f1e23514e2ffc7d376899f61a8855a2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 21 Oct 2025 16:38:02 +0200 Subject: [PATCH 020/102] Rename Graph::iter() to Graph::vertices() --- src/models.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index 1163b97..8b728bc 100644 --- a/src/models.rs +++ b/src/models.rs @@ -101,7 +101,7 @@ impl Graph { Some(Incidence(self.incidence_vertices.len() - 1)); } - pub fn iter(&self) -> impl Iterator { + pub fn vertices(&self) -> impl Iterator { (0..self.incidence_headers.len()).map(Vertex) } -- 2.43.0 From 74ca390ac2a21206497fb93d9373e7b86f860b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 24 Oct 2025 23:14:04 +0200 Subject: [PATCH 021/102] Add tests for Graph::vertices() --- src/models.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/models.rs b/src/models.rs index 8b728bc..e31e264 100644 --- a/src/models.rs +++ b/src/models.rs @@ -188,6 +188,32 @@ mod tests { } } + #[test] + fn vertices_empty() { + let graph = Graph::new(); + assert_eq!( + graph.vertices().count(), + 0, + "vertex iterator of empty graph should have no elements" + ); + } + + #[test] + fn vertices() { + let (graph, vertices) = make_test_graph(); + assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); + + let mut vertices: Vec = vertices.into_iter().collect(); + for v in graph.vertices() { + if let Some(index) = vertices.iter().position(|&x| x == v) { + vertices.swap_remove(index); + } else { + panic!("unexpected vertex of {v:?} from iterator"); + } + } + println!("vertices: {:?}", vertices); + } + fn make_test_graph() -> (Graph, [Vertex; 10]) { let mut graph = Graph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); -- 2.43.0 From a26b0eba8c1f03747202e80695c39354500a7b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 24 Oct 2025 23:29:25 +0200 Subject: [PATCH 022/102] Add tests for Graph::neighbors() --- src/models.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/models.rs b/src/models.rs index e31e264..325000a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -214,6 +214,34 @@ mod tests { println!("vertices: {:?}", vertices); } + #[test] + fn neighbors_empty() { + let mut graph = Graph::new(); + let vertex = graph.add_vertex(); + assert_eq!( + graph.neighbors(vertex).count(), + 0, + "neighbor iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn neighbors() { + let (graph, vertices) = make_test_graph(); + // Checks neighbors of vertex 4. + assert_eq!(graph.neighbors(vertices[4]).count(), 4, "unexpected vertex count"); + + let mut neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + for v in graph.neighbors(vertices[4]) { + if let Some(index) = neighbors.iter().position(|&x| x == v) { + neighbors.swap_remove(index); + } else { + panic!("unexpected vertex of {v:?} from iterator"); + } + } + println!("vertices: {:?}", vertices); + } + fn make_test_graph() -> (Graph, [Vertex; 10]) { let mut graph = Graph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); -- 2.43.0 From 6ff02f8c67d90e9a2c2487c8385f4e89f9a16d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 00:41:08 +0200 Subject: [PATCH 023/102] Update vertex iterator tests --- src/models.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/models.rs b/src/models.rs index 325000a..64268af 100644 --- a/src/models.rs +++ b/src/models.rs @@ -203,15 +203,14 @@ mod tests { let (graph, vertices) = make_test_graph(); assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); - let mut vertices: Vec = vertices.into_iter().collect(); + // Expects each vertex to appear exactly once. for v in graph.vertices() { - if let Some(index) = vertices.iter().position(|&x| x == v) { - vertices.swap_remove(index); - } else { - panic!("unexpected vertex of {v:?} from iterator"); - } + assert_eq!( + vertices.iter().filter(|&x| *x == v).count(), + 1, + "unexpected vertex {v:?} from the iterator" + ); } - println!("vertices: {:?}", vertices); } #[test] @@ -229,17 +228,22 @@ mod tests { fn neighbors() { let (graph, vertices) = make_test_graph(); // Checks neighbors of vertex 4. - assert_eq!(graph.neighbors(vertices[4]).count(), 4, "unexpected vertex count"); + assert_eq!( + graph.neighbors(vertices[4]).count(), + 4, + "unexpected vertex count" + ); - let mut neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. + let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; for v in graph.neighbors(vertices[4]) { - if let Some(index) = neighbors.iter().position(|&x| x == v) { - neighbors.swap_remove(index); - } else { - panic!("unexpected vertex of {v:?} from iterator"); - } + assert_eq!( + neighbors.iter().filter(|&x| *x == v).count(), + 1, + "unexpected neighbor {v:?} of {:?} from the iterator", + vertices[4] + ); } - println!("vertices: {:?}", vertices); } fn make_test_graph() -> (Graph, [Vertex; 10]) { -- 2.43.0 From 50f3a150bbaa63098837d2b240c5d8f0b9ac710b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 00:51:48 +0200 Subject: [PATCH 024/102] Add tests for some trivial graphs --- src/models.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/models.rs b/src/models.rs index 64268af..58e25b0 100644 --- a/src/models.rs +++ b/src/models.rs @@ -117,12 +117,31 @@ impl Graph { mod tests { use super::*; + #[test] + fn add_vertex() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); + } + + #[test] + fn vertex_count_empty() { + let graph = Graph::new(); + assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); + } + #[test] fn vertex_count() { let (graph, _) = make_test_graph(); assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } + #[test] + fn edge_count_empty() { + let graph = Graph::new(); + assert_eq!(graph.edge_count(), 0, "unexpected edge count"); + } + #[test] fn edge_count() { let (graph, _) = make_test_graph(); @@ -130,7 +149,14 @@ mod tests { } #[test] - fn degrees() { + fn degree_zero() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); + } + + #[test] + fn degree() { let (graph, vertices) = make_test_graph(); let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { @@ -144,7 +170,19 @@ mod tests { } #[test] - fn adjacencies() { + fn are_adjacent_single_edge() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); + assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); + graph.add_edge(v1, v2); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + assert!(graph.are_adjacent(v2, v1), "should be adjacent"); + } + + #[test] + fn are_adjacent() { let (graph, vertices) = make_test_graph(); assert!( graph.are_adjacent(vertices[0], vertices[1]), -- 2.43.0 From 761920b2c0c73efbcb302cac1978d98ff54c61c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 00:56:24 +0200 Subject: [PATCH 025/102] Update Graph::are_adjacent() to use neighbors() iterator --- src/models.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/models.rs b/src/models.rs index 58e25b0..4b6148d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -61,20 +61,8 @@ impl Graph { self.incidence_headers[v.0].incidence_count } - // TODO: 'fn are_adjacent()' could probably be done with a neighbor vertex iterator. pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - let Some(mut incidence) = self.incidence_headers[v1.0].first_incidence else { - return false; - }; - loop { - if self.incidence_vertices[incidence.0] == v2 { - return true; - } - let Some(next) = self.next_incidences[incidence.0] else { - return false; - }; - incidence = next; - } + self.neighbors(v1).find(|&x| x == v2).is_some() } // TODO: 'fn add_vertex()' needs variants for custom vertex data. -- 2.43.0 From 149db22fe8a8426aec6e0a6bfb8eeef1b94a4373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 00:57:02 +0200 Subject: [PATCH 026/102] Add todo --- src/models.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index 4b6148d..8de20af 100644 --- a/src/models.rs +++ b/src/models.rs @@ -28,9 +28,10 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { } } -// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. // TODO: Graph fields must not be public. // TODO: Add functions to reserve memory for vertices and edges. +// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. +// TODO: Add function to delete vertices. // TODO: Add function to delete edges. // TODO: Add iterator of edges. pub struct Graph { -- 2.43.0 From 64d0302d647cea4933ffd73dfa27b868468ff632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 01:03:02 +0200 Subject: [PATCH 027/102] Update Dijkstra's algorithm to use Graph::neighbors() iterator --- src/algorithms.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index f263163..831d645 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -35,14 +35,7 @@ pub fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec (Vec>, Vec Date: Sat, 25 Oct 2025 01:15:16 +0200 Subject: [PATCH 028/102] Add tests for Dijkstra's algorithm on trivial graphs --- tests/dijkstra.rs | 60 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index b540223..ba953e9 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -2,6 +2,58 @@ use grapherity::algorithms; use grapherity::models::Graph; use grapherity::models::Vertex; +#[test] +fn dijkstra_single_vertex() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let (distances, predecessors) = algorithms::dijkstra(&graph, v1); + assert_eq!( + distances.len(), + 1, + "distances count must equal vertex count" + ); + assert_eq!( + predecessors.len(), + 1, + "predecessors count must equal vertex count" + ); + assert_eq!( + distances[0], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + predecessors[0], None, + "unexpected predecessor of source vertex", + ); +} + +#[test] +fn dijkstra_disconnected() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + graph.add_vertex(); + let (distances, predecessors) = algorithms::dijkstra(&graph, v1); + assert_eq!( + distances.len(), + 2, + "distances count must equal vertex count" + ); + assert_eq!( + predecessors.len(), + 2, + "predecessors count must equal vertex count" + ); + assert_eq!( + distances[1], None, + "unexpected distance of disconnected vertex" + ); + assert_eq!( + predecessors[0], None, + "unexpected predecessor of disconnected vertex", + ); +} + #[test] fn dijkstra() { let (graph, vertices) = make_test_graph(); @@ -33,22 +85,22 @@ fn dijkstra() { assert_eq!( distances.len(), graph.vertex_count(), - "distances count from Dijkstra's algorithm must equal vertex count" + "distances count must equal vertex count" ); assert_eq!( predecessors.len(), graph.vertex_count(), - "predecessors count from Dijkstra's algorithm must equal vertex count" + "predecessors count must equal vertex count" ); for i in 0..graph.vertex_count() { assert_eq!( distances[i], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?} from Dijkstra's algorithm", + "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); assert!( expected_predecessors_from_v0[i].contains(&predecessors[i]), - "unexpected predecessor {:?} of {:?} from Dijkstra's algorithm", + "unexpected predecessor {:?} of {:?}", predecessors[i], vertices[i] ); -- 2.43.0 From 3565b6b5482a12ebb75033e6dc17a6aa8b36f20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Sat, 25 Oct 2025 01:23:05 +0200 Subject: [PATCH 029/102] Remove pub from structs and fields that should have been private Graph fields can now be private because of the new iterators Graph::vertices() and Graph::neighbors(). Without them there was no way to access vertices and incidences without the public Graph fields. --- src/models.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/models.rs b/src/models.rs index 8de20af..3576374 100644 --- a/src/models.rs +++ b/src/models.rs @@ -2,15 +2,12 @@ #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Vertex(pub usize); -// TODO: Incidence value must not be public. -// TODO: Incidence must not be public. #[derive(Copy, Clone, PartialEq, Eq)] -pub struct Incidence(pub usize); +struct Incidence(usize); -// TODO: VertexIncidenceHeader and its fields must not be public, but without iterators there is currently no other way to access vertex incidences. -pub struct VertexIncidenceHeader { +struct VertexIncidenceHeader { incidence_count: usize, - pub first_incidence: Option, + first_incidence: Option, } pub struct VertexNeighborIterator<'a> { @@ -28,7 +25,6 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { } } -// TODO: Graph fields must not be public. // TODO: Add functions to reserve memory for vertices and edges. // TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. // TODO: Add function to delete vertices. @@ -36,9 +32,9 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { // TODO: Add iterator of edges. pub struct Graph { // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - pub incidence_headers: Vec, - pub next_incidences: Vec>, - pub incidence_vertices: Vec, + incidence_headers: Vec, + next_incidences: Vec>, + incidence_vertices: Vec, } impl Graph { -- 2.43.0 From c5a0f2a3f6e3193a4e4a0ac59f7f739d787f6fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 3 Nov 2025 14:03:21 +0100 Subject: [PATCH 030/102] Add tests for loops and multiple edges --- src/models.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/models.rs b/src/models.rs index 3576374..d0ca964 100644 --- a/src/models.rs +++ b/src/models.rs @@ -154,6 +154,17 @@ mod tests { } } + #[test] + fn are_adjacent_vertex_self() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.are_adjacent(v, v), + false, + "should not be adjacent to itself" + ); + } + #[test] fn are_adjacent_single_edge() { let mut graph = Graph::new(); @@ -269,6 +280,75 @@ mod tests { } } + #[test] + fn loop_edge() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert_eq!( + graph.are_adjacent(v, v), + true, + "vertex with loop edge should be adjacent to itself" + ); + let mut neighbors = graph.neighbors(v); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself" + ); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself twice" + ); + assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); + } + + #[test] + fn multiple_edges() { + let mult = 3; + let mut graph = Graph::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + for _ in 0..mult { + graph.add_edge(vertices[0], vertices[1]); + } + assert_eq!( + graph.vertex_count(), + vertices.len(), + "unexpected vertex count" + ); + assert_eq!(graph.edge_count(), mult, "unexpected edge count"); + for v in vertices { + assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); + } + assert_eq!( + graph.are_adjacent(vertices[0], vertices[1]), + true, + "should be adjacent" + ); + for i in 0..2 { + let mut neighbors = graph.neighbors(vertices[i]); + for j in 0..mult { + assert_eq!( + neighbors.next(), + Some(vertices[1 - i]), + "neighbor {j} of vertex {:?} should be {:?}", + vertices[i], + vertices[1 - i] + ); + } + assert_eq!( + neighbors.next(), + None, + "two many neighbors of {:?} from iterator", + vertices[i] + ); + } + } + fn make_test_graph() -> (Graph, [Vertex; 10]) { let mut graph = Graph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); -- 2.43.0 From 489ed6d50666360e52b5d19ab711f7cbd284a39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 19 Mar 2026 21:21:52 +0100 Subject: [PATCH 031/102] Combine next_incidences and incidence_vertices of Graph into one, and rename incidence_headers to vertices --- src/models.rs | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/models.rs b/src/models.rs index d0ca964..942cc8d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -10,6 +10,11 @@ struct VertexIncidenceHeader { first_incidence: Option, } +struct IncidenceEntry { + next: Option, + neighbor: Vertex, +} + pub struct VertexNeighborIterator<'a> { graph: &'a Graph, incidence: Option, @@ -20,8 +25,9 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { fn next(&mut self) -> Option { let incidence = self.incidence?; - self.incidence = self.graph.next_incidences[incidence.0]; - Some(self.graph.incidence_vertices[incidence.0]) + let entry = &self.graph.incidences[incidence.0]; + self.incidence = entry.next; + Some(entry.neighbor) } } @@ -32,30 +38,28 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { // TODO: Add iterator of edges. pub struct Graph { // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - incidence_headers: Vec, - next_incidences: Vec>, - incidence_vertices: Vec, + vertices: Vec, + incidences: Vec, } impl Graph { pub fn new() -> Graph { Graph { - incidence_headers: vec![], - next_incidences: vec![], - incidence_vertices: vec![], + vertices: vec![], + incidences: vec![], } } pub fn vertex_count(&self) -> usize { - self.incidence_headers.len() + self.vertices.len() } pub fn edge_count(&self) -> usize { - self.next_incidences.len() / 2 + self.incidences.len() / 2 } pub fn degree(&self, v: Vertex) -> usize { - self.incidence_headers[v.0].incidence_count + self.vertices[v.0].incidence_count } pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { @@ -64,11 +68,11 @@ impl Graph { // TODO: 'fn add_vertex()' needs variants for custom vertex data. pub fn add_vertex(&mut self) -> Vertex { - self.incidence_headers.push(VertexIncidenceHeader { + self.vertices.push(VertexIncidenceHeader { incidence_count: 0, first_incidence: None, }); - Vertex(self.incidence_headers.len() - 1) + Vertex(self.vertices.len() - 1) } // TODO: 'fn add_edge()' needs variants for custom edge data. @@ -78,22 +82,22 @@ impl Graph { } fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { - self.incidence_vertices.push(v2); - self.next_incidences - .push(self.incidence_headers[v1.0].first_incidence.take()); - self.incidence_headers[v1.0].incidence_count += 1; - self.incidence_headers[v1.0].first_incidence = - Some(Incidence(self.incidence_vertices.len() - 1)); + self.incidences.push(IncidenceEntry { + next: self.vertices[v1.0].first_incidence.take(), + neighbor: v2, + }); + self.vertices[v1.0].incidence_count += 1; + self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); } pub fn vertices(&self) -> impl Iterator { - (0..self.incidence_headers.len()).map(Vertex) + (0..self.vertices.len()).map(Vertex) } pub fn neighbors(&self, v: Vertex) -> impl Iterator { VertexNeighborIterator { graph: self, - incidence: self.incidence_headers[v.0].first_incidence, + incidence: self.vertices[v.0].first_incidence, } } } -- 2.43.0 From da9ba7b0ad37ddbec1c6d9b02aa2e5d8dd0ea4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:26:35 +0200 Subject: [PATCH 032/102] Add GraphTopology trait and use it to interface graph model with algorithm --- src/algorithms.rs | 31 ++++++++++-------- src/lib.rs | 1 + src/models.rs | 82 ++++++++++++++++++++++++++--------------------- src/traits.rs | 12 +++++++ tests/dijkstra.rs | 6 ++-- 5 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 src/traits.rs diff --git a/src/algorithms.rs b/src/algorithms.rs index 831d645..3bb54e9 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,22 +1,21 @@ use std::cmp::Ordering; use std::collections::BinaryHeap; -use crate::models::Graph; -use crate::models::Vertex; +use crate::traits::GraphTopology; #[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { +struct DistanceOrderedVertex { distance: u32, - vertex: Vertex, + vertex: V, } -impl PartialOrd for DistanceOrderedVertex { +impl PartialOrd for DistanceOrderedVertex { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.distance.cmp(&other.distance)) + Some(self.cmp(other)) } } -impl Ord for DistanceOrderedVertex { +impl Ord for DistanceOrderedVertex { fn cmp(&self, other: &Self) -> Ordering { other.distance.cmp(&self.distance) } @@ -24,28 +23,34 @@ impl Ord for DistanceOrderedVertex { // TODO: Maybe introduce a return struct type for Dijkstra's algorithm? // TODO: Maybe variant of Dijkstra's algorithm without predecessors? -pub fn dijkstra(graph: &Graph, source: Vertex) -> (Vec>, Vec>) { +// TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. +pub fn dijkstra(graph: &G, source: G::Vertex) -> (Vec>, Vec>) +where + G: GraphTopology, + G::Vertex: Into, +{ let mut distances = vec![None; graph.vertex_count()]; let mut predecessors = vec![None; graph.vertex_count()]; let mut heap = BinaryHeap::new(); - distances[source.0] = Some(0); + distances[source.into()] = Some(0); heap.push(DistanceOrderedVertex { vertex: source, distance: 0, }); + while let Some(v) = heap.pop() { for neighbor in graph.neighbors(v.vertex) { // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. let edge_weight = 1; - let new_distance = distances[v.vertex.0].unwrap() + edge_weight; - if match distances[neighbor.0] { + let new_distance = distances[v.vertex.into()].unwrap() + edge_weight; + if match distances[neighbor.into()] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[neighbor.0] = Some(new_distance); - predecessors[neighbor.0] = Some(v.vertex); + distances[neighbor.into()] = Some(new_distance); + predecessors[neighbor.into()] = Some(v.vertex); heap.push(DistanceOrderedVertex { vertex: neighbor, distance: new_distance, diff --git a/src/lib.rs b/src/lib.rs index 67722ce..2c999c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod algorithms; pub mod models; +pub mod traits; diff --git a/src/models.rs b/src/models.rs index 942cc8d..9aeb4e5 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,13 @@ -// TODO: Vertex value must not be public. +use crate::traits::GraphTopology; + #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Vertex(pub usize); +pub struct Vertex(usize); + +impl From for usize { + fn from(v: Vertex) -> usize { + v.0 + } +} #[derive(Copy, Clone, PartialEq, Eq)] struct Incidence(usize); @@ -14,8 +21,7 @@ struct IncidenceEntry { next: Option, neighbor: Vertex, } - -pub struct VertexNeighborIterator<'a> { +struct VertexNeighborIterator<'a> { graph: &'a Graph, incidence: Option, } @@ -50,37 +56,6 @@ impl Graph { } } - pub fn vertex_count(&self) -> usize { - self.vertices.len() - } - - pub fn edge_count(&self) -> usize { - self.incidences.len() / 2 - } - - pub fn degree(&self, v: Vertex) -> usize { - self.vertices[v.0].incidence_count - } - - pub fn are_adjacent(&self, v1: Vertex, v2: Vertex) -> bool { - self.neighbors(v1).find(|&x| x == v2).is_some() - } - - // TODO: 'fn add_vertex()' needs variants for custom vertex data. - pub fn add_vertex(&mut self) -> Vertex { - self.vertices.push(VertexIncidenceHeader { - incidence_count: 0, - first_incidence: None, - }); - Vertex(self.vertices.len() - 1) - } - - // TODO: 'fn add_edge()' needs variants for custom edge data. - pub fn add_edge(&mut self, v1: Vertex, v2: Vertex) { - self.add_incidence(v1, v2); - self.add_incidence(v2, v1); - } - fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { self.incidences.push(IncidenceEntry { next: self.vertices[v1.0].first_incidence.take(), @@ -89,17 +64,50 @@ impl Graph { self.vertices[v1.0].incidence_count += 1; self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); } +} - pub fn vertices(&self) -> impl Iterator { +impl GraphTopology for Graph { + type Vertex = Vertex; + + fn vertex_count(&self) -> usize { + self.vertices.len() + } + + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + + 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.neighbors(v1).find(|&x| x == v2).is_some() + } + + fn vertices(&self) -> impl Iterator { (0..self.vertices.len()).map(Vertex) } - pub fn neighbors(&self, v: Vertex) -> impl Iterator { + fn neighbors(&self, v: Self::Vertex) -> impl Iterator { VertexNeighborIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } } + + fn add_vertex(&mut self) -> Self::Vertex { + self.vertices.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + Vertex(self.vertices.len() - 1) + } + + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) { + self.add_incidence(v1, v2); + self.add_incidence(v2, v1); + } } #[cfg(test)] diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..8a06dec --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,12 @@ +pub trait GraphTopology { + type Vertex: Copy + Eq; + + fn vertex_count(&self) -> usize; + fn edge_count(&self) -> usize; + fn degree(&self, v: Self::Vertex) -> usize; + fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; + fn vertices(&self) -> impl Iterator; + fn neighbors(&self, v: Self::Vertex) -> impl Iterator; + fn add_vertex(&mut self) -> Self::Vertex; + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex); +} diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index ba953e9..6fe87d0 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,6 +1,6 @@ use grapherity::algorithms; use grapherity::models::Graph; -use grapherity::models::Vertex; +use grapherity::traits::GraphTopology; #[test] fn dijkstra_single_vertex() { @@ -107,9 +107,9 @@ fn dijkstra() { } } -fn make_test_graph() -> (Graph, [Vertex; 10]) { +fn make_test_graph() -> (Graph, [::Vertex; 10]) { let mut graph = Graph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + let vertices = core::array::from_fn::<_, 10, _>(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); graph.add_edge(vertices[1], vertices[3]); -- 2.43.0 From 9088dd46832119d1de9eb27509e576c9ffbb80ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:28:21 +0200 Subject: [PATCH 033/102] Add edge return type to GraphTopology::add_edge() There is currently no use for the return type, but it will come. --- src/models.rs | 7 +++++-- src/traits.rs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/models.rs b/src/models.rs index 9aeb4e5..23a0809 100644 --- a/src/models.rs +++ b/src/models.rs @@ -10,7 +10,7 @@ impl From for usize { } #[derive(Copy, Clone, PartialEq, Eq)] -struct Incidence(usize); +pub struct Incidence(usize); struct VertexIncidenceHeader { incidence_count: usize, @@ -69,6 +69,8 @@ impl Graph { impl GraphTopology for Graph { type Vertex = Vertex; + type Edge = Incidence; + fn vertex_count(&self) -> usize { self.vertices.len() } @@ -104,9 +106,10 @@ impl GraphTopology for Graph { Vertex(self.vertices.len() - 1) } - fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) { + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { self.add_incidence(v1, v2); self.add_incidence(v2, v1); + Incidence(self.incidences.len() - 2) } } diff --git a/src/traits.rs b/src/traits.rs index 8a06dec..d4de040 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,6 @@ pub trait GraphTopology { type Vertex: Copy + Eq; + type Edge; fn vertex_count(&self) -> usize; fn edge_count(&self) -> usize; @@ -8,5 +9,5 @@ pub trait GraphTopology { fn vertices(&self) -> impl Iterator; fn neighbors(&self, v: Self::Vertex) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; - fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex); + fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } -- 2.43.0 From ab830ed221bee02414d9f1ee0a0495a749aa3069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:31:11 +0200 Subject: [PATCH 034/102] Rename Graph to AppendGraph for future other graph models --- src/models.rs | 36 ++++++++++++++++++------------------ tests/dijkstra.rs | 10 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/models.rs b/src/models.rs index 23a0809..e98ca63 100644 --- a/src/models.rs +++ b/src/models.rs @@ -22,7 +22,7 @@ struct IncidenceEntry { neighbor: Vertex, } struct VertexNeighborIterator<'a> { - graph: &'a Graph, + graph: &'a AppendGraph, incidence: Option, } @@ -42,15 +42,15 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { // TODO: Add function to delete vertices. // TODO: Add function to delete edges. // TODO: Add iterator of edges. -pub struct Graph { +pub struct AppendGraph { // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? vertices: Vec, incidences: Vec, } -impl Graph { - pub fn new() -> Graph { - Graph { +impl AppendGraph { + pub fn new() -> Self { + Self { vertices: vec![], incidences: vec![], } @@ -66,7 +66,7 @@ impl Graph { } } -impl GraphTopology for Graph { +impl GraphTopology for AppendGraph { type Vertex = Vertex; type Edge = Incidence; @@ -119,14 +119,14 @@ mod tests { #[test] fn add_vertex() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v = graph.add_vertex(); assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); } #[test] fn vertex_count_empty() { - let graph = Graph::new(); + let graph = AppendGraph::new(); assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); } @@ -138,7 +138,7 @@ mod tests { #[test] fn edge_count_empty() { - let graph = Graph::new(); + let graph = AppendGraph::new(); assert_eq!(graph.edge_count(), 0, "unexpected edge count"); } @@ -150,7 +150,7 @@ mod tests { #[test] fn degree_zero() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v = graph.add_vertex(); assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); } @@ -171,7 +171,7 @@ mod tests { #[test] fn are_adjacent_vertex_self() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v = graph.add_vertex(); assert_eq!( graph.are_adjacent(v, v), @@ -182,7 +182,7 @@ mod tests { #[test] fn are_adjacent_single_edge() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); @@ -239,7 +239,7 @@ mod tests { #[test] fn vertices_empty() { - let graph = Graph::new(); + let graph = AppendGraph::new(); assert_eq!( graph.vertices().count(), 0, @@ -264,7 +264,7 @@ mod tests { #[test] fn neighbors_empty() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let vertex = graph.add_vertex(); assert_eq!( graph.neighbors(vertex).count(), @@ -297,7 +297,7 @@ mod tests { #[test] fn loop_edge() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v = graph.add_vertex(); graph.add_edge(v, v); assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); @@ -325,7 +325,7 @@ mod tests { #[test] fn multiple_edges() { let mult = 3; - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; for _ in 0..mult { graph.add_edge(vertices[0], vertices[1]); @@ -364,8 +364,8 @@ mod tests { } } - fn make_test_graph() -> (Graph, [Vertex; 10]) { - let mut graph = Graph::new(); + fn make_test_graph() -> (AppendGraph, [Vertex; 10]) { + let mut graph = AppendGraph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 6fe87d0..37196da 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,10 +1,10 @@ use grapherity::algorithms; -use grapherity::models::Graph; +use grapherity::models::AppendGraph; use grapherity::traits::GraphTopology; #[test] fn dijkstra_single_vertex() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); let (distances, predecessors) = algorithms::dijkstra(&graph, v1); assert_eq!( @@ -30,7 +30,7 @@ fn dijkstra_single_vertex() { #[test] fn dijkstra_disconnected() { - let mut graph = Graph::new(); + let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); graph.add_vertex(); let (distances, predecessors) = algorithms::dijkstra(&graph, v1); @@ -107,8 +107,8 @@ fn dijkstra() { } } -fn make_test_graph() -> (Graph, [::Vertex; 10]) { - let mut graph = Graph::new(); +fn make_test_graph() -> (AppendGraph, [::Vertex; 10]) { + let mut graph = AppendGraph::new(); let vertices = core::array::from_fn::<_, 10, _>(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); -- 2.43.0 From 41f2332735355d4712d4df34b5f10041559d91b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:36:41 +0200 Subject: [PATCH 035/102] Add test for add_edge() return value --- src/models.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/models.rs b/src/models.rs index e98ca63..58d0636 100644 --- a/src/models.rs +++ b/src/models.rs @@ -9,7 +9,7 @@ impl From for usize { } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Incidence(usize); struct VertexIncidenceHeader { @@ -136,6 +136,15 @@ mod tests { assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } + #[test] + fn add_edge() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); + } + #[test] fn edge_count_empty() { let graph = AppendGraph::new(); -- 2.43.0 From a250bde626be294b801e13a842ee9d67af71e9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 14:49:07 +0200 Subject: [PATCH 036/102] Add DijkstraResult return type instead of unnamed tuple --- src/algorithms.rs | 26 ++++++++++++++++---------- tests/dijkstra.rs | 32 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 3bb54e9..7b65faf 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -21,19 +21,25 @@ impl Ord for DistanceOrderedVertex { } } -// TODO: Maybe introduce a return struct type for Dijkstra's algorithm? +pub struct DijkstraResult { + pub distances: Vec>, + pub predecessors: Vec>, +} + // TODO: Maybe variant of Dijkstra's algorithm without predecessors? // TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. -pub fn dijkstra(graph: &G, source: G::Vertex) -> (Vec>, Vec>) +pub fn dijkstra(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, G::Vertex: Into, { - let mut distances = vec![None; graph.vertex_count()]; - let mut predecessors = vec![None; graph.vertex_count()]; + let mut result = DijkstraResult { + distances: vec![None; graph.vertex_count()], + predecessors: vec![None; graph.vertex_count()], + }; let mut heap = BinaryHeap::new(); - distances[source.into()] = Some(0); + result.distances[source.into()] = Some(0); heap.push(DistanceOrderedVertex { vertex: source, distance: 0, @@ -43,14 +49,14 @@ where for neighbor in graph.neighbors(v.vertex) { // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. let edge_weight = 1; - let new_distance = distances[v.vertex.into()].unwrap() + edge_weight; - if match distances[neighbor.into()] { + let new_distance = result.distances[v.vertex.into()].unwrap() + edge_weight; + if match result.distances[neighbor.into()] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[neighbor.into()] = Some(new_distance); - predecessors[neighbor.into()] = Some(v.vertex); + result.distances[neighbor.into()] = Some(new_distance); + result.predecessors[neighbor.into()] = Some(v.vertex); heap.push(DistanceOrderedVertex { vertex: neighbor, distance: new_distance, @@ -58,5 +64,5 @@ where } } } - (distances, predecessors) + result } diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 37196da..f4dc9ad 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -6,24 +6,24 @@ use grapherity::traits::GraphTopology; fn dijkstra_single_vertex() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); - let (distances, predecessors) = algorithms::dijkstra(&graph, v1); + let result = algorithms::dijkstra(&graph, v1); assert_eq!( - distances.len(), + result.distances.len(), 1, "distances count must equal vertex count" ); assert_eq!( - predecessors.len(), + result.predecessors.len(), 1, "predecessors count must equal vertex count" ); assert_eq!( - distances[0], + result.distances[0], Some(0), "unexpected distance of source vertex" ); assert_eq!( - predecessors[0], None, + result.predecessors[0], None, "unexpected predecessor of source vertex", ); } @@ -33,23 +33,23 @@ fn dijkstra_disconnected() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); graph.add_vertex(); - let (distances, predecessors) = algorithms::dijkstra(&graph, v1); + let result = algorithms::dijkstra(&graph, v1); assert_eq!( - distances.len(), + result.distances.len(), 2, "distances count must equal vertex count" ); assert_eq!( - predecessors.len(), + result.predecessors.len(), 2, "predecessors count must equal vertex count" ); assert_eq!( - distances[1], None, + result.distances[1], None, "unexpected distance of disconnected vertex" ); assert_eq!( - predecessors[0], None, + result.predecessors[0], None, "unexpected predecessor of disconnected vertex", ); } @@ -57,7 +57,7 @@ fn dijkstra_disconnected() { #[test] fn dijkstra() { let (graph, vertices) = make_test_graph(); - let (distances, predecessors) = algorithms::dijkstra(&graph, vertices[0]); + let result = algorithms::dijkstra(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), Some(1), @@ -83,25 +83,25 @@ fn dijkstra() { vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], ]; assert_eq!( - distances.len(), + result.distances.len(), graph.vertex_count(), "distances count must equal vertex count" ); assert_eq!( - predecessors.len(), + result.predecessors.len(), graph.vertex_count(), "predecessors count must equal vertex count" ); for i in 0..graph.vertex_count() { assert_eq!( - distances[i], expected_distances_from_v0[i], + result.distances[i], expected_distances_from_v0[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); assert!( - expected_predecessors_from_v0[i].contains(&predecessors[i]), + expected_predecessors_from_v0[i].contains(&result.predecessors[i]), "unexpected predecessor {:?} of {:?}", - predecessors[i], + result.predecessors[i], vertices[i] ); } -- 2.43.0 From a939f9b88918198abfb050e985e30f1aac34f041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 15:50:45 +0200 Subject: [PATCH 037/102] Add Dijkstra variant without returning predecessors --- src/algorithms.rs | 41 +++++++++++++++++++++++++++------------ tests/dijkstra.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 7b65faf..b8282e4 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -26,20 +26,37 @@ pub struct DijkstraResult { pub predecessors: Vec>, } -// TODO: Maybe variant of Dijkstra's algorithm without predecessors? -// TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. pub fn dijkstra(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, G::Vertex: Into, { - let mut result = DijkstraResult { - distances: vec![None; graph.vertex_count()], - predecessors: vec![None; graph.vertex_count()], - }; + let mut predecessors = vec![None; graph.vertex_count()]; + let distances = dijkstra_impl(graph, source, |neighbor, predecessor| { + predecessors[neighbor.into()] = Some(predecessor); + }); + DijkstraResult { distances, predecessors } +} + +pub fn dijkstra_distances(graph: &G, source: G::Vertex) -> Vec> +where + G: GraphTopology, + G::Vertex: Into, +{ + dijkstra_impl(graph, source, |_, _| {}) +} + +// TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. +fn dijkstra_impl(graph: &G, source: G::Vertex, mut on_relax: F) -> Vec> +where + G: GraphTopology, + G::Vertex: Into, + F: FnMut(G::Vertex, G::Vertex), +{ + let mut distances = vec![None; graph.vertex_count()]; let mut heap = BinaryHeap::new(); - result.distances[source.into()] = Some(0); + distances[source.into()] = Some(0); heap.push(DistanceOrderedVertex { vertex: source, distance: 0, @@ -49,14 +66,14 @@ where for neighbor in graph.neighbors(v.vertex) { // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. let edge_weight = 1; - let new_distance = result.distances[v.vertex.into()].unwrap() + edge_weight; - if match result.distances[neighbor.into()] { + let new_distance = distances[v.vertex.into()].unwrap() + edge_weight; + if match distances[neighbor.into()] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - result.distances[neighbor.into()] = Some(new_distance); - result.predecessors[neighbor.into()] = Some(v.vertex); + distances[neighbor.into()] = Some(new_distance); + on_relax(neighbor, v.vertex); heap.push(DistanceOrderedVertex { vertex: neighbor, distance: new_distance, @@ -64,5 +81,5 @@ where } } } - result + distances } diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index f4dc9ad..76383a4 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -107,6 +107,55 @@ fn dijkstra() { } } +#[test] +fn dijkstra_distances_single_vertex() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + let distances = algorithms::dijkstra_distances(&graph, v1); + assert_eq!(distances.len(), 1, "distances count must equal vertex count"); + assert_eq!(distances[0], Some(0), "unexpected distance of source vertex"); +} + +#[test] +fn dijkstra_distances_disconnected() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + graph.add_vertex(); + let distances = algorithms::dijkstra_distances(&graph, v1); + assert_eq!(distances.len(), 2, "distances count must equal vertex count"); + assert_eq!(distances[1], None, "unexpected distance of disconnected vertex"); +} + +#[test] +fn dijkstra_distances() { + let (graph, vertices) = make_test_graph(); + let distances = algorithms::dijkstra_distances(&graph, vertices[0]); + 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), + ]; + assert_eq!( + distances.len(), + graph.vertex_count(), + "distances count must equal vertex count" + ); + for i in 0..graph.vertex_count() { + assert_eq!( + distances[i], expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?}", + vertices[0], vertices[i] + ); + } +} + fn make_test_graph() -> (AppendGraph, [::Vertex; 10]) { let mut graph = AppendGraph::new(); let vertices = core::array::from_fn::<_, 10, _>(|_| graph.add_vertex()); -- 2.43.0 From ee26b7a3e09ae030460165b779693713bca5edd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 16:31:17 +0200 Subject: [PATCH 038/102] Move AppendGraph to its own module --- src/models.rs | 396 +------------------------------------ src/models/append_graph.rs | 390 ++++++++++++++++++++++++++++++++++++ src/traits.rs | 2 + tests/dijkstra.rs | 2 +- 4 files changed, 394 insertions(+), 396 deletions(-) create mode 100644 src/models/append_graph.rs diff --git a/src/models.rs b/src/models.rs index 58d0636..e53138e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,395 +1 @@ -use crate::traits::GraphTopology; - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Vertex(usize); - -impl From for usize { - fn from(v: Vertex) -> usize { - v.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Incidence(usize); - -struct VertexIncidenceHeader { - incidence_count: usize, - first_incidence: Option, -} - -struct IncidenceEntry { - next: Option, - neighbor: Vertex, -} -struct VertexNeighborIterator<'a> { - graph: &'a AppendGraph, - incidence: Option, -} - -impl<'a> Iterator for VertexNeighborIterator<'a> { - type Item = Vertex; - - fn next(&mut self) -> Option { - let incidence = self.incidence?; - let entry = &self.graph.incidences[incidence.0]; - self.incidence = entry.next; - Some(entry.neighbor) - } -} - -// TODO: Add functions to reserve memory for vertices and edges. -// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. -// TODO: Add function to delete vertices. -// TODO: Add function to delete edges. -// TODO: Add iterator of edges. -pub struct AppendGraph { - // TODO: Index 'incidence_headers' by a 'Vertex' instead of 'Vertex.0'? - vertices: Vec, - incidences: Vec, -} - -impl AppendGraph { - pub fn new() -> Self { - Self { - vertices: vec![], - incidences: vec![], - } - } - - fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { - self.incidences.push(IncidenceEntry { - next: self.vertices[v1.0].first_incidence.take(), - neighbor: v2, - }); - self.vertices[v1.0].incidence_count += 1; - self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); - } -} - -impl GraphTopology for AppendGraph { - type Vertex = Vertex; - - type Edge = Incidence; - - fn vertex_count(&self) -> usize { - self.vertices.len() - } - - fn edge_count(&self) -> usize { - self.incidences.len() / 2 - } - - 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.neighbors(v1).find(|&x| x == v2).is_some() - } - - fn vertices(&self) -> impl Iterator { - (0..self.vertices.len()).map(Vertex) - } - - fn neighbors(&self, v: Self::Vertex) -> impl Iterator { - VertexNeighborIterator { - graph: self, - incidence: self.vertices[v.0].first_incidence, - } - } - - fn add_vertex(&mut self) -> Self::Vertex { - self.vertices.push(VertexIncidenceHeader { - incidence_count: 0, - first_incidence: None, - }); - 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); - Incidence(self.incidences.len() - 2) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn add_vertex() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); - } - - #[test] - fn vertex_count_empty() { - let graph = AppendGraph::new(); - assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); - } - - #[test] - fn vertex_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); - } - - #[test] - fn add_edge() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); - } - - #[test] - fn edge_count_empty() { - let graph = AppendGraph::new(); - assert_eq!(graph.edge_count(), 0, "unexpected edge count"); - } - - #[test] - fn edge_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.edge_count(), 14, "unexpected edge count"); - } - - #[test] - fn degree_zero() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); - } - - #[test] - fn degree() { - let (graph, vertices) = make_test_graph(); - let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; - for i in 0..graph.vertex_count() { - assert_eq!( - graph.degree(vertices[i]), - expected_degrees[i], - "unexpected degree of {:?}", - vertices[i] - ); - } - } - - #[test] - fn are_adjacent_vertex_self() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert_eq!( - graph.are_adjacent(v, v), - false, - "should not be adjacent to itself" - ); - } - - #[test] - fn are_adjacent_single_edge() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); - assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); - graph.add_edge(v1, v2); - assert!(graph.are_adjacent(v1, v2), "should be adjacent"); - assert!(graph.are_adjacent(v2, v1), "should be adjacent"); - } - - #[test] - fn are_adjacent() { - let (graph, vertices) = make_test_graph(); - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[1] - ); - assert!( - graph.are_adjacent(vertices[9], vertices[5]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[5] - ); - assert!( - !graph.are_adjacent(vertices[9], vertices[3]), - "unexpected adjacency of {:?} and {:?}", - vertices[9], - vertices[3] - ); - - for i in 0..graph.vertex_count() { - let exp = match i { - 2 => continue, - 1 | 4 | 5 | 6 => true, - _ => false, - }; - assert_eq!( - graph.are_adjacent(vertices[2], vertices[i]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[2], - vertices[i] - ); - assert_eq!( - graph.are_adjacent(vertices[i], vertices[2]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[i], - vertices[2] - ); - } - } - - #[test] - fn vertices_empty() { - let graph = AppendGraph::new(); - assert_eq!( - graph.vertices().count(), - 0, - "vertex iterator of empty graph should have no elements" - ); - } - - #[test] - fn vertices() { - let (graph, vertices) = make_test_graph(); - assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); - - // Expects each vertex to appear exactly once. - for v in graph.vertices() { - assert_eq!( - vertices.iter().filter(|&x| *x == v).count(), - 1, - "unexpected vertex {v:?} from the iterator" - ); - } - } - - #[test] - fn neighbors_empty() { - let mut graph = AppendGraph::new(); - let vertex = graph.add_vertex(); - assert_eq!( - graph.neighbors(vertex).count(), - 0, - "neighbor iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn neighbors() { - let (graph, vertices) = make_test_graph(); - // Checks neighbors of vertex 4. - assert_eq!( - graph.neighbors(vertices[4]).count(), - 4, - "unexpected vertex count" - ); - - // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; - for v in graph.neighbors(vertices[4]) { - assert_eq!( - neighbors.iter().filter(|&x| *x == v).count(), - 1, - "unexpected neighbor {v:?} of {:?} from the iterator", - vertices[4] - ); - } - } - - #[test] - fn loop_edge() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert_eq!( - graph.are_adjacent(v, v), - true, - "vertex with loop edge should be adjacent to itself" - ); - let mut neighbors = graph.neighbors(v); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself" - ); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself twice" - ); - assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); - } - - #[test] - fn multiple_edges() { - let mult = 3; - let mut graph = AppendGraph::new(); - let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..mult { - graph.add_edge(vertices[0], vertices[1]); - } - assert_eq!( - graph.vertex_count(), - vertices.len(), - "unexpected vertex count" - ); - assert_eq!(graph.edge_count(), mult, "unexpected edge count"); - for v in vertices { - assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); - } - assert_eq!( - graph.are_adjacent(vertices[0], vertices[1]), - true, - "should be adjacent" - ); - for i in 0..2 { - let mut neighbors = graph.neighbors(vertices[i]); - for j in 0..mult { - assert_eq!( - neighbors.next(), - Some(vertices[1 - i]), - "neighbor {j} of vertex {:?} should be {:?}", - vertices[i], - vertices[1 - i] - ); - } - assert_eq!( - neighbors.next(), - None, - "two many neighbors of {:?} from iterator", - vertices[i] - ); - } - } - - fn make_test_graph() -> (AppendGraph, [Vertex; 10]) { - let mut graph = AppendGraph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - (graph, vertices) - } -} +pub mod append_graph; diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs new file mode 100644 index 0000000..d702bed --- /dev/null +++ b/src/models/append_graph.rs @@ -0,0 +1,390 @@ +use crate::traits::GraphTopology; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Vertex(usize); + +impl From for usize { + fn from(v: Vertex) -> usize { + v.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Incidence(usize); + +struct VertexIncidenceHeader { + incidence_count: usize, + first_incidence: Option, +} + +struct IncidenceEntry { + next: Option, + neighbor: Vertex, +} +struct VertexNeighborIterator<'a> { + graph: &'a AppendGraph, + incidence: Option, +} + +impl<'a> Iterator for VertexNeighborIterator<'a> { + type Item = Vertex; + + fn next(&mut self) -> Option { + let incidence = self.incidence?; + let entry = &self.graph.incidences[incidence.0]; + self.incidence = entry.next; + Some(entry.neighbor) + } +} + +pub struct AppendGraph { + // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? + vertices: Vec, + incidences: Vec, +} + +impl AppendGraph { + pub fn new() -> Self { + Self { + vertices: vec![], + incidences: vec![], + } + } + + fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { + self.incidences.push(IncidenceEntry { + next: self.vertices[v1.0].first_incidence.take(), + neighbor: v2, + }); + self.vertices[v1.0].incidence_count += 1; + self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); + } +} + +impl GraphTopology for AppendGraph { + type Vertex = Vertex; + + type Edge = Incidence; + + fn vertex_count(&self) -> usize { + self.vertices.len() + } + + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + + 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.neighbors(v1).find(|&x| x == v2).is_some() + } + + fn vertices(&self) -> impl Iterator { + (0..self.vertices.len()).map(Vertex) + } + + fn neighbors(&self, v: Self::Vertex) -> impl Iterator { + VertexNeighborIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + } + + fn add_vertex(&mut self) -> Self::Vertex { + self.vertices.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + 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); + Incidence(self.incidences.len() - 2) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_vertex() { + let mut graph = AppendGraph::new(); + let v = graph.add_vertex(); + assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); + } + + #[test] + fn vertex_count_empty() { + let graph = AppendGraph::new(); + assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); + } + + #[test] + fn vertex_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + } + + #[test] + fn add_edge() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); + } + + #[test] + fn edge_count_empty() { + let graph = AppendGraph::new(); + assert_eq!(graph.edge_count(), 0, "unexpected edge count"); + } + + #[test] + fn edge_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.edge_count(), 14, "unexpected edge count"); + } + + #[test] + fn degree_zero() { + let mut graph = AppendGraph::new(); + let v = graph.add_vertex(); + assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); + } + + #[test] + fn degree() { + let (graph, vertices) = make_test_graph(); + let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + for i in 0..graph.vertex_count() { + assert_eq!( + graph.degree(vertices[i]), + expected_degrees[i], + "unexpected degree of {:?}", + vertices[i] + ); + } + } + + #[test] + fn are_adjacent_vertex_self() { + let mut graph = AppendGraph::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.are_adjacent(v, v), + false, + "should not be adjacent to itself" + ); + } + + #[test] + fn are_adjacent_single_edge() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); + assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); + graph.add_edge(v1, v2); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + assert!(graph.are_adjacent(v2, v1), "should be adjacent"); + } + + #[test] + fn are_adjacent() { + let (graph, vertices) = make_test_graph(); + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[1] + ); + assert!( + graph.are_adjacent(vertices[9], vertices[5]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[5] + ); + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); + + for i in 0..graph.vertex_count() { + let exp = match i { + 2 => continue, + 1 | 4 | 5 | 6 => true, + _ => false, + }; + assert_eq!( + graph.are_adjacent(vertices[2], vertices[i]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[2], + vertices[i] + ); + assert_eq!( + graph.are_adjacent(vertices[i], vertices[2]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[i], + vertices[2] + ); + } + } + + #[test] + fn vertices_empty() { + let graph = AppendGraph::new(); + assert_eq!( + graph.vertices().count(), + 0, + "vertex iterator of empty graph should have no elements" + ); + } + + #[test] + fn vertices() { + let (graph, vertices) = make_test_graph(); + assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); + + // Expects each vertex to appear exactly once. + for v in graph.vertices() { + assert_eq!( + vertices.iter().filter(|&x| *x == v).count(), + 1, + "unexpected vertex {v:?} from the iterator" + ); + } + } + + #[test] + fn neighbors_empty() { + let mut graph = AppendGraph::new(); + let vertex = graph.add_vertex(); + assert_eq!( + graph.neighbors(vertex).count(), + 0, + "neighbor iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn neighbors() { + let (graph, vertices) = make_test_graph(); + // Checks neighbors of vertex 4. + assert_eq!( + graph.neighbors(vertices[4]).count(), + 4, + "unexpected vertex count" + ); + + // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. + let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + for v in graph.neighbors(vertices[4]) { + assert_eq!( + neighbors.iter().filter(|&x| *x == v).count(), + 1, + "unexpected neighbor {v:?} of {:?} from the iterator", + vertices[4] + ); + } + } + + #[test] + fn loop_edge() { + let mut graph = AppendGraph::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert_eq!( + graph.are_adjacent(v, v), + true, + "vertex with loop edge should be adjacent to itself" + ); + let mut neighbors = graph.neighbors(v); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself" + ); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself twice" + ); + assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); + } + + #[test] + fn multiple_edges() { + let mult = 3; + let mut graph = AppendGraph::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + for _ in 0..mult { + graph.add_edge(vertices[0], vertices[1]); + } + assert_eq!( + graph.vertex_count(), + vertices.len(), + "unexpected vertex count" + ); + assert_eq!(graph.edge_count(), mult, "unexpected edge count"); + for v in vertices { + assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); + } + assert_eq!( + graph.are_adjacent(vertices[0], vertices[1]), + true, + "should be adjacent" + ); + for i in 0..2 { + let mut neighbors = graph.neighbors(vertices[i]); + for j in 0..mult { + assert_eq!( + neighbors.next(), + Some(vertices[1 - i]), + "neighbor {j} of vertex {:?} should be {:?}", + vertices[i], + vertices[1 - i] + ); + } + assert_eq!( + neighbors.next(), + None, + "two many neighbors of {:?} from iterator", + vertices[i] + ); + } + } + + fn make_test_graph() -> (AppendGraph, [Vertex; 10]) { + let mut graph = AppendGraph::new(); + let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + (graph, vertices) + } +} diff --git a/src/traits.rs b/src/traits.rs index d4de040..c86de3d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,3 +1,5 @@ +// TODO: Add functions to reserve memory for vertices and edges. +// TODO: Add iterator of edges. pub trait GraphTopology { type Vertex: Copy + Eq; type Edge; diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 76383a4..384a6cb 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,5 +1,5 @@ use grapherity::algorithms; -use grapherity::models::AppendGraph; +use grapherity::models::append_graph::AppendGraph; use grapherity::traits::GraphTopology; #[test] -- 2.43.0 From d0cbebcae07f1198097673b581e5962a93211614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 16:32:17 +0200 Subject: [PATCH 039/102] Fix formatting --- src/algorithms.rs | 5 ++++- tests/dijkstra.rs | 23 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index b8282e4..a5016ce 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -35,7 +35,10 @@ where let distances = dijkstra_impl(graph, source, |neighbor, predecessor| { predecessors[neighbor.into()] = Some(predecessor); }); - DijkstraResult { distances, predecessors } + DijkstraResult { + distances, + predecessors, + } } pub fn dijkstra_distances(graph: &G, source: G::Vertex) -> Vec> diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 384a6cb..1884970 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -112,8 +112,16 @@ fn dijkstra_distances_single_vertex() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); let distances = algorithms::dijkstra_distances(&graph, v1); - assert_eq!(distances.len(), 1, "distances count must equal vertex count"); - assert_eq!(distances[0], Some(0), "unexpected distance of source vertex"); + assert_eq!( + distances.len(), + 1, + "distances count must equal vertex count" + ); + assert_eq!( + distances[0], + Some(0), + "unexpected distance of source vertex" + ); } #[test] @@ -122,8 +130,15 @@ fn dijkstra_distances_disconnected() { let v1 = graph.add_vertex(); graph.add_vertex(); let distances = algorithms::dijkstra_distances(&graph, v1); - assert_eq!(distances.len(), 2, "distances count must equal vertex count"); - assert_eq!(distances[1], None, "unexpected distance of disconnected vertex"); + assert_eq!( + distances.len(), + 2, + "distances count must equal vertex count" + ); + assert_eq!( + distances[1], None, + "unexpected distance of disconnected vertex" + ); } #[test] -- 2.43.0 From 8126352c9bd7e1739d8c2041bd91f6980afe4590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 16:33:05 +0200 Subject: [PATCH 040/102] Add Graph as a straight copy of AppendGraph --- src/models.rs | 1 + src/models/graph.rs | 393 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 src/models/graph.rs diff --git a/src/models.rs b/src/models.rs index e53138e..f1601bc 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1 +1,2 @@ pub mod append_graph; +pub mod graph; diff --git a/src/models/graph.rs b/src/models/graph.rs new file mode 100644 index 0000000..2a59bcc --- /dev/null +++ b/src/models/graph.rs @@ -0,0 +1,393 @@ +use crate::traits::GraphTopology; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Vertex(usize); + +impl From for usize { + fn from(v: Vertex) -> usize { + v.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Incidence(usize); + +struct VertexIncidenceHeader { + incidence_count: usize, + first_incidence: Option, +} + +struct IncidenceEntry { + next: Option, + neighbor: Vertex, +} +struct VertexNeighborIterator<'a> { + graph: &'a Graph, + incidence: Option, +} + +impl<'a> Iterator for VertexNeighborIterator<'a> { + type Item = Vertex; + + fn next(&mut self) -> Option { + let incidence = self.incidence?; + let entry = &self.graph.incidences[incidence.0]; + self.incidence = entry.next; + Some(entry.neighbor) + } +} + +// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. +// TODO: Add function to delete vertices. +// TODO: Add function to delete edges. +pub struct Graph { + // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? + vertices: Vec, + incidences: Vec, +} + +impl Graph { + pub fn new() -> Self { + Self { + vertices: vec![], + incidences: vec![], + } + } + + fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { + self.incidences.push(IncidenceEntry { + next: self.vertices[v1.0].first_incidence.take(), + neighbor: v2, + }); + self.vertices[v1.0].incidence_count += 1; + self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); + } +} + +impl GraphTopology for Graph { + type Vertex = Vertex; + + type Edge = Incidence; + + fn vertex_count(&self) -> usize { + self.vertices.len() + } + + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + + 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.neighbors(v1).find(|&x| x == v2).is_some() + } + + fn vertices(&self) -> impl Iterator { + (0..self.vertices.len()).map(Vertex) + } + + fn neighbors(&self, v: Self::Vertex) -> impl Iterator { + VertexNeighborIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + } + + fn add_vertex(&mut self) -> Self::Vertex { + self.vertices.push(VertexIncidenceHeader { + incidence_count: 0, + first_incidence: None, + }); + 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); + Incidence(self.incidences.len() - 2) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_vertex() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); + } + + #[test] + fn vertex_count_empty() { + let graph = Graph::new(); + assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); + } + + #[test] + fn vertex_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + } + + #[test] + fn add_edge() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); + } + + #[test] + fn edge_count_empty() { + let graph = Graph::new(); + assert_eq!(graph.edge_count(), 0, "unexpected edge count"); + } + + #[test] + fn edge_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.edge_count(), 14, "unexpected edge count"); + } + + #[test] + fn degree_zero() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); + } + + #[test] + fn degree() { + let (graph, vertices) = make_test_graph(); + let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + for i in 0..graph.vertex_count() { + assert_eq!( + graph.degree(vertices[i]), + expected_degrees[i], + "unexpected degree of {:?}", + vertices[i] + ); + } + } + + #[test] + fn are_adjacent_vertex_self() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.are_adjacent(v, v), + false, + "should not be adjacent to itself" + ); + } + + #[test] + fn are_adjacent_single_edge() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); + assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); + graph.add_edge(v1, v2); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + assert!(graph.are_adjacent(v2, v1), "should be adjacent"); + } + + #[test] + fn are_adjacent() { + let (graph, vertices) = make_test_graph(); + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[1] + ); + assert!( + graph.are_adjacent(vertices[9], vertices[5]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[5] + ); + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); + + for i in 0..graph.vertex_count() { + let exp = match i { + 2 => continue, + 1 | 4 | 5 | 6 => true, + _ => false, + }; + assert_eq!( + graph.are_adjacent(vertices[2], vertices[i]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[2], + vertices[i] + ); + assert_eq!( + graph.are_adjacent(vertices[i], vertices[2]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[i], + vertices[2] + ); + } + } + + #[test] + fn vertices_empty() { + let graph = Graph::new(); + assert_eq!( + graph.vertices().count(), + 0, + "vertex iterator of empty graph should have no elements" + ); + } + + #[test] + fn vertices() { + let (graph, vertices) = make_test_graph(); + assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); + + // Expects each vertex to appear exactly once. + for v in graph.vertices() { + assert_eq!( + vertices.iter().filter(|&x| *x == v).count(), + 1, + "unexpected vertex {v:?} from the iterator" + ); + } + } + + #[test] + fn neighbors_empty() { + let mut graph = Graph::new(); + let vertex = graph.add_vertex(); + assert_eq!( + graph.neighbors(vertex).count(), + 0, + "neighbor iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn neighbors() { + let (graph, vertices) = make_test_graph(); + // Checks neighbors of vertex 4. + assert_eq!( + graph.neighbors(vertices[4]).count(), + 4, + "unexpected vertex count" + ); + + // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. + let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + for v in graph.neighbors(vertices[4]) { + assert_eq!( + neighbors.iter().filter(|&x| *x == v).count(), + 1, + "unexpected neighbor {v:?} of {:?} from the iterator", + vertices[4] + ); + } + } + + #[test] + fn loop_edge() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert_eq!( + graph.are_adjacent(v, v), + true, + "vertex with loop edge should be adjacent to itself" + ); + let mut neighbors = graph.neighbors(v); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself" + ); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself twice" + ); + assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); + } + + #[test] + fn multiple_edges() { + let mult = 3; + let mut graph = Graph::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + for _ in 0..mult { + graph.add_edge(vertices[0], vertices[1]); + } + assert_eq!( + graph.vertex_count(), + vertices.len(), + "unexpected vertex count" + ); + assert_eq!(graph.edge_count(), mult, "unexpected edge count"); + for v in vertices { + assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); + } + assert_eq!( + graph.are_adjacent(vertices[0], vertices[1]), + true, + "should be adjacent" + ); + for i in 0..2 { + let mut neighbors = graph.neighbors(vertices[i]); + for j in 0..mult { + assert_eq!( + neighbors.next(), + Some(vertices[1 - i]), + "neighbor {j} of vertex {:?} should be {:?}", + vertices[i], + vertices[1 - i] + ); + } + assert_eq!( + neighbors.next(), + None, + "two many neighbors of {:?} from iterator", + vertices[i] + ); + } + } + + fn make_test_graph() -> (Graph, [Vertex; 10]) { + let mut graph = Graph::new(); + let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + (graph, vertices) + } +} -- 2.43.0 From b54800c1be14112c25277e0c3e5b0ea5011c8637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 22 Apr 2026 16:33:33 +0200 Subject: [PATCH 041/102] Add new GraphTopologyDeletion trait for Graph to implement --- src/traits.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index c86de3d..6894835 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -13,3 +13,8 @@ pub trait GraphTopology { fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } + +pub trait GraphTopologyDeletion: GraphTopology { + fn delete_vertex(&mut self, v: Self::Vertex); + fn delete_edge(&mut self, e: Self::Edge); +} -- 2.43.0 From eabde7d6351fee8552854268b1985d25e759d39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 18:10:43 +0200 Subject: [PATCH 042/102] Add Default implementation for AppendGraph --- src/models/append_graph.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index d702bed..1f050ca 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -61,6 +61,12 @@ impl AppendGraph { } } +impl Default for AppendGraph { + fn default() -> Self { + Self::new() + } +} + impl GraphTopology for AppendGraph { type Vertex = Vertex; -- 2.43.0 From 6180786e33605617a3d42c7deeb3cc28e9768715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 18:13:10 +0200 Subject: [PATCH 043/102] Fix AppendGraph unit tests --- src/models/append_graph.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 1f050ca..4a76fe6 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -214,7 +214,7 @@ mod tests { assert!( graph.are_adjacent(vertices[9], vertices[5]), "expected {:?} and {:?} to be adjacent", - vertices[0], + vertices[9], vertices[5] ); assert!( @@ -329,7 +329,7 @@ mod tests { Some(v), "vertex should be neighbor of itself twice" ); - assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); + assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); } #[test] @@ -368,7 +368,7 @@ mod tests { assert_eq!( neighbors.next(), None, - "two many neighbors of {:?} from iterator", + "too many neighbors of {:?} from iterator", vertices[i] ); } -- 2.43.0 From 02cd1a0dea2f8394b23af80f5bfe39e477bd3783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 18:18:36 +0200 Subject: [PATCH 044/102] Fix several minor issues in AppendGraph::are_adjacent(), unit tests, and formatting --- src/models/append_graph.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 4a76fe6..2423463 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -21,6 +21,7 @@ struct IncidenceEntry { next: Option, neighbor: Vertex, } + struct VertexNeighborIterator<'a> { graph: &'a AppendGraph, incidence: Option, @@ -85,7 +86,7 @@ impl GraphTopology for AppendGraph { } fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.neighbors(v1).find(|&x| x == v2).is_some() + self.neighbors(v1).any(|x| x == v2) } fn vertices(&self) -> impl Iterator { @@ -183,9 +184,8 @@ mod tests { fn are_adjacent_vertex_self() { let mut graph = AppendGraph::new(); let v = graph.add_vertex(); - assert_eq!( - graph.are_adjacent(v, v), - false, + assert!( + !graph.are_adjacent(v, v), "should not be adjacent to itself" ); } @@ -290,11 +290,11 @@ mod tests { assert_eq!( graph.neighbors(vertices[4]).count(), 4, - "unexpected vertex count" + "unexpected neighbor count" ); // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; for v in graph.neighbors(vertices[4]) { assert_eq!( neighbors.iter().filter(|&x| *x == v).count(), @@ -313,9 +313,8 @@ mod tests { assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); assert_eq!(graph.edge_count(), 1, "unexpected edge count"); assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert_eq!( + assert!( graph.are_adjacent(v, v), - true, "vertex with loop edge should be adjacent to itself" ); let mut neighbors = graph.neighbors(v); @@ -334,10 +333,10 @@ mod tests { #[test] fn multiple_edges() { - let mult = 3; + let k = 3; let mut graph = AppendGraph::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..mult { + for _ in 0..k { graph.add_edge(vertices[0], vertices[1]); } assert_eq!( @@ -345,18 +344,17 @@ mod tests { vertices.len(), "unexpected vertex count" ); - assert_eq!(graph.edge_count(), mult, "unexpected edge count"); + assert_eq!(graph.edge_count(), k, "unexpected edge count"); for v in vertices { - assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); + assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); } - assert_eq!( + assert!( graph.are_adjacent(vertices[0], vertices[1]), - true, "should be adjacent" ); for i in 0..2 { let mut neighbors = graph.neighbors(vertices[i]); - for j in 0..mult { + for j in 0..k { assert_eq!( neighbors.next(), Some(vertices[1 - i]), -- 2.43.0 From 67e16f50cd3f8180db375763a0d1e0892539817f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 18:23:54 +0200 Subject: [PATCH 045/102] Fix same issues in Graph draft as well --- src/models/graph.rs | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 2a59bcc..498e286 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -21,6 +21,7 @@ struct IncidenceEntry { next: Option, neighbor: Vertex, } + struct VertexNeighborIterator<'a> { graph: &'a Graph, incidence: Option, @@ -64,6 +65,12 @@ impl Graph { } } +impl Default for Graph { + fn default() -> Self { + Self::new() + } +} + impl GraphTopology for Graph { type Vertex = Vertex; @@ -82,7 +89,7 @@ impl GraphTopology for Graph { } fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.neighbors(v1).find(|&x| x == v2).is_some() + self.neighbors(v1).any(|x| x == v2) } fn vertices(&self) -> impl Iterator { @@ -180,9 +187,8 @@ mod tests { fn are_adjacent_vertex_self() { let mut graph = Graph::new(); let v = graph.add_vertex(); - assert_eq!( - graph.are_adjacent(v, v), - false, + assert!( + !graph.are_adjacent(v, v), "should not be adjacent to itself" ); } @@ -211,7 +217,7 @@ mod tests { assert!( graph.are_adjacent(vertices[9], vertices[5]), "expected {:?} and {:?} to be adjacent", - vertices[0], + vertices[9], vertices[5] ); assert!( @@ -287,11 +293,11 @@ mod tests { assert_eq!( graph.neighbors(vertices[4]).count(), 4, - "unexpected vertex count" + "unexpected neighbor count" ); // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors: Vec = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; for v in graph.neighbors(vertices[4]) { assert_eq!( neighbors.iter().filter(|&x| *x == v).count(), @@ -310,9 +316,8 @@ mod tests { assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); assert_eq!(graph.edge_count(), 1, "unexpected edge count"); assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert_eq!( + assert!( graph.are_adjacent(v, v), - true, "vertex with loop edge should be adjacent to itself" ); let mut neighbors = graph.neighbors(v); @@ -326,15 +331,15 @@ mod tests { Some(v), "vertex should be neighbor of itself twice" ); - assert_eq!(neighbors.next(), None, "two many neighbors from iterator"); + assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); } #[test] fn multiple_edges() { - let mult = 3; + let k = 3; let mut graph = Graph::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..mult { + for _ in 0..k { graph.add_edge(vertices[0], vertices[1]); } assert_eq!( @@ -342,18 +347,17 @@ mod tests { vertices.len(), "unexpected vertex count" ); - assert_eq!(graph.edge_count(), mult, "unexpected edge count"); + assert_eq!(graph.edge_count(), k, "unexpected edge count"); for v in vertices { - assert_eq!(graph.degree(v), mult, "unexpected degree of {v:?}"); + assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); } - assert_eq!( + assert!( graph.are_adjacent(vertices[0], vertices[1]), - true, "should be adjacent" ); for i in 0..2 { let mut neighbors = graph.neighbors(vertices[i]); - for j in 0..mult { + for j in 0..k { assert_eq!( neighbors.next(), Some(vertices[1 - i]), @@ -365,7 +369,7 @@ mod tests { assert_eq!( neighbors.next(), None, - "two many neighbors of {:?} from iterator", + "too many neighbors of {:?} from iterator", vertices[i] ); } -- 2.43.0 From 37a911dce0be9a65c22472bcddcc6c717d1998fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 18:25:23 +0200 Subject: [PATCH 046/102] Change GraphTopology implementation for Graph with generational arena --- Cargo.toml | 1 + src/models/graph.rs | 79 +++++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b338f02..80cdb46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +typed-generational-arena = "0.2.9" diff --git a/src/models/graph.rs b/src/models/graph.rs index 498e286..5e57c72 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,67 +1,67 @@ +use typed_generational_arena::{Arena, Index}; + use crate::traits::GraphTopology; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Vertex(usize); +type Vertex = Index; -impl From for usize { - fn from(v: Vertex) -> usize { - v.0 - } -} +type Edge = Index; #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Incidence(usize); +struct VertexSlot(usize); -struct VertexIncidenceHeader { +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct IncidenceSlot(usize); + +pub struct VertexIncidenceHeader { incidence_count: usize, - first_incidence: Option, + first_incidence: Option, } -struct IncidenceEntry { - next: Option, - neighbor: Vertex, +pub struct IncidenceEntry { + next: Option, + neighbor: VertexSlot, } struct VertexNeighborIterator<'a> { graph: &'a Graph, - incidence: Option, + incidence: Option, } impl<'a> Iterator for VertexNeighborIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { + // TODO: Benchmark storing full Index (one read, larger entries) vs slot + get_idx() (two reads, smaller entries). let incidence = self.incidence?; - let entry = &self.graph.incidences[incidence.0]; + let index = self.graph.incidences.get_idx(incidence.0)?; + let entry = &self.graph.incidences[index]; self.incidence = entry.next; - Some(entry.neighbor) + self.graph.vertices.get_idx(entry.neighbor.0) } } -// TODO: Graph cannot support deleting vertices because of the external indices "Vertex". They could be made stable with e.g. slotmap or generational arena. -// TODO: Add function to delete vertices. -// TODO: Add function to delete edges. pub struct Graph { - // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? - vertices: Vec, - incidences: Vec, + // TODO: Arena index and generation types could be externalized to Graph. + vertices: Arena, + incidences: Arena, } impl Graph { pub fn new() -> Self { Self { - vertices: vec![], - incidences: vec![], + vertices: Arena::new(), + incidences: Arena::new(), } } - fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { - self.incidences.push(IncidenceEntry { - next: self.vertices[v1.0].first_incidence.take(), - neighbor: v2, + fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge { + let edge = self.incidences.insert(IncidenceEntry { + next: self.vertices[v1].first_incidence.take(), + neighbor: VertexSlot(v2.arr_idx()), }); - self.vertices[v1.0].incidence_count += 1; - self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); + self.vertices[v1].incidence_count += 1; + self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); + edge } } @@ -74,7 +74,7 @@ impl Default for Graph { impl GraphTopology for Graph { type Vertex = Vertex; - type Edge = Incidence; + type Edge = Edge; fn vertex_count(&self) -> usize { self.vertices.len() @@ -85,7 +85,7 @@ impl GraphTopology for Graph { } fn degree(&self, v: Self::Vertex) -> usize { - self.vertices[v.0].incidence_count + self.vertices[v].incidence_count } fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { @@ -93,31 +93,32 @@ impl GraphTopology for Graph { } fn vertices(&self) -> impl Iterator { - (0..self.vertices.len()).map(Vertex) + self.vertices.iter().map(|(i, _)| i) } fn neighbors(&self, v: Self::Vertex) -> impl Iterator { VertexNeighborIterator { graph: self, - incidence: self.vertices[v.0].first_incidence, + incidence: self.vertices[v].first_incidence, } } fn add_vertex(&mut self) -> Self::Vertex { - self.vertices.push(VertexIncidenceHeader { + self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, first_incidence: None, - }); - Vertex(self.vertices.len() - 1) + }) } fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge { - self.add_incidence(v1, v2); + let first = self.add_incidence(v1, v2); self.add_incidence(v2, v1); - Incidence(self.incidences.len() - 2) + first } } +// TODO: impl GraphTopologyDeletion for Graph. + #[cfg(test)] mod tests { use super::*; -- 2.43.0 From 0a30307a0d9c974c3ae1cd550ee6117bbe677d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 22:49:12 +0200 Subject: [PATCH 047/102] Update todos --- src/models/graph.rs | 2 +- src/traits.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 5e57c72..0639e61 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -31,7 +31,7 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { - // TODO: Benchmark storing full Index (one read, larger entries) vs slot + get_idx() (two reads, smaller entries). + // TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries). let incidence = self.incidence?; let index = self.graph.incidences.get_idx(incidence.0)?; let entry = &self.graph.incidences[index]; diff --git a/src/traits.rs b/src/traits.rs index 6894835..aa28583 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,6 @@ // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add iterator of edges. +// TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; type Edge; -- 2.43.0 From a8168b35e4d9bea2aca391bc9d6fb277b23cd577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 23 Apr 2026 22:50:11 +0200 Subject: [PATCH 048/102] Add unit tests for GraphTopologyDeletion for Graph --- src/models/graph.rs | 103 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 0639e61..2ce0f8d 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,6 +1,6 @@ use typed_generational_arena::{Arena, Index}; -use crate::traits::GraphTopology; +use crate::traits::{GraphTopology, GraphTopologyDeletion}; type Vertex = Index; @@ -117,7 +117,16 @@ impl GraphTopology for Graph { } } -// TODO: impl GraphTopologyDeletion for Graph. +// TODO: Benchmark delete with storing "previous" in O(1) vs. linear lookup in O(degree). +impl GraphTopologyDeletion for Graph { + fn delete_vertex(&mut self, v: Self::Vertex) { + todo!() + } + + fn delete_edge(&mut self, e: Self::Edge) { + todo!() + } +} #[cfg(test)] mod tests { @@ -376,6 +385,96 @@ mod tests { } } + #[test] + fn delete_vertex() { + let mut graph = Graph::new(); + let v = graph.add_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" + ); + assert_ne!( + graph.add_vertex(), + v, + "unexpected duplicate vertex after delete" + ); + } + + #[test] + fn delete_vertex_invalid_index() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.delete_vertex(v); + let result = std::panic::catch_unwind(move || graph.delete_vertex(v)); + assert!(result.is_err(), "second deletion should panic"); + } + + #[test] + fn delete_vertex_connected() { + let (mut graph, vertices) = make_test_graph(); + assert_eq!( + graph.vertex_count(), + 10, + "unexpected vertex count before delete" + ); + assert_eq!( + graph.edge_count(), + 14, + "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(), 10, "unexpected edge count after delete"); + } + + #[test] + fn delete_edge() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 1, "unexpected edge count 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_ne!( + graph.add_edge(v1, v2), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_invalid_index() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + graph.delete_edge(e); + let result = std::panic::catch_unwind(move || graph.delete_edge(e)); + assert!(result.is_err(), "second deletion should panic"); + } + fn make_test_graph() -> (Graph, [Vertex; 10]) { let mut graph = Graph::new(); let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); -- 2.43.0 From 0ae4d486cb0b28ef337c8a613081c4a0af14c6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 28 Apr 2026 17:29:54 +0200 Subject: [PATCH 049/102] Add and update todos --- src/traits.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index aa28583..ffb2cc1 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,7 @@ // TODO: Add functions to reserve memory for vertices and edges. -// TODO: Add iterator of edges. +// TODO: Add iterator of all edges. +// TODO: Add iterator of incident edges for a vertex. +// TODO: Add finding incident vertices for an edge. // TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; -- 2.43.0 From f07550182877008aaa8853c11d6a6f3238cb77ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 28 Apr 2026 17:35:00 +0200 Subject: [PATCH 050/102] Add comments for AppendGraph and Graph add_incidence() --- src/models/append_graph.rs | 2 ++ src/models/graph.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 2423463..7868ae7 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -52,6 +52,8 @@ 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) { self.incidences.push(IncidenceEntry { next: self.vertices[v1.0].first_incidence.take(), diff --git a/src/models/graph.rs b/src/models/graph.rs index 2ce0f8d..88e34a3 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -54,6 +54,8 @@ 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 { let edge = self.incidences.insert(IncidenceEntry { next: self.vertices[v1].first_incidence.take(), -- 2.43.0 From 8e0b3325f53dacedd12623a04fbbaf54061edd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 28 Apr 2026 17:36:38 +0200 Subject: [PATCH 051/102] Update and adds tests for Graph edge deletion --- src/models/graph.rs | 131 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/models/graph.rs b/src/models/graph.rs index 88e34a3..80e939d 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -438,6 +438,31 @@ mod tests { "unexpected vertex count after delete" ); assert_eq!(graph.edge_count(), 10, "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[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.neighbors(v).any(|u| u == vertices[2]), + "unexpected adjacency of {v:?} to deleted vertex {:?}", + vertices[2] + ); + } } #[test] @@ -452,6 +477,20 @@ mod tests { "unexpected vertex count before delete" ); assert_eq!(graph.edge_count(), 1, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v1, v2), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v1), + 1, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v2), + 1, + "unexpected vertex degree before delete" + ); graph.delete_edge(e); assert_eq!( graph.vertex_count(), @@ -459,6 +498,98 @@ mod tests { "unexpected vertex count after delete" ); assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + assert!( + !graph.are_adjacent(v1, v2), + "unexpected adjacency after delete" + ); + assert_eq!(graph.degree(v1), 0, "unexpected vertex degree after delete"); + assert_eq!(graph.degree(v2), 0, "unexpected vertex degree after delete"); + assert_ne!( + graph.add_edge(v1, v2), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_loop() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + 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"); + assert_ne!( + graph.add_edge(v, v), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_multiple() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + graph.add_edge(v1, v2); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 2, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v1, v2), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v1), + 2, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v2), + 2, + "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(), 1, "unexpected edge count after delete"); + assert!( + graph.are_adjacent(v1, v2), + "expected vertices to be adjacent after delete" + ); + assert_eq!(graph.degree(v1), 1, "unexpected vertex degree after delete"); + assert_eq!(graph.degree(v2), 1, "unexpected vertex degree after delete"); assert_ne!( graph.add_edge(v1, v2), e, -- 2.43.0 From ceedea6a0f5366c2aec489d06fcfb50139d20ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 28 Apr 2026 17:38:41 +0200 Subject: [PATCH 052/102] Fix formatting --- src/models/graph.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 80e939d..d934124 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -526,11 +526,7 @@ mod tests { graph.are_adjacent(v, v), "expected vertex to be self-adjacent before delete" ); - assert_eq!( - graph.degree(v), - 2, - "unexpected vertex degree before delete" - ); + assert_eq!(graph.degree(v), 2, "unexpected vertex degree before delete"); graph.delete_edge(e); assert_eq!( graph.vertex_count(), -- 2.43.0 From 2793f8af8adbbb5467c1e793c3c4ca4e92c0a077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 28 Apr 2026 17:39:32 +0200 Subject: [PATCH 053/102] Add Graph::delete_edge() implementation --- src/models/graph.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index d934124..33a7224 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -40,6 +40,22 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { } } +struct VertexIncidenceIterator<'a> { + graph: &'a Graph, + incidence: Option, +} + +impl<'a> Iterator for VertexIncidenceIterator<'a> { + type Item = Edge; + + fn next(&mut self) -> Option { + let current = self.incidence?; + let index = self.graph.incidences.get_idx(current.0)?; + self.incidence = self.graph.incidences[index].next; + Some(index) + } +} + pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -65,6 +81,32 @@ impl Graph { self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); edge } + + // Deletes a single incidence "e" of an edge, which is composed of two such incidences, from the + // incidences arena. "next" is the next incidence after "e", both originating at the vertex + // "start". + fn delete_incidence(&mut self, e: Edge, start: VertexSlot, next: Option) { + let start_vertex = self + .vertices + .get_idx(start.0) + .expect("missing incident neighbor, corrupt internal data state"); + let vertex_header = &mut self.vertices[start_vertex]; + vertex_header.incidence_count -= 1; + let first = vertex_header + .first_incidence + .expect("incident neighbor without incidences, corrupt internal data state"); + if first.0 == e.arr_idx() { + vertex_header.first_incidence = next; + } else { + let previous = VertexIncidenceIterator { + graph: self, + incidence: self.vertices[start_vertex].first_incidence, + } + .find(|i| self.incidences[*i].next.is_some_and(|s| s.0 == e.arr_idx())) + .expect("cannot find previous incidence, corrupt internal data state"); + self.incidences[previous].next = next; + } + } } impl Default for Graph { @@ -125,8 +167,27 @@ impl GraphTopologyDeletion for Graph { todo!() } + // "e" must index the first of two paired incidences. We want to avoid the calculations required + // to allow either of the incidences to be specified, if possible. + // The incidence entries are removed before patching the linked lists. This is safe because + // delete_incidence() searches by raw slot index (IncidenceSlot.0) and only dereferences the + // predecessor node, never the removed entries themselves. fn delete_edge(&mut self, e: Self::Edge) { - todo!() + let f = self + .incidences + .get_idx(e.arr_idx() + 1) + .expect("invalid paired incidence index, corrupt internal data state"); + let e_entry = &self + .incidences + .remove(e) + .expect("attempt to delete an invalid edge"); + let f_entry = &self + .incidences + .remove(f) + .expect("cannot read paired incidence, corrupt internal data state"); + + self.delete_incidence(f, e_entry.neighbor, f_entry.next); + self.delete_incidence(e, f_entry.neighbor, e_entry.next); } } -- 2.43.0 From 5b0d6ca1ad0d5d0b9db1f2d3b23cede2f7e3d281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 29 Apr 2026 18:24:10 +0200 Subject: [PATCH 054/102] Fix Graph::delete_edge() for paired index with loops - Add new tests for this case - Fix double list traversal for loop deletions - Rename Graph::delete_incidence() to update_incidence_list(), and its arguments and local vars --- src/models/graph.rs | 70 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 33a7224..e13fff4 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -82,16 +82,21 @@ impl Graph { edge } - // Deletes a single incidence "e" of an edge, which is composed of two such incidences, from the - // incidences arena. "next" is the next incidence after "e", both originating at the vertex - // "start". - fn delete_incidence(&mut self, e: Edge, start: VertexSlot, next: Option) { - let start_vertex = self + // Updates the source vertex incidence list after the incidence "e" was deleted from the + // incidences arena. "next" is the next incidence after "e" in the source vertex incidence list. + fn update_incidence_list( + &mut self, + e: Edge, + source: VertexSlot, + next: Option, + is_loop: bool, + ) { + let source_vertex = self .vertices - .get_idx(start.0) + .get_idx(source.0) .expect("missing incident neighbor, corrupt internal data state"); - let vertex_header = &mut self.vertices[start_vertex]; - vertex_header.incidence_count -= 1; + let vertex_header = &mut self.vertices[source_vertex]; + vertex_header.incidence_count -= if is_loop { 2 } else { 1 }; let first = vertex_header .first_incidence .expect("incident neighbor without incidences, corrupt internal data state"); @@ -100,9 +105,9 @@ impl Graph { } else { let previous = VertexIncidenceIterator { graph: self, - incidence: self.vertices[start_vertex].first_incidence, + incidence: self.vertices[source_vertex].first_incidence, } - .find(|i| self.incidences[*i].next.is_some_and(|s| s.0 == e.arr_idx())) + .find(|f| self.incidences[*f].next.is_some_and(|i| i.0 == e.arr_idx())) .expect("cannot find previous incidence, corrupt internal data state"); self.incidences[previous].next = next; } @@ -167,15 +172,13 @@ impl GraphTopologyDeletion for Graph { todo!() } - // "e" must index the first of two paired incidences. We want to avoid the calculations required - // to allow either of the incidences to be specified, if possible. // The incidence entries are removed before patching the linked lists. This is safe because - // delete_incidence() searches by raw slot index (IncidenceSlot.0) and only dereferences the - // predecessor node, never the removed entries themselves. + // update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences + // the predecessor, never the removed entries themselves. fn delete_edge(&mut self, e: Self::Edge) { let f = self .incidences - .get_idx(e.arr_idx() + 1) + .get_idx(e.arr_idx() ^ 1) .expect("invalid paired incidence index, corrupt internal data state"); let e_entry = &self .incidences @@ -186,8 +189,14 @@ impl GraphTopologyDeletion for Graph { .remove(f) .expect("cannot read paired incidence, corrupt internal data state"); - self.delete_incidence(f, e_entry.neighbor, f_entry.next); - self.delete_incidence(e, f_entry.neighbor, e_entry.next); + if e_entry.neighbor != f_entry.neighbor { + self.update_incidence_list(e, f_entry.neighbor, e_entry.next, false); + self.update_incidence_list(f, e_entry.neighbor, f_entry.next, false); + } else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) { + self.update_incidence_list(f, e_entry.neighbor, e_entry.next, true); + } else { + self.update_incidence_list(e, e_entry.neighbor, f_entry.next, true); + } } } @@ -654,6 +663,33 @@ mod tests { ); } + #[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 f = graph + .incidences + .get_idx(e.arr_idx() + 1) + .expect("paired index should be valid"); + graph.delete_edge(f); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + } + + #[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 f = graph + .incidences + .get_idx(e.arr_idx() + 1) + .expect("paired index should be valid"); + graph.delete_edge(f); + assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); + } + #[test] fn delete_edge_invalid_index() { let mut graph = Graph::new(); -- 2.43.0 From b3dc0ea666c3c8a1d947cde505e628341a7a1977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 29 Apr 2026 20:49:34 +0200 Subject: [PATCH 055/102] Add test for Graph vertex deletion with loop --- src/models/graph.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/models/graph.rs b/src/models/graph.rs index e13fff4..0d3868b 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -479,6 +479,36 @@ mod tests { ); } + #[test] + fn delete_vertex_loop() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + 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"); + assert_ne!( + graph.add_vertex(), + v, + "unexpected duplicate vertex after delete" + ); + } + #[test] fn delete_vertex_invalid_index() { let mut graph = Graph::new(); -- 2.43.0 From 09c5850ff3e126a7d6167fa638009b113ae87cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 29 Apr 2026 20:50:19 +0200 Subject: [PATCH 056/102] Add Graph::delete_vertex() implementation --- src/models/graph.rs | 61 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 0d3868b..4f6e16f 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -56,6 +56,19 @@ impl<'a> Iterator for VertexIncidenceIterator<'a> { } } +struct VertexIncidenceCursor { + incidence: Option, +} + +impl VertexIncidenceCursor { + fn next(&mut self, graph: &Graph) -> Option { + let current = self.incidence?; + let index = graph.incidences.get_idx(current.0)?; + self.incidence = graph.incidences[index].next; + Some(index) + } +} + pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -82,6 +95,22 @@ impl Graph { edge } + fn remove_incidence_pair(&mut self, e: Edge) -> (Edge, IncidenceEntry, IncidenceEntry) { + let f = self + .incidences + .get_idx(e.arr_idx() ^ 1) + .expect("invalid paired incidence index, corrupt internal data state"); + let e_entry = self + .incidences + .remove(e) + .expect("attempt to delete an invalid edge"); + let f_entry = self + .incidences + .remove(f) + .expect("cannot read paired incidence, corrupt internal data state"); + (f, e_entry, f_entry) + } + // Updates the source vertex incidence list after the incidence "e" was deleted from the // incidences arena. "next" is the next incidence after "e" in the source vertex incidence list. fn update_incidence_list( @@ -169,26 +198,30 @@ impl GraphTopology for Graph { // TODO: Benchmark delete with storing "previous" in O(1) vs. linear lookup in O(degree). impl GraphTopologyDeletion for Graph { fn delete_vertex(&mut self, v: Self::Vertex) { - todo!() + let v_header = self + .vertices + .remove(v) + .expect("attempt to delete an invalid vertex"); + let mut cursor = VertexIncidenceCursor { + incidence: v_header.first_incidence, + }; + while let Some(e) = cursor.next(self) { + // Since v is being deleted, there are no update_incidence_list() calls for e, no need + // to fix v's incidence list. + let (f, e_entry, f_entry) = self.remove_incidence_pair(e); + if e_entry.neighbor != f_entry.neighbor { + self.update_incidence_list(f, e_entry.neighbor, f_entry.next, false); + } else { + cursor.incidence = f_entry.next; + } + } } // The incidence entries are removed before patching the linked lists. This is safe because // update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences // the predecessor, never the removed entries themselves. fn delete_edge(&mut self, e: Self::Edge) { - let f = self - .incidences - .get_idx(e.arr_idx() ^ 1) - .expect("invalid paired incidence index, corrupt internal data state"); - let e_entry = &self - .incidences - .remove(e) - .expect("attempt to delete an invalid edge"); - let f_entry = &self - .incidences - .remove(f) - .expect("cannot read paired incidence, corrupt internal data state"); - + let (f, e_entry, f_entry) = self.remove_incidence_pair(e); if e_entry.neighbor != f_entry.neighbor { self.update_incidence_list(e, f_entry.neighbor, e_entry.next, false); self.update_incidence_list(f, e_entry.neighbor, f_entry.next, false); -- 2.43.0 From 8324f93ebde16737478953d3efd1c924dfaf18f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 30 Apr 2026 17:41:53 +0200 Subject: [PATCH 057/102] Add graph topology test helper macros to deduplicate test code --- src/lib.rs | 2 + src/models/append_graph.rs | 274 +------------ src/models/graph.rs | 523 +------------------------ src/testing.rs | 1 + src/testing/graph_topology_testing.rs | 538 ++++++++++++++++++++++++++ 5 files changed, 547 insertions(+), 791 deletions(-) create mode 100644 src/testing.rs create mode 100644 src/testing/graph_topology_testing.rs diff --git a/src/lib.rs b/src/lib.rs index 2c999c7..43bdd7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ pub mod algorithms; pub mod models; pub mod traits; + +mod testing; diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 7868ae7..03e7904 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -121,276 +121,6 @@ impl GraphTopology for AppendGraph { mod tests { use super::*; - #[test] - fn add_vertex() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); - } - - #[test] - fn vertex_count_empty() { - let graph = AppendGraph::new(); - assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); - } - - #[test] - fn vertex_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); - } - - #[test] - fn add_edge() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); - } - - #[test] - fn edge_count_empty() { - let graph = AppendGraph::new(); - assert_eq!(graph.edge_count(), 0, "unexpected edge count"); - } - - #[test] - fn edge_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.edge_count(), 14, "unexpected edge count"); - } - - #[test] - fn degree_zero() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); - } - - #[test] - fn degree() { - let (graph, vertices) = make_test_graph(); - let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; - for i in 0..graph.vertex_count() { - assert_eq!( - graph.degree(vertices[i]), - expected_degrees[i], - "unexpected degree of {:?}", - vertices[i] - ); - } - } - - #[test] - fn are_adjacent_vertex_self() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - assert!( - !graph.are_adjacent(v, v), - "should not be adjacent to itself" - ); - } - - #[test] - fn are_adjacent_single_edge() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); - assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); - graph.add_edge(v1, v2); - assert!(graph.are_adjacent(v1, v2), "should be adjacent"); - assert!(graph.are_adjacent(v2, v1), "should be adjacent"); - } - - #[test] - fn are_adjacent() { - let (graph, vertices) = make_test_graph(); - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[1] - ); - assert!( - graph.are_adjacent(vertices[9], vertices[5]), - "expected {:?} and {:?} to be adjacent", - vertices[9], - vertices[5] - ); - assert!( - !graph.are_adjacent(vertices[9], vertices[3]), - "unexpected adjacency of {:?} and {:?}", - vertices[9], - vertices[3] - ); - - for i in 0..graph.vertex_count() { - let exp = match i { - 2 => continue, - 1 | 4 | 5 | 6 => true, - _ => false, - }; - assert_eq!( - graph.are_adjacent(vertices[2], vertices[i]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[2], - vertices[i] - ); - assert_eq!( - graph.are_adjacent(vertices[i], vertices[2]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[i], - vertices[2] - ); - } - } - - #[test] - fn vertices_empty() { - let graph = AppendGraph::new(); - assert_eq!( - graph.vertices().count(), - 0, - "vertex iterator of empty graph should have no elements" - ); - } - - #[test] - fn vertices() { - let (graph, vertices) = make_test_graph(); - assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); - - // Expects each vertex to appear exactly once. - for v in graph.vertices() { - assert_eq!( - vertices.iter().filter(|&x| *x == v).count(), - 1, - "unexpected vertex {v:?} from the iterator" - ); - } - } - - #[test] - fn neighbors_empty() { - let mut graph = AppendGraph::new(); - let vertex = graph.add_vertex(); - assert_eq!( - graph.neighbors(vertex).count(), - 0, - "neighbor iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn neighbors() { - let (graph, vertices) = make_test_graph(); - // Checks neighbors of vertex 4. - assert_eq!( - graph.neighbors(vertices[4]).count(), - 4, - "unexpected neighbor count" - ); - - // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; - for v in graph.neighbors(vertices[4]) { - assert_eq!( - neighbors.iter().filter(|&x| *x == v).count(), - 1, - "unexpected neighbor {v:?} of {:?} from the iterator", - vertices[4] - ); - } - } - - #[test] - fn loop_edge() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert!( - graph.are_adjacent(v, v), - "vertex with loop edge should be adjacent to itself" - ); - let mut neighbors = graph.neighbors(v); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself" - ); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself twice" - ); - assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); - } - - #[test] - fn multiple_edges() { - let k = 3; - let mut graph = AppendGraph::new(); - let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..k { - graph.add_edge(vertices[0], vertices[1]); - } - assert_eq!( - graph.vertex_count(), - vertices.len(), - "unexpected vertex count" - ); - assert_eq!(graph.edge_count(), k, "unexpected edge count"); - for v in vertices { - assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); - } - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "should be adjacent" - ); - for i in 0..2 { - let mut neighbors = graph.neighbors(vertices[i]); - for j in 0..k { - assert_eq!( - neighbors.next(), - Some(vertices[1 - i]), - "neighbor {j} of vertex {:?} should be {:?}", - vertices[i], - vertices[1 - i] - ); - } - assert_eq!( - neighbors.next(), - None, - "too many neighbors of {:?} from iterator", - vertices[i] - ); - } - } - - fn make_test_graph() -> (AppendGraph, [Vertex; 10]) { - let mut graph = AppendGraph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - (graph, vertices) - } + crate::graph_topology_test_fixtures!(AppendGraph); + crate::graph_topology_tests!(AppendGraph); } diff --git a/src/models/graph.rs b/src/models/graph.rs index 4f6e16f..22f8dea 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -112,7 +112,7 @@ impl Graph { } // Updates the source vertex incidence list after the incidence "e" was deleted from the - // incidences arena. "next" is the next incidence after "e" in the source vertex incidence list. + // incidence arena. "next" is the next incidence after "e" in the source vertex incidence list. fn update_incidence_list( &mut self, e: Edge, @@ -237,494 +237,9 @@ impl GraphTopologyDeletion for Graph { mod tests { use super::*; - #[test] - fn add_vertex() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); - } - - #[test] - fn vertex_count_empty() { - let graph = Graph::new(); - assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); - } - - #[test] - fn vertex_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); - } - - #[test] - fn add_edge() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); - } - - #[test] - fn edge_count_empty() { - let graph = Graph::new(); - assert_eq!(graph.edge_count(), 0, "unexpected edge count"); - } - - #[test] - fn edge_count() { - let (graph, _) = make_test_graph(); - assert_eq!(graph.edge_count(), 14, "unexpected edge count"); - } - - #[test] - fn degree_zero() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); - } - - #[test] - fn degree() { - let (graph, vertices) = make_test_graph(); - let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; - for i in 0..graph.vertex_count() { - assert_eq!( - graph.degree(vertices[i]), - expected_degrees[i], - "unexpected degree of {:?}", - vertices[i] - ); - } - } - - #[test] - fn are_adjacent_vertex_self() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - assert!( - !graph.are_adjacent(v, v), - "should not be adjacent to itself" - ); - } - - #[test] - fn are_adjacent_single_edge() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); - assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); - graph.add_edge(v1, v2); - assert!(graph.are_adjacent(v1, v2), "should be adjacent"); - assert!(graph.are_adjacent(v2, v1), "should be adjacent"); - } - - #[test] - fn are_adjacent() { - let (graph, vertices) = make_test_graph(); - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "expected {:?} and {:?} to be adjacent", - vertices[0], - vertices[1] - ); - assert!( - graph.are_adjacent(vertices[9], vertices[5]), - "expected {:?} and {:?} to be adjacent", - vertices[9], - vertices[5] - ); - assert!( - !graph.are_adjacent(vertices[9], vertices[3]), - "unexpected adjacency of {:?} and {:?}", - vertices[9], - vertices[3] - ); - - for i in 0..graph.vertex_count() { - let exp = match i { - 2 => continue, - 1 | 4 | 5 | 6 => true, - _ => false, - }; - assert_eq!( - graph.are_adjacent(vertices[2], vertices[i]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[2], - vertices[i] - ); - assert_eq!( - graph.are_adjacent(vertices[i], vertices[2]), - exp, - "unexpected adjacency of {:?} and {:?}", - vertices[i], - vertices[2] - ); - } - } - - #[test] - fn vertices_empty() { - let graph = Graph::new(); - assert_eq!( - graph.vertices().count(), - 0, - "vertex iterator of empty graph should have no elements" - ); - } - - #[test] - fn vertices() { - let (graph, vertices) = make_test_graph(); - assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); - - // Expects each vertex to appear exactly once. - for v in graph.vertices() { - assert_eq!( - vertices.iter().filter(|&x| *x == v).count(), - 1, - "unexpected vertex {v:?} from the iterator" - ); - } - } - - #[test] - fn neighbors_empty() { - let mut graph = Graph::new(); - let vertex = graph.add_vertex(); - assert_eq!( - graph.neighbors(vertex).count(), - 0, - "neighbor iterator of vertex with degree 0 should have no elements" - ); - } - - #[test] - fn neighbors() { - let (graph, vertices) = make_test_graph(); - // Checks neighbors of vertex 4. - assert_eq!( - graph.neighbors(vertices[4]).count(), - 4, - "unexpected neighbor count" - ); - - // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; - for v in graph.neighbors(vertices[4]) { - assert_eq!( - neighbors.iter().filter(|&x| *x == v).count(), - 1, - "unexpected neighbor {v:?} of {:?} from the iterator", - vertices[4] - ); - } - } - - #[test] - fn loop_edge() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert!( - graph.are_adjacent(v, v), - "vertex with loop edge should be adjacent to itself" - ); - let mut neighbors = graph.neighbors(v); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself" - ); - assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself twice" - ); - assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); - } - - #[test] - fn multiple_edges() { - let k = 3; - let mut graph = Graph::new(); - let vertices = [graph.add_vertex(), graph.add_vertex()]; - for _ in 0..k { - graph.add_edge(vertices[0], vertices[1]); - } - assert_eq!( - graph.vertex_count(), - vertices.len(), - "unexpected vertex count" - ); - assert_eq!(graph.edge_count(), k, "unexpected edge count"); - for v in vertices { - assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); - } - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "should be adjacent" - ); - for i in 0..2 { - let mut neighbors = graph.neighbors(vertices[i]); - for j in 0..k { - assert_eq!( - neighbors.next(), - Some(vertices[1 - i]), - "neighbor {j} of vertex {:?} should be {:?}", - vertices[i], - vertices[1 - i] - ); - } - assert_eq!( - neighbors.next(), - None, - "too many neighbors of {:?} from iterator", - vertices[i] - ); - } - } - - #[test] - fn delete_vertex() { - let mut graph = Graph::new(); - let v = graph.add_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" - ); - assert_ne!( - graph.add_vertex(), - v, - "unexpected duplicate vertex after delete" - ); - } - - #[test] - fn delete_vertex_loop() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - graph.add_edge(v, v); - 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"); - assert_ne!( - graph.add_vertex(), - v, - "unexpected duplicate vertex after delete" - ); - } - - #[test] - fn delete_vertex_invalid_index() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - graph.delete_vertex(v); - let result = std::panic::catch_unwind(move || graph.delete_vertex(v)); - assert!(result.is_err(), "second deletion should panic"); - } - - #[test] - fn delete_vertex_connected() { - let (mut graph, vertices) = make_test_graph(); - assert_eq!( - graph.vertex_count(), - 10, - "unexpected vertex count before delete" - ); - assert_eq!( - graph.edge_count(), - 14, - "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(), 10, "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[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.neighbors(v).any(|u| u == vertices[2]), - "unexpected adjacency of {v:?} to deleted vertex {:?}", - vertices[2] - ); - } - } - - #[test] - fn delete_edge() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - 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(v1, v2), - "expected vertices to be adjacent before delete" - ); - assert_eq!( - graph.degree(v1), - 1, - "unexpected vertex degree before delete" - ); - assert_eq!( - graph.degree(v2), - 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(v1, v2), - "unexpected adjacency after delete" - ); - assert_eq!(graph.degree(v1), 0, "unexpected vertex degree after delete"); - assert_eq!(graph.degree(v2), 0, "unexpected vertex degree after delete"); - assert_ne!( - graph.add_edge(v1, v2), - e, - "unexpected duplicate edge after delete" - ); - } - - #[test] - fn delete_edge_loop() { - let mut graph = Graph::new(); - let v = graph.add_vertex(); - let e = graph.add_edge(v, v); - 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"); - assert_ne!( - graph.add_edge(v, v), - e, - "unexpected duplicate edge after delete" - ); - } - - #[test] - fn delete_edge_multiple() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - graph.add_edge(v1, v2); - assert_eq!( - graph.vertex_count(), - 2, - "unexpected vertex count before delete" - ); - assert_eq!(graph.edge_count(), 2, "unexpected edge count before delete"); - assert!( - graph.are_adjacent(v1, v2), - "expected vertices to be adjacent before delete" - ); - assert_eq!( - graph.degree(v1), - 2, - "unexpected vertex degree before delete" - ); - assert_eq!( - graph.degree(v2), - 2, - "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(), 1, "unexpected edge count after delete"); - assert!( - graph.are_adjacent(v1, v2), - "expected vertices to be adjacent after delete" - ); - assert_eq!(graph.degree(v1), 1, "unexpected vertex degree after delete"); - assert_eq!(graph.degree(v2), 1, "unexpected vertex degree after delete"); - assert_ne!( - graph.add_edge(v1, v2), - e, - "unexpected duplicate edge after delete" - ); - } + crate::graph_topology_test_fixtures!(Graph); + crate::graph_topology_tests!(Graph); + crate::graph_topology_deletion_tests!(Graph); #[test] fn delete_edge_paired_index() { @@ -753,34 +268,4 @@ mod tests { assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); } - #[test] - fn delete_edge_invalid_index() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let e = graph.add_edge(v1, v2); - graph.delete_edge(e); - let result = std::panic::catch_unwind(move || graph.delete_edge(e)); - assert!(result.is_err(), "second deletion should panic"); - } - - fn make_test_graph() -> (Graph, [Vertex; 10]) { - let mut graph = Graph::new(); - let vertices: [Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - (graph, vertices) - } } diff --git a/src/testing.rs b/src/testing.rs new file mode 100644 index 0000000..ecf3963 --- /dev/null +++ b/src/testing.rs @@ -0,0 +1 @@ +pub(crate) mod graph_topology_testing; diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs new file mode 100644 index 0000000..ad29576 --- /dev/null +++ b/src/testing/graph_topology_testing.rs @@ -0,0 +1,538 @@ +#[cfg(test)] +#[macro_export] +macro_rules! graph_topology_test_fixtures { + ($T:ty) => { + fn make_test_graph() -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 10]) { + let mut graph = <$T>::new(); + let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = + core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[1], vertices[2]); + graph.add_edge(vertices[1], vertices[3]); + graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[4]); + graph.add_edge(vertices[2], vertices[5]); + graph.add_edge(vertices[2], vertices[6]); + graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[7]); + graph.add_edge(vertices[4], vertices[8]); + graph.add_edge(vertices[5], vertices[9]); + graph.add_edge(vertices[6], vertices[9]); + graph.add_edge(vertices[7], vertices[8]); + graph.add_edge(vertices[7], vertices[9]); + (graph, vertices) + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! graph_topology_tests { + ($T:ty) => { + #[test] + fn add_vertex() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); + } + + #[test] + fn vertex_count_empty() { + let graph = <$T>::new(); + assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); + } + + #[test] + fn vertex_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); + } + + #[test] + fn add_edge() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + assert_ne!(graph.add_edge(v1, v2), e, "unexpected duplicate edge"); + } + + #[test] + fn edge_count_empty() { + let graph = <$T>::new(); + assert_eq!(graph.edge_count(), 0, "unexpected edge count"); + } + + #[test] + fn edge_count() { + let (graph, _) = make_test_graph(); + assert_eq!(graph.edge_count(), 14, "unexpected edge count"); + } + + #[test] + fn degree_zero() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); + } + + #[test] + fn degree() { + let (graph, vertices) = make_test_graph(); + let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + for i in 0..graph.vertex_count() { + assert_eq!( + graph.degree(vertices[i]), + expected_degrees[i], + "unexpected degree of {:?}", + vertices[i] + ); + } + } + + #[test] + fn are_adjacent_vertex_self() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert!( + !graph.are_adjacent(v, v), + "should not be adjacent to itself" + ); + } + + #[test] + fn are_adjacent_single_edge() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + assert!(!graph.are_adjacent(v1, v2), "should not be adjacent"); + assert!(!graph.are_adjacent(v2, v1), "should not be adjacent"); + graph.add_edge(v1, v2); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + assert!(graph.are_adjacent(v2, v1), "should be adjacent"); + } + + #[test] + fn are_adjacent() { + let (graph, vertices) = make_test_graph(); + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "expected {:?} and {:?} to be adjacent", + vertices[0], + vertices[1] + ); + assert!( + graph.are_adjacent(vertices[9], vertices[5]), + "expected {:?} and {:?} to be adjacent", + vertices[9], + vertices[5] + ); + assert!( + !graph.are_adjacent(vertices[9], vertices[3]), + "unexpected adjacency of {:?} and {:?}", + vertices[9], + vertices[3] + ); + + for i in 0..graph.vertex_count() { + let exp = match i { + 2 => continue, + 1 | 4 | 5 | 6 => true, + _ => false, + }; + assert_eq!( + graph.are_adjacent(vertices[2], vertices[i]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[2], + vertices[i] + ); + assert_eq!( + graph.are_adjacent(vertices[i], vertices[2]), + exp, + "unexpected adjacency of {:?} and {:?}", + vertices[i], + vertices[2] + ); + } + } + + #[test] + fn vertices_empty() { + let graph = <$T>::new(); + assert_eq!( + graph.vertices().count(), + 0, + "vertex iterator of empty graph should have no elements" + ); + } + + #[test] + fn vertices() { + let (graph, vertices) = make_test_graph(); + assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); + + // Expects each vertex to appear exactly once. + for v in graph.vertices() { + assert_eq!( + vertices.iter().filter(|&x| *x == v).count(), + 1, + "unexpected vertex {v:?} from the iterator" + ); + } + } + + #[test] + fn neighbors_empty() { + let mut graph = <$T>::new(); + let vertex = graph.add_vertex(); + assert_eq!( + graph.neighbors(vertex).count(), + 0, + "neighbor iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn neighbors() { + let (graph, vertices) = make_test_graph(); + // Checks neighbors of vertex 4. + assert_eq!( + graph.neighbors(vertices[4]).count(), + 4, + "unexpected neighbor count" + ); + + // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. + let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + for v in graph.neighbors(vertices[4]) { + assert_eq!( + neighbors.iter().filter(|&x| *x == v).count(), + 1, + "unexpected neighbor {v:?} of {:?} from the iterator", + vertices[4] + ); + } + } + + #[test] + fn loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert!( + graph.are_adjacent(v, v), + "vertex with loop edge should be adjacent to itself" + ); + let mut neighbors = graph.neighbors(v); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself" + ); + assert_eq!( + neighbors.next(), + Some(v), + "vertex should be neighbor of itself twice" + ); + assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); + } + + #[test] + fn multiple_edges() { + let k = 3; + let mut graph = <$T>::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + for _ in 0..k { + graph.add_edge(vertices[0], vertices[1]); + } + assert_eq!( + graph.vertex_count(), + vertices.len(), + "unexpected vertex count" + ); + assert_eq!(graph.edge_count(), k, "unexpected edge count"); + for v in vertices { + assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); + } + assert!( + graph.are_adjacent(vertices[0], vertices[1]), + "should be adjacent" + ); + for i in 0..2 { + let mut neighbors = graph.neighbors(vertices[i]); + for j in 0..k { + assert_eq!( + neighbors.next(), + Some(vertices[1 - i]), + "neighbor {j} of vertex {:?} should be {:?}", + vertices[i], + vertices[1 - i] + ); + } + assert_eq!( + neighbors.next(), + None, + "too many neighbors of {:?} from iterator", + vertices[i] + ); + } + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! graph_topology_deletion_tests { + ($T:ty) => { + #[test] + fn delete_vertex() { + let mut graph = Graph::new(); + let v = graph.add_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" + ); + assert_ne!( + graph.add_vertex(), + v, + "unexpected duplicate vertex after delete" + ); + } + + #[test] + fn delete_vertex_loop() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + 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"); + assert_ne!( + graph.add_vertex(), + v, + "unexpected duplicate vertex after delete" + ); + } + + #[test] + fn delete_vertex_invalid_index() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + graph.delete_vertex(v); + let result = std::panic::catch_unwind(move || graph.delete_vertex(v)); + assert!(result.is_err(), "second deletion should panic"); + } + + #[test] + fn delete_vertex_connected() { + let (mut graph, vertices) = make_test_graph(); + assert_eq!( + graph.vertex_count(), + 10, + "unexpected vertex count before delete" + ); + assert_eq!( + graph.edge_count(), + 14, + "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(), 10, "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[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.neighbors(v).any(|u| u == vertices[2]), + "unexpected adjacency of {v:?} to deleted vertex {:?}", + vertices[2] + ); + } + } + + #[test] + fn delete_edge() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + 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(v1, v2), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v1), + 1, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v2), + 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(v1, v2), + "unexpected adjacency after delete" + ); + assert_eq!(graph.degree(v1), 0, "unexpected vertex degree after delete"); + assert_eq!(graph.degree(v2), 0, "unexpected vertex degree after delete"); + assert_ne!( + graph.add_edge(v1, v2), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_loop() { + let mut graph = Graph::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + 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"); + assert_ne!( + graph.add_edge(v, v), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_multiple() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + graph.add_edge(v1, v2); + assert_eq!( + graph.vertex_count(), + 2, + "unexpected vertex count before delete" + ); + assert_eq!(graph.edge_count(), 2, "unexpected edge count before delete"); + assert!( + graph.are_adjacent(v1, v2), + "expected vertices to be adjacent before delete" + ); + assert_eq!( + graph.degree(v1), + 2, + "unexpected vertex degree before delete" + ); + assert_eq!( + graph.degree(v2), + 2, + "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(), 1, "unexpected edge count after delete"); + assert!( + graph.are_adjacent(v1, v2), + "expected vertices to be adjacent after delete" + ); + assert_eq!(graph.degree(v1), 1, "unexpected vertex degree after delete"); + assert_eq!(graph.degree(v2), 1, "unexpected vertex degree after delete"); + assert_ne!( + graph.add_edge(v1, v2), + e, + "unexpected duplicate edge after delete" + ); + } + + #[test] + fn delete_edge_invalid_index() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + graph.delete_edge(e); + let result = std::panic::catch_unwind(move || graph.delete_edge(e)); + assert!(result.is_err(), "second deletion should panic"); + } + }; +} -- 2.43.0 From feaca335b11301bac4859f44c9cab0ad79c2158d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 5 May 2026 19:21:48 +0200 Subject: [PATCH 058/102] Add loops and multi-edges to the test graph, fix neighbors() test --- src/models/graph.rs | 1 - src/testing/graph_topology_testing.rs | 51 ++++++++++++++++++--------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index 22f8dea..6a69bca 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -267,5 +267,4 @@ mod tests { graph.delete_edge(f); assert_eq!(graph.edge_count(), 0, "unexpected edge count after delete"); } - } diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index ad29576..1e09b5f 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -7,13 +7,17 @@ macro_rules! graph_topology_test_fixtures { let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); graph.add_edge(vertices[0], vertices[1]); + graph.add_edge(vertices[0], vertices[1]); graph.add_edge(vertices[1], vertices[2]); graph.add_edge(vertices[1], vertices[3]); graph.add_edge(vertices[1], vertices[4]); + graph.add_edge(vertices[2], vertices[2]); + graph.add_edge(vertices[2], vertices[4]); graph.add_edge(vertices[2], vertices[4]); graph.add_edge(vertices[2], vertices[5]); graph.add_edge(vertices[2], vertices[6]); graph.add_edge(vertices[3], vertices[6]); + graph.add_edge(vertices[4], vertices[4]); graph.add_edge(vertices[4], vertices[7]); graph.add_edge(vertices[4], vertices[8]); graph.add_edge(vertices[5], vertices[9]); @@ -66,7 +70,7 @@ macro_rules! graph_topology_tests { #[test] fn edge_count() { let (graph, _) = make_test_graph(); - assert_eq!(graph.edge_count(), 14, "unexpected edge count"); + assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } #[test] @@ -79,7 +83,7 @@ macro_rules! graph_topology_tests { #[test] fn degree() { let (graph, vertices) = make_test_graph(); - let expected_degrees = [1, 4, 4, 2, 4, 2, 3, 3, 2, 3]; + let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { assert_eq!( graph.degree(vertices[i]), @@ -136,8 +140,7 @@ macro_rules! graph_topology_tests { for i in 0..graph.vertex_count() { let exp = match i { - 2 => continue, - 1 | 4 | 5 | 6 => true, + 1 | 2 | 4 | 5 | 6 => true, _ => false, }; assert_eq!( @@ -199,20 +202,35 @@ macro_rules! graph_topology_tests { // Checks neighbors of vertex 4. assert_eq!( graph.neighbors(vertices[4]).count(), - 4, + 7, "unexpected neighbor count" ); - - // Expects each neighbor to appear exactly once. This will not work if there are multiple edges. - let neighbors = vec![vertices[1], vertices[2], vertices[7], vertices[8]]; + let mut expected_neighbors = vec![ + vertices[1], + vertices[2], + vertices[2], + vertices[4], + vertices[4], + vertices[7], + vertices[8], + ]; for v in graph.neighbors(vertices[4]) { - assert_eq!( - neighbors.iter().filter(|&x| *x == v).count(), - 1, - "unexpected neighbor {v:?} of {:?} from the iterator", - vertices[4] - ); + let i = expected_neighbors + .iter() + .position(|w| *w == v) + .expect(&format!( + "unexpected neighbor {v:?} of {:?} from the iterator", + vertices[4] + )); + expected_neighbors.swap_remove(i); } + assert_eq!( + expected_neighbors.len(), + 0, + "expected neighbors {:?} of {:?} were not matched by the iterator", + expected_neighbors, + vertices[4] + ); } #[test] @@ -359,7 +377,7 @@ macro_rules! graph_topology_deletion_tests { ); assert_eq!( graph.edge_count(), - 14, + 18, "unexpected edge count before delete" ); graph.delete_vertex(vertices[2]); @@ -368,12 +386,13 @@ macro_rules! graph_topology_deletion_tests { 9, "unexpected vertex count after delete" ); - assert_eq!(graph.edge_count(), 10, "unexpected edge 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]), -- 2.43.0 From 18245b9955641aa3359d5da14b78e0b4bcb353f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 5 May 2026 22:37:32 +0200 Subject: [PATCH 059/102] Add Copy and Eq traits to GraphTopology::Edge --- src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index ffb2cc1..a767df0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,7 +5,7 @@ // TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; - type Edge; + type Edge: Copy + Eq; fn vertex_count(&self) -> usize; fn edge_count(&self) -> usize; -- 2.43.0 From c589095737632ccbea1b9c910d7ee980d31c6279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 5 May 2026 22:52:07 +0200 Subject: [PATCH 060/102] Add test for GraphTopology::vertices() after delete --- src/testing/graph_topology_testing.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 1e09b5f..bf864f0 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -553,5 +553,35 @@ macro_rules! graph_topology_deletion_tests { let result = std::panic::catch_unwind(move || graph.delete_edge(e)); assert!(result.is_err(), "second deletion should panic"); } + + #[test] + fn vertices_after_delete() { + let (mut graph, vertices) = make_test_graph(); + graph.delete_vertex(vertices[2]); + 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, vertices[2], + "deleted vertex {:?} appeared in iterator", + vertices[2] + ); + } + for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { + assert!( + graph.vertices().any(|v| v == vertices[i]), + "expected vertex {:?} missing from iterator after delete", + vertices[i] + ); + } + } }; } -- 2.43.0 From 6e9867a65ecfe1a2847284de1483a56680672c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 5 May 2026 22:54:33 +0200 Subject: [PATCH 061/102] Add GraphTopology::edges() and tests --- src/models/append_graph.rs | 4 + src/models/graph.rs | 7 ++ src/testing/graph_topology_testing.rs | 113 +++++++++++++++++++------- src/traits.rs | 2 +- 4 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 03e7904..7ee75c3 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -102,6 +102,10 @@ impl GraphTopology for AppendGraph { } } + fn edges(&self) -> impl Iterator { + (0..self.incidences.len()).step_by(2).map(Incidence) + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.push(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/models/graph.rs b/src/models/graph.rs index 6a69bca..d3110cf 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -181,6 +181,13 @@ impl GraphTopology for Graph { } } + fn edges(&self) -> impl Iterator { + self.incidences + .iter() + .filter(|(i, _)| i.arr_idx() % 2 == 0) + .map(|(i, _)| i) + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index bf864f0..51131ae 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -2,29 +2,35 @@ #[macro_export] macro_rules! graph_topology_test_fixtures { ($T:ty) => { - fn make_test_graph() -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 10]) { + fn make_test_graph() -> ( + $T, + [<$T as $crate::traits::GraphTopology>::Vertex; 10], + [<$T as $crate::traits::GraphTopology>::Edge; 18], + ) { let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[2]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[4]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - (graph, vertices) + let edges = [ + graph.add_edge(vertices[0], vertices[1]), + graph.add_edge(vertices[0], vertices[1]), + graph.add_edge(vertices[1], vertices[2]), + graph.add_edge(vertices[1], vertices[3]), + graph.add_edge(vertices[1], vertices[4]), + graph.add_edge(vertices[2], vertices[2]), + graph.add_edge(vertices[2], vertices[4]), + graph.add_edge(vertices[2], vertices[4]), + graph.add_edge(vertices[2], vertices[5]), + graph.add_edge(vertices[2], vertices[6]), + graph.add_edge(vertices[3], vertices[6]), + graph.add_edge(vertices[4], vertices[4]), + graph.add_edge(vertices[4], vertices[7]), + graph.add_edge(vertices[4], vertices[8]), + graph.add_edge(vertices[5], vertices[9]), + graph.add_edge(vertices[6], vertices[9]), + graph.add_edge(vertices[7], vertices[8]), + graph.add_edge(vertices[7], vertices[9]), + ]; + (graph, vertices, edges) } }; } @@ -48,7 +54,7 @@ macro_rules! graph_topology_tests { #[test] fn vertex_count() { - let (graph, _) = make_test_graph(); + let (graph, _, _) = make_test_graph(); assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } @@ -69,7 +75,7 @@ macro_rules! graph_topology_tests { #[test] fn edge_count() { - let (graph, _) = make_test_graph(); + let (graph, _, _) = make_test_graph(); assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } @@ -82,7 +88,7 @@ macro_rules! graph_topology_tests { #[test] fn degree() { - let (graph, vertices) = make_test_graph(); + let (graph, vertices, _) = make_test_graph(); let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { assert_eq!( @@ -118,7 +124,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent() { - let (graph, vertices) = make_test_graph(); + let (graph, vertices, _) = make_test_graph(); assert!( graph.are_adjacent(vertices[0], vertices[1]), "expected {:?} and {:?} to be adjacent", @@ -172,7 +178,7 @@ macro_rules! graph_topology_tests { #[test] fn vertices() { - let (graph, vertices) = make_test_graph(); + let (graph, vertices, _) = make_test_graph(); assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); // Expects each vertex to appear exactly once. @@ -198,7 +204,7 @@ macro_rules! graph_topology_tests { #[test] fn neighbors() { - let (graph, vertices) = make_test_graph(); + let (graph, vertices, _) = make_test_graph(); // Checks neighbors of vertex 4. assert_eq!( graph.neighbors(vertices[4]).count(), @@ -233,6 +239,31 @@ macro_rules! graph_topology_tests { ); } + #[test] + fn edges_empty() { + let graph = <$T>::new(); + assert_eq!( + graph.edges().count(), + 0, + "edge iterator of empty graph should have no elements" + ); + } + + #[test] + fn edges() { + let (graph, _, edges) = make_test_graph(); + assert_eq!(graph.edges().count(), 18, "unexpected edge count"); + + // Expects each edge to appear exactly once. + for e in graph.edges() { + assert_eq!( + edges.iter().filter(|&x| *x == e).count(), + 1, + "unexpected edge {e:?} from the iterator" + ); + } + } + #[test] fn loop_edge() { let mut graph = <$T>::new(); @@ -369,7 +400,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_connected() { - let (mut graph, vertices) = make_test_graph(); + let (mut graph, vertices, _) = make_test_graph(); assert_eq!( graph.vertex_count(), 10, @@ -556,7 +587,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn vertices_after_delete() { - let (mut graph, vertices) = make_test_graph(); + let (mut graph, vertices, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!( graph.vertex_count(), @@ -583,5 +614,31 @@ macro_rules! graph_topology_deletion_tests { ); } } + + #[test] + fn edges_after_delete() { + let (mut graph, vertices, edges) = make_test_graph(); + 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]), + "deleted edge {:?} appeared in iterator", + edges[i] + ); + } + for i in [0, 1, 3, 4, 10, 11, 12, 13, 14, 15, 16, 17] { + assert!( + graph.edges().any(|e| e == edges[i]), + "expected edge {:?} missing from iterator after delete", + edges[i] + ); + } + } }; } diff --git a/src/traits.rs b/src/traits.rs index a767df0..a1eb4f5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,4 @@ // TODO: Add functions to reserve memory for vertices and edges. -// TODO: Add iterator of all edges. // TODO: Add iterator of incident edges for a vertex. // TODO: Add finding incident vertices for an edge. // TODO: Split out GraphTopologyAddition trait. @@ -13,6 +12,7 @@ pub trait GraphTopology { fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; fn neighbors(&self, v: Self::Vertex) -> impl Iterator; + fn edges(&self) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } -- 2.43.0 From f40c03b113a40d0812ff6fa3bbb7b969026b2895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 6 May 2026 20:28:00 +0200 Subject: [PATCH 062/102] Add VertexMap to attach data to vertices --- src/lib.rs | 1 + src/maps.rs | 122 +++++++++++++++++++++++++++++++++++++ src/models/append_graph.rs | 9 +++ src/models/graph.rs | 9 +++ src/traits.rs | 4 ++ tests/dijkstra.rs | 77 +++++++---------------- 6 files changed, 168 insertions(+), 54 deletions(-) create mode 100644 src/maps.rs diff --git a/src/lib.rs b/src/lib.rs index 43bdd7f..4ce7c62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod algorithms; +pub mod maps; pub mod models; pub mod traits; diff --git a/src/maps.rs b/src/maps.rs new file mode 100644 index 0000000..70406d5 --- /dev/null +++ b/src/maps.rs @@ -0,0 +1,122 @@ +use std::ops::{Index, IndexMut}; + +use crate::traits::GraphTopology; + +pub struct VertexMap { + data: Vec, + default: T, + to_index: fn(V) -> usize, +} + +impl VertexMap { + pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self { + Self { + data: vec![default.clone(); capacity], + default, + to_index, + } + } + + pub fn sync>(&mut self, graph: &G) { + let capacity = graph.vertex_capacity(); + if capacity > self.data.len() { + self.data.resize(capacity, self.default.clone()); + } + } +} + +impl Index for VertexMap { + type Output = T; + + fn index(&self, v: V) -> &T { + let i = (self.to_index)(v); + if i < self.data.len() { + &self.data[i] + } else { + &self.default + } + } +} + +impl IndexMut for VertexMap { + fn index_mut(&mut self, v: V) -> &mut T { + let i = (self.to_index)(v); + if i >= self.data.len() { + self.data.resize(i + 1, self.default.clone()); + } + &mut self.data[i] + } +} + +#[cfg(test)] +mod tests { + use crate::models::append_graph::AppendGraph; + use crate::traits::GraphTopology; + + #[test] + fn initial_values_are_default() { + let mut graph = AppendGraph::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() { + let mut graph = AppendGraph::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() { + let mut graph = AppendGraph::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() { + let mut graph = AppendGraph::new(); + let v1 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let v2 = graph.add_vertex(); + map[v2] = 7; + assert_eq!(map[v1], 0); + assert_eq!(map[v2], 7); + } + + #[test] + fn sync_expands_to_new_vertices() { + let mut graph = AppendGraph::new(); + graph.add_vertex(); + let mut map = graph.vertex_map(42); + graph.add_vertex(); + graph.add_vertex(); + assert!( + map.data.len() < graph.vertex_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.data.len(), graph.vertex_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = AppendGraph::new(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.add_vertex(); + map.sync(&graph); + assert_eq!(map[v], 5); + } +} diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 7ee75c3..83f0127 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,3 +1,4 @@ +use crate::maps::VertexMap; use crate::traits::GraphTopology; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -83,6 +84,14 @@ impl GraphTopology for AppendGraph { self.incidences.len() / 2 } + fn vertex_capacity(&self) -> usize { + self.vertices.len() + } + + fn vertex_map(&self, default: T) -> VertexMap { + VertexMap::new(default, |v| v.0, self.vertex_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v.0].incidence_count } diff --git a/src/models/graph.rs b/src/models/graph.rs index d3110cf..d69372a 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,5 +1,6 @@ use typed_generational_arena::{Arena, Index}; +use crate::maps::VertexMap; use crate::traits::{GraphTopology, GraphTopologyDeletion}; type Vertex = Index; @@ -162,6 +163,14 @@ impl GraphTopology for Graph { self.incidences.len() / 2 } + fn vertex_capacity(&self) -> usize { + self.vertices.capacity() + } + + fn vertex_map(&self, default: T) -> VertexMap { + VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].incidence_count } diff --git a/src/traits.rs b/src/traits.rs index a1eb4f5..515c006 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,3 +1,5 @@ +use crate::maps::VertexMap; + // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add iterator of incident edges for a vertex. // TODO: Add finding incident vertices for an edge. @@ -8,6 +10,8 @@ pub trait GraphTopology { fn vertex_count(&self) -> usize; fn edge_count(&self) -> usize; + fn vertex_capacity(&self) -> usize; + fn vertex_map(&self, default: T) -> VertexMap; fn degree(&self, v: Self::Vertex) -> usize; fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 1884970..d816106 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -8,22 +8,12 @@ fn dijkstra_single_vertex() { let v1 = graph.add_vertex(); let result = algorithms::dijkstra(&graph, v1); assert_eq!( - result.distances.len(), - 1, - "distances count must equal vertex count" - ); - assert_eq!( - result.predecessors.len(), - 1, - "predecessors count must equal vertex count" - ); - assert_eq!( - result.distances[0], + result.distances[v1], Some(0), "unexpected distance of source vertex" ); assert_eq!( - result.predecessors[0], None, + result.predecessors[v1], None, "unexpected predecessor of source vertex", ); } @@ -32,24 +22,23 @@ fn dijkstra_single_vertex() { fn dijkstra_disconnected() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); - graph.add_vertex(); + let v2 = graph.add_vertex(); let result = algorithms::dijkstra(&graph, v1); assert_eq!( - result.distances.len(), - 2, - "distances count must equal vertex count" + result.distances[v1], + Some(0), + "unexpected distance of source vertex" ); assert_eq!( - result.predecessors.len(), - 2, - "predecessors count must equal vertex count" - ); - assert_eq!( - result.distances[1], None, + result.distances[v2], None, "unexpected distance of disconnected vertex" ); assert_eq!( - result.predecessors[0], None, + result.predecessors[v1], None, + "unexpected predecessor of source vertex", + ); + assert_eq!( + result.predecessors[v2], None, "unexpected predecessor of disconnected vertex", ); } @@ -82,26 +71,16 @@ fn dijkstra() { vec![Some(vertices[4])], vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], ]; - assert_eq!( - result.distances.len(), - graph.vertex_count(), - "distances count must equal vertex count" - ); - assert_eq!( - result.predecessors.len(), - graph.vertex_count(), - "predecessors count must equal vertex count" - ); for i in 0..graph.vertex_count() { assert_eq!( - result.distances[i], expected_distances_from_v0[i], + result.distances[vertices[i]], expected_distances_from_v0[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[i]), + expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), "unexpected predecessor {:?} of {:?}", - result.predecessors[i], + result.predecessors[vertices[i]], vertices[i] ); } @@ -113,12 +92,7 @@ fn dijkstra_distances_single_vertex() { let v1 = graph.add_vertex(); let distances = algorithms::dijkstra_distances(&graph, v1); assert_eq!( - distances.len(), - 1, - "distances count must equal vertex count" - ); - assert_eq!( - distances[0], + distances[v1], Some(0), "unexpected distance of source vertex" ); @@ -128,16 +102,16 @@ fn dijkstra_distances_single_vertex() { fn dijkstra_distances_disconnected() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); - graph.add_vertex(); + let v2 = graph.add_vertex(); let distances = algorithms::dijkstra_distances(&graph, v1); assert_eq!( - distances.len(), - 2, - "distances count must equal vertex count" + distances[v1], + Some(0), + "unexpected distance of source vertex" ); assert_eq!( - distances[1], None, - "unexpected distance of disconnected vertex" + distances[v2], None, + "unexpected distance of disconnected vertex", ); } @@ -157,14 +131,9 @@ fn dijkstra_distances() { Some(3), Some(4), ]; - assert_eq!( - distances.len(), - graph.vertex_count(), - "distances count must equal vertex count" - ); for i in 0..graph.vertex_count() { assert_eq!( - distances[i], expected_distances_from_v0[i], + distances[vertices[i]], expected_distances_from_v0[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); -- 2.43.0 From 166b91cdd29d1042ee81295b4916bdf393348795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 6 May 2026 20:28:53 +0200 Subject: [PATCH 063/102] Update Dijkstra's algorithm to use VertexMap --- src/algorithms.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index a5016ce..375e46c 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; use std::collections::BinaryHeap; +use crate::maps::VertexMap; use crate::traits::GraphTopology; #[derive(PartialEq, Eq)] @@ -21,19 +22,18 @@ impl Ord for DistanceOrderedVertex { } } -pub struct DijkstraResult { - pub distances: Vec>, - pub predecessors: Vec>, +pub struct DijkstraResult { + pub distances: VertexMap>, + pub predecessors: VertexMap>, } pub fn dijkstra(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, - G::Vertex: Into, { - let mut predecessors = vec![None; graph.vertex_count()]; + let mut predecessors = graph.vertex_map(None); let distances = dijkstra_impl(graph, source, |neighbor, predecessor| { - predecessors[neighbor.into()] = Some(predecessor); + predecessors[neighbor] = Some(predecessor); }); DijkstraResult { distances, @@ -41,25 +41,27 @@ where } } -pub fn dijkstra_distances(graph: &G, source: G::Vertex) -> Vec> +pub fn dijkstra_distances(graph: &G, source: G::Vertex) -> VertexMap> where G: GraphTopology, - G::Vertex: Into, { dijkstra_impl(graph, source, |_, _| {}) } -// TODO: Replace Vec storage with VertexMap, this should make the bound on G::Vertex here obsolete. -fn dijkstra_impl(graph: &G, source: G::Vertex, mut on_relax: F) -> Vec> +// TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. +fn dijkstra_impl( + graph: &G, + source: G::Vertex, + mut on_relax: F, +) -> VertexMap> where G: GraphTopology, - G::Vertex: Into, F: FnMut(G::Vertex, G::Vertex), { - let mut distances = vec![None; graph.vertex_count()]; + let mut distances = graph.vertex_map(None); let mut heap = BinaryHeap::new(); - distances[source.into()] = Some(0); + distances[source] = Some(0); heap.push(DistanceOrderedVertex { vertex: source, distance: 0, @@ -67,15 +69,14 @@ where while let Some(v) = heap.pop() { for neighbor in graph.neighbors(v.vertex) { - // TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. let edge_weight = 1; - let new_distance = distances[v.vertex.into()].unwrap() + edge_weight; - if match distances[neighbor.into()] { + let new_distance = distances[v.vertex].unwrap() + edge_weight; + if match distances[neighbor] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[neighbor.into()] = Some(new_distance); + distances[neighbor] = Some(new_distance); on_relax(neighbor, v.vertex); heap.push(DistanceOrderedVertex { vertex: neighbor, -- 2.43.0 From 7f91743b02276908c92eb0c52da1f2dd17e0a5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 6 May 2026 23:37:48 +0200 Subject: [PATCH 064/102] Add VertexMap tests for Graph (vertex deletion) --- src/maps.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/maps.rs b/src/maps.rs index 70406d5..0c123bb 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -51,7 +51,8 @@ impl IndexMut for VertexMap { #[cfg(test)] mod tests { use crate::models::append_graph::AppendGraph; - use crate::traits::GraphTopology; + use crate::models::graph::Graph; + use crate::traits::{GraphTopology, GraphTopologyDeletion}; #[test] fn initial_values_are_default() { @@ -119,4 +120,44 @@ mod tests { map.sync(&graph); assert_eq!(map[v], 5); } + + #[test] + fn surviving_vertex_readable_after_delete() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 1; + map[v2] = 2; + graph.delete_vertex(v2); + assert_eq!(map[v1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = Graph::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let capacity_before = graph.vertex_capacity(); + graph.delete_vertex(v2); + map.sync(&graph); + assert_eq!(map.data.len(), capacity_before); + assert_eq!(map[v1], 0); + } + + #[test] + fn reused_slot_returns_old_value() { + let mut graph = Graph::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 vertex v1 sees the old value. Callers must reinitialize stale + // slots after deletion. + assert_eq!(map[v2], 99); + } } -- 2.43.0 From 016cece6263706c30dc198fa675fa9971a6a9731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 00:12:06 +0200 Subject: [PATCH 065/102] Update VertexMap tests to validate both graph models --- src/maps.rs | 117 +++-------------------------- src/testing.rs | 1 + src/testing/vertex_map_testing.rs | 120 ++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 107 deletions(-) create mode 100644 src/testing/vertex_map_testing.rs diff --git a/src/maps.rs b/src/maps.rs index 0c123bb..f79d4a8 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -49,115 +49,18 @@ impl IndexMut for VertexMap { } #[cfg(test)] -mod tests { +mod append_graph_tests { use crate::models::append_graph::AppendGraph; + use crate::traits::GraphTopology; + + crate::vertex_map_tests!(AppendGraph); +} + +#[cfg(test)] +mod graph_tests { use crate::models::graph::Graph; use crate::traits::{GraphTopology, GraphTopologyDeletion}; - #[test] - fn initial_values_are_default() { - let mut graph = AppendGraph::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() { - let mut graph = AppendGraph::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() { - let mut graph = AppendGraph::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() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let v2 = graph.add_vertex(); - map[v2] = 7; - assert_eq!(map[v1], 0); - assert_eq!(map[v2], 7); - } - - #[test] - fn sync_expands_to_new_vertices() { - let mut graph = AppendGraph::new(); - graph.add_vertex(); - let mut map = graph.vertex_map(42); - graph.add_vertex(); - graph.add_vertex(); - assert!( - map.data.len() < graph.vertex_capacity(), - "precondition: map is stale before sync" - ); - map.sync(&graph); - assert_eq!(map.data.len(), graph.vertex_capacity()); - } - - #[test] - fn sync_does_not_overwrite_existing_values() { - let mut graph = AppendGraph::new(); - let v = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v] = 5; - graph.add_vertex(); - map.sync(&graph); - assert_eq!(map[v], 5); - } - - #[test] - fn surviving_vertex_readable_after_delete() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 1; - map[v2] = 2; - graph.delete_vertex(v2); - assert_eq!(map[v1], 1); - } - - #[test] - fn capacity_does_not_shrink_after_delete() { - let mut graph = Graph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let capacity_before = graph.vertex_capacity(); - graph.delete_vertex(v2); - map.sync(&graph); - assert_eq!(map.data.len(), capacity_before); - assert_eq!(map[v1], 0); - } - - #[test] - fn reused_slot_returns_old_value() { - let mut graph = Graph::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 vertex v1 sees the old value. Callers must reinitialize stale - // slots after deletion. - assert_eq!(map[v2], 99); - } + crate::vertex_map_tests!(Graph); + crate::vertex_map_deletion_tests!(Graph); } diff --git a/src/testing.rs b/src/testing.rs index ecf3963..7975b1f 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1 +1,2 @@ pub(crate) mod graph_topology_testing; +pub(crate) mod vertex_map_testing; diff --git a/src/testing/vertex_map_testing.rs b/src/testing/vertex_map_testing.rs new file mode 100644 index 0000000..e97df88 --- /dev/null +++ b/src/testing/vertex_map_testing.rs @@ -0,0 +1,120 @@ +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_tests { + ($T:ty) => { + #[test] + fn initial_values_are_default() { + 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() { + 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() { + 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() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let v2 = graph.add_vertex(); + map[v2] = 7; + assert_eq!(map[v1], 0); + assert_eq!(map[v2], 7); + } + + #[test] + fn sync_expands_to_new_vertices() { + let mut graph = <$T>::new(); + graph.add_vertex(); + let mut map = graph.vertex_map(42); + let initial_len = map.data.len(); + while graph.vertex_capacity() <= initial_len { + graph.add_vertex(); + } + assert!( + map.data.len() < graph.vertex_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.data.len(), graph.vertex_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.add_vertex(); + map.sync(&graph); + assert_eq!(map[v], 5); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_deletion_tests { + ($T:ty) => { + #[test] + fn surviving_vertex_readable_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 1; + map[v2] = 2; + graph.delete_vertex(v2); + assert_eq!(map[v1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let capacity_before = graph.vertex_capacity(); + graph.delete_vertex(v2); + map.sync(&graph); + assert_eq!(map.data.len(), capacity_before); + assert_eq!(map[v1], 0); + } + + #[test] + fn reused_slot_returns_old_value() { + 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); + } + }; +} -- 2.43.0 From e074fdf944781d02f6e7babe598478a6062a085f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 11:38:15 +0200 Subject: [PATCH 066/102] Move edge_count() in method order --- src/models/append_graph.rs | 8 ++++---- src/models/graph.rs | 8 ++++---- src/traits.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 83f0127..816e6af 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -80,10 +80,6 @@ impl GraphTopology for AppendGraph { self.vertices.len() } - fn edge_count(&self) -> usize { - self.incidences.len() / 2 - } - fn vertex_capacity(&self) -> usize { self.vertices.len() } @@ -92,6 +88,10 @@ impl GraphTopology for AppendGraph { VertexMap::new(default, |v| v.0, self.vertex_capacity()) } + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v.0].incidence_count } diff --git a/src/models/graph.rs b/src/models/graph.rs index d69372a..8970e24 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -159,10 +159,6 @@ impl GraphTopology for Graph { self.vertices.len() } - fn edge_count(&self) -> usize { - self.incidences.len() / 2 - } - fn vertex_capacity(&self) -> usize { self.vertices.capacity() } @@ -171,6 +167,10 @@ impl GraphTopology for Graph { VertexMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) } + fn edge_count(&self) -> usize { + self.incidences.len() / 2 + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].incidence_count } diff --git a/src/traits.rs b/src/traits.rs index 515c006..16c7129 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -9,9 +9,9 @@ pub trait GraphTopology { type Edge: Copy + Eq; fn vertex_count(&self) -> usize; - fn edge_count(&self) -> usize; fn vertex_capacity(&self) -> usize; fn vertex_map(&self, default: T) -> VertexMap; + fn edge_count(&self) -> usize; fn degree(&self, v: Self::Vertex) -> usize; fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; -- 2.43.0 From 6fe3acb306fbc8c6b01e81881accc3c47bd01fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 16:03:17 +0200 Subject: [PATCH 067/102] Add EdgeMap and private generic EntityMap, update tests --- src/maps.rs | 108 +++++++++++-- src/models/append_graph.rs | 10 +- src/models/graph.rs | 10 +- src/testing.rs | 2 +- src/testing/maps_testing.rs | 261 ++++++++++++++++++++++++++++++ src/testing/vertex_map_testing.rs | 120 -------------- src/traits.rs | 4 +- 7 files changed, 375 insertions(+), 140 deletions(-) create mode 100644 src/testing/maps_testing.rs delete mode 100644 src/testing/vertex_map_testing.rs diff --git a/src/maps.rs b/src/maps.rs index f79d4a8..161fca5 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -3,25 +3,18 @@ use std::ops::{Index, IndexMut}; use crate::traits::GraphTopology; pub struct VertexMap { - data: Vec, - default: T, - to_index: fn(V) -> usize, + inner: EntityMap, } impl VertexMap { pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self { Self { - data: vec![default.clone(); capacity], - default, - to_index, + inner: EntityMap::new(default, to_index, capacity), } } pub fn sync>(&mut self, graph: &G) { - let capacity = graph.vertex_capacity(); - if capacity > self.data.len() { - self.data.resize(capacity, self.default.clone()); - } + self.inner.resize(graph.vertex_capacity()); } } @@ -29,7 +22,73 @@ impl Index for VertexMap { type Output = T; fn index(&self, v: V) -> &T { - let i = (self.to_index)(v); + &self.inner[v] + } +} + +impl IndexMut for VertexMap { + fn index_mut(&mut self, v: V) -> &mut T { + &mut self.inner[v] + } +} + +pub struct EdgeMap { + inner: EntityMap, +} + +impl EdgeMap { + pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { + Self { + inner: EntityMap::new(default, to_index, capacity), + } + } + + pub fn sync>(&mut self, graph: &G) { + self.inner.resize(graph.edge_capacity()); + } +} + +impl Index for EdgeMap { + type Output = T; + + fn index(&self, e: E) -> &T { + &self.inner[e] + } +} + +impl IndexMut for EdgeMap { + fn index_mut(&mut self, e: E) -> &mut T { + &mut self.inner[e] + } +} + +struct EntityMap { + data: Vec, + default: T, + to_index: fn(E) -> usize, +} + +impl EntityMap { + pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { + Self { + data: vec![default.clone(); capacity], + default, + to_index, + } + } + + pub fn resize(&mut self, capacity: usize) { + if capacity > self.data.len() { + self.data.resize(capacity, self.default.clone()); + } + } +} + +impl Index for EntityMap { + type Output = T; + + fn index(&self, e: E) -> &T { + let i = (self.to_index)(e); if i < self.data.len() { &self.data[i] } else { @@ -38,9 +97,9 @@ impl Index for VertexMap { } } -impl IndexMut for VertexMap { - fn index_mut(&mut self, v: V) -> &mut T { - let i = (self.to_index)(v); +impl IndexMut for EntityMap { + 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()); } @@ -49,7 +108,7 @@ impl IndexMut for VertexMap { } #[cfg(test)] -mod append_graph_tests { +mod append_graph_vertex_map_tests { use crate::models::append_graph::AppendGraph; use crate::traits::GraphTopology; @@ -57,10 +116,27 @@ mod append_graph_tests { } #[cfg(test)] -mod graph_tests { +mod append_graph_edge_map_tests { + use crate::models::append_graph::AppendGraph; + use crate::traits::GraphTopology; + + crate::edge_map_tests!(AppendGraph); +} + +#[cfg(test)] +mod graph_vertex_map_tests { use crate::models::graph::Graph; use crate::traits::{GraphTopology, GraphTopologyDeletion}; crate::vertex_map_tests!(Graph); crate::vertex_map_deletion_tests!(Graph); } + +#[cfg(test)] +mod graph_edge_map_tests { + use crate::models::graph::Graph; + use crate::traits::{GraphTopology, GraphTopologyDeletion}; + + crate::edge_map_tests!(Graph); + crate::edge_map_deletion_tests!(Graph); +} diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 816e6af..cd4d92a 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,4 +1,4 @@ -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; use crate::traits::GraphTopology; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -92,6 +92,14 @@ impl GraphTopology for AppendGraph { self.incidences.len() / 2 } + fn edge_capacity(&self) -> usize { + self.incidences.len() / 2 + } + + fn edge_map(&self, default: T) -> EdgeMap { + EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v.0].incidence_count } diff --git a/src/models/graph.rs b/src/models/graph.rs index 8970e24..41c7f95 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,6 +1,6 @@ use typed_generational_arena::{Arena, Index}; -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; use crate::traits::{GraphTopology, GraphTopologyDeletion}; type Vertex = Index; @@ -171,6 +171,14 @@ impl GraphTopology for Graph { self.incidences.len() / 2 } + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } + + fn edge_map(&self, default: T) -> EdgeMap { + EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].incidence_count } diff --git a/src/testing.rs b/src/testing.rs index 7975b1f..6dccf6c 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,2 +1,2 @@ pub(crate) mod graph_topology_testing; -pub(crate) mod vertex_map_testing; +pub(crate) mod maps_testing; diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs new file mode 100644 index 0000000..bc73c6f --- /dev/null +++ b/src/testing/maps_testing.rs @@ -0,0 +1,261 @@ +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_tests { + ($T:ty) => { + #[test] + fn initial_values_are_default() { + 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() { + 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() { + 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() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let v2 = graph.add_vertex(); + map[v2] = 7; + assert_eq!(map[v1], 0); + assert_eq!(map[v2], 7); + } + + #[test] + fn sync_expands_to_new_vertices() { + let mut graph = <$T>::new(); + graph.add_vertex(); + let mut map = graph.vertex_map(42); + let initial_len = map.inner.data.len(); + while graph.vertex_capacity() <= initial_len { + graph.add_vertex(); + } + assert!( + map.inner.data.len() < graph.vertex_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.inner.data.len(), graph.vertex_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.add_vertex(); + map.sync(&graph); + assert_eq!(map[v], 5); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_deletion_tests { + ($T:ty) => { + #[test] + fn surviving_vertex_readable_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 1; + map[v2] = 2; + graph.delete_vertex(v2); + assert_eq!(map[v1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 5; + let capacity_before = graph.vertex_capacity(); + graph.delete_vertex(v2); + map.sync(&graph); + assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map[v1], 5); + } + + #[test] + fn reused_slot_returns_old_value() { + 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); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! edge_map_tests { + ($T:ty) => { + #[test] + fn initial_values_are_default() { + 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() { + 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() { + 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() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + let e2 = graph.add_edge(v1, v2); + map[e2] = 7; + assert_eq!(map[e1], 0); + assert_eq!(map[e2], 7); + } + + #[test] + fn sync_expands_to_new_edges() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + graph.add_edge(v1, v2); + let mut map = graph.edge_map(42); + let initial_len = map.inner.data.len(); + while graph.edge_capacity() <= initial_len { + graph.add_edge(v1, v2); + } + assert!( + map.inner.data.len() < graph.edge_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.inner.data.len(), graph.edge_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e] = 5; + graph.add_edge(v1, v2); + map.sync(&graph); + assert_eq!(map[e], 5); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! edge_map_deletion_tests { + ($T:ty) => { + #[test] + fn surviving_edge_readable_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 1; + map[e2] = 2; + graph.delete_edge(e2); + assert_eq!(map[e1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 5; + let capacity_before = graph.edge_capacity(); + graph.delete_edge(e2); + map.sync(&graph); + assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map[e1], 5); + } + + #[test] + fn reused_slot_returns_old_value() { + 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); + } + }; +} diff --git a/src/testing/vertex_map_testing.rs b/src/testing/vertex_map_testing.rs deleted file mode 100644 index e97df88..0000000 --- a/src/testing/vertex_map_testing.rs +++ /dev/null @@ -1,120 +0,0 @@ -#[cfg(test)] -#[macro_export] -macro_rules! vertex_map_tests { - ($T:ty) => { - #[test] - fn initial_values_are_default() { - 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() { - 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() { - 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() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let v2 = graph.add_vertex(); - map[v2] = 7; - assert_eq!(map[v1], 0); - assert_eq!(map[v2], 7); - } - - #[test] - fn sync_expands_to_new_vertices() { - let mut graph = <$T>::new(); - graph.add_vertex(); - let mut map = graph.vertex_map(42); - let initial_len = map.data.len(); - while graph.vertex_capacity() <= initial_len { - graph.add_vertex(); - } - assert!( - map.data.len() < graph.vertex_capacity(), - "precondition: map is stale before sync" - ); - map.sync(&graph); - assert_eq!(map.data.len(), graph.vertex_capacity()); - } - - #[test] - fn sync_does_not_overwrite_existing_values() { - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v] = 5; - graph.add_vertex(); - map.sync(&graph); - assert_eq!(map[v], 5); - } - }; -} - -#[cfg(test)] -#[macro_export] -macro_rules! vertex_map_deletion_tests { - ($T:ty) => { - #[test] - fn surviving_vertex_readable_after_delete() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 1; - map[v2] = 2; - graph.delete_vertex(v2); - assert_eq!(map[v1], 1); - } - - #[test] - fn capacity_does_not_shrink_after_delete() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let capacity_before = graph.vertex_capacity(); - graph.delete_vertex(v2); - map.sync(&graph); - assert_eq!(map.data.len(), capacity_before); - assert_eq!(map[v1], 0); - } - - #[test] - fn reused_slot_returns_old_value() { - 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); - } - }; -} diff --git a/src/traits.rs b/src/traits.rs index 16c7129..9964ad0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add iterator of incident edges for a vertex. @@ -12,6 +12,8 @@ pub trait GraphTopology { fn vertex_capacity(&self) -> usize; fn vertex_map(&self, default: T) -> VertexMap; fn edge_count(&self) -> usize; + fn edge_capacity(&self) -> usize; + fn edge_map(&self, default: T) -> EdgeMap; fn degree(&self, v: Self::Vertex) -> usize; fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; -- 2.43.0 From 726e7691ea9db42e99862bf1a42d6c9b00d7097c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 17:15:34 +0200 Subject: [PATCH 068/102] Add weight parameter for Dijkstra's algorithm, add unweighted variants (unused) --- src/algorithms.rs | 37 +++++++++++++++++++++++++++++++------ tests/dijkstra.rs | 12 ++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 375e46c..dc40241 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -27,12 +27,13 @@ pub struct DijkstraResult { pub predecessors: VertexMap>, } -pub fn dijkstra(graph: &G, source: G::Vertex) -> DijkstraResult +pub fn dijkstra(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult where G: GraphTopology, + W: Fn(G::Edge) -> u32, { let mut predecessors = graph.vertex_map(None); - let distances = dijkstra_impl(graph, source, |neighbor, predecessor| { + let distances = dijkstra_impl(graph, source, weight, |neighbor, predecessor| { predecessors[neighbor] = Some(predecessor); }); DijkstraResult { @@ -41,21 +42,44 @@ where } } -pub fn dijkstra_distances(graph: &G, source: G::Vertex) -> VertexMap> +pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, { - dijkstra_impl(graph, source, |_, _| {}) + dijkstra(graph, source, |_| 1) } -// TODO: Add a way to provide custom edge weights for Dijkstra's algorithm. -fn dijkstra_impl( +pub fn dijkstra_distances( graph: &G, source: G::Vertex, + weight: W, +) -> VertexMap> +where + G: GraphTopology, + W: Fn(G::Edge) -> u32, +{ + dijkstra_impl(graph, source, weight, |_, _| {}) +} + +pub fn dijkstra_distances_unweighted( + graph: &G, + source: G::Vertex, +) -> VertexMap> +where + G: GraphTopology, +{ + dijkstra_distances(graph, source, |_| 1) +} + +fn dijkstra_impl( + graph: &G, + source: G::Vertex, + weight: W, mut on_relax: F, ) -> VertexMap> where G: GraphTopology, + W: Fn(G::Edge) -> u32, F: FnMut(G::Vertex, G::Vertex), { let mut distances = graph.vertex_map(None); @@ -69,6 +93,7 @@ where while let Some(v) = heap.pop() { for neighbor in graph.neighbors(v.vertex) { + // TODO: Use custom edge weights in Dijkstra's algorithm. let edge_weight = 1; let new_distance = distances[v.vertex].unwrap() + edge_weight; if match distances[neighbor] { diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index d816106..6b45023 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -6,7 +6,7 @@ use grapherity::traits::GraphTopology; fn dijkstra_single_vertex() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); - let result = algorithms::dijkstra(&graph, v1); + let result = algorithms::dijkstra_unweighted(&graph, v1); assert_eq!( result.distances[v1], Some(0), @@ -23,7 +23,7 @@ fn dijkstra_disconnected() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); - let result = algorithms::dijkstra(&graph, v1); + let result = algorithms::dijkstra_unweighted(&graph, v1); assert_eq!( result.distances[v1], Some(0), @@ -46,7 +46,7 @@ fn dijkstra_disconnected() { #[test] fn dijkstra() { let (graph, vertices) = make_test_graph(); - let result = algorithms::dijkstra(&graph, vertices[0]); + let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), Some(1), @@ -90,7 +90,7 @@ fn dijkstra() { fn dijkstra_distances_single_vertex() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); - let distances = algorithms::dijkstra_distances(&graph, v1); + let distances = algorithms::dijkstra_distances_unweighted(&graph, v1); assert_eq!( distances[v1], Some(0), @@ -103,7 +103,7 @@ fn dijkstra_distances_disconnected() { let mut graph = AppendGraph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); - let distances = algorithms::dijkstra_distances(&graph, v1); + let distances = algorithms::dijkstra_distances_unweighted(&graph, v1); assert_eq!( distances[v1], Some(0), @@ -118,7 +118,7 @@ fn dijkstra_distances_disconnected() { #[test] fn dijkstra_distances() { let (graph, vertices) = make_test_graph(); - let distances = algorithms::dijkstra_distances(&graph, vertices[0]); + let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); let expected_distances_from_v0 = [ Some(0), Some(1), -- 2.43.0 From a7be995e34a43401c8f7c86fed527a8c3a0a675b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 17:34:20 +0200 Subject: [PATCH 069/102] Change "neighbor" terminology to "adjacent vertex" throughout the code --- src/algorithms.rs | 14 +++---- src/models/append_graph.rs | 16 ++++---- src/models/graph.rs | 34 ++++++++-------- src/testing/graph_topology_testing.rs | 56 +++++++++++++-------------- src/traits.rs | 2 +- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index dc40241..0a7a963 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -33,8 +33,8 @@ where W: Fn(G::Edge) -> u32, { let mut predecessors = graph.vertex_map(None); - let distances = dijkstra_impl(graph, source, weight, |neighbor, predecessor| { - predecessors[neighbor] = Some(predecessor); + let distances = dijkstra_impl(graph, source, weight, |adjacent, predecessor| { + predecessors[adjacent] = Some(predecessor); }); DijkstraResult { distances, @@ -92,19 +92,19 @@ where }); while let Some(v) = heap.pop() { - for neighbor in graph.neighbors(v.vertex) { + for adjacent in graph.adjacent_vertices(v.vertex) { // TODO: Use custom edge weights in Dijkstra's algorithm. let edge_weight = 1; let new_distance = distances[v.vertex].unwrap() + edge_weight; - if match distances[neighbor] { + if match distances[adjacent] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[neighbor] = Some(new_distance); - on_relax(neighbor, v.vertex); + distances[adjacent] = Some(new_distance); + on_relax(adjacent, v.vertex); heap.push(DistanceOrderedVertex { - vertex: neighbor, + vertex: adjacent, distance: new_distance, }); } diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index cd4d92a..8716167 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -20,22 +20,22 @@ struct VertexIncidenceHeader { struct IncidenceEntry { next: Option, - neighbor: Vertex, + adjacent: Vertex, } -struct VertexNeighborIterator<'a> { +struct VertexAdjacenceIterator<'a> { graph: &'a AppendGraph, incidence: Option, } -impl<'a> Iterator for VertexNeighborIterator<'a> { +impl<'a> Iterator for VertexAdjacenceIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { let incidence = self.incidence?; let entry = &self.graph.incidences[incidence.0]; self.incidence = entry.next; - Some(entry.neighbor) + Some(entry.adjacent) } } @@ -58,7 +58,7 @@ impl AppendGraph { fn add_incidence(&mut self, v1: Vertex, v2: Vertex) { self.incidences.push(IncidenceEntry { next: self.vertices[v1.0].first_incidence.take(), - neighbor: v2, + adjacent: v2, }); self.vertices[v1.0].incidence_count += 1; self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); @@ -105,15 +105,15 @@ impl GraphTopology for AppendGraph { } fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.neighbors(v1).any(|x| x == v2) + self.adjacent_vertices(v1).any(|x| x == v2) } fn vertices(&self) -> impl Iterator { (0..self.vertices.len()).map(Vertex) } - fn neighbors(&self, v: Self::Vertex) -> impl Iterator { - VertexNeighborIterator { + fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { + VertexAdjacenceIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } diff --git a/src/models/graph.rs b/src/models/graph.rs index 41c7f95..4fd3b62 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -20,15 +20,15 @@ pub struct VertexIncidenceHeader { pub struct IncidenceEntry { next: Option, - neighbor: VertexSlot, + adjacent: VertexSlot, } -struct VertexNeighborIterator<'a> { +struct VertexAdjacenceIterator<'a> { graph: &'a Graph, incidence: Option, } -impl<'a> Iterator for VertexNeighborIterator<'a> { +impl<'a> Iterator for VertexAdjacenceIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { @@ -37,7 +37,7 @@ impl<'a> Iterator for VertexNeighborIterator<'a> { let index = self.graph.incidences.get_idx(incidence.0)?; let entry = &self.graph.incidences[index]; self.incidence = entry.next; - self.graph.vertices.get_idx(entry.neighbor.0) + self.graph.vertices.get_idx(entry.adjacent.0) } } @@ -89,7 +89,7 @@ impl Graph { fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge { let edge = self.incidences.insert(IncidenceEntry { next: self.vertices[v1].first_incidence.take(), - neighbor: VertexSlot(v2.arr_idx()), + adjacent: VertexSlot(v2.arr_idx()), }); self.vertices[v1].incidence_count += 1; self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx())); @@ -124,12 +124,12 @@ impl Graph { let source_vertex = self .vertices .get_idx(source.0) - .expect("missing incident neighbor, corrupt internal data state"); + .expect("missing incident vertex, corrupt internal data state"); let vertex_header = &mut self.vertices[source_vertex]; vertex_header.incidence_count -= if is_loop { 2 } else { 1 }; let first = vertex_header .first_incidence - .expect("incident neighbor without incidences, corrupt internal data state"); + .expect("incident vertex without incidences, corrupt internal data state"); if first.0 == e.arr_idx() { vertex_header.first_incidence = next; } else { @@ -184,15 +184,15 @@ impl GraphTopology for Graph { } fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool { - self.neighbors(v1).any(|x| x == v2) + self.adjacent_vertices(v1).any(|x| x == v2) } fn vertices(&self) -> impl Iterator { self.vertices.iter().map(|(i, _)| i) } - fn neighbors(&self, v: Self::Vertex) -> impl Iterator { - VertexNeighborIterator { + fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { + VertexAdjacenceIterator { graph: self, incidence: self.vertices[v].first_incidence, } @@ -233,8 +233,8 @@ impl GraphTopologyDeletion for Graph { // Since v is being deleted, there are no update_incidence_list() calls for e, no need // to fix v's incidence list. let (f, e_entry, f_entry) = self.remove_incidence_pair(e); - if e_entry.neighbor != f_entry.neighbor { - self.update_incidence_list(f, e_entry.neighbor, f_entry.next, false); + if e_entry.adjacent != f_entry.adjacent { + self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false); } else { cursor.incidence = f_entry.next; } @@ -246,13 +246,13 @@ impl GraphTopologyDeletion for Graph { // the predecessor, never the removed entries themselves. fn delete_edge(&mut self, e: Self::Edge) { let (f, e_entry, f_entry) = self.remove_incidence_pair(e); - if e_entry.neighbor != f_entry.neighbor { - self.update_incidence_list(e, f_entry.neighbor, e_entry.next, false); - self.update_incidence_list(f, e_entry.neighbor, f_entry.next, false); + 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) { - self.update_incidence_list(f, e_entry.neighbor, e_entry.next, true); + self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true); } else { - self.update_incidence_list(e, e_entry.neighbor, f_entry.next, true); + self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true); } } } diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 51131ae..6316fe6 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -192,26 +192,26 @@ macro_rules! graph_topology_tests { } #[test] - fn neighbors_empty() { + fn adjacent_vertices_empty() { let mut graph = <$T>::new(); let vertex = graph.add_vertex(); assert_eq!( - graph.neighbors(vertex).count(), + graph.adjacent_vertices(vertex).count(), 0, - "neighbor iterator of vertex with degree 0 should have no elements" + "adjacent vertex iterator of vertex with degree 0 should have no elements" ); } #[test] - fn neighbors() { + fn adjacent_vertices() { let (graph, vertices, _) = make_test_graph(); - // Checks neighbors of vertex 4. + // Checks adjacency of vertex 4. assert_eq!( - graph.neighbors(vertices[4]).count(), + graph.adjacent_vertices(vertices[4]).count(), 7, - "unexpected neighbor count" + "unexpected adjacency count" ); - let mut expected_neighbors = vec![ + let mut expected_adjacency = vec![ vertices[1], vertices[2], vertices[2], @@ -220,21 +220,21 @@ macro_rules! graph_topology_tests { vertices[7], vertices[8], ]; - for v in graph.neighbors(vertices[4]) { - let i = expected_neighbors + for v in graph.adjacent_vertices(vertices[4]) { + let i = expected_adjacency .iter() .position(|w| *w == v) .expect(&format!( - "unexpected neighbor {v:?} of {:?} from the iterator", + "unexpected adjacent vertex {v:?} of {:?} from the iterator", vertices[4] )); - expected_neighbors.swap_remove(i); + expected_adjacency.swap_remove(i); } assert_eq!( - expected_neighbors.len(), + expected_adjacency.len(), 0, - "expected neighbors {:?} of {:?} were not matched by the iterator", - expected_neighbors, + "expected adjacent vertices {:?} of {:?} were not matched by the iterator", + expected_adjacency, vertices[4] ); } @@ -276,18 +276,18 @@ macro_rules! graph_topology_tests { graph.are_adjacent(v, v), "vertex with loop edge should be adjacent to itself" ); - let mut neighbors = graph.neighbors(v); + let mut iter = graph.adjacent_vertices(v); + assert_eq!(iter.next(), Some(v), "vertex should be adjacent to itself"); assert_eq!( - neighbors.next(), + iter.next(), Some(v), - "vertex should be neighbor of itself" + "vertex should be adjacent to itself twice" ); assert_eq!( - neighbors.next(), - Some(v), - "vertex should be neighbor of itself twice" + iter.next(), + None, + "too many adjacent vertices from iterator" ); - assert_eq!(neighbors.next(), None, "too many neighbors from iterator"); } #[test] @@ -312,20 +312,20 @@ macro_rules! graph_topology_tests { "should be adjacent" ); for i in 0..2 { - let mut neighbors = graph.neighbors(vertices[i]); + let mut iter = graph.adjacent_vertices(vertices[i]); for j in 0..k { assert_eq!( - neighbors.next(), + iter.next(), Some(vertices[1 - i]), - "neighbor {j} of vertex {:?} should be {:?}", + "adjacent vertex {j} of vertex {:?} should be {:?}", vertices[i], vertices[1 - i] ); } assert_eq!( - neighbors.next(), + iter.next(), None, - "too many neighbors of {:?} from iterator", + "too many adjacent vertices of {:?} from iterator", vertices[i] ); } @@ -439,7 +439,7 @@ macro_rules! graph_topology_deletion_tests { } for v in [vertices[1], vertices[4], vertices[5], vertices[6]] { assert!( - !graph.neighbors(v).any(|u| u == vertices[2]), + !graph.adjacent_vertices(v).any(|u| u == vertices[2]), "unexpected adjacency of {v:?} to deleted vertex {:?}", vertices[2] ); diff --git a/src/traits.rs b/src/traits.rs index 9964ad0..51f18cf 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -17,7 +17,7 @@ pub trait GraphTopology { fn degree(&self, v: Self::Vertex) -> usize; fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; - fn neighbors(&self, v: Self::Vertex) -> impl Iterator; + fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; fn edges(&self) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; -- 2.43.0 From 4e475732901b19010c09ab25b9bd2d6e497654c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 7 May 2026 18:01:16 +0200 Subject: [PATCH 070/102] Renamed the private graph iterators to attune for upcoming changes --- src/models/append_graph.rs | 6 +++--- src/models/graph.rs | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 8716167..4664703 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -23,12 +23,12 @@ struct IncidenceEntry { adjacent: Vertex, } -struct VertexAdjacenceIterator<'a> { +struct AdjacentVertexIterator<'a> { graph: &'a AppendGraph, incidence: Option, } -impl<'a> Iterator for VertexAdjacenceIterator<'a> { +impl<'a> Iterator for AdjacentVertexIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { @@ -113,7 +113,7 @@ impl GraphTopology for AppendGraph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - VertexAdjacenceIterator { + AdjacentVertexIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } diff --git a/src/models/graph.rs b/src/models/graph.rs index 4fd3b62..1c65694 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -23,50 +23,50 @@ pub struct IncidenceEntry { adjacent: VertexSlot, } -struct VertexAdjacenceIterator<'a> { +struct AdjacentVertexIterator<'a> { graph: &'a Graph, incidence: Option, } -impl<'a> Iterator for VertexAdjacenceIterator<'a> { +impl<'a> Iterator for AdjacentVertexIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { // TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries). let incidence = self.incidence?; - let index = self.graph.incidences.get_idx(incidence.0)?; - let entry = &self.graph.incidences[index]; + let i = self.graph.incidences.get_idx(incidence.0).unwrap(); + let entry = &self.graph.incidences[i]; self.incidence = entry.next; - self.graph.vertices.get_idx(entry.adjacent.0) + Some(self.graph.vertices.get_idx(entry.adjacent.0).unwrap()) } } -struct VertexIncidenceIterator<'a> { +struct IncidentEdgeIterator<'a> { graph: &'a Graph, incidence: Option, } -impl<'a> Iterator for VertexIncidenceIterator<'a> { +impl<'a> Iterator for IncidentEdgeIterator<'a> { type Item = Edge; fn next(&mut self) -> Option { let current = self.incidence?; - let index = self.graph.incidences.get_idx(current.0)?; - self.incidence = self.graph.incidences[index].next; - Some(index) + let e = self.graph.incidences.get_idx(current.0).unwrap(); + self.incidence = self.graph.incidences[e].next; + Some(e) } } -struct VertexIncidenceCursor { +struct IncidentEdgeCursor { incidence: Option, } -impl VertexIncidenceCursor { +impl IncidentEdgeCursor { fn next(&mut self, graph: &Graph) -> Option { let current = self.incidence?; - let index = graph.incidences.get_idx(current.0)?; - self.incidence = graph.incidences[index].next; - Some(index) + let e = graph.incidences.get_idx(current.0).unwrap(); + self.incidence = graph.incidences[e].next; + Some(e) } } @@ -133,7 +133,7 @@ impl Graph { if first.0 == e.arr_idx() { vertex_header.first_incidence = next; } else { - let previous = VertexIncidenceIterator { + let previous = IncidentEdgeIterator { graph: self, incidence: self.vertices[source_vertex].first_incidence, } @@ -192,7 +192,7 @@ impl GraphTopology for Graph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - VertexAdjacenceIterator { + AdjacentVertexIterator { graph: self, incidence: self.vertices[v].first_incidence, } @@ -226,7 +226,7 @@ impl GraphTopologyDeletion for Graph { .vertices .remove(v) .expect("attempt to delete an invalid vertex"); - let mut cursor = VertexIncidenceCursor { + let mut cursor = IncidentEdgeCursor { incidence: v_header.first_incidence, }; while let Some(e) = cursor.next(self) { -- 2.43.0 From d3fcd690a56f1823359ab50bc528caf61f544ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 12:57:16 +0200 Subject: [PATCH 071/102] Remove deprecated From for usize implementation --- src/models/append_graph.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 4664703..6ac77db 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -4,12 +4,6 @@ use crate::traits::GraphTopology; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Vertex(usize); -impl From for usize { - fn from(v: Vertex) -> usize { - v.0 - } -} - #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Incidence(usize); -- 2.43.0 From 0bcd270d12bfe6dbb8f413cbd57c9d47ce6357fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 13:12:23 +0200 Subject: [PATCH 072/102] Add GraphTopology::incidences(), update and add related tests --- src/models/append_graph.rs | 33 ++- src/models/graph.rs | 29 ++ src/testing/graph_topology_testing.rs | 378 +++++++++++++++++++++----- src/traits.rs | 1 + 4 files changed, 378 insertions(+), 63 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 6ac77db..02f6b5f 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -7,6 +7,12 @@ pub struct Vertex(usize); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Incidence(usize); +impl Incidence { + fn normalize(&self) -> Self { + Self(self.0 & !1) + } +} + struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, @@ -26,13 +32,29 @@ impl<'a> Iterator for AdjacentVertexIterator<'a> { type Item = Vertex; fn next(&mut self) -> Option { - let incidence = self.incidence?; - let entry = &self.graph.incidences[incidence.0]; + let current = self.incidence?; + let entry = &self.graph.incidences[current.0]; self.incidence = entry.next; Some(entry.adjacent) } } +struct IncidenceIterator<'a> { + graph: &'a AppendGraph, + incidence: Option, +} + +impl<'a> Iterator for IncidenceIterator<'a> { + type Item = (Vertex, Incidence); + + fn next(&mut self) -> Option { + let current = self.incidence?; + let entry = &self.graph.incidences[current.0]; + self.incidence = entry.next; + Some((entry.adjacent, current.normalize())) + } +} + pub struct AppendGraph { // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? vertices: Vec, @@ -117,6 +139,13 @@ impl GraphTopology for AppendGraph { (0..self.incidences.len()).step_by(2).map(Incidence) } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + IncidenceIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.push(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/models/graph.rs b/src/models/graph.rs index 1c65694..eddaa5c 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -70,6 +70,24 @@ impl IncidentEdgeCursor { } } +struct IncidenceIterator<'a> { + graph: &'a Graph, + incidence: Option, +} + +impl<'a> Iterator for IncidenceIterator<'a> { + type Item = (Vertex, Edge); + + fn next(&mut self) -> Option { + let current = self.incidence?; + let e = self.graph.incidences.get_idx(current.0).unwrap(); + let entry = &self.graph.incidences[e]; + self.incidence = entry.next; + let v = self.graph.vertices.get_idx(entry.adjacent.0).unwrap(); + Some((v, self.graph.normalize_edge(e))) + } +} + pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -142,6 +160,10 @@ impl Graph { self.incidences[previous].next = next; } } + + fn normalize_edge(&self, e: Edge) -> Edge { + self.incidences.get_idx(e.arr_idx() & !1).unwrap() + } } impl Default for Graph { @@ -205,6 +227,13 @@ impl GraphTopology for Graph { .map(|(i, _)| i) } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { + IncidenceIterator { + graph: self, + incidence: self.vertices[v].first_incidence, + } + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 6316fe6..6ecd67e 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -6,6 +6,10 @@ macro_rules! graph_topology_test_fixtures { $T, [<$T as $crate::traits::GraphTopology>::Vertex; 10], [<$T as $crate::traits::GraphTopology>::Edge; 18], + [Vec<( + <$T as $crate::traits::GraphTopology>::Vertex, + <$T as $crate::traits::GraphTopology>::Edge, + )>; 10], ) { let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = @@ -30,7 +34,53 @@ macro_rules! graph_topology_test_fixtures { graph.add_edge(vertices[7], vertices[8]), graph.add_edge(vertices[7], vertices[9]), ]; - (graph, vertices, edges) + let incidences: [Vec<_>; 10] = [ + vec![(vertices[1], edges[0]), (vertices[1], edges[1])], + vec![ + (vertices[0], edges[0]), + (vertices[0], edges[1]), + (vertices[2], edges[2]), + (vertices[3], edges[3]), + (vertices[4], edges[4]), + ], + vec![ + (vertices[2], edges[5]), + (vertices[2], edges[5]), + (vertices[1], edges[2]), + (vertices[4], edges[6]), + (vertices[4], edges[7]), + (vertices[5], edges[8]), + (vertices[6], edges[9]), + ], + vec![(vertices[1], edges[3]), (vertices[6], edges[10])], + vec![ + (vertices[1], edges[4]), + (vertices[2], edges[6]), + (vertices[2], edges[7]), + (vertices[4], edges[11]), + (vertices[4], edges[11]), + (vertices[7], edges[12]), + (vertices[8], edges[13]), + ], + vec![(vertices[2], edges[8]), (vertices[9], edges[14])], + vec![ + (vertices[2], edges[9]), + (vertices[3], edges[10]), + (vertices[9], edges[15]), + ], + vec![ + (vertices[4], edges[12]), + (vertices[8], edges[16]), + (vertices[9], edges[17]), + ], + vec![(vertices[4], edges[13]), (vertices[7], edges[16])], + vec![ + (vertices[5], edges[14]), + (vertices[6], edges[15]), + (vertices[7], edges[17]), + ], + ]; + (graph, vertices, edges, incidences) } }; } @@ -54,7 +104,7 @@ macro_rules! graph_topology_tests { #[test] fn vertex_count() { - let (graph, _, _) = make_test_graph(); + let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } @@ -75,7 +125,7 @@ macro_rules! graph_topology_tests { #[test] fn edge_count() { - let (graph, _, _) = make_test_graph(); + let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } @@ -88,7 +138,7 @@ macro_rules! graph_topology_tests { #[test] fn degree() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { assert_eq!( @@ -100,6 +150,36 @@ macro_rules! graph_topology_tests { } } + #[test] + fn loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + graph.add_edge(v, v); + assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); + assert_eq!(graph.edge_count(), 1, "unexpected edge count"); + assert_eq!(graph.degree(v), 2, "unexpected degree"); + assert!( + graph.are_adjacent(v, v), + "vertex with loop edge should be adjacent to itself" + ); + } + + #[test] + fn multiple_edges() { + let k = 3; + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + for _ in 0..k { + graph.add_edge(v1, v2); + } + assert_eq!(graph.vertex_count(), 2, "unexpected vertex count"); + assert_eq!(graph.edge_count(), k, "unexpected edge count"); + assert_eq!(graph.degree(v1), k, "unexpected degree of vertex {v1:?}"); + assert_eq!(graph.degree(v2), k, "unexpected degree of vertex {v2:?}"); + assert!(graph.are_adjacent(v1, v2), "should be adjacent"); + } + #[test] fn are_adjacent_vertex_self() { let mut graph = <$T>::new(); @@ -124,7 +204,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); assert!( graph.are_adjacent(vertices[0], vertices[1]), "expected {:?} and {:?} to be adjacent", @@ -178,7 +258,7 @@ macro_rules! graph_topology_tests { #[test] fn vertices() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); // Expects each vertex to appear exactly once. @@ -194,9 +274,9 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices_empty() { let mut graph = <$T>::new(); - let vertex = graph.add_vertex(); + let v = graph.add_vertex(); assert_eq!( - graph.adjacent_vertices(vertex).count(), + graph.adjacent_vertices(v).count(), 0, "adjacent vertex iterator of vertex with degree 0 should have no elements" ); @@ -204,7 +284,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices() { - let (graph, vertices, _) = make_test_graph(); + let (graph, vertices, _, _) = make_test_graph(); // Checks adjacency of vertex 4. assert_eq!( graph.adjacent_vertices(vertices[4]).count(), @@ -240,42 +320,10 @@ macro_rules! graph_topology_tests { } #[test] - fn edges_empty() { - let graph = <$T>::new(); - assert_eq!( - graph.edges().count(), - 0, - "edge iterator of empty graph should have no elements" - ); - } - - #[test] - fn edges() { - let (graph, _, edges) = make_test_graph(); - assert_eq!(graph.edges().count(), 18, "unexpected edge count"); - - // Expects each edge to appear exactly once. - for e in graph.edges() { - assert_eq!( - edges.iter().filter(|&x| *x == e).count(), - 1, - "unexpected edge {e:?} from the iterator" - ); - } - } - - #[test] - fn loop_edge() { + fn adjacent_vertices_loop_edge() { let mut graph = <$T>::new(); let v = graph.add_vertex(); graph.add_edge(v, v); - assert_eq!(graph.vertex_count(), 1, "unexpected vertex count"); - assert_eq!(graph.edge_count(), 1, "unexpected edge count"); - assert_eq!(graph.degree(v), 2, "unexpected degree"); - assert!( - graph.are_adjacent(v, v), - "vertex with loop edge should be adjacent to itself" - ); let mut iter = graph.adjacent_vertices(v); assert_eq!(iter.next(), Some(v), "vertex should be adjacent to itself"); assert_eq!( @@ -291,35 +339,172 @@ macro_rules! graph_topology_tests { } #[test] - fn multiple_edges() { + fn adjacent_vertices_multiple_edges() { let k = 3; let mut graph = <$T>::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; for _ in 0..k { graph.add_edge(vertices[0], vertices[1]); } - assert_eq!( - graph.vertex_count(), - vertices.len(), - "unexpected vertex count" - ); - assert_eq!(graph.edge_count(), k, "unexpected edge count"); - for v in vertices { - assert_eq!(graph.degree(v), k, "unexpected degree of {v:?}"); - } - assert!( - graph.are_adjacent(vertices[0], vertices[1]), - "should be adjacent" - ); for i in 0..2 { let mut iter = graph.adjacent_vertices(vertices[i]); for j in 0..k { assert_eq!( iter.next(), Some(vertices[1 - i]), - "adjacent vertex {j} of vertex {:?} should be {:?}", + "unexpected adjacent vertex {j} of vertex {:?}", + vertices[i] + ); + } + assert_eq!( + iter.next(), + None, + "too many adjacent vertices of vertex {:?} from iterator", + vertices[i] + ); + } + } + + #[test] + fn edges_empty() { + let graph = <$T>::new(); + assert_eq!( + graph.edges().count(), + 0, + "edge iterator of empty graph should have no elements" + ); + } + + #[test] + fn edges() { + let (graph, _, edges, _) = make_test_graph(); + assert_eq!(graph.edges().count(), 18, "unexpected edge count"); + + // Expects each edge to appear exactly once. + for e in graph.edges() { + assert_eq!( + edges.iter().filter(|&x| *x == e).count(), + 1, + "unexpected edge {e:?} from the iterator" + ); + } + } + + #[test] + fn incidences_empty() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.incidences(v).count(), + 0, + "incidence iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn incidences() { + let (graph, vertices, _, incidences) = make_test_graph(); + for i in 0..10 { + assert_eq!( + graph.incidences(vertices[i]).count(), + incidences[i].len(), + "unexpected incidence count for vertex {:?}", + vertices[i] + ); + let mut remaining = incidences[i].clone(); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} from the iterator", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} were not matched by the iterator", + remaining, + vertices[i] + ); + } + } + + #[test] + fn incidences_edge_consistency() { + // For each incidence (v, e) of u, the same edge e must appear in the incidences of v. + // For loop edges (u == v), the edge must appear exactly twice in the incidences of u. + let (graph, _, _, _) = make_test_graph(); + for u in graph.vertices() { + for (v, e) in graph.incidences(u) { + if u == v { + assert_eq!( + graph.incidences(u).filter(|(_, f)| *f == e).count(), + 2, + "loop edge {e:?} should appear exactly twice in incidences of vertex {u:?}" + ); + } else { + assert!( + graph.incidences(v).any(|(w, f)| w == u && f == e), + "edge {e:?} from incidences of vertex {u:?} missing in incidences of adjacent vertex {v:?}" + ); + } + } + } + } + + #[test] + fn incidences_loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + let mut iter = graph.incidences(v); + assert_eq!( + iter.next(), + Some((v, e)), + "vertex should be adjacent to itself" + ); + assert_eq!( + iter.next(), + Some((v, e)), + "vertex should be adjacent to itself twice" + ); + assert_eq!( + iter.next(), + None, + "too many adjacent vertices from iterator" + ); + } + + #[test] + fn incidences_multiple_edges() { + let k = 3; + let mut graph = <$T>::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + let mut edges = Vec::new(); + for _ in 0..k { + edges.push(graph.add_edge(vertices[0], vertices[1])); + } + for i in 0..2 { + let mut iter = graph.incidences(vertices[i]); + for j in 0..k { + let current = iter.next().expect(&format!( + "incidence {j} missing, expected {k} incidences for vertex {:?}", + vertices[i] + )); + assert_eq!( + current.0, + vertices[1 - i], + "unexpected adjacent vertex of vertex {:?} in incidence {j}", + vertices[i] + ); + assert_eq!( + edges.iter().filter(|e| **e == current.1).count(), + 1, + "unexpected incident edge {:?} of vertex {:?}", + current.1, vertices[i], - vertices[1 - i] ); } assert_eq!( @@ -400,7 +585,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_connected() { - let (mut graph, vertices, _) = make_test_graph(); + let (mut graph, vertices, _, _) = make_test_graph(); assert_eq!( graph.vertex_count(), 10, @@ -587,7 +772,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn vertices_after_delete() { - let (mut graph, vertices, _) = make_test_graph(); + let (mut graph, vertices, _, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!( graph.vertex_count(), @@ -617,7 +802,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn edges_after_delete() { - let (mut graph, vertices, edges) = make_test_graph(); + let (mut graph, vertices, edges, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); assert_eq!( @@ -640,5 +825,76 @@ macro_rules! graph_topology_deletion_tests { ); } } + + #[test] + fn incidences_after_delete_vertex() { + let (mut graph, vertices, _, incidences) = make_test_graph(); + graph.delete_vertex(vertices[2]); + for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { + let mut remaining: Vec<_> = incidences[i] + .iter() + .filter(|(v, _)| *v != vertices[2]) + .cloned() + .collect(); + assert_eq!( + graph.incidences(vertices[i]).count(), + remaining.len(), + "unexpected incidence count for vertex {:?} after delete", + vertices[i] + ); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} after delete", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} not matched after delete", + remaining, + vertices[i] + ); + } + } + + #[test] + fn incidences_after_delete_edge() { + let (mut graph, vertices, edges, incidences) = make_test_graph(); + // Deletes the edge from vertices[1] to vertices[2]. + graph.delete_edge(edges[2]); + for i in 0..10 { + let mut remaining: Vec<_> = incidences[i] + .iter() + .filter(|(_, e)| *e != edges[2]) + .cloned() + .collect(); + assert_eq!( + graph.incidences(vertices[i]).count(), + remaining.len(), + "unexpected incidence count for vertex {:?} after delete", + vertices[i] + ); + for incidence in graph.incidences(vertices[i]) { + let pos = remaining + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} after delete", + vertices[i] + )); + remaining.swap_remove(pos); + } + assert!( + remaining.is_empty(), + "expected incidences {:?} of vertex {:?} not matched after delete", + remaining, + vertices[i] + ); + } + } }; } diff --git a/src/traits.rs b/src/traits.rs index 51f18cf..d38d0ad 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -19,6 +19,7 @@ pub trait GraphTopology { fn vertices(&self) -> impl Iterator; fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; fn edges(&self) -> impl Iterator; + fn incidences(&self, v: Self::Vertex) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } -- 2.43.0 From 0f5f0d67d24437685571cd1d30789e127ea54c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 13:16:23 +0200 Subject: [PATCH 073/102] Update Dijkstra's to use incidences instead of neighbors, and with it custom edge weights --- src/algorithms.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index 0a7a963..01a6c31 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -92,19 +92,17 @@ where }); while let Some(v) = heap.pop() { - for adjacent in graph.adjacent_vertices(v.vertex) { - // TODO: Use custom edge weights in Dijkstra's algorithm. - let edge_weight = 1; - let new_distance = distances[v.vertex].unwrap() + edge_weight; - if match distances[adjacent] { + for incidence in graph.incidences(v.vertex) { + let new_distance = distances[v.vertex].unwrap() + weight(incidence.1); + if match distances[incidence.0] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { - distances[adjacent] = Some(new_distance); - on_relax(adjacent, v.vertex); + distances[incidence.0] = Some(new_distance); + on_relax(incidence.0, v.vertex); heap.push(DistanceOrderedVertex { - vertex: adjacent, + vertex: incidence.0, distance: new_distance, }); } -- 2.43.0 From 2eb054eb0c813c1f543a4a2c17382007b7b03070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 20:40:20 +0200 Subject: [PATCH 074/102] Refactor AppendGraph iterators into one RawIncidenceIterator --- src/models/append_graph.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 02f6b5f..58d5ea8 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -23,35 +23,19 @@ struct IncidenceEntry { adjacent: Vertex, } -struct AdjacentVertexIterator<'a> { +struct RawIncidenceIterator<'a> { graph: &'a AppendGraph, incidence: Option, } -impl<'a> Iterator for AdjacentVertexIterator<'a> { - type Item = Vertex; - - fn next(&mut self) -> Option { - let current = self.incidence?; - let entry = &self.graph.incidences[current.0]; - self.incidence = entry.next; - Some(entry.adjacent) - } -} - -struct IncidenceIterator<'a> { - graph: &'a AppendGraph, - incidence: Option, -} - -impl<'a> Iterator for IncidenceIterator<'a> { +impl<'a> Iterator for RawIncidenceIterator<'a> { type Item = (Vertex, Incidence); fn next(&mut self) -> Option { let current = self.incidence?; let entry = &self.graph.incidences[current.0]; self.incidence = entry.next; - Some((entry.adjacent, current.normalize())) + Some((entry.adjacent, current)) } } @@ -129,10 +113,11 @@ impl GraphTopology for AppendGraph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - AdjacentVertexIterator { + RawIncidenceIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } + .map(|(v, _)| v) } fn edges(&self) -> impl Iterator { @@ -140,10 +125,11 @@ impl GraphTopology for AppendGraph { } fn incidences(&self, v: Self::Vertex) -> impl Iterator { - IncidenceIterator { + RawIncidenceIterator { graph: self, incidence: self.vertices[v.0].first_incidence, } + .map(|(v, e)| (v, e.normalize())) } fn add_vertex(&mut self) -> Self::Vertex { -- 2.43.0 From 85e25ef0ca1586549295fb6af1d3ef210c9bf8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 8 May 2026 20:48:43 +0200 Subject: [PATCH 075/102] Refactor Graph iterator structs into private method used with std::iter::from_fn() --- src/models/graph.rs | 95 ++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 70 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index eddaa5c..fa3262e 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -18,73 +18,19 @@ pub struct VertexIncidenceHeader { first_incidence: Option, } +#[derive(Copy, Clone)] pub struct IncidenceEntry { next: Option, adjacent: VertexSlot, } -struct AdjacentVertexIterator<'a> { - graph: &'a Graph, - incidence: Option, -} - -impl<'a> Iterator for AdjacentVertexIterator<'a> { - type Item = Vertex; - - fn next(&mut self) -> Option { - // TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries). - let incidence = self.incidence?; - let i = self.graph.incidences.get_idx(incidence.0).unwrap(); - let entry = &self.graph.incidences[i]; - self.incidence = entry.next; - Some(self.graph.vertices.get_idx(entry.adjacent.0).unwrap()) - } -} - -struct IncidentEdgeIterator<'a> { - graph: &'a Graph, - incidence: Option, -} - -impl<'a> Iterator for IncidentEdgeIterator<'a> { - type Item = Edge; - - fn next(&mut self) -> Option { - let current = self.incidence?; - let e = self.graph.incidences.get_idx(current.0).unwrap(); - self.incidence = self.graph.incidences[e].next; - Some(e) - } -} - struct IncidentEdgeCursor { incidence: Option, } impl IncidentEdgeCursor { fn next(&mut self, graph: &Graph) -> Option { - let current = self.incidence?; - let e = graph.incidences.get_idx(current.0).unwrap(); - self.incidence = graph.incidences[e].next; - Some(e) - } -} - -struct IncidenceIterator<'a> { - graph: &'a Graph, - incidence: Option, -} - -impl<'a> Iterator for IncidenceIterator<'a> { - type Item = (Vertex, Edge); - - fn next(&mut self) -> Option { - let current = self.incidence?; - let e = self.graph.incidences.get_idx(current.0).unwrap(); - let entry = &self.graph.incidences[e]; - self.incidence = entry.next; - let v = self.graph.vertices.get_idx(entry.adjacent.0).unwrap(); - Some((v, self.graph.normalize_edge(e))) + graph.step_incidence(&mut self.incidence).map(|(_, e)| e) } } @@ -151,16 +97,27 @@ impl Graph { if first.0 == e.arr_idx() { vertex_header.first_incidence = next; } else { - let previous = IncidentEdgeIterator { - graph: self, - incidence: self.vertices[source_vertex].first_incidence, - } - .find(|f| self.incidences[*f].next.is_some_and(|i| i.0 == e.arr_idx())) - .expect("cannot find previous incidence, corrupt internal data state"); + let graph: &Graph = self; + let mut incidence = self.vertices[source_vertex].first_incidence; + let (_, previous) = std::iter::from_fn(move || graph.step_incidence(&mut incidence)) + .find(|(_, f)| { + graph.incidences[*f] + .next + .is_some_and(|i| i.0 == e.arr_idx()) + }) + .expect("cannot find previous incidence, corrupt internal data state"); self.incidences[previous].next = next; } } + fn step_incidence(&self, incidence: &mut Option) -> Option<(VertexSlot, Edge)> { + let current = (*incidence)?; + let e = self.incidences.get_idx(current.0).unwrap(); + let entry = self.incidences[e]; + *incidence = entry.next; + Some((entry.adjacent, e)) + } + fn normalize_edge(&self, e: Edge) -> Edge { self.incidences.get_idx(e.arr_idx() & !1).unwrap() } @@ -214,10 +171,9 @@ impl GraphTopology for Graph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - AdjacentVertexIterator { - graph: self, - incidence: self.vertices[v].first_incidence, - } + let mut incidence = self.vertices[v].first_incidence; + std::iter::from_fn(move || self.step_incidence(&mut incidence)) + .map(|(vs, _)| self.vertices.get_idx(vs.0).unwrap()) } fn edges(&self) -> impl Iterator { @@ -228,10 +184,9 @@ impl GraphTopology for Graph { } fn incidences(&self, v: Self::Vertex) -> impl Iterator { - IncidenceIterator { - graph: self, - incidence: self.vertices[v].first_incidence, - } + let mut incidence = self.vertices[v].first_incidence; + std::iter::from_fn(move || self.step_incidence(&mut incidence)) + .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) } fn add_vertex(&mut self) -> Self::Vertex { -- 2.43.0 From f45fc98afc02a685f0b607e17aa9cd4b1618ee7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 12 May 2026 13:37:27 +0200 Subject: [PATCH 076/102] Add GraphTopology::incident_vertices and tests --- src/models/append_graph.rs | 20 ++++ src/models/graph.rs | 30 ++++++ src/testing/graph_topology_testing.rs | 129 ++++++++++++++++++++++++++ src/traits.rs | 2 +- 4 files changed, 180 insertions(+), 1 deletion(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 58d5ea8..4902e9b 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -120,6 +120,12 @@ impl GraphTopology for AppendGraph { .map(|(v, _)| v) } + 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) + } + fn edges(&self) -> impl Iterator { (0..self.incidences.len()).step_by(2).map(Incidence) } @@ -153,4 +159,18 @@ mod tests { crate::graph_topology_test_fixtures!(AppendGraph); crate::graph_topology_tests!(AppendGraph); + + #[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 = Incidence(e.0 + 1); + let (u1, u2) = graph.incident_vertices(f); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" + ); + } } diff --git a/src/models/graph.rs b/src/models/graph.rs index fa3262e..c05567e 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -176,6 +176,19 @@ impl GraphTopology for Graph { .map(|(vs, _)| self.vertices.get_idx(vs.0).unwrap()) } + fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { + let v2 = self + .vertices + .get_idx(self.incidences[e].adjacent.0) + .unwrap(); + let f = self.incidences.get_idx(e.arr_idx() ^ 1).unwrap(); + let v1 = self + .vertices + .get_idx(self.incidences[f].adjacent.0) + .unwrap(); + (v1, v2) + } + fn edges(&self) -> impl Iterator { self.incidences .iter() @@ -249,6 +262,23 @@ mod tests { crate::graph_topology_tests!(Graph); crate::graph_topology_deletion_tests!(Graph); + #[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 f = graph + .incidences + .get_idx(e.arr_idx() + 1) + .expect("paired index should be valid"); + let (u1, u2) = graph.incident_vertices(f); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?}" + ); + } + #[test] fn delete_edge_paired_index() { let mut graph = Graph::new(); diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 6ecd67e..d4a4288 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -365,6 +365,84 @@ macro_rules! graph_topology_tests { } } + #[test] + fn incident_vertices_single_edge() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + let (u1, u2) = graph.incident_vertices(e); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for edge {e:?}" + ); + } + + #[test] + fn incident_vertices_loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + assert_eq!( + graph.incident_vertices(e), + (v, v), + "unexpected incident vertices for loop edge {e:?}" + ); + } + + #[test] + fn incident_vertices_multiple_edges() { + 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); + assert_ne!(e1, e2, "edges should be distinct"); + let (u1, u2) = graph.incident_vertices(e1); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for first multi-edge {e1:?}" + ); + let (u1, u2) = graph.incident_vertices(e2); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for second multi-edge {e2:?}" + ); + } + + #[test] + fn incident_vertices() { + let (graph, vertices, edges, _) = make_test_graph(); + let expected: [(<$T as $crate::traits::GraphTopology>::Vertex, <$T as $crate::traits::GraphTopology>::Vertex); 18] = [ + (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]), + ]; + for (i, &(v1, v2)) in expected.iter().enumerate() { + let (u1, u2) = graph.incident_vertices(edges[i]); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for edges[{i}]: edge {:?}", + edges[i] + ); + } + } + #[test] fn edges_empty() { let graph = <$T>::new(); @@ -514,6 +592,20 @@ macro_rules! graph_topology_tests { vertices[i] ); } + } + + #[test] + fn incident_vertices_incidences_consistency() { + let (graph, _, _, _) = make_test_graph(); + for u in graph.vertices() { + for (v, e) in graph.incidences(u) { + let (w1, w2) = graph.incident_vertices(e); + assert!( + (w1 == u && w2 == v) || (w1 == v && w2 == u), + "incident vertices {w1:?} and {w2:?} of edge {e:?} are inconsistent with incidence ({v:?}, {e:?}) of vertex {u:?}" + ); + } + } } }; } @@ -800,6 +892,43 @@ macro_rules! graph_topology_deletion_tests { } } + #[test] + fn incident_vertices_after_delete_edge() { + let (mut graph, vertices, edges, _) = make_test_graph(); + graph.delete_edge(edges[2]); + let expected: [( + usize, + ::Vertex, + ::Vertex, + ); 17] = [ + (0, vertices[0], vertices[1]), + (1, vertices[0], vertices[1]), + (3, vertices[1], vertices[3]), + (4, vertices[1], vertices[4]), + (5, vertices[2], vertices[2]), + (6, vertices[2], vertices[4]), + (7, vertices[2], vertices[4]), + (8, vertices[2], vertices[5]), + (9, vertices[2], vertices[6]), + (10, vertices[3], vertices[6]), + (11, vertices[4], vertices[4]), + (12, vertices[4], vertices[7]), + (13, vertices[4], vertices[8]), + (14, vertices[5], vertices[9]), + (15, vertices[6], vertices[9]), + (16, vertices[7], vertices[8]), + (17, vertices[7], vertices[9]), + ]; + for (i, v1, v2) in expected { + let (u1, u2) = graph.incident_vertices(edges[i]); + assert!( + (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), + "unexpected incident vertices {u1:?} and {u2:?} for edges[{i}] ({:?}) after delete", + edges[i] + ); + } + } + #[test] fn edges_after_delete() { let (mut graph, vertices, edges, _) = make_test_graph(); diff --git a/src/traits.rs b/src/traits.rs index d38d0ad..d622c80 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,7 +2,6 @@ use crate::maps::{EdgeMap, VertexMap}; // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add iterator of incident edges for a vertex. -// TODO: Add finding incident vertices for an edge. // TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; @@ -18,6 +17,7 @@ pub trait GraphTopology { fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator; fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; + fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex); fn edges(&self) -> impl Iterator; fn incidences(&self, v: Self::Vertex) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; -- 2.43.0 From ac688b4fc08538229cd7c526870d37200588619f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 12 May 2026 13:39:13 +0200 Subject: [PATCH 077/102] Extracted some assert code for GraphTopology::incidences into new fn --- src/testing/graph_topology_testing.rs | 82 ++++++++++++--------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index d4a4288..8769c53 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -960,33 +960,12 @@ macro_rules! graph_topology_deletion_tests { let (mut graph, vertices, _, incidences) = make_test_graph(); graph.delete_vertex(vertices[2]); for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { - let mut remaining: Vec<_> = incidences[i] + let remaining = incidences[i] .iter() .filter(|(v, _)| *v != vertices[2]) .cloned() .collect(); - assert_eq!( - graph.incidences(vertices[i]).count(), - remaining.len(), - "unexpected incidence count for vertex {:?} after delete", - vertices[i] - ); - for incidence in graph.incidences(vertices[i]) { - let pos = remaining - .iter() - .position(|(v, e)| *v == incidence.0 && *e == incidence.1) - .expect(&format!( - "unexpected incidence {incidence:?} of vertex {:?} after delete", - vertices[i] - )); - remaining.swap_remove(pos); - } - assert!( - remaining.is_empty(), - "expected incidences {:?} of vertex {:?} not matched after delete", - remaining, - vertices[i] - ); + assert_vertex_incidences(&graph, vertices[i], remaining); } } @@ -996,34 +975,45 @@ macro_rules! graph_topology_deletion_tests { // Deletes the edge from vertices[1] to vertices[2]. graph.delete_edge(edges[2]); for i in 0..10 { - let mut remaining: Vec<_> = incidences[i] + let remaining = incidences[i] .iter() .filter(|(_, e)| *e != edges[2]) .cloned() .collect(); - assert_eq!( - graph.incidences(vertices[i]).count(), - remaining.len(), - "unexpected incidence count for vertex {:?} after delete", - vertices[i] - ); - for incidence in graph.incidences(vertices[i]) { - let pos = remaining - .iter() - .position(|(v, e)| *v == incidence.0 && *e == incidence.1) - .expect(&format!( - "unexpected incidence {incidence:?} of vertex {:?} after delete", - vertices[i] - )); - remaining.swap_remove(pos); - } - assert!( - remaining.is_empty(), - "expected incidences {:?} of vertex {:?} not matched after delete", - remaining, - vertices[i] - ); + assert_vertex_incidences(&graph, vertices[i], remaining); } } + + fn assert_vertex_incidences( + graph: &$T, + v: <$T as $crate::traits::GraphTopology>::Vertex, + mut expected: Vec<( + <$T as $crate::traits::GraphTopology>::Vertex, + <$T as $crate::traits::GraphTopology>::Edge, + )>, + ) { + assert_eq!( + graph.incidences(v).count(), + expected.len(), + "unexpected incidence count for vertex {:?} after delete", + v + ); + for incidence in graph.incidences(v) { + let pos = expected + .iter() + .position(|(u, e)| *u == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} after delete", + v + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {:?} of vertex {:?} not matched after delete", + expected, + v + ); + } }; } -- 2.43.0 From be63527478c120b50c6715c3cdab7bafc6737e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 12 May 2026 13:55:04 +0200 Subject: [PATCH 078/102] Add incident vertices to edges return value in make_test_graph() fixture --- src/testing/graph_topology_testing.rs | 187 ++++++++++---------------- 1 file changed, 74 insertions(+), 113 deletions(-) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 8769c53..74ffec8 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -5,7 +5,11 @@ macro_rules! graph_topology_test_fixtures { fn make_test_graph() -> ( $T, [<$T as $crate::traits::GraphTopology>::Vertex; 10], - [<$T as $crate::traits::GraphTopology>::Edge; 18], + [( + <$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, @@ -15,69 +19,70 @@ macro_rules! graph_topology_test_fixtures { let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); let edges = [ - graph.add_edge(vertices[0], vertices[1]), - graph.add_edge(vertices[0], vertices[1]), - graph.add_edge(vertices[1], vertices[2]), - graph.add_edge(vertices[1], vertices[3]), - graph.add_edge(vertices[1], vertices[4]), - graph.add_edge(vertices[2], vertices[2]), - graph.add_edge(vertices[2], vertices[4]), - graph.add_edge(vertices[2], vertices[4]), - graph.add_edge(vertices[2], vertices[5]), - graph.add_edge(vertices[2], vertices[6]), - graph.add_edge(vertices[3], vertices[6]), - graph.add_edge(vertices[4], vertices[4]), - graph.add_edge(vertices[4], vertices[7]), - graph.add_edge(vertices[4], vertices[8]), - graph.add_edge(vertices[5], vertices[9]), - graph.add_edge(vertices[6], vertices[9]), - graph.add_edge(vertices[7], vertices[8]), - graph.add_edge(vertices[7], vertices[9]), - ]; + (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(|(v1, v2)| (graph.add_edge(v1, v2), v1, v2)); let incidences: [Vec<_>; 10] = [ - vec![(vertices[1], edges[0]), (vertices[1], edges[1])], + vec![(vertices[1], edges[0].0), (vertices[1], edges[1].0)], vec![ - (vertices[0], edges[0]), - (vertices[0], edges[1]), - (vertices[2], edges[2]), - (vertices[3], edges[3]), - (vertices[4], edges[4]), + (vertices[0], edges[0].0), + (vertices[0], edges[1].0), + (vertices[2], edges[2].0), + (vertices[3], edges[3].0), + (vertices[4], edges[4].0), ], vec![ - (vertices[2], edges[5]), - (vertices[2], edges[5]), - (vertices[1], edges[2]), - (vertices[4], edges[6]), - (vertices[4], edges[7]), - (vertices[5], edges[8]), - (vertices[6], edges[9]), + (vertices[2], edges[5].0), + (vertices[2], edges[5].0), + (vertices[1], edges[2].0), + (vertices[4], edges[6].0), + (vertices[4], edges[7].0), + (vertices[5], edges[8].0), + (vertices[6], edges[9].0), ], - vec![(vertices[1], edges[3]), (vertices[6], edges[10])], + vec![(vertices[1], edges[3].0), (vertices[6], edges[10].0)], vec![ - (vertices[1], edges[4]), - (vertices[2], edges[6]), - (vertices[2], edges[7]), - (vertices[4], edges[11]), - (vertices[4], edges[11]), - (vertices[7], edges[12]), - (vertices[8], edges[13]), + (vertices[1], edges[4].0), + (vertices[2], edges[6].0), + (vertices[2], edges[7].0), + (vertices[4], edges[11].0), + (vertices[4], edges[11].0), + (vertices[7], edges[12].0), + (vertices[8], edges[13].0), ], - vec![(vertices[2], edges[8]), (vertices[9], edges[14])], + vec![(vertices[2], edges[8].0), (vertices[9], edges[14].0)], vec![ - (vertices[2], edges[9]), - (vertices[3], edges[10]), - (vertices[9], edges[15]), + (vertices[2], edges[9].0), + (vertices[3], edges[10].0), + (vertices[9], edges[15].0), ], vec![ - (vertices[4], edges[12]), - (vertices[8], edges[16]), - (vertices[9], edges[17]), + (vertices[4], edges[12].0), + (vertices[8], edges[16].0), + (vertices[9], edges[17].0), ], - vec![(vertices[4], edges[13]), (vertices[7], edges[16])], + vec![(vertices[4], edges[13].0), (vertices[7], edges[16].0)], vec![ - (vertices[5], edges[14]), - (vertices[6], edges[15]), - (vertices[7], edges[17]), + (vertices[5], edges[14].0), + (vertices[6], edges[15].0), + (vertices[7], edges[17].0), ], ]; (graph, vertices, edges, incidences) @@ -412,33 +417,12 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices() { - let (graph, vertices, edges, _) = make_test_graph(); - let expected: [(<$T as $crate::traits::GraphTopology>::Vertex, <$T as $crate::traits::GraphTopology>::Vertex); 18] = [ - (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]), - ]; - for (i, &(v1, v2)) in expected.iter().enumerate() { - let (u1, u2) = graph.incident_vertices(edges[i]); + let (graph, _, edges, _) = make_test_graph(); + for &(e, v1, v2) in edges.iter() { + let (u1, u2) = graph.incident_vertices(e); assert!( (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edges[{i}]: edge {:?}", - edges[i] + "unexpected incident vertices {u1:?} and {u2:?} for edge {e:?}" ); } } @@ -461,7 +445,7 @@ macro_rules! graph_topology_tests { // Expects each edge to appear exactly once. for e in graph.edges() { assert_eq!( - edges.iter().filter(|&x| *x == e).count(), + edges.iter().filter(|&&(f, _, _)| f == e).count(), 1, "unexpected edge {e:?} from the iterator" ); @@ -894,37 +878,14 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incident_vertices_after_delete_edge() { - let (mut graph, vertices, edges, _) = make_test_graph(); - graph.delete_edge(edges[2]); - let expected: [( - usize, - ::Vertex, - ::Vertex, - ); 17] = [ - (0, vertices[0], vertices[1]), - (1, vertices[0], vertices[1]), - (3, vertices[1], vertices[3]), - (4, vertices[1], vertices[4]), - (5, vertices[2], vertices[2]), - (6, vertices[2], vertices[4]), - (7, vertices[2], vertices[4]), - (8, vertices[2], vertices[5]), - (9, vertices[2], vertices[6]), - (10, vertices[3], vertices[6]), - (11, vertices[4], vertices[4]), - (12, vertices[4], vertices[7]), - (13, vertices[4], vertices[8]), - (14, vertices[5], vertices[9]), - (15, vertices[6], vertices[9]), - (16, vertices[7], vertices[8]), - (17, vertices[7], vertices[9]), - ]; - for (i, v1, v2) in expected { - let (u1, u2) = graph.incident_vertices(edges[i]); + let (mut graph, _, edges, _) = make_test_graph(); + let e = edges[2].0; + graph.delete_edge(e); + for &(f, v1, v2) in edges.iter().filter(|&&(f, _, _)| f != e) { + let (u1, u2) = graph.incident_vertices(f); assert!( (u1 == v1 && u2 == v2) || (u1 == v2 && u2 == v1), - "unexpected incident vertices {u1:?} and {u2:?} for edges[{i}] ({:?}) after delete", - edges[i] + "unexpected incident vertices {u1:?} and {u2:?} for edge {f:?} after delete" ); } } @@ -941,16 +902,16 @@ macro_rules! graph_topology_deletion_tests { ); for i in [2, 5, 6, 7, 8, 9] { assert!( - !graph.edges().any(|e| e == edges[i]), + !graph.edges().any(|e| e == edges[i].0), "deleted edge {:?} appeared in iterator", - edges[i] + 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]), + graph.edges().any(|e| e == edges[i].0), "expected edge {:?} missing from iterator after delete", - edges[i] + edges[i].0 ); } } @@ -973,11 +934,11 @@ macro_rules! graph_topology_deletion_tests { fn incidences_after_delete_edge() { let (mut graph, vertices, edges, incidences) = make_test_graph(); // Deletes the edge from vertices[1] to vertices[2]. - graph.delete_edge(edges[2]); + graph.delete_edge(edges[2].0); for i in 0..10 { let remaining = incidences[i] .iter() - .filter(|(_, e)| *e != edges[2]) + .filter(|(_, e)| *e != edges[2].0) .cloned() .collect(); assert_vertex_incidences(&graph, vertices[i], remaining); -- 2.43.0 From f1d5ccafd20229c73f6e6c898c0d7795cfb787dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 12 May 2026 18:25:22 +0200 Subject: [PATCH 079/102] Add len() for VertexMap and EdgeMap --- src/maps.rs | 14 ++++++++++++++ src/testing/maps_testing.rs | 16 ++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/maps.rs b/src/maps.rs index 161fca5..494f842 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -13,6 +13,11 @@ impl VertexMap { } } + // Reads beyond len() are valid and return the default value. + pub fn len(&self) -> usize { + self.inner.len() + } + pub fn sync>(&mut self, graph: &G) { self.inner.resize(graph.vertex_capacity()); } @@ -43,6 +48,11 @@ impl EdgeMap { } } + // Reads beyond len() are valid and return the default value. + pub fn len(&self) -> usize { + self.inner.len() + } + pub fn sync>(&mut self, graph: &G) { self.inner.resize(graph.edge_capacity()); } @@ -77,6 +87,10 @@ impl EntityMap { } } + pub fn len(&self) -> usize { + self.data.len() + } + pub fn resize(&mut self, capacity: usize) { if capacity > self.data.len() { self.data.resize(capacity, self.default.clone()); diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs index bc73c6f..5c91259 100644 --- a/src/testing/maps_testing.rs +++ b/src/testing/maps_testing.rs @@ -48,16 +48,16 @@ macro_rules! vertex_map_tests { let mut graph = <$T>::new(); graph.add_vertex(); let mut map = graph.vertex_map(42); - let initial_len = map.inner.data.len(); + let initial_len = map.len(); while graph.vertex_capacity() <= initial_len { graph.add_vertex(); } assert!( - map.inner.data.len() < graph.vertex_capacity(), + map.len() < graph.vertex_capacity(), "precondition: map is stale before sync" ); map.sync(&graph); - assert_eq!(map.inner.data.len(), graph.vertex_capacity()); + assert_eq!(map.len(), graph.vertex_capacity()); } #[test] @@ -99,7 +99,7 @@ macro_rules! vertex_map_deletion_tests { let capacity_before = graph.vertex_capacity(); graph.delete_vertex(v2); map.sync(&graph); - assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map.len(), capacity_before); assert_eq!(map[v1], 5); } @@ -180,16 +180,16 @@ macro_rules! edge_map_tests { let v2 = graph.add_vertex(); graph.add_edge(v1, v2); let mut map = graph.edge_map(42); - let initial_len = map.inner.data.len(); + let initial_len = map.len(); while graph.edge_capacity() <= initial_len { graph.add_edge(v1, v2); } assert!( - map.inner.data.len() < graph.edge_capacity(), + map.len() < graph.edge_capacity(), "precondition: map is stale before sync" ); map.sync(&graph); - assert_eq!(map.inner.data.len(), graph.edge_capacity()); + assert_eq!(map.len(), graph.edge_capacity()); } #[test] @@ -237,7 +237,7 @@ macro_rules! edge_map_deletion_tests { let capacity_before = graph.edge_capacity(); graph.delete_edge(e2); map.sync(&graph); - assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map.len(), capacity_before); assert_eq!(map[e1], 5); } -- 2.43.0 From ff148532ab42962156529a90a27732d5494d3e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 12 May 2026 18:32:45 +0200 Subject: [PATCH 080/102] Move maps tests to integration tests --- src/maps.rs | 34 ---------------------------------- src/testing/maps_testing.rs | 4 ---- tests/maps.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 38 deletions(-) create mode 100644 tests/maps.rs diff --git a/src/maps.rs b/src/maps.rs index 494f842..b7c24ef 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -120,37 +120,3 @@ impl IndexMut for EntityMap { &mut self.data[i] } } - -#[cfg(test)] -mod append_graph_vertex_map_tests { - use crate::models::append_graph::AppendGraph; - use crate::traits::GraphTopology; - - crate::vertex_map_tests!(AppendGraph); -} - -#[cfg(test)] -mod append_graph_edge_map_tests { - use crate::models::append_graph::AppendGraph; - use crate::traits::GraphTopology; - - crate::edge_map_tests!(AppendGraph); -} - -#[cfg(test)] -mod graph_vertex_map_tests { - use crate::models::graph::Graph; - use crate::traits::{GraphTopology, GraphTopologyDeletion}; - - crate::vertex_map_tests!(Graph); - crate::vertex_map_deletion_tests!(Graph); -} - -#[cfg(test)] -mod graph_edge_map_tests { - use crate::models::graph::Graph; - use crate::traits::{GraphTopology, GraphTopologyDeletion}; - - crate::edge_map_tests!(Graph); - crate::edge_map_deletion_tests!(Graph); -} diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs index 5c91259..5aad6f9 100644 --- a/src/testing/maps_testing.rs +++ b/src/testing/maps_testing.rs @@ -1,4 +1,3 @@ -#[cfg(test)] #[macro_export] macro_rules! vertex_map_tests { ($T:ty) => { @@ -73,7 +72,6 @@ macro_rules! vertex_map_tests { }; } -#[cfg(test)] #[macro_export] macro_rules! vertex_map_deletion_tests { ($T:ty) => { @@ -120,7 +118,6 @@ macro_rules! vertex_map_deletion_tests { }; } -#[cfg(test)] #[macro_export] macro_rules! edge_map_tests { ($T:ty) => { @@ -207,7 +204,6 @@ macro_rules! edge_map_tests { }; } -#[cfg(test)] #[macro_export] macro_rules! edge_map_deletion_tests { ($T:ty) => { diff --git a/tests/maps.rs b/tests/maps.rs new file mode 100644 index 0000000..c242a77 --- /dev/null +++ b/tests/maps.rs @@ -0,0 +1,29 @@ +mod append_graph_vertex_map_tests { + use grapherity::models::append_graph::AppendGraph; + use grapherity::traits::GraphTopology; + + grapherity::vertex_map_tests!(AppendGraph); +} + +mod append_graph_edge_map_tests { + use grapherity::models::append_graph::AppendGraph; + use grapherity::traits::GraphTopology; + + grapherity::edge_map_tests!(AppendGraph); +} + +mod graph_vertex_map_tests { + use grapherity::models::graph::Graph; + use grapherity::traits::{GraphTopology, GraphTopologyDeletion}; + + grapherity::vertex_map_tests!(Graph); + grapherity::vertex_map_deletion_tests!(Graph); +} + +mod graph_edge_map_tests { + use grapherity::models::graph::Graph; + use grapherity::traits::{GraphTopology, GraphTopologyDeletion}; + + grapherity::edge_map_tests!(Graph); + grapherity::edge_map_deletion_tests!(Graph); +} -- 2.43.0 From 6191def3240a272691235eb3dafaef8b2d68976a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 10:30:36 +0200 Subject: [PATCH 081/102] Fix format --- src/testing/graph_topology_testing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 74ffec8..60bc154 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -578,7 +578,7 @@ macro_rules! graph_topology_tests { } } - #[test] + #[test] fn incident_vertices_incidences_consistency() { let (graph, _, _, _) = make_test_graph(); for u in graph.vertices() { -- 2.43.0 From 340fde213336ab85e76e7e2e863e5f291739cab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 10:32:37 +0200 Subject: [PATCH 082/102] Add Graph::raw_incidences() iterator as a wrapper for step_incidence() --- src/models/graph.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/models/graph.rs b/src/models/graph.rs index c05567e..70372b3 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -98,8 +98,8 @@ impl Graph { vertex_header.first_incidence = next; } else { let graph: &Graph = self; - let mut incidence = self.vertices[source_vertex].first_incidence; - let (_, previous) = std::iter::from_fn(move || graph.step_incidence(&mut incidence)) + let (_, previous) = self + .raw_incidences(source_vertex) .find(|(_, f)| { graph.incidences[*f] .next @@ -110,6 +110,11 @@ impl Graph { } } + fn raw_incidences(&self, v: Vertex) -> impl Iterator { + let mut incidence = self.vertices[v].first_incidence; + std::iter::from_fn(move || self.step_incidence(&mut incidence)) + } + fn step_incidence(&self, incidence: &mut Option) -> Option<(VertexSlot, Edge)> { let current = (*incidence)?; let e = self.incidences.get_idx(current.0).unwrap(); @@ -171,8 +176,7 @@ impl GraphTopology for Graph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - let mut incidence = self.vertices[v].first_incidence; - std::iter::from_fn(move || self.step_incidence(&mut incidence)) + self.raw_incidences(v) .map(|(vs, _)| self.vertices.get_idx(vs.0).unwrap()) } @@ -197,8 +201,7 @@ impl GraphTopology for Graph { } fn incidences(&self, v: Self::Vertex) -> impl Iterator { - let mut incidence = self.vertices[v].first_incidence; - std::iter::from_fn(move || self.step_incidence(&mut incidence)) + self.raw_incidences(v) .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) } -- 2.43.0 From 384dca2d2089b66e28df65efbc3ed5836ace35c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 10:58:47 +0200 Subject: [PATCH 083/102] Fix inconsistent local var name in test function --- src/testing/graph_topology_testing.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 60bc154..05595a4 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -473,21 +473,21 @@ macro_rules! graph_topology_tests { "unexpected incidence count for vertex {:?}", vertices[i] ); - let mut remaining = incidences[i].clone(); + let mut expected = incidences[i].clone(); for incidence in graph.incidences(vertices[i]) { - let pos = remaining + let pos = expected .iter() .position(|(v, e)| *v == incidence.0 && *e == incidence.1) .expect(&format!( "unexpected incidence {incidence:?} of vertex {:?} from the iterator", vertices[i] )); - remaining.swap_remove(pos); + expected.swap_remove(pos); } assert!( - remaining.is_empty(), + expected.is_empty(), "expected incidences {:?} of vertex {:?} were not matched by the iterator", - remaining, + expected, vertices[i] ); } -- 2.43.0 From f862ac55ec590e9e402938a4d931b30841c556b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 10:59:38 +0200 Subject: [PATCH 084/102] Add GraphTopology::incident_edges() and tests --- src/models/append_graph.rs | 8 ++ src/models/graph.rs | 4 + src/testing/graph_topology_testing.rs | 172 ++++++++++++++++++++++++++ src/traits.rs | 2 +- 4 files changed, 185 insertions(+), 1 deletion(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 4902e9b..eef3575 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -130,6 +130,14 @@ impl GraphTopology for AppendGraph { (0..self.incidences.len()).step_by(2).map(Incidence) } + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { + RawIncidenceIterator { + graph: self, + incidence: self.vertices[v.0].first_incidence, + } + .map(|(_, e)| e.normalize()) + } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { RawIncidenceIterator { graph: self, diff --git a/src/models/graph.rs b/src/models/graph.rs index 70372b3..0dc4be3 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -200,6 +200,10 @@ impl GraphTopology for Graph { .map(|(i, _)| i) } + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { + self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e)) + } + fn incidences(&self, v: Self::Vertex) -> impl Iterator { self.raw_incidences(v) .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 05595a4..f8bae96 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -452,6 +452,92 @@ macro_rules! graph_topology_tests { } } + #[test] + fn incident_edges_empty() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + assert_eq!( + graph.incident_edges(v).count(), + 0, + "incident edge iterator of vertex with degree 0 should have no elements" + ); + } + + #[test] + fn incident_edges() { + let (graph, vertices, _, incidences) = make_test_graph(); + for i in 0..10 { + assert_eq!( + graph.incident_edges(vertices[i]).count(), + incidences[i].len(), + "unexpected incident edge count for vertex {:?}", + vertices[i] + ); + let mut expected: Vec<_> = incidences[i].iter().map(|(_, e)| *e).collect(); + for e in graph.incident_edges(vertices[i]) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} from the iterator", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + expected, + vertices[i] + ); + } + } + + #[test] + fn incident_edges_loop_edge() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + let mut iter = graph.incident_edges(v); + assert_eq!(iter.next(), Some(e), "loop edge should appear in incident edges"); + assert_eq!( + iter.next(), + Some(e), + "loop edge should appear twice in incident edges" + ); + assert_eq!(iter.next(), None, "too many incident edges from iterator"); + } + + #[test] + fn incident_edges_multiple_edges() { + let k = 3; + let mut graph = <$T>::new(); + let vertices = [graph.add_vertex(), graph.add_vertex()]; + let mut edges = Vec::new(); + for _ in 0..k { + edges.push(graph.add_edge(vertices[0], vertices[1])); + } + for i in 0..2 { + let mut expected = edges.clone(); + for e in graph.incident_edges(vertices[i]) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "unexpected incident edge {e:?} of vertex {:?}", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + expected, + vertices[i] + ); + } + } + #[test] fn incidences_empty() { let mut graph = <$T>::new(); @@ -591,6 +677,28 @@ macro_rules! graph_topology_tests { } } } + + #[test] + fn incident_edges_incidences_consistency() { + let (graph, _, _, _) = make_test_graph(); + for u in graph.vertices() { + let mut expected: Vec<_> = graph.incidences(u).map(|(_, e)| e).collect(); + for e in graph.incident_edges(u) { + let pos = expected + .iter() + .position(|f| *f == e) + .expect(&format!( + "incident edge {e:?} of vertex {u:?} missing from incidences" + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "incidence edges {:?} of vertex {u:?} not matched by incident_edges", + expected + ); + } + } }; } @@ -916,6 +1024,70 @@ macro_rules! graph_topology_deletion_tests { } } + #[test] + fn incident_edges_after_delete_edge() { + let (mut graph, vertices, edges, incidences) = make_test_graph(); + graph.delete_edge(edges[2].0); + for i in 0..10 { + let mut expected: Vec<_> = incidences[i] + .iter() + .filter(|(_, e)| *e != edges[2].0) + .map(|(_, e)| *e) + .collect(); + assert_eq!( + graph.incident_edges(vertices[i]).count(), + expected.len(), + "unexpected incident edge count for vertex {:?} after delete", + vertices[i] + ); + for e in graph.incident_edges(vertices[i]) { + let pos = expected.iter().position(|f| *f == e).expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} after delete", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} not matched after delete", + expected, + vertices[i] + ); + } + } + + #[test] + fn incident_edges_after_delete_vertex() { + let (mut graph, vertices, _, incidences) = make_test_graph(); + graph.delete_vertex(vertices[2]); + for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { + let mut expected: Vec<_> = incidences[i] + .iter() + .filter(|(v, _)| *v != vertices[2]) + .map(|(_, e)| *e) + .collect(); + assert_eq!( + graph.incident_edges(vertices[i]).count(), + expected.len(), + "unexpected incident edge count for vertex {:?} after delete", + vertices[i] + ); + for e in graph.incident_edges(vertices[i]) { + let pos = expected.iter().position(|f| *f == e).expect(&format!( + "unexpected incident edge {e:?} of vertex {:?} after delete", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incident edges {:?} of vertex {:?} not matched after delete", + expected, + vertices[i] + ); + } + } + #[test] fn incidences_after_delete_vertex() { let (mut graph, vertices, _, incidences) = make_test_graph(); diff --git a/src/traits.rs b/src/traits.rs index d622c80..fecd07c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,6 @@ use crate::maps::{EdgeMap, VertexMap}; // TODO: Add functions to reserve memory for vertices and edges. -// TODO: Add iterator of incident edges for a vertex. // TODO: Split out GraphTopologyAddition trait. pub trait GraphTopology { type Vertex: Copy + Eq; @@ -19,6 +18,7 @@ pub trait GraphTopology { fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex); fn edges(&self) -> impl Iterator; + fn incident_edges(&self, v: Self::Vertex) -> impl Iterator; fn incidences(&self, v: Self::Vertex) -> impl Iterator; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; -- 2.43.0 From aaa1b34db3299b04068234c87f8c3a97f101026c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 12:16:10 +0200 Subject: [PATCH 085/102] Add todo that was accidentally removed --- src/models/graph.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/graph.rs b/src/models/graph.rs index 0dc4be3..025d388 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -116,6 +116,7 @@ impl Graph { } fn step_incidence(&self, incidence: &mut Option) -> 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 entry = self.incidences[e]; -- 2.43.0 From e479074151c10eef6df67c369d8b4962ffed56c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 12:18:27 +0200 Subject: [PATCH 086/102] Remove todo, proposed change would not affect callers --- src/models/append_graph.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index eef3575..d0abc41 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -40,7 +40,6 @@ impl<'a> Iterator for RawIncidenceIterator<'a> { } pub struct AppendGraph { - // TODO: Index 'vertices' by a 'Vertex' instead of 'Vertex.0'? vertices: Vec, incidences: Vec, } -- 2.43.0 From 600664acb6fb8c5f97bc48f2dc328bb0ad192ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 14:02:07 +0200 Subject: [PATCH 087/102] Update tests for Dijkstra's algorithm to test both graph models Use the already established macro pattern, move actual tests to a new file in src/testing. Use the make_test_graph() function from the graph topology tests instead of duplicating it for the Dijkstra tests. --- src/testing.rs | 1 + src/testing/dijkstra_testing.rs | 152 +++++++++++++++++++++++ src/testing/graph_topology_testing.rs | 3 - tests/dijkstra.rs | 166 ++------------------------ 4 files changed, 163 insertions(+), 159 deletions(-) create mode 100644 src/testing/dijkstra_testing.rs diff --git a/src/testing.rs b/src/testing.rs index 6dccf6c..0d4f69d 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,2 +1,3 @@ +pub(crate) mod dijkstra_testing; pub(crate) mod graph_topology_testing; pub(crate) mod maps_testing; diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs new file mode 100644 index 0000000..25b939f --- /dev/null +++ b/src/testing/dijkstra_testing.rs @@ -0,0 +1,152 @@ +#[macro_export] +macro_rules! dijkstra_tests { + ($T:ty) => { + #[test] + fn dijkstra_single_vertex() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let result = $crate::algorithms::dijkstra_unweighted(&graph, v1); + assert_eq!( + result.distances[v1], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.predecessors[v1], + None, + "unexpected predecessor of source vertex", + ); + } + + #[test] + fn dijkstra_disconnected() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let result = $crate::algorithms::dijkstra_unweighted(&graph, v1); + assert_eq!( + result.distances[v1], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + result.distances[v2], + None, + "unexpected distance of disconnected vertex" + ); + assert_eq!( + result.predecessors[v1], + None, + "unexpected predecessor of source vertex", + ); + assert_eq!( + result.predecessors[v2], + None, + "unexpected predecessor of disconnected vertex", + ); + } + + #[test] + fn dijkstra() { + let (graph, vertices, _, _) = make_test_graph(); + let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); + 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), + ]; + 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..graph.vertex_count() { + assert_eq!( + result.distances[vertices[i]], + expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?}", + vertices[0], + vertices[i] + ); + assert!( + expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), + "unexpected predecessor {:?} of {:?}", + result.predecessors[vertices[i]], + vertices[i] + ); + } + } + + #[test] + fn dijkstra_distances_single_vertex() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v1); + assert_eq!( + distances[v1], + Some(0), + "unexpected distance of source vertex" + ); + } + + #[test] + fn dijkstra_distances_disconnected() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v1); + assert_eq!( + distances[v1], + Some(0), + "unexpected distance of source vertex" + ); + assert_eq!( + distances[v2], + None, + "unexpected distance of disconnected vertex", + ); + } + + #[test] + fn dijkstra_distances() { + let (graph, vertices, _, _) = make_test_graph(); + let distances = + $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + 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..graph.vertex_count() { + assert_eq!( + distances[vertices[i]], + expected_distances_from_v0[i], + "unexpected distance from {:?} to {:?}", + vertices[0], + vertices[i] + ); + } + } + }; +} diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index f8bae96..21bd040 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -1,4 +1,3 @@ -#[cfg(test)] #[macro_export] macro_rules! graph_topology_test_fixtures { ($T:ty) => { @@ -90,7 +89,6 @@ macro_rules! graph_topology_test_fixtures { }; } -#[cfg(test)] #[macro_export] macro_rules! graph_topology_tests { ($T:ty) => { @@ -702,7 +700,6 @@ macro_rules! graph_topology_tests { }; } -#[cfg(test)] #[macro_export] macro_rules! graph_topology_deletion_tests { ($T:ty) => { diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 6b45023..1e4db9f 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,161 +1,15 @@ -use grapherity::algorithms; -use grapherity::models::append_graph::AppendGraph; -use grapherity::traits::GraphTopology; +mod append_graph_tests { + use grapherity::models::append_graph::AppendGraph; + use grapherity::traits::GraphTopology; -#[test] -fn dijkstra_single_vertex() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let result = algorithms::dijkstra_unweighted(&graph, v1); - assert_eq!( - result.distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[v1], None, - "unexpected predecessor of source vertex", - ); + grapherity::graph_topology_test_fixtures!(AppendGraph); + grapherity::dijkstra_tests!(AppendGraph); } -#[test] -fn dijkstra_disconnected() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let result = algorithms::dijkstra_unweighted(&graph, v1); - assert_eq!( - result.distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.distances[v2], None, - "unexpected distance of disconnected vertex" - ); - assert_eq!( - result.predecessors[v1], None, - "unexpected predecessor of source vertex", - ); - assert_eq!( - result.predecessors[v2], None, - "unexpected predecessor of disconnected vertex", - ); -} +mod graph_tests { + use grapherity::models::graph::Graph; + use grapherity::traits::GraphTopology; -#[test] -fn dijkstra() { - let (graph, vertices) = make_test_graph(); - let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); - 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), - ]; - 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..graph.vertex_count() { - assert_eq!( - result.distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } -} - -#[test] -fn dijkstra_distances_single_vertex() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let distances = algorithms::dijkstra_distances_unweighted(&graph, v1); - assert_eq!( - distances[v1], - Some(0), - "unexpected distance of source vertex" - ); -} - -#[test] -fn dijkstra_distances_disconnected() { - let mut graph = AppendGraph::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let distances = algorithms::dijkstra_distances_unweighted(&graph, v1); - assert_eq!( - distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - distances[v2], None, - "unexpected distance of disconnected vertex", - ); -} - -#[test] -fn dijkstra_distances() { - let (graph, vertices) = make_test_graph(); - let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - 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..graph.vertex_count() { - assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } -} - -fn make_test_graph() -> (AppendGraph, [::Vertex; 10]) { - let mut graph = AppendGraph::new(); - let vertices = core::array::from_fn::<_, 10, _>(|_| graph.add_vertex()); - graph.add_edge(vertices[0], vertices[1]); - graph.add_edge(vertices[1], vertices[2]); - graph.add_edge(vertices[1], vertices[3]); - graph.add_edge(vertices[1], vertices[4]); - graph.add_edge(vertices[2], vertices[4]); - graph.add_edge(vertices[2], vertices[5]); - graph.add_edge(vertices[2], vertices[6]); - graph.add_edge(vertices[3], vertices[6]); - graph.add_edge(vertices[4], vertices[7]); - graph.add_edge(vertices[4], vertices[8]); - graph.add_edge(vertices[5], vertices[9]); - graph.add_edge(vertices[6], vertices[9]); - graph.add_edge(vertices[7], vertices[8]); - graph.add_edge(vertices[7], vertices[9]); - (graph, vertices) + grapherity::graph_topology_test_fixtures!(Graph); + grapherity::dijkstra_tests!(Graph); } -- 2.43.0 From 13daf20e1c214199cf384a277ce0bb1a658b1a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Wed, 13 May 2026 14:04:07 +0200 Subject: [PATCH 088/102] Update test names for Dijkstra's algorithm to match the tested functions This should have happened when the functions were renamed with the introduction of custom edge weights --- src/testing/dijkstra_testing.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index 25b939f..c68c58d 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -2,7 +2,7 @@ macro_rules! dijkstra_tests { ($T:ty) => { #[test] - fn dijkstra_single_vertex() { + fn dijkstra_unweighted_single_vertex() { let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let result = $crate::algorithms::dijkstra_unweighted(&graph, v1); @@ -19,7 +19,7 @@ macro_rules! dijkstra_tests { } #[test] - fn dijkstra_disconnected() { + fn dijkstra_unweighted_disconnected() { let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -47,7 +47,7 @@ macro_rules! dijkstra_tests { } #[test] - fn dijkstra() { + fn dijkstra_unweighted() { let (graph, vertices, _, _) = make_test_graph(); let result = $crate::algorithms::dijkstra_unweighted(&graph, vertices[0]); let expected_distances_from_v0 = [ @@ -92,7 +92,7 @@ macro_rules! dijkstra_tests { } #[test] - fn dijkstra_distances_single_vertex() { + fn dijkstra_distances_unweighted_single_vertex() { let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v1); @@ -104,7 +104,7 @@ macro_rules! dijkstra_tests { } #[test] - fn dijkstra_distances_disconnected() { + fn dijkstra_distances_unweighted_disconnected() { let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -122,7 +122,7 @@ macro_rules! dijkstra_tests { } #[test] - fn dijkstra_distances() { + fn dijkstra_distances_unweighted() { let (graph, vertices, _, _) = make_test_graph(); let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); -- 2.43.0 From 25b5d7864a4577ffc5a7885db49ce19928c168c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 18 May 2026 12:45:28 +0200 Subject: [PATCH 089/102] Refactor Dijkstra test assertions into new functions --- src/testing/dijkstra_testing.rs | 260 +++++++++++++++++++++----------- 1 file changed, 169 insertions(+), 91 deletions(-) diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index c68c58d..365731e 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -4,64 +4,170 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_unweighted_single_vertex() { let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, v1); - assert_eq!( - result.distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.predecessors[v1], - None, - "unexpected predecessor of source vertex", - ); + let v = graph.add_vertex(); + let result = $crate::algorithms::dijkstra_unweighted(&graph, v); + assert_single_vertex(&result, v) } #[test] fn dijkstra_unweighted_disconnected() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let result = $crate::algorithms::dijkstra_unweighted(&graph, v1); - assert_eq!( - result.distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - result.distances[v2], - None, - "unexpected distance of disconnected vertex" - ); - assert_eq!( - result.predecessors[v1], - None, - "unexpected predecessor of source vertex", - ); - assert_eq!( - result.predecessors[v2], - None, - "unexpected predecessor of disconnected vertex", - ); + let (graph, vertices) = make_test_graph_dijkstra_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() { + 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_dijkstra_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, + >, + 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, + >, + 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, + >, + 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(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])], @@ -74,14 +180,7 @@ macro_rules! dijkstra_tests { vec![Some(vertices[4])], vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], ]; - for i in 0..graph.vertex_count() { - assert_eq!( - result.distances[vertices[i]], - expected_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], - vertices[i] - ); + for i in 0..10 { assert!( expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), "unexpected predecessor {:?} of {:?}", @@ -91,41 +190,13 @@ macro_rules! dijkstra_tests { } } - #[test] - fn dijkstra_distances_unweighted_single_vertex() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v1); - assert_eq!( - distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - } - - #[test] - fn dijkstra_distances_unweighted_disconnected() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v1); - assert_eq!( - distances[v1], - Some(0), - "unexpected distance of source vertex" - ); - assert_eq!( - distances[v2], - None, - "unexpected distance of disconnected vertex", - ); - } - - #[test] - fn dijkstra_distances_unweighted() { - let (graph, vertices, _, _) = make_test_graph(); - let distances = - $crate::algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); + fn assert_distances_unweighted_test_graph( + distances: &$crate::maps::VertexMap< + <$T as $crate::traits::GraphTopology>::Vertex, + Option, + >, + vertices: &[<$T as $crate::traits::GraphTopology>::Vertex], + ) { let expected_distances_from_v0 = [ Some(0), Some(1), @@ -138,15 +209,22 @@ macro_rules! dijkstra_tests { Some(3), Some(4), ]; - for i in 0..graph.vertex_count() { + for i in 0..10 { assert_eq!( - distances[vertices[i]], - expected_distances_from_v0[i], + distances[vertices[i]], expected_distances_from_v0[i], "unexpected distance from {:?} to {:?}", - vertices[0], - vertices[i] + vertices[0], vertices[i] ); } } + + fn make_test_graph_dijkstra_disconnected() + -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { + let mut graph = <$T>::new(); + let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 3] = + core::array::from_fn(|_| graph.add_vertex()); + graph.add_edge(vertices[1], vertices[2]); + (graph, vertices) + } }; } -- 2.43.0 From 465a3836839a907e32ff06ec4ced9c0c59f185d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 18 May 2026 12:48:12 +0200 Subject: [PATCH 090/102] Add tests for Dijkstra's algorithm with edge weights --- src/testing/dijkstra_testing.rs | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index 365731e..837ff22 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -1,6 +1,71 @@ #[macro_export] macro_rules! dijkstra_tests { ($T:ty) => { + #[test] + fn dijkstra_single_vertex() { + 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_dijkstra_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() { + 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() { let mut graph = <$T>::new(); @@ -226,5 +291,27 @@ macro_rules! dijkstra_tests { graph.add_edge(vertices[1], vertices[2]); (graph, vertices) } + + 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>, + ) { + 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) + } }; } -- 2.43.0 From ce692cb0b2615570171cf32cf94371b932ebf892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 18 May 2026 15:55:56 +0200 Subject: [PATCH 091/102] Add trait imports to test functions, making macros self-contained --- src/testing/dijkstra_testing.rs | 6 +++ src/testing/graph_topology_testing.rs | 66 +++++++++++++++++++++++++++ src/testing/maps_testing.rs | 24 ++++++++++ tests/dijkstra.rs | 2 - tests/maps.rs | 4 -- 5 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index 837ff22..bf6300b 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -3,6 +3,7 @@ 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, |_| { @@ -23,6 +24,7 @@ macro_rules! dijkstra_tests { #[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()); @@ -68,6 +70,7 @@ macro_rules! dijkstra_tests { #[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); @@ -91,6 +94,7 @@ macro_rules! dijkstra_tests { #[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); @@ -285,6 +289,7 @@ macro_rules! dijkstra_tests { fn make_test_graph_dijkstra_disconnected() -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { + 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()); @@ -306,6 +311,7 @@ macro_rules! dijkstra_tests { )>; 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] { diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 21bd040..cbaa1a3 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -14,6 +14,7 @@ macro_rules! graph_topology_test_fixtures { <$T as $crate::traits::GraphTopology>::Edge, )>; 10], ) { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); @@ -94,6 +95,7 @@ macro_rules! graph_topology_tests { ($T:ty) => { #[test] fn add_vertex() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); @@ -101,18 +103,21 @@ macro_rules! graph_topology_tests { #[test] fn vertex_count_empty() { + use $crate::traits::GraphTopology; let graph = <$T>::new(); assert_eq!(graph.vertex_count(), 0, "unexpected vertex count"); } #[test] fn vertex_count() { + use $crate::traits::GraphTopology; let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.vertex_count(), 10, "unexpected vertex count"); } #[test] fn add_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -122,18 +127,21 @@ macro_rules! graph_topology_tests { #[test] fn edge_count_empty() { + use $crate::traits::GraphTopology; let graph = <$T>::new(); assert_eq!(graph.edge_count(), 0, "unexpected edge count"); } #[test] fn edge_count() { + use $crate::traits::GraphTopology; let (graph, _, _, _) = make_test_graph(); assert_eq!(graph.edge_count(), 18, "unexpected edge count"); } #[test] fn degree_zero() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!(graph.degree(v), 0, "unexpected non-zero degree"); @@ -141,6 +149,7 @@ macro_rules! graph_topology_tests { #[test] fn degree() { + use $crate::traits::GraphTopology; let (graph, vertices, _, _) = make_test_graph(); let expected_degrees = [2, 5, 7, 2, 7, 2, 3, 3, 2, 3]; for i in 0..graph.vertex_count() { @@ -155,6 +164,7 @@ macro_rules! graph_topology_tests { #[test] fn loop_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); graph.add_edge(v, v); @@ -169,6 +179,7 @@ macro_rules! graph_topology_tests { #[test] fn multiple_edges() { + use $crate::traits::GraphTopology; let k = 3; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); @@ -185,6 +196,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent_vertex_self() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert!( @@ -195,6 +207,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent_single_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -207,6 +220,7 @@ macro_rules! graph_topology_tests { #[test] fn are_adjacent() { + use $crate::traits::GraphTopology; let (graph, vertices, _, _) = make_test_graph(); assert!( graph.are_adjacent(vertices[0], vertices[1]), @@ -251,6 +265,7 @@ macro_rules! graph_topology_tests { #[test] fn vertices_empty() { + use $crate::traits::GraphTopology; let graph = <$T>::new(); assert_eq!( graph.vertices().count(), @@ -261,6 +276,7 @@ macro_rules! graph_topology_tests { #[test] fn vertices() { + use $crate::traits::GraphTopology; let (graph, vertices, _, _) = make_test_graph(); assert_eq!(graph.vertices().count(), 10, "unexpected vertex count"); @@ -276,6 +292,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices_empty() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -287,6 +304,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices() { + use $crate::traits::GraphTopology; let (graph, vertices, _, _) = make_test_graph(); // Checks adjacency of vertex 4. assert_eq!( @@ -324,6 +342,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices_loop_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); graph.add_edge(v, v); @@ -343,6 +362,7 @@ macro_rules! graph_topology_tests { #[test] fn adjacent_vertices_multiple_edges() { + use $crate::traits::GraphTopology; let k = 3; let mut graph = <$T>::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; @@ -370,6 +390,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices_single_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -383,6 +404,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices_loop_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); let e = graph.add_edge(v, v); @@ -395,6 +417,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices_multiple_edges() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -415,6 +438,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices() { + use $crate::traits::GraphTopology; let (graph, _, edges, _) = make_test_graph(); for &(e, v1, v2) in edges.iter() { let (u1, u2) = graph.incident_vertices(e); @@ -427,6 +451,7 @@ macro_rules! graph_topology_tests { #[test] fn edges_empty() { + use $crate::traits::GraphTopology; let graph = <$T>::new(); assert_eq!( graph.edges().count(), @@ -437,6 +462,7 @@ macro_rules! graph_topology_tests { #[test] fn edges() { + use $crate::traits::GraphTopology; let (graph, _, edges, _) = make_test_graph(); assert_eq!(graph.edges().count(), 18, "unexpected edge count"); @@ -452,6 +478,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_edges_empty() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -463,6 +490,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_edges() { + use $crate::traits::GraphTopology; let (graph, vertices, _, incidences) = make_test_graph(); for i in 0..10 { assert_eq!( @@ -493,6 +521,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_edges_loop_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); let e = graph.add_edge(v, v); @@ -508,6 +537,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_edges_multiple_edges() { + use $crate::traits::GraphTopology; let k = 3; let mut graph = <$T>::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; @@ -538,6 +568,7 @@ macro_rules! graph_topology_tests { #[test] fn incidences_empty() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -549,6 +580,7 @@ macro_rules! graph_topology_tests { #[test] fn incidences() { + use $crate::traits::GraphTopology; let (graph, vertices, _, incidences) = make_test_graph(); for i in 0..10 { assert_eq!( @@ -579,6 +611,7 @@ macro_rules! graph_topology_tests { #[test] fn incidences_edge_consistency() { + use $crate::traits::GraphTopology; // For each incidence (v, e) of u, the same edge e must appear in the incidences of v. // For loop edges (u == v), the edge must appear exactly twice in the incidences of u. let (graph, _, _, _) = make_test_graph(); @@ -602,6 +635,7 @@ macro_rules! graph_topology_tests { #[test] fn incidences_loop_edge() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); let e = graph.add_edge(v, v); @@ -625,6 +659,7 @@ macro_rules! graph_topology_tests { #[test] fn incidences_multiple_edges() { + use $crate::traits::GraphTopology; let k = 3; let mut graph = <$T>::new(); let vertices = [graph.add_vertex(), graph.add_vertex()]; @@ -664,6 +699,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_vertices_incidences_consistency() { + use $crate::traits::GraphTopology; let (graph, _, _, _) = make_test_graph(); for u in graph.vertices() { for (v, e) in graph.incidences(u) { @@ -678,6 +714,7 @@ macro_rules! graph_topology_tests { #[test] fn incident_edges_incidences_consistency() { + use $crate::traits::GraphTopology; let (graph, _, _, _) = make_test_graph(); for u in graph.vertices() { let mut expected: Vec<_> = graph.incidences(u).map(|(_, e)| e).collect(); @@ -705,6 +742,8 @@ macro_rules! graph_topology_deletion_tests { ($T:ty) => { #[test] fn delete_vertex() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v = graph.add_vertex(); assert_eq!( @@ -727,6 +766,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_loop() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v = graph.add_vertex(); graph.add_edge(v, v); @@ -757,6 +798,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_invalid_index() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v = graph.add_vertex(); graph.delete_vertex(v); @@ -766,6 +809,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_connected() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, _, _) = make_test_graph(); assert_eq!( graph.vertex_count(), @@ -814,6 +859,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_edge() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -860,6 +907,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_edge_loop() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v = graph.add_vertex(); let e = graph.add_edge(v, v); @@ -895,6 +944,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_edge_multiple() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -942,6 +993,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_edge_invalid_index() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -953,6 +1006,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn vertices_after_delete() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, _, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!( @@ -983,6 +1038,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incident_vertices_after_delete_edge() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, _, edges, _) = make_test_graph(); let e = edges[2].0; graph.delete_edge(e); @@ -997,6 +1054,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn edges_after_delete() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, edges, _) = make_test_graph(); graph.delete_vertex(vertices[2]); assert_eq!(graph.edge_count(), 12, "unexpected edge count after delete"); @@ -1023,6 +1082,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incident_edges_after_delete_edge() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, edges, incidences) = make_test_graph(); graph.delete_edge(edges[2].0); for i in 0..10 { @@ -1055,6 +1116,8 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incident_edges_after_delete_vertex() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, _, incidences) = make_test_graph(); graph.delete_vertex(vertices[2]); for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { @@ -1087,6 +1150,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incidences_after_delete_vertex() { + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, _, incidences) = make_test_graph(); graph.delete_vertex(vertices[2]); for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] { @@ -1101,6 +1165,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn incidences_after_delete_edge() { + use $crate::traits::GraphTopologyDeletion; let (mut graph, vertices, edges, incidences) = make_test_graph(); // Deletes the edge from vertices[1] to vertices[2]. graph.delete_edge(edges[2].0); @@ -1122,6 +1187,7 @@ macro_rules! graph_topology_deletion_tests { <$T as $crate::traits::GraphTopology>::Edge, )>, ) { + use $crate::traits::GraphTopology; assert_eq!( graph.incidences(v).count(), expected.len(), diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs index 5aad6f9..fdac415 100644 --- a/src/testing/maps_testing.rs +++ b/src/testing/maps_testing.rs @@ -3,6 +3,7 @@ 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(); @@ -13,6 +14,7 @@ macro_rules! vertex_map_tests { #[test] fn write_and_read() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -24,6 +26,7 @@ macro_rules! vertex_map_tests { #[test] fn lazy_growth_on_read() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); graph.add_vertex(); let map = graph.vertex_map(99); @@ -33,6 +36,7 @@ macro_rules! vertex_map_tests { #[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); @@ -44,6 +48,7 @@ macro_rules! vertex_map_tests { #[test] fn sync_expands_to_new_vertices() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); graph.add_vertex(); let mut map = graph.vertex_map(42); @@ -61,6 +66,7 @@ macro_rules! vertex_map_tests { #[test] fn sync_does_not_overwrite_existing_values() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); let mut map = graph.vertex_map(0); @@ -77,6 +83,8 @@ 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(); @@ -89,6 +97,8 @@ macro_rules! vertex_map_deletion_tests { #[test] fn capacity_does_not_shrink_after_delete() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -103,6 +113,8 @@ macro_rules! vertex_map_deletion_tests { #[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(); @@ -123,6 +135,7 @@ 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(); @@ -135,6 +148,7 @@ macro_rules! edge_map_tests { #[test] fn write_and_read() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -148,6 +162,7 @@ macro_rules! edge_map_tests { #[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(); @@ -159,6 +174,7 @@ macro_rules! edge_map_tests { #[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(); @@ -172,6 +188,7 @@ macro_rules! edge_map_tests { #[test] fn sync_expands_to_new_edges() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -191,6 +208,7 @@ macro_rules! edge_map_tests { #[test] fn sync_does_not_overwrite_existing_values() { + use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -209,6 +227,8 @@ 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(); @@ -223,6 +243,8 @@ macro_rules! edge_map_deletion_tests { #[test] fn capacity_does_not_shrink_after_delete() { + use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyDeletion; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -239,6 +261,8 @@ macro_rules! edge_map_deletion_tests { #[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(); diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 1e4db9f..5a54b49 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -1,6 +1,5 @@ mod append_graph_tests { use grapherity::models::append_graph::AppendGraph; - use grapherity::traits::GraphTopology; grapherity::graph_topology_test_fixtures!(AppendGraph); grapherity::dijkstra_tests!(AppendGraph); @@ -8,7 +7,6 @@ mod append_graph_tests { mod graph_tests { use grapherity::models::graph::Graph; - use grapherity::traits::GraphTopology; grapherity::graph_topology_test_fixtures!(Graph); grapherity::dijkstra_tests!(Graph); diff --git a/tests/maps.rs b/tests/maps.rs index c242a77..0b4e2f7 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -1,20 +1,17 @@ mod append_graph_vertex_map_tests { use grapherity::models::append_graph::AppendGraph; - use grapherity::traits::GraphTopology; grapherity::vertex_map_tests!(AppendGraph); } mod append_graph_edge_map_tests { use grapherity::models::append_graph::AppendGraph; - use grapherity::traits::GraphTopology; grapherity::edge_map_tests!(AppendGraph); } mod graph_vertex_map_tests { use grapherity::models::graph::Graph; - use grapherity::traits::{GraphTopology, GraphTopologyDeletion}; grapherity::vertex_map_tests!(Graph); grapherity::vertex_map_deletion_tests!(Graph); @@ -22,7 +19,6 @@ mod graph_vertex_map_tests { mod graph_edge_map_tests { use grapherity::models::graph::Graph; - use grapherity::traits::{GraphTopology, GraphTopologyDeletion}; grapherity::edge_map_tests!(Graph); grapherity::edge_map_deletion_tests!(Graph); -- 2.43.0 From 3adfb4b9ebc86551d854eb861a5126b37419406f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 18 May 2026 16:15:46 +0200 Subject: [PATCH 092/102] Add todo for custom weight type for Dijkstra's algorithm --- src/algorithms.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/algorithms.rs b/src/algorithms.rs index 01a6c31..10736bb 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -27,6 +27,7 @@ pub struct DijkstraResult { pub predecessors: VertexMap>, } +// TODO: Generalize the return type of the weight function. pub fn dijkstra(graph: &G, source: G::Vertex, weight: W) -> DijkstraResult where G: GraphTopology, -- 2.43.0 From e29901294883973b4ebb4082ba0b21354306cf95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 25 Jun 2026 20:17:55 +0200 Subject: [PATCH 093/102] Move the disconnected test graph to the more general graph_topology_test_fixtures macro --- src/testing/dijkstra_testing.rs | 16 +++------------- src/testing/graph_topology_testing.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index bf6300b..bbb2579 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -14,7 +14,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_disconnected() { - let (graph, vertices) = make_test_graph_dijkstra_disconnected(); + let (graph, vertices) = make_test_graph_disconnected(); let result = $crate::algorithms::dijkstra(&graph, vertices[0], |_| { panic!("unexpected call of weight functor") }); @@ -79,7 +79,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_unweighted_disconnected() { - let (graph, vertices) = make_test_graph_dijkstra_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]); @@ -103,7 +103,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_distances_unweighted_disconnected() { - let (graph, vertices) = make_test_graph_dijkstra_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]); @@ -287,16 +287,6 @@ macro_rules! dijkstra_tests { } } - fn make_test_graph_dijkstra_disconnected() - -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { - 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()); - graph.add_edge(vertices[1], vertices[2]); - (graph, vertices) - } - fn make_test_graph_weighted() -> ( $T, [<$T as $crate::traits::GraphTopology>::Vertex; 10], diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index cbaa1a3..de09aaf 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -87,6 +87,17 @@ macro_rules! graph_topology_test_fixtures { ]; (graph, vertices, edges, incidences) } + + #[allow(dead_code)] + fn make_test_graph_disconnected() + -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { + 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()); + graph.add_edge(vertices[1], vertices[2]); + (graph, vertices) + } }; } -- 2.43.0 From 91ac925417eb2b95f79ddbb7255387b8c1936f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 25 Jun 2026 21:54:49 +0200 Subject: [PATCH 094/102] Add breadth-first search algorithms and tests --- src/algorithms.rs | 77 ++++++++++- src/testing.rs | 1 + src/testing/bfs_testing.rs | 253 +++++++++++++++++++++++++++++++++++++ tests/bfs.rs | 13 ++ 4 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/testing/bfs_testing.rs create mode 100644 tests/bfs.rs diff --git a/src/algorithms.rs b/src/algorithms.rs index 10736bb..4a7e57d 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,5 +1,5 @@ use std::cmp::Ordering; -use std::collections::BinaryHeap; +use std::collections::{BinaryHeap, VecDeque}; use crate::maps::VertexMap; use crate::traits::GraphTopology; @@ -111,3 +111,78 @@ where } distances } + +pub struct BfsResult { + pub distances: VertexMap>, + pub predecessors: VertexMap>, +} + +pub fn bfs(graph: &G, source: G::Vertex) -> BfsResult +where + G: GraphTopology, +{ + let mut predecessors = graph.vertex_map(None); + let (distances, _) = bfs_impl(graph, source, |neighbor, predecessor| { + predecessors[neighbor] = Some(predecessor); + true + }); + BfsResult { + distances, + predecessors, + } +} + +pub fn bfs_distances(graph: &G, source: G::Vertex) -> VertexMap> +where + G: GraphTopology, +{ + bfs_impl(graph, source, |_, _| true).0 +} + +pub fn bfs_find(graph: &G, source: G::Vertex, target: G::Vertex) -> Option +where + G: GraphTopology, +{ + bfs_find_where(graph, source, |v| v == target).map(|(_, distance)| distance) +} + +pub fn bfs_find_where(graph: &G, source: G::Vertex, predicate: P) -> Option<(G::Vertex, u32)> +where + G: GraphTopology, + P: Fn(G::Vertex) -> bool, +{ + if predicate(source) { + return Some((source, 0)); + } + bfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1 +} + +fn bfs_impl( + graph: &G, + source: G::Vertex, + mut on_discover: F, +) -> (VertexMap>, Option<(G::Vertex, u32)>) +where + G: GraphTopology, + F: FnMut(G::Vertex, G::Vertex) -> bool, +{ + let mut distances = graph.vertex_map(None); + let mut queue = VecDeque::new(); + + distances[source] = Some(0); + queue.push_back(source); + + while let Some(v) = queue.pop_front() { + for neighbor in graph.adjacent_vertices(v) { + if distances[neighbor].is_none() { + let distance = distances[v].unwrap() + 1; + distances[neighbor] = Some(distance); + if !on_discover(neighbor, v) { + return (distances, Some((neighbor, distance))); + } + queue.push_back(neighbor); + } + } + } + (distances, None) +} diff --git a/src/testing.rs b/src/testing.rs index 0d4f69d..c262cb1 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,3 +1,4 @@ +pub(crate) mod bfs_testing; pub(crate) mod dijkstra_testing; pub(crate) mod graph_topology_testing; pub(crate) mod maps_testing; diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs new file mode 100644 index 0000000..49f75c5 --- /dev/null +++ b/src/testing/bfs_testing.rs @@ -0,0 +1,253 @@ +#[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(); + let result = $crate::algorithms::bfs_find_where(&graph, v, |u| u == v); + assert_eq!(result, Some((v, 0)), "source should match with distance 0"); + } + + #[test] + fn bfs_find_where_disconnected() { + let (graph, vertices) = make_test_graph_disconnected(); + let target = vertices[1]; + assert_eq!( + $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| v == target), + None, + "disconnected vertex should not be found" + ); + } + + #[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, + >, + 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] + ); + } + } + }; +} diff --git a/tests/bfs.rs b/tests/bfs.rs new file mode 100644 index 0000000..52996e5 --- /dev/null +++ b/tests/bfs.rs @@ -0,0 +1,13 @@ +mod append_graph_tests { + use grapherity::models::append_graph::AppendGraph; + + grapherity::graph_topology_test_fixtures!(AppendGraph); + grapherity::bfs_tests!(AppendGraph); +} + +mod graph_tests { + use grapherity::models::graph::Graph; + + grapherity::graph_topology_test_fixtures!(Graph); + grapherity::bfs_tests!(Graph); +} -- 2.43.0 From 135f2561415f158ded5fe30ec6fa841225675281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 26 Jun 2026 15:34:42 +0200 Subject: [PATCH 095/102] Add depth-first search algorithms and tests --- src/algorithms.rs | 74 ++++++++++++++ src/testing.rs | 1 + src/testing/dfs_testing.rs | 201 +++++++++++++++++++++++++++++++++++++ tests/dfs.rs | 13 +++ 4 files changed, 289 insertions(+) create mode 100644 src/testing/dfs_testing.rs create mode 100644 tests/dfs.rs diff --git a/src/algorithms.rs b/src/algorithms.rs index 4a7e57d..d2000d5 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -186,3 +186,77 @@ where } (distances, None) } + +pub struct DfsResult { + pub visited: VertexMap, + pub predecessors: VertexMap>, +} + +pub fn dfs(graph: &G, source: G::Vertex) -> DfsResult +where + G: GraphTopology, +{ + let mut predecessors = graph.vertex_map(None); + let (visited, _) = dfs_impl(graph, source, |neighbor, predecessor| { + predecessors[neighbor] = Some(predecessor); + true + }); + DfsResult { + visited, + predecessors, + } +} + +pub fn dfs_visited(graph: &G, source: G::Vertex) -> VertexMap +where + G: GraphTopology, +{ + dfs_impl(graph, source, |_, _| true).0 +} + +pub fn dfs_find(graph: &G, source: G::Vertex, target: G::Vertex) -> bool +where + G: GraphTopology, +{ + dfs_find_where(graph, source, |v| v == target).is_some() +} + +pub fn dfs_find_where(graph: &G, source: G::Vertex, predicate: P) -> Option +where + G: GraphTopology, + P: Fn(G::Vertex) -> bool, +{ + if predicate(source) { + return Some(source); + } + dfs_impl(graph, source, |neighbor, _| !predicate(neighbor)).1 +} + +fn dfs_impl( + graph: &G, + source: G::Vertex, + mut on_discover: F, +) -> (VertexMap, Option) +where + G: GraphTopology, + F: FnMut(G::Vertex, G::Vertex) -> bool, +{ + let mut visited = graph.vertex_map(false); + visited[source] = true; + let mut stack = vec![(source, None::)]; + + while let Some((v, predecessor)) = stack.pop() { + if let Some(p) = predecessor { + if !on_discover(v, p) { + return (visited, Some(v)); + } + } + for neighbor in graph.adjacent_vertices(v) { + if !visited[neighbor] { + visited[neighbor] = true; + stack.push((neighbor, Some(v))); + } + } + } + (visited, None) +} diff --git a/src/testing.rs b/src/testing.rs index c262cb1..c8ded87 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,4 +1,5 @@ pub(crate) mod bfs_testing; +pub(crate) mod dfs_testing; pub(crate) mod dijkstra_testing; pub(crate) mod graph_topology_testing; pub(crate) mod maps_testing; diff --git a/src/testing/dfs_testing.rs b/src/testing/dfs_testing.rs new file mode 100644 index 0000000..255445d --- /dev/null +++ b/src/testing/dfs_testing.rs @@ -0,0 +1,201 @@ +#[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" + ); + } + } + }; +} diff --git a/tests/dfs.rs b/tests/dfs.rs new file mode 100644 index 0000000..4524c57 --- /dev/null +++ b/tests/dfs.rs @@ -0,0 +1,13 @@ +mod append_graph_tests { + use grapherity::models::append_graph::AppendGraph; + + grapherity::graph_topology_test_fixtures!(AppendGraph); + grapherity::dfs_tests!(AppendGraph); +} + +mod graph_tests { + use grapherity::models::graph::Graph; + + grapherity::graph_topology_test_fixtures!(Graph); + grapherity::dfs_tests!(Graph); +} -- 2.43.0 From c147ed4974822829c87b4a28a1ca61ec69e0e888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 26 Jun 2026 15:35:23 +0200 Subject: [PATCH 096/102] Update breadth-first search tests --- src/testing/bfs_testing.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs index 49f75c5..7d49f92 100644 --- a/src/testing/bfs_testing.rs +++ b/src/testing/bfs_testing.rs @@ -141,18 +141,21 @@ macro_rules! bfs_tests { use $crate::traits::GraphTopology; let mut graph = <$T>::new(); let v = graph.add_vertex(); - let result = $crate::algorithms::bfs_find_where(&graph, v, |u| u == v); - assert_eq!(result, Some((v, 0)), "source should match with distance 0"); + 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(); - let target = vertices[1]; assert_eq!( - $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| v == target), + $crate::algorithms::bfs_find_where(&graph, vertices[0], |v| v == vertices[1]), None, - "disconnected vertex should not be found" + "disconnected vertex {:?} should not be found", + vertices[1] ); } -- 2.43.0 From c197766229a67b179622a3f7c5c9aea3d4d9fc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 26 Jun 2026 15:38:49 +0200 Subject: [PATCH 097/102] Fix formatting --- src/testing/bfs_testing.rs | 3 ++- src/testing/dfs_testing.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs index 7d49f92..532d289 100644 --- a/src/testing/bfs_testing.rs +++ b/src/testing/bfs_testing.rs @@ -187,7 +187,8 @@ macro_rules! bfs_tests { assert!( found == vertices[7] || found == vertices[8], "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", - vertices[7], vertices[8] + vertices[7], + vertices[8] ); } diff --git a/src/testing/dfs_testing.rs b/src/testing/dfs_testing.rs index 255445d..205f020 100644 --- a/src/testing/dfs_testing.rs +++ b/src/testing/dfs_testing.rs @@ -157,7 +157,8 @@ macro_rules! dfs_tests { assert_eq!( $crate::algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), Some(vertices[9]), - "expected to find connected vertex"); + "expected to find connected vertex" + ); } fn assert_dfs_visited( @@ -190,7 +191,10 @@ macro_rules! dfs_tests { 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!( + visited[p], + "predecessor {p:?} of vertex {v:?} should be visited" + ); assert!( graph.are_adjacent(v, p), "predecessor {p:?} of vertex {v:?} should be adjacent" -- 2.43.0 From f009a86f1e66f6369af0a7bbe8f3b80ef36c2392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 26 Jun 2026 23:36:41 +0200 Subject: [PATCH 098/102] Add GraphTopology.incidence_cursor() for certain use-cases where the iterator is problematic Also add raw_incidences() and step_incidence() to AppendGraph analogously to Graph, which allows to remove RawIncidenceIterator and simplify its call sites. --- src/models/append_graph.rs | 52 ++++++++++++++++++++------------------ src/models/graph.rs | 23 +++++++++++++++-- src/traits.rs | 6 +++++ 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index d0abc41..65657f8 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,5 +1,5 @@ use crate::maps::{EdgeMap, VertexMap}; -use crate::traits::GraphTopology; +use crate::traits::{GraphTopology, IncidenceCursor}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Vertex(usize); @@ -18,24 +18,20 @@ struct VertexIncidenceHeader { first_incidence: Option, } +#[derive(Copy, Clone)] struct IncidenceEntry { next: Option, adjacent: Vertex, } -struct RawIncidenceIterator<'a> { - graph: &'a AppendGraph, +#[derive(Copy, Clone)] +pub struct AppendGraphIncidenceCursor { incidence: Option, } -impl<'a> Iterator for RawIncidenceIterator<'a> { - type Item = (Vertex, Incidence); - - fn next(&mut self) -> Option { - let current = self.incidence?; - let entry = &self.graph.incidences[current.0]; - self.incidence = entry.next; - Some((entry.adjacent, current)) +impl IncidenceCursor for AppendGraphIncidenceCursor { + fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Incidence)> { + graph.step_incidence(&mut self.incidence) } } @@ -62,6 +58,18 @@ impl AppendGraph { self.vertices[v1.0].incidence_count += 1; self.vertices[v1.0].first_incidence = Some(Incidence(self.incidences.len() - 1)); } + + fn raw_incidences(&self, v: Vertex) -> impl Iterator { + let mut incidence = self.vertices[v.0].first_incidence; + std::iter::from_fn(move || self.step_incidence(&mut incidence)) + } + + fn step_incidence(&self, incidence: &mut Option) -> Option<(Vertex, Incidence)> { + let current = (*incidence)?; + let entry = self.incidences[current.0]; + *incidence = entry.next; + Some((entry.adjacent, current)) + } } impl Default for AppendGraph { @@ -72,8 +80,8 @@ impl Default for AppendGraph { impl GraphTopology for AppendGraph { type Vertex = Vertex; - type Edge = Incidence; + type IncidenceCursor = AppendGraphIncidenceCursor; fn vertex_count(&self) -> usize { self.vertices.len() @@ -112,11 +120,7 @@ impl GraphTopology for AppendGraph { } fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator { - RawIncidenceIterator { - graph: self, - incidence: self.vertices[v.0].first_incidence, - } - .map(|(v, _)| v) + self.raw_incidences(v).map(|(v, _)| v) } fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) { @@ -130,19 +134,17 @@ impl GraphTopology for AppendGraph { } fn incident_edges(&self, v: Self::Vertex) -> impl Iterator { - RawIncidenceIterator { - graph: self, - incidence: self.vertices[v.0].first_incidence, - } - .map(|(_, e)| e.normalize()) + self.raw_incidences(v).map(|(_, e)| e.normalize()) } fn incidences(&self, v: Self::Vertex) -> impl Iterator { - RawIncidenceIterator { - graph: self, + self.raw_incidences(v).map(|(v, e)| (v, e.normalize())) + } + + fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { + AppendGraphIncidenceCursor { incidence: self.vertices[v.0].first_incidence, } - .map(|(v, e)| (v, e.normalize())) } fn add_vertex(&mut self) -> Self::Vertex { diff --git a/src/models/graph.rs b/src/models/graph.rs index 025d388..309d268 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,7 +1,7 @@ use typed_generational_arena::{Arena, Index}; use crate::maps::{EdgeMap, VertexMap}; -use crate::traits::{GraphTopology, GraphTopologyDeletion}; +use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor}; type Vertex = Index; @@ -34,6 +34,19 @@ impl IncidentEdgeCursor { } } +#[derive(Copy, Clone)] +pub struct GraphIncidenceCursor { + incidence: Option, +} + +impl IncidenceCursor 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(), e)) + } +} + pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -137,8 +150,8 @@ impl Default for Graph { impl GraphTopology for Graph { type Vertex = Vertex; - type Edge = Edge; + type IncidenceCursor = GraphIncidenceCursor; fn vertex_count(&self) -> usize { self.vertices.len() @@ -210,6 +223,12 @@ impl GraphTopology for Graph { .map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e))) } + fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor { + GraphIncidenceCursor { + incidence: self.vertices[v].first_incidence, + } + } + fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { incidence_count: 0, diff --git a/src/traits.rs b/src/traits.rs index fecd07c..e3112bb 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,6 +5,7 @@ use crate::maps::{EdgeMap, VertexMap}; pub trait GraphTopology { type Vertex: Copy + Eq; type Edge: Copy + Eq; + type IncidenceCursor: IncidenceCursor + Copy; fn vertex_count(&self) -> usize; fn vertex_capacity(&self) -> usize; @@ -20,6 +21,7 @@ pub trait GraphTopology { fn edges(&self) -> impl Iterator; fn incident_edges(&self, v: Self::Vertex) -> impl Iterator; fn incidences(&self, v: Self::Vertex) -> impl Iterator; + fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor; fn add_vertex(&mut self) -> Self::Vertex; fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } @@ -28,3 +30,7 @@ pub trait GraphTopologyDeletion: GraphTopology { fn delete_vertex(&mut self, v: Self::Vertex); fn delete_edge(&mut self, e: Self::Edge); } + +pub trait IncidenceCursor { + fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>; +} -- 2.43.0 From 21c95b4796747dc40368212494acc941737b9d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 29 Jun 2026 21:03:10 +0200 Subject: [PATCH 099/102] Add find_path algorithm and tests --- src/algorithms.rs | 56 +++++++++++++- src/testing.rs | 1 + src/testing/find_path_testing.rs | 123 +++++++++++++++++++++++++++++++ tests/find_path.rs | 13 ++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 src/testing/find_path_testing.rs create mode 100644 tests/find_path.rs diff --git a/src/algorithms.rs b/src/algorithms.rs index d2000d5..a2620b8 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use std::collections::{BinaryHeap, VecDeque}; use crate::maps::VertexMap; -use crate::traits::GraphTopology; +use crate::traits::{GraphTopology, IncidenceCursor}; #[derive(PartialEq, Eq)] struct DistanceOrderedVertex { @@ -260,3 +260,57 @@ where } (visited, None) } + +pub fn find_path(graph: &G, source: G::Vertex, target: G::Vertex) -> Option> +where + G: GraphTopology, +{ + find_path_where(graph, source, |v| v == target) +} + +pub fn find_path_where(graph: &G, source: G::Vertex, predicate: P) -> Option> +where + G: GraphTopology, + P: Fn(G::Vertex) -> bool, +{ + if predicate(source) { + return Some(vec![]); + } + + let mut visited = graph.vertex_map(false); + visited[source] = true; + + struct Frame { + arrival_edge: Option, + cursor: G::IncidenceCursor, + } + + let mut stack: Vec> = vec![Frame { + arrival_edge: None, + cursor: graph.incidence_cursor(source), + }]; + + while let Some(frame) = stack.last_mut() { + match frame.cursor.next(graph) { + None => { + stack.pop(); + } + Some((neighbor, edge)) => { + if predicate(neighbor) { + let mut path: Vec = + stack.iter().filter_map(|f| f.arrival_edge).collect(); + path.push(edge); + return Some(path); + } + if !visited[neighbor] { + visited[neighbor] = true; + stack.push(Frame { + arrival_edge: Some(edge), + cursor: graph.incidence_cursor(neighbor), + }); + } + } + } + } + None +} diff --git a/src/testing.rs b/src/testing.rs index c8ded87..a345512 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,5 +1,6 @@ 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; diff --git a/src/testing/find_path_testing.rs b/src/testing/find_path_testing.rs new file mode 100644 index 0000000..c7cfbdc --- /dev/null +++ b/src/testing/find_path_testing.rs @@ -0,0 +1,123 @@ +#[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::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::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::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::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::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::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::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::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:?}" + ); + } + }; +} diff --git a/tests/find_path.rs b/tests/find_path.rs new file mode 100644 index 0000000..c46085e --- /dev/null +++ b/tests/find_path.rs @@ -0,0 +1,13 @@ +mod append_graph_tests { + use grapherity::models::append_graph::AppendGraph; + + grapherity::graph_topology_test_fixtures!(AppendGraph); + grapherity::find_path_tests!(AppendGraph); +} + +mod graph_tests { + use grapherity::models::graph::Graph; + + grapherity::graph_topology_test_fixtures!(Graph); + grapherity::find_path_tests!(Graph); +} -- 2.43.0 From fb64e370dcd4ae1d29669ef796fd85aeb382c362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Mon, 29 Jun 2026 21:04:48 +0200 Subject: [PATCH 100/102] Update readme and license information --- CONTRIBUTING.md | 10 +++ LICENSE | 9 --- LICENSE-APACHE | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 18 +++++ NOTICE | 7 ++ README.md | 17 ++++- 6 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 NOTICE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9f86fcf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing + +By submitting a contribution (pull request, patch, or commit), you agree that: + +1. You have the right to license the contribution. +2. Your contribution is licensed under the project's dual license: + - MIT License + - Apache License 2.0 + +If you do not agree, please do not submit contributions. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 444ba28..0000000 --- a/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2025 warrence - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..210b87d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,18 @@ +Copyright 2025-2026 Stefan Müller + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..84097e3 --- /dev/null +++ b/NOTICE @@ -0,0 +1,7 @@ +This product includes software developed by Stefan Müller. + +Copyright 2025-2026 Stefan Müller + +Licensed under the MIT License and the Apache License 2.0. + +No additional attribution notices. diff --git a/README.md b/README.md index 81671cb..f8ce1bc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ # grapherity -Graph editing, algorithms, and visualization \ No newline at end of file +Library for graph models and algorithms. + +This library is still experimental and its API may therefore change frequently. + +## License + +This project is dual-licensed under the terms of both: + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit) + +You may choose either license for your use of this software. + +### Contributions + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -- 2.43.0 From 6b742a53f5f828a26039170db29388a9fc87c55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 30 Jun 2026 10:21:05 +0200 Subject: [PATCH 101/102] Update cargo package metadata --- Cargo.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 80cdb46..1b5961e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "grapherity" version = "0.1.0" +authors = ["Stefan Müller"] edition = "2024" +rust-version = "1.85.0" +description = "Graph models and algorithms." +repository = "https://git.aksdb.de/warrence/grapherity" +license = "MIT OR Apache-2.0" +keywords = ["graph", "graph-algorithms"] +categories = ["algorithms", "data-structures", "mathematics"] [dependencies] typed-generational-arena = "0.2.9" -- 2.43.0 From db79da7301bbbef3659a9e4021f9a784bc606bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 30 Jun 2026 10:23:27 +0200 Subject: [PATCH 102/102] Update cargo package metadata --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1b5961e..a994a96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Stefan Müller"] edition = "2024" rust-version = "1.85.0" -description = "Graph models and algorithms." +description = "Graph models and algorithms" repository = "https://git.aksdb.de/warrence/grapherity" license = "MIT OR Apache-2.0" keywords = ["graph", "graph-algorithms"] -- 2.43.0