use std::collections::VecDeque;
use crate::graph::{NodeIndex, SimTopology};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DepthMetric {
LongestPath,
ShortestPath,
}
#[derive(Debug, Clone, Default)]
pub struct Layering {
pub layer: Vec<u32>,
pub slot: Vec<usize>,
pub layers: Vec<Vec<usize>>,
pub children: Vec<Vec<usize>>,
}
pub fn compute_layering(topo: &SimTopology<'_>, explicit_roots: &[NodeIndex]) -> Layering {
let pairs: Vec<(usize, usize)> = topo.edges.iter().map(|e| (e.from.index(), e.to.index())).collect();
let roots: Vec<usize> = explicit_roots.iter().map(|r| r.index()).collect();
let inner = uzor_figures::figure::dag::layering::compute_layering(topo.node_count, &pairs, &roots);
Layering { layer: inner.layer, slot: inner.slot, layers: inner.layers, children: inner.children }
}
pub fn compute_bfs_layering(topo: &SimTopology<'_>, explicit_roots: &[NodeIndex]) -> Layering {
let n = topo.node_count;
let mut depth = vec![0u32; n];
let mut children: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut visited = vec![false; n];
let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); n];
for e in topo.edges {
let a = e.from.index();
let b = e.to.index();
if a < n && b < n && a != b {
adjacency[a].push(b);
}
}
let mut roots: Vec<usize> = explicit_roots.iter().map(|r| r.index()).filter(|&i| i < n).collect();
if roots.is_empty() {
let mut in_degree = vec![0u32; n];
for e in topo.edges {
let a = e.from.index();
let b = e.to.index();
if a < n && b < n && a != b {
in_degree[b] += 1;
}
}
roots = (0..n).filter(|&i| in_degree[i] == 0).collect();
}
if roots.is_empty() && n > 0 {
roots = vec![0];
}
let mut queue: VecDeque<usize> = VecDeque::new();
for &r in &roots {
if !visited[r] {
visited[r] = true;
depth[r] = 0;
queue.push_back(r);
}
}
bfs_layering_fill(&adjacency, &mut visited, &mut depth, &mut children, &mut queue);
for i in 0..n {
if !visited[i] {
visited[i] = true;
depth[i] = 0;
queue.push_back(i);
bfs_layering_fill(&adjacency, &mut visited, &mut depth, &mut children, &mut queue);
}
}
let max_layer = depth.iter().copied().max().unwrap_or(0);
let mut layers: Vec<Vec<usize>> = vec![Vec::new(); if n == 0 { 0 } else { max_layer as usize + 1 }];
for i in 0..n {
layers[depth[i] as usize].push(i);
}
let mut slot = vec![0usize; n];
for layer in &layers {
for (s, &node) in layer.iter().enumerate() {
slot[node] = s;
}
}
Layering { layer: depth, slot, layers, children }
}
fn bfs_layering_fill(adjacency: &[Vec<usize>], visited: &mut [bool], depth: &mut [u32], children: &mut [Vec<usize>], queue: &mut VecDeque<usize>) {
while let Some(u) = queue.pop_front() {
for &v in &adjacency[u] {
if !visited[v] {
visited[v] = true;
depth[v] = depth[u] + 1;
children[u].push(v);
queue.push_back(v);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::SimEdge;
fn topo(node_count: usize, edges: &[SimEdge]) -> SimTopology<'_> {
SimTopology { node_count, edges, degree: &[], radii: vec![1.0; node_count] }
}
fn e(from: u32, to: u32) -> SimEdge {
SimEdge { from: NodeIndex(from), to: NodeIndex(to), weight: 1.0 }
}
#[test]
fn chain_layers_increase_by_one_each_hop() {
let edges = [e(0, 1), e(1, 2), e(2, 3)];
let t = topo(4, &edges);
let layering = compute_layering(&t, &[]);
assert_eq!(layering.layer, vec![0, 1, 2, 3]);
}
#[test]
fn diamond_layer_is_one_plus_max_of_parent_layers() {
let edges = [e(0, 1), e(0, 2), e(1, 3), e(2, 3)];
let t = topo(4, &edges);
let layering = compute_layering(&t, &[]);
assert_eq!(layering.layer[0], 0);
assert_eq!(layering.layer[1], 1);
assert_eq!(layering.layer[2], 1);
assert_eq!(layering.layer[3], 1 + layering.layer[1].max(layering.layer[2]));
}
#[test]
fn multi_depth_parents_take_the_longest_path() {
let edges = [e(0, 1), e(2, 3), e(3, 4), e(1, 5), e(4, 5)];
let t = topo(6, &edges);
let layering = compute_layering(&t, &[]);
assert_eq!(layering.layer[0], 0); assert_eq!(layering.layer[2], 0); assert_eq!(layering.layer[1], 1); assert_eq!(layering.layer[3], 1); assert_eq!(layering.layer[4], 2); assert_eq!(layering.layer[5], 1 + layering.layer[1].max(layering.layer[4])); assert_eq!(layering.layer[5], 3);
}
#[test]
fn bfs_layering_takes_the_shortest_path_on_the_same_multi_parent_fixture() {
let edges = [e(0, 1), e(2, 3), e(3, 4), e(1, 5), e(4, 5)];
let t = topo(6, &edges);
let bfs = compute_bfs_layering(&t, &[]);
assert_eq!(bfs.layer[0], 0); assert_eq!(bfs.layer[2], 0); assert_eq!(bfs.layer[1], 1); assert_eq!(bfs.layer[3], 1); assert_eq!(bfs.layer[4], 2); assert_eq!(bfs.layer[5], 2, "D must take the SHORTEST path (via C, depth 1) — 2, not Kahn's longest-path 3");
let kahn = compute_layering(&t, &[]);
assert_ne!(bfs.layer[5], kahn.layer[5], "the two metrics must genuinely disagree on this fixture — that's the whole point of DepthMetric existing");
}
#[test]
fn bfs_layering_is_deterministic_and_disconnected_components_each_get_their_own_root() {
let edges = [e(0, 1)];
let t = topo(3, &edges);
let first = compute_bfs_layering(&t, &[]);
let second = compute_bfs_layering(&t, &[]);
assert_eq!(first.layer, second.layer);
assert_eq!(first.layer[0], 0);
assert_eq!(first.layer[1], 1);
assert_eq!(first.layer[2], 0, "node 2 has no incoming edge and no path from node 0 — it must become its own secondary root, not stay unassigned");
}
#[test]
fn cycle_is_broken_by_a_back_edge_and_layers_cleanly() {
let edges = [e(0, 1), e(1, 2), e(2, 0)];
let t = topo(3, &edges);
let layering = compute_layering(&t, &[]);
assert_eq!(layering.layer, vec![0, 1, 2]);
assert_eq!(layering.layers, vec![vec![0], vec![1], vec![2]]);
}
#[test]
fn layering_is_deterministic_across_repeated_calls() {
let edges = [e(0, 1), e(0, 2), e(1, 3), e(2, 3), e(1, 4), e(4, 3)];
let t = topo(5, &edges);
let first = compute_layering(&t, &[]);
let second = compute_layering(&t, &[]);
assert_eq!(first.layer, second.layer);
assert_eq!(first.slot, second.slot);
assert_eq!(first.layers, second.layers);
}
#[test]
fn explicit_roots_override_auto_detection() {
let edges = [e(0, 2), e(1, 2)];
let t = topo(3, &edges);
let layering = compute_layering(&t, &[NodeIndex(0)]);
assert_eq!(layering.layer[0], 0);
assert_eq!(layering.layer[1], 0);
assert_eq!(layering.layer[2], 1);
}
#[test]
fn empty_topology_produces_empty_layering() {
let t = topo(0, &[]);
let layering = compute_layering(&t, &[]);
assert!(layering.layer.is_empty());
assert!(layering.layers.is_empty());
}
}