use rust_physics_engine::graph::coloring::{
chromatic_number_exact_small, color_count, greedy_coloring, is_proper_coloring,
max_clique_bron_kerbosch, max_independent_set_small, vertex_cover_exact_small, Order,
};
use rust_physics_engine::graph::core::Graph;
use rust_physics_engine::graph::layout::{
circular_layout, crossing_number_estimate, fruchterman_reingold, kamada_kawai,
planarity_test, spectral_layout, tree_layout_reingold_tilford,
};
use rust_physics_engine::graph::matching::blossom_max_matching;
use rust_physics_engine::graph::spectral::adjacency_spectrum;
use rust_physics_engine::monte_carlo::Rng;
fn pick(rng: &mut Rng, n: usize) -> usize {
((u128::from(rng.next_u64()) * n as u128) >> 64) as usize
}
fn random_graph(n: usize, p: f64, rng: &mut Rng) -> Graph {
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
if rng.next_f64() < p {
g.add_edge(u, v, 1.0);
}
}
}
g
}
#[test]
fn prop_koenig_links_matching_and_cover() {
let mut rng = Rng::new(0x_C047);
let mut bipartite_seen = 0;
for _ in 0..300 {
let n = 1 + pick(&mut rng, 10);
let g = random_graph(n, 0.15 + 0.5 * rng.next_f64(), &mut rng);
let matching = blossom_max_matching(&g);
let size = matching.iter().filter(|x| x.is_some()).count() / 2;
let cover = vertex_cover_exact_small(&g);
assert!(size <= cover.len(), "a matching of {size} under a cover of {}", cover.len());
if g.is_bipartite().is_some() {
bipartite_seen += 1;
assert_eq!(size, cover.len(), "Koenig's theorem fails on a bipartite graph");
}
}
assert!(bipartite_seen > 30, "only {bipartite_seen} bipartite graphs were drawn");
for n in [3usize, 5, 7, 9, 11] {
let mut c = Graph::new(n, false);
for i in 0..n {
c.add_edge(i, (i + 1) % n, 1.0);
}
let m = blossom_max_matching(&c);
let size = m.iter().filter(|x| x.is_some()).count() / 2;
assert_eq!(size, n / 2, "a maximum matching of C_{n}");
assert_eq!(vertex_cover_exact_small(&c).len(), n / 2 + 1, "a minimum cover of C_{n}");
}
}
#[test]
fn prop_spectral_bounds_bracket_the_chromatic_number() {
let mut rng = Rng::new(0x_40FF);
for _ in 0..200 {
let n = 2 + pick(&mut rng, 8);
let g = random_graph(n, 0.2 + 0.6 * rng.next_f64(), &mut rng);
if g.edge_count() == 0 {
continue;
}
let chi = chromatic_number_exact_small(&g) as f64;
let spectrum = adjacency_spectrum(&g);
let hi = *spectrum.last().expect("non-empty");
let lo = spectrum[0];
assert!(chi <= 1.0 + hi + 1e-6, "Wilf: chi {chi} over 1 + {hi}");
assert!(lo < -1e-9, "a graph with an edge has a negative eigenvalue");
let hoffman = 1.0 - hi / lo;
assert!(chi >= hoffman - 1e-6, "Hoffman: chi {chi} under {hoffman}");
}
for n in 2..=7usize {
let mut k = Graph::new(n, false);
for i in 0..n {
for j in i + 1..n {
k.add_edge(i, j, 1.0);
}
}
let s = adjacency_spectrum(&k);
let chi = chromatic_number_exact_small(&k) as f64;
assert!((chi - (1.0 + s[n - 1])).abs() < 1e-6, "Wilf is not tight on K_{n}");
assert!((chi - (1.0 - s[n - 1] / s[0])).abs() < 1e-6, "Hoffman is not tight on K_{n}");
}
}
#[test]
fn prop_planar_graphs_are_four_colorable() {
let mut rng = Rng::new(0x_4C01);
let mut planar_seen = 0;
let mut needed_four = 0;
for _ in 0..400 {
let n = 1 + pick(&mut rng, 10);
let g = random_graph(n, 0.1 + 0.45 * rng.next_f64(), &mut rng);
if !planarity_test(&g) {
continue;
}
planar_seen += 1;
let chi = chromatic_number_exact_small(&g);
assert!(chi <= 4, "a planar graph needed {chi} colours");
if chi == 4 {
needed_four += 1;
}
let c = greedy_coloring(&g, Order::SmallestLast);
assert!(is_proper_coloring(&g, &c));
assert!(color_count(&c) <= 6, "the degeneracy order used {} colours", color_count(&c));
if n >= 3 {
assert!(g.edge_count() <= 3 * n - 6, "over Euler's bound but called planar");
}
}
assert!(planar_seen > 100, "only {planar_seen} planar graphs were drawn");
assert!(needed_four > 0, "no drawn planar graph actually needed four colours");
}
#[test]
fn prop_clique_and_independence_bound_the_chromatic_number() {
let mut rng = Rng::new(0x_C119);
for _ in 0..200 {
let n = 1 + pick(&mut rng, 10);
let g = random_graph(n, 0.2 + 0.6 * rng.next_f64(), &mut rng);
let chi = chromatic_number_exact_small(&g);
let omega = max_clique_bron_kerbosch(&g).len();
let alpha = max_independent_set_small(&g).len();
assert!(chi >= omega, "chi {chi} under the clique number {omega}");
assert!(chi * alpha >= n, "chi {chi} times alpha {alpha} under n = {n}");
assert!(chi <= n);
}
}
#[test]
fn prop_nonplanar_graphs_cross_in_every_drawing() {
let mut rng = Rng::new(0x_C205);
let mut checked = 0;
for _ in 0..200 {
let n = 3 + pick(&mut rng, 8);
let g = random_graph(n, 0.3 + 0.5 * rng.next_f64(), &mut rng);
if planarity_test(&g) {
continue;
}
checked += 1;
let drawings = [
circular_layout(n),
kamada_kawai(&g, 200),
fruchterman_reingold(&g, 200, &mut rng),
spectral_layout(&g),
];
for (i, d) in drawings.iter().enumerate() {
assert!(
crossing_number_estimate(&g, d) >= 1,
"drawing {i} of a non-planar graph has no crossings"
);
}
}
assert!(checked > 50, "only {checked} non-planar graphs were drawn");
for _ in 0..100 {
let n = 1 + pick(&mut rng, 20);
let mut t = Graph::new(n, false);
for v in 1..n {
t.add_edge(pick(&mut rng, v), v, 1.0);
}
assert!(planarity_test(&t), "a tree is planar");
assert_eq!(crossing_number_estimate(&t, &tree_layout_reingold_tilford(&t, 0)), 0);
}
}