#![allow(dead_code)]
use std::collections::VecDeque;
use oxicuda_blas::GpuFloat;
use crate::error::{SparseError, SparseResult};
use crate::format::CsrMatrix;
pub fn rcm_ordering<T: GpuFloat>(matrix: &CsrMatrix<T>) -> SparseResult<Vec<usize>> {
if matrix.rows() != matrix.cols() {
return Err(SparseError::DimensionMismatch(format!(
"RCM requires square matrix, got {}x{}",
matrix.rows(),
matrix.cols()
)));
}
let n = matrix.rows() as usize;
if n == 0 {
return Ok(Vec::new());
}
let (h_row_ptr, h_col_idx, _) = matrix.to_host()?;
rcm_ordering_host(&h_row_ptr, &h_col_idx, n)
}
pub fn rcm_ordering_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Vec<usize>> {
if n == 0 {
return Ok(Vec::new());
}
let degrees: Vec<usize> = (0..n)
.map(|i| {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
col_idx[start..end]
.iter()
.filter(|&&c| c as usize != i && (c as usize) < n)
.count()
})
.collect();
let start_node = find_pseudo_peripheral(row_ptr, col_idx, °rees, n);
let mut visited = vec![false; n];
let mut order = Vec::with_capacity(n);
let starts = [start_node];
let mut queue: VecDeque<usize> = VecDeque::new();
let mut component_start = 0;
while order.len() < n {
let root = if component_start < starts.len() {
starts[component_start]
} else {
match visited.iter().position(|&v| !v) {
Some(node) => node,
None => break,
}
};
component_start += 1;
if visited[root] {
continue;
}
visited[root] = true;
queue.push_back(root);
while let Some(node) = queue.pop_front() {
order.push(node);
let start = row_ptr[node] as usize;
let end = row_ptr[node + 1] as usize;
let mut neighbors: Vec<usize> = col_idx[start..end]
.iter()
.map(|&c| c as usize)
.filter(|&c| c < n && c != node && !visited[c])
.collect();
neighbors.sort_unstable();
neighbors.dedup();
neighbors.sort_by_key(|&nbr| degrees[nbr]);
for nbr in neighbors {
if !visited[nbr] {
visited[nbr] = true;
queue.push_back(nbr);
}
}
}
}
order.reverse();
Ok(order)
}
fn find_pseudo_peripheral(row_ptr: &[i32], col_idx: &[i32], degrees: &[usize], n: usize) -> usize {
let mut current = 0;
let mut min_deg = degrees[0];
for (i, &d) in degrees.iter().enumerate().skip(1) {
if d < min_deg {
min_deg = d;
current = i;
}
}
for _ in 0..5 {
let (last_level, _) = bfs_levels(row_ptr, col_idx, n, current);
if last_level.is_empty() {
break;
}
let mut best = last_level[0];
let mut best_deg = degrees[best];
for &node in &last_level[1..] {
if degrees[node] < best_deg {
best_deg = degrees[node];
best = node;
}
}
if best == current {
break;
}
current = best;
}
current
}
fn bfs_levels(row_ptr: &[i32], col_idx: &[i32], n: usize, root: usize) -> (Vec<usize>, usize) {
let mut visited = vec![false; n];
let mut current_level = Vec::new();
let mut next_level = Vec::new();
visited[root] = true;
current_level.push(root);
let mut num_levels = 1;
loop {
for &node in ¤t_level {
let start = row_ptr[node] as usize;
let end = row_ptr[node + 1] as usize;
for &c in &col_idx[start..end] {
let nbr = c as usize;
if nbr < n && !visited[nbr] {
visited[nbr] = true;
next_level.push(nbr);
}
}
}
if next_level.is_empty() {
break;
}
num_levels += 1;
current_level.clear();
std::mem::swap(&mut current_level, &mut next_level);
}
(current_level, num_levels)
}
pub fn amd_ordering<T: GpuFloat>(matrix: &CsrMatrix<T>) -> SparseResult<Vec<usize>> {
if matrix.rows() != matrix.cols() {
return Err(SparseError::DimensionMismatch(format!(
"AMD requires square matrix, got {}x{}",
matrix.rows(),
matrix.cols()
)));
}
let n = matrix.rows() as usize;
if n == 0 {
return Ok(Vec::new());
}
let (h_row_ptr, h_col_idx, _) = matrix.to_host()?;
amd_ordering_host(&h_row_ptr, &h_col_idx, n)
}
pub fn amd_ordering_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Vec<usize>> {
if n == 0 {
return Ok(Vec::new());
}
let mut adj: Vec<Vec<usize>> = Vec::with_capacity(n);
for i in 0..n {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
let mut neighbors: Vec<usize> = col_idx[start..end]
.iter()
.map(|&c| c as usize)
.filter(|&c| c != i && c < n)
.collect();
neighbors.sort_unstable();
neighbors.dedup();
adj.push(neighbors);
}
let mut eliminated = vec![false; n];
let mut degree: Vec<usize> = adj.iter().map(|a| a.len()).collect();
let mut perm = Vec::with_capacity(n);
for _ in 0..n {
let mut min_node = None;
let mut min_deg = usize::MAX;
for (i, (&d, &elim)) in degree.iter().zip(eliminated.iter()).enumerate() {
if !elim && d < min_deg {
min_deg = d;
min_node = Some(i);
}
}
let node = match min_node {
Some(v) => v,
None => break,
};
perm.push(node);
eliminated[node] = true;
let neighbors: Vec<usize> = adj[node]
.iter()
.copied()
.filter(|&nbr| !eliminated[nbr])
.collect();
for &nbr in &neighbors {
adj[nbr].retain(|&x| x != node);
for &other in &neighbors {
if other != nbr && !adj[nbr].contains(&other) {
adj[nbr].push(other);
}
}
degree[nbr] = adj[nbr].iter().filter(|&&x| !eliminated[x]).count();
}
degree[node] = 0;
}
Ok(perm)
}
pub fn permute_csr<T: GpuFloat>(
matrix: &CsrMatrix<T>,
perm: &[usize],
) -> SparseResult<CsrMatrix<T>> {
let n = matrix.rows() as usize;
if perm.len() != n {
return Err(SparseError::InvalidArgument(format!(
"permutation length ({}) must match matrix dimension ({})",
perm.len(),
n
)));
}
let inv_perm = inverse_permutation(perm);
if inv_perm.len() != n {
return Err(SparseError::InvalidArgument(
"invalid permutation: not a valid bijection".to_string(),
));
}
let (h_row_ptr, h_col_idx, h_values) = matrix.to_host()?;
let mut new_row_ptr = vec![0i32; n + 1];
let mut new_entries: Vec<Vec<(i32, T)>> = Vec::with_capacity(n);
for new_row in 0..n {
let old_row = perm[new_row];
if old_row >= n {
return Err(SparseError::InvalidArgument(format!(
"permutation index {} out of bounds (n={})",
old_row, n
)));
}
let start = h_row_ptr[old_row] as usize;
let end = h_row_ptr[old_row + 1] as usize;
let mut entries: Vec<(i32, T)> = Vec::with_capacity(end - start);
for k in start..end {
let old_col = h_col_idx[k] as usize;
if old_col >= n {
return Err(SparseError::InvalidArgument(format!(
"column index {} out of bounds (n={})",
old_col, n
)));
}
let new_col = inv_perm[old_col];
entries.push((new_col as i32, h_values[k]));
}
entries.sort_by_key(|&(c, _)| c);
new_row_ptr[new_row + 1] = new_row_ptr[new_row] + entries.len() as i32;
new_entries.push(entries);
}
let nnz = new_row_ptr[n] as usize;
if nnz == 0 {
return Err(SparseError::ZeroNnz);
}
let mut new_col_idx = Vec::with_capacity(nnz);
let mut new_values = Vec::with_capacity(nnz);
for entries in &new_entries {
for &(c, v) in entries {
new_col_idx.push(c);
new_values.push(v);
}
}
CsrMatrix::from_host(
matrix.rows(),
matrix.cols(),
&new_row_ptr,
&new_col_idx,
&new_values,
)
}
pub fn inverse_permutation(perm: &[usize]) -> Vec<usize> {
let n = perm.len();
let mut inv = vec![0usize; n];
for (new_idx, &old_idx) in perm.iter().enumerate() {
if old_idx < n {
inv[old_idx] = new_idx;
}
}
inv
}
pub fn bandwidth(row_ptr: &[i32], col_idx: &[i32], n: usize) -> usize {
let mut bw = 0usize;
for i in 0..n {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
for &c in &col_idx[start..end] {
let j = c as usize;
let diff = i.abs_diff(j);
if diff > bw {
bw = diff;
}
}
}
bw
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rcm_identity() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let perm = rcm_ordering_host(&row_ptr, &col_idx, 3);
assert!(perm.is_ok());
let perm = perm.expect("test: should succeed");
assert_eq!(perm.len(), 3);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, vec![0, 1, 2]);
}
#[test]
fn rcm_tridiagonal() {
let row_ptr = vec![0, 2, 5, 8, 11, 13];
let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4];
let perm = rcm_ordering_host(&row_ptr, &col_idx, 5);
assert!(perm.is_ok());
let perm = perm.expect("test: should succeed");
assert_eq!(perm.len(), 5);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, vec![0, 1, 2, 3, 4]);
}
#[test]
fn rcm_reduces_bandwidth() {
let row_ptr = vec![0, 5, 7, 9, 11, 13];
let col_idx = vec![0, 1, 2, 3, 4, 0, 1, 0, 2, 0, 3, 0, 4];
let n = 5;
let orig_bw = bandwidth(&row_ptr, &col_idx, n);
assert_eq!(orig_bw, 4);
let perm = rcm_ordering_host(&row_ptr, &col_idx, n);
assert!(perm.is_ok());
let perm = perm.expect("test: should succeed");
let inv = inverse_permutation(&perm);
let mut new_bw = 0;
for (i, &old_row) in perm.iter().enumerate().take(n) {
let start = row_ptr[old_row] as usize;
let end = row_ptr[old_row + 1] as usize;
for &c in &col_idx[start..end] {
let new_col = inv[c as usize];
let diff = i.abs_diff(new_col);
if diff > new_bw {
new_bw = diff;
}
}
}
assert!(new_bw <= orig_bw);
}
#[test]
fn amd_identity() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let perm = amd_ordering_host(&row_ptr, &col_idx, 3);
assert!(perm.is_ok());
let perm = perm.expect("test: should succeed");
assert_eq!(perm.len(), 3);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, vec![0, 1, 2]);
}
#[test]
fn amd_tridiagonal() {
let row_ptr = vec![0, 2, 5, 8, 10];
let col_idx = vec![0, 1, 0, 1, 2, 1, 2, 3, 2, 3];
let perm = amd_ordering_host(&row_ptr, &col_idx, 4);
assert!(perm.is_ok());
let perm = perm.expect("test: should succeed");
assert_eq!(perm.len(), 4);
let mut sorted = perm.clone();
sorted.sort_unstable();
assert_eq!(sorted, vec![0, 1, 2, 3]);
}
#[test]
fn inverse_permutation_roundtrip() {
let perm = vec![3, 1, 0, 2];
let inv = inverse_permutation(&perm);
assert_eq!(inv, vec![2, 1, 3, 0]);
let inv_inv = inverse_permutation(&inv);
assert_eq!(inv_inv, perm);
}
#[test]
fn bandwidth_calculation() {
let row_ptr = vec![0, 2, 5, 7];
let col_idx = vec![0, 1, 0, 1, 2, 1, 2];
assert_eq!(bandwidth(&row_ptr, &col_idx, 3), 1);
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
assert_eq!(bandwidth(&row_ptr, &col_idx, 3), 0);
}
#[test]
fn rcm_empty() {
let perm = rcm_ordering_host(&[0], &[], 0);
assert!(perm.is_ok());
assert!(perm.expect("test: should succeed").is_empty());
}
#[test]
fn amd_empty() {
let perm = amd_ordering_host(&[0], &[], 0);
assert!(perm.is_ok());
assert!(perm.expect("test: should succeed").is_empty());
}
}