Release 0.2.4 #7

Merged
warrence merged 19 commits from release-0.2.4 into main 2026-07-31 09:38:27 +02:00
3 changed files with 212 additions and 20 deletions
Showing only changes of commit 3fa4b66981 - Show all commits
+3 -1
View File
@@ -34,7 +34,9 @@ pub struct AppendGraphIncidenceCursor {
impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor { impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
fn next(&mut self, graph: &AppendGraph) -> Option<(Vertex, Edge)> { 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()))
} }
} }
+12 -4
View File
@@ -43,9 +43,12 @@ pub struct GraphIncidenceCursor {
impl IncidenceCursor<Graph> for GraphIncidenceCursor { impl IncidenceCursor<Graph> for GraphIncidenceCursor {
fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> { fn next(&mut self, graph: &Graph) -> Option<(Vertex, Edge)> {
graph graph.step_incidence(&mut self.incidence).map(|(vs, e)| {
.step_incidence(&mut self.incidence) (
.map(|(vs, e)| (graph.vertices.get_idx(vs.0).unwrap(), 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 { 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()
}
} }
} }
+197 -15
View File
@@ -298,7 +298,7 @@ macro_rules! graph_topology_tests {
assert_eq!( assert_eq!(
vertices.iter().filter(|&x| *x == v).count(), vertices.iter().filter(|&x| *x == v).count(),
1, 1,
"unexpected vertex {v:?} from the iterator" "unexpected vertex {v:?} from iterator"
); );
} }
} }
@@ -339,7 +339,7 @@ macro_rules! graph_topology_tests {
.iter() .iter()
.position(|w| *w == v) .position(|w| *w == v)
.expect(&format!( .expect(&format!(
"unexpected adjacent vertex {v:?} of {:?} from the iterator", "unexpected adjacent vertex {v:?} of {:?} from iterator",
vertices[4] vertices[4]
)); ));
expected_adjacency.swap_remove(i); expected_adjacency.swap_remove(i);
@@ -347,7 +347,7 @@ macro_rules! graph_topology_tests {
assert_eq!( assert_eq!(
expected_adjacency.len(), expected_adjacency.len(),
0, 0,
"expected adjacent vertices {:?} of {:?} were not matched by the iterator", "expected adjacent vertices {:?} of {:?} were not matched by iterator",
expected_adjacency, expected_adjacency,
vertices[4] vertices[4]
); );
@@ -484,7 +484,7 @@ macro_rules! graph_topology_tests {
assert_eq!( assert_eq!(
edges.iter().filter(|&&(f, _, _)| f == e).count(), edges.iter().filter(|&&(f, _, _)| f == e).count(),
1, 1,
"unexpected edge {e:?} from the iterator" "unexpected edge {e:?} from iterator"
); );
} }
} }
@@ -518,14 +518,14 @@ macro_rules! graph_topology_tests {
.iter() .iter()
.position(|f| *f == e) .position(|f| *f == e)
.expect(&format!( .expect(&format!(
"unexpected incident edge {e:?} of vertex {:?} from the iterator", "unexpected incident edge {e:?} of vertex {:?} from iterator",
vertices[i] vertices[i]
)); ));
expected.swap_remove(pos); expected.swap_remove(pos);
} }
assert!( assert!(
expected.is_empty(), 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, expected,
vertices[i] vertices[i]
); );
@@ -572,7 +572,7 @@ macro_rules! graph_topology_tests {
} }
assert!( assert!(
expected.is_empty(), 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, expected,
vertices[i] vertices[i]
); );
@@ -608,14 +608,14 @@ macro_rules! graph_topology_tests {
.iter() .iter()
.position(|(v, e)| *v == incidence.0 && *e == incidence.1) .position(|(v, e)| *v == incidence.0 && *e == incidence.1)
.expect(&format!( .expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} from the iterator", "unexpected incidence {incidence:?} of vertex {:?} from iterator",
vertices[i] vertices[i]
)); ));
expected.swap_remove(pos); expected.swap_remove(pos);
} }
assert!( assert!(
expected.is_empty(), expected.is_empty(),
"expected incidences {:?} of vertex {:?} were not matched by the iterator", "expected incidences {:?} of vertex {:?} were not matched by iterator",
expected, expected,
vertices[i] 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] #[test]
fn incident_vertices_incidences_consistency() { fn incident_vertices_incidences_consistency() {
use $crate::traits::GraphTopology; use $crate::traits::GraphTopology;
@@ -1103,8 +1228,7 @@ macro_rules! graph_topology_deletion_tests {
for i in 0..10 { for i in 0..10 {
let mut expected: Vec<_> = incidences[i] let mut expected: Vec<_> = incidences[i]
.iter() .iter()
.filter(|(_, e)| *e != edges[2].0) .filter_map(|(_, e)| (*e != edges[2].0).then_some(*e))
.map(|(_, e)| *e)
.collect(); .collect();
assert_eq!( assert_eq!(
graph.incident_edges(vertices[i]).count(), 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] { for i in [0, 1, 3, 4, 5, 6, 7, 8, 9] {
let mut expected: Vec<_> = incidences[i] let mut expected: Vec<_> = incidences[i]
.iter() .iter()
.filter(|(v, _)| *v != vertices[2]) .filter_map(|(v, e)| (*v != vertices[2]).then_some(*e))
.map(|(_, e)| *e)
.collect(); .collect();
assert_eq!( assert_eq!(
graph.incident_edges(vertices[i]).count(), graph.incident_edges(vertices[i]).count(),
@@ -1213,14 +1336,73 @@ macro_rules! graph_topology_deletion_tests {
.iter() .iter()
.position(|(u, e)| *u == incidence.0 && *e == incidence.1) .position(|(u, e)| *u == incidence.0 && *e == incidence.1)
.expect(&format!( .expect(&format!(
"unexpected incidence {incidence:?} of vertex {:?} after delete", "unexpected incidence {incidence:?} of vertex {:?} from iterator after delete",
v v
)); ));
expected.swap_remove(pos); expected.swap_remove(pos);
} }
assert!( assert!(
expected.is_empty(), 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, expected,
v v
); );