use oxicuda_blas::GpuFloat;
use oxicuda_memory::DeviceBuffer;
use crate::error::{SparseError, SparseResult};
pub struct CooMatrix<T: GpuFloat> {
rows: u32,
cols: u32,
nnz: u32,
row_idx: DeviceBuffer<i32>,
col_idx: DeviceBuffer<i32>,
values: DeviceBuffer<T>,
sorted: bool,
}
impl<T: GpuFloat> CooMatrix<T> {
pub fn from_host(
rows: u32,
cols: u32,
row_idx: &[i32],
col_idx: &[i32],
values: &[T],
) -> SparseResult<Self> {
if rows == 0 || cols == 0 {
return Err(SparseError::InvalidFormat(
"rows and cols must be non-zero".to_string(),
));
}
let nnz = values.len();
if nnz == 0 {
return Err(SparseError::ZeroNnz);
}
if row_idx.len() != nnz || col_idx.len() != nnz {
return Err(SparseError::InvalidFormat(format!(
"row_idx ({}), col_idx ({}), and values ({}) must have equal length",
row_idx.len(),
col_idx.len(),
nnz
)));
}
let d_row_idx = DeviceBuffer::from_host(row_idx)?;
let d_col_idx = DeviceBuffer::from_host(col_idx)?;
let d_values = DeviceBuffer::from_host(values)?;
Ok(Self {
rows,
cols,
nnz: nnz as u32,
row_idx: d_row_idx,
col_idx: d_col_idx,
values: d_values,
sorted: false,
})
}
pub fn from_device(
rows: u32,
cols: u32,
nnz: u32,
row_idx: DeviceBuffer<i32>,
col_idx: DeviceBuffer<i32>,
values: DeviceBuffer<T>,
) -> SparseResult<Self> {
if row_idx.len() != nnz as usize
|| col_idx.len() != nnz as usize
|| values.len() != nnz as usize
{
return Err(SparseError::InvalidFormat(
"all arrays must have length equal to nnz".to_string(),
));
}
Ok(Self {
rows,
cols,
nnz,
row_idx,
col_idx,
values,
sorted: false,
})
}
#[must_use]
pub fn with_sorted(mut self, sorted: bool) -> Self {
self.sorted = sorted;
self
}
pub fn to_host(&self) -> SparseResult<(Vec<i32>, Vec<i32>, Vec<T>)> {
let mut h_row_idx = vec![0i32; self.row_idx.len()];
let mut h_col_idx = vec![0i32; self.col_idx.len()];
let mut h_values = vec![T::gpu_zero(); self.values.len()];
self.row_idx.copy_to_host(&mut h_row_idx)?;
self.col_idx.copy_to_host(&mut h_col_idx)?;
self.values.copy_to_host(&mut h_values)?;
Ok((h_row_idx, h_col_idx, h_values))
}
pub fn to_csr(&self) -> SparseResult<super::CsrMatrix<T>> {
let (h_row_idx, h_col_idx, h_values) = self.to_host()?;
let mut row_counts = vec![0i32; self.rows as usize];
for &r in &h_row_idx {
row_counts[r as usize] += 1;
}
let mut h_row_ptr = vec![0i32; self.rows as usize + 1];
for i in 0..self.rows as usize {
h_row_ptr[i + 1] = h_row_ptr[i] + row_counts[i];
}
let mut h_csr_col_idx = vec![0i32; self.nnz as usize];
let mut h_csr_values = vec![T::gpu_zero(); self.nnz as usize];
let mut write_pos = h_row_ptr.clone();
for i in 0..self.nnz as usize {
let row = h_row_idx[i] as usize;
let dest = write_pos[row] as usize;
h_csr_col_idx[dest] = h_col_idx[i];
h_csr_values[dest] = h_values[i];
write_pos[row] += 1;
}
super::CsrMatrix::from_host(
self.rows,
self.cols,
&h_row_ptr,
&h_csr_col_idx,
&h_csr_values,
)
}
pub fn to_csc(&self) -> SparseResult<super::CscMatrix<T>> {
let (h_row_idx, h_col_idx, h_values) = self.to_host()?;
let mut col_counts = vec![0i32; self.cols as usize];
for &c in &h_col_idx {
col_counts[c as usize] += 1;
}
let mut h_col_ptr = vec![0i32; self.cols as usize + 1];
for i in 0..self.cols as usize {
h_col_ptr[i + 1] = h_col_ptr[i] + col_counts[i];
}
let mut h_csc_row_idx = vec![0i32; self.nnz as usize];
let mut h_csc_values = vec![T::gpu_zero(); self.nnz as usize];
let mut write_pos = h_col_ptr.clone();
for i in 0..self.nnz as usize {
let col = h_col_idx[i] as usize;
let dest = write_pos[col] as usize;
h_csc_row_idx[dest] = h_row_idx[i];
h_csc_values[dest] = h_values[i];
write_pos[col] += 1;
}
super::CscMatrix::from_host(
self.rows,
self.cols,
&h_col_ptr,
&h_csc_row_idx,
&h_csc_values,
)
}
#[inline]
pub fn is_sorted(&self) -> bool {
self.sorted
}
#[inline]
pub fn rows(&self) -> u32 {
self.rows
}
#[inline]
pub fn cols(&self) -> u32 {
self.cols
}
#[inline]
pub fn nnz(&self) -> u32 {
self.nnz
}
#[inline]
pub fn row_idx(&self) -> &DeviceBuffer<i32> {
&self.row_idx
}
#[inline]
pub fn col_idx(&self) -> &DeviceBuffer<i32> {
&self.col_idx
}
#[inline]
pub fn values(&self) -> &DeviceBuffer<T> {
&self.values
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coo_validation_mismatched_lengths() {
let result = CooMatrix::<f32>::from_host(3, 3, &[0, 1], &[0, 1, 2], &[1.0; 3]);
assert!(result.is_err());
}
#[test]
fn coo_validation_zero_nnz() {
let result = CooMatrix::<f32>::from_host(2, 2, &[], &[], &[]);
assert!(matches!(result, Err(SparseError::ZeroNnz)));
}
#[test]
fn coo_sorted_flag() {
}
}