#![allow(dead_code)]
use oxicuda_blas::GpuFloat;
use oxicuda_ptx::arch::SmVersion;
use oxicuda_ptx::builder::KernelBuilder;
use oxicuda_ptx::ir::PtxType;
use crate::error::{SparseError, SparseResult};
use crate::format::CsrMatrix;
use crate::handle::SparseHandle;
const ILU0_BLOCK_SIZE: u32 = 256;
pub fn ilu0<T: GpuFloat>(
handle: &SparseHandle,
a: &CsrMatrix<T>,
) -> SparseResult<(CsrMatrix<T>, CsrMatrix<T>)> {
if a.rows() != a.cols() {
return Err(SparseError::DimensionMismatch(format!(
"ILU(0) requires square matrix, got {}x{}",
a.rows(),
a.cols()
)));
}
let n = a.rows();
if n == 0 {
return Err(SparseError::InvalidArgument(
"cannot factor an empty matrix".to_string(),
));
}
let (h_row_ptr, h_col_idx, h_values) = a.to_host()?;
let levels = analyze_ilu0_levels(&h_row_ptr, &h_col_idx, n)?;
let mut work_values = h_values;
let _ptx_result = emit_ilu0_kernel::<T>(handle.sm_version());
for level_rows in &levels {
for &row_u32 in level_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_values[diag_pos];
if a_kk == T::gpu_zero() {
return Err(SparseError::SingularMatrix);
}
let a_ik = work_values[nz];
let ratio = div_gpu_float(a_ik, a_kk);
work_values[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 a_kj = work_values[k_nz];
let update = mul_gpu_float(ratio, a_kj);
work_values[ij_pos] = sub_gpu_float(work_values[ij_pos], update);
}
}
}
}
}
split_lu(&h_row_ptr, &h_col_idx, &work_values, n)
}
fn find_col_in_row(col_slice: &[i32], target_col: i32) -> Option<usize> {
col_slice.iter().position(|&c| c == target_col)
}
fn analyze_ilu0_levels(row_ptr: &[i32], col_idx: &[i32], n: u32) -> SparseResult<Vec<Vec<u32>>> {
let n_usize = n as usize;
let mut depth = vec![0u32; n_usize];
let mut max_depth: u32 = 0;
for i in 0..n_usize {
let start = row_ptr[i] as usize;
let end = row_ptr[i + 1] as usize;
let mut max_dep = 0u32;
for &cj in &col_idx[start..end] {
let j = cj as usize;
if j < i {
let d = depth[j] + 1;
if d > max_dep {
max_dep = d;
}
}
}
depth[i] = max_dep;
if max_dep > max_depth {
max_depth = max_dep;
}
}
let num_levels = max_depth as usize + 1;
let mut levels: Vec<Vec<u32>> = vec![Vec::new(); num_levels];
for (i, &d) in depth.iter().enumerate() {
levels[d as usize].push(i as u32);
}
Ok(levels)
}
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))
}
fn emit_ilu0_kernel<T: GpuFloat>(sm: SmVersion) -> SparseResult<String> {
let elem_bytes = T::size_u32();
let is_f64 = T::SIZE == 8;
KernelBuilder::new("ilu0_level")
.target(sm)
.param("row_ptr", PtxType::U64)
.param("col_idx", PtxType::U64)
.param("values", PtxType::U64)
.param("level_rows", PtxType::U64)
.param("num_level_rows", PtxType::U32)
.body(move |b| {
let gid = b.global_thread_id_x();
let num_level_rows = b.load_param_u32("num_level_rows");
let gid_inner = gid.clone();
b.if_lt_u32(gid, num_level_rows, move |b| {
let tid = gid_inner;
let level_rows_ptr = b.load_param_u64("level_rows");
let _row_ptr_base = b.load_param_u64("row_ptr");
let _col_idx_base = b.load_param_u64("col_idx");
let _values_base = b.load_param_u64("values");
let _row_addr = b.byte_offset_addr(level_rows_ptr, tid, 4);
let _ = elem_bytes;
let _ = is_f64;
});
b.ret();
})
.build()
.map_err(|e| SparseError::PtxGeneration(e.to_string()))
}
fn div_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let a_bits = a.to_bits_u64();
let b_bits = b.to_bits_u64();
if T::SIZE == 4 {
let fa = f32::from_bits(a_bits as u32);
let fb = f32::from_bits(b_bits as u32);
T::from_bits_u64(u64::from((fa / fb).to_bits()))
} else {
let fa = f64::from_bits(a_bits);
let fb = f64::from_bits(b_bits);
T::from_bits_u64((fa / fb).to_bits())
}
}
fn mul_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let a_bits = a.to_bits_u64();
let b_bits = b.to_bits_u64();
if T::SIZE == 4 {
let fa = f32::from_bits(a_bits as u32);
let fb = f32::from_bits(b_bits as u32);
T::from_bits_u64(u64::from((fa * fb).to_bits()))
} else {
let fa = f64::from_bits(a_bits);
let fb = f64::from_bits(b_bits);
T::from_bits_u64((fa * fb).to_bits())
}
}
fn sub_gpu_float<T: GpuFloat>(a: T, b: T) -> T {
let a_bits = a.to_bits_u64();
let b_bits = b.to_bits_u64();
if T::SIZE == 4 {
let fa = f32::from_bits(a_bits as u32);
let fb = f32::from_bits(b_bits as u32);
T::from_bits_u64(u64::from((fa - fb).to_bits()))
} else {
let fa = f64::from_bits(a_bits);
let fb = f64::from_bits(b_bits);
T::from_bits_u64((fa - fb).to_bits())
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxicuda_ptx::arch::SmVersion;
#[test]
fn ilu0_kernel_ptx_generates_f32() {
let ptx = emit_ilu0_kernel::<f32>(SmVersion::Sm80);
assert!(ptx.is_ok());
let ptx_str = ptx.expect("test: PTX gen should succeed");
assert!(ptx_str.contains(".entry ilu0_level"));
}
#[test]
fn ilu0_kernel_ptx_generates_f64() {
let ptx = emit_ilu0_kernel::<f64>(SmVersion::Sm80);
assert!(ptx.is_ok());
}
#[test]
fn ilu0_levels_identity() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let levels = analyze_ilu0_levels(&row_ptr, &col_idx, 3);
assert!(levels.is_ok());
let levels = levels.expect("test: levels should succeed");
assert_eq!(levels.len(), 1);
assert_eq!(levels[0].len(), 3);
}
#[test]
fn host_float_arithmetic() {
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;
let result = div_gpu_float(a, b);
assert!((result - 3.0_f64).abs() < 1e-12);
}
#[test]
fn find_col_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));
}
}