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] 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;