Add Clone to graph models, and tests
This commit is contained in:
@@ -43,6 +43,7 @@ pub type AppendGraphEdgeMap<T> = ElementMap<Edge, T>;
|
|||||||
/// An [`Incidence`] for [`AppendGraph`].
|
/// An [`Incidence`] for [`AppendGraph`].
|
||||||
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
|
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct VertexIncidenceHeader {
|
struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<Edge>,
|
first_incidence: Option<Edge>,
|
||||||
@@ -101,11 +102,18 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
|||||||
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
|
||||||
/// time since vertex degrees are stored. Space complexity is *O(|V| + |E|)*.
|
/// time since vertex degrees are stored. Space complexity is *O(|V| + |E|)*.
|
||||||
///
|
///
|
||||||
|
/// # Cloning
|
||||||
|
///
|
||||||
|
/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or
|
||||||
|
/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect
|
||||||
|
/// each other.
|
||||||
|
///
|
||||||
/// [`add_vertex`]: Self::add_vertex
|
/// [`add_vertex`]: Self::add_vertex
|
||||||
/// [`add_edge`]: Self::add_edge
|
/// [`add_edge`]: Self::add_edge
|
||||||
/// [`degree`]: Self::degree
|
/// [`degree`]: Self::degree
|
||||||
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
||||||
/// [`Graph`]: crate::models::Graph
|
/// [`Graph`]: crate::models::Graph
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AppendGraph {
|
pub struct AppendGraph {
|
||||||
vertices: Vec<VertexIncidenceHeader>,
|
vertices: Vec<VertexIncidenceHeader>,
|
||||||
incidences: Vec<IncidenceEntry>,
|
incidences: Vec<IncidenceEntry>,
|
||||||
@@ -257,6 +265,8 @@ mod trait_tests {
|
|||||||
|
|
||||||
crate::graph_topology_tests!(AppendGraph);
|
crate::graph_topology_tests!(AppendGraph);
|
||||||
crate::graph_topology_addition_tests!(AppendGraph);
|
crate::graph_topology_addition_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_clone_tests!(AppendGraph);
|
||||||
|
crate::graph_topology_addition_clone_tests!(AppendGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -87,9 +87,16 @@ impl IncidenceCursor<FrozenGraph> for FrozenGraphIncidenceCursor {
|
|||||||
///
|
///
|
||||||
/// [`degree`] runs in *O(1)* time. Space complexity is *O(|V| + |E|)*.
|
/// [`degree`] runs in *O(1)* time. Space complexity is *O(|V| + |E|)*.
|
||||||
///
|
///
|
||||||
|
/// # Cloning
|
||||||
|
///
|
||||||
|
/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or
|
||||||
|
/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect
|
||||||
|
/// each other.
|
||||||
|
///
|
||||||
/// [`degree`]: Self::degree
|
/// [`degree`]: Self::degree
|
||||||
/// [`AppendGraph`]: crate::models::AppendGraph
|
/// [`AppendGraph`]: crate::models::AppendGraph
|
||||||
/// [`Graph`]: crate::models::Graph
|
/// [`Graph`]: crate::models::Graph
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct FrozenGraph {
|
pub struct FrozenGraph {
|
||||||
vertices: Vec<usize>,
|
vertices: Vec<usize>,
|
||||||
incidences: Vec<IncidenceEntry>,
|
incidences: Vec<IncidenceEntry>,
|
||||||
@@ -255,6 +262,7 @@ mod trait_tests {
|
|||||||
use super::FrozenGraph;
|
use super::FrozenGraph;
|
||||||
|
|
||||||
crate::graph_topology_tests!(FrozenGraph);
|
crate::graph_topology_tests!(FrozenGraph);
|
||||||
|
crate::graph_topology_clone_tests!(FrozenGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ impl IncidenceSlot {
|
|||||||
/// Referenced by [`Vertex`] as a type parameter of the underlying arena. Not intended for direct
|
/// Referenced by [`Vertex`] as a type parameter of the underlying arena. Not intended for direct
|
||||||
/// external use.
|
/// external use.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct VertexIncidenceHeader {
|
pub struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<IncidenceSlot>,
|
first_incidence: Option<IncidenceSlot>,
|
||||||
@@ -129,12 +130,19 @@ impl IncidenceCursor<Graph> for GraphIncidenceCursor {
|
|||||||
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
|
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
|
||||||
/// potentially avoiding reallocation.
|
/// potentially avoiding reallocation.
|
||||||
///
|
///
|
||||||
|
/// # Cloning
|
||||||
|
///
|
||||||
|
/// [`Vertex`] and [`Edge`] handles obtained from a clone are valid, and refer to the same vertex or
|
||||||
|
/// edge in the original, and vice versa. However, once cloned, the two instances no longer affect
|
||||||
|
/// each other.
|
||||||
|
///
|
||||||
/// [`add_vertex`]: Self::add_vertex
|
/// [`add_vertex`]: Self::add_vertex
|
||||||
/// [`add_edge`]: Self::add_edge
|
/// [`add_edge`]: Self::add_edge
|
||||||
/// [`degree`]: Self::degree
|
/// [`degree`]: Self::degree
|
||||||
/// [`delete_vertex`]: Self::delete_vertex
|
/// [`delete_vertex`]: Self::delete_vertex
|
||||||
/// [`delete_edge`]: Self::delete_edge
|
/// [`delete_edge`]: Self::delete_edge
|
||||||
/// [`AppendGraph`]: crate::models::AppendGraph
|
/// [`AppendGraph`]: crate::models::AppendGraph
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Graph {
|
pub struct Graph {
|
||||||
// TODO: Arena index and generation types could be externalized to Graph.
|
// TODO: Arena index and generation types could be externalized to Graph.
|
||||||
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
vertices: Arena<VertexIncidenceHeader, usize, usize>,
|
||||||
@@ -398,6 +406,8 @@ mod trait_tests {
|
|||||||
crate::graph_topology_addition_tests!(Graph);
|
crate::graph_topology_addition_tests!(Graph);
|
||||||
crate::graph_topology_deletion_tests!(Graph);
|
crate::graph_topology_deletion_tests!(Graph);
|
||||||
crate::graph_topology_addition_deletion_tests!(Graph);
|
crate::graph_topology_addition_deletion_tests!(Graph);
|
||||||
|
crate::graph_topology_clone_tests!(Graph);
|
||||||
|
crate::graph_topology_addition_clone_tests!(Graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
//! Test fixture and test macros for graph topology and algorithm implementations.
|
//! Test fixture and test macros for graph topology and algorithm implementations.
|
||||||
|
|
||||||
pub mod fixtures;
|
pub mod fixtures;
|
||||||
|
pub mod graph_topology_addition_clone_testing;
|
||||||
|
pub mod graph_topology_clone_testing;
|
||||||
pub mod graph_topology_addition_deletion_testing;
|
pub mod graph_topology_addition_deletion_testing;
|
||||||
pub mod graph_topology_addition_testing;
|
pub mod graph_topology_addition_testing;
|
||||||
pub mod graph_topology_deletion_testing;
|
pub mod graph_topology_deletion_testing;
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
|
use crate::traits::GraphTopologyAddition;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! graph_topology_addition_clone_tests {
|
||||||
|
($T:ty) => {
|
||||||
|
$crate::graph_topology_addition_clone_tests!(@wrap $T,
|
||||||
|
clone_independent,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
$crate::testing::graph_topology_addition_clone_testing::$name::<$T>();
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_independent<G: Clone + GraphTopologyAddition>() {
|
||||||
|
let (mut graph, [u, v], _) = G::single_edge();
|
||||||
|
let mut clone = graph.clone();
|
||||||
|
|
||||||
|
let w = clone.add_vertex();
|
||||||
|
clone.add_edge(u, w);
|
||||||
|
assert_eq!(clone.vertex_count(), 3, "precondition: vertex count of clone");
|
||||||
|
assert_eq!(clone.edge_count(), 2, "precondition: edge count of clone");
|
||||||
|
assert_eq!(
|
||||||
|
graph.vertex_count(),
|
||||||
|
2,
|
||||||
|
"mutating the clone should not affect the vertex count of the original"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
graph.edge_count(),
|
||||||
|
1,
|
||||||
|
"mutating the clone should not affect the edge count of the original"
|
||||||
|
);
|
||||||
|
|
||||||
|
let x = graph.add_vertex();
|
||||||
|
graph.add_edge(v, x);
|
||||||
|
assert_eq!(graph.vertex_count(), 3, "precondition: vertex count of original");
|
||||||
|
assert_eq!(graph.edge_count(), 2, "precondition: edge count of original");
|
||||||
|
assert_eq!(
|
||||||
|
clone.vertex_count(),
|
||||||
|
3,
|
||||||
|
"mutating the original should not affect the vertex count of the clone"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clone.edge_count(),
|
||||||
|
2,
|
||||||
|
"mutating the original should not affect the edge count of the clone"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::testing::fixtures::MakeTestGraph;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! graph_topology_clone_tests {
|
||||||
|
($T:ty) => {
|
||||||
|
$crate::graph_topology_clone_tests!(@wrap $T,
|
||||||
|
clone_preserves_vertex_and_edge_count,
|
||||||
|
clone_handles_interchangeable,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
(@wrap $T:ty, $($name:ident),* $(,)?) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
$crate::testing::graph_topology_clone_testing::$name::<$T>();
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_preserves_vertex_and_edge_count<G: Clone + MakeTestGraph>() {
|
||||||
|
let (graph, ..) = G::standard();
|
||||||
|
let clone = graph.clone();
|
||||||
|
assert_eq!(
|
||||||
|
clone.vertex_count(),
|
||||||
|
graph.vertex_count(),
|
||||||
|
"clone should have the same vertex count as the original"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clone.edge_count(),
|
||||||
|
graph.edge_count(),
|
||||||
|
"clone should have the same edge count as the original"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries the clone using vertex and edge handles obtained from the original graph, and asserts
|
||||||
|
/// that the results are identical to querying the original with those same handles. This is only
|
||||||
|
/// meaningful because Vertex and Edge handles are not tied to a particular graph instance.
|
||||||
|
pub fn clone_handles_interchangeable<G: Clone + MakeTestGraph>()
|
||||||
|
where
|
||||||
|
G::Vertex: Debug,
|
||||||
|
G::Edge: Debug,
|
||||||
|
{
|
||||||
|
let (graph, vertices, edges, _) = G::standard();
|
||||||
|
let clone = graph.clone();
|
||||||
|
for &v in vertices.iter() {
|
||||||
|
assert_eq!(
|
||||||
|
clone.degree(v),
|
||||||
|
graph.degree(v),
|
||||||
|
"vertex {v:?} should have the same degree in the clone as in the original"
|
||||||
|
);
|
||||||
|
let mut expected: Vec<_> = graph.incidences(v).collect();
|
||||||
|
for incidence in clone.incidences(v) {
|
||||||
|
let pos = expected
|
||||||
|
.iter()
|
||||||
|
.position(|x| *x == incidence)
|
||||||
|
.expect(&format!(
|
||||||
|
"unexpected incidence {incidence:?} of vertex {v:?} in the clone"
|
||||||
|
));
|
||||||
|
expected.swap_remove(pos);
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
expected.is_empty(),
|
||||||
|
"expected incidences {expected:?} of vertex {v:?} are missing in the clone"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for &(e, u, v) in edges.iter() {
|
||||||
|
assert_eq!(
|
||||||
|
clone.incident_vertices(e),
|
||||||
|
(u, v),
|
||||||
|
"edge {e:?} should have the same incident vertices in the clone as in the original"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user