Add Incidence struct for GraphTopology to use

This commit is contained in:
2026-08-06 09:57:51 +02:00
parent 110e6310aa
commit 2851cf60fd
8 changed files with 154 additions and 110 deletions
+11 -8
View File
@@ -34,7 +34,7 @@ use std::collections::VecDeque;
use std::hash::Hash;
use crate::maps::EntityMap;
use crate::traits::{GraphTopology, IncidenceCursor};
use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
pub struct DijkstraResult<V: Copy> {
@@ -236,16 +236,16 @@ where
heap.push(source, Reverse(0u32));
while let Some((v, Reverse(v_distance))) = heap.pop() {
for incidence in graph.incidences(v) {
let new_distance = v_distance + weights(incidence.1);
if match distances[incidence.0] {
for Incidence { vertex: u, edge: e } in graph.incidences(v) {
let new_distance = v_distance + weights(e);
if match distances[u] {
None => true,
Some(old_distance) if old_distance > new_distance => true,
_ => false,
} {
distances[incidence.0] = Some(new_distance);
on_relax(incidence.0, v);
heap.push_increase(incidence.0, Reverse(new_distance));
distances[u] = Some(new_distance);
on_relax(u, v);
heap.push_increase(u, Reverse(new_distance));
}
}
}
@@ -741,7 +741,10 @@ where
None => {
stack.pop();
}
Some((neighbor, edge)) => {
Some(Incidence {
vertex: neighbor,
edge,
}) => {
if predicate(neighbor) {
let mut path: Vec<G::Edge> =
stack.iter().filter_map(|f| f.arrival_edge).collect();
+1 -1
View File
@@ -136,7 +136,7 @@ pub mod traits;
/// Convenience re-exports of graph topology traits for common use.
pub mod prelude {
pub use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
pub use crate::traits::{GraphTopology, GraphTopologyDeletion, Incidence, IncidenceCursor};
}
mod testing;
+4 -2
View File
@@ -5,5 +5,7 @@ pub mod graph;
// TODO: Compressed-sparse-row graph model.
pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap};
pub use graph::{Graph, GraphEdgeMap, GraphVertexMap};
pub use append_graph::{
AppendGraph, AppendGraphEdgeMap, AppendGraphIncidence, AppendGraphVertexMap,
};
pub use graph::{Graph, GraphEdgeMap, GraphIncidence, GraphVertexMap};
+14 -5
View File
@@ -1,7 +1,7 @@
//! [`AppendGraph`], an undirected graph topology supporting addition only.
use crate::maps::EntityMap;
use crate::traits::{GraphTopology, IncidenceCursor};
use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
/// An opaque handle identifying a vertex in an [`AppendGraph`].
///
@@ -23,6 +23,9 @@ pub type AppendGraphVertexMap<T> = EntityMap<Vertex, T>;
/// An [`EntityMap`] for [`AppendGraph`] edges.
pub type AppendGraphEdgeMap<T> = EntityMap<Edge, T>;
/// An [`Incidence`] for [`AppendGraph`].
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
impl Edge {
fn normalize(&self) -> Self {
Self(self.0 & !1)
@@ -50,10 +53,13 @@ pub struct AppendGraphIncidenceCursor {
}
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> {
fn next(&mut self, graph: &AppendGraph) -> Option<AppendGraphIncidence> {
graph
.step_incidence(&mut self.incidence)
.map(|(v, e)| (v, e.normalize()))
.map(|(v, e)| Incidence {
vertex: v,
edge: e.normalize(),
})
}
}
@@ -191,8 +197,11 @@ impl GraphTopology for AppendGraph {
self.raw_incidences(v).map(|(_, e)| e.normalize())
}
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
self.raw_incidences(v).map(|(v, e)| (v, e.normalize()))
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = AppendGraphIncidence> {
self.raw_incidences(v).map(|(v, e)| Incidence {
vertex: v,
edge: e.normalize(),
})
}
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
+16 -11
View File
@@ -3,7 +3,7 @@
use typed_generational_arena::{Arena, Index};
use crate::maps::EntityMap;
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
use crate::traits::{GraphTopology, GraphTopologyDeletion, Incidence, IncidenceCursor};
/// An opaque handle identifying a vertex in a [`Graph`].
///
@@ -23,6 +23,9 @@ pub type GraphVertexMap<T> = EntityMap<Vertex, T>;
/// An [`EntityMap`] for [`Graph`] edges.
pub type GraphEdgeMap<T> = EntityMap<Edge, T>;
/// An [`Incidence`] for [`Graph`].
pub type GraphIncidence = Incidence<Vertex, Edge>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct VertexSlot(usize);
@@ -66,13 +69,13 @@ pub struct GraphIncidenceCursor {
}
impl IncidenceCursor<Graph> for GraphIncidenceCursor {
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
graph.step_incidence(&mut self.incidence).map(|(vs, e)| {
(
graph.vertices.get_idx(vs.0).unwrap(),
graph.normalize_edge(e),
)
})
fn next(&mut self, graph: &Graph) -> Option<GraphIncidence> {
graph
.step_incidence(&mut self.incidence)
.map(|(vs, e)| Incidence {
vertex: graph.vertices.get_idx(vs.0).unwrap(),
edge: graph.normalize_edge(e),
})
}
}
@@ -297,9 +300,11 @@ impl GraphTopology for Graph {
self.raw_incidences(v).map(|(_, e)| self.normalize_edge(e))
}
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)> {
self.raw_incidences(v)
.map(|(vs, e)| (self.vertices.get_idx(vs.0).unwrap(), self.normalize_edge(e)))
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = GraphIncidence> {
self.raw_incidences(v).map(|(vs, e)| Incidence {
vertex: self.vertices.get_idx(vs.0).unwrap(),
edge: self.normalize_edge(e),
})
}
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
+6 -4
View File
@@ -296,10 +296,12 @@ macro_rules! dijkstra_tests {
<$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],
[Vec<
$crate::traits::Incidence<
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Edge,
>,
>; 10],
$crate::maps::EntityMap<<$T as $crate::traits::GraphTopology>::Edge, u32>,
) {
use $crate::traits::GraphTopology;
+83 -74
View File
@@ -10,12 +10,12 @@ macro_rules! graph_topology_test_fixtures {
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Vertex,
); 18],
[Vec<(
[Vec<$crate::traits::Incidence<
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Edge,
)>; 10],
>>; 10],
) {
use $crate::traits::GraphTopology;
use $crate::traits::{GraphTopology, Incidence};
let mut graph = <$T>::new();
let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] =
core::array::from_fn(|_| graph.add_vertex());
@@ -40,50 +40,54 @@ macro_rules! graph_topology_test_fixtures {
(vertices[7], vertices[9]),
]
.map(|(v1, v2)| (graph.add_edge(v1, v2), v1, v2));
let incidences: [Vec<_>; 10] = [
vec![(vertices[1], edges[0].0), (vertices[1], edges[1].0)],
let i = |vertex, edge| Incidence { vertex, edge };
let incidences: [Vec<Incidence<
<$T as GraphTopology>::Vertex,
<$T as GraphTopology>::Edge,
>>; 10] = [
vec![i(vertices[1], edges[0].0), i(vertices[1], edges[1].0)],
vec![
(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),
i(vertices[0], edges[0].0),
i(vertices[0], edges[1].0),
i(vertices[2], edges[2].0),
i(vertices[3], edges[3].0),
i(vertices[4], edges[4].0),
],
vec![
(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),
i(vertices[2], edges[5].0),
i(vertices[2], edges[5].0),
i(vertices[1], edges[2].0),
i(vertices[4], edges[6].0),
i(vertices[4], edges[7].0),
i(vertices[5], edges[8].0),
i(vertices[6], edges[9].0),
],
vec![(vertices[1], edges[3].0), (vertices[6], edges[10].0)],
vec![i(vertices[1], edges[3].0), i(vertices[6], edges[10].0)],
vec![
(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),
i(vertices[1], edges[4].0),
i(vertices[2], edges[6].0),
i(vertices[2], edges[7].0),
i(vertices[4], edges[11].0),
i(vertices[4], edges[11].0),
i(vertices[7], edges[12].0),
i(vertices[8], edges[13].0),
],
vec![(vertices[2], edges[8].0), (vertices[9], edges[14].0)],
vec![i(vertices[2], edges[8].0), i(vertices[9], edges[14].0)],
vec![
(vertices[2], edges[9].0),
(vertices[3], edges[10].0),
(vertices[9], edges[15].0),
i(vertices[2], edges[9].0),
i(vertices[3], edges[10].0),
i(vertices[9], edges[15].0),
],
vec![
(vertices[4], edges[12].0),
(vertices[8], edges[16].0),
(vertices[9], edges[17].0),
i(vertices[4], edges[12].0),
i(vertices[8], edges[16].0),
i(vertices[9], edges[17].0),
],
vec![(vertices[4], edges[13].0), (vertices[7], edges[16].0)],
vec![i(vertices[4], edges[13].0), i(vertices[7], edges[16].0)],
vec![
(vertices[5], edges[14].0),
(vertices[6], edges[15].0),
(vertices[7], edges[17].0),
i(vertices[5], edges[14].0),
i(vertices[6], edges[15].0),
i(vertices[7], edges[17].0),
],
];
(graph, vertices, edges, incidences)
@@ -540,7 +544,7 @@ macro_rules! graph_topology_tests {
"unexpected incident edge count for vertex {:?}",
vertices[i]
);
let mut expected: Vec<_> = incidences[i].iter().map(|(_, e)| *e).collect();
let mut expected: Vec<_> = incidences[i].iter().map(|x| x.edge).collect();
for e in graph.incident_edges(vertices[i]) {
let pos = expected
.iter()
@@ -634,7 +638,7 @@ macro_rules! graph_topology_tests {
for incidence in graph.incidences(vertices[i]) {
let pos = expected
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.position(|x| *x == incidence)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from iterator",
vertices[i]
@@ -657,17 +661,21 @@ macro_rules! graph_topology_tests {
// 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 {
for incidence in graph.incidences(u) {
if u == incidence.vertex {
assert_eq!(
graph.incidences(u).filter(|(_, f)| *f == e).count(),
graph.incidences(u).filter(|x| x.edge == incidence.edge).count(),
2,
"loop edge {e:?} should appear exactly twice in incidences of vertex {u:?}"
"loop edge {:?} should appear exactly twice in incidences of vertex {u:?}",
incidence.edge
);
} 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:?}"
graph
.incidences(incidence.vertex)
.any(|x| x.vertex == u && x.edge == incidence.edge),
"edge {:?} from incidences of vertex {u:?} missing in incidences of adjacent vertex {:?}",
incidence.edge, incidence.vertex
);
}
}
@@ -676,19 +684,19 @@ macro_rules! graph_topology_tests {
#[test]
fn incidences_loop_edge() {
use $crate::traits::GraphTopology;
use $crate::traits::{GraphTopology, Incidence};
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)),
Some(Incidence { vertex: v, edge: e }),
"vertex should be adjacent to itself"
);
assert_eq!(
iter.next(),
Some((v, e)),
Some(Incidence { vertex: v, edge: e }),
"vertex should be adjacent to itself twice"
);
assert_eq!(
@@ -716,16 +724,16 @@ macro_rules! graph_topology_tests {
vertices[i]
));
assert_eq!(
current.0,
current.vertex,
vertices[1 - i],
"unexpected adjacent vertex of vertex {:?} in incidence {j}",
vertices[i]
);
assert_eq!(
edges.iter().filter(|e| **e == current.1).count(),
edges.iter().filter(|e| **e == current.edge).count(),
1,
"unexpected incident edge {:?} of vertex {:?}",
current.1,
current.edge,
vertices[i],
);
}
@@ -761,7 +769,7 @@ macro_rules! graph_topology_tests {
while let Some(incidence) = cursor.next(&graph) {
let pos = expected
.iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.position(|x| *x == incidence)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor",
vertices[i]
@@ -779,19 +787,19 @@ macro_rules! graph_topology_tests {
#[test]
fn incidence_cursor_loop_edge() {
use $crate::traits::{GraphTopology, IncidenceCursor};
use $crate::traits::{GraphTopology, Incidence, 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)),
Some(Incidence { vertex: v, edge: e }),
"vertex should be adjacent to itself"
);
assert_eq!(
cursor.next(&graph),
Some((v, e)),
Some(Incidence { vertex: v, edge: e }),
"vertex should be adjacent to itself twice"
);
assert_eq!(
@@ -819,16 +827,16 @@ macro_rules! graph_topology_tests {
vertices[i]
));
assert_eq!(
current.0,
current.vertex,
vertices[1 - i],
"unexpected adjacent vertex of vertex {:?} in incidence {j}",
vertices[i]
);
assert_eq!(
edges.iter().filter(|e| **e == current.1).count(),
edges.iter().filter(|e| **e == current.edge).count(),
1,
"unexpected incident edge {:?} of vertex {:?}",
current.1,
current.edge,
vertices[i],
);
}
@@ -868,11 +876,12 @@ macro_rules! graph_topology_tests {
use $crate::traits::GraphTopology;
let (graph, _, _, _) = make_test_graph();
for u in graph.vertices() {
for (v, e) in graph.incidences(u) {
let (w1, w2) = graph.incident_vertices(e);
for incidence in graph.incidences(u) {
let (w1, w2) = graph.incident_vertices(incidence.edge);
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:?}"
(w1 == u && w2 == incidence.vertex) || (w1 == incidence.vertex && w2 == u),
"edge endpoints {w1:?} and {w2:?} are inconsistent with incidence ({:?}, {:?}) of vertex {u:?}",
incidence.vertex, incidence.edge
);
}
}
@@ -883,7 +892,7 @@ macro_rules! graph_topology_tests {
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();
let mut expected: Vec<_> = graph.incidences(u).map(|x| x.edge).collect();
for e in graph.incident_edges(u) {
let pos = expected
.iter()
@@ -1256,7 +1265,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 {
let mut expected: Vec<_> = incidences[i]
.iter()
.filter_map(|(_, e)| (*e != edges[2].0).then_some(*e))
.filter_map(|x| (x.edge != edges[2].0).then_some(x.edge))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1289,7 +1298,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_map(|(v, e)| (*v != vertices[2]).then_some(*e))
.filter_map(|x| (x.vertex != vertices[2]).then_some(x.edge))
.collect();
assert_eq!(
graph.incident_edges(vertices[i]).count(),
@@ -1321,7 +1330,7 @@ macro_rules! graph_topology_deletion_tests {
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let remaining = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.filter(|x| x.vertex != vertices[2])
.cloned()
.collect();
assert_vertex_incidences(&graph, vertices[i], remaining);
@@ -1337,7 +1346,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 {
let remaining = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.filter(|x| x.edge != edges[2].0)
.cloned()
.collect();
assert_vertex_incidences(&graph, vertices[i], remaining);
@@ -1347,10 +1356,10 @@ macro_rules! graph_topology_deletion_tests {
fn assert_vertex_incidences(
graph: &$T,
v: <$T as $crate::traits::GraphTopology>::Vertex,
mut expected: Vec<(
mut expected: Vec<$crate::traits::Incidence<
<$T as $crate::traits::GraphTopology>::Vertex,
<$T as $crate::traits::GraphTopology>::Edge,
)>,
>>,
) {
use $crate::traits::GraphTopology;
assert_eq!(
@@ -1362,7 +1371,7 @@ macro_rules! graph_topology_deletion_tests {
for incidence in graph.incidences(v) {
let pos = expected
.iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.position(|x| *x == incidence)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from iterator after delete",
v
@@ -1385,7 +1394,7 @@ macro_rules! graph_topology_deletion_tests {
for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let remaining = incidences[i]
.iter()
.filter(|(v, _)| *v != vertices[2])
.filter(|x| x.vertex != vertices[2])
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
@@ -1401,7 +1410,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 {
let remaining = incidences[i]
.iter()
.filter(|(_, e)| *e != edges[2].0)
.filter(|x| x.edge != edges[2].0)
.cloned()
.collect();
assert_vertex_incidence_cursor(&graph, vertices[i], remaining);
@@ -1411,17 +1420,17 @@ macro_rules! graph_topology_deletion_tests {
fn assert_vertex_incidence_cursor(
graph: &$T,
v: <$T as $crate::traits::GraphTopology>::Vertex,
mut expected: Vec<(
mut expected: Vec<$crate::traits::Incidence<
<$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)
.position(|x| *x == incidence)
.expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from cursor after delete",
v
+19 -5
View File
@@ -4,7 +4,6 @@ use crate::maps::EntityMap;
// TODO: Add functions to reserve memory for vertices and edges.
// TODO: Split out GraphTopologyAddition trait.
// TODO: Introduce an Incidence struct.
/// A trait representing an undirected graph topology.
///
/// An undirected graph is a set of vertices and undirected edges, where each edge connects either
@@ -165,7 +164,10 @@ pub trait GraphTopology {
/// # Panics
///
/// Panics if `v` is not a valid vertex of this graph.
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = (Self::Vertex, Self::Edge)>;
fn incidences(
&self,
v: Self::Vertex,
) -> impl Iterator<Item = Incidence<Self::Vertex, Self::Edge>>;
/// Returns a cursor over all incidences of `v`, analogously to
/// [`incidences`](Self::incidences), initially positioned before the first one.
@@ -221,8 +223,8 @@ pub trait GraphTopologyDeletion: GraphTopology {
/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over
/// [`GraphTopology::incidences`].
pub trait IncidenceCursor<G: GraphTopology + ?Sized>: Copy {
/// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the
/// traversal is exhausted.
/// Advances the cursor and returns the next incidence, or `None` if the traversal is
/// exhausted.
///
/// # Examples
///
@@ -248,5 +250,17 @@ pub trait IncidenceCursor<G: GraphTopology + ?Sized>: Copy {
/// assert!(c2.next(&graph).is_some());
/// assert!(c2.next(&graph).is_none());
/// ```
fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>;
fn next(&mut self, graph: &G) -> Option<Incidence<G::Vertex, G::Edge>>;
}
/// An incidence of some vertex *v* in a graph, i.e. a vertex-edge pair *(u, e)* such that *u* is
/// adjacent to *v* and *e* is an edge between them.
///
/// Return type of [`GraphTopology::incidences`] and [`IncidenceCursor::next`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Incidence<V, E> {
/// The vertex adjacent to *v*.
pub vertex: V,
/// The edge between *v* and [`vertex`](Self::vertex).
pub edge: E,
}