Release 0.3.0 #8
+2
-2
@@ -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
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user