Release 0.3.0 #8

Merged
warrence merged 69 commits from release-0.3.0 into main 2026-10-15 10:06:13 +02:00
9 changed files with 228 additions and 79 deletions
Showing only changes of commit b124e58b38 - Show all commits
+2 -2
View File
@@ -146,8 +146,8 @@ pub mod traits;
/// Convenience re-exports of graph topology traits for common use. /// Convenience re-exports of graph topology traits for common use.
pub mod prelude { pub mod prelude {
pub use crate::traits::{ pub use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, GraphTopologyDeletion, GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
Incidence, IncidenceCursor, GraphTopologyDeletion, Incidence, IncidenceCursor,
}; };
} }
+29
View File
@@ -94,6 +94,35 @@ impl<E: Copy, T: Clone> ElementMap<E, T> {
to_index: self.to_index, to_index: self.to_index,
} }
} }
/// Creates a new `ElementMap` indexed by elements of type `F`, such that for each element *e*
/// of this map that `mapping` maps to some *f*, the new map contains at *f* a clone of the
/// value this map contains at *e*.
///
/// A slot of `mapping` counts as unmapped if it holds the default value of `mapping`, which
/// must therefore never be a valid element of `F`. Unmapped slots are skipped. Slots of the new
/// map that no element is mapped to, and slots of this map beyond the length of `mapping` or
/// vice versa, are left at this map's default value. The `template` is only used to determine
/// the length and the index conversion function of the new map.
///
/// Iterates the raw slots of both maps, so it neither needs the graph nor the element handles.
pub(crate) fn remap<F: Copy + Eq>(
&self,
mapping: &ElementMap<E, F>,
template: &ElementMap<F, ()>,
) -> ElementMap<F, T> {
let mut result = ElementMap {
data: vec![self.default.clone(); template.data.len()],
default: self.default.clone(),
to_index: template.to_index,
};
for (value, &f) in self.data.iter().zip(&mapping.data) {
if f != mapping.default {
result.data[(template.to_index)(f)] = value.clone();
}
}
result
}
} }
impl<E: Copy, T: Clone> Index<E> for ElementMap<E, T> { impl<E: Copy, T: Clone> Index<E> for ElementMap<E, T> {
+5 -9
View File
@@ -5,7 +5,8 @@ use std::num::NonZeroUsize;
use crate::maps::ElementMap; use crate::maps::ElementMap;
use crate::models; use crate::models;
use crate::traits::{ use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence, IncidenceCursor, GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence,
IncidenceCursor,
}; };
/// An opaque handle identifying a vertex in an [`AppendGraph`]. /// An opaque handle identifying a vertex in an [`AppendGraph`].
@@ -261,17 +262,12 @@ impl GraphTopologyAddition for AppendGraph {
} }
impl GraphTopologyConversion for AppendGraph { impl GraphTopologyConversion for AppendGraph {
fn convert<H: GraphTopology>( fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
source: &H,
) -> (
Self,
ElementMap<H::Vertex, Vertex>,
ElementMap<H::Edge, Edge>,
) {
let mut edge_map = source.edge_map(Edge(NonZeroUsize::MAX)); let mut edge_map = source.edge_map(Edge(NonZeroUsize::MAX));
let (result, vertex_map) = let (result, vertex_map) =
models::convert_to_addition(source, Vertex(usize::MAX), |e, new_e| edge_map[e] = new_e); models::convert_to_addition(source, Vertex(usize::MAX), |e, new_e| edge_map[e] = new_e);
(result, vertex_map, edge_map) let iso = GraphIsomorphism::new(&result, vertex_map, edge_map);
(result, iso)
} }
} }
+12 -18
View File
@@ -3,7 +3,9 @@
use std::ops::IndexMut; use std::ops::IndexMut;
use crate::maps::ElementMap; use crate::maps::ElementMap;
use crate::traits::{GraphTopology, GraphTopologyConversion, Incidence, IncidenceCursor}; use crate::traits::{
GraphIsomorphism, GraphTopology, GraphTopologyConversion, Incidence, IncidenceCursor,
};
/// An opaque handle identifying a vertex in a [`FrozenGraph`]. /// An opaque handle identifying a vertex in a [`FrozenGraph`].
/// ///
@@ -77,10 +79,10 @@ impl IncidenceCursor<FrozenGraph> for FrozenGraphIncidenceCursor {
/// let u = graph.add_vertex(); /// let u = graph.add_vertex();
/// let v = graph.add_vertex(); /// let v = graph.add_vertex();
/// let e = graph.add_edge(u, v); /// let e = graph.add_edge(u, v);
/// let (graph, vertex_map, edge_map) = FrozenGraph::convert(&graph); /// let (graph, isomorphism) = FrozenGraph::convert(&graph);
/// ///
/// // Queries the FrozenGraph instance via the returned map. /// // Queries the FrozenGraph instance via the returned map.
/// assert!(graph.are_adjacent(vertex_map[u], vertex_map[v])); /// assert!(graph.are_adjacent(isomorphism.vertex(u), isomorphism.vertex(v)));
/// ``` /// ```
/// ///
/// # Time and space complexity /// # Time and space complexity
@@ -193,13 +195,7 @@ impl GraphTopology for FrozenGraph {
} }
impl GraphTopologyConversion for FrozenGraph { impl GraphTopologyConversion for FrozenGraph {
fn convert<G: GraphTopology>( fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
source: &G,
) -> (
Self,
ElementMap<G::Vertex, Vertex>,
ElementMap<G::Edge, Edge>,
) {
// Builds vertex list with sentinel and vertex map. // Builds vertex list with sentinel and vertex map.
let mut vertex_map = source.vertex_map(Vertex(usize::MAX)); let mut vertex_map = source.vertex_map(Vertex(usize::MAX));
let mut vertices = Vec::with_capacity(source.vertex_count() + 1); let mut vertices = Vec::with_capacity(source.vertex_count() + 1);
@@ -237,14 +233,12 @@ impl GraphTopologyConversion for FrozenGraph {
} }
} }
( let result = Self {
Self { vertices,
vertices, incidences,
incidences, };
}, let iso = GraphIsomorphism::new(&result, vertex_map, edge_map);
vertex_map, (result, iso)
edge_map,
)
} }
} }
+5 -10
View File
@@ -6,8 +6,8 @@ use typed_generational_arena::{Arena, Index};
use crate::maps::ElementMap; use crate::maps::ElementMap;
use crate::models; use crate::models;
use crate::traits::{ use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, GraphTopologyDeletion, GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
Incidence, IncidenceCursor, GraphTopologyDeletion, Incidence, IncidenceCursor,
}; };
/// An opaque handle identifying a vertex in a [`Graph`]. /// An opaque handle identifying a vertex in a [`Graph`].
@@ -399,19 +399,14 @@ impl GraphTopologyDeletion for Graph {
} }
impl GraphTopologyConversion for Graph { impl GraphTopologyConversion for Graph {
fn convert<H: GraphTopology>( fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
source: &H,
) -> (
Self,
ElementMap<H::Vertex, Vertex>,
ElementMap<H::Edge, Edge>,
) {
let mut edge_map = source.edge_map(Edge::new(usize::MAX, usize::MAX)); let mut edge_map = source.edge_map(Edge::new(usize::MAX, usize::MAX));
let (result, vertex_map) = let (result, vertex_map) =
models::convert_to_addition(source, Vertex::new(usize::MAX, usize::MAX), |e, new_e| { models::convert_to_addition(source, Vertex::new(usize::MAX, usize::MAX), |e, new_e| {
edge_map[e] = new_e edge_map[e] = new_e
}); });
(result, vertex_map, edge_map) let iso = GraphIsomorphism::new(&result, vertex_map, edge_map);
(result, iso)
} }
} }
+22 -22
View File
@@ -185,16 +185,16 @@ impl MakeTestGraph for FrozenGraph {
[Vec<Incidence<Self::Vertex, Self::Edge>>; 10], [Vec<Incidence<Self::Vertex, Self::Edge>>; 10],
) { ) {
let (build, vertices, edges, incidences) = AppendGraph::standard(); let (build, vertices, edges, incidences) = AppendGraph::standard();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
( (
graph, graph,
vertices.map(|v| vertex_map[v]), vertices.map(|v| iso.vertex(v)),
edges.map(|(e, u, v)| (edge_map[e], vertex_map[u], vertex_map[v])), edges.map(|(e, u, v)| (iso.edge(e), iso.vertex(u), iso.vertex(v))),
incidences.map(|vec| { incidences.map(|vec| {
vec.iter() vec.iter()
.map(|i| Incidence { .map(|i| Incidence {
vertex: vertex_map[i.vertex], vertex: iso.vertex(i.vertex),
edge: edge_map[i.edge], edge: iso.edge(i.edge),
}) })
.collect() .collect()
}), }),
@@ -207,55 +207,55 @@ impl MakeTestGraph for FrozenGraph {
fn single_vertex() -> (Self, Self::Vertex) { fn single_vertex() -> (Self, Self::Vertex) {
let (build, v) = AppendGraph::single_vertex(); let (build, v) = AppendGraph::single_vertex();
let (graph, vertex_map, _) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertex_map[v]) (graph, iso.vertex(v))
} }
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge) { fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge) {
let (build, vertices, e) = AppendGraph::single_edge(); let (build, vertices, e) = AppendGraph::single_edge();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| vertex_map[v]), edge_map[e]) (graph, vertices.map(|v| iso.vertex(v)), iso.edge(e))
} }
fn disconnected() -> (Self, [Self::Vertex; 3]) { fn disconnected() -> (Self, [Self::Vertex; 3]) {
let (build, vertices) = AppendGraph::disconnected(); let (build, vertices) = AppendGraph::disconnected();
let (graph, vertex_map, _) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| vertex_map[v])) (graph, vertices.map(|v| iso.vertex(v)))
} }
fn loop_edge() -> (Self, Self::Vertex, Self::Edge) { fn loop_edge() -> (Self, Self::Vertex, Self::Edge) {
let (build, v, e) = AppendGraph::loop_edge(); let (build, v, e) = AppendGraph::loop_edge();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertex_map[v], edge_map[e]) (graph, iso.vertex(v), iso.edge(e))
} }
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]) { fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]) {
let (build, vertices, edges) = AppendGraph::multiple_edges(); let (build, vertices, edges) = AppendGraph::multiple_edges();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
( (
graph, graph,
vertices.map(|v| vertex_map[v]), vertices.map(|v| iso.vertex(v)),
edges.map(|e| edge_map[e]), edges.map(|e| iso.edge(e)),
) )
} }
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]) { fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]) {
let (build, vertices, edges) = AppendGraph::two_edge_path(); let (build, vertices, edges) = AppendGraph::two_edge_path();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
( (
graph, graph,
vertices.map(|v| vertex_map[v]), vertices.map(|v| iso.vertex(v)),
edges.map(|e| edge_map[e]), edges.map(|e| iso.edge(e)),
) )
} }
fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]) { fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]) {
let (build, vertices, edges) = AppendGraph::two_edge_path_with_loops(); let (build, vertices, edges) = AppendGraph::two_edge_path_with_loops();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build); let (graph, iso) = FrozenGraph::convert(&build);
( (
graph, graph,
vertices.map(|v| vertex_map[v]), vertices.map(|v| iso.vertex(v)),
edges.map(|e| edge_map[e]), edges.map(|e| iso.edge(e)),
) )
} }
} }
@@ -27,6 +27,6 @@ where
H: MakeTestGraph + GraphTopologyAddition, H: MakeTestGraph + GraphTopologyAddition,
{ {
let (mut source, vertices, _) = H::single_edge(); let (mut source, vertices, _) = H::single_edge();
let (mut graph, vertex_map, _) = G::convert(&source); let (mut graph, iso) = G::convert(&source);
fixtures::assert_mutation_independent(&mut source, &mut graph, &vertices, |v| vertex_map[v]); fixtures::assert_mutation_independent(&mut source, &mut graph, &vertices, |v| iso.vertex(v));
} }
@@ -31,13 +31,13 @@ where
H::Edge: Debug, H::Edge: Debug,
{ {
let (source, vertices, edges, _) = H::standard(); let (source, vertices, edges, _) = H::standard();
let (graph, vertex_map, edge_map) = G::convert(&source); let (graph, iso) = G::convert(&source);
fixtures::assert_isomorphic( fixtures::assert_isomorphic(
&source, &source,
&graph, &graph,
&vertices, &vertices,
|v| vertex_map[v], |v| iso.vertex(v),
&edges, &edges,
|e| edge_map[e], |e| iso.edge(e),
); );
} }
+148 -13
View File
@@ -362,14 +362,14 @@ pub struct Incidence<V, E> {
pub edge: E, pub edge: E,
} }
// TODO-0.3.0: Provide and document a way to transform an ElementMap for source to an ElementMap for the copy.
/// A trait for constructing an undirected graph topology by converting another graph /// A trait for constructing an undirected graph topology by converting another graph
/// implementation to this one. /// implementation to this one.
pub trait GraphTopologyConversion: GraphTopology { pub trait GraphTopologyConversion: GraphTopology {
/// Creates a new graph instance that is isomorphic to `source`. /// Creates a new graph instance that is isomorphic to `source`.
/// ///
/// Returns the converted graph instance and two mappings from the vertices and edges of /// Returns the converted graph instance and a [`GraphIsomorphism`] instance that maps the
/// `source` to the corresponding vertices and edges of the returned graph. /// vertices and edges of `source` to the corresponding vertices and edges of the returned
/// graph.
/// ///
/// # Examples /// # Examples
/// ///
@@ -380,18 +380,153 @@ pub trait GraphTopologyConversion: GraphTopology {
/// let mut source = AppendGraph::new(); /// let mut source = AppendGraph::new();
/// let u = source.add_vertex(); /// let u = source.add_vertex();
/// let v = source.add_vertex(); /// let v = source.add_vertex();
/// let e = source.add_edge(u, v); /// source.add_edge(u, v);
/// let (graph, vertex_map, edge_map) = FrozenGraph::convert(&source); /// let (graph, iso) = FrozenGraph::convert(&source);
/// ///
/// assert!(graph.are_adjacent(vertex_map[u], vertex_map[v])); /// assert!(graph.are_adjacent(iso.vertex(u), iso.vertex(v)));
/// ``` /// ```
fn convert<G: GraphTopology>( ///
source: &G, /// The isomorphism can also convert [`ElementMap`] instances defined on `source` so that they
) -> ( /// can be indexed by vertex or edge handles of the new graph.
Self, ///
ElementMap<G::Vertex, Self::Vertex>, /// ```
ElementMap<G::Edge, Self::Edge>, /// # use grapherity::prelude::*;
) /// # use grapherity::models::{AppendGraph, FrozenGraph};
///
/// # let mut source = AppendGraph::new();
/// # let u = source.add_vertex();
/// # let v = source.add_vertex();
/// # let (graph, isomorphism) = FrozenGraph::convert(&source);
///
/// let mut source_map = source.vertex_map(0);
/// source_map[u] = 7;
/// let map = isomorphism.convert_vertex_map(&source_map);
///
/// assert_eq!(map[isomorphism.vertex(u)], 7);
/// ```
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>)
where where
Self: Sized; Self: Sized;
} }
/// An explicit isomorphism between a source graph of type `G` and a graph of type `H` converted
/// from it.
///
/// Returned by [`GraphTopologyConversion::convert`]. It maps each vertex and edge handle of the
/// source graph to the corresponding handle of the converted graph, and can transfer
/// [`ElementMap`] instances defined on the source over to the converted graph.
///
/// The isomorphism describes the source as it was when it was converted. Handle lookups stay
/// correct for handles that were valid then. If the source graph type reuses storage after
/// deletions (see [`GraphTopologyDeletion`]), do not delete vertices or edges from the source
/// before converting maps, since a new element could take the place of a deleted one and be paired
/// with it.
pub struct GraphIsomorphism<G: GraphTopology, H: GraphTopology> {
vertices: ElementMap<G::Vertex, H::Vertex>,
edges: ElementMap<G::Edge, H::Edge>,
vertex_map_template: ElementMap<H::Vertex, ()>,
edge_map_template: ElementMap<H::Edge, ()>,
}
impl<G: GraphTopology, H: GraphTopology> GraphIsomorphism<G, H> {
/// Creates an isomorphism from the given handle mappings, for use by implementations of
/// [`GraphTopologyConversion::convert`].
///
/// `vertices` and `edges` must map every vertex and edge of the source to the corresponding
/// vertex and edge of `target`, respectively. Their default values mark unmapped slots, so they
/// must be handles that are never valid in `target`, and must not have been assigned to any
/// mapped slot. Passing mappings that are not an isomorphism results in unspecified, but
/// memory-safe, behaviour.
pub fn new(
target: &H,
vertices: ElementMap<G::Vertex, H::Vertex>,
edges: ElementMap<G::Edge, H::Edge>,
) -> Self {
Self {
vertices,
edges,
vertex_map_template: target.vertex_map(()),
edge_map_template: target.edge_map(()),
}
}
/// Returns the vertex of the converted graph that corresponds to the source vertex `v`.
///
/// `v` must have been a vertex of the source graph when it was converted. Otherwise, the
/// returned handle may be invalid, or this method may panic.
pub fn vertex(&self, v: G::Vertex) -> H::Vertex {
self.vertices[v]
}
/// Returns the edge of the converted graph that corresponds to the source edge `e`.
///
/// `e` must have been an edge of the source graph when it was converted. Otherwise, the
/// returned handle may be invalid, or this method may panic.
pub fn edge(&self, e: G::Edge) -> H::Edge {
self.edges[e]
}
/// Converts a vertex map of the source into an equivalent vertex map of the converted graph.
///
/// For each source vertex *v*, the returned map holds a clone of `map[v]` at `vertex(v)`. It
/// has the same default value as `map`, and it covers the vertices of the converted graph at
/// the time of conversion. Use [`GraphTopologyAddition::sync_vertex_map`] before accessing
/// vertices added to the converted graph afterwards. Missing slots of `map`, for instance
/// because `map` was created before the source grew, are treated as holding the default value.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::{AppendGraph, FrozenGraph};
/// let mut source = AppendGraph::new();
/// let u = source.add_vertex();
/// let v = source.add_vertex();
/// let (graph, iso) = FrozenGraph::convert(&source);
///
/// let mut labels = source.vertex_map("none");
/// labels[u] = "start";
/// let converted = iso.convert_vertex_map(&labels);
/// assert_eq!(converted[iso.vertex(u)], "start");
/// assert_eq!(converted[iso.vertex(v)], "none");
/// ```
pub fn convert_vertex_map<T: Clone>(
&self,
map: &ElementMap<G::Vertex, T>,
) -> ElementMap<H::Vertex, T> {
map.remap(&self.vertices, &self.vertex_map_template)
}
/// Converts an edge map of the source into an equivalent edge map of the converted graph.
///
/// For each source edge *e*, the returned map holds a clone of `map[e]` at `edge(e)`. It has
/// the same default value as `map`, and it covers the edges of the converted graph at the time
/// of conversion. Use [`GraphTopologyAddition::sync_edge_map`] before accessing edges added to
/// the converted graph afterwards. Missing slots of `map`, for instance because `map` was
/// created before the source grew, are treated as holding the default value.
///
/// # Examples
///
/// ```
/// # use grapherity::prelude::*;
/// # use grapherity::models::{AppendGraph, FrozenGraph};
/// let mut source = AppendGraph::new();
/// let u = source.add_vertex();
/// let v = source.add_vertex();
/// let e = source.add_edge(u, v);
/// let f = source.add_edge(u, v);
/// let (graph, iso) = FrozenGraph::convert(&source);
///
/// let mut weights = source.edge_map(1);
/// weights[e] = 5;
/// let converted = iso.convert_edge_map(&weights);
/// assert_eq!(converted[iso.edge(e)], 5);
/// assert_eq!(converted[iso.edge(f)], 1);
/// ```
pub fn convert_edge_map<T: Clone>(
&self,
map: &ElementMap<G::Edge, T>,
) -> ElementMap<H::Edge, T> {
map.remap(&self.edges, &self.edge_map_template)
}
}