#![allow(dead_code)]
use oxicuda_blas::GpuFloat;
use crate::error::{SparseError, SparseResult};
use crate::format::CsrMatrix;
use crate::handle::SparseHandle;
fn to_f64<T: GpuFloat>(val: T) -> f64 {
if T::SIZE == 4 {
f32::from_bits(val.to_bits_u64() as u32) as f64
} else {
f64::from_bits(val.to_bits_u64())
}
}
fn from_f64<T: GpuFloat>(val: f64) -> T {
if T::SIZE == 4 {
T::from_bits_u64(u64::from((val as f32).to_bits()))
} else {
T::from_bits_u64(val.to_bits())
}
}
pub struct GraphColoring {
pub num_colors: usize,
pub colors: Vec<u32>,
pub color_offsets: Vec<usize>,
pub color_order: Vec<u32>,
}
impl GraphColoring {
pub fn from_csr<T: GpuFloat>(
_handle: &SparseHandle,
matrix: &CsrMatrix<T>,
) -> SparseResult<Self> {
if matrix.rows() != matrix.cols() {
return Err(SparseError::DimensionMismatch(format!(
"GraphColoring requires square matrix, got {}x{}",
matrix.rows(),
matrix.cols()
)));
}
let n = matrix.rows() as usize;
if n == 0 {
return Ok(Self {
num_colors: 0,
colors: Vec::new(),
color_offsets: vec![0],
color_order: Vec::new(),
});
}
let (h_row_ptr, h_col_idx, _h_values) = matrix.to_host()?;
Self::from_csr_host(&h_row_ptr, &h_col_idx, n)
}
pub fn from_csr_host(row_ptr: &[i32], col_idx: &[i32], n: usize) -> SparseResult<Self> {
if n == 0 {
return Ok(Self {
num_colors: 0,
colors: Vec::new(),
color_offsets: vec![0],
color_order: Vec::new(),
});
}
let mut colors = vec![u32::MAX; n];
let mut max_color: u32 = 0;
let mut forbidden = Vec::new();
for i in 0..n {
let row_start = row_ptr[i] as usize;
let row_end = row_ptr[i + 1] as usize;
for f in forbidden.iter_mut() {
*f = false;
}
for nz in row_start..row_end {
let j = col_idx[nz] as usize;
if j == i || j >= n {
continue;
}
if colors[j] != u32::MAX {
let c = colors[j] as usize;
if c >= forbidden.len() {
forbidden.resize(c + 1, false);
}
forbidden[c] = true;
}
let j_start = row_ptr[j] as usize;
let j_end = row_ptr[j + 1] as usize;
for &col in &col_idx[j_start..j_end] {
let k = col as usize;
if k == i || k >= n {
continue;
}
if colors[k] != u32::MAX {
let c = colors[k] as usize;
if c >= forbidden.len() {
forbidden.resize(c + 1, false);
}
forbidden[c] = true;
}
}
}
let mut chosen = 0u32;
loop {
let c = chosen as usize;
if c >= forbidden.len() || !forbidden[c] {
break;
}
chosen += 1;
}
colors[i] = chosen;
if chosen > max_color {
max_color = chosen;
}
}
let num_colors = max_color as usize + 1;
let mut color_counts = vec![0usize; num_colors];
for &c in &colors {
color_counts[c as usize] += 1;
}
let mut color_offsets = vec![0usize; num_colors + 1];
for c in 0..num_colors {
color_offsets[c + 1] = color_offsets[c] + color_counts[c];
}
let mut color_order = vec![0u32; n];
let mut write_pos = color_offsets[..num_colors].to_vec();
for (i, &c) in colors.iter().enumerate() {
let c_usize = c as usize;
color_order[write_pos[c_usize]] = i as u32;
write_pos[c_usize] += 1;
}
Ok(Self {
num_colors,
colors,
color_offsets,
color_order,
})
}
pub fn rows_for_color(&self, color: usize) -> &[u32] {
if color >= self.num_colors {
return &[];
}
let start = self.color_offsets[color];
let end = self.color_offsets[color + 1];
&self.color_order[start..end]
}
pub fn parallel_ilu0<T: GpuFloat>(
&self,
_handle: &SparseHandle,
matrix: &CsrMatrix<T>,
) -> SparseResult<(CsrMatrix<T>, CsrMatrix<T>)> {
let n = matrix.rows() as usize;
if n == 0 {
return Err(SparseError::InvalidArgument(
"cannot factor an empty matrix".to_string(),
));
}
let (h_row_ptr, h_col_idx, h_values) = matrix.to_host()?;
let mut work = h_values;
for color in 0..self.num_colors {
let rows = self.rows_for_color(color);
for &row_u32 in rows {
let row = row_u32 as usize;
let row_start = h_row_ptr[row] as usize;
let row_end = h_row_ptr[row + 1] as usize;
for nz in row_start..row_end {
let k = h_col_idx[nz] as usize;
if k >= row {
break;
}
let k_start = h_row_ptr[k] as usize;
let k_end = h_row_ptr[k + 1] as usize;
let diag_pos = find_col_in_row(&h_col_idx[k_start..k_end], k as i32);
let diag_pos = match diag_pos {
Some(pos) => k_start + pos,
None => return Err(SparseError::SingularMatrix),
};
let a_kk = work[diag_pos];
if a_kk == T::gpu_zero() {
return Err(SparseError::SingularMatrix);
}
let ratio = div_gpu_float(work[nz], a_kk);
work[nz] = ratio;
for k_nz in (diag_pos + 1)..k_end {
let j = h_col_idx[k_nz];
if let Some(ij_off) = find_col_in_row(&h_col_idx[row_start..row_end], j) {
let ij_pos = row_start + ij_off;
let update = mul_gpu_float(ratio, work[k_nz]);
work[ij_pos] = sub_gpu_float(work[ij_pos], update);
}
}
}
}
}
split_lu(&h_row_ptr, &h_col_idx, &work, matrix.rows())
}
pub fn validate(&self, row_ptr: &[i32], col_idx: &[i32], n: usize) -> bool {
for i in 0..n {
let row_start = row_ptr[i] as usize;
let row_end = row_ptr[i + 1] as usize;
let ci = self.colors[i];
for nz in row_start..row_end {
let j = col_idx[nz] as usize;
if j == i || j >= n {
continue;
}
if self.colors[j] == ci {
return false;
}
let j_start = row_ptr[j] as usize;
let j_end = row_ptr[j + 1] as usize;
for &col in &col_idx[j_start..j_end] {
let k = col as usize;
if k == i || k >= n {
continue;
}
if self.colors[k] == ci {
return false;
}
}
}
}
true
}
}
fn find_col_in_row(col_slice: &[i32], target_col: i32) -> Option<usize> {
col_slice.iter().position(|&c| c == target_col)
}
fn div_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let fa = to_f64(a);
let fb = to_f64(b);
from_f64::<T>(fa / fb)
}
fn mul_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let fa = to_f64(a);
let fb = to_f64(b);
from_f64::<T>(fa * fb)
}
fn sub_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let fa = to_f64(a);
let fb = to_f64(b);
from_f64::<T>(fa - fb)
}
fn split_lu<T: GpuFloat>(
row_ptr: &[i32],
col_idx: &[i32],
values: &[T],
n: u32,
) -> SparseResult<(CsrMatrix<T>, CsrMatrix<T>)> {
let n_usize = n as usize;
let mut l_nnz = 0usize;
let mut u_nnz = 0usize;
for i in 0..n_usize {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
for &cj in &col_idx[start..end] {
let j = cj as usize;
if j < i {
l_nnz += 1;
} else {
u_nnz += 1;
}
}
l_nnz += 1; }
let mut l_row_ptr = vec![0i32; n_usize + 1];
let mut l_col_idx = Vec::with_capacity(l_nnz);
let mut l_values = Vec::with_capacity(l_nnz);
let mut u_row_ptr = vec![0i32; n_usize + 1];
let mut u_col_idx = Vec::with_capacity(u_nnz);
let mut u_values = Vec::with_capacity(u_nnz);
for i in 0..n_usize {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
for idx in start..end {
let j = col_idx[idx] as usize;
if j < i {
l_col_idx.push(col_idx[idx]);
l_values.push(values[idx]);
}
}
l_col_idx.push(i as i32);
l_values.push(T::gpu_one());
l_row_ptr[i + 1] = l_col_idx.len() as i32;
for idx in start..end {
let j = col_idx[idx] as usize;
if j >= i {
u_col_idx.push(col_idx[idx]);
u_values.push(values[idx]);
}
}
u_row_ptr[i + 1] = u_col_idx.len() as i32;
}
let l_mat = CsrMatrix::from_host(n, n, &l_row_ptr, &l_col_idx, &l_values)?;
let u_mat = CsrMatrix::from_host(n, n, &u_row_ptr, &u_col_idx, &u_values)?;
Ok((l_mat, u_mat))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coloring_diagonal_matrix() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let n = 3;
let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
assert!(coloring.is_ok());
let coloring = match coloring {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
assert_eq!(coloring.num_colors, 1);
for &c in &coloring.colors {
assert_eq!(c, 0);
}
}
#[test]
fn coloring_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 n = 4;
let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
assert!(coloring.is_ok());
let coloring = match coloring {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
assert!(coloring.num_colors >= 3, "need >= 3 colors for path graph");
assert!(coloring.validate(&row_ptr, &col_idx, n));
}
#[test]
fn coloring_dense_small() {
let row_ptr = vec![0, 3, 6, 9];
let col_idx = vec![0, 1, 2, 0, 1, 2, 0, 1, 2];
let n = 3;
let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
assert!(coloring.is_ok());
let coloring = match coloring {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
assert_eq!(coloring.num_colors, 3);
assert!(coloring.validate(&row_ptr, &col_idx, n));
}
#[test]
fn coloring_rows_for_color() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let n = 3;
let coloring = match GraphColoring::from_csr_host(&row_ptr, &col_idx, n) {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
let rows = coloring.rows_for_color(0);
assert_eq!(rows.len(), 3);
let rows = coloring.rows_for_color(1);
assert_eq!(rows.len(), 0);
}
#[test]
fn coloring_single_node() {
let row_ptr = vec![0, 1];
let col_idx = vec![0];
let n = 1;
let coloring = GraphColoring::from_csr_host(&row_ptr, &col_idx, n);
assert!(coloring.is_ok());
let coloring = match coloring {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
assert_eq!(coloring.num_colors, 1);
assert_eq!(coloring.colors[0], 0);
assert_eq!(coloring.color_order.len(), 1);
}
#[test]
fn coloring_color_offsets_consistency() {
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 n = 5;
let coloring = match GraphColoring::from_csr_host(&row_ptr, &col_idx, n) {
Ok(v) => v,
Err(e) => panic!("test: {e}"),
};
let mut total = 0;
for c in 0..coloring.num_colors {
let count = coloring.rows_for_color(c).len();
total += count;
}
assert_eq!(total, n);
assert_eq!(
coloring.color_offsets[coloring.num_colors], n,
"last color offset should equal n"
);
assert!(coloring.validate(&row_ptr, &col_idx, n));
}
#[test]
fn coloring_validate_detects_bad_coloring() {
let row_ptr = vec![0, 2, 4, 6];
let col_idx = vec![0, 1, 1, 2, 0, 2]; let n = 3;
let bad_coloring = GraphColoring {
num_colors: 1,
colors: vec![0, 0, 0],
color_offsets: vec![0, 3],
color_order: vec![0, 1, 2],
};
assert!(!bad_coloring.validate(&row_ptr, &col_idx, n));
}
#[test]
fn host_float_arithmetic_f32() {
let a = 6.0_f32;
let b = 2.0_f32;
let result = div_gpu_float(a, b);
assert!((result - 3.0_f32).abs() < 1e-6);
let result = mul_gpu_float(a, b);
assert!((result - 12.0_f32).abs() < 1e-6);
let result = sub_gpu_float(a, b);
assert!((result - 4.0_f32).abs() < 1e-6);
}
#[test]
fn host_float_arithmetic_f64() {
let a = 6.0_f64;
let b = 2.0_f64;
assert!((div_gpu_float(a, b) - 3.0_f64).abs() < 1e-12);
assert!((mul_gpu_float(a, b) - 12.0_f64).abs() < 1e-12);
assert!((sub_gpu_float(a, b) - 4.0_f64).abs() < 1e-12);
}
#[test]
fn find_col_in_row_works() {
let cols = [0, 2, 5, 7];
assert_eq!(find_col_in_row(&cols, 2), Some(1));
assert_eq!(find_col_in_row(&cols, 3), None);
assert_eq!(find_col_in_row(&cols, 7), Some(3));
}
}