Add FrozenGraph (without tests)
This commit is contained in:
@@ -6,7 +6,7 @@ This crate provides data structures to model graphs and to perform algorithms on
|
|||||||
|
|
||||||
Currently supported are:
|
Currently supported are:
|
||||||
|
|
||||||
* Undirected graph types `Graph` and `AppendGraph`, built on a flat, index-based adjacency list,
|
* Undirected graph types `Graph`, `AppendGraph`, and `FrozenGraph`, built on a flat, index-based adjacency list,
|
||||||
* Maps to freely associate custom data with vertices and edges,
|
* Maps to freely associate custom data with vertices and edges,
|
||||||
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
|
* Connectivity and pathing algorithms: Dijkstra's algorithm, DFS, and BFS in different variants,
|
||||||
* Exposed traits to implement custom graph data structures or algorithms.
|
* Exposed traits to implement custom graph data structures or algorithms.
|
||||||
|
|||||||
+12
-5
@@ -5,8 +5,8 @@
|
|||||||
//!
|
//!
|
||||||
//! Currently supported are:
|
//! Currently supported are:
|
||||||
//!
|
//!
|
||||||
//! * Undirected graph types [`Graph`] and [`AppendGraph`] built on a flat, index-based adjacency
|
//! * Undirected graph types [`Graph`], [`AppendGraph`], and [`FrozenGraph`] built on a flat,
|
||||||
//! list,
|
//! index-based adjacency list,
|
||||||
//! * [`ElementMap`] to freely associate custom data with vertices and edges,
|
//! * [`ElementMap`] to freely associate custom data with vertices and edges,
|
||||||
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
|
//! * Connectivity and pathing algorithms: [Dijkstra's algorithm], [DFS], and [BFS] in different
|
||||||
//! variants,
|
//! variants,
|
||||||
@@ -62,9 +62,11 @@
|
|||||||
//!
|
//!
|
||||||
//! # Graph types
|
//! # Graph types
|
||||||
//!
|
//!
|
||||||
//! [`Graph`] and [`AppendGraph`] both implement [`GraphTopology`] using a flat, index-based adjacency
|
//! [`Graph`], [`AppendGraph`], and [`FrozenGraph`] all implement [`GraphTopology`] using a flat,
|
||||||
//! list to store edges, which means that vertex and edge insertions happen in `O(1)`. Degree
|
//! index-based adjacency list to store edges. Degree lookups run in `O(1)`.
|
||||||
//! lookups are also `O(1)`.
|
//!
|
||||||
|
//! [`Graph`] and [`AppendGraph`] implement [`GraphTopologyAddition`], which provides functionality
|
||||||
|
//! for iterative graph construction. Vertex and edge insertions happen in `O(1)`.
|
||||||
//!
|
//!
|
||||||
//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
|
//! [`Graph`] additionally implements [`GraphTopologyDeletion`], thereby supporting deletion of
|
||||||
//! vertices and edges.
|
//! vertices and edges.
|
||||||
@@ -73,6 +75,10 @@
|
|||||||
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
|
//! be smaller and more performant compared to [`Graph`] by not requiring the per-element generation
|
||||||
//! tracking needed for stable handles after deletion.
|
//! tracking needed for stable handles after deletion.
|
||||||
//!
|
//!
|
||||||
|
//! [`FrozenGraph`] uses a memory layout that is more cache-friendly for traversals than
|
||||||
|
//! [`AppendGraph`], but the trade-off is that its topology must be constructed during its
|
||||||
|
//! initialization. It cannot be modified afterward.
|
||||||
|
//!
|
||||||
//! # Vertices and edges
|
//! # Vertices and edges
|
||||||
//!
|
//!
|
||||||
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
|
//! Graphs in this library use stable indices [`GraphTopology::Vertex`] and [`GraphTopology::Edge`]
|
||||||
@@ -119,6 +125,7 @@
|
|||||||
//! [DFS]: crate::algorithms::dfs
|
//! [DFS]: crate::algorithms::dfs
|
||||||
//! [`ElementMap`]: crate::maps::ElementMap
|
//! [`ElementMap`]: crate::maps::ElementMap
|
||||||
//! [`AppendGraph`]: crate::models::AppendGraph
|
//! [`AppendGraph`]: crate::models::AppendGraph
|
||||||
|
//! [`FrozenGraph`]: crate::models::FrozenGraph
|
||||||
//! [`Graph`]: crate::models::Graph
|
//! [`Graph`]: crate::models::Graph
|
||||||
//! [`traits`]: crate::traits
|
//! [`traits`]: crate::traits
|
||||||
//! [`GraphTopology`]: crate::traits::GraphTopology
|
//! [`GraphTopology`]: crate::traits::GraphTopology
|
||||||
|
|||||||
+4
-2
@@ -1,11 +1,13 @@
|
|||||||
//! Concrete graph topology models.
|
//! Concrete graph topology models.
|
||||||
|
|
||||||
pub mod append_graph;
|
pub mod append_graph;
|
||||||
|
pub mod frozen_graph;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
|
|
||||||
// TODO: Compressed-sparse-row graph model.
|
|
||||||
|
|
||||||
pub use append_graph::{
|
pub use append_graph::{
|
||||||
AppendGraph, AppendGraphEdgeMap, AppendGraphIncidence, AppendGraphVertexMap,
|
AppendGraph, AppendGraphEdgeMap, AppendGraphIncidence, AppendGraphVertexMap,
|
||||||
};
|
};
|
||||||
|
pub use frozen_graph::{
|
||||||
|
FrozenGraph, FrozenGraphEdgeMap, FrozenGraphIncidence, FrozenGraphVertexMap,
|
||||||
|
};
|
||||||
pub use graph::{Graph, GraphEdgeMap, GraphIncidence, GraphVertexMap};
|
pub use graph::{Graph, GraphEdgeMap, GraphIncidence, GraphVertexMap};
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
|||||||
///
|
///
|
||||||
/// `AppendGraph` is optimised for workloads that incrementally build a graph and query it
|
/// `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
|
/// repeatedly. [`Vertex`] and [`Edge`] handles are never invalidated. Use [`Graph`] instead if you
|
||||||
/// need to remove vertices or edges.
|
/// need to remove vertices or edges. Use [`FrozenGraph`] if you do not need incremental
|
||||||
|
/// construction.
|
||||||
///
|
///
|
||||||
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
|
/// Incidences are stored as interleaved adjacency lists in a single flat [`Vec`]. In general,
|
||||||
/// vertex neighborhood traversals result in scattered index jumps.
|
/// vertex neighborhood traversals result in scattered index jumps.
|
||||||
@@ -93,6 +94,7 @@ impl IncidenceCursor<AppendGraph> for AppendGraphIncidenceCursor {
|
|||||||
/// [`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
|
||||||
/// [`Graph`]: crate::models::Graph
|
/// [`Graph`]: crate::models::Graph
|
||||||
pub struct AppendGraph {
|
pub struct AppendGraph {
|
||||||
vertices: Vec<VertexIncidenceHeader>,
|
vertices: Vec<VertexIncidenceHeader>,
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
//! [`FrozenGraph`], an undirected graph topology supporting only one-time construction.
|
||||||
|
|
||||||
|
use std::ops::IndexMut;
|
||||||
|
|
||||||
|
use crate::maps::ElementMap;
|
||||||
|
use crate::traits::{GraphTopology, Incidence, IncidenceCursor};
|
||||||
|
|
||||||
|
/// An opaque handle identifying a vertex in a [`FrozenGraph`].
|
||||||
|
///
|
||||||
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
|
/// [`FrozenGraph::vertices`].
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub struct Vertex(usize);
|
||||||
|
|
||||||
|
/// An opaque handle identifying an edge in a [`FrozenGraph`].
|
||||||
|
///
|
||||||
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
|
/// [`FrozenGraph::edges`].
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub struct Edge(usize);
|
||||||
|
|
||||||
|
/// An [`ElementMap`] for [`FrozenGraph`] vertices.
|
||||||
|
pub type FrozenGraphVertexMap<T> = ElementMap<Vertex, T>;
|
||||||
|
|
||||||
|
/// An [`ElementMap`] for [`FrozenGraph`] edges.
|
||||||
|
pub type FrozenGraphEdgeMap<T> = ElementMap<Edge, T>;
|
||||||
|
|
||||||
|
/// An [`Incidence`] for [`FrozenGraph`].
|
||||||
|
pub type FrozenGraphIncidence = Incidence<Vertex, Edge>;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct IncidenceEntry {
|
||||||
|
adjacent: Vertex,
|
||||||
|
opposite: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resumable cursor over the incidences of a single vertex in a [`FrozenGraph`].
|
||||||
|
///
|
||||||
|
/// Obtain via [`FrozenGraph::incidence_cursor`]. See [`IncidenceCursor`] on usage guidance.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct FrozenGraphIncidenceCursor {
|
||||||
|
current: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IncidenceCursor<FrozenGraph> for FrozenGraphIncidenceCursor {
|
||||||
|
fn next(&mut self, graph: &FrozenGraph) -> Option<FrozenGraphIncidence> {
|
||||||
|
if self.current < self.end {
|
||||||
|
let incidence = graph.normalize_incidence(self.current);
|
||||||
|
self.current += 1;
|
||||||
|
Some(incidence)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An undirected graph that supports only one-time construction, and no further modification.
|
||||||
|
///
|
||||||
|
/// `FrozenGraph` is optimised for workloads that repeatedly query a graph, but do not require
|
||||||
|
/// changing it once it is constructed. [`Vertex`] and [`Edge`] handles are never invalidated. Use
|
||||||
|
/// [`AppendGraph`] instead if you need to incrementally add vertices or edges, or [`Graph`] if you
|
||||||
|
/// need to remove vertices or edges after initial construction.
|
||||||
|
///
|
||||||
|
/// `FrozenGraph` uses a compressed-sparse data representation. Incidences are stored as contiguous
|
||||||
|
/// adjacency lists per vertex in a single flat [`Vec`]. Vertex neighborhood traversals are
|
||||||
|
/// therefore cache-friendly without index jumps.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grapherity::prelude::*;
|
||||||
|
/// use grapherity::models::{FrozenGraph, AppendGraph};
|
||||||
|
///
|
||||||
|
/// // Constructs a FrozenGraph instance via AppendGraph.
|
||||||
|
/// let mut graph = AppendGraph::new();
|
||||||
|
/// let v1 = graph.add_vertex();
|
||||||
|
/// let v2 = graph.add_vertex();
|
||||||
|
/// let e = graph.add_edge(v1, v2);
|
||||||
|
/// let (graph, vertices, edges) = FrozenGraph::from_graph(&graph);
|
||||||
|
///
|
||||||
|
/// // Queries the FrozenGraph instance.
|
||||||
|
/// assert!(graph.are_adjacent(vertices[v1], vertices[v2]));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Time and space complexity
|
||||||
|
///
|
||||||
|
/// [`degree`] runs in *O(1)* time. Space complexity is *O(|V| + |E|)*.
|
||||||
|
///
|
||||||
|
/// [`degree`]: Self::degree
|
||||||
|
/// [`AppendGraph`]: crate::models::AppendGraph
|
||||||
|
/// [`Graph`]: crate::models::Graph
|
||||||
|
pub struct FrozenGraph {
|
||||||
|
vertices: Vec<usize>,
|
||||||
|
incidences: Vec<IncidenceEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrozenGraph {
|
||||||
|
/// Creates a graph instance as a copy of another graph.
|
||||||
|
///
|
||||||
|
/// Returns the copied graph instance and two mappings of the `source` graph vertices and edges
|
||||||
|
/// to the new ones. Note that the returned mappings cannot support additional growth, i.e. any
|
||||||
|
/// vertex or edge added to the `source` graph after calling `from_graph` will map to an invalid
|
||||||
|
/// vertex or edge in the new `FrozenGraph`.
|
||||||
|
pub fn from_graph<G: GraphTopology>(
|
||||||
|
source: &G,
|
||||||
|
) -> (
|
||||||
|
Self,
|
||||||
|
ElementMap<G::Vertex, Vertex>,
|
||||||
|
ElementMap<G::Edge, Edge>,
|
||||||
|
) {
|
||||||
|
// Builds vertex list with sentinel and vertex map.
|
||||||
|
let mut vertex_map = source.vertex_map(Vertex(usize::MAX));
|
||||||
|
let mut vertices = Vec::with_capacity(source.vertex_count() + 1);
|
||||||
|
let mut offset = 0;
|
||||||
|
for (i, v) in source.vertices().enumerate() {
|
||||||
|
vertex_map[v] = Vertex(i);
|
||||||
|
vertices.push(offset);
|
||||||
|
offset += source.degree(v);
|
||||||
|
}
|
||||||
|
vertices.push(offset);
|
||||||
|
|
||||||
|
// Builds incidence list and edge map. Uses "usize::MAX" as "unset" here: an edge can never
|
||||||
|
// be normalized to "usize::MAX", since it must be the smaller index of two incidences.
|
||||||
|
const UNSET: usize = usize::MAX;
|
||||||
|
let mut edge_map = source.edge_map(Edge(UNSET));
|
||||||
|
let mut incidences = Vec::with_capacity(source.edge_count() * 2);
|
||||||
|
for v in source.vertices() {
|
||||||
|
for Incidence { vertex: u, edge: e } in source.incidences(v) {
|
||||||
|
let em = edge_map.index_mut(e);
|
||||||
|
if em.0 == UNSET {
|
||||||
|
// Adds the first incidence for the edge, but we don't know the opposite, yet.
|
||||||
|
*em = Edge(incidences.len());
|
||||||
|
incidences.push(IncidenceEntry {
|
||||||
|
adjacent: vertex_map[u],
|
||||||
|
opposite: 0,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Adds the second incidence for the edge and fills the opposite for the first.
|
||||||
|
incidences[em.0].opposite = incidences.len();
|
||||||
|
incidences.push(IncidenceEntry {
|
||||||
|
adjacent: vertex_map[u],
|
||||||
|
opposite: em.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
vertices,
|
||||||
|
incidences,
|
||||||
|
},
|
||||||
|
vertex_map,
|
||||||
|
edge_map,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = usize> {
|
||||||
|
self.vertices[v.0]..self.vertices[v.0 + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_edge(&self, e: usize) -> Edge {
|
||||||
|
let f = self.incidences[e].opposite;
|
||||||
|
Edge(if e < f { e } else { f })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_incidence(&self, i: usize) -> FrozenGraphIncidence {
|
||||||
|
let entry = &self.incidences[i];
|
||||||
|
Incidence {
|
||||||
|
vertex: entry.adjacent,
|
||||||
|
edge: Edge(if i < entry.opposite {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
entry.opposite
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<G: GraphTopology> From<&G> for FrozenGraph {
|
||||||
|
fn from(graph: &G) -> Self {
|
||||||
|
Self::from_graph(graph).0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphTopology for FrozenGraph {
|
||||||
|
type Vertex = Vertex;
|
||||||
|
type Edge = Edge;
|
||||||
|
type IncidenceCursor = FrozenGraphIncidenceCursor;
|
||||||
|
|
||||||
|
fn vertex_count(&self) -> usize {
|
||||||
|
self.vertices.len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_map<T: Clone>(&self, default: T) -> ElementMap<Self::Vertex, T> {
|
||||||
|
ElementMap::new(default, |v| v.0, self.vertex_count())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edge_count(&self) -> usize {
|
||||||
|
self.incidences.len() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||||
|
ElementMap::new(default, |e| e.0, self.edge_count())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn degree(&self, v: Self::Vertex) -> usize {
|
||||||
|
self.vertices[v.0 + 1] - self.vertices[v.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool {
|
||||||
|
self.adjacent_vertices(v1).any(|x| x == v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
|
||||||
|
(0..self.vertices.len() - 1).map(Vertex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjacent_vertices(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Vertex> {
|
||||||
|
self.raw_incidences(v).map(|i| self.incidences[i].adjacent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||||
|
let entry = &self.incidences[e.0];
|
||||||
|
(self.incidences[entry.opposite].adjacent, entry.adjacent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
||||||
|
self.incidences
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, x)| (i < x.opposite).then_some(Edge(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
||||||
|
self.raw_incidences(v).map(|i| self.normalize_edge(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incidences(&self, v: Self::Vertex) -> impl Iterator<Item = FrozenGraphIncidence> {
|
||||||
|
self.raw_incidences(v).map(|i| self.normalize_incidence(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor {
|
||||||
|
FrozenGraphIncidenceCursor {
|
||||||
|
current: self.vertices[v.0],
|
||||||
|
end: self.vertices[v.0 + 1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user