use crate::exact::bigint::BigInt;
use crate::linalg::matrix::Matrix;
use crate::mesh::Mesh;
use crate::monte_carlo::Rng;
#[derive(Debug, Clone, PartialEq)]
pub struct Graph {
pub n: usize,
pub adj: Vec<Vec<(usize, f64)>>,
pub directed: bool,
}
impl Graph {
#[must_use]
pub fn new(n: usize, directed: bool) -> Self {
Self {
n,
adj: vec![Vec::new(); n],
directed,
}
}
pub fn add_edge(&mut self, u: usize, v: usize, w: f64) {
assert!(u < self.n && v < self.n, "endpoint outside 0..{}", self.n);
self.adj[u].push((v, w));
if !self.directed && u != v {
self.adj[v].push((u, w));
}
}
#[must_use]
pub fn from_edges(n: usize, edges: &[(usize, usize, f64)], directed: bool) -> Self {
let mut g = Graph::new(n, directed);
for &(u, v, w) in edges {
g.add_edge(u, v, w);
}
g
}
#[must_use]
pub fn from_adjacency_matrix(m: &Matrix) -> Self {
assert_eq!(m.rows, m.cols, "the adjacency matrix must be square");
let n = m.rows;
let symmetric = (0..n).all(|i| (0..n).all(|j| m.get(i, j) == m.get(j, i)));
let mut g = Graph::new(n, !symmetric);
for i in 0..n {
let start = if symmetric { i } else { 0 };
for j in start..n {
if m.get(i, j) != 0.0 {
g.add_edge(i, j, m.get(i, j));
}
}
}
g
}
#[must_use]
pub fn to_adjacency_matrix(&self) -> Matrix {
let mut m = Matrix::zeros(self.n, self.n);
for u in 0..self.n {
for &(v, w) in &self.adj[u] {
m.set(u, v, m.get(u, v) + w);
}
}
m
}
#[must_use]
pub fn degree(&self, v: usize) -> usize {
self.adj[v].len()
}
#[must_use]
pub fn out_degree(&self, v: usize) -> usize {
self.adj[v].len()
}
#[must_use]
pub fn in_degree(&self, v: usize) -> usize {
self.adj
.iter()
.map(|list| list.iter().filter(|&&(t, _)| t == v).count())
.sum()
}
#[must_use]
pub fn edges(&self) -> Vec<(usize, usize, f64)> {
let mut out = Vec::new();
for u in 0..self.n {
for &(v, w) in &self.adj[u] {
if self.directed || u <= v {
out.push((u, v, w));
}
}
}
out
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.edges().len()
}
#[must_use]
pub fn reverse(&self) -> Graph {
if !self.directed {
return self.clone();
}
let mut g = Graph::new(self.n, true);
for u in 0..self.n {
for &(v, w) in &self.adj[u] {
g.adj[v].push((u, w));
}
}
g
}
#[must_use]
pub fn subgraph(&self, vs: &[usize]) -> Graph {
let mut index = vec![usize::MAX; self.n];
for (new, &old) in vs.iter().enumerate() {
assert!(old < self.n, "vertex {old} is outside 0..{}", self.n);
assert!(index[old] == usize::MAX, "vertex {old} appears twice");
index[old] = new;
}
let mut g = Graph::new(vs.len(), self.directed);
for (new_u, &u) in vs.iter().enumerate() {
for &(v, w) in &self.adj[u] {
let new_v = index[v];
if new_v == usize::MAX {
continue;
}
g.adj[new_u].push((new_v, w));
}
}
g
}
#[must_use]
pub fn complement(&self) -> Graph {
let mut present = vec![vec![false; self.n]; self.n];
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
present[u][v] = true;
}
}
let mut g = Graph::new(self.n, self.directed);
for u in 0..self.n {
let start = if self.directed { 0 } else { u + 1 };
for v in start..self.n {
if u != v && !present[u][v] {
g.add_edge(u, v, 1.0);
}
}
}
g
}
fn undirected_neighbors(&self, v: usize, incoming: &[Vec<usize>]) -> Vec<usize> {
let mut out: Vec<usize> = self.adj[v].iter().map(|&(t, _)| t).collect();
if self.directed {
out.extend_from_slice(&incoming[v]);
}
out
}
fn incoming_lists(&self) -> Vec<Vec<usize>> {
let mut inc = vec![Vec::new(); self.n];
if self.directed {
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
inc[v].push(u);
}
}
}
inc
}
#[must_use]
pub fn is_connected(&self) -> bool {
self.connected_components().len() <= 1
}
#[must_use]
pub fn connected_components(&self) -> Vec<Vec<usize>> {
let inc = self.incoming_lists();
let mut seen = vec![false; self.n];
let mut out = Vec::new();
for s in 0..self.n {
if seen[s] {
continue;
}
let mut comp = Vec::new();
let mut stack = vec![s];
seen[s] = true;
while let Some(v) = stack.pop() {
comp.push(v);
for w in self.undirected_neighbors(v, &inc) {
if !seen[w] {
seen[w] = true;
stack.push(w);
}
}
}
comp.sort_unstable();
out.push(comp);
}
out
}
#[must_use]
pub fn strongly_connected_components(&self) -> Vec<Vec<usize>> {
if !self.directed {
return self.connected_components();
}
let n = self.n;
let mut index = vec![usize::MAX; n];
let mut low = vec![0usize; n];
let mut on_stack = vec![false; n];
let mut stack: Vec<usize> = Vec::new();
let mut next_index = 0usize;
let mut out = Vec::new();
for root in 0..n {
if index[root] != usize::MAX {
continue;
}
let mut frames: Vec<(usize, usize)> = vec![(root, 0)];
index[root] = next_index;
low[root] = next_index;
next_index += 1;
stack.push(root);
on_stack[root] = true;
while let Some(&mut (v, ref mut i)) = frames.last_mut() {
if *i < self.adj[v].len() {
let w = self.adj[v][*i].0;
*i += 1;
if index[w] == usize::MAX {
index[w] = next_index;
low[w] = next_index;
next_index += 1;
stack.push(w);
on_stack[w] = true;
frames.push((w, 0));
} else if on_stack[w] {
low[v] = low[v].min(index[w]);
}
} else {
frames.pop();
if let Some(&(parent, _)) = frames.last() {
low[parent] = low[parent].min(low[v]);
}
if low[v] == index[v] {
let mut comp = Vec::new();
loop {
let w = stack.pop().expect("stack holds the component");
on_stack[w] = false;
comp.push(w);
if w == v {
break;
}
}
comp.sort_unstable();
out.push(comp);
}
}
}
}
out
}
#[must_use]
pub fn condensation(&self) -> (Graph, Vec<usize>) {
let comps = self.strongly_connected_components();
let mut label = vec![0usize; self.n];
for (c, comp) in comps.iter().enumerate() {
for &v in comp {
label[v] = c;
}
}
let mut g = Graph::new(comps.len(), true);
let mut present = vec![vec![false; comps.len()]; comps.len()];
for u in 0..self.n {
for &(v, w) in &self.adj[u] {
let (a, b) = (label[u], label[v]);
if a != b && !present[a][b] {
present[a][b] = true;
g.add_edge(a, b, w);
}
}
}
(g, label)
}
#[must_use]
pub fn is_bipartite(&self) -> Option<Vec<bool>> {
let inc = self.incoming_lists();
let mut color = vec![None; self.n];
for s in 0..self.n {
if color[s].is_some() {
continue;
}
color[s] = Some(false);
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
let cv = color[v].unwrap();
for w in self.undirected_neighbors(v, &inc) {
match color[w] {
None => {
color[w] = Some(!cv);
queue.push_back(w);
}
Some(cw) if cw == cv => return None,
Some(_) => {}
}
}
}
}
Some(color.into_iter().map(Option::unwrap).collect())
}
#[must_use]
pub fn is_tree(&self) -> bool {
self.n > 0 && self.is_connected() && self.edge_count() == self.n - 1
}
#[must_use]
pub fn is_dag(&self) -> bool {
self.directed && self.topological_sort().is_some()
}
#[must_use]
pub fn topological_sort(&self) -> Option<Vec<usize>> {
if !self.directed && self.edge_count() > 0 {
return None;
}
let mut indeg = vec![0usize; self.n];
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
indeg[v] += 1;
}
}
let mut ready: std::collections::BinaryHeap<std::cmp::Reverse<usize>> = (0..self.n)
.filter(|&v| indeg[v] == 0)
.map(std::cmp::Reverse)
.collect();
let mut order = Vec::with_capacity(self.n);
while let Some(std::cmp::Reverse(v)) = ready.pop() {
order.push(v);
for &(w, _) in &self.adj[v] {
indeg[w] -= 1;
if indeg[w] == 0 {
ready.push(std::cmp::Reverse(w));
}
}
}
(order.len() == self.n).then_some(order)
}
#[must_use]
pub fn bfs(&self, s: usize) -> Vec<Option<usize>> {
let mut dist = vec![None; self.n];
dist[s] = Some(0);
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
let d = dist[v].unwrap();
for &(w, _) in &self.adj[v] {
if dist[w].is_none() {
dist[w] = Some(d + 1);
queue.push_back(w);
}
}
}
dist
}
#[must_use]
pub fn dfs(&self, s: usize) -> Vec<usize> {
let mut seen = vec![false; self.n];
let mut order = Vec::new();
let mut frames: Vec<(usize, usize)> = vec![(s, 0)];
seen[s] = true;
order.push(s);
while let Some(&mut (v, ref mut i)) = frames.last_mut() {
if *i < self.adj[v].len() {
let w = self.adj[v][*i].0;
*i += 1;
if !seen[w] {
seen[w] = true;
order.push(w);
frames.push((w, 0));
}
} else {
frames.pop();
}
}
order
}
#[must_use]
pub fn bridges(&self) -> Vec<(usize, usize)> {
let (adj, _) = self.undirected_arc_lists();
let mut disc = vec![usize::MAX; self.n];
let mut low = vec![0usize; self.n];
let mut timer = 0usize;
let mut out = Vec::new();
for root in 0..self.n {
if disc[root] != usize::MAX {
continue;
}
let mut frames: Vec<(usize, usize, usize)> = vec![(root, usize::MAX, 0)];
disc[root] = timer;
low[root] = timer;
timer += 1;
while let Some(&mut (v, from_arc, ref mut i)) = frames.last_mut() {
if *i < adj[v].len() {
let (w, arc) = adj[v][*i];
*i += 1;
if arc == from_arc {
continue;
}
if disc[w] == usize::MAX {
disc[w] = timer;
low[w] = timer;
timer += 1;
frames.push((w, arc, 0));
} else {
low[v] = low[v].min(disc[w]);
}
} else {
frames.pop();
if let Some(&(parent, _, _)) = frames.last() {
low[parent] = low[parent].min(low[v]);
if low[v] > disc[parent] {
out.push((parent.min(v), parent.max(v)));
}
}
}
}
}
out.sort_unstable();
out
}
#[must_use]
pub fn articulation_points(&self) -> Vec<usize> {
let (adj, _) = self.undirected_arc_lists();
let mut disc = vec![usize::MAX; self.n];
let mut low = vec![0usize; self.n];
let mut timer = 0usize;
let mut is_ap = vec![false; self.n];
for root in 0..self.n {
if disc[root] != usize::MAX {
continue;
}
let mut root_children = 0usize;
let mut frames: Vec<(usize, usize, usize)> = vec![(root, usize::MAX, 0)];
disc[root] = timer;
low[root] = timer;
timer += 1;
while let Some(&mut (v, from_arc, ref mut i)) = frames.last_mut() {
if *i < adj[v].len() {
let (w, arc) = adj[v][*i];
*i += 1;
if arc == from_arc {
continue;
}
if disc[w] == usize::MAX {
if v == root {
root_children += 1;
}
disc[w] = timer;
low[w] = timer;
timer += 1;
frames.push((w, arc, 0));
} else {
low[v] = low[v].min(disc[w]);
}
} else {
frames.pop();
if let Some(&(parent, _, _)) = frames.last() {
low[parent] = low[parent].min(low[v]);
if parent != root && low[v] >= disc[parent] {
is_ap[parent] = true;
}
}
}
}
if root_children > 1 {
is_ap[root] = true;
}
}
(0..self.n).filter(|&v| is_ap[v]).collect()
}
fn undirected_arc_lists(&self) -> (Vec<Vec<(usize, usize)>>, usize) {
let mut adj = vec![Vec::new(); self.n];
let mut id = 0usize;
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
if !self.directed && u > v {
continue;
}
adj[u].push((v, id));
if u != v {
adj[v].push((u, id));
}
id += 1;
}
}
(adj, id)
}
#[must_use]
pub fn eulerian_circuit(&self) -> Option<Vec<usize>> {
self.hierholzer(true)
}
#[must_use]
pub fn eulerian_path(&self) -> Option<Vec<usize>> {
self.hierholzer(false)
}
fn hierholzer(&self, need_circuit: bool) -> Option<Vec<usize>> {
let m = self.edge_count();
if m == 0 {
return Some(if self.n == 0 { Vec::new() } else { vec![0] });
}
let with_edges: Vec<usize> = (0..self.n)
.filter(|&v| !self.adj[v].is_empty() || self.in_degree(v) > 0)
.collect();
let comps = self.connected_components();
let comp_of = |v: usize| comps.iter().position(|c| c.binary_search(&v).is_ok()).unwrap();
let first_comp = comp_of(with_edges[0]);
if with_edges.iter().any(|&v| comp_of(v) != first_comp) {
return None;
}
let start = if self.directed {
let mut plus = Vec::new();
let mut minus = Vec::new();
for v in 0..self.n {
let (o, i) = (self.out_degree(v) as i64, self.in_degree(v) as i64);
match o - i {
0 => {}
1 => plus.push(v),
-1 => minus.push(v),
_ => return None,
}
}
match (plus.len(), minus.len()) {
(0, 0) => with_edges[0],
(1, 1) if !need_circuit => plus[0],
_ => return None,
}
} else {
let odd: Vec<usize> = (0..self.n).filter(|&v| !self.degree(v).is_multiple_of(2)).collect();
match odd.len() {
0 => with_edges[0],
2 if !need_circuit => odd[0],
_ => return None,
}
};
let adj: Vec<Vec<(usize, usize)>> = if self.directed {
let mut a = vec![Vec::new(); self.n];
let mut id = 0usize;
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
a[u].push((v, id));
id += 1;
}
}
a
} else {
self.undirected_arc_lists().0
};
let mut used = vec![false; m];
let mut cursor = vec![0usize; self.n];
let mut stack = vec![start];
let mut circuit = Vec::with_capacity(m + 1);
while let Some(&v) = stack.last() {
while cursor[v] < adj[v].len() && used[adj[v][cursor[v]].1] {
cursor[v] += 1;
}
if cursor[v] < adj[v].len() {
let (w, id) = adj[v][cursor[v]];
used[id] = true;
stack.push(w);
} else {
circuit.push(v);
stack.pop();
}
}
circuit.reverse();
(circuit.len() == m + 1).then_some(circuit)
}
#[must_use]
pub fn hamiltonian_path_small(&self) -> Option<Vec<usize>> {
assert!(self.n <= 20, "hamiltonian_path_small needs n <= 20");
let n = self.n;
if n == 0 {
return Some(Vec::new());
}
let mut reach = vec![0u32; n];
for u in 0..n {
for &(v, _) in &self.adj[u] {
if u != v {
reach[u] |= 1 << v;
}
}
}
let full = 1usize << n;
let mut seen = vec![0u32; full];
for v in 0..n {
seen[1 << v] |= 1 << v;
}
for mask in 1..full {
let ends = seen[mask];
if ends == 0 {
continue;
}
for last in 0..n {
if ends >> last & 1 == 0 {
continue;
}
let mut cand = reach[last] & !(mask as u32);
while cand != 0 {
let next = cand.trailing_zeros() as usize;
cand &= cand - 1;
seen[mask | 1 << next] |= 1 << next;
}
}
}
let last = (0..n).find(|&v| seen[full - 1] >> v & 1 == 1)?;
let mut path = vec![last];
let mut mask = full - 1;
let mut cur = last;
while mask.count_ones() > 1 {
let prev_mask = mask & !(1 << cur);
let prev = (0..n)
.find(|&p| {
seen[prev_mask] >> p & 1 == 1 && reach[p] >> cur & 1 == 1
})
.expect("a predecessor must exist");
path.push(prev);
mask = prev_mask;
cur = prev;
}
path.reverse();
Some(path)
}
#[must_use]
pub fn girth(&self) -> Option<usize> {
let (adj, _) = self.undirected_arc_lists();
if (0..self.n).any(|u| self.adj[u].iter().any(|&(v, _)| v == u)) {
return Some(1);
}
let mut best = usize::MAX;
for root in 0..self.n {
let mut dist = vec![usize::MAX; self.n];
let mut from = vec![usize::MAX; self.n];
dist[root] = 0;
let mut queue = std::collections::VecDeque::from(vec![root]);
while let Some(v) = queue.pop_front() {
if 2 * dist[v] >= best {
break;
}
for &(w, arc) in &adj[v] {
if arc == from[v] {
continue;
}
if dist[w] == usize::MAX {
dist[w] = dist[v] + 1;
from[w] = arc;
queue.push_back(w);
} else {
best = best.min(dist[v] + dist[w] + 1);
}
}
}
}
(best != usize::MAX).then_some(best)
}
#[must_use]
pub fn eccentricities(&self) -> Vec<Option<usize>> {
(0..self.n)
.map(|v| {
let d = self.bfs(v);
d.iter().copied().try_fold(0usize, |acc, x| Some(acc.max(x?)))
})
.collect()
}
#[must_use]
pub fn diameter(&self) -> Option<usize> {
self.eccentricities()
.into_iter()
.try_fold(0usize, |acc, x| Some(acc.max(x?)))
}
#[must_use]
pub fn radius(&self) -> Option<usize> {
let ecc = self.eccentricities();
if ecc.iter().any(Option::is_none) || ecc.is_empty() {
return None;
}
ecc.into_iter().flatten().min()
}
#[must_use]
pub fn center(&self) -> Vec<usize> {
let Some(r) = self.radius() else {
return Vec::new();
};
let ecc = self.eccentricities();
(0..self.n).filter(|&v| ecc[v] == Some(r)).collect()
}
#[must_use]
pub fn density(&self) -> f64 {
if self.n < 2 {
return 0.0;
}
let simple = self.simple_neighbor_sets();
let m: usize = simple.iter().map(std::collections::BTreeSet::len).sum();
let possible = self.n * (self.n - 1);
if self.directed {
m as f64 / possible as f64
} else {
m as f64 / possible as f64
}
}
fn simple_neighbor_sets(&self) -> Vec<std::collections::BTreeSet<usize>> {
let mut sets = vec![std::collections::BTreeSet::new(); self.n];
for u in 0..self.n {
for &(v, _) in &self.adj[u] {
if u != v {
sets[u].insert(v);
if self.directed {
sets[v].insert(u);
}
}
}
}
sets
}
#[must_use]
pub fn clustering_coefficient(&self, v: usize) -> f64 {
let sets = self.simple_neighbor_sets();
let nbrs: Vec<usize> = sets[v].iter().copied().collect();
let k = nbrs.len();
if k < 2 {
return 0.0;
}
let mut links = 0usize;
for i in 0..k {
for j in i + 1..k {
if sets[nbrs[i]].contains(&nbrs[j]) {
links += 1;
}
}
}
2.0 * links as f64 / (k * (k - 1)) as f64
}
#[must_use]
pub fn average_clustering(&self) -> f64 {
if self.n == 0 {
return 0.0;
}
(0..self.n).map(|v| self.clustering_coefficient(v)).sum::<f64>() / self.n as f64
}
#[must_use]
pub fn transitivity(&self) -> f64 {
let sets = self.simple_neighbor_sets();
let mut triangles = 0usize;
let mut triples = 0usize;
for v in 0..self.n {
let nbrs: Vec<usize> = sets[v].iter().copied().collect();
let k = nbrs.len();
if k < 2 {
continue;
}
triples += k * (k - 1) / 2;
for i in 0..k {
for j in i + 1..k {
if sets[nbrs[i]].contains(&nbrs[j]) {
triangles += 1;
}
}
}
}
if triples == 0 {
return 0.0;
}
triangles as f64 / triples as f64
}
#[must_use]
pub fn degree_distribution(&self) -> Vec<usize> {
let max = (0..self.n).map(|v| self.degree(v)).max().unwrap_or(0);
let mut out = vec![0usize; max + 1];
for v in 0..self.n {
out[self.degree(v)] += 1;
}
out
}
#[must_use]
pub fn assortativity(&self) -> f64 {
let deg: Vec<f64> = (0..self.n).map(|v| self.degree(v) as f64).collect();
let mut pairs: Vec<(f64, f64)> = Vec::new();
for (u, v, _) in self.edges() {
pairs.push((deg[u], deg[v]));
pairs.push((deg[v], deg[u]));
}
if pairs.is_empty() {
return 0.0;
}
let m = pairs.len() as f64;
let mx = pairs.iter().map(|p| p.0).sum::<f64>() / m;
let my = pairs.iter().map(|p| p.1).sum::<f64>() / m;
let mut cov = 0.0;
let mut vx = 0.0;
let mut vy = 0.0;
for &(x, y) in &pairs {
cov += (x - mx) * (y - my);
vx += (x - mx) * (x - mx);
vy += (y - my) * (y - my);
}
if vx == 0.0 || vy == 0.0 {
return 0.0;
}
cov / (vx * vy).sqrt()
}
#[must_use]
pub fn k_core(&self, k: usize) -> Vec<usize> {
let core = self.core_numbers();
(0..self.n).filter(|&v| core[v] >= k).collect()
}
#[must_use]
pub fn core_numbers(&self) -> Vec<usize> {
let sets = self.simple_neighbor_sets();
let mut deg: Vec<usize> = sets.iter().map(std::collections::BTreeSet::len).collect();
let mut removed = vec![false; self.n];
let mut core = vec![0usize; self.n];
let mut running = 0usize;
for _ in 0..self.n {
let v = (0..self.n)
.filter(|&v| !removed[v])
.min_by_key(|&v| deg[v])
.expect("a vertex remains");
running = running.max(deg[v]);
core[v] = running;
removed[v] = true;
for &w in &sets[v] {
if !removed[w] {
deg[w] -= 1;
}
}
}
core
}
}
#[must_use]
pub fn complete_graph(n: usize) -> Graph {
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
g.add_edge(u, v, 1.0);
}
}
g
}
#[must_use]
pub fn cycle_graph(n: usize) -> Graph {
assert!(n >= 3, "a simple cycle needs at least three vertices");
let mut g = Graph::new(n, false);
for u in 0..n {
g.add_edge(u, (u + 1) % n, 1.0);
}
g
}
#[must_use]
pub fn path_graph(n: usize) -> Graph {
let mut g = Graph::new(n, false);
for u in 0..n.saturating_sub(1) {
g.add_edge(u, u + 1, 1.0);
}
g
}
#[must_use]
pub fn star_graph(n: usize) -> Graph {
let mut g = Graph::new(n, false);
for v in 1..n {
g.add_edge(0, v, 1.0);
}
g
}
#[must_use]
pub fn wheel_graph(n: usize) -> Graph {
assert!(n >= 4, "a wheel needs a hub and a cycle of at least three");
let mut g = Graph::new(n, false);
let rim = n - 1;
for i in 0..rim {
g.add_edge(0, i + 1, 1.0);
g.add_edge(i + 1, (i + 1) % rim + 1, 1.0);
}
g
}
#[must_use]
pub fn grid_2d(w: usize, h: usize) -> Graph {
let mut g = Graph::new(w * h, false);
for y in 0..h {
for x in 0..w {
let v = y * w + x;
if x + 1 < w {
g.add_edge(v, v + 1, 1.0);
}
if y + 1 < h {
g.add_edge(v, v + w, 1.0);
}
}
}
g
}
#[must_use]
pub fn hypercube_graph(d: u32) -> Graph {
assert!(d <= 20, "d must be at most 20");
let n = 1usize << d;
let mut g = Graph::new(n, false);
for v in 0..n {
for b in 0..d {
let w = v ^ (1 << b);
if v < w {
g.add_edge(v, w, 1.0);
}
}
}
g
}
#[must_use]
pub fn petersen_graph() -> Graph {
let mut g = Graph::new(10, false);
for i in 0..5 {
g.add_edge(i, (i + 1) % 5, 1.0);
g.add_edge(5 + i, 5 + (i + 2) % 5, 1.0);
g.add_edge(i, 5 + i, 1.0);
}
g
}
#[must_use]
pub fn complete_bipartite(m: usize, n: usize) -> Graph {
let mut g = Graph::new(m + n, false);
for u in 0..m {
for v in 0..n {
g.add_edge(u, m + v, 1.0);
}
}
g
}
fn bounded(rng: &mut Rng, bound: u64) -> u64 {
((u128::from(rng.next_u64()) * u128::from(bound)) >> 64) as u64
}
pub fn erdos_renyi(n: usize, p: f64, rng: &mut Rng) -> Graph {
assert!((0.0..=1.0).contains(&p), "p must be a probability");
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
}
pub fn barabasi_albert(n: usize, m: usize, rng: &mut Rng) -> Graph {
assert!(m >= 1 && m < n, "m must satisfy 1 <= m < n");
let mut g = complete_graph(m);
g.n = n;
g.adj.resize(n, Vec::new());
let mut targets: Vec<usize> = Vec::new();
for (u, v, _) in g.edges() {
targets.push(u);
targets.push(v);
}
if targets.is_empty() {
targets.push(0);
}
for v in m..n {
let mut chosen: Vec<usize> = Vec::new();
while chosen.len() < m {
let t = targets[bounded(rng, targets.len() as u64) as usize];
if t != v && !chosen.contains(&t) {
chosen.push(t);
}
}
for &t in &chosen {
g.add_edge(v, t, 1.0);
targets.push(v);
targets.push(t);
}
}
g
}
pub fn watts_strogatz(n: usize, k: usize, beta: f64, rng: &mut Rng) -> Graph {
assert!(k >= 2 && k.is_multiple_of(2) && k < n, "k must be even with 2 <= k < n");
assert!((0.0..=1.0).contains(&beta), "beta must be a probability");
let mut present = vec![std::collections::BTreeSet::new(); n];
let mut edges: Vec<(usize, usize)> = Vec::new();
for u in 0..n {
for d in 1..=k / 2 {
let v = (u + d) % n;
present[u].insert(v);
present[v].insert(u);
edges.push((u, v));
}
}
for idx in 0..edges.len() {
if rng.next_f64() >= beta {
continue;
}
let (u, v) = edges[idx];
let w = bounded(rng, n as u64) as usize;
if w == u || present[u].contains(&w) {
continue;
}
present[u].remove(&v);
present[v].remove(&u);
present[u].insert(w);
present[w].insert(u);
edges[idx] = (u, w);
}
let mut g = Graph::new(n, false);
for &(u, v) in &edges {
g.add_edge(u, v, 1.0);
}
g
}
pub fn random_regular(n: usize, d: usize, rng: &mut Rng) -> Option<Graph> {
assert!(d < n, "a d-regular simple graph needs d < n");
if !(n * d).is_multiple_of(2) {
return None;
}
if d == 0 {
return Some(Graph::new(n, false));
}
'attempt: for _ in 0..1_000 {
let mut half: Vec<usize> = (0..n).flat_map(|v| std::iter::repeat_n(v, d)).collect();
for i in (1..half.len()).rev() {
half.swap(i, bounded(rng, i as u64 + 1) as usize);
}
let mut seen = vec![std::collections::BTreeSet::new(); n];
let mut edges = Vec::with_capacity(half.len() / 2);
for pair in half.chunks(2) {
let (u, v) = (pair[0], pair[1]);
if u == v || seen[u].contains(&v) {
continue 'attempt;
}
seen[u].insert(v);
seen[v].insert(u);
edges.push((u, v));
}
let mut g = Graph::new(n, false);
for (u, v) in edges {
g.add_edge(u, v, 1.0);
}
return Some(g);
}
None
}
pub fn random_geometric(n: usize, radius: f64, rng: &mut Rng) -> (Graph, Vec<(f64, f64)>) {
assert!(radius >= 0.0, "radius must be non-negative");
let pts: Vec<(f64, f64)> = (0..n).map(|_| (rng.next_f64(), rng.next_f64())).collect();
let mut g = Graph::new(n, false);
let r2 = radius * radius;
for u in 0..n {
for v in u + 1..n {
let (dx, dy) = (pts[u].0 - pts[v].0, pts[u].1 - pts[v].1);
if dx * dx + dy * dy <= r2 {
g.add_edge(u, v, (dx * dx + dy * dy).sqrt());
}
}
}
(g, pts)
}
pub fn stochastic_block_model(sizes: &[usize], p_matrix: &[Vec<f64>], rng: &mut Rng) -> Graph {
assert_eq!(p_matrix.len(), sizes.len(), "one row per block");
assert!(
p_matrix.iter().all(|r| r.len() == sizes.len()),
"p_matrix must be square"
);
assert!(
p_matrix.iter().flatten().all(|p| (0.0..=1.0).contains(p)),
"every entry must be a probability"
);
let mut block = Vec::new();
for (b, &s) in sizes.iter().enumerate() {
block.extend(std::iter::repeat_n(b, s));
}
let n = block.len();
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
if rng.next_f64() < p_matrix[block[u]][block[v]] {
g.add_edge(u, v, 1.0);
}
}
}
g
}
#[must_use]
pub fn graph_from_mesh(mesh: &Mesh) -> Graph {
let mut g = Graph::new(mesh.vertices.len(), false);
let mut seen = std::collections::BTreeSet::new();
for tri in &mesh.indices {
for k in 0..3 {
let (a, b) = (tri[k], tri[(k + 1) % 3]);
let key = (a.min(b), a.max(b));
if a != b && seen.insert(key) {
let d = (mesh.vertices[a] - mesh.vertices[b]).magnitude();
g.add_edge(key.0, key.1, d);
}
}
}
g
}
#[must_use]
pub fn line_graph(g: &Graph) -> (Graph, Vec<(usize, usize)>) {
assert!(!g.directed, "line_graph is defined here for undirected graphs");
let edges: Vec<(usize, usize)> = g.edges().into_iter().map(|(u, v, _)| (u, v)).collect();
let mut out = Graph::new(edges.len(), false);
for i in 0..edges.len() {
for j in i + 1..edges.len() {
let (a, b) = (edges[i], edges[j]);
if a.0 == b.0 || a.0 == b.1 || a.1 == b.0 || a.1 == b.1 {
out.add_edge(i, j, 1.0);
}
}
}
(out, edges)
}
#[must_use]
pub fn cartesian_product(g: &Graph, h: &Graph) -> Graph {
let mut out = Graph::new(g.n * h.n, false);
let idx = |u: usize, x: usize| u * h.n + x;
for (u, v, w) in g.edges() {
for x in 0..h.n {
out.add_edge(idx(u, x), idx(v, x), w);
}
}
for (x, y, w) in h.edges() {
for u in 0..g.n {
out.add_edge(idx(u, x), idx(u, y), w);
}
}
out
}
#[must_use]
pub fn tensor_product(g: &Graph, h: &Graph) -> Graph {
let mut out = Graph::new(g.n * h.n, false);
let idx = |u: usize, x: usize| u * h.n + x;
for (u, v, w1) in g.edges() {
for (x, y, w2) in h.edges() {
out.add_edge(idx(u, x), idx(v, y), w1 * w2);
if x != y && u != v {
out.add_edge(idx(u, y), idx(v, x), w1 * w2);
}
}
}
out
}
#[must_use]
pub fn canonical_form_small(g: &Graph) -> Vec<u64> {
assert!(g.n <= 10, "canonical_form_small needs n <= 10");
let n = g.n;
let mut bits = vec![0u64; n];
for u in 0..n {
for &(v, _) in &g.adj[u] {
if u != v {
bits[u] |= 1 << v;
if !g.directed {
bits[v] |= 1 << u;
}
}
}
}
let mut best: Option<Vec<u64>> = None;
let mut perm: Vec<usize> = (0..n).collect();
loop {
let mut inv = vec![0usize; n];
for (new, &old) in perm.iter().enumerate() {
inv[old] = new;
}
let rows: Vec<u64> = (0..n)
.map(|i| {
let old = perm[i];
let mut r = 0u64;
for v in 0..n {
if bits[old] >> v & 1 == 1 {
r |= 1 << inv[v];
}
}
r
})
.collect();
if best.as_ref().is_none_or(|b| rows < *b) {
best = Some(rows);
}
if !next_permutation(&mut perm) {
break;
}
}
best.unwrap_or_default()
}
fn next_permutation(p: &mut [usize]) -> bool {
let n = p.len();
if n < 2 {
return false;
}
let mut i = n - 1;
while i > 0 && p[i - 1] >= p[i] {
i -= 1;
}
if i == 0 {
return false;
}
let pivot = i - 1;
let mut j = n - 1;
while p[j] <= p[pivot] {
j -= 1;
}
p.swap(pivot, j);
p[i..].reverse();
true
}
#[must_use]
pub fn is_isomorphic_small(g: &Graph, h: &Graph) -> bool {
if g.n != h.n || g.edge_count() != h.edge_count() || g.directed != h.directed {
return false;
}
let mut dg: Vec<usize> = (0..g.n).map(|v| g.degree(v)).collect();
let mut dh: Vec<usize> = (0..h.n).map(|v| h.degree(v)).collect();
dg.sort_unstable();
dh.sort_unstable();
if dg != dh {
return false;
}
canonical_form_small(g) == canonical_form_small(h)
}
#[must_use]
pub fn graph6_encode(g: &Graph) -> String {
assert!(!g.directed, "graph6 encodes undirected graphs");
assert!(g.n <= 62, "this encoder handles n <= 62");
let mut present = vec![vec![false; g.n]; g.n];
for (u, v, _) in g.edges() {
if u != v {
present[u][v] = true;
present[v][u] = true;
}
}
let mut bits: Vec<bool> = Vec::new();
for j in 1..g.n {
for i in 0..j {
bits.push(present[i][j]);
}
}
while !bits.len().is_multiple_of(6) {
bits.push(false);
}
let mut s = String::new();
s.push((g.n as u8 + 63) as char);
for chunk in bits.chunks(6) {
let mut byte = 0u8;
for (k, &b) in chunk.iter().enumerate() {
if b {
byte |= 1 << (5 - k);
}
}
s.push((byte + 63) as char);
}
s
}
#[must_use]
pub fn graph6_decode(s: &str) -> Graph {
let bytes: Vec<u8> = s.bytes().collect();
assert!(!bytes.is_empty(), "an empty string is not graph6");
assert!(
bytes.iter().all(|&b| (63..=126).contains(&b)),
"graph6 bytes must be printable"
);
let n = (bytes[0] - 63) as usize;
let needed = n * n.saturating_sub(1) / 2;
let mut bits: Vec<bool> = Vec::with_capacity(bytes.len().saturating_sub(1) * 6);
for &b in &bytes[1..] {
let v = b - 63;
for k in (0..6).rev() {
bits.push(v >> k & 1 == 1);
}
}
assert!(bits.len() >= needed, "the string is too short for n = {n}");
let mut g = Graph::new(n, false);
let mut idx = 0usize;
for j in 1..n {
for i in 0..j {
if bits[idx] {
g.add_edge(i, j, 1.0);
}
idx += 1;
}
}
g
}
#[must_use]
pub fn spanning_tree_count_exact(g: &Graph) -> BigInt {
assert!(!g.directed, "the matrix-tree theorem here is for undirected graphs");
if g.n == 0 {
return BigInt::zero();
}
if g.n == 1 {
return BigInt::one();
}
let n = g.n - 1;
let mut a = vec![vec![0i64; n]; n];
for (u, v, _) in g.edges() {
if u == v {
continue;
}
if u < n {
a[u][u] += 1;
}
if v < n {
a[v][v] += 1;
}
if u < n && v < n {
a[u][v] -= 1;
a[v][u] -= 1;
}
}
bareiss_determinant(a)
}
fn bareiss_determinant(mut a: Vec<Vec<i64>>) -> BigInt {
let n = a.len();
if n == 0 {
return BigInt::one();
}
let mut m: Vec<Vec<BigInt>> = a
.drain(..)
.map(|row| row.into_iter().map(BigInt::from_i64).collect())
.collect();
let mut prev = BigInt::one();
let mut sign = 1i64;
for k in 0..n - 1 {
if m[k][k].is_zero() {
let Some(r) = (k + 1..n).find(|&r| !m[r][k].is_zero()) else {
return BigInt::zero();
};
m.swap(k, r);
sign = -sign;
}
for i in k + 1..n {
for j in k + 1..n {
let num = m[i][j].mul(&m[k][k]).sub(&m[i][k].mul(&m[k][j]));
let (q, r) = num.div_rem(&prev);
debug_assert!(r.is_zero(), "Bareiss division must be exact");
m[i][j] = q;
}
}
prev = m[k][k].clone();
}
let det = m[n - 1][n - 1].clone();
if sign < 0 { det.neg() } else { det }
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
fn reachable(g: &Graph) -> Vec<Vec<bool>> {
let n = g.n;
let mut r = vec![vec![false; n]; n];
for (i, row) in r.iter_mut().enumerate() {
row[i] = true;
}
for u in 0..n {
for &(v, _) in &g.adj[u] {
r[u][v] = true;
}
}
loop {
let mut changed = false;
for i in 0..n {
for k in 0..n {
if r[i][k] {
for j in 0..n {
if r[k][j] && !r[i][j] {
r[i][j] = true;
changed = true;
}
}
}
}
}
if !changed {
break;
}
}
r
}
fn component_count(g: &Graph) -> usize {
let n = g.n;
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(n);
for u in 0..n {
for &(v, _) in &g.adj[u] {
ds.union(u, v);
}
}
ds.count()
}
fn random_graph(n: usize, p: f64, directed: bool, rng: &mut Rng) -> Graph {
let mut g = Graph::new(n, directed);
for u in 0..n {
let start = if directed { 0 } else { u + 1 };
for v in start..n {
if u != v && rng.next_f64() < p {
g.add_edge(u, v, 1.0);
}
}
}
g
}
#[test]
fn matrix_and_edge_list_round_trip() {
let mut rng = Rng::new(11);
for directed in [false, true] {
for n in 1..=8usize {
let g = random_graph(n, 0.4, directed, &mut rng);
let m = g.to_adjacency_matrix();
let back = Graph::from_adjacency_matrix(&m);
let mut a: Vec<(usize, usize)> = g
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect();
let mut b: Vec<(usize, usize)> = back
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect();
a.sort_unstable();
b.sort_unstable();
assert_eq!(a, b, "n = {n}, directed = {directed}");
assert_eq!(back.to_adjacency_matrix().data, m.data);
}
}
}
#[test]
fn edge_count_matches_the_degree_sum() {
let mut rng = Rng::new(22);
for directed in [false, true] {
for n in 1..=10usize {
let g = random_graph(n, 0.35, directed, &mut rng);
let deg_sum: usize = (0..n).map(|v| g.degree(v)).sum();
let want = if directed { deg_sum } else { deg_sum / 2 };
assert_eq!(g.edge_count(), want, "n = {n}, directed = {directed}");
let in_sum: usize = (0..n).map(|v| g.in_degree(v)).sum();
assert_eq!(in_sum, deg_sum);
}
}
}
#[test]
fn reverse_transposes_reachability() {
let mut rng = Rng::new(33);
for n in 1..=8usize {
let g = random_graph(n, 0.3, true, &mut rng);
let r = g.reverse();
assert_eq!(r.reverse().to_adjacency_matrix().data, g.to_adjacency_matrix().data);
let rg = reachable(&g);
let rr = reachable(&r);
for i in 0..n {
for j in 0..n {
assert_eq!(rg[i][j], rr[j][i], "({i}, {j}) at n = {n}");
}
}
}
}
#[test]
fn subgraph_keeps_exactly_the_internal_edges() {
let mut rng = Rng::new(44);
for _ in 0..50 {
let g = random_graph(9, 0.4, false, &mut rng);
let vs: Vec<usize> = (0..9).filter(|_| rng.next_f64() < 0.5).collect();
if vs.is_empty() {
continue;
}
let sub = g.subgraph(&vs);
assert_eq!(sub.n, vs.len());
let expected = g
.edges()
.into_iter()
.filter(|&(u, v, _)| vs.contains(&u) && vs.contains(&v))
.count();
assert_eq!(sub.edge_count(), expected, "on {vs:?}");
}
}
#[test]
fn complement_partitions_the_complete_graph() {
let mut rng = Rng::new(55);
for n in 1..=9usize {
let g = random_graph(n, 0.45, false, &mut rng);
let c = g.complement();
assert_eq!(g.edge_count() + c.edge_count(), n * (n - 1) / 2, "n = {n}");
let ge: BTreeSet<(usize, usize)> =
g.edges().into_iter().map(|(u, v, _)| (u.min(v), u.max(v))).collect();
let ce: BTreeSet<(usize, usize)> =
c.edges().into_iter().map(|(u, v, _)| (u.min(v), u.max(v))).collect();
assert!(ge.is_disjoint(&ce));
assert_eq!(
c.complement()
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect::<BTreeSet<_>>(),
ge
);
}
}
#[test]
fn components_match_union_find() {
let mut rng = Rng::new(66);
for directed in [false, true] {
for n in 1..=12usize {
let g = random_graph(n, 0.15, directed, &mut rng);
let comps = g.connected_components();
assert_eq!(comps.len(), component_count(&g), "n = {n}");
assert_eq!(comps.iter().map(Vec::len).sum::<usize>(), n);
assert_eq!(g.is_connected(), comps.len() <= 1);
let mut flat: Vec<usize> = comps.iter().flatten().copied().collect();
flat.sort_unstable();
assert_eq!(flat, (0..n).collect::<Vec<_>>());
assert!(comps.windows(2).all(|w| w[0][0] < w[1][0]));
}
}
}
#[test]
fn scc_matches_mutual_reachability() {
let mut rng = Rng::new(77);
for n in 1..=10usize {
for _ in 0..20 {
let g = random_graph(n, 0.25, true, &mut rng);
let r = reachable(&g);
let comps = g.strongly_connected_components();
let mut label = vec![usize::MAX; n];
for (c, comp) in comps.iter().enumerate() {
for &v in comp {
assert_eq!(label[v], usize::MAX, "vertex {v} in two components");
label[v] = c;
}
}
for i in 0..n {
for j in 0..n {
let mutual = r[i][j] && r[j][i];
assert_eq!(
label[i] == label[j],
mutual,
"({i}, {j}) mutual = {mutual}"
);
}
}
for u in 0..n {
for &(v, _) in &g.adj[u] {
if label[u] != label[v] {
assert!(label[u] > label[v], "component order is wrong");
}
}
}
let (cond, cl) = g.condensation();
assert_eq!(cond.n, comps.len());
assert!(cond.is_dag() || cond.n <= 1);
assert_eq!(cl, label);
}
}
}
#[test]
fn bipartite_iff_no_odd_cycle() {
let mut rng = Rng::new(88);
for n in 1..=9usize {
for _ in 0..30 {
let g = random_graph(n, 0.3, false, &mut rng);
match g.is_bipartite() {
Some(color) => {
for (u, v, _) in g.edges() {
assert_ne!(color[u], color[v], "invalid colouring");
}
assert!(!has_odd_cycle(&g), "coloured but has an odd cycle");
}
None => assert!(has_odd_cycle(&g), "refused but has no odd cycle"),
}
}
}
assert!(cycle_graph(6).is_bipartite().is_some());
assert!(cycle_graph(5).is_bipartite().is_none());
assert!(complete_bipartite(3, 4).is_bipartite().is_some());
assert!(complete_graph(3).is_bipartite().is_none());
assert!(petersen_graph().is_bipartite().is_none(), "girth 5 is odd");
assert!(hypercube_graph(4).is_bipartite().is_some());
}
fn has_odd_cycle(g: &Graph) -> bool {
let mut color = vec![None; g.n];
for s in 0..g.n {
if color[s].is_some() {
continue;
}
color[s] = Some(false);
let mut q = std::collections::VecDeque::from(vec![s]);
while let Some(v) = q.pop_front() {
let cv = color[v].unwrap();
for &(w, _) in &g.adj[v] {
match color[w] {
None => {
color[w] = Some(!cv);
q.push_back(w);
}
Some(cw) if cw == cv => return true,
Some(_) => {}
}
}
}
}
false
}
#[test]
fn bridges_are_exactly_the_component_splitting_edges() {
let mut rng = Rng::new(101);
for n in 2..=9usize {
for _ in 0..30 {
let g = random_graph(n, 0.3, false, &mut rng);
let base = component_count(&g);
let found: BTreeSet<(usize, usize)> = g.bridges().into_iter().collect();
let mut expected = BTreeSet::new();
let all = g.edges();
for (i, &(u, v, _)) in all.iter().enumerate() {
if u == v {
continue;
}
let mut h = Graph::new(n, false);
for (j, &(a, b, w)) in all.iter().enumerate() {
if i != j {
h.add_edge(a, b, w);
}
}
if component_count(&h) > base {
expected.insert((u.min(v), u.max(v)));
}
}
assert_eq!(found, expected, "n = {n}");
}
}
assert_eq!(path_graph(5).bridges().len(), 4);
assert!(cycle_graph(5).bridges().is_empty());
}
#[test]
fn articulation_points_are_exactly_the_cut_vertices() {
let mut rng = Rng::new(111);
for n in 3..=9usize {
for _ in 0..30 {
let g = random_graph(n, 0.3, false, &mut rng);
let found: BTreeSet<usize> = g.articulation_points().into_iter().collect();
let mut expected = BTreeSet::new();
for v in 0..n {
let rest: Vec<usize> = (0..n).filter(|&x| x != v).collect();
let before = component_count(&g.subgraph(&rest)) ;
let with_v = component_count(&g);
let isolated = g.degree(v) == 0;
let effective = if isolated { with_v - 1 } else { with_v };
if before > effective {
expected.insert(v);
}
}
assert_eq!(found, expected, "n = {n}");
}
}
assert_eq!(path_graph(5).articulation_points(), vec![1, 2, 3]);
assert!(cycle_graph(5).articulation_points().is_empty());
assert_eq!(star_graph(6).articulation_points(), vec![0]);
}
#[test]
fn topological_sort_is_valid_and_lexicographically_least() {
let mut rng = Rng::new(121);
for n in 1..=8usize {
for _ in 0..30 {
let perm = crate::discrete::combinatorics::random_permutation(n, &mut rng);
let mut g = Graph::new(n, true);
for i in 0..n {
for j in 0..n {
if perm[i] < perm[j] && rng.next_f64() < 0.35 {
g.add_edge(i, j, 1.0);
}
}
}
let order = g.topological_sort().expect("a DAG has an order");
assert!(g.is_dag());
let pos: Vec<usize> = {
let mut p = vec![0; n];
for (i, &v) in order.iter().enumerate() {
p[v] = i;
}
p
};
for (u, v, _) in g.edges() {
assert!(pos[u] < pos[v], "arc {u} -> {v} points backwards");
}
if n <= 7 {
let least = crate::discrete::combinatorics::permutations_iter(
&(0..n).collect::<Vec<_>>(),
)
.filter(|p| {
let mut q = vec![0usize; n];
for (i, &v) in p.iter().enumerate() {
q[v] = i;
}
g.edges().iter().all(|&(u, v, _)| q[u] < q[v])
})
.min()
.unwrap();
assert_eq!(order, least, "not the least order");
}
}
}
assert!(Graph::from_edges(3, &[(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)], true)
.topological_sort()
.is_none());
}
#[test]
fn bfs_and_dfs_agree_on_reachability() {
let mut rng = Rng::new(131);
for directed in [false, true] {
for n in 1..=10usize {
let g = random_graph(n, 0.25, directed, &mut rng);
let r = reachable(&g);
for s in 0..n {
let d = g.bfs(s);
let seen: BTreeSet<usize> = g.dfs(s).into_iter().collect();
for v in 0..n {
assert_eq!(d[v].is_some(), r[s][v], "bfs at ({s}, {v})");
assert_eq!(seen.contains(&v), r[s][v], "dfs at ({s}, {v})");
}
assert_eq!(d[s], Some(0));
for v in 0..n {
if let Some(k) = d[v] {
if k > 0 {
let ok = (0..n).any(|u| {
d[u] == Some(k - 1) && g.adj[u].iter().any(|&(t, _)| t == v)
});
assert!(ok, "no predecessor at distance {} for {v}", k - 1);
}
}
}
}
}
}
let p = path_graph(7);
assert_eq!(p.bfs(0), (0..7).map(Some).collect::<Vec<_>>());
}
#[test]
fn eulerian_circuits_use_every_edge_once() {
for g in [cycle_graph(5), complete_graph(5), complete_graph(7)] {
let circuit = g.eulerian_circuit().expect("even degrees give a circuit");
check_euler(&g, &circuit, true);
}
let p = path_graph(5);
assert!(p.eulerian_circuit().is_none());
let walk = p.eulerian_path().expect("a path graph has an Eulerian path");
check_euler(&p, &walk, false);
let k = Graph::from_edges(
4,
&[
(0, 1, 1.0),
(0, 1, 1.0),
(0, 2, 1.0),
(0, 2, 1.0),
(0, 3, 1.0),
(1, 3, 1.0),
(2, 3, 1.0),
],
false,
);
assert!(k.eulerian_circuit().is_none());
assert!(k.eulerian_path().is_none());
assert!(complete_graph(4).eulerian_path().is_none());
let d = Graph::from_edges(
3,
&[(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)],
true,
);
let c = d.eulerian_circuit().expect("balanced degrees give a circuit");
check_euler(&d, &c, true);
}
fn check_euler(g: &Graph, walk: &[usize], closed: bool) {
assert_eq!(walk.len(), g.edge_count() + 1, "wrong walk length");
if closed {
assert_eq!(walk[0], *walk.last().unwrap(), "the walk does not close");
}
let key = |a: usize, b: usize| {
if g.directed {
(a, b)
} else {
(a.min(b), a.max(b))
}
};
let mut remaining: Vec<(usize, usize)> =
g.edges().into_iter().map(|(u, v, _)| key(u, v)).collect();
for w in walk.windows(2) {
let k = key(w[0], w[1]);
let pos = remaining
.iter()
.position(|&e| e == k)
.unwrap_or_else(|| panic!("step {w:?} is not an unused edge"));
remaining.remove(pos);
}
assert!(remaining.is_empty(), "edges left unused: {remaining:?}");
}
#[test]
fn hamiltonian_path_matches_brute_force() {
let mut rng = Rng::new(141);
for n in 1..=7usize {
for _ in 0..25 {
let g = random_graph(n, 0.4, false, &mut rng);
let brute = crate::discrete::combinatorics::permutations_iter(
&(0..n).collect::<Vec<_>>(),
)
.any(|p| {
p.windows(2)
.all(|w| g.adj[w[0]].iter().any(|&(t, _)| t == w[1]))
});
match g.hamiltonian_path_small() {
Some(path) => {
assert!(brute, "found a path brute force says is impossible");
assert_eq!(path.len(), n);
assert!(crate::discrete::combinatorics::is_permutation(&path));
for w in path.windows(2) {
assert!(
g.adj[w[0]].iter().any(|&(t, _)| t == w[1]),
"{} -> {} is not an edge",
w[0],
w[1]
);
}
}
None => assert!(!brute, "missed a path brute force found"),
}
}
}
assert!(petersen_graph().hamiltonian_path_small().is_some());
assert!(star_graph(5).hamiltonian_path_small().is_none());
}
#[test]
fn girth_matches_brute_force() {
let mut rng = Rng::new(151);
for n in 3..=7usize {
for _ in 0..25 {
let g = random_graph(n, 0.4, false, &mut rng);
let brute = brute_girth(&g);
assert_eq!(g.girth(), brute, "n = {n}");
}
}
assert_eq!(cycle_graph(7).girth(), Some(7));
assert_eq!(complete_graph(5).girth(), Some(3));
assert_eq!(petersen_graph().girth(), Some(5));
assert_eq!(complete_bipartite(3, 3).girth(), Some(4));
assert_eq!(path_graph(5).girth(), None);
assert_eq!(hypercube_graph(3).girth(), Some(4));
}
fn brute_girth(g: &Graph) -> Option<usize> {
let n = g.n;
let adj = |a: usize, b: usize| g.adj[a].iter().any(|&(t, _)| t == b);
let mut best = None;
for len in 3..=n {
for combo in crate::discrete::combinatorics::combinations_iter(n, len) {
for perm in crate::discrete::combinatorics::permutations_iter(&combo) {
let ok = (0..len).all(|i| adj(perm[i], perm[(i + 1) % len]));
if ok {
best = Some(best.map_or(len, |b: usize| b.min(len)));
}
}
}
if best.is_some() {
return best;
}
}
best
}
#[test]
fn radius_diameter_and_center_are_consistent() {
for g in [
path_graph(7),
cycle_graph(8),
complete_graph(6),
star_graph(7),
petersen_graph(),
grid_2d(4, 3),
hypercube_graph(3),
] {
let ecc = g.eccentricities();
let r = g.radius().unwrap();
let d = g.diameter().unwrap();
assert_eq!(r, ecc.iter().flatten().copied().min().unwrap());
assert_eq!(d, ecc.iter().flatten().copied().max().unwrap());
assert!(r <= d && d <= 2 * r, "r = {r}, d = {d}");
let c = g.center();
assert!(!c.is_empty());
for &v in &c {
assert_eq!(ecc[v], Some(r));
}
assert_eq!(c.len(), (0..g.n).filter(|&v| ecc[v] == Some(r)).count());
}
assert_eq!(path_graph(7).diameter(), Some(6));
assert_eq!(path_graph(7).radius(), Some(3));
assert_eq!(path_graph(7).center(), vec![3]);
assert_eq!(star_graph(7).diameter(), Some(2));
assert_eq!(star_graph(7).center(), vec![0]);
assert_eq!(complete_graph(6).diameter(), Some(1));
assert_eq!(petersen_graph().diameter(), Some(2));
assert_eq!(hypercube_graph(4).diameter(), Some(4));
assert_eq!(Graph::new(3, false).diameter(), None);
}
#[test]
fn clustering_matches_closed_forms() {
for n in 3..=7usize {
let k = complete_graph(n);
for v in 0..n {
assert!((k.clustering_coefficient(v) - 1.0).abs() < 1e-12);
}
assert!((k.average_clustering() - 1.0).abs() < 1e-12);
assert!((k.transitivity() - 1.0).abs() < 1e-12);
}
for g in [cycle_graph(6), complete_bipartite(3, 3), petersen_graph()] {
assert_eq!(g.average_clustering(), 0.0);
assert_eq!(g.transitivity(), 0.0);
}
let mut g = Graph::new(7, false);
for v in 1..7 {
g.add_edge(0, v, 1.0);
}
g.add_edge(1, 2, 1.0);
assert!((g.clustering_coefficient(1) - 1.0).abs() < 1e-12);
assert!((g.clustering_coefficient(0) - 1.0 / 15.0).abs() < 1e-12);
assert!(
g.average_clustering() > g.transitivity(),
"the two measures should differ here"
);
}
#[test]
fn degree_distribution_and_density_are_consistent() {
let mut rng = Rng::new(161);
for n in 2..=10usize {
let g = random_graph(n, 0.4, false, &mut rng);
let dist = g.degree_distribution();
assert_eq!(dist.iter().sum::<usize>(), n);
for (d, &count) in dist.iter().enumerate() {
assert_eq!(count, (0..n).filter(|&v| g.degree(v) == d).count());
}
let want = 2.0 * g.edge_count() as f64 / (n * (n - 1)) as f64;
assert!((g.density() - want).abs() < 1e-12, "n = {n}");
}
assert!((complete_graph(6).density() - 1.0).abs() < 1e-12);
assert_eq!(Graph::new(6, false).density(), 0.0);
}
#[test]
fn k_core_satisfies_its_definition() {
let mut rng = Rng::new(171);
for n in 1..=10usize {
for _ in 0..20 {
let g = random_graph(n, 0.35, false, &mut rng);
let core = g.core_numbers();
for k in 0..=n {
let inside = g.k_core(k);
for &v in &inside {
let d = g.adj[v]
.iter()
.filter(|&&(t, _)| t != v && inside.contains(&t))
.count();
assert!(d >= k, "vertex {v} has only {d} neighbours in the {k}-core");
}
assert_eq!(inside, peel(&g, k), "k = {k}, n = {n}");
}
for v in 0..n {
assert!(core[v] <= g.degree(v));
}
}
}
assert_eq!(complete_graph(5).k_core(4), vec![0, 1, 2, 3, 4]);
assert!(complete_graph(5).k_core(5).is_empty());
assert_eq!(cycle_graph(6).core_numbers(), vec![2; 6]);
}
fn peel(g: &Graph, k: usize) -> Vec<usize> {
let mut alive: Vec<bool> = vec![true; g.n];
loop {
let mut removed = false;
for v in 0..g.n {
if !alive[v] {
continue;
}
let d = g.adj[v]
.iter()
.filter(|&&(t, _)| t != v && alive[t])
.count();
if d < k {
alive[v] = false;
removed = true;
}
}
if !removed {
break;
}
}
(0..g.n).filter(|&v| alive[v]).collect()
}
#[test]
fn assortativity_is_a_bounded_correlation() {
let mut rng = Rng::new(181);
for n in 2..=10usize {
let g = random_graph(n, 0.4, false, &mut rng);
let a = g.assortativity();
assert!((-1.0..=1.0).contains(&a) || a == 0.0, "n = {n} gave {a}");
}
assert!((star_graph(8).assortativity() + 1.0).abs() < 1e-9);
assert_eq!(cycle_graph(6).assortativity(), 0.0);
assert_eq!(complete_graph(5).assortativity(), 0.0);
}
#[test]
fn named_graphs_have_their_defining_properties() {
for n in 1..=8usize {
let k = complete_graph(n);
assert_eq!(k.edge_count(), n * (n - 1) / 2);
assert!((0..n).all(|v| k.degree(v) == n - 1));
}
for n in 3..=9usize {
let c = cycle_graph(n);
assert_eq!(c.edge_count(), n);
assert!((0..n).all(|v| c.degree(v) == 2));
assert!(c.is_connected());
assert!(!c.is_tree());
}
for n in 1..=9usize {
let p = path_graph(n);
assert_eq!(p.edge_count(), n.saturating_sub(1));
assert!(p.is_tree());
let s = star_graph(n);
assert!(s.is_tree());
assert_eq!(s.degree(0), n.saturating_sub(1));
}
for n in 4..=9usize {
let w = wheel_graph(n);
assert_eq!(w.edge_count(), 2 * (n - 1));
assert_eq!(w.degree(0), n - 1);
assert!((1..n).all(|v| w.degree(v) == 3));
}
let grid = grid_2d(4, 3);
assert_eq!(grid.n, 12);
assert_eq!(grid.edge_count(), 3 * 3 + 4 * 2);
assert!(grid.is_bipartite().is_some());
for d in 0..=5u32 {
let h = hypercube_graph(d);
assert_eq!(h.n, 1 << d);
assert!((0..h.n).all(|v| h.degree(v) == d as usize));
assert_eq!(h.edge_count(), (d as usize) * (1 << d) / 2);
assert!(h.is_bipartite().is_some());
}
let p = petersen_graph();
assert_eq!(p.n, 10);
assert_eq!(p.edge_count(), 15);
assert!((0..10).all(|v| p.degree(v) == 3), "not 3-regular");
assert_eq!(p.girth(), Some(5));
assert_eq!(p.diameter(), Some(2));
assert!(p.is_connected());
for m in 1..=5usize {
for n in 1..=5usize {
let b = complete_bipartite(m, n);
assert_eq!(b.edge_count(), m * n);
let color = b.is_bipartite().expect("bipartite by construction");
assert!((0..m).all(|v| color[v] == color[0]));
}
}
}
#[test]
fn random_generators_respect_their_parameters() {
let mut rng = Rng::new(191);
assert_eq!(erdos_renyi(8, 0.0, &mut rng).edge_count(), 0);
assert_eq!(erdos_renyi(8, 1.0, &mut rng).edge_count(), 28);
let mut total = 0usize;
for _ in 0..200 {
total += erdos_renyi(20, 0.3, &mut rng).edge_count();
}
let mean = total as f64 / 200.0;
let expected = 0.3 * 190.0;
assert!((mean - expected).abs() < 0.1 * expected, "mean {mean} vs {expected}");
for m in 1..=3usize {
let g = barabasi_albert(30, m, &mut rng);
assert_eq!(g.n, 30);
assert_eq!(g.edge_count(), m * (m - 1) / 2 + m * (30 - m));
assert!(g.is_connected());
}
let ring = watts_strogatz(20, 4, 0.0, &mut rng);
assert_eq!(ring.edge_count(), 40);
assert!((0..20).all(|v| ring.degree(v) == 4));
let rewired = watts_strogatz(20, 4, 0.5, &mut rng);
assert_eq!(rewired.edge_count(), 40);
for (n, d) in [(10usize, 3usize), (12, 4), (9, 4), (20, 5)] {
let g = random_regular(n, d, &mut rng).expect("a d-regular graph exists");
assert!((0..n).all(|v| g.degree(v) == d), "n = {n}, d = {d}");
let e: BTreeSet<(usize, usize)> = g
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect();
assert_eq!(e.len(), g.edge_count());
assert!(e.iter().all(|&(u, v)| u != v));
}
assert!(random_regular(5, 3, &mut rng).is_none());
let (g, pts) = random_geometric(30, 0.3, &mut rng);
for u in 0..30 {
for v in u + 1..30 {
let (dx, dy) = (pts[u].0 - pts[v].0, pts[u].1 - pts[v].1);
let near = (dx * dx + dy * dy).sqrt() <= 0.3;
let joined = g.adj[u].iter().any(|&(t, _)| t == v);
assert_eq!(near, joined, "({u}, {v})");
}
}
let p = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let sbm = stochastic_block_model(&[5, 5], &p, &mut rng);
assert_eq!(sbm.connected_components().len(), 2);
assert_eq!(sbm.edge_count(), 10 + 10);
}
#[test]
fn line_graph_has_the_expected_size() {
let mut rng = Rng::new(201);
for n in 2..=8usize {
let g = random_graph(n, 0.4, false, &mut rng);
let (l, edges) = line_graph(&g);
assert_eq!(l.n, g.edge_count());
assert_eq!(edges.len(), g.edge_count());
let want: usize = (0..n).map(|v| g.degree(v) * g.degree(v).saturating_sub(1) / 2).sum();
assert_eq!(l.edge_count(), want, "n = {n}");
}
for n in 3..=7usize {
let (l, _) = line_graph(&cycle_graph(n));
assert!(is_isomorphic_small(&l, &cycle_graph(n)), "n = {n}");
}
let (l, _) = line_graph(&complete_graph(3));
assert!(is_isomorphic_small(&l, &complete_graph(3)));
}
#[test]
fn products_have_the_expected_size() {
let a = path_graph(3);
let b = path_graph(4);
let c = cartesian_product(&a, &b);
assert_eq!(c.n, 12);
assert_eq!(c.edge_count(), 3 * 3 + 4 * 2);
let small = cartesian_product(&path_graph(2), &path_graph(3));
assert!(is_isomorphic_small(&small, &grid_2d(3, 2)));
let mut dc: Vec<usize> = (0..12).map(|v| c.degree(v)).collect();
let g43 = grid_2d(4, 3);
let mut dg: Vec<usize> = (0..12).map(|v| g43.degree(v)).collect();
dc.sort_unstable();
dg.sort_unstable();
assert_eq!(dc, dg);
let q3 = cartesian_product(&hypercube_graph(2), &complete_graph(2));
assert!(is_isomorphic_small(&q3, &hypercube_graph(3)));
let t = tensor_product(&complete_graph(2), &complete_graph(2));
assert_eq!(t.n, 4);
assert_eq!(t.edge_count(), 2);
assert_eq!(t.connected_components().len(), 2);
}
#[test]
fn isomorphism_is_relabelling_invariant() {
let mut rng = Rng::new(211);
for n in 1..=7usize {
for _ in 0..20 {
let g = random_graph(n, 0.4, false, &mut rng);
let perm = crate::discrete::combinatorics::random_permutation(n, &mut rng);
let mut h = Graph::new(n, false);
for (u, v, w) in g.edges() {
h.add_edge(perm[u], perm[v], w);
}
assert!(is_isomorphic_small(&g, &h), "relabelling broke isomorphism");
assert_eq!(canonical_form_small(&g), canonical_form_small(&h));
}
}
let mut two_triangles = Graph::new(6, false);
for (u, v) in [(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3)] {
two_triangles.add_edge(u, v, 1.0);
}
let c6 = cycle_graph(6);
let mut d1: Vec<usize> = (0..6).map(|v| two_triangles.degree(v)).collect();
let mut d2: Vec<usize> = (0..6).map(|v| c6.degree(v)).collect();
d1.sort_unstable();
d2.sort_unstable();
assert_eq!(d1, d2, "the degree sequences must agree for this to be a test");
assert!(!is_isomorphic_small(&two_triangles, &c6));
assert!(!is_isomorphic_small(&complete_graph(4), &complete_graph(5)));
}
#[test]
fn graph6_round_trips() {
let mut rng = Rng::new(221);
for n in 1..=10usize {
for _ in 0..20 {
let g = random_graph(n, 0.4, false, &mut rng);
let s = graph6_encode(&g);
let back = graph6_decode(&s);
assert_eq!(back.n, g.n);
let ge: BTreeSet<(usize, usize)> = g
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect();
let be: BTreeSet<(usize, usize)> = back
.edges()
.into_iter()
.map(|(u, v, _)| (u.min(v), u.max(v)))
.collect();
assert_eq!(ge, be, "round trip failed for {s}");
assert!(s.bytes().all(|b| (63..=126).contains(&b)), "not printable");
}
}
assert_eq!(graph6_encode(&complete_graph(5)), "D~{");
assert_eq!(graph6_encode(&Graph::new(5, false)), "D??");
assert_eq!(graph6_decode("D~{").edge_count(), 10);
}
#[test]
fn matrix_tree_theorem_gives_cayleys_formula() {
for n in 1..=12u64 {
let want = if n <= 2 {
BigInt::one()
} else {
BigInt::from_u64(n).pow(n - 2)
};
assert_eq!(
spanning_tree_count_exact(&complete_graph(n as usize)),
want,
"Cayley fails at n = {n}"
);
}
assert_eq!(
spanning_tree_count_exact(&complete_graph(12)).to_string(),
"61917364224"
);
for n in 3..=8usize {
assert_eq!(spanning_tree_count_exact(&path_graph(n)), BigInt::one());
assert_eq!(
spanning_tree_count_exact(&cycle_graph(n)),
BigInt::from_u64(n as u64)
);
}
for m in 1..=4u64 {
for n in 1..=4u64 {
let want = BigInt::from_u64(m)
.pow(n - 1)
.mul(&BigInt::from_u64(n).pow(m - 1));
assert_eq!(
spanning_tree_count_exact(&complete_bipartite(m as usize, n as usize)),
want,
"K_{{{m},{n}}}"
);
}
}
assert_eq!(
spanning_tree_count_exact(&petersen_graph()),
BigInt::from_u64(2000)
);
assert_eq!(spanning_tree_count_exact(&Graph::new(3, false)), BigInt::zero());
}
}