use crate::bundle::INVALID_ID;
use crate::graph::Graph;
use crate::internal::permutation::{apply_permutation, inverse_permutation};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cch {
pub rank: Vec<u32>,
pub order: Vec<u32>,
pub elimination_tree_parent: Vec<u32>,
pub up_first_out: Vec<u32>,
pub up_head: Vec<u32>,
pub up_tail: Vec<u32>,
pub down_first_out: Vec<u32>,
pub down_head: Vec<u32>,
pub down_to_up: Vec<u32>,
pub input_arc_to_cch_arc: Vec<u32>,
pub forward_input_arc_of_cch: Vec<u32>,
pub backward_input_arc_of_cch: Vec<u32>,
pub first_extra_forward_input_arc_of_cch: Vec<u32>,
pub extra_forward_input_arc_of_cch: Vec<u32>,
pub first_extra_backward_input_arc_of_cch: Vec<u32>,
pub extra_backward_input_arc_of_cch: Vec<u32>,
}
impl Cch {
#[must_use]
#[inline]
pub fn node_count(&self) -> usize {
self.rank.len()
}
#[must_use]
#[inline]
pub fn cch_arc_count(&self) -> usize {
self.up_head.len()
}
#[must_use]
pub fn view(&self) -> crate::bundle::CchView<'_> {
crate::bundle::CchView {
rank: &self.rank,
elimination_tree_parent: &self.elimination_tree_parent,
up_first_out: &self.up_first_out,
up_head: &self.up_head,
down_first_out: &self.down_first_out,
down_head: &self.down_head,
down_to_up: &self.down_to_up,
}
}
#[must_use]
pub fn build(graph: &Graph, order: &[u32]) -> Cch {
let node_count = order.len();
assert_eq!(
node_count,
graph.node_count(),
"order length must equal graph node count"
);
let rank = inverse_permutation(order);
let input_arc_count = graph.arc_count();
let mut orig_tail = Vec::with_capacity(input_arc_count);
let mut orig_head = Vec::with_capacity(input_arc_count);
for v in 0..node_count {
let start = graph.first_out[v] as usize;
let end = graph.first_out[v + 1] as usize;
for arc in start..end {
orig_tail.push(rank[v]);
orig_head.push(rank[graph.head[arc] as usize]);
}
}
let mut input_tail = orig_tail.clone();
let mut input_head = orig_head.clone();
sort_by_tail_then_head(node_count, &mut input_tail, &mut input_head);
let (sym_tail, sym_head) = symmetrize_and_dedup(node_count, &input_tail, &input_head);
let (mut up_tail, mut up_head) =
compute_chordal_supergraph(node_count, &sym_tail, &sym_head);
sort_by_tail_then_head(node_count, &mut up_tail, &mut up_head);
let up_first_out = invert_vector(&up_tail, node_count);
let mapping = build_input_arc_mapping(&orig_tail, &orig_head, &up_first_out, &up_head);
let mut elimination_tree_parent = vec![0u32; node_count];
for x in 0..node_count {
if up_first_out[x] == up_first_out[x + 1] {
elimination_tree_parent[x] = INVALID_ID;
} else {
elimination_tree_parent[x] = up_head[up_first_out[x] as usize];
}
}
let mut down_tail = up_head.clone();
let down_head_unsorted = up_tail.clone();
let down_to_up =
compute_sort_perm_by_tail_then_head(node_count, &mut down_tail, &down_head_unsorted);
let down_head = apply_permutation(&down_to_up, &down_head_unsorted);
let down_first_out = invert_vector(&down_tail, node_count);
Cch {
rank,
order: order.to_vec(),
elimination_tree_parent,
up_first_out,
up_head,
up_tail,
down_first_out,
down_head,
down_to_up,
input_arc_to_cch_arc: mapping.input_arc_to_cch_arc,
forward_input_arc_of_cch: mapping.forward_input_arc_of_cch,
backward_input_arc_of_cch: mapping.backward_input_arc_of_cch,
first_extra_forward_input_arc_of_cch: mapping.first_extra_forward_input_arc_of_cch,
extra_forward_input_arc_of_cch: mapping.extra_forward_input_arc_of_cch,
first_extra_backward_input_arc_of_cch: mapping.first_extra_backward_input_arc_of_cch,
extra_backward_input_arc_of_cch: mapping.extra_backward_input_arc_of_cch,
}
}
}
struct InputArcMapping {
input_arc_to_cch_arc: Vec<u32>,
forward_input_arc_of_cch: Vec<u32>,
backward_input_arc_of_cch: Vec<u32>,
first_extra_forward_input_arc_of_cch: Vec<u32>,
extra_forward_input_arc_of_cch: Vec<u32>,
first_extra_backward_input_arc_of_cch: Vec<u32>,
extra_backward_input_arc_of_cch: Vec<u32>,
}
fn build_input_arc_mapping(
orig_tail: &[u32],
orig_head: &[u32],
up_first_out: &[u32],
up_head: &[u32],
) -> InputArcMapping {
let input_arc_count = orig_tail.len();
let cch_arc_count = up_head.len();
let mut input_arc_to_cch_arc = vec![INVALID_ID; input_arc_count];
let mut forward_input_arc_of_cch = vec![INVALID_ID; cch_arc_count];
let mut backward_input_arc_of_cch = vec![INVALID_ID; cch_arc_count];
let mut extra_forward: Vec<Vec<u32>> = vec![Vec::new(); cch_arc_count];
let mut extra_backward: Vec<Vec<u32>> = vec![Vec::new(); cch_arc_count];
for input_arc in 0..input_arc_count {
let t = orig_tail[input_arc];
let h = orig_head[input_arc];
if t == h {
continue; }
let upward = t < h;
let (lo, hi) = if upward { (t, h) } else { (h, t) };
let cch_arc = find_up_arc(up_first_out, up_head, lo, hi);
#[allow(
clippy::cast_possible_truncation,
reason = "input_arc < input_arc_count <= u32::MAX (CCH limit)"
)]
let input_arc_u32 = input_arc as u32;
input_arc_to_cch_arc[input_arc] = cch_arc;
let ci = cch_arc as usize;
if upward {
if forward_input_arc_of_cch[ci] == INVALID_ID {
forward_input_arc_of_cch[ci] = input_arc_u32;
} else {
extra_forward[ci].push(input_arc_u32);
}
} else if backward_input_arc_of_cch[ci] == INVALID_ID {
backward_input_arc_of_cch[ci] = input_arc_u32;
} else {
extra_backward[ci].push(input_arc_u32);
}
}
let (first_extra_forward_input_arc_of_cch, extra_forward_input_arc_of_cch) =
flatten_extra(&extra_forward);
let (first_extra_backward_input_arc_of_cch, extra_backward_input_arc_of_cch) =
flatten_extra(&extra_backward);
InputArcMapping {
input_arc_to_cch_arc,
forward_input_arc_of_cch,
backward_input_arc_of_cch,
first_extra_forward_input_arc_of_cch,
extra_forward_input_arc_of_cch,
first_extra_backward_input_arc_of_cch,
extra_backward_input_arc_of_cch,
}
}
fn find_up_arc(up_first_out: &[u32], up_head: &[u32], lo: u32, hi: u32) -> u32 {
let start = up_first_out[lo as usize] as usize;
let end = up_first_out[lo as usize + 1] as usize;
let slice = &up_head[start..end];
let offset = slice
.binary_search(&hi)
.expect("input arc must correspond to an existing CCH up-arc");
#[allow(
clippy::cast_possible_truncation,
reason = "arc id < cch_arc_count <= u32::MAX (CCH limit)"
)]
let arc = (start + offset) as u32;
arc
}
fn flatten_extra(per_arc: &[Vec<u32>]) -> (Vec<u32>, Vec<u32>) {
let mut first_out = vec![0u32; per_arc.len() + 1];
for (i, list) in per_arc.iter().enumerate() {
#[allow(
clippy::cast_possible_truncation,
reason = "extra-arc count fits u32 (CCH limit)"
)]
let len = list.len() as u32;
first_out[i + 1] = first_out[i] + len;
}
let mut flat = Vec::with_capacity(first_out[per_arc.len()] as usize);
for list in per_arc {
flat.extend_from_slice(list);
}
(first_out, flat)
}
fn compute_sort_perm_by_tail_then_head(
node_count: usize,
tail: &mut [u32],
head: &[u32],
) -> Vec<u32> {
let p = stable_sort_perm_by_key(head, node_count);
let tail_by_p: Vec<u32> = p.iter().map(|&i| tail[i as usize]).collect();
let q = stable_sort_perm_by_key(&tail_by_p, node_count);
let r: Vec<u32> = q.iter().map(|&qi| p[qi as usize]).collect();
let sorted_tail: Vec<u32> = r.iter().map(|&i| tail[i as usize]).collect();
tail.copy_from_slice(&sorted_tail);
r
}
fn sort_by_tail_then_head(node_count: usize, tail: &mut Vec<u32>, head: &mut Vec<u32>) {
let mut t = std::mem::take(tail);
let p = compute_sort_perm_by_tail_then_head(node_count, &mut t, head);
let sorted_head: Vec<u32> = p.iter().map(|&i| head[i as usize]).collect();
*tail = t;
*head = sorted_head;
}
fn stable_sort_perm_by_key(v: &[u32], key_count: usize) -> Vec<u32> {
let mut bucket_pos = vec![0u32; key_count + 1];
for &k in v {
bucket_pos[k as usize + 1] += 1;
}
for i in 0..key_count {
bucket_pos[i + 1] += bucket_pos[i];
}
let mut p = vec![0u32; v.len()];
for (i, &k) in v.iter().enumerate() {
let pos = bucket_pos[k as usize] as usize;
p[pos] = u32::try_from(i).expect("arc index fits u32");
bucket_pos[k as usize] += 1;
}
p
}
fn symmetrize_and_dedup(
node_count: usize,
input_tail: &[u32],
input_head: &[u32],
) -> (Vec<u32>, Vec<u32>) {
let input_arc_count = input_tail.len();
let mut sym_tail = Vec::with_capacity(2 * input_arc_count);
let mut sym_head = Vec::with_capacity(2 * input_arc_count);
sym_tail.extend_from_slice(input_tail);
sym_head.extend_from_slice(input_head);
sym_tail.extend_from_slice(input_head);
sym_head.extend_from_slice(input_tail);
sort_by_tail_then_head(node_count, &mut sym_tail, &mut sym_head);
let total = sym_tail.len();
let mut kept_tail = Vec::with_capacity(total);
let mut kept_head = Vec::with_capacity(total);
if input_arc_count != 0 && sym_tail[0] != sym_head[0] {
kept_tail.push(sym_tail[0]);
kept_head.push(sym_head[0]);
}
for i in 1..total {
let not_dup = sym_head[i] != sym_head[i - 1] || sym_tail[i] != sym_tail[i - 1];
let not_loop = sym_tail[i] != sym_head[i];
if not_dup && not_loop {
kept_tail.push(sym_tail[i]);
kept_head.push(sym_head[i]);
}
}
(kept_tail, kept_head)
}
fn compute_chordal_supergraph(
node_count: usize,
tail: &[u32],
head: &[u32],
) -> (Vec<u32>, Vec<u32>) {
let mut nodes: Vec<Vec<u32>> = vec![Vec::new(); node_count];
for i in 0..tail.len() {
if tail[i] < head[i] {
nodes[tail[i] as usize].push(head[i]);
}
}
for list in &mut nodes {
list.sort_unstable();
list.dedup();
}
let mut up_tail = Vec::new();
let mut up_head = Vec::new();
for n in 0..node_count {
if nodes[n].is_empty() {
continue;
}
let lowest_neighbor = nodes[n][0] as usize;
let merged = merge_unique(&nodes[n][1..], &nodes[lowest_neighbor]);
nodes[lowest_neighbor] = merged;
let tail_n = u32::try_from(n).expect("node id fits u32");
for &neighbor in &nodes[n] {
up_tail.push(tail_n);
up_head.push(neighbor);
}
}
(up_tail, up_head)
}
fn merge_unique(a: &[u32], b: &[u32]) -> Vec<u32> {
let mut out = Vec::with_capacity(a.len() + b.len());
let (mut i, mut j) = (0usize, 0usize);
while i < a.len() && j < b.len() {
match a[i].cmp(&b[j]) {
std::cmp::Ordering::Less => {
push_unique(&mut out, a[i]);
i += 1;
}
std::cmp::Ordering::Greater => {
push_unique(&mut out, b[j]);
j += 1;
}
std::cmp::Ordering::Equal => {
push_unique(&mut out, a[i]);
i += 1;
j += 1;
}
}
}
while i < a.len() {
push_unique(&mut out, a[i]);
i += 1;
}
while j < b.len() {
push_unique(&mut out, b[j]);
j += 1;
}
out
}
#[inline]
fn push_unique(out: &mut Vec<u32>, value: u32) {
if out.last() != Some(&value) {
out.push(value);
}
}
fn invert_vector(tail: &[u32], element_count: usize) -> Vec<u32> {
let mut index = vec![0u32; element_count + 1];
if tail.is_empty() {
return index;
}
let mut pos = 0usize;
for (i, slot) in index.iter_mut().take(element_count).enumerate() {
while pos < tail.len() && (tail[pos] as usize) < i {
pos += 1;
}
*slot = u32::try_from(pos).expect("arc index fits u32");
}
index[element_count] = u32::try_from(tail.len()).expect("arc count fits u32");
index
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_graph(n: usize) -> Graph {
Graph {
first_out: vec![0u32; n + 1],
head: vec![],
weight: vec![],
}
}
#[test]
fn build_empty_graph() {
let g = empty_graph(0);
let order: Vec<u32> = vec![];
let c = Cch::build(&g, &order);
assert_eq!(c.node_count(), 0);
assert_eq!(c.cch_arc_count(), 0);
assert!(c.rank.is_empty());
assert!(c.order.is_empty());
assert!(c.elimination_tree_parent.is_empty());
assert_eq!(c.up_first_out, vec![0]);
assert!(c.up_head.is_empty());
assert!(c.up_tail.is_empty());
assert_eq!(c.down_first_out, vec![0]);
assert!(c.down_head.is_empty());
assert!(c.down_to_up.is_empty());
}
#[test]
fn view_borrows_struct_fields() {
let g = Graph {
first_out: vec![0, 1, 1],
head: vec![1],
weight: vec![5],
};
let c = Cch::build(&g, &[0u32, 1]);
let v = c.view();
assert_eq!(v.rank, c.rank.as_slice());
assert_eq!(
v.elimination_tree_parent,
c.elimination_tree_parent.as_slice()
);
assert_eq!(v.up_first_out, c.up_first_out.as_slice());
assert_eq!(v.up_head, c.up_head.as_slice());
assert_eq!(v.down_first_out, c.down_first_out.as_slice());
assert_eq!(v.down_head, c.down_head.as_slice());
assert_eq!(v.down_to_up, c.down_to_up.as_slice());
}
#[test]
fn build_single_node() {
let g = empty_graph(1);
let order: Vec<u32> = vec![0];
let c = Cch::build(&g, &order);
assert_eq!(c.rank, vec![0]);
assert_eq!(c.order, vec![0]);
assert_eq!(c.elimination_tree_parent, vec![INVALID_ID]);
assert_eq!(c.up_first_out, vec![0, 0]);
assert!(c.up_head.is_empty());
assert_eq!(c.down_first_out, vec![0, 0]);
assert!(c.down_to_up.is_empty());
}
fn csr(node_count: usize, tail: &[u32], head: &[u32]) -> Graph {
let mut counts = vec![0u32; node_count];
for &t in tail {
counts[t as usize] += 1;
}
let mut first_out = vec![0u32; node_count + 1];
for v in 0..node_count {
first_out[v + 1] = first_out[v] + counts[v];
}
let mut next: Vec<usize> = first_out[..node_count]
.iter()
.map(|&x| x as usize)
.collect();
let mut g_head = vec![0u32; head.len()];
for (&t, &h) in tail.iter().zip(head.iter()) {
g_head[next[t as usize]] = h;
next[t as usize] += 1;
}
Graph {
first_out,
head: g_head,
weight: vec![1u32; head.len()],
}
}
#[test]
fn build_triangle_fillin() {
let tail = [0u32, 1, 0, 2];
let head = [1u32, 0, 2, 0];
let g = csr(3, &tail, &head);
let order: Vec<u32> = vec![0, 1, 2];
let c = Cch::build(&g, &order);
assert_eq!(c.up_tail, vec![0, 0, 1]);
assert_eq!(c.up_head, vec![1, 2, 2]);
assert_eq!(c.up_first_out, vec![0, 2, 3, 3]);
assert_eq!(c.elimination_tree_parent, vec![1, 2, INVALID_ID]);
assert_eq!(c.down_first_out, vec![0, 0, 1, 3]);
assert_eq!(c.down_head, vec![0, 0, 1]);
assert_eq!(c.down_to_up, vec![0, 1, 2]);
}
#[test]
fn invert_vector_empty() {
assert_eq!(invert_vector(&[], 3), vec![0, 0, 0, 0]);
assert_eq!(invert_vector(&[], 0), vec![0]);
}
#[test]
fn invert_vector_basic() {
assert_eq!(invert_vector(&[0, 0, 2], 3), vec![0, 2, 2, 3]);
}
#[test]
fn merge_unique_basic() {
assert_eq!(merge_unique(&[1, 3, 5], &[2, 3, 6]), vec![1, 2, 3, 5, 6]);
assert_eq!(merge_unique(&[], &[1, 2]), vec![1, 2]);
assert_eq!(merge_unique(&[1, 2], &[]), vec![1, 2]);
assert_eq!(merge_unique(&[], &[] as &[u32]), Vec::<u32>::new());
}
#[test]
fn stable_sort_perm_by_key_basic() {
assert_eq!(stable_sort_perm_by_key(&[2, 0, 1, 0], 3), vec![1, 3, 2, 0]);
}
#[test]
fn sort_by_tail_then_head_basic() {
let mut tail = vec![2u32, 0, 2, 0];
let mut head = vec![1u32, 3, 0, 1];
sort_by_tail_then_head(4, &mut tail, &mut head);
assert_eq!(tail, vec![0, 0, 2, 2]);
assert_eq!(head, vec![1, 3, 0, 1]);
}
}