use super::elimination_tree::EliminationTree;
pub fn postorder(etree: &EliminationTree) -> (Vec<usize>, Vec<usize>) {
let n = etree.n;
if n == 0 {
return (Vec::new(), Vec::new());
}
let children = etree.children();
let sizes = etree.subtree_sizes();
let roots = etree.roots();
let mut order = Vec::with_capacity(n);
let mut stack: Vec<(usize, usize)> = Vec::new();
let mut sorted_roots = roots;
sorted_roots.sort_unstable_by_key(|&r| sizes[r]);
for &root in &sorted_roots {
stack.push((root, 0));
while let Some((node, child_idx)) = stack.last_mut() {
let node = *node;
let mut sorted_children = children[node].clone();
sorted_children.sort_unstable_by_key(|&c| sizes[c]);
if *child_idx < sorted_children.len() {
let child = sorted_children[*child_idx];
*child_idx += 1;
stack.push((child, 0));
} else {
order.push(node);
stack.pop();
}
}
}
let mut inv = vec![0usize; n];
for (k, &node) in order.iter().enumerate() {
inv[node] = k;
}
(order, inv)
}
pub fn biased_postorder(etree: &EliminationTree, bias: &[bool]) -> (Vec<usize>, Vec<usize>) {
let n = etree.n;
debug_assert_eq!(
bias.len(),
n,
"biased_postorder bias length must equal etree.n"
);
if n == 0 {
return (Vec::new(), Vec::new());
}
let children = etree.children();
let sizes = etree.subtree_sizes();
let roots = etree.roots();
let mut order = Vec::with_capacity(n);
let mut stack: Vec<(usize, Vec<usize>, usize)> = Vec::new();
let mut sorted_roots = roots;
sorted_roots.sort_unstable_by_key(|&r| sizes[r]);
for &root in &sorted_roots {
let merged = merge_bias_partition(&children[root], &sizes, bias);
stack.push((root, merged, 0));
while let Some((node, sorted_children, child_idx)) = stack.last_mut() {
let node_id = *node;
if *child_idx < sorted_children.len() {
let child = sorted_children[*child_idx];
*child_idx += 1;
let next_children = merge_bias_partition(&children[child], &sizes, bias);
stack.push((child, next_children, 0));
} else {
order.push(node_id);
stack.pop();
}
}
}
let mut inv = vec![0usize; n];
for (k, &node) in order.iter().enumerate() {
inv[node] = k;
}
(order, inv)
}
pub fn schur_constrained_postorder(
etree: &EliminationTree,
is_schur: &[bool],
) -> (Vec<usize>, Vec<usize>) {
let n = etree.n;
debug_assert_eq!(
is_schur.len(),
n,
"schur_constrained_postorder is_schur length must equal etree.n"
);
if n == 0 {
return (Vec::new(), Vec::new());
}
let children = etree.children();
let sizes = etree.subtree_sizes();
let roots = etree.roots();
let mut order = Vec::with_capacity(n);
let sorted_roots = schur_partition_children(&roots, &sizes, is_schur);
let mut stack: Vec<(usize, Vec<usize>, usize)> = Vec::new();
for &root in sorted_roots.iter() {
let merged = schur_partition_children(&children[root], &sizes, is_schur);
stack.push((root, merged, 0));
while let Some((node, sorted_children, child_idx)) = stack.last_mut() {
let node_id = *node;
if *child_idx < sorted_children.len() {
let child = sorted_children[*child_idx];
*child_idx += 1;
let next_children = schur_partition_children(&children[child], &sizes, is_schur);
stack.push((child, next_children, 0));
} else {
if !is_schur[node_id] {
order.push(node_id);
}
stack.pop();
}
}
}
for (k, &flag) in is_schur.iter().enumerate() {
if flag {
order.push(k);
}
}
let mut inv = vec![0usize; n];
for (k, &node) in order.iter().enumerate() {
inv[node] = k;
}
(order, inv)
}
fn schur_partition_children(children: &[usize], sizes: &[usize], is_schur: &[bool]) -> Vec<usize> {
let mut nonschur: Vec<usize> = children.iter().copied().filter(|&c| !is_schur[c]).collect();
let mut schur: Vec<usize> = children.iter().copied().filter(|&c| is_schur[c]).collect();
nonschur.sort_unstable_by_key(|&c| sizes[c]);
schur.sort_unstable();
nonschur.extend(schur);
nonschur
}
fn merge_bias_partition(children: &[usize], sizes: &[usize], bias: &[bool]) -> Vec<usize> {
let mut early: Vec<usize> = children.iter().copied().filter(|&c| !bias[c]).collect();
let mut late: Vec<usize> = children.iter().copied().filter(|&c| bias[c]).collect();
early.sort_unstable_by_key(|&c| sizes[c]);
late.sort_unstable_by_key(|&c| sizes[c]);
early.extend(late);
early
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse::csc::CscMatrix;
#[test]
fn test_postorder_tridiagonal() {
let m =
CscMatrix::from_triplets(4, &[0, 1, 1, 2, 2, 3, 3], &[0, 0, 1, 1, 2, 2, 3], &[1.0; 7])
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let (order, inv) = postorder(&etree);
assert_eq!(order.len(), 4);
assert_eq!(order, vec![0, 1, 2, 3]);
for (k, &node) in order.iter().enumerate() {
assert_eq!(inv[node], k);
}
}
#[test]
fn test_postorder_valid_topological_order() {
let m = CscMatrix::from_triplets(
5,
&[0, 1, 2, 3, 4, 1, 2, 3, 4],
&[0, 0, 0, 0, 0, 1, 2, 3, 4],
&[1.0; 9],
)
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let (order, inv) = postorder(&etree);
assert_eq!(order.len(), 5);
for j in 0..5 {
if let Some(p) = etree.parent[j] {
assert!(
inv[j] < inv[p],
"child {} (pos {}) should appear before parent {} (pos {})",
j,
inv[j],
p,
inv[p]
);
}
}
}
#[test]
fn test_postorder_diagonal() {
let m = CscMatrix::from_triplets(3, &[0, 1, 2], &[0, 1, 2], &[1.0; 3]).unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let (order, _) = postorder(&etree);
assert_eq!(order.len(), 3);
let mut sorted = order.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2]);
}
#[test]
fn test_postorder_inverse_roundtrip() {
let m =
CscMatrix::from_triplets(4, &[0, 1, 1, 2, 2, 3, 3], &[0, 0, 1, 1, 2, 2, 3], &[1.0; 7])
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let (order, inv) = postorder(&etree);
for j in 0..4 {
assert_eq!(order[inv[j]], j);
}
for k in 0..4 {
assert_eq!(inv[order[k]], k);
}
}
#[test]
fn test_schur_postorder_no_schur_matches_postorder() {
let m = CscMatrix::from_triplets(
5,
&[0, 1, 2, 3, 4, 1, 2, 3, 4],
&[0, 0, 0, 0, 0, 1, 2, 3, 4],
&[1.0; 9],
)
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let (a, _) = postorder(&etree);
let (b, _) = schur_constrained_postorder(&etree, &[false; 5]);
assert_eq!(a, b);
}
#[test]
fn test_schur_postorder_chain_tail_pinned() {
let m = CscMatrix::from_triplets(
5,
&[0, 1, 1, 2, 2, 3, 3, 4, 4],
&[0, 0, 1, 1, 2, 2, 3, 3, 4],
&[1.0; 9],
)
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let mut is_schur = vec![false; 5];
is_schur[3] = true;
is_schur[4] = true;
let (post, inv) = schur_constrained_postorder(&etree, &is_schur);
assert_eq!(post[3], 3);
assert_eq!(post[4], 4);
assert_eq!(inv[3], 3);
assert_eq!(inv[4], 4);
}
#[test]
fn test_schur_postorder_topological_property() {
let m = CscMatrix::from_triplets(
6,
&[0, 1, 2, 3, 4, 5, 1, 4, 2, 4, 3, 4, 5],
&[0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4],
&[1.0; 13],
)
.unwrap();
let pat = m.symmetric_pattern();
let etree = EliminationTree::from_pattern(&pat);
let mut is_schur = vec![false; 6];
is_schur[4] = true;
is_schur[5] = true;
let (_post, inv) = schur_constrained_postorder(&etree, &is_schur);
for j in 0..6 {
if let Some(p) = etree.parent[j] {
assert!(
inv[j] < inv[p],
"child {} (pos {}) must precede parent {} (pos {})",
j,
inv[j],
p,
inv[p]
);
}
}
assert!(inv[4] >= 4);
assert!(inv[5] >= 4);
assert!(inv[4] < inv[5] || inv[5] < inv[4]); }
#[test]
fn test_schur_postorder_forest_tail_identity() {
let etree = EliminationTree {
parent: vec![
Some(1),
Some(2),
Some(3),
Some(5),
Some(7),
None,
None,
None,
],
n: 8,
};
let is_schur = vec![false, false, false, false, true, true, true, true];
let (post, inv) = schur_constrained_postorder(&etree, &is_schur);
for k in 4..8 {
assert_eq!(
post[k], k,
"tail identity violated: post[{}] = {} (expected {})",
k, post[k], k
);
assert_eq!(inv[k], k);
}
for j in 0..8 {
if let Some(p) = etree.parent[j] {
assert!(
inv[j] < inv[p],
"child {} (pos {}) must precede parent {} (pos {})",
j,
inv[j],
p,
inv[p]
);
}
}
let mut prefix: Vec<usize> = post[0..4].to_vec();
prefix.sort();
assert_eq!(prefix, vec![0, 1, 2, 3]);
}
#[test]
fn test_schur_postorder_empty_etree() {
let etree = EliminationTree {
parent: Vec::new(),
n: 0,
};
let (post, inv) = schur_constrained_postorder(&etree, &[]);
assert!(post.is_empty());
assert!(inv.is_empty());
}
#[test]
fn test_postorder_empty() {
let etree = EliminationTree {
parent: Vec::new(),
n: 0,
};
let (order, inv) = postorder(&etree);
assert!(order.is_empty());
assert!(inv.is_empty());
}
}