Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cd965db4f | ||
|
|
6f557a92aa | ||
|
|
536a39c82b | ||
|
|
6403862977 | ||
|
|
3ae0aa9b67 | ||
|
|
5b6ad13f0e | ||
|
|
f41b77903f | ||
|
|
3fb3e67e8e | ||
|
|
8c57f5d2c4 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grapherity"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Stefan Müller"]
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
+153
-21
@@ -1,6 +1,48 @@
|
||||
//! Generators for common graphs.
|
||||
|
||||
use crate::traits::{GraphTopology, GraphTopologyAddition};
|
||||
use crate::models::AppendGraph;
|
||||
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion};
|
||||
|
||||
/// Builds a graph of type `G` by running a generator on a temporary [`AppendGraph`].
|
||||
///
|
||||
/// `generate_as` fills a temporary graph and converts it to `G`. It returns the handles of the
|
||||
/// elements it created as handles of the resulting graph. This makes it possible to obtain graph
|
||||
/// types directly that do not implement [`GraphTopologyAddition`], such as [`FrozenGraph`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use grapherity::generators;
|
||||
/// use grapherity::models::FrozenGraph;
|
||||
/// use grapherity::traits::GraphTopology;
|
||||
///
|
||||
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph, _, _>(generators::petersen);
|
||||
/// assert_eq!(graph.vertex_count(), 10);
|
||||
/// assert_eq!(graph.edge_count(), 15);
|
||||
/// assert!(graph.vertices().all(|v| graph.degree(v) == 3));
|
||||
/// ```
|
||||
///
|
||||
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
||||
pub fn generate_as<G, const V: usize, const E: usize>(
|
||||
generator: impl FnOnce(
|
||||
&mut AppendGraph,
|
||||
) -> (
|
||||
[<AppendGraph as GraphTopology>::Vertex; V],
|
||||
[<AppendGraph as GraphTopology>::Edge; E],
|
||||
),
|
||||
) -> (G, [G::Vertex; V], [G::Edge; E])
|
||||
where
|
||||
G: GraphTopologyConversion,
|
||||
{
|
||||
let mut builder = AppendGraph::new();
|
||||
let (vertices, edges) = generator(&mut builder);
|
||||
let (graph, iso) = G::convert(&builder);
|
||||
(
|
||||
graph,
|
||||
vertices.map(|v| iso.vertex(v)),
|
||||
edges.map(|e| iso.edge(e)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a Petersen graph.
|
||||
///
|
||||
@@ -42,38 +84,128 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::*;
|
||||
use crate::models::AppendGraph;
|
||||
use crate::models::{FrozenGraph, Graph};
|
||||
|
||||
#[test]
|
||||
fn petersen() {
|
||||
let mut graph = AppendGraph::new();
|
||||
let (vertices, edges) = super::petersen(&mut graph);
|
||||
assert_eq!(
|
||||
graph.vertex_count(),
|
||||
10,
|
||||
"unexpected number of added vertices"
|
||||
);
|
||||
assert_eq!(graph.edge_count(), 15, "unexpected number of added edges");
|
||||
fn petersen_append_graph() {
|
||||
petersen_add::<AppendGraph>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn petersen_graph() {
|
||||
petersen_add::<Graph>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn petersen_frozen_graph() {
|
||||
petersen_generate_as::<FrozenGraph>();
|
||||
}
|
||||
|
||||
fn petersen_generate_as<G: GraphTopologyConversion>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, edges) = generate_as::<G, _, _>(petersen);
|
||||
assert_petersen(&graph, &vertices, &edges);
|
||||
}
|
||||
|
||||
fn petersen_add<G: GraphTopologyAddition>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let mut graph = G::default();
|
||||
let (vertices, edges) = petersen(&mut graph);
|
||||
assert_petersen(&graph, &vertices, &edges);
|
||||
}
|
||||
|
||||
fn assert_petersen<G: GraphTopology>(graph: &G, vertices: &[G::Vertex], edges: &[G::Edge])
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
assert_element_counts(graph, 10, 15);
|
||||
assert_handles(graph.vertices(), vertices, "vertex");
|
||||
assert_handles(graph.edges(), edges, "edge");
|
||||
assert_no_loops_no_multi_edges(graph);
|
||||
assert!(
|
||||
graph.vertices().all(|v| graph.degree(v) == 3),
|
||||
"all vertices should have degree 3"
|
||||
);
|
||||
assert_handles_in_graph(&graph, &vertices, &edges);
|
||||
// Checks that each pair of vertices (u, v) has no common neighbor if they are adjacent, and
|
||||
// exactly one common neighbor otherwise.
|
||||
for (i, &v) in vertices.iter().enumerate() {
|
||||
for &u in &vertices[i + 1..] {
|
||||
if graph.are_adjacent(v, u) {
|
||||
assert!(
|
||||
graph.common_neighbors(u, v).next().is_none(),
|
||||
"adjacent vertices {u:?} and {v:?} should not have a common neighbor"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
graph.common_neighbors(u, v).count(),
|
||||
1,
|
||||
"non-adjacent vertices {u:?} and {v:?} should have exactly one common neighbor"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_handles_in_graph<G: GraphTopology>(
|
||||
fn assert_element_counts<G: GraphTopology>(
|
||||
graph: &G,
|
||||
vertices: &[G::Vertex],
|
||||
edges: &[G::Edge],
|
||||
expected_vertex_count: usize,
|
||||
expected_edge_count: usize,
|
||||
) {
|
||||
assert!(
|
||||
vertices.iter().all(|&v| graph.vertices().any(|u| u == v)),
|
||||
"all returned vertices should be in the graph"
|
||||
assert_eq!(
|
||||
graph.vertex_count(),
|
||||
expected_vertex_count,
|
||||
"unexpected number of vertices"
|
||||
);
|
||||
assert!(
|
||||
edges.iter().all(|&e| graph.edges().any(|f| f == e)),
|
||||
"all returned edges should be in the graph"
|
||||
assert_eq!(
|
||||
graph.edge_count(),
|
||||
expected_edge_count,
|
||||
"unexpected number of edges"
|
||||
);
|
||||
}
|
||||
|
||||
/// Asserts that every handle in `returned` is unique and in `all`.
|
||||
fn assert_handles<T: PartialEq + Debug>(
|
||||
all: impl Iterator<Item = T>,
|
||||
returned: &[T],
|
||||
kind: &str,
|
||||
) {
|
||||
let mut remaining: Vec<T> = all.collect();
|
||||
for e in returned {
|
||||
let Some(position) = remaining.iter().position(|x| x == e) else {
|
||||
panic!("returned {kind} {e:?} is not in the graph or appears more than once");
|
||||
};
|
||||
remaining.swap_remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_no_loops_no_multi_edges<G: GraphTopology>(graph: &G)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
for e in graph.edges() {
|
||||
let (u, v) = graph.incident_vertices(e);
|
||||
assert_ne!(u, v, "unexpected loop edge {e:?}");
|
||||
}
|
||||
for v in graph.vertices() {
|
||||
let mut neighbors = Vec::new();
|
||||
for u in graph.adjacent_vertices(v) {
|
||||
assert!(
|
||||
!neighbors.contains(&u),
|
||||
"unexpected multi-edge between {v:?} and {u:?}"
|
||||
);
|
||||
neighbors.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -141,9 +141,10 @@ impl<E: Copy, T: Clone> IndexMut<E> for ElementMap<E, T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn initial_values_are_default() {
|
||||
let map = ElementMap::new(42, |e| e, 6);
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::models::{AppendGraph, FrozenGraph};
|
||||
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion, Incidence};
|
||||
|
||||
pub trait MakeTestGraph: GraphTopology + Sized {
|
||||
/// Returns a versatile, pre-defined standard test graph.
|
||||
// Since this is only a test fixture, the complex return type here is deemed acceptable.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn standard() -> (
|
||||
@@ -14,20 +15,32 @@ pub trait MakeTestGraph: GraphTopology + Sized {
|
||||
[Vec<Incidence<Self::Vertex, Self::Edge>>; 10],
|
||||
);
|
||||
|
||||
/// Returns an empty graph.
|
||||
fn empty() -> Self;
|
||||
|
||||
/// Returns a graph with a single vertex and no edges.
|
||||
fn single_vertex() -> (Self, Self::Vertex);
|
||||
|
||||
/// Returns a graph with two vertices connected by a single edge.
|
||||
fn single_edge() -> (Self, [Self::Vertex; 2], Self::Edge);
|
||||
|
||||
/// Returns a graph with vertices *v0*, *v1*, and *v2*, where *v0* is isolated, and the other
|
||||
/// two are connected by a single edge.
|
||||
fn disconnected() -> (Self, [Self::Vertex; 3]);
|
||||
|
||||
/// Returns a graph with a single vertex that has a single loop edge.
|
||||
fn loop_edge() -> (Self, Self::Vertex, Self::Edge);
|
||||
|
||||
/// Returns a graph with two vertices that are connected by `K` multi-edges.
|
||||
fn multiple_edges<const K: usize>() -> (Self, [Self::Vertex; 2], [Self::Edge; K]);
|
||||
|
||||
/// Returns a graph that is a path with three vertices and two edges between them, returned in
|
||||
/// order.
|
||||
fn two_edge_path() -> (Self, [Self::Vertex; 3], [Self::Edge; 2]);
|
||||
|
||||
/// Returns a graph that is a path with three vertices *v0*, *v1*, and *v2*, and two edges
|
||||
/// *e0 = (v0, v1)* and *e1 = (v1, v2)* between them, and two loops on the first two vertices
|
||||
/// *e2 = (v0, v0)* and *e3 = (v1, v1)*.
|
||||
fn two_edge_path_with_loops() -> (Self, [Self::Vertex; 3], [Self::Edge; 4]);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ macro_rules! graph_topology_tests {
|
||||
incidence_cursor_copy,
|
||||
incident_vertices_incidences_consistency,
|
||||
incident_edges_incidences_consistency,
|
||||
common_neighbors,
|
||||
common_neighbors_same_vertex,
|
||||
common_neighbors_single,
|
||||
common_neighbors_disconnected,
|
||||
common_neighbors_multiple_edges,
|
||||
common_neighbors_loop_edge,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -771,3 +777,118 @@ where
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn common_neighbors<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, vertices, _, _) = G::standard();
|
||||
// Pairs of vertex indices with the indices of their expected common neighbors.
|
||||
let cases: [(usize, usize, &[usize]); _] = [
|
||||
(0, 3, &[1]),
|
||||
(1, 6, &[2, 3]),
|
||||
(5, 8, &[]),
|
||||
(2, 9, &[5, 6]),
|
||||
(7, 3, &[]),
|
||||
(0, 1, &[]),
|
||||
(6, 7, &[9]),
|
||||
// Loop only on one side, adjacent.
|
||||
(2, 6, &[2]),
|
||||
// Loop only on one side, not adjacent.
|
||||
(2, 7, &[4]),
|
||||
// Adjacent through a double edge, and both have a loop.
|
||||
(2, 4, &[1, 2, 4]),
|
||||
];
|
||||
for (i, j, expected) in cases {
|
||||
let expected: Vec<_> = expected.iter().map(|&k| vertices[k]).collect();
|
||||
assert_common_neighbors(&graph, vertices[i], vertices[j], &expected);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn common_neighbors_same_vertex<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, vertices, _, _) = G::standard();
|
||||
// Vertex 1 has a double edge to vertex 0, but no loop.
|
||||
assert_common_neighbors(
|
||||
&graph,
|
||||
vertices[1],
|
||||
vertices[1],
|
||||
&[vertices[0], vertices[2], vertices[3], vertices[4]],
|
||||
);
|
||||
// Vertex 4 has a loop, and is therefore its own common neighbor with itself.
|
||||
assert_common_neighbors(
|
||||
&graph,
|
||||
vertices[4],
|
||||
vertices[4],
|
||||
&[
|
||||
vertices[1],
|
||||
vertices[2],
|
||||
vertices[4],
|
||||
vertices[7],
|
||||
vertices[8],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn common_neighbors_single<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, v) = G::single_vertex();
|
||||
assert_common_neighbors(&graph, v, v, &[]);
|
||||
}
|
||||
|
||||
pub fn common_neighbors_disconnected<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, v) = G::disconnected();
|
||||
assert_common_neighbors(&graph, v[0], v[0], &[]);
|
||||
assert_common_neighbors(&graph, v[0], v[1], &[]);
|
||||
}
|
||||
|
||||
pub fn common_neighbors_multiple_edges<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, [v0, v1], _) = G::multiple_edges::<3>();
|
||||
assert_common_neighbors(&graph, v0, v1, &[]);
|
||||
assert_common_neighbors(&graph, v1, v1, &[v0]);
|
||||
}
|
||||
|
||||
pub fn common_neighbors_loop_edge<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
let (graph, v, _) = G::loop_edge();
|
||||
assert_common_neighbors(&graph, v, v, &[v]);
|
||||
}
|
||||
|
||||
/// Asserts that `u` and `v` have exactly the `expected` common neighbors, each yielded once, for
|
||||
/// both argument orders.
|
||||
fn assert_common_neighbors<G: MakeTestGraph>(
|
||||
graph: &G,
|
||||
u: G::Vertex,
|
||||
v: G::Vertex,
|
||||
expected: &[G::Vertex],
|
||||
) where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
for (a, b) in [(u, v), (v, u)] {
|
||||
let mut remaining = expected.to_vec();
|
||||
for w in graph.common_neighbors(a, b) {
|
||||
let pos = remaining.iter().position(|&x| x == w).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"unexpected or repeated common neighbor {w:?} of vertices {a:?} and {b:?} from iterator"
|
||||
)
|
||||
});
|
||||
remaining.swap_remove(pos);
|
||||
}
|
||||
assert!(
|
||||
remaining.is_empty(),
|
||||
"expected common neighbors {remaining:?} of vertices {a:?} and {b:?} were not matched by iterator"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,58 @@ pub trait GraphTopology {
|
||||
///
|
||||
/// Panics if `v` is not a valid vertex of this graph.
|
||||
fn incidence_cursor(&self, v: Self::Vertex) -> Self::IncidenceCursor;
|
||||
|
||||
/// Returns an iterator over all vertices that are adjacent to both `u` and `v`.
|
||||
///
|
||||
/// Every common neighbor is yielded exactly once, in unspecified order, regardless of how
|
||||
/// many parallel edges connect it to `u` or `v`. This method is symmetric up to permutations,
|
||||
/// i.e. swapping the arguments will yield the same vertices, but not necessarily in the same
|
||||
/// order.
|
||||
///
|
||||
/// If `u` has a loop and is adjacent to `v`, then `u` is a common neighbor, and will be yielded
|
||||
/// exactly once, and analogously for a loop at `v`.
|
||||
///
|
||||
/// If `u == v`, then the common neighbors are all neighbors of `u`, including `u` itself if and
|
||||
/// only if it has a loop.
|
||||
///
|
||||
/// All neighbors of `u` are collected when this method is called, while those of `v` are
|
||||
/// traversed lazily.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `u` or `v` is not a valid vertex of this graph.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use grapherity::models::AppendGraph;
|
||||
/// use grapherity::prelude::*;
|
||||
///
|
||||
/// let mut graph = AppendGraph::new();
|
||||
/// let [u, w, v] = core::array::from_fn(|_| graph.add_vertex());
|
||||
/// // Parallel edges do not yield `w` more than once.
|
||||
/// graph.add_edge(u, w);
|
||||
/// graph.add_edge(u, w);
|
||||
/// graph.add_edge(w, v);
|
||||
/// assert!(graph.common_neighbors(u, v).eq([w]));
|
||||
/// ```
|
||||
// TODO: FrozenGraph could provide a sorted-merge override, if its vertices were sorted.
|
||||
fn common_neighbors(
|
||||
&self,
|
||||
u: Self::Vertex,
|
||||
v: Self::Vertex,
|
||||
) -> impl Iterator<Item = Self::Vertex> {
|
||||
// TODO: Instead of Vec, this could use a vertex ElementMap with marked neighbors if the product of the degrees of u and v is large compared to |V|.
|
||||
// TODO: Instead of Vec, this could use a reusable, user-provided(?) "scratch-pad" vertex ElementMap, to not pay its allocation.
|
||||
let mut u_neighbors: Vec<_> = self.adjacent_vertices(u).collect();
|
||||
let mut v_neighbors = self.adjacent_vertices(v);
|
||||
std::iter::from_fn(move || {
|
||||
let w = v_neighbors.find(|w| u_neighbors.contains(w))?;
|
||||
// Removes all parallel entries, so that `w` is not yielded again.
|
||||
u_neighbors.retain(|x| *x != w);
|
||||
Some(w)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that adds construction operations to an undirected graph topology.
|
||||
|
||||
Reference in New Issue
Block a user