From 3fa4b669815691600da1f5b66b0fe7dc6804a415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 22:06:28 +0200 Subject: [PATCH] Fix IncidenceCursor for AppendGraph and Graph, and add tests --- src/models/append_graph.rs | 4 +- src/models/graph.rs | 16 +- src/testing/graph_topology_testing.rs | 212 ++++++++++++++++++++++++-- 3 files changed, 212 insertions(+), 20 deletions(-) diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 9ccfef7..9ca63b3 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -34,7 +34,9 @@ pub struct AppendGraphIncidenceCursor { impl IncidenceCursor for AppendGraphIncidenceCursor { fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> { - graph.step_incidence(&mut self.incidence) + graph + .step_incidence(&mut self.incidence) + .map(|(v, e)| (v, e.normalize())) } } diff --git a/src/models/graph.rs b/src/models/graph.rs index ed86205..b7cb736 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -43,9 +43,12 @@ pub struct GraphIncidenceCursor { 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)) + graph.step_incidence(&mut self.incidence).map(|(vs, e)| { + ( + graph.vertices.get_idx(vs.0).unwrap(), + graph.normalize_edge(e), + ) + }) } } @@ -140,7 +143,12 @@ impl Graph { } fn normalize_edge(&self, e: Edge) -> Edge { - self.incidences.get_idx(e.arr_idx() & !1).unwrap() + let i = e.arr_idx(); + if i & 1 == 0 { + e + } else { + self.incidences.get_idx(i ^ 1).unwrap() + } } } diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 400290c..cd46e13 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -298,7 +298,7 @@ macro_rules! graph_topology_tests { assert_eq!( vertices.iter().filter(|&x| *x == v).count(), 1, - "unexpected vertex {v:?} from the iterator" + "unexpected vertex {v:?} from iterator" ); } } @@ -339,7 +339,7 @@ macro_rules! graph_topology_tests { .iter() .position(|w| *w == v) .expect(&format!( - "unexpected adjacent vertex {v:?} of {:?} from the iterator", + "unexpected adjacent vertex {v:?} of {:?} from iterator", vertices[4] )); expected_adjacency.swap_remove(i); @@ -347,7 +347,7 @@ macro_rules! graph_topology_tests { assert_eq!( expected_adjacency.len(), 0, - "expected adjacent vertices {:?} of {:?} were not matched by the iterator", + "expected adjacent vertices {:?} of {:?} were not matched by iterator", expected_adjacency, vertices[4] ); @@ -484,7 +484,7 @@ macro_rules! graph_topology_tests { assert_eq!( edges.iter().filter(|&&(f, _, _)| f == e).count(), 1, - "unexpected edge {e:?} from the iterator" + "unexpected edge {e:?} from iterator" ); } } @@ -518,14 +518,14 @@ macro_rules! graph_topology_tests { .iter() .position(|f| *f == e) .expect(&format!( - "unexpected incident edge {e:?} of vertex {:?} from the iterator", + "unexpected incident edge {e:?} of vertex {:?} from iterator", vertices[i] )); expected.swap_remove(pos); } assert!( expected.is_empty(), - "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + "expected incident edges {:?} of vertex {:?} were not matched by iterator", expected, vertices[i] ); @@ -572,7 +572,7 @@ macro_rules! graph_topology_tests { } assert!( expected.is_empty(), - "expected incident edges {:?} of vertex {:?} were not matched by the iterator", + "expected incident edges {:?} of vertex {:?} were not matched by iterator", expected, vertices[i] ); @@ -608,14 +608,14 @@ macro_rules! graph_topology_tests { .iter() .position(|(v, e)| *v == incidence.0 && *e == incidence.1) .expect(&format!( - "unexpected incidence {incidence:?} of vertex {:?} from the iterator", + "unexpected incidence {incidence:?} of vertex {:?} from iterator", vertices[i] )); expected.swap_remove(pos); } assert!( expected.is_empty(), - "expected incidences {:?} of vertex {:?} were not matched by the iterator", + "expected incidences {:?} of vertex {:?} were not matched by iterator", expected, vertices[i] ); @@ -710,6 +710,131 @@ macro_rules! graph_topology_tests { } } + #[test] + fn incidence_cursor_empty() { + use $crate::traits::{GraphTopology, IncidenceCursor}; + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let mut cursor = graph.incidence_cursor(v); + assert_eq!( + cursor.next(&graph), + None, + "incidence cursor of vertex with degree 0 should immediately be exhausted" + ); + } + + #[test] + fn incidence_cursor() { + use $crate::traits::{GraphTopology, IncidenceCursor}; + let (graph, vertices, _, incidences) = make_test_graph(); + for i in 0..10 { + let mut expected = incidences[i].clone(); + let mut cursor = graph.incidence_cursor(vertices[i]); + while let Some(incidence) = cursor.next(&graph) { + let pos = expected + .iter() + .position(|(v, e)| *v == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} from cursor", + vertices[i] + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {:?} of vertex {:?} were not matched by cursor", + expected, + vertices[i] + ); + } + } + + #[test] + fn incidence_cursor_loop_edge() { + use $crate::traits::{GraphTopology, IncidenceCursor}; + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let e = graph.add_edge(v, v); + let mut cursor = graph.incidence_cursor(v); + assert_eq!( + cursor.next(&graph), + Some((v, e)), + "vertex should be adjacent to itself" + ); + assert_eq!( + cursor.next(&graph), + Some((v, e)), + "vertex should be adjacent to itself twice" + ); + assert_eq!( + cursor.next(&graph), + None, + "too many incidences from cursor" + ); + } + + #[test] + fn incidence_cursor_multiple_edges() { + use $crate::traits::{GraphTopology, IncidenceCursor}; + 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 cursor = graph.incidence_cursor(vertices[i]); + for j in 0..k { + let current = cursor.next(&graph).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], + ); + } + assert_eq!( + cursor.next(&graph), + None, + "too many incidences of {:?} from cursor", + vertices[i] + ); + } + } + + #[test] + fn incidence_cursor_copy() { + use $crate::traits::{GraphTopology, IncidenceCursor}; + // Constructs a graph with two vertices connected to `v`. + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + for _ in 0..2 { + let u = graph.add_vertex(); + graph.add_edge(u, v); + } + let mut c1 = graph.incidence_cursor(v); + assert!(c1.next(&graph).is_some(), "expected first incidence"); + // Copies cursor mid-traversal. + let mut c2 = c1; + // Continues iteration with original cursor. + assert!(c1.next(&graph).is_some(), "expected second incidence from original cursor"); + assert!(c1.next(&graph).is_none(), "expected original cursor to be exhausted"); + // Replays from the copy point with the copied cursor. + assert!(c2.next(&graph).is_some(), "expected second incidence from copied cursor"); + assert!(c2.next(&graph).is_none(), "expected copied cursor to be exhausted"); + } + #[test] fn incident_vertices_incidences_consistency() { use $crate::traits::GraphTopology; @@ -1103,8 +1228,7 @@ macro_rules! graph_topology_deletion_tests { for i in 0..10 { let mut expected: Vec<_> = incidences[i] .iter() - .filter(|(_, e)| *e != edges[2].0) - .map(|(_, e)| *e) + .filter_map(|(_, e)| (*e != edges[2].0).then_some(*e)) .collect(); assert_eq!( graph.incident_edges(vertices[i]).count(), @@ -1137,8 +1261,7 @@ macro_rules! graph_topology_deletion_tests { 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) + .filter_map(|(v, e)| (*v != vertices[2]).then_some(*e)) .collect(); assert_eq!( graph.incident_edges(vertices[i]).count(), @@ -1213,14 +1336,73 @@ macro_rules! graph_topology_deletion_tests { .iter() .position(|(u, e)| *u == incidence.0 && *e == incidence.1) .expect(&format!( - "unexpected incidence {incidence:?} of vertex {:?} after delete", + "unexpected incidence {incidence:?} of vertex {:?} from iterator after delete", v )); expected.swap_remove(pos); } assert!( expected.is_empty(), - "expected incidences {:?} of vertex {:?} not matched after delete", + "expected incidences {:?} of vertex {:?} not matched by iterator after delete", + expected, + v + ); + } + + #[test] + fn incidence_cursor_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] { + let remaining = incidences[i] + .iter() + .filter(|(v, _)| *v != vertices[2]) + .cloned() + .collect(); + assert_vertex_incidence_cursor(&graph, vertices[i], remaining); + } + } + + #[test] + fn incidence_cursor_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); + for i in 0..10 { + let remaining = incidences[i] + .iter() + .filter(|(_, e)| *e != edges[2].0) + .cloned() + .collect(); + assert_vertex_incidence_cursor(&graph, vertices[i], remaining); + } + } + + fn assert_vertex_incidence_cursor( + graph: &$T, + v: <$T as $crate::traits::GraphTopology>::Vertex, + mut expected: Vec<( + <$T as $crate::traits::GraphTopology>::Vertex, + <$T as $crate::traits::GraphTopology>::Edge, + )>, + ) { + use $crate::traits::IncidenceCursor; + let mut cursor = graph.incidence_cursor(v); + while let Some(incidence) = cursor.next(graph) { + let pos = expected + .iter() + .position(|(u, e)| *u == incidence.0 && *e == incidence.1) + .expect(&format!( + "unexpected incidence {incidence:?} of vertex {:?} from cursor after delete", + v + )); + expected.swap_remove(pos); + } + assert!( + expected.is_empty(), + "expected incidences {:?} of vertex {:?} not matched by cursor after delete", expected, v );