use crate::sparse::csc::CscPattern;
pub fn amd_order(pattern: &CscPattern) -> Vec<usize> {
let n = pattern.n;
if n == 0 {
return Vec::new();
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for (j, adj_j) in adj.iter_mut().enumerate() {
for k in pattern.col_ptr[j]..pattern.col_ptr[j + 1] {
let i = pattern.row_idx[k];
if i != j {
adj_j.push(i);
}
}
}
let mut eliminated = vec![false; n];
let mut degree = vec![0usize; n];
for i in 0..n {
degree[i] = adj[i].len();
}
let mut mark = vec![false; n];
let mut perm = Vec::with_capacity(n);
let mut neighbors: Vec<usize> = Vec::with_capacity(n);
for _ in 0..n {
let mut min_deg = usize::MAX;
let mut pivot = 0;
for i in 0..n {
if !eliminated[i] && degree[i] < min_deg {
min_deg = degree[i];
pivot = i;
}
}
eliminated[pivot] = true;
perm.push(pivot);
neighbors.clear();
for &i in &adj[pivot] {
if !eliminated[i] {
neighbors.push(i);
}
}
let n_remaining = n - perm.len(); if neighbors.len() == n_remaining {
for &nb in &neighbors {
perm.push(nb);
}
return perm;
}
for i in 0..neighbors.len() {
let a = neighbors[i];
for &x in &adj[a] {
mark[x] = true;
}
for &b in &neighbors[i + 1..] {
if !mark[b] {
adj[a].push(b);
adj[b].push(a);
mark[b] = true;
}
}
for &x in &adj[a] {
mark[x] = false;
}
}
for &nb in &neighbors {
degree[nb] = adj[nb].iter().filter(|&&x| !eliminated[x]).count();
}
}
perm
}
#[allow(clippy::needless_range_loop)]
pub fn permute_pattern(pattern: &CscPattern, perm: &[usize]) -> CscPattern {
let n = pattern.n;
let mut inv_perm = vec![0usize; n];
for (new, &old) in perm.iter().enumerate() {
inv_perm[old] = new;
}
let mut col_ptr = vec![0usize; n + 1];
for old_j in 0..n {
let new_j = inv_perm[old_j];
let nnz_j = pattern.col_ptr[old_j + 1] - pattern.col_ptr[old_j];
col_ptr[new_j + 1] = nnz_j;
}
for j in 0..n {
col_ptr[j + 1] += col_ptr[j];
}
let nnz = col_ptr[n];
let mut row_idx = vec![0usize; nnz];
let mut offsets: Vec<usize> = col_ptr[..n].to_vec();
for old_j in 0..n {
let new_j = inv_perm[old_j];
let start = pattern.col_ptr[old_j];
let end = pattern.col_ptr[old_j + 1];
for k in start..end {
let new_i = inv_perm[pattern.row_idx[k]];
row_idx[offsets[new_j]] = new_i;
offsets[new_j] += 1;
}
}
for j in 0..n {
let start = col_ptr[j];
let end = col_ptr[j + 1];
row_idx[start..end].sort_unstable();
}
CscPattern {
n,
col_ptr,
row_idx,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse::csc::CscMatrix;
fn arrow_matrix_5() -> CscPattern {
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();
m.symmetric_pattern()
}
#[test]
fn test_amd_valid_permutation() {
let pat = arrow_matrix_5();
let perm = amd_order(&pat);
assert_eq!(perm.len(), 5);
let mut sorted = perm.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_amd_arrow_matrix() {
let pat = arrow_matrix_5();
let perm = amd_order(&pat);
assert!(
perm[..3].iter().all(|&p| p != 0),
"leaf nodes should be eliminated before hub"
);
let fill = estimate_fill(&pat, &perm);
assert_eq!(fill, 0, "AMD on arrow matrix should produce zero fill");
}
#[test]
fn test_amd_diagonal_matrix() {
let m = CscMatrix::from_triplets(4, &[0, 1, 2, 3], &[0, 1, 2, 3], &[1.0; 4]).unwrap();
let pat = m.symmetric_pattern();
let perm = amd_order(&pat);
assert_eq!(perm.len(), 4);
let mut sorted = perm.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2, 3]);
}
#[test]
fn test_amd_tridiagonal() {
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 perm = amd_order(&pat);
assert_eq!(perm.len(), 5);
}
#[test]
fn test_amd_empty() {
let pat = CscPattern {
n: 0,
col_ptr: vec![0],
row_idx: vec![],
};
let perm = amd_order(&pat);
assert_eq!(perm.len(), 0);
}
#[test]
fn test_permute_pattern() {
let m = CscMatrix::from_triplets(
3,
&[0, 1, 1, 2, 2],
&[0, 0, 1, 1, 2],
&[1.0, -1.0, 2.0, -1.0, 1.0],
)
.unwrap();
let pat = m.symmetric_pattern();
let perm = vec![2, 1, 0];
let permuted = permute_pattern(&pat, &perm);
assert_eq!(permuted.n, 3);
assert_eq!(permuted.col_ptr[3], pat.col_ptr[3]);
}
#[test]
fn test_amd_reduces_fill_on_arrow() {
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 amd_perm = amd_order(&pat);
let natural_perm: Vec<usize> = (0..5).collect();
let natural_fill = estimate_fill(&pat, &natural_perm);
let amd_fill = estimate_fill(&pat, &amd_perm);
assert!(
amd_fill <= natural_fill,
"AMD fill {} > natural fill {}",
amd_fill,
natural_fill
);
}
fn estimate_fill(pattern: &CscPattern, perm: &[usize]) -> usize {
let n = pattern.n;
let permuted = permute_pattern(pattern, perm);
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for j in 0..n {
for k in permuted.col_ptr[j]..permuted.col_ptr[j + 1] {
let i = permuted.row_idx[k];
if i != j && !adj[j].contains(&i) {
adj[j].push(i);
}
}
}
let mut eliminated = vec![false; n];
let mut fill = 0usize;
for j in 0..n {
eliminated[j] = true;
let neighbors: Vec<usize> =
adj[j].iter().copied().filter(|&i| !eliminated[i]).collect();
for a in 0..neighbors.len() {
for b in (a + 1)..neighbors.len() {
let (u, v) = (neighbors[a], neighbors[b]);
if !adj[u].contains(&v) {
adj[u].push(v);
adj[v].push(u);
fill += 1;
}
}
}
}
fill
}
}