From 818b37ec153818a15539e2039825037499981c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 10 Jul 2026 10:18:39 +0200 Subject: [PATCH 1/7] Add linter warning for missing docs --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 46964c6..86d3680 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(missing_docs)] + pub mod algorithms; pub mod maps; pub mod models; From c131f520f4afe44301c489a2bf6e864e0c170a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Tue, 14 Jul 2026 20:01:08 +0200 Subject: [PATCH 2/7] Add crate doc front-page, and update readme --- README.md | 59 ++++++++++++++++++++++- src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/maps.rs | 1 - src/models.rs | 2 + 4 files changed, 188 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8ce1bc..1aab12a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,63 @@ # grapherity -Library for graph models and algorithms. +Graph models and algorithms. -This library is still experimental and its API may therefore change frequently. +This crate provides data structures to model graphs and to perform algorithms on these data structures. The functionality presented is designed to be easy to use, flexible, and performant. + +Currently supported are: + +* Undirected graph types `Graph` and `AppendGraph`, built on a flat, index-based adjacency list, +* Maps to freely associate custom data with vertices and edges, +* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants, +* Exposed traits to implement custom graph data structures or algorithms. + +For more information, see the [documentation](https://docs.rs/grapherity/latest/grapherity/). + +## Example + +``` +// Brings commonly used traits into scope. +use grapherity::prelude::*; +use grapherity::models::Graph; +use grapherity::algorithms; + +let mut graph = Graph::new(); +// Creates a map for edge weights with a default weight. +let mut weights = graph.edge_map(1); +// Adds three vertices and two edges. +let v1 = graph.add_vertex(); +let v2 = graph.add_vertex(); +let v3 = graph.add_vertex(); +graph.add_edge(v1, v2); +let e = graph.add_edge(v2, v3); +// Sets a non-default weight for the second edge. +weights[e] = 5; + +// Checks the number of vertices. +assert_eq!(graph.vertex_count(), 3); +// Checks the number of edges. +assert_eq!(graph.edge_count(), 2); +// Checks that v1 and v2 are adjacent. +assert!(graph.are_adjacent(v1, v2)); +// Checks that v1 and v3 are not adjacent. +assert!(!graph.are_adjacent(v1, v3)); +// Checks the sum of all vertex degrees. +let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum(); +assert_eq!(sum, 1 + 2 + 1); + +// Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3. +let result = algorithms::dijkstra(&graph, v1, |e| weights[e]); +assert_eq!(result.distances[v3], Some(1 + 5)); +assert_eq!(result.predecessors[v3], Some(v2)); + +// Deletes the middle vertex and its incident edges. +graph.delete_vertex(v2); + +// Calls Dijkstra's algorithm again on the now disconnected graph. +let result = algorithms::dijkstra(&graph, v1, |e| weights[e]); +assert_eq!(result.distances[v3], None); +assert_eq!(result.predecessors[v3], None); +``` ## License diff --git a/src/lib.rs b/src/lib.rs index 86d3680..892c8aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,132 @@ +//! Graph models and algorithms. +//! +//! This crate provides data structures to model graphs and to perform algorithms on these data +//! structures. The functionality presented is designed to be easy to use, flexible, and performant. +//! +//! Currently supported are: +//! +//! * Undirected graph types [`Graph`] and [`AppendGraph`] built on a flat, index-based adjacency +//! list, +//! * [`VertexMap`] and [`EdgeMap`] to freely associate custom data with vertices and edges, +//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different +//! variants, +//! * Exposed traits to implement custom graph data structures or algorithms. +//! +//! # Usage example +//! +//! This example demonstrates some of this library's features. +//! +//! ``` +//! // Brings commonly used traits into scope. +//! use grapherity::prelude::*; +//! use grapherity::models::Graph; +//! use grapherity::algorithms; +//! +//! let mut graph = Graph::new(); +//! // Creates a map for edge weights with a default weight. +//! let mut weights = graph.edge_map(1); +//! // Adds three vertices and two edges. +//! let v1 = graph.add_vertex(); +//! let v2 = graph.add_vertex(); +//! let v3 = graph.add_vertex(); +//! graph.add_edge(v1, v2); +//! let e = graph.add_edge(v2, v3); +//! // Sets a non-default weight for the second edge. +//! weights[e] = 5; +//! +//! // Checks the number of vertices. +//! assert_eq!(graph.vertex_count(), 3); +//! // Checks the number of edges. +//! assert_eq!(graph.edge_count(), 2); +//! // Checks that v1 and v2 are adjacent. +//! assert!(graph.are_adjacent(v1, v2)); +//! // Checks that v1 and v3 are not adjacent. +//! assert!(!graph.are_adjacent(v1, v3)); +//! // Checks the sum of all vertex degrees. +//! let sum: usize = graph.vertices().map(|v| graph.degree(v)).sum(); +//! assert_eq!(sum, 1 + 2 + 1); +//! +//! // Calls Dijkstra's algorithm using the edge weights to find the shortest path from v1 to v3. +//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]); +//! assert_eq!(result.distances[v3], Some(1 + 5)); +//! assert_eq!(result.predecessors[v3], Some(v2)); +//! +//! // Deletes the middle vertex and its incident edges. +//! graph.delete_vertex(v2); +//! +//! // Calls Dijkstra's algorithm again on the now disconnected graph. +//! let result = algorithms::dijkstra(&graph, v1, |e| weights[e]); +//! assert_eq!(result.distances[v3], None); +//! assert_eq!(result.predecessors[v3], None); +//! ``` +//! +//! # Graph types +//! +//! [`Graph`] and [`AppendGraph`] both implement [`GraphTopology`] using a flat, index-based adjacency +//! list to store edges, which means that vertex and edge insertions happen in `O(1)`. Degree +//! lookups are also `O(1)`. +//! +//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of +//! vertices and edges. +//! +//! [`AppendGraph`] does not support deletion, vertices and edges can only be added, allowing it to +//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation +//! tracking needed for stable handles after deletion. +//! +//! # Vertices and edges +//! +//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`] +//! to denote vertices and edges. This means that vertices and edges returned by graph functions +//! never change, even if the underlying graph data structure is mutated. However, vertices and +//! edges can become invalid when the denoted element is deleted. Code using an invalid vertex or +//! edge in a graph method will panic. +//! +//! ```should_panic +//! # use grapherity::prelude::*; +//! # use grapherity::models::Graph; +//! let mut graph = Graph::new(); +//! let v = graph.add_vertex(); +//! graph.delete_vertex(v); +//! // This will panic because 'v' was deleted and is not valid anymore. +//! graph.degree(v); +//! ``` +//! +//! [`VertexMap`] and [`EdgeMap`] are not required to panic if provided with an invalid +//! vertex or edge. +//! +//! Vertices and edges are provided by the graph and not intended to be generated by users. +//! +//! # Loops and multi-edges +//! +//! Loops and multi edges are supported as well. In the following example, `v1` will have two loops +//! and two edges to `v2`. +//! +//! ``` +//! # use grapherity::prelude::*; +//! # use grapherity::models::Graph; +//! let mut graph = Graph::new(); +//! let v1 = graph.add_vertex(); +//! let v2 = graph.add_vertex(); +//! for _ in 0..2 { +//! graph.add_edge(v1, v1); +//! graph.add_edge(v1, v2); +//! } +//! assert_eq!(graph.edge_count(), 4); +//! assert_eq!(graph.degree(v1), 6); +//! ``` +//! +//! [BFS]: crate::algorithms::bfs +//! [Dijkstra's algorithm]: crate::algorithms::dijkstra +//! [DFS]: crate::algorithms::dfs +//! [`EdgeMap`]: crate::maps::EdgeMap +//! [`VertexMap`]: crate::maps::VertexMap +//! [`AppendGraph`]: crate::models::AppendGraph +//! [`Graph`]: crate::models::Graph +//! [`GraphTopology`]: crate::traits::GraphTopology +//! [`GraphTopology::Edge`]: crate::traits::GraphTopology::Edge +//! [`GraphTopology::Vertex`]: crate::traits::GraphTopology::Vertex +//! [`GraphTopologyDeletion`]: crate::traits::GraphTopologyDeletion + #![warn(missing_docs)] pub mod algorithms; diff --git a/src/maps.rs b/src/maps.rs index 1bda1a1..277ceb5 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -23,7 +23,6 @@ impl VertexMap { self.capacity() } - pub fn sync>(&mut self, graph: &G) { self.inner.resize(graph.vertex_capacity()); } diff --git a/src/models.rs b/src/models.rs index 5f556aa..d6b95a4 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,5 +1,7 @@ pub mod append_graph; pub mod graph; +// TODO: Compressed-sparse-row graph model. + pub use append_graph::{AppendGraph, AppendGraphEdgeMap, AppendGraphVertexMap}; pub use graph::{Graph, GraphEdgeMap, GraphVertexMap}; From 2c53d9294381dafac6726bafe12c201a37b2c1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 09:50:43 +0200 Subject: [PATCH 3/7] Fix readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1aab12a..9954e5b 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ assert_eq!(result.predecessors[v3], None); This project is dual-licensed under the terms of both: -- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/license/mit) You may choose either license for your use of this software. From 7345684f0069a2f14ef3571cc2c71d798f080bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 11:04:08 +0200 Subject: [PATCH 4/7] Add documentation for traits module --- src/traits.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index e3112bb..7c948c2 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,35 +2,244 @@ use crate::maps::{EdgeMap, VertexMap}; // 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. +/// +/// # Vertices and Edges +/// +/// Vertices and edges are identified by opaque handles ([`Vertex`] and [`Edge`]) that implement +/// [`Copy`] and [`Eq`]. Handles remain valid for the lifetime of the graph unless the graph also +/// implements [`GraphTopologyDeletion`] and the item identified by the handle is explicitly +/// deleted. +/// +/// 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 +/// +/// This trait covers graph construction and querying only. To delete vertices and edges, see +/// [`GraphTopologyDeletion`]. +/// +/// [`Edge`]: GraphTopology::Edge +/// [`Vertex`]: GraphTopology::Vertex pub trait GraphTopology { + /// An opaque, stable handle identifying a vertex. type Vertex: Copy + Eq; + + /// An opaque, stable handle identifying an edge. type Edge: Copy + Eq; + + /// A resumable position in the incidence list of a vertex. + /// + /// Prefer [`incidences`](Self::incidences) for straightforward iteration. A cursor is useful + /// when an algorithm needs to pause traversal, perform other graph queries or mutations, and + /// then continue from where it left off. This cursor type can be obtained via + /// [`incidence_cursor`](Self::incidence_cursor). type IncidenceCursor: IncidenceCursor + Copy; + /// 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 a [`VertexMap`] with every slot initialised to `default`. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let v1 = graph.add_vertex(); + /// let mut labels = graph.vertex_map("z"); + /// assert_eq!(labels[v1], "z"); + /// labels[v1] = "a"; + /// assert_eq!(labels[v1], "a"); + /// + /// // A new vertex is immediately available for read and write. + /// let v2 = graph.add_vertex(); + /// assert_eq!(labels[v2], "z"); + /// labels[v2] = "b"; + /// assert_eq!(labels[v2], "b"); + /// ``` fn vertex_map(&self, default: T) -> VertexMap; + + /// 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 [`EdgeMap`] with every slot initialised to `default`. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// let mut graph = Graph::new(); + /// let v1 = graph.add_vertex(); + /// let v2 = graph.add_vertex(); + /// let e1 = graph.add_edge(v1, v2); + /// let mut weights = graph.edge_map(5); + /// assert_eq!(weights[e1], 5); + /// weights[e1] = 1; + /// assert_eq!(weights[e1], 1); + /// + /// // A new edge is immediately available for read and write. + /// let e2 = graph.add_edge(v1, v2); + /// assert_eq!(weights[e2], 5); + /// weights[e2] = 2; + /// assert_eq!(weights[e2], 2); + /// ``` fn edge_map(&self, default: T) -> EdgeMap; + + /// Returns the degree of `v`, i.e. the number of incident edges, where each loop contributes 2. + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn degree(&self, v: Self::Vertex) -> usize; + + /// Returns `true` if there is at least one edge between `v1` and `v2`, and `false` otherwise. + /// + /// A vertex is adjacent to itself if and only if it has a loop edge. + /// + /// # Panics + /// + /// Panics if `v1` or `v2` is not a valid vertex of this graph. fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; + + /// Returns an iterator over all vertices in the graph. fn vertices(&self) -> impl Iterator; + + /// Returns an iterator over all vertices adjacent to `v`. + /// + /// Algorithms may prefer this over [`incidences`](Self::incidences) if the edges are not + /// required, since implementors may be able to provide an iterator faster than the trivial + /// mapping. The complexity of this method must not exceed that of + /// [`incidences`](Self::incidences). + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator; + + /// Returns the two endpoints of edge `e`, which are identical if and only if `e` is a loop + /// edge. + /// + /// # Panics + /// + /// Panics if `e` is not a valid edge of this graph. fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex); + + /// Returns an iterator over all edges in the graph. fn edges(&self) -> impl Iterator; + + /// Returns an iterator over all edges incident to `v`. + /// + /// Algorithms may prefer this over [`incidences`](Self::incidences) if the vertices are not + /// required, since implementors may be able to provide an iterator faster than the trivial + /// mapping. The complexity of this method must not exceed that of + /// [`incidences`](Self::incidences). + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn incident_edges(&self, v: Self::Vertex) -> impl Iterator; + + /// Returns an iterator over all incidences of `v`, i.e. vertex-edge pairs `(u, e)` such that + /// `u` is adjacent to `v` and `e` is an edge between them. + /// + /// Use [`incidence_cursor`](Self::incidence_cursor) instead for a traversal that needs to + /// suspend and resume across other state updates. + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn incidences(&self, v: Self::Vertex) -> impl Iterator; + + /// Returns a cursor over all incidences of `v`, analogously to + /// [`incidences`](Self::incidences), initially positioned before the first one. + /// + /// See [`IncidenceCursor`](Self::IncidenceCursor) for when to prefer a cursor over + /// [`incidences`](Self::incidences). + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor; + + /// Adds a new isolated vertex and returns its handle. fn add_vertex(&mut self) -> Self::Vertex; + + /// Adds a new edge between `v1` and `v2` and returns its handle. + /// + /// # Panics + /// + /// Panics if `v1` or `v2` is not a valid vertex of this graph. fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge; } +/// A trait that adds deletion operations to an undirected graph topology. +/// +/// This trait provides methods for deletion of vertices and edges in an undirected graph. These +/// operations will invalidate handles to all deleted vertices and edges. Methods accepting invalid +/// vertices or edges as parameters panic. pub trait GraphTopologyDeletion: GraphTopology { + /// Deletes the vertex `v` and all its incident edges from the graph. Note that this also + /// invalidates the handles of all edges incident to `v`. + /// + /// # Panics + /// + /// Panics if `v` is not a valid vertex of this graph. fn delete_vertex(&mut self, v: Self::Vertex); + + /// Deletes the edge `e` from the graph. This operation only invalidates `e` and no other vertex + /// or edge handles. + /// + /// # Panics + /// + /// Panics if `e` is not a valid edge of this graph. fn delete_edge(&mut self, e: Self::Edge); } +/// A cursor for traversing the incidences of a vertex one step at a time. +/// +/// Cursors are obtained via [`GraphTopology::incidence_cursor`]. See +/// [`GraphTopology::IncidenceCursor`] for guidance on when to prefer a cursor over +/// [`GraphTopology::incidences`]. pub trait IncidenceCursor { + /// Advances the cursor and returns the next incidence as `Some((u, e))`, or `None` if the + /// traversal is exhausted. + /// + /// # Examples + /// + /// ``` + /// # use grapherity::prelude::*; + /// # use grapherity::models::Graph; + /// // Constructs a graph with two vertices connected to `v`. + /// let mut graph = Graph::new(); + /// let v = graph.add_vertex(); + /// for _ in 0..2 { + /// let u = graph.add_vertex(); + /// graph.add_edge(u, v); + /// } + /// + /// // Iterates over the incidences of `v` with a cursor. + /// let mut c1 = graph.incidence_cursor(v); + /// assert!(c1.next(&graph).is_some()); + /// let mut c2 = c1; + /// // Continues iteration with original cursor. + /// assert!(c1.next(&graph).is_some()); + /// assert!(c1.next(&graph).is_none()); + /// // Iterates over the last incidence again with the copied cursor. + /// assert!(c2.next(&graph).is_some()); + /// assert!(c2.next(&graph).is_none()); + /// ``` fn next(&mut self, graph: &G) -> Option<(G::Vertex, G::Edge)>; } From b62c263b084be5d25cb4cb47cf78190879f5e45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 11:12:21 +0200 Subject: [PATCH 5/7] Update package version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 17d64d2..0ccf888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grapherity" -version = "0.2.2" +version = "0.2.3" authors = ["Stefan Müller"] edition = "2024" rust-version = "1.85.0" From e6b08d9148eea184990ee1dd2542322953d05a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 11:13:31 +0200 Subject: [PATCH 6/7] Remove doc warnings for early doc release --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 892c8aa..3b611e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,8 +127,6 @@ //! [`GraphTopology::Vertex`]: crate::traits::GraphTopology::Vertex //! [`GraphTopologyDeletion`]: crate::traits::GraphTopologyDeletion -#![warn(missing_docs)] - pub mod algorithms; pub mod maps; pub mod models; From e8568a084a75bfa897b8cc1b7158fae4f4cdcb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 17 Jul 2026 11:15:12 +0200 Subject: [PATCH 7/7] Fix doc example for early doc release --- src/traits.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/traits.rs b/src/traits.rs index 7c948c2..d98b55a 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -222,6 +222,7 @@ pub trait IncidenceCursor { /// ``` /// # use grapherity::prelude::*; /// # use grapherity::models::Graph; + /// # use grapherity::traits::IncidenceCursor; /// // Constructs a graph with two vertices connected to `v`. /// let mut graph = Graph::new(); /// let v = graph.add_vertex();