#[cfg(feature = "sparse")]
use numr::algorithm::sparse_linalg::LuSymbolic;
#[cfg(feature = "sparse")]
pub fn compute_elimination_tree(n: usize, col_ptrs: &[i64], row_indices: &[i64]) -> Vec<usize> {
let mut etree = vec![n; n]; let mut ancestor = vec![n; n];
for col in 0..n {
let start = col_ptrs[col] as usize;
let end = col_ptrs[col + 1] as usize;
for &ri in &row_indices[start..end] {
let mut row = ri as usize;
if row >= col {
continue; }
loop {
let anc = ancestor[row];
if anc == col || anc == n {
break;
}
ancestor[row] = col; if etree[row] == n {
etree[row] = col;
}
row = anc;
}
ancestor[row] = col;
if etree[row] == n {
etree[row] = col;
}
}
}
etree
}
#[cfg(feature = "sparse")]
pub fn compute_post_order(etree: &[usize], n: usize) -> Vec<usize> {
let mut first_child = vec![n; n]; let mut next_sibling = vec![n; n];
for j in (0..n).rev() {
let parent = etree[j];
if parent < n {
next_sibling[j] = first_child[parent];
first_child[parent] = j;
}
}
let mut post_order = Vec::with_capacity(n);
let mut stack: Vec<(usize, bool)> = Vec::with_capacity(n);
for j in (0..n).rev() {
if etree[j] == n {
stack.push((j, false));
}
}
while let Some((node, visited)) = stack.pop() {
if visited {
post_order.push(node);
} else {
stack.push((node, true));
let mut child = first_child[node];
let mut children = Vec::new();
while child < n {
children.push(child);
child = next_sibling[child];
}
for &c in children.iter().rev() {
stack.push((c, false));
}
}
}
post_order
}
#[cfg(feature = "sparse")]
pub fn compute_column_reach(
n: usize,
etree: &[usize],
_post_order: &[usize],
col_ptrs: &[i64],
row_indices: &[i64],
) -> Vec<Vec<usize>> {
let mut reach = vec![vec![]; n];
let mut mark = vec![0usize; n];
for col in 0..n {
let start = col_ptrs[col] as usize;
let end = col_ptrs[col + 1] as usize;
for &ri in &row_indices[start..end] {
let row = ri as usize;
if row >= col {
continue;
}
let mut j = row;
while j < col && mark[j] != col + 1 {
reach[col].push(j);
mark[j] = col + 1; j = etree[j];
}
}
reach[col].sort_unstable();
}
reach
}
#[cfg(feature = "sparse")]
pub fn predict_lu_pattern(
n: usize,
_etree: &[usize],
reach: &[Vec<usize>],
col_ptrs: &[i64],
row_indices: &[i64],
) -> (Vec<i64>, Vec<i64>, Vec<i64>, Vec<i64>) {
let mut l_col_ptrs = vec![0i64; n + 1];
let mut l_row_indices = Vec::new();
let mut u_col_ptrs = vec![0i64; n + 1];
let mut u_row_indices = Vec::new();
for col in 0..n {
let u_start = u_row_indices.len();
for &j in &reach[col] {
u_row_indices.push(j as i64);
}
u_row_indices.push(col as i64); u_col_ptrs[col + 1] = u_row_indices.len() as i64;
let l_start = l_row_indices.len();
let mut l_rows = Vec::new();
let a_start = col_ptrs[col] as usize;
let a_end = col_ptrs[col + 1] as usize;
for &ri in &row_indices[a_start..a_end] {
let row = ri as usize;
if row > col {
l_rows.push(row);
}
}
l_rows.sort_unstable();
l_rows.dedup();
for row in l_rows {
l_row_indices.push(row as i64);
}
l_col_ptrs[col + 1] = l_row_indices.len() as i64;
let _ = (u_start, l_start); }
(l_col_ptrs, l_row_indices, u_col_ptrs, u_row_indices)
}
#[cfg(feature = "sparse")]
pub fn compute_lu_symbolic(n: usize, col_ptrs: &[i64], row_indices: &[i64]) -> LuSymbolic {
let etree = compute_elimination_tree(n, col_ptrs, row_indices);
let post_order = compute_post_order(&etree, n);
let reach = compute_column_reach(n, &etree, &post_order, col_ptrs, row_indices);
let (l_col_ptrs, l_row_indices, u_col_ptrs, u_row_indices) =
predict_lu_pattern(n, &etree, &reach, col_ptrs, row_indices);
LuSymbolic {
n,
etree,
post_order,
reach,
l_col_ptrs,
l_row_indices,
u_col_ptrs,
u_row_indices,
workspace_size: n,
}
}
#[cfg(all(test, feature = "sparse"))]
mod tests {
use super::*;
#[test]
fn test_identity_matrix() {
let col_ptrs = vec![0i64, 1, 2, 3];
let row_indices = vec![0i64, 1, 2];
let n = 3;
let etree = compute_elimination_tree(n, &col_ptrs, &row_indices);
assert!(etree.iter().all(|&p| p == n), "etree = {:?}", etree);
let post_order = compute_post_order(&etree, n);
assert_eq!(post_order.len(), n);
let reach = compute_column_reach(n, &etree, &post_order, &col_ptrs, &row_indices);
assert!(reach.iter().all(|r| r.is_empty()), "reach = {:?}", reach);
let symbolic = compute_lu_symbolic(n, &col_ptrs, &row_indices);
assert_eq!(symbolic.n, 3);
}
#[test]
fn test_tridiagonal_matrix() {
let col_ptrs = vec![0i64, 2, 5, 8, 10];
let row_indices = vec![0i64, 1, 0, 1, 2, 1, 2, 3, 2, 3];
let n = 4;
let etree = compute_elimination_tree(n, &col_ptrs, &row_indices);
assert_eq!(etree[0], 1, "etree[0] should be 1");
assert_eq!(etree[1], 2, "etree[1] should be 2");
assert_eq!(etree[2], 3, "etree[2] should be 3");
let symbolic = compute_lu_symbolic(n, &col_ptrs, &row_indices);
assert_eq!(symbolic.n, 4);
assert!(
symbolic.reach[2].contains(&1),
"reach[2] = {:?}",
symbolic.reach[2]
);
}
#[test]
fn test_arrow_matrix() {
let col_ptrs = vec![0i64, 4, 6, 8, 10];
let row_indices = vec![0i64, 1, 2, 3, 0, 1, 0, 2, 0, 3];
let n = 4;
let etree = compute_elimination_tree(n, &col_ptrs, &row_indices);
assert!(etree[0] < n, "Column 0 should have a parent");
let symbolic = compute_lu_symbolic(n, &col_ptrs, &row_indices);
assert_eq!(symbolic.n, 4);
assert!(symbolic.reach[1].contains(&0));
assert!(symbolic.reach[2].contains(&0));
assert!(symbolic.reach[3].contains(&0));
}
#[test]
fn test_post_order_covers_all_nodes() {
let col_ptrs = vec![0i64, 2, 4, 6, 7, 9];
let row_indices = vec![0i64, 1, 0, 1, 2, 3, 2, 3, 4];
let n = 5;
let etree = compute_elimination_tree(n, &col_ptrs, &row_indices);
let post_order = compute_post_order(&etree, n);
assert_eq!(post_order.len(), n);
let mut sorted = post_order.clone();
sorted.sort_unstable();
assert_eq!(sorted, (0..n).collect::<Vec<_>>());
}
}