From 10d0c382cc86e5f43b094770be7fb4470f7fcb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 6 Aug 2026 14:17:21 +0200 Subject: [PATCH] Add GraphTopologyAddition trait from GraphTopology Move capacity and add methods from GraphTopology trait to new GraphTopologyAddition trait --- src/generators.rs | 4 ++-- src/lib.rs | 4 +++- src/models/append_graph.rs | 20 ++++++++++--------- src/models/graph.rs | 22 ++++++++++++--------- src/testing/bfs_testing.rs | 8 ++++---- src/testing/dfs_testing.rs | 14 +++++++------- src/testing/dijkstra_testing.rs | 8 ++++---- src/testing/graph_topology_testing.rs | 12 ++++++------ src/testing/maps_testing.rs | 18 ++++++++--------- src/traits.rs | 28 ++++++++++++++++----------- 10 files changed, 75 insertions(+), 63 deletions(-) diff --git a/src/generators.rs b/src/generators.rs index 774e317..55f47a1 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1,4 +1,4 @@ -use crate::traits::GraphTopology; +use crate::traits::{GraphTopology, GraphTopologyAddition}; pub fn petersen( graph: &mut G, @@ -7,7 +7,7 @@ pub fn petersen( [::Edge; 15], ) where - G: GraphTopology, + G: GraphTopologyAddition, { const N: usize = 5; const K: usize = 2; diff --git a/src/lib.rs b/src/lib.rs index e902d00..024fec4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,7 +136,9 @@ pub mod traits; /// Convenience re-exports of graph topology traits for common use. pub mod prelude { - pub use crate::traits::{GraphTopology, GraphTopologyDeletion, Incidence, IncidenceCursor}; + pub use crate::traits::{ + GraphTopology, GraphTopologyAddition, GraphTopologyDeletion, Incidence, IncidenceCursor, + }; } mod testing; diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 00c2d36..d3f37fe 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,7 +1,7 @@ //! [`AppendGraph`], an undirected graph topology supporting addition only. use crate::maps::EntityMap; -use crate::traits::{GraphTopology, Incidence, IncidenceCursor}; +use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence, IncidenceCursor}; /// An opaque handle identifying a vertex in an [`AppendGraph`]. /// @@ -147,10 +147,6 @@ impl GraphTopology for AppendGraph { self.vertices.len() } - fn vertex_capacity(&self) -> usize { - self.vertices.capacity() - } - fn vertex_map(&self, default: T) -> EntityMap { EntityMap::new(default, |v| v.0, self.vertex_capacity()) } @@ -159,10 +155,6 @@ impl GraphTopology for AppendGraph { self.incidences.len() / 2 } - fn edge_capacity(&self) -> usize { - self.incidences.capacity() / 2 - } - fn edge_map(&self, default: T) -> EntityMap { EntityMap::new(default, |e| e.0 / 2, self.edge_capacity()) } @@ -209,6 +201,16 @@ impl GraphTopology for AppendGraph { incidence: self.vertices[v.0].first_incidence, } } +} + +impl GraphTopologyAddition for AppendGraph { + fn vertex_capacity(&self) -> usize { + self.vertices.capacity() + } + + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } fn add_vertex(&mut self) -> Self::Vertex { self.vertices.push(VertexIncidenceHeader { diff --git a/src/models/graph.rs b/src/models/graph.rs index 39454eb..d0b37c4 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -3,7 +3,9 @@ use typed_generational_arena::{Arena, Index}; use crate::maps::EntityMap; -use crate::traits::{GraphTopology, GraphTopologyDeletion, Incidence, IncidenceCursor}; +use crate::traits::{ + GraphTopology, GraphTopologyAddition, GraphTopologyDeletion, Incidence, IncidenceCursor, +}; /// An opaque handle identifying a vertex in a [`Graph`]. /// @@ -239,10 +241,6 @@ impl GraphTopology for Graph { self.vertices.len() } - fn vertex_capacity(&self) -> usize { - self.vertices.capacity() - } - fn vertex_map(&self, default: T) -> EntityMap { EntityMap::new(default, |v| v.arr_idx(), self.vertex_capacity()) } @@ -251,10 +249,6 @@ impl GraphTopology for Graph { self.incidences.len() / 2 } - fn edge_capacity(&self) -> usize { - self.incidences.capacity() / 2 - } - fn edge_map(&self, default: T) -> EntityMap { EntityMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) } @@ -312,6 +306,16 @@ impl GraphTopology for Graph { incidence: self.vertices[v].first_incidence, } } +} + +impl GraphTopologyAddition for Graph { + fn vertex_capacity(&self) -> usize { + self.vertices.capacity() + } + + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } fn add_vertex(&mut self) -> Self::Vertex { self.vertices.insert(VertexIncidenceHeader { diff --git a/src/testing/bfs_testing.rs b/src/testing/bfs_testing.rs index 233a9e7..b5e4365 100644 --- a/src/testing/bfs_testing.rs +++ b/src/testing/bfs_testing.rs @@ -4,7 +4,7 @@ macro_rules! bfs_tests { ($T:ty) => { #[test] fn bfs_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let result = $crate::algorithms::bfs(&graph, v); @@ -54,7 +54,7 @@ macro_rules! bfs_tests { #[test] fn bfs_distances_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let distances = $crate::algorithms::bfs_distances(&graph, v); @@ -91,7 +91,7 @@ macro_rules! bfs_tests { #[test] fn bfs_find_source() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -139,7 +139,7 @@ macro_rules! bfs_tests { #[test] fn bfs_find_where_source_matches() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( diff --git a/src/testing/dfs_testing.rs b/src/testing/dfs_testing.rs index d302773..b89ff30 100644 --- a/src/testing/dfs_testing.rs +++ b/src/testing/dfs_testing.rs @@ -4,7 +4,7 @@ macro_rules! dfs_tests { ($T:ty) => { #[test] fn dfs_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let result = $crate::algorithms::dfs(&graph, v); @@ -49,7 +49,7 @@ macro_rules! dfs_tests { #[test] fn dfs_visited_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let visited = $crate::algorithms::dfs_visited(&graph, v); @@ -78,7 +78,7 @@ macro_rules! dfs_tests { #[test] fn dfs_find_source() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert!( @@ -111,7 +111,7 @@ macro_rules! dfs_tests { #[test] fn dfs_find_where_source_matches() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -205,7 +205,7 @@ macro_rules! dfs_tests { #[test] fn dfs_find_path_source_equals_target() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( @@ -227,7 +227,7 @@ macro_rules! dfs_tests { #[test] fn dfs_find_path_adjacent() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -255,7 +255,7 @@ macro_rules! dfs_tests { #[test] fn dfs_find_path_where_source_matches() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_eq!( diff --git a/src/testing/dijkstra_testing.rs b/src/testing/dijkstra_testing.rs index 3b06326..34cbe79 100644 --- a/src/testing/dijkstra_testing.rs +++ b/src/testing/dijkstra_testing.rs @@ -4,7 +4,7 @@ macro_rules! dijkstra_tests { ($T:ty) => { #[test] fn dijkstra_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let result = $crate::algorithms::dijkstra(&graph, v, |_| { @@ -25,7 +25,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_zero_weight_loop() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 3] = core::array::from_fn(|_| graph.add_vertex()); @@ -71,7 +71,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_unweighted_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let result = $crate::algorithms::dijkstra_unweighted(&graph, v); @@ -95,7 +95,7 @@ macro_rules! dijkstra_tests { #[test] fn dijkstra_distances_unweighted_single_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); let distances = $crate::algorithms::dijkstra_distances_unweighted(&graph, v); diff --git a/src/testing/graph_topology_testing.rs b/src/testing/graph_topology_testing.rs index 5ee3be3..f7ea567 100644 --- a/src/testing/graph_topology_testing.rs +++ b/src/testing/graph_topology_testing.rs @@ -15,7 +15,7 @@ macro_rules! graph_topology_test_fixtures { <$T as $crate::traits::GraphTopology>::Edge, >>; 10], ) { - use $crate::traits::{GraphTopology, Incidence}; + use $crate::traits::{GraphTopology, GraphTopologyAddition, Incidence}; let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 10] = core::array::from_fn(|_| graph.add_vertex()); @@ -96,7 +96,7 @@ macro_rules! graph_topology_test_fixtures { #[allow(dead_code)] fn make_test_graph_disconnected() -> ($T, [<$T as $crate::traits::GraphTopology>::Vertex; 3]) { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let vertices: [<$T as $crate::traits::GraphTopology>::Vertex; 3] = core::array::from_fn(|_| graph.add_vertex()); @@ -112,7 +112,7 @@ macro_rules! graph_topology_tests { ($T:ty) => { #[test] fn add_vertex() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v = graph.add_vertex(); assert_ne!(graph.add_vertex(), v, "unexpected duplicate vertex"); @@ -147,7 +147,7 @@ macro_rules! graph_topology_tests { #[test] fn add_edge() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -974,7 +974,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_vertex_invalid_index() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v = graph.add_vertex(); @@ -1169,7 +1169,7 @@ macro_rules! graph_topology_deletion_tests { #[test] fn delete_edge_invalid_index() { - use $crate::traits::GraphTopology; + use $crate::traits::GraphTopologyAddition; use $crate::traits::GraphTopologyDeletion; let mut graph = Graph::new(); let v1 = graph.add_vertex(); diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs index b9c7e1f..daa3b67 100644 --- a/src/testing/maps_testing.rs +++ b/src/testing/maps_testing.rs @@ -4,7 +4,7 @@ macro_rules! entity_map_tests { ($T:ty) => { #[test] fn initial_values_are_default() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -15,7 +15,7 @@ macro_rules! entity_map_tests { #[test] fn write_and_read() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -27,7 +27,7 @@ macro_rules! entity_map_tests { #[test] fn lazy_growth_on_read() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); graph.add_vertex(); let map = graph.vertex_map(99); @@ -37,7 +37,7 @@ macro_rules! entity_map_tests { #[test] fn lazy_growth_on_write() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let mut map = graph.vertex_map(0); @@ -49,7 +49,7 @@ macro_rules! entity_map_tests { #[test] fn expand_to_new_vertices() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); graph.add_vertex(); let mut map = graph.vertex_map(42); @@ -67,7 +67,7 @@ macro_rules! entity_map_tests { #[test] fn expand_does_not_overwrite_existing_values() { - use $crate::traits::GraphTopology; + use $crate::traits::{GraphTopology, GraphTopologyAddition}; let mut graph = <$T>::new(); let v = graph.add_vertex(); let mut map = graph.vertex_map(0); @@ -85,8 +85,7 @@ macro_rules! entity_map_deletion_tests { ($T:ty) => { #[test] fn surviving_vertex_readable_after_delete() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; + use $crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyDeletion}; let mut graph = <$T>::new(); let v1 = graph.add_vertex(); let v2 = graph.add_vertex(); @@ -99,8 +98,7 @@ macro_rules! entity_map_deletion_tests { #[test] fn reused_slot_returns_old_value() { - use $crate::traits::GraphTopology; - use $crate::traits::GraphTopologyDeletion; + use $crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyDeletion}; let mut graph = <$T>::new(); graph.add_vertex(); let v1 = graph.add_vertex(); diff --git a/src/traits.rs b/src/traits.rs index 6e92aff..a625d06 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,13 +2,11 @@ use crate::maps::EntityMap; -// TODO: Add functions to reserve memory for vertices and edges. -// TODO: Split out GraphTopologyAddition trait. /// A trait representing an undirected graph topology. /// /// An undirected graph is a set of vertices and undirected edges, where each edge connects either /// exactly two vertices or one vertex with itself (loop edge). This trait provides methods for -/// querying a graph topology, iterating over vertices and edges, and adding new vertices and edges. +/// querying a graph topology, and iterating over vertices and edges. /// /// # Vertices and edges /// @@ -20,9 +18,10 @@ use crate::maps::EntityMap; /// Methods accepting vertices or edges as parameters panic if the handle was invalidated by a /// deletion, and return incorrect results if the handle was not produced by this graph instance. /// -/// # Deletion +/// # Addition and deletion /// -/// This trait covers graph construction and querying only. To delete vertices and edges, see +/// This trait covers graph querying only. To construct a graph incrementally by adding vertices and +/// edges, see [`GraphTopologyAddition`]. To delete vertices and edges, see /// [`GraphTopologyDeletion`]. /// /// [`Edge`]: GraphTopology::Edge @@ -45,9 +44,6 @@ pub trait GraphTopology { /// Returns the number of vertices in the graph. fn vertex_count(&self) -> usize; - /// Returns the total number of vertices the graph can hold without reallocating. - fn vertex_capacity(&self) -> usize; - /// Creates and returns an [`EntityMap`] for the vertices with every slot initialised to /// `default`. /// @@ -74,9 +70,6 @@ pub trait GraphTopology { /// Returns the number of edges in the graph. fn edge_count(&self) -> usize; - /// Returns the total number of edges the graph can hold without reallocating. - fn edge_capacity(&self) -> usize; - /// Creates and returns an [`EntityMap`] for the edges with every slot initialised to `default`. /// /// # Examples @@ -179,6 +172,19 @@ pub trait GraphTopology { /// /// Panics if `v` is not a valid vertex of this graph. fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor; +} + +// TODO: Add functions to reserve memory for vertices and edges. +/// A trait that adds construction operations to an undirected graph topology. +/// +/// This trait provides methods for addition of vertices and edges, and capacity management in an +/// undirected graph. +pub trait GraphTopologyAddition: GraphTopology { + /// Returns the total number of vertices the graph can hold without reallocating. + fn vertex_capacity(&self) -> usize; + + /// Returns the total number of edges the graph can hold without reallocating. + fn edge_capacity(&self) -> usize; /// Adds a new isolated vertex and returns its handle. fn add_vertex(&mut self) -> Self::Vertex;