#![allow(clippy::too_many_arguments)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ServiceEdge {
pub parent: u16,
pub child: u16,
}
const MAX_NODES: usize = 256;
pub fn infer_service_graph_from_observed(
observed: &[ServiceEdge],
num_services: usize,
out_edges: &mut [(u16, u16)],
) -> usize {
if num_services == 0 || num_services > MAX_NODES {
return 0;
}
let mut seen = [[false; MAX_NODES]; MAX_NODES];
let mut count: usize = 0;
let mut i = 0;
while i < observed.len() {
let e = observed[i];
let p = e.parent as usize;
let c = e.child as usize;
if p < num_services && c < num_services && p != c && !seen[p][c] {
seen[p][c] = true;
if count < out_edges.len() {
out_edges[count] = (e.parent, e.child);
count += 1;
}
}
i += 1;
}
canonical_sort(&mut out_edges[..count]);
count
}
fn canonical_sort(edges: &mut [(u16, u16)]) {
let n = edges.len();
let mut i = 1;
while i < n {
let cur = edges[i];
let mut j = i;
while j > 0 && (edges[j - 1].0 > cur.0
|| (edges[j - 1].0 == cur.0 && edges[j - 1].1 > cur.1))
{
edges[j] = edges[j - 1];
j -= 1;
}
edges[j] = cur;
i += 1;
}
}
pub fn tarjan_scc(
edges: &[(u16, u16)],
num_services: usize,
scc_id_out: &mut [u16],
) -> usize {
if num_services == 0 || num_services > MAX_NODES || scc_id_out.len() < num_services {
return 0;
}
let mut adj_start = [0_u16; MAX_NODES + 1];
let mut adj_targets = [0_u16; MAX_NODES * MAX_NODES];
let mut out_count = [0_u16; MAX_NODES];
let mut e = 0;
while e < edges.len() {
let (p, _c) = edges[e];
if (p as usize) < num_services {
out_count[p as usize] += 1;
}
e += 1;
}
let mut acc = 0_u16;
let mut k = 0;
while k <= num_services {
adj_start[k] = acc;
if k < num_services {
acc = acc.saturating_add(out_count[k]);
}
k += 1;
}
let mut filled = [0_u16; MAX_NODES];
e = 0;
while e < edges.len() {
let (p, c) = edges[e];
if (p as usize) < num_services && (c as usize) < num_services {
let pos = adj_start[p as usize] + filled[p as usize];
if (pos as usize) < adj_targets.len() {
adj_targets[pos as usize] = c;
filled[p as usize] += 1;
}
}
e += 1;
}
let mut index = 0_u32;
let mut stack = [0_u16; MAX_NODES];
let mut on_stack = [false; MAX_NODES];
let mut top = 0_usize;
let mut node_index = [u32::MAX; MAX_NODES];
let mut node_lowlink = [0_u32; MAX_NODES];
let mut scc_count = 0_usize;
let mut dfs_stack = [(0_u16, 0_u16); MAX_NODES];
let mut dfs_top = 0_usize;
for v0 in 0..num_services {
if node_index[v0] != u32::MAX {
continue;
}
dfs_stack[dfs_top] = (v0 as u16, 0);
dfs_top += 1;
node_index[v0] = index;
node_lowlink[v0] = index;
index += 1;
stack[top] = v0 as u16;
on_stack[v0] = true;
top += 1;
while dfs_top > 0 {
let (v, mut iter_pos) = dfs_stack[dfs_top - 1];
let start = adj_start[v as usize];
let end = adj_start[v as usize + 1];
let mut advanced = false;
while (start + iter_pos) < end {
let w = adj_targets[(start + iter_pos) as usize];
iter_pos += 1;
if node_index[w as usize] == u32::MAX {
dfs_stack[dfs_top - 1].1 = iter_pos;
dfs_stack[dfs_top] = (w, 0);
dfs_top += 1;
node_index[w as usize] = index;
node_lowlink[w as usize] = index;
index += 1;
if top < MAX_NODES {
stack[top] = w;
on_stack[w as usize] = true;
top += 1;
}
advanced = true;
break;
} else if on_stack[w as usize] {
let lw = node_lowlink[v as usize];
let iw = node_index[w as usize];
if iw < lw {
node_lowlink[v as usize] = iw;
}
}
}
if advanced {
continue;
}
dfs_top -= 1;
if dfs_top > 0 {
let parent = dfs_stack[dfs_top - 1].0;
let lp = node_lowlink[parent as usize];
let lv = node_lowlink[v as usize];
if lv < lp {
node_lowlink[parent as usize] = lv;
}
}
if node_lowlink[v as usize] == node_index[v as usize] {
loop {
if top == 0 {
break;
}
top -= 1;
let w = stack[top];
on_stack[w as usize] = false;
scc_id_out[w as usize] = scc_count as u16;
if w == v {
break;
}
}
scc_count += 1;
}
}
}
scc_count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deduplicates_and_drops_self_loops() {
let observed = [
ServiceEdge { parent: 0, child: 1 },
ServiceEdge { parent: 0, child: 1 }, ServiceEdge { parent: 1, child: 1 }, ServiceEdge { parent: 2, child: 0 },
];
let mut out = [(0_u16, 0_u16); 16];
let n = infer_service_graph_from_observed(&observed, 3, &mut out);
assert_eq!(n, 2, "self-loop dropped, duplicate dedup");
assert_eq!(out[0], (0, 1));
assert_eq!(out[1], (2, 0));
}
#[test]
fn out_of_range_indices_dropped() {
let observed = [
ServiceEdge { parent: 0, child: 5 }, ServiceEdge { parent: 99, child: 0 }, ServiceEdge { parent: 1, child: 2 },
];
let mut out = [(0_u16, 0_u16); 16];
let n = infer_service_graph_from_observed(&observed, 3, &mut out);
assert_eq!(n, 1);
assert_eq!(out[0], (1, 2));
}
#[test]
fn scc_linear_chain_yields_n_components() {
let edges = [(0_u16, 1_u16), (1, 2), (2, 3)];
let mut scc_id = [0_u16; 4];
let n = tarjan_scc(&edges, 4, &mut scc_id);
assert_eq!(n, 4);
}
#[test]
fn scc_cycle_collapses_to_one() {
let edges = [(0_u16, 1_u16), (1, 2), (2, 0)];
let mut scc_id = [0_u16; 3];
let n = tarjan_scc(&edges, 3, &mut scc_id);
assert_eq!(n, 1);
assert_eq!(scc_id[0], scc_id[1]);
assert_eq!(scc_id[1], scc_id[2]);
}
#[test]
fn scc_mixed_topology() {
let edges = [(0_u16, 1_u16), (1, 2), (2, 1)];
let mut scc_id = [0_u16; 4];
let n = tarjan_scc(&edges, 4, &mut scc_id);
assert_eq!(n, 3);
}
}