#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::needless_range_loop,
clippy::too_many_lines
)]
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DominatorMode {
Out,
In,
}
#[derive(Clone, Debug)]
pub struct DominatorTree {
pub idom: Vec<i32>,
pub tree: Graph,
pub leftout: Vec<VertexId>,
}
pub fn dominator_tree(
graph: &Graph,
root: VertexId,
mode: DominatorMode,
) -> IgraphResult<DominatorTree> {
let n = graph.vcount();
if !graph.is_directed() {
return Err(IgraphError::InvalidArgument(
"dominator tree of an undirected graph requested".to_string(),
));
}
if root >= n {
return Err(IgraphError::VertexOutOfRange { id: root, n });
}
if i32::try_from(n).is_err() {
return Err(IgraphError::InvalidArgument(format!(
"dominator_tree: vcount {n} exceeds i32::MAX"
)));
}
let n_us = n as usize;
let mut succ: Vec<Vec<VertexId>> = Vec::with_capacity(n_us);
let mut pred: Vec<Vec<VertexId>> = Vec::with_capacity(n_us);
for v in 0..n {
let (s, p) = match mode {
DominatorMode::Out => (graph.out_neighbors_vec(v)?, graph.in_neighbors_vec(v)?),
DominatorMode::In => (graph.in_neighbors_vec(v)?, graph.out_neighbors_vec(v)?),
};
succ.push(s);
pred.push(p);
}
let mut parent: Vec<i32> = vec![-2; n_us];
let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
let mut visited = vec![false; n_us];
parent[root as usize] = -1;
visited[root as usize] = true;
order.push(root);
let mut stack: Vec<(VertexId, usize)> = Vec::with_capacity(n_us);
stack.push((root, 0));
while let Some(&(v, cursor)) = stack.last() {
let mut next_cursor = cursor;
let neis = &succ[v as usize];
let mut found: Option<VertexId> = None;
while next_cursor < neis.len() {
let u = neis[next_cursor];
next_cursor += 1;
if !visited[u as usize] {
found = Some(u);
break;
}
}
let top_idx = stack.len() - 1;
if let Some(u) = found {
stack[top_idx].1 = next_cursor;
visited[u as usize] = true;
parent[u as usize] = v as i32;
order.push(u);
stack.push((u, 0));
} else {
stack.pop();
}
}
let component_size = order.len();
let mut semi: Vec<i32> = vec![-1; n_us];
let mut vertex: Vec<i32> = vec![-1; component_size];
for (i, &v) in order.iter().enumerate() {
semi[v as usize] = i as i32;
vertex[i] = v as i32;
}
let mut leftout: Vec<VertexId> = Vec::with_capacity(n_us - component_size);
for v in 0..n {
if parent[v as usize] == -2 {
leftout.push(v);
}
}
for plist in pred.iter_mut().take(n_us) {
plist.retain(|&u| parent[u as usize] >= -1);
}
let mut ancestor: Vec<i32> = vec![-1; n_us];
let mut label: Vec<i32> = (0..n_us).map(|i| i as i32).collect();
let mut bucket_head: Vec<i32> = vec![-1; n_us];
let mut bucket_next: Vec<i32> = vec![-1; n_us];
let mut idom: Vec<i32> = vec![-2; n_us];
for i in (1..component_size).rev() {
let w_i32 = vertex[i];
let w_us = w_i32 as usize;
let preds_len = pred[w_us].len();
for j in 0..preds_len {
let v_pred = pred[w_us][j] as i32;
let u = dom_eval(v_pred, &mut ancestor, &mut label, &semi);
let su = semi[u as usize];
let sw = semi[w_us];
if su >= 0 && (sw < 0 || su < sw) {
semi[w_us] = su;
}
}
let semi_w = semi[w_us];
let bucket_owner = vertex[semi_w as usize] as usize;
bucket_next[w_us] = bucket_head[bucket_owner];
bucket_head[bucket_owner] = w_i32;
let pw = parent[w_us];
ancestor[w_us] = pw;
let pw_us = pw as usize;
while bucket_head[pw_us] != -1 {
let v_b = bucket_head[pw_us];
bucket_head[pw_us] = bucket_next[v_b as usize];
let u = dom_eval(v_b, &mut ancestor, &mut label, &semi);
if semi[u as usize] < semi[v_b as usize] {
idom[v_b as usize] = u;
} else {
idom[v_b as usize] = pw;
}
}
}
for i in 1..component_size {
let w_us = vertex[i] as usize;
let chk = vertex[semi[w_us] as usize];
if idom[w_us] != chk {
idom[w_us] = idom[idom[w_us] as usize];
}
}
idom[root as usize] = -1;
let mut tree = Graph::new(n, true)?;
for w in 0..n {
if w == root {
continue;
}
let d = idom[w as usize];
if d < 0 {
continue;
}
match mode {
DominatorMode::Out => tree.add_edge(d as VertexId, w)?,
DominatorMode::In => tree.add_edge(w, d as VertexId)?,
}
}
Ok(DominatorTree {
idom,
tree,
leftout,
})
}
fn dom_eval(v: i32, ancestor: &mut [i32], label: &mut [i32], semi: &[i32]) -> i32 {
if ancestor[v as usize] == -1 {
v
} else {
dom_compress(v, ancestor, label, semi);
label[v as usize]
}
}
fn dom_compress(v: i32, ancestor: &mut [i32], label: &mut [i32], semi: &[i32]) {
let mut path: Vec<i32> = Vec::new();
let mut w = v;
while ancestor[w as usize] != -1 {
path.push(w);
w = ancestor[w as usize];
}
let mut iter = path.into_iter().rev();
let Some(mut top) = iter.next() else {
return;
};
for pretop in iter {
if semi[label[top as usize] as usize] < semi[label[pretop as usize] as usize] {
label[pretop as usize] = label[top as usize];
}
ancestor[pretop as usize] = ancestor[top as usize];
top = pretop;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_undirected_graph() {
let g = Graph::with_vertices(2);
let err = dominator_tree(&g, 0, DominatorMode::Out).expect_err("must reject undirected");
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn rejects_out_of_range_root() {
let g = Graph::new(3, true).expect("directed graph");
let err = dominator_tree(&g, 5, DominatorMode::Out).expect_err("must reject bad root");
match err {
IgraphError::VertexOutOfRange { id, n } => {
assert_eq!(id, 5);
assert_eq!(n, 3);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn root_alone_in_directed_graph() {
let mut g = Graph::new(3, true).expect("directed graph");
g.add_edge(1, 2).expect("add edge");
let dt = dominator_tree(&g, 0, DominatorMode::Out).expect("compute");
assert_eq!(dt.idom, vec![-1, -2, -2]);
assert_eq!(dt.leftout, vec![1, 2]);
assert_eq!(dt.tree.ecount(), 0);
assert_eq!(dt.tree.vcount(), 3);
}
#[test]
fn lengauer_tarjan_classical_13v() {
let edges = [
(0u32, 1u32),
(0, 2),
(0, 3),
(1, 4),
(2, 1),
(2, 4),
(2, 5),
(3, 6),
(3, 7),
(4, 12),
(5, 8),
(6, 9),
(7, 9),
(7, 10),
(8, 5),
(8, 11),
(9, 11),
(10, 9),
(11, 0),
(11, 9),
(12, 8),
];
let mut g = Graph::new(13, true).expect("directed graph");
for (u, v) in edges {
g.add_edge(u, v).expect("add edge");
}
let dt = dominator_tree(&g, 0, DominatorMode::Out).expect("compute");
assert_eq!(dt.idom, vec![-1, 0, 0, 0, 0, 0, 3, 3, 0, 0, 7, 0, 4]);
assert!(dt.leftout.is_empty());
assert_eq!(dt.tree.ecount(), 12);
}
#[test]
fn lengauer_tarjan_in_mode_matches_reverse_out_mode() {
let edges = [
(0u32, 1u32),
(0, 2),
(0, 3),
(1, 4),
(2, 1),
(2, 4),
(2, 5),
(3, 6),
(3, 7),
(4, 12),
(5, 8),
(6, 9),
(7, 9),
(7, 10),
(8, 5),
(8, 11),
(9, 11),
(10, 9),
(11, 0),
(11, 9),
(12, 8),
];
let mut g_forward = Graph::new(13, true).expect("forward");
for (u, v) in edges {
g_forward.add_edge(u, v).expect("add edge");
}
let mut g_reverse = Graph::new(13, true).expect("reverse");
for (u, v) in edges {
g_reverse.add_edge(v, u).expect("add reverse edge");
}
let in_mode = dominator_tree(&g_forward, 0, DominatorMode::In).expect("In mode");
let out_mode_reversed =
dominator_tree(&g_reverse, 0, DominatorMode::Out).expect("Out on reversed");
assert_eq!(in_mode.idom, out_mode_reversed.idom);
}
#[test]
fn unreachable_vertices_land_in_leftout() {
let mut g = Graph::new(7, true).expect("directed graph");
g.add_edge(0, 1).expect("e");
g.add_edge(0, 2).expect("e");
g.add_edge(1, 3).expect("e");
g.add_edge(2, 3).expect("e");
g.add_edge(4, 5).expect("e");
g.add_edge(5, 6).expect("e");
g.add_edge(6, 4).expect("e");
let dt = dominator_tree(&g, 0, DominatorMode::Out).expect("compute");
assert_eq!(dt.idom[0], -1);
assert_eq!(dt.idom[1], 0);
assert_eq!(dt.idom[2], 0);
assert_eq!(dt.idom[3], 0);
assert_eq!(dt.idom[4], -2);
assert_eq!(dt.idom[5], -2);
assert_eq!(dt.idom[6], -2);
assert_eq!(dt.leftout, vec![4, 5, 6]);
assert_eq!(dt.tree.ecount(), 3);
}
#[test]
fn idom_lies_on_every_root_to_w_path_brute_force() {
let mut g = Graph::new(6, true).expect("directed graph");
g.add_edge(0, 1).expect("e");
g.add_edge(0, 2).expect("e");
g.add_edge(1, 3).expect("e");
g.add_edge(2, 3).expect("e");
g.add_edge(3, 4).expect("e");
g.add_edge(4, 5).expect("e");
let dt = dominator_tree(&g, 0, DominatorMode::Out).expect("compute");
assert_eq!(dt.idom, vec![-1, 0, 0, 0, 3, 4]);
for w in 1..6u32 {
let d = dt.idom[w as usize];
assert!(d >= 0, "vertex {w} should be reachable");
let d_u = d as u32;
let paths = enumerate_simple_paths(&g, 0, w);
assert!(!paths.is_empty(), "no path 0 -> {w}?");
for p in &paths {
if w != 0 {
assert!(
p.contains(&d_u),
"idom({w}) = {d_u} must appear on every path: {p:?}"
);
}
}
}
}
fn enumerate_simple_paths(g: &Graph, s: VertexId, t: VertexId) -> Vec<Vec<VertexId>> {
let mut out: Vec<Vec<VertexId>> = Vec::new();
let mut stack: Vec<VertexId> = vec![s];
let mut on_stack = vec![false; g.vcount() as usize];
on_stack[s as usize] = true;
dfs_paths(g, s, t, &mut stack, &mut on_stack, &mut out);
out
}
fn dfs_paths(
g: &Graph,
cur: VertexId,
t: VertexId,
stack: &mut Vec<VertexId>,
on_stack: &mut [bool],
out: &mut Vec<Vec<VertexId>>,
) {
if cur == t {
out.push(stack.clone());
return;
}
let neis = g.out_neighbors_vec(cur).expect("out neis");
for u in neis {
if !on_stack[u as usize] {
on_stack[u as usize] = true;
stack.push(u);
dfs_paths(g, u, t, stack, on_stack, out);
stack.pop();
on_stack[u as usize] = false;
}
}
}
}