use rust_igraph::{DominatorMode, Graph, dominator_tree};
fn print_result(label: &str, n: u32, idom: &[i32], tree: &Graph, leftout: &[u32]) {
println!("{label}");
print!(" idom = [");
for (i, &d) in idom.iter().enumerate() {
if i > 0 {
print!(", ");
}
if d == -1 {
print!("root");
} else if d == -2 {
print!(" --");
} else {
print!("{d:>3}");
}
}
println!("]");
let m = u32::try_from(tree.ecount()).expect("ecount fits u32");
println!(" tree edges ({m} total):");
for eid in 0..m {
let (u, v) = tree.edge(eid).expect("tree edge");
println!(" edge {eid:>2}: {u} -> {v}");
}
println!(
" leftout = {:?} (reachable from root: {})",
leftout,
n as usize - leftout.len()
);
}
fn verify_dominance(label: &str, g: &Graph, root: u32, idom: &[i32]) {
let n = g.vcount();
let neighbors: Vec<Vec<u32>> = (0..n)
.map(|v| g.neighbors(v).expect("vertex in range"))
.collect();
let mut mismatches = 0usize;
for w in 0..n {
if w == root || idom[w as usize] < 0 {
continue;
}
let target = u32::try_from(idom[w as usize]).expect("idom in range");
let mut on_stack = vec![false; n as usize];
let mut path = Vec::new();
dfs_paths(
root,
w,
target,
&neighbors,
&mut on_stack,
&mut path,
&mut mismatches,
);
}
println!(" {label}: {mismatches} mismatch(es) over all (path, w) checks");
}
fn dfs_paths(
cur: u32,
target_w: u32,
must_visit: u32,
nbrs: &[Vec<u32>],
on_stack: &mut [bool],
path: &mut Vec<u32>,
mismatches: &mut usize,
) {
on_stack[cur as usize] = true;
path.push(cur);
if cur == target_w {
if !path[..path.len() - 1].contains(&must_visit) {
println!(
" MISMATCH: path {path:?} reaches {target_w} without going through idom={must_visit}"
);
*mismatches += 1;
}
} else {
for &next in &nbrs[cur as usize] {
if !on_stack[next as usize] {
dfs_paths(next, target_w, must_visit, nbrs, on_stack, path, mismatches);
}
}
}
path.pop();
on_stack[cur as usize] = false;
}
#[allow(clippy::too_many_lines)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut g13 = Graph::new(13, true)?;
let edges13 = [
(0u32, 1u32),
(0, 7),
(0, 10),
(1, 2),
(1, 5),
(2, 3),
(3, 4),
(4, 3),
(4, 0),
(5, 3),
(5, 6),
(6, 3),
(7, 8),
(7, 10),
(7, 11),
(8, 9),
(9, 4),
(9, 8),
(10, 11),
(11, 12),
(12, 9),
];
for (u, v) in edges13 {
g13.add_edge(u, v)?;
}
let dt = dominator_tree(&g13, 0, DominatorMode::Out)?;
print_result(
"1) 13-vertex classical LT example (igraph C reference)",
g13.vcount(),
&dt.idom,
&dt.tree,
&dt.leftout,
);
verify_dominance("brute-force path check", &g13, 0, &dt.idom);
println!();
let mut tree6 = Graph::new(6, true)?;
for (u, v) in [(0u32, 1u32), (1, 2), (2, 3), (1, 4), (4, 5)] {
tree6.add_edge(u, v)?;
}
let dt = dominator_tree(&tree6, 0, DominatorMode::Out)?;
print_result(
"2) 6-vertex DAG (R-igraph test-flow.R)",
tree6.vcount(),
&dt.idom,
&dt.tree,
&dt.leftout,
);
verify_dominance("brute-force path check", &tree6, 0, &dt.idom);
println!();
let mut g20 = Graph::new(20, true)?;
let edges20 = [
(0u32, 1u32),
(0, 2),
(0, 3),
(1, 4),
(2, 1),
(2, 4),
(2, 8),
(3, 9),
(3, 10),
(4, 15),
(8, 11),
(9, 12),
(10, 12),
(10, 13),
(11, 8),
(11, 14),
(12, 14),
(13, 12),
(14, 12),
(14, 0),
(15, 11),
];
for (u, v) in edges20 {
g20.add_edge(u, v)?;
}
let dt = dominator_tree(&g20, 0, DominatorMode::Out)?;
print_result(
"3) 20-vertex flowgraph with unreachable component (mode=Out)",
g20.vcount(),
&dt.idom,
&dt.tree,
&dt.leftout,
);
verify_dominance("brute-force path check", &g20, 0, &dt.idom);
println!();
let mut g13_rev = Graph::new(13, true)?;
let edges_rev = [
(1u32, 0u32),
(2, 0),
(3, 0),
(4, 1),
(1, 2),
(4, 2),
(5, 2),
(6, 3),
(7, 3),
(12, 4),
(8, 5),
(9, 6),
(9, 7),
(10, 7),
(5, 8),
(11, 8),
(11, 9),
(9, 10),
(9, 11),
(0, 11),
(8, 12),
];
for (u, v) in edges_rev {
g13_rev.add_edge(u, v)?;
}
let dt = dominator_tree(&g13_rev, 0, DominatorMode::In)?;
print_result(
"4) 13-vertex reversed flowgraph (mode=In post-dominator)",
g13_rev.vcount(),
&dt.idom,
&dt.tree,
&dt.leftout,
);
println!();
println!("All four cases compute their dominator tree in O(|V|+|E|·α) time.");
Ok(())
}