Compare commits
12
Commits
main
...
generators
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
783a82fc18 | ||
|
|
be7acfce1f | ||
|
|
f5bb3ad07a | ||
|
|
baa6775156 | ||
|
|
32fae0b24c | ||
|
|
026005fe46 | ||
|
|
421207a1cd | ||
|
|
9b11090e82 | ||
|
|
d1463544f7 | ||
|
|
ba3717f246 | ||
|
|
f34a7e6541 | ||
|
|
5fc586f58e |
+447
-68
@@ -3,34 +3,39 @@
|
||||
use crate::models::AppendGraph;
|
||||
use crate::traits::{GraphTopology, GraphTopologyAddition, GraphTopologyConversion};
|
||||
|
||||
/// Builds a graph of type `G` by running a generator on a temporary [`AppendGraph`].
|
||||
/// Builds a graph of type `G` by running a generator on a temporary graph.
|
||||
///
|
||||
/// `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`].
|
||||
/// `generate_as` fills a temporary [`AppendGraph`] 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::prelude::*;
|
||||
/// use grapherity::generators;
|
||||
/// use grapherity::models::FrozenGraph;
|
||||
/// use grapherity::traits::GraphTopology;
|
||||
///
|
||||
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph, _, _>(generators::petersen);
|
||||
/// // Generates a parameterless Petersen graph as a FrozenGraph model.
|
||||
/// 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));
|
||||
///
|
||||
/// // Generates a parameterized complete graph as a FrozenGraph model.
|
||||
/// let (graph, vertices, edges) = generators::generate_as::<FrozenGraph>(|g| generators::complete(g, 5));
|
||||
/// assert_eq!(graph.vertex_count(), 5);
|
||||
/// assert!(graph.vertices().all(|v| graph.degree(v) == 4));
|
||||
/// ```
|
||||
///
|
||||
/// [`FrozenGraph`]: crate::models::FrozenGraph
|
||||
pub fn generate_as<G, const V: usize, const E: usize>(
|
||||
pub fn generate_as<G>(
|
||||
generator: impl FnOnce(
|
||||
&mut AppendGraph,
|
||||
) -> (
|
||||
[<AppendGraph as GraphTopology>::Vertex; V],
|
||||
[<AppendGraph as GraphTopology>::Edge; E],
|
||||
Vec<<AppendGraph as GraphTopology>::Vertex>,
|
||||
Vec<<AppendGraph as GraphTopology>::Edge>,
|
||||
),
|
||||
) -> (G, [G::Vertex; V], [G::Edge; E])
|
||||
) -> (G, Vec<G::Vertex>, Vec<G::Edge>)
|
||||
where
|
||||
G: GraphTopologyConversion,
|
||||
{
|
||||
@@ -39,33 +44,92 @@ where
|
||||
let (graph, iso) = G::convert(&builder);
|
||||
(
|
||||
graph,
|
||||
vertices.map(|v| iso.vertex(v)),
|
||||
edges.map(|e| iso.edge(e)),
|
||||
vertices.into_iter().map(|v| iso.vertex(v)).collect(),
|
||||
edges.into_iter().map(|e| iso.edge(e)).collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a path graph.
|
||||
///
|
||||
/// Generates a [path graph] *P(n)* with `n` vertices and `n - 1` edges in `graph` as a new
|
||||
/// connected component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges
|
||||
/// *e0, e1, ..., e(n - 2)* in order, i.e. *e0 = (v0, v1)*, *e1 = (v1, v2)*, etc. and *v0* and
|
||||
/// *v(n-1)* being the two terminal vertices
|
||||
///
|
||||
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex and no edges are added.
|
||||
///
|
||||
/// [Path graph]: https://en.wikipedia.org/wiki/Path_graph
|
||||
pub fn path<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||
let edge_capacity = n.saturating_sub(1);
|
||||
graph.reserve_vertices(n);
|
||||
graph.reserve_edges(edge_capacity);
|
||||
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||
let edges = (0..edge_capacity)
|
||||
.map(|i| graph.add_edge(vertices[i], vertices[i + 1]))
|
||||
.collect();
|
||||
(vertices, edges)
|
||||
}
|
||||
|
||||
/// Generates a cycle graph.
|
||||
///
|
||||
/// Generates a [cycle graph] *C(n)* with `n` vertices and `n` edges in `graph` as a new connected
|
||||
/// component and returns the new vertices *v0, v1, ..., v(n - 1)* and edges *e0, e1, ..., e(n - 1)*
|
||||
/// in order, i.e. *e0 = (v0, v1), e1 = (v1, v2), ..., e(n - 1) = (v(n - 1), v0)*.
|
||||
///
|
||||
/// For `n == 0`, nothing is added. For `n == 1`, a single vertex with a loop is added. For
|
||||
/// `n == 2`, two vertices and two edges between them (multi-edge) are added.
|
||||
///
|
||||
/// [Cycle graph]: https://en.wikipedia.org/wiki/Cycle_graph
|
||||
pub fn cycle<G: GraphTopologyAddition>(graph: &mut G, n: usize) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||
graph.reserve_vertices(n);
|
||||
graph.reserve_edges(n);
|
||||
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||
let edges = (0..n)
|
||||
.map(|i| graph.add_edge(vertices[i], vertices[if i < n - 1 { i + 1 } else { 0 }]))
|
||||
.collect();
|
||||
(vertices, edges)
|
||||
}
|
||||
|
||||
/// Generates a complete graph.
|
||||
///
|
||||
/// Generates a [complete graph] *K(n)* with `n` vertices and `n * (n - 1) / 2` edges in `graph` as
|
||||
/// a new connected component and returns the new vertices and edges.
|
||||
///
|
||||
/// For `n == 0`, nothing is added. For `n == 1`, only a single vertex is added.
|
||||
///
|
||||
/// [Complete graph]: https://en.wikipedia.org/wiki/Complete_graph
|
||||
pub fn complete<G: GraphTopologyAddition>(
|
||||
graph: &mut G,
|
||||
n: usize,
|
||||
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||
let edge_capacity = n * n.saturating_sub(1) / 2;
|
||||
graph.reserve_vertices(n);
|
||||
graph.reserve_edges(edge_capacity);
|
||||
let vertices: Vec<_> = (0..n).map(|_| graph.add_vertex()).collect();
|
||||
let mut edges = Vec::with_capacity(edge_capacity);
|
||||
for (i, &u) in vertices.iter().enumerate() {
|
||||
for &v in &vertices[i + 1..] {
|
||||
edges.push(graph.add_edge(u, v));
|
||||
}
|
||||
}
|
||||
(vertices, edges)
|
||||
}
|
||||
|
||||
/// Generates a Petersen graph.
|
||||
///
|
||||
/// Generates a [Petersen graph] in `graph` as a new connected component and returns the new
|
||||
/// vertices and edges. This graph is also the Kneser graph *KG(5, 2)*.
|
||||
///
|
||||
/// [Petersen graph]: https://en.wikipedia.org/wiki/Petersen_graph
|
||||
pub fn petersen<G>(
|
||||
graph: &mut G,
|
||||
) -> (
|
||||
[<G as GraphTopology>::Vertex; 10],
|
||||
[<G as GraphTopology>::Edge; 15],
|
||||
)
|
||||
where
|
||||
G: GraphTopologyAddition,
|
||||
{
|
||||
pub fn petersen<G: GraphTopologyAddition>(graph: &mut G) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||
const N: usize = 5;
|
||||
const K: usize = 2;
|
||||
|
||||
graph.reserve_vertices(10);
|
||||
graph.reserve_edges(15);
|
||||
let vertices = core::array::from_fn(|_| graph.add_vertex());
|
||||
let edges = core::array::from_fn(|j| {
|
||||
let vertices: Vec<_> = (0..10).map(|_| graph.add_vertex()).collect();
|
||||
let edges = (0..15)
|
||||
.map(|j| {
|
||||
let i = j / 3;
|
||||
match j % 3 {
|
||||
0 => {
|
||||
@@ -78,7 +142,8 @@ where
|
||||
}
|
||||
_ => graph.add_edge(vertices[i], vertices[i + N]),
|
||||
}
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
(vertices, edges)
|
||||
}
|
||||
|
||||
@@ -87,67 +152,342 @@ mod tests {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::*;
|
||||
use crate::models::{FrozenGraph, Graph};
|
||||
use crate::testing::fixtures::MakeTestGraph;
|
||||
|
||||
#[test]
|
||||
fn petersen_append_graph() {
|
||||
petersen_add::<AppendGraph>();
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Spec {
|
||||
Path(usize),
|
||||
Cycle(usize),
|
||||
Complete(usize),
|
||||
Petersen,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn petersen_graph() {
|
||||
petersen_add::<Graph>();
|
||||
impl Spec {
|
||||
fn generate<G: GraphTopologyAddition>(
|
||||
&self,
|
||||
graph: &mut G,
|
||||
) -> (Vec<G::Vertex>, Vec<G::Edge>) {
|
||||
match *self {
|
||||
Spec::Path(n) => path(graph, n),
|
||||
Spec::Cycle(n) => cycle(graph, n),
|
||||
Spec::Complete(n) => complete(graph, n),
|
||||
Spec::Petersen => petersen(graph),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn petersen_frozen_graph() {
|
||||
petersen_generate_as::<FrozenGraph>();
|
||||
}
|
||||
|
||||
fn petersen_generate_as<G: GraphTopologyConversion>()
|
||||
fn assert<G: GraphTopology>(&self, built: &Built<G>)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, edges) = generate_as::<G, _, _>(petersen);
|
||||
assert_petersen(&graph, &vertices, &edges);
|
||||
match *self {
|
||||
Spec::Path(n) => assert_path(built, n),
|
||||
Spec::Cycle(n) => assert_cycle(built, n),
|
||||
Spec::Complete(n) => assert_complete(built, n),
|
||||
Spec::Petersen => assert_petersen(built),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn petersen_add<G: GraphTopologyAddition>()
|
||||
struct Built<G: GraphTopology> {
|
||||
graph: G,
|
||||
vertices: Vec<G::Vertex>,
|
||||
edges: Vec<G::Edge>,
|
||||
initial_vertex_count: usize,
|
||||
initial_edge_count: usize,
|
||||
}
|
||||
|
||||
/// Generates the test for the `append_graph` harness for the given `Spec` expression.
|
||||
macro_rules! min_harness_tests {
|
||||
($spec:expr) => {
|
||||
#[test]
|
||||
fn append_graph() {
|
||||
run($spec, add_to_empty::<$crate::models::AppendGraph>);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates one test per harness for the given `Spec` expression.
|
||||
macro_rules! all_harness_tests {
|
||||
($spec:expr) => {
|
||||
min_harness_tests!($spec);
|
||||
|
||||
#[test]
|
||||
fn append_graph_non_empty() {
|
||||
run($spec, add_to_standard::<$crate::models::AppendGraph>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph() {
|
||||
run($spec, add_to_empty::<$crate::models::Graph>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_non_empty() {
|
||||
run($spec, add_to_standard::<$crate::models::Graph>);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_graph() {
|
||||
run($spec, conv_generate_as::<$crate::models::FrozenGraph>);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates the module tree `<group>::<case>::<harness>` for each generator case.
|
||||
///
|
||||
/// The `all_harnesses` cases run on every harness, the `min_harnesses` cases only on a reduced
|
||||
/// set of harnesses.
|
||||
macro_rules! generator_tests {
|
||||
($($group:ident {
|
||||
all { $($all_case:ident => $all_generator:expr),+ $(,)? }
|
||||
min { $($min_case:ident => $min_generator:expr),* $(,)? }
|
||||
})+) => {
|
||||
$(
|
||||
mod $group {
|
||||
use super::*;
|
||||
|
||||
$(
|
||||
mod $all_case {
|
||||
use super::*;
|
||||
|
||||
all_harness_tests!($all_generator);
|
||||
}
|
||||
)+
|
||||
|
||||
$(
|
||||
mod $min_case {
|
||||
use super::*;
|
||||
|
||||
min_harness_tests!($min_generator);
|
||||
}
|
||||
)*
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
generator_tests! {
|
||||
path {
|
||||
all {
|
||||
small => Spec::Path(4),
|
||||
}
|
||||
min {
|
||||
empty => Spec::Path(0),
|
||||
single_vertex => Spec::Path(1),
|
||||
single_edge => Spec::Path(2),
|
||||
large => Spec::Path(21),
|
||||
}
|
||||
}
|
||||
cycle {
|
||||
all {
|
||||
small_even => Spec::Cycle(4),
|
||||
}
|
||||
min {
|
||||
empty => Spec::Cycle(0),
|
||||
loop_edge => Spec::Cycle(1),
|
||||
multi_edge => Spec::Cycle(2),
|
||||
large_odd => Spec::Cycle(21),
|
||||
}
|
||||
}
|
||||
complete {
|
||||
all {
|
||||
small_even => Spec::Complete(4),
|
||||
}
|
||||
min {
|
||||
empty => Spec::Complete(0),
|
||||
single_vertex => Spec::Complete(1),
|
||||
single_edge => Spec::Complete(2),
|
||||
large_odd => Spec::Complete(13),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod petersen {
|
||||
use super::*;
|
||||
|
||||
all_harness_tests!(Spec::Petersen);
|
||||
}
|
||||
|
||||
/// Builds a graph with `harness` and asserts that it matches what `spec` should produce.
|
||||
fn run<G: GraphTopology>(spec: Spec, harness: impl FnOnce(&Spec) -> Built<G>)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let built = harness(&spec);
|
||||
spec.assert(&built);
|
||||
}
|
||||
|
||||
/// Runs `spec` on an empty graph of type `G`.
|
||||
fn add_to_empty<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||
let mut graph = G::default();
|
||||
let (vertices, edges) = petersen(&mut graph);
|
||||
assert_petersen(&graph, &vertices, &edges);
|
||||
let (vertices, edges) = spec.generate(&mut graph);
|
||||
Built {
|
||||
graph,
|
||||
vertices,
|
||||
edges,
|
||||
initial_vertex_count: 0,
|
||||
initial_edge_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_petersen<G: GraphTopology>(graph: &G, vertices: &[G::Vertex], edges: &[G::Edge])
|
||||
/// Runs `spec` on a non-empty test graph of type `G`.
|
||||
fn add_to_standard<G: GraphTopologyAddition>(spec: &Spec) -> Built<G> {
|
||||
let (mut graph, initial_vertices, initial_edges, _) = G::standard();
|
||||
let (vertices, edges) = spec.generate(&mut graph);
|
||||
Built {
|
||||
graph,
|
||||
vertices,
|
||||
edges,
|
||||
initial_vertex_count: initial_vertices.len(),
|
||||
initial_edge_count: initial_edges.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `spec` within `generate_as` for type `G`.
|
||||
fn conv_generate_as<G: GraphTopologyConversion>(spec: &Spec) -> Built<G> {
|
||||
let (graph, vertices, edges) = generate_as::<G>(|graph| spec.generate(graph));
|
||||
Built {
|
||||
graph,
|
||||
vertices,
|
||||
edges,
|
||||
initial_vertex_count: 0,
|
||||
initial_edge_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_path<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||
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_element_counts(built, n, n.saturating_sub(1));
|
||||
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||
assert_no_loops(built);
|
||||
assert_no_multi_edges(built);
|
||||
match n {
|
||||
0 => {}
|
||||
1 => assert_eq!(
|
||||
built.graph.degree(built.vertices[0]),
|
||||
0,
|
||||
"path n == 1 should be an isolated vertex"
|
||||
),
|
||||
_ => {
|
||||
for (i, &v) in built.vertices.iter().enumerate() {
|
||||
assert_eq!(
|
||||
built.graph.degree(v),
|
||||
if i == 0 || i == n - 1 { 1 } else { 2 },
|
||||
"unexpected degree of vertex {v:?} in path n == {n}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_path_order(built);
|
||||
}
|
||||
|
||||
fn assert_cycle<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
assert_element_counts(built, n, n);
|
||||
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||
if n != 1 {
|
||||
assert_no_loops(built);
|
||||
}
|
||||
if n != 2 {
|
||||
assert_no_multi_edges(built);
|
||||
}
|
||||
assert!(
|
||||
graph.vertices().all(|v| graph.degree(v) == 3),
|
||||
"all vertices should have degree 3"
|
||||
built.vertices.iter().all(|&v| built.graph.degree(v) == 2),
|
||||
"all new vertices should have degree 2"
|
||||
);
|
||||
assert_path_order(built);
|
||||
// Asserts the closing edge e(n - 1) == (v(n - 1), v0).
|
||||
if n > 0 {
|
||||
// For n == 1, u == v and this validates the loop. For n == 2, the above asserted one of
|
||||
// the two multi-edges, and this asserts the other.
|
||||
let e = built.edges[n - 1];
|
||||
let u = built.vertices[0];
|
||||
let v = built.vertices[n - 1];
|
||||
let incident_vertices = built.graph.incident_vertices(e);
|
||||
assert!(
|
||||
incident_vertices == (u, v) || incident_vertices == (v, u),
|
||||
"edge {e:?} should have incident vertices {u:?} and {v:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_complete<G: GraphTopology>(built: &Built<G>, n: usize)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
assert_element_counts(built, n, n * n.saturating_sub(1) / 2);
|
||||
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||
// The predicate in `all` is only executed if `vertices` has any elements, i.e. for n > 0.
|
||||
assert!(
|
||||
built
|
||||
.vertices
|
||||
.iter()
|
||||
.all(|&v| built.graph.degree(v) == n - 1),
|
||||
"all new vertices should have degree {}",
|
||||
n - 1
|
||||
);
|
||||
// Asserts that all pairs of vertices have exactly one edge between them. This also includes
|
||||
// checks for loops and multi-edges. If there are no duplicates, then this implies that all
|
||||
// expected edges were also matched due to the validated counts from above.
|
||||
let mut seen = vec![false; n * n];
|
||||
let position = |v, e| {
|
||||
built
|
||||
.vertices
|
||||
.iter()
|
||||
.position(|&x| x == v)
|
||||
.unwrap_or_else(|| panic!("edge {e:?} has an invalid incident vertex {v:?}"))
|
||||
};
|
||||
for &e in &built.edges {
|
||||
let (u, v) = built.graph.incident_vertices(e);
|
||||
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||
let (i, j) = (position(u, e), position(v, e));
|
||||
let (i, j) = (i.min(j), i.max(j));
|
||||
assert!(
|
||||
!seen[i * n + j],
|
||||
"unexpected multi-edge {e:?} between vertices {u:?} and {v:?}"
|
||||
);
|
||||
seen[i * n + j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_petersen<G: GraphTopology>(built: &Built<G>)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
assert_element_counts(built, 10, 15);
|
||||
assert_handles(built.graph.vertices(), &built.vertices, "vertex");
|
||||
assert_handles(built.graph.edges(), &built.edges, "edge");
|
||||
assert_no_loops(built);
|
||||
assert_no_multi_edges(built);
|
||||
assert!(
|
||||
built.vertices.iter().all(|&v| built.graph.degree(v) == 3),
|
||||
"all new vertices should have degree 3"
|
||||
);
|
||||
// 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) {
|
||||
for (i, &v) in built.vertices.iter().enumerate() {
|
||||
for &u in &built.vertices[i + 1..] {
|
||||
if built.graph.are_adjacent(v, u) {
|
||||
assert!(
|
||||
graph.common_neighbors(u, v).next().is_none(),
|
||||
built.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(),
|
||||
built.graph.common_neighbors(u, v).count(),
|
||||
1,
|
||||
"non-adjacent vertices {u:?} and {v:?} should have exactly one common neighbor"
|
||||
);
|
||||
@@ -157,19 +497,29 @@ mod tests {
|
||||
}
|
||||
|
||||
fn assert_element_counts<G: GraphTopology>(
|
||||
graph: &G,
|
||||
built: &Built<G>,
|
||||
expected_vertex_count: usize,
|
||||
expected_edge_count: usize,
|
||||
) {
|
||||
assert_eq!(
|
||||
graph.vertex_count(),
|
||||
expected_vertex_count,
|
||||
"unexpected number of vertices"
|
||||
built.graph.vertex_count(),
|
||||
built.initial_vertex_count + expected_vertex_count,
|
||||
"unexpected number of added vertices"
|
||||
);
|
||||
assert_eq!(
|
||||
graph.edge_count(),
|
||||
built.vertices.len(),
|
||||
expected_vertex_count,
|
||||
"unexpected number of returned vertices"
|
||||
);
|
||||
assert_eq!(
|
||||
built.graph.edge_count(),
|
||||
built.initial_edge_count + expected_edge_count,
|
||||
"unexpected number of added edges"
|
||||
);
|
||||
assert_eq!(
|
||||
built.edges.len(),
|
||||
expected_edge_count,
|
||||
"unexpected number of edges"
|
||||
"unexpected number of returned edges"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,24 +538,53 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_no_loops_no_multi_edges<G: GraphTopology>(graph: &G)
|
||||
fn assert_no_loops<G: GraphTopology>(built: &Built<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 &e in &built.edges {
|
||||
let (u, v) = built.graph.incident_vertices(e);
|
||||
assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}");
|
||||
}
|
||||
for v in graph.vertices() {
|
||||
}
|
||||
|
||||
fn assert_no_multi_edges<G: GraphTopology>(built: &Built<G>)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
for &v in &built.vertices {
|
||||
let mut neighbors = Vec::new();
|
||||
for u in graph.adjacent_vertices(v) {
|
||||
let mut loop_count = 0;
|
||||
for u in built.graph.adjacent_vertices(v) {
|
||||
if u == v {
|
||||
loop_count += 1;
|
||||
assert!(loop_count < 3, "unexpected multi-edge loop at vertex {v:?}");
|
||||
} else {
|
||||
assert!(
|
||||
!neighbors.contains(&u),
|
||||
"unexpected multi-edge between {v:?} and {u:?}"
|
||||
"unexpected multi-edge between vertices {v:?} and {u:?}"
|
||||
);
|
||||
neighbors.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that each returned edge *ei* connects the returned vertices *vi* and *v(i + 1)*.
|
||||
fn assert_path_order<G: GraphTopology>(built: &Built<G>)
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
for (w, &e) in built.vertices.windows(2).zip(&built.edges) {
|
||||
let incident_vertices = built.graph.incident_vertices(e);
|
||||
assert!(
|
||||
incident_vertices == (w[0], w[1]) || incident_vertices == (w[1], w[0]),
|
||||
"edge {e:?} should have incident vertices {:?} and {:?}",
|
||||
w[0],
|
||||
w[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,13 +281,12 @@ where
|
||||
F: Fn(V) -> Option<u32>,
|
||||
{
|
||||
let expected = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4];
|
||||
for (i, v) in vertices.iter().enumerate() {
|
||||
for (i, &v) in vertices.iter().enumerate() {
|
||||
assert_eq!(
|
||||
actual_for(*v),
|
||||
actual_for(v),
|
||||
Some(expected[i]),
|
||||
"unexpected distance from vertex {:?} to vertex {:?}",
|
||||
vertices[0],
|
||||
vertices[i]
|
||||
"unexpected distance from vertex {:?} to vertex {v:?}",
|
||||
vertices[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -321,8 +320,8 @@ pub fn assert_standard_unweighted_predecessors_v0<V: Debug + Copy + PartialEq>(
|
||||
/// Asserts that the path given as `path_option` is a valid path in `graph`
|
||||
///
|
||||
/// Walks the path and asserts that it is `Some`, that it is empty if and only if
|
||||
/// `source == target`, that its edges are pairwise incident in the order given, and that it starts
|
||||
/// at `source` and ends at `target`.
|
||||
/// `source == target` for any `target` in `targets`, that its edges are pairwise incident in the
|
||||
/// order given, and that it starts at `source` and ends at any `target` in `targets`.
|
||||
pub fn assert_valid_path<G: GraphTopology>(
|
||||
graph: &G,
|
||||
path_option: Option<Vec<G::Edge>>,
|
||||
|
||||
Reference in New Issue
Block a user