diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index c20ed1d..dae6622 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -43,6 +43,7 @@ pub type AppendGraphEdgeMap = ElementMap; /// An [`Incidence`] for [`AppendGraph`]. pub type AppendGraphIncidence = Incidence; +#[derive(Clone)] struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, @@ -101,11 +102,18 @@ impl IncidenceCursor for AppendGraphIncidenceCursor { /// 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|)*. /// +/// # 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_edge`]: Self::add_edge /// [`degree`]: Self::degree /// [`FrozenGraph`]: crate::models::FrozenGraph /// [`Graph`]: crate::models::Graph +#[derive(Clone)] pub struct AppendGraph { vertices: Vec, incidences: Vec, @@ -257,6 +265,8 @@ mod trait_tests { crate::graph_topology_tests!(AppendGraph); crate::graph_topology_addition_tests!(AppendGraph); + crate::graph_topology_clone_tests!(AppendGraph); + crate::graph_topology_addition_clone_tests!(AppendGraph); } #[cfg(test)] diff --git a/src/models/frozen_graph.rs b/src/models/frozen_graph.rs index 5b62182..d090a39 100644 --- a/src/models/frozen_graph.rs +++ b/src/models/frozen_graph.rs @@ -87,9 +87,16 @@ impl IncidenceCursor for FrozenGraphIncidenceCursor { /// /// [`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 /// [`AppendGraph`]: crate::models::AppendGraph /// [`Graph`]: crate::models::Graph +#[derive(Clone)] pub struct FrozenGraph { vertices: Vec, incidences: Vec, @@ -255,6 +262,7 @@ mod trait_tests { use super::FrozenGraph; crate::graph_topology_tests!(FrozenGraph); + crate::graph_topology_clone_tests!(FrozenGraph); } #[cfg(test)] diff --git a/src/models/graph.rs b/src/models/graph.rs index 3895d80..ebfdfe1 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -49,6 +49,7 @@ impl IncidenceSlot { /// Referenced by [`Vertex`] as a type parameter of the underlying arena. Not intended for direct /// external use. #[doc(hidden)] +#[derive(Clone)] pub struct VertexIncidenceHeader { incidence_count: usize, first_incidence: Option, @@ -129,12 +130,19 @@ impl IncidenceCursor for GraphIncidenceCursor { /// planned, performing deletions first allows freed slots to be reused by subsequent additions, /// 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_edge`]: Self::add_edge /// [`degree`]: Self::degree /// [`delete_vertex`]: Self::delete_vertex /// [`delete_edge`]: Self::delete_edge /// [`AppendGraph`]: crate::models::AppendGraph +#[derive(Clone)] pub struct Graph { // TODO: Arena index and generation types could be externalized to Graph. vertices: Arena, @@ -398,6 +406,8 @@ mod trait_tests { crate::graph_topology_addition_tests!(Graph); crate::graph_topology_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)] diff --git a/src/testing.rs b/src/testing.rs index 7b6b7d6..1049f84 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,6 +1,8 @@ //! Test fixture and test macros for graph topology and algorithm implementations. 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_testing; pub mod graph_topology_deletion_testing; diff --git a/src/testing/graph_topology_addition_clone_testing.rs b/src/testing/graph_topology_addition_clone_testing.rs new file mode 100644 index 0000000..b476dfe --- /dev/null +++ b/src/testing/graph_topology_addition_clone_testing.rs @@ -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() { + 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" + ); +} diff --git a/src/testing/graph_topology_clone_testing.rs b/src/testing/graph_topology_clone_testing.rs new file mode 100644 index 0000000..12b9606 --- /dev/null +++ b/src/testing/graph_topology_clone_testing.rs @@ -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() { + 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() +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" + ); + } +}