Merge branch 'docs' into release-0.2.4

This commit is contained in:
2026-07-31 08:44:10 +02:00
9 changed files with 624 additions and 20 deletions
+51 -2
View File
@@ -1,13 +1,26 @@
//! [`AppendGraph`], an undirected graph topology supporting addition only.
use crate::maps::{EdgeMap, VertexMap};
use crate::traits::{GraphTopology, IncidenceCursor};
/// An opaque handle identifying a vertex in an [`AppendGraph`].
///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Vertex(usize);
/// An opaque handle identifying an edge in an [`AppendGraph`].
///
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Edge(usize);
/// A [`VertexMap`] for [`AppendGraph`] vertices.
pub type AppendGraphVertexMap<T> = VertexMap<Vertex, T>;
/// An [`EdgeMap`] for [`AppendGraph`] edges.
pub type AppendGraphEdgeMap<T> = EdgeMap<Edge, T>;
impl Edge {
@@ -16,6 +29,7 @@ impl Edge {
}
}
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
struct VertexIncidenceHeader {
incidence_count: usize,
first_incidence: Option<Edge>,
@@ -27,6 +41,9 @@ struct IncidenceEntry {
adjacent: Vertex,
}
/// A resumable cursor over the incidences of a single vertex in an [`AppendGraph`].
///
/// Obtain via [`AppendGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
#[derive(Copy, Clone)]
pub struct AppendGraphIncidenceCursor {
incidence: Option<Edge>,
@@ -40,12 +57,44 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
}
}
/// An undirected graph that supports adding vertices and edges, but not deleting them.
///
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it
/// repeatedly. [`Vertex`] and [`Edge`] handles are never invalidated. Use [`Graph`] instead if you
/// need to remove vertices or edges.
///
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
/// vertex neighborhood traversals result in scattered index jumps.
///
/// # Examples
///
/// ```
/// use grapherity::prelude::*;
/// use grapherity::models::AppendGraph;
///
/// let mut graph = AppendGraph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let e = graph.add_edge(v1, v2);
/// assert!(graph.are_adjacent(v1, v2));
/// ```
///
/// # Time and space complexity
///
/// 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|)*.
///
/// [`add_vertex`]: Self::add_vertex
/// [`add_edge`]: Self::add_edge
/// [`degree`]: Self::degree
/// [`Graph`]: crate::models::Graph
pub struct AppendGraph {
vertices: Vec<VertexIncidenceHeader>,
incidences: Vec<IncidenceEntry>,
}
impl AppendGraph {
/// Creates an empty graph instance with no vertices or edges.
pub fn new() -> Self {
Self {
vertices: vec![],
@@ -53,8 +102,8 @@ impl AppendGraph {
}
}
// Adds a single incidence of an edge, which is composed by two such incidences, to the
// incidences vector.
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences vector.
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) {
self.incidences.push(IncidenceEntry {
next: self.vertices[v1.0].first_incidence.take(),
+76 -7
View File
@@ -1,12 +1,26 @@
//! [`Graph`], an undirected graph topology supporting addition and deletion.
use typed_generational_arena::{Arena, Index};
use crate::maps::{EdgeMap, VertexMap};
use crate::traits::{GraphTopology, GraphTopologyDeletion, IncidenceCursor};
/// An opaque handle identifying a vertex in a [`Graph`].
///
/// Handles remain valid until the vertex is explicitly deleted. Obtain via graph methods like
/// [`Graph::add_vertex`] and [`Graph::vertices`].
pub type Vertex = Index<VertexIncidenceHeader, usize, usize>;
/// An opaque handle identifying an edge in a [`Graph`].
///
/// Handles remain valid until the edge is explicitly deleted, or one of its endpoint vertices is
/// deleted. Obtain via graph methods like [`Graph::add_edge`] and [`Graph::edges`].
pub type Edge = Index<IncidenceEntry, usize, usize>;
/// A [`VertexMap`] for [`Graph`] vertices.
pub type GraphVertexMap<T> = VertexMap<Vertex, T>;
/// An [`EdgeMap`] for [`Graph`] edges.
pub type GraphEdgeMap<T> = EdgeMap<Edge, T>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -15,11 +29,18 @@ struct VertexSlot(usize);
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct IncidenceSlot(usize);
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not
/// intended for direct external use.
#[doc(hidden)]
pub struct VertexIncidenceHeader {
incidence_count: usize,
first_incidence: Option<IncidenceSlot>,
}
/// `pub` because [`Edge`] references it as a type parameter of the underlying arena. Not intended
/// for direct external use.
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct IncidenceEntry {
next: Option<IncidenceSlot>,
@@ -36,6 +57,9 @@ impl IncidentEdgeCursor {
}
}
/// A resumable cursor over the incidences of a single vertex in a [`Graph`].
///
/// Obtain via [`Graph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
#[derive(Copy, Clone)]
pub struct GraphIncidenceCursor {
incidence: Option<IncidenceSlot>,
@@ -52,6 +76,49 @@ impl IncidenceCursor<Graph> for GraphIncidenceCursor {
}
}
/// An undirected graph that supports adding vertices and edges, and deleting them.
///
/// `Graph` is suited for workloads that need to modify the graph structure over time, i.e. adding
/// amd removing vertices and edges. [`Vertex`] and [`Edge`] handles remain valid until the vertex
/// or edge they identify is explicitly deleted. Use [`AppendGraph`] instead if you only need to
/// append vertices and edges.
///
/// Incidences are stored as interleaved adjacency lists in a generational [`Arena`]. In general,
/// vertex neighborhood traversals result in scattered memory accesses.
///
/// # Examples
///
/// ```
/// use grapherity::prelude::*;
/// use grapherity::models::Graph;
///
/// let mut graph = Graph::new();
/// let v1 = graph.add_vertex();
/// let v2 = graph.add_vertex();
/// let _e = graph.add_edge(v1, v2);
/// assert!(graph.are_adjacent(v1, v2));
/// graph.delete_vertex(v1);
/// assert_eq!(graph.degree(v2), 0);
/// ```
///
/// # Time and space complexity
///
/// Both [`add_vertex`] and [`add_edge`] run in amortised *O(1)* time. [`degree`] runs in *O(1)*
/// time since vertex degrees are stored. [`delete_vertex`] runs in *O(degree(v))* time.
/// [`delete_edge`] runs in *O(degree(u) + degree(v))* time, where *u* and *v* are the edge's
/// endpoints.
///
/// Space complexity is *O(|V| + |E|)*, but freed slots for vertices and edges are not compacted and
/// their allocation is never reclaimed. Capacity only grows. If additions and deletions are both
/// planned, performing deletions first allows freed slots to be reused by subsequent additions,
/// potentially avoiding reallocation.
///
/// [`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
pub struct Graph {
// TODO: Arena index and generation types could be externalized to Graph.
vertices: Arena<VertexIncidenceHeader, usize, usize>,
@@ -59,6 +126,7 @@ pub struct Graph {
}
impl Graph {
/// Creates an empty graph instance with no vertices or edges.
pub fn new() -> Self {
Self {
vertices: Arena::new(),
@@ -66,8 +134,8 @@ impl Graph {
}
}
// Adds a single incidence of an edge, which is composed by two such incidences, to the
// incidences arena, and returns its index.
/// Adds a single incidence of an edge, which is composed by two such incidences, to the
/// incidences arena, and returns its index.
fn add_incidence(&mut self, v1: Vertex, v2: Vertex) -> Edge {
let edge = self.incidences.insert(IncidenceEntry {
next: self.vertices[v1].first_incidence.take(),
@@ -94,8 +162,9 @@ impl Graph {
(f, e_entry, f_entry)
}
// Updates the source vertex incidence list after the incidence "e" was deleted from the
// incidence arena. "next" is the next incidence after "e" in the source vertex incidence list.
/// Updates the `source` vertex incidence list after the incidence `e` was deleted from the
/// incidence arena. `next` is the next incidence after `e` in the `source` vertex incidence
/// list.
fn update_incidence_list(
&mut self,
e: Edge,
@@ -275,10 +344,10 @@ impl GraphTopologyDeletion for Graph {
}
}
// The incidence entries are removed before patching the linked lists. This is safe because
// update_incidence_list() searches by raw slot index (IncidenceSlot.0) and only dereferences
// the predecessor, never the removed entries themselves.
fn delete_edge(&mut self, e: Self::Edge) {
// The incidence entries are removed before patching the linked lists. This is safe because
// `update_incidence_list` searches by raw slot index (IncidenceSlot.0) and only
// dereferences the predecessor, never the removed entries themselves.
let (f, e_entry, f_entry) = self.remove_incidence_pair(e);
if e_entry.adjacent != f_entry.adjacent {
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);