#![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;
pub fn ic0<T: GpuFloat>(handle: &SparseHandle, a: &CsrMatrix<T>) -> SparseResult<CsrMatrix<T>> {
if a.rows() != a.cols() {
return Err(SparseError::DimensionMismatch(format!(
"IC(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_ic0_levels(&h_row_ptr, &h_col_idx, n)?;
let _ptx_result = emit_ic0_kernel::<T>(handle.sm_version());
let mut work = h_values;
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 l_kk = work[diag_pos];
if l_kk == T::gpu_zero() {
return Err(SparseError::SingularMatrix);
}
let a_ik = div_gpu_float(work[nz], l_kk);
work[nz] = a_ik;
for ij in (nz + 1)..row_end {
let j = h_col_idx[ij];
if let Some(kj_off) = find_col_in_row(&h_col_idx[k_start..k_end], j) {
let kj_pos = k_start + kj_off;
let a_kj = work[kj_pos];
let update = mul_gpu_float(a_ik, a_kj);
work[ij] = sub_gpu_float(work[ij], update);
}
}
}
let diag_pos = find_col_in_row(&h_col_idx[row_start..row_end], row as i32);
let diag_pos = match diag_pos {
Some(pos) => row_start + pos,
None => return Err(SparseError::SingularMatrix),
};
let diag_val = to_f64(work[diag_pos]);
if diag_val <= 0.0 {
return Err(SparseError::SingularMatrix);
}
work[diag_pos] = from_f64::<T>(diag_val.sqrt());
}
}
extract_lower_triangle(&h_row_ptr, &h_col_idx, &work, n)
}
fn find_col_in_row(col_slice: &[i32], target_col: i32) -> Option<usize> {
col_slice.iter().position(|&c| c == target_col)
}
fn analyze_ic0_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 extract_lower_triangle<T: GpuFloat>(
row_ptr: &[i32],
col_idx: &[i32],
values: &[T],
n: u32,
) -> SparseResult<CsrMatrix<T>> {
let n_usize = n as usize;
let mut l_row_ptr = vec![0i32; n_usize + 1];
let mut l_col_idx = Vec::new();
let mut l_values = Vec::new();
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_row_ptr[i + 1] = l_col_idx.len() as i32;
}
if l_values.is_empty() {
return Err(SparseError::ZeroNnz);
}
CsrMatrix::from_host(n, n, &l_row_ptr, &l_col_idx, &l_values)
}
fn emit_ic0_kernel<T: GpuFloat>(sm: SmVersion) -> SparseResult<String> {
let _elem_bytes = T::size_u32();
KernelBuilder::new("ic0_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);
});
b.ret();
})
.build()
.map_err(|e| SparseError::PtxGeneration(e.to_string()))
}
fn to_f64<T: GpuFloat>(v: T) -> f64 {
if T::SIZE == 4 {
f64::from(f32::from_bits(v.to_bits_u64() as u32))
} else {
f64::from_bits(v.to_bits_u64())
}
}
fn from_f64<T: GpuFloat>(v: f64) -> T {
if T::SIZE == 4 {
T::from_bits_u64(u64::from((v as f32).to_bits()))
} else {
T::from_bits_u64(v.to_bits())
}
}
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)
}
#[cfg(test)]
mod tests {
use super::*;
use oxicuda_ptx::arch::SmVersion;
#[test]
fn ic0_kernel_ptx_generates_f32() {
let ptx = emit_ic0_kernel::<f32>(SmVersion::Sm80);
assert!(ptx.is_ok());
let ptx_str = ptx.expect("test: PTX gen should succeed");
assert!(ptx_str.contains(".entry ic0_level"));
}
#[test]
fn ic0_kernel_ptx_generates_f64() {
let ptx = emit_ic0_kernel::<f64>(SmVersion::Sm80);
assert!(ptx.is_ok());
}
#[test]
fn ic0_levels_diagonal() {
let row_ptr = vec![0, 1, 2, 3];
let col_idx = vec![0, 1, 2];
let levels = analyze_ic0_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_arithmetic_f32() {
let a = 9.0_f32;
let result = to_f64(a);
assert!((result - 9.0_f64).abs() < 1e-6);
let b = from_f64::<f32>(9.0);
assert!((b - 9.0_f32).abs() < 1e-6);
}
#[test]
fn host_arithmetic_f64() {
let a = 9.0_f64;
let result = to_f64(a);
assert!((result - 9.0_f64).abs() < 1e-12);
}
#[test]
fn find_col_works() {
let cols = [0, 1, 3, 5];
assert_eq!(find_col_in_row(&cols, 3), Some(2));
assert_eq!(find_col_in_row(&cols, 2), None);
}
}