Change GraphTopologyConversion::convert return value to include the new GraphIsomorphism

This allows to convert maps for the converted graph.
This commit is contained in:
2026-10-12 21:33:49 +02:00
parent 1979d33649
commit b124e58b38
9 changed files with 228 additions and 79 deletions
+2 -2
View File
@@ -146,8 +146,8 @@ pub mod traits;
/// Convenience re-exports of graph topology traits for common use.
pub mod prelude {
pub use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, GraphTopologyDeletion,
Incidence, IncidenceCursor,
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
GraphTopologyDeletion, Incidence, IncidenceCursor,
};
}
+29
View File
@@ -94,6 +94,35 @@ impl<E: Copy, T: Clone> ElementMap<E, T> {
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> {
+5 -9
View File
@@ -5,7 +5,8 @@ use std::num::NonZeroUsize;
use crate::maps::ElementMap;
use crate::models;
use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence, IncidenceCursor,
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence,
IncidenceCursor,
};
/// An opaque handle identifying a vertex in an [`AppendGraph`].
@@ -261,17 +262,12 @@ impl GraphTopologyAddition for AppendGraph {
}
impl GraphTopologyConversion for AppendGraph {
fn convert<H: GraphTopology>(
source: &H,
) -> (
Self,
ElementMap<H::Vertex, Vertex>,
ElementMap<H::Edge, Edge>,
) {
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
let mut edge_map = source.edge_map(Edge(NonZeroUsize::MAX));
let (result, vertex_map) =
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)
}
}
+10 -16
View File
@@ -3,7 +3,9 @@
use std::ops::IndexMut;
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`].
///
@@ -77,10 +79,10 @@ impl IncidenceCursor<FrozenGraph> for FrozenGraphIncidenceCursor {
/// let u = graph.add_vertex();
/// let v = graph.add_vertex();
/// 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.
/// assert!(graph.are_adjacent(vertex_map[u], vertex_map[v]));
/// assert!(graph.are_adjacent(isomorphism.vertex(u), isomorphism.vertex(v)));
/// ```
///
/// # Time and space complexity
@@ -193,13 +195,7 @@ impl GraphTopology for FrozenGraph {
}
impl GraphTopologyConversion for FrozenGraph {
fn convert<G: GraphTopology>(
source: &G,
) -> (
Self,
ElementMap<G::Vertex, Vertex>,
ElementMap<G::Edge, Edge>,
) {
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
// 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);
@@ -237,14 +233,12 @@ impl GraphTopologyConversion for FrozenGraph {
}
}
(
Self {
let result = Self {
vertices,
incidences,
},
vertex_map,
edge_map,
)
};
let iso = GraphIsomorphism::new(&result, vertex_map, edge_map);
(result, iso)
}
}
+5 -10
View File
@@ -6,8 +6,8 @@ use typed_generational_arena::{Arena, Index};
use crate::maps::ElementMap;
use crate::models;
use crate::traits::{
GraphTopology, GraphTopologyAddition, GraphTopologyConversion, GraphTopologyDeletion,
Incidence, IncidenceCursor,
GraphIsomorphism, GraphTopology, GraphTopologyAddition, GraphTopologyConversion,
GraphTopologyDeletion, Incidence, IncidenceCursor,
};
/// An opaque handle identifying a vertex in a [`Graph`].
@@ -399,19 +399,14 @@ impl GraphTopologyDeletion for Graph {
}
impl GraphTopologyConversion for Graph {
fn convert<H: GraphTopology>(
source: &H,
) -> (
Self,
ElementMap<H::Vertex, Vertex>,
ElementMap<H::Edge, Edge>,
) {
fn convert<G: GraphTopology>(source: &G) -> (Self, GraphIsomorphism<G, Self>) {
let mut edge_map = source.edge_map(Edge::new(usize::MAX, usize::MAX));
let (result, vertex_map) =
models::convert_to_addition(source, Vertex::new(usize::MAX, 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)
}
}
+22 -22
View File
@@ -185,16 +185,16 @@ impl MakeTestGraph for FrozenGraph {
[Vec<Incidence<Self::Vertex, Self::Edge>>; 10],
) {
let (build, vertices, edges, incidences) = AppendGraph::standard();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build);
let (graph, iso) = FrozenGraph::convert(&build);
(
graph,
vertices.map(|v| vertex_map[v]),
edges.map(|(e, u, v)| (edge_map[e], vertex_map[u], vertex_map[v])),
vertices.map(|v| iso.vertex(v)),
edges.map(|(e, u, v)| (iso.edge(e), iso.vertex(u), iso.vertex(v))),
incidences.map(|vec| {
vec.iter()
.map(|i| Incidence {
vertex: vertex_map[i.vertex],
edge: edge_map[i.edge],
vertex: iso.vertex(i.vertex),
edge: iso.edge(i.edge),
})
.collect()
}),
@@ -207,55 +207,55 @@ impl MakeTestGraph for FrozenGraph {
fn single_vertex() -> (Self, Self::Vertex) {
let (build, v) = AppendGraph::single_vertex();
let (graph, vertex_map, _) = FrozenGraph::convert(&build);
(graph, vertex_map[v])
let (graph, iso) = FrozenGraph::convert(&build);
(graph, iso.vertex(v))
}
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge) {
let (build, vertices, e) = AppendGraph::single_edge();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| vertex_map[v]), edge_map[e])
let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| iso.vertex(v)), iso.edge(e))
}
fn disconnected() -> (Self, [Self::Vertex; 3]) {
let (build, vertices) = AppendGraph::disconnected();
let (graph, vertex_map, _) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| vertex_map[v]))
let (graph, iso) = FrozenGraph::convert(&build);
(graph, vertices.map(|v| iso.vertex(v)))
}
fn loop_edge() -> (Self, Self::Vertex, Self::Edge) {
let (build, v, e) = AppendGraph::loop_edge();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build);
(graph, vertex_map[v], edge_map[e])
let (graph, iso) = FrozenGraph::convert(&build);
(graph, iso.vertex(v), iso.edge(e))
}
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]) {
let (build, vertices, edges) = AppendGraph::multiple_edges();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build);
let (graph, iso) = FrozenGraph::convert(&build);
(
graph,
vertices.map(|v| vertex_map[v]),
edges.map(|e| edge_map[e]),
vertices.map(|v| iso.vertex(v)),
edges.map(|e| iso.edge(e)),
)
}
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]) {
let (build, vertices, edges) = AppendGraph::two_edge_path();
let (graph, vertex_map, edge_map) = FrozenGraph::convert(&build);
let (graph, iso) = FrozenGraph::convert(&build);
(
graph,
vertices.map(|v| vertex_map[v]),
edges.map(|e| edge_map[e]),
vertices.map(|v| iso.vertex(v)),
edges.map(|e| iso.edge(e)),
)
}
fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]) {
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,
vertices.map(|v| vertex_map[v]),
edges.map(|e| edge_map[e]),
vertices.map(|v| iso.vertex(v)),
edges.map(|e| iso.edge(e)),
)
}
}
@@ -27,6 +27,6 @@ where
H: MakeTestGraph + GraphTopologyAddition,
{
let (mut source, vertices, _) = H::single_edge();
let (mut graph, vertex_map, _) = G::convert(&source);
fixtures::assert_mutation_independent(&mut source, &mut graph, &vertices, |v| vertex_map[v]);
let (mut graph, iso) = G::convert(&source);
fixtures::assert_mutation_independent(&mut source, &mut graph, &vertices, |v| iso.vertex(v));
}
@@ -31,13 +31,13 @@ where
H::Edge: Debug,
{
let (source, vertices, edges, _) = H::standard();
let (graph, vertex_map, edge_map) = G::convert(&source);
let (graph, iso) = G::convert(&source);
fixtures::assert_isomorphic(
&source,
&graph,
&vertices,
|v| vertex_map[v],
|v| iso.vertex(v),
&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,
}
// 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
/// implementation to this one.
pub trait GraphTopologyConversion: GraphTopology {
/// Creates a new graph instance that is isomorphic to `source`.
///
/// Returns the converted graph instance and two mappings from the vertices and edges of
/// `source` to the corresponding vertices and edges of the returned graph.
/// Returns the converted graph instance and a [`GraphIsomorphism`] instance that maps the
/// vertices and edges of `source` to the corresponding vertices and edges of the returned
/// graph.
///
/// # Examples
///
@@ -380,18 +380,153 @@ pub trait GraphTopologyConversion: GraphTopology {
/// let mut source = AppendGraph::new();
/// let u = source.add_vertex();
/// let v = source.add_vertex();
/// let e = source.add_edge(u, v);
/// let (graph, vertex_map, edge_map) = FrozenGraph::convert(&source);
/// source.add_edge(u, v);
/// 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,
) -> (
Self,
ElementMap<G::Vertex, Self::Vertex>,
ElementMap<G::Edge, Self::Edge>,
)
///
/// 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.
///
/// ```
/// # 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
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)
}
}