use alloc::vec;
use alloc::vec::Vec;
use crate::scalar::Scalar;
use super::CsrMatrix;
use super::add::DimensionMismatch;
use super::sorted_csr::SortedCsrMatrix;
pub fn spmm_csr<T: Scalar>(
a: &CsrMatrix<T>,
b: &CsrMatrix<T>,
) -> Result<SortedCsrMatrix<T>, DimensionMismatch> {
if a.cols() != b.rows() {
return Err(DimensionMismatch::Shape);
}
let m = a.rows();
let n = b.cols();
let mut out_row_ptr = vec![0u32; m + 1];
let mut out_col: Vec<u32> = Vec::new();
let mut out_val: Vec<T> = Vec::new();
let mut dense = vec![T::zero(); n];
let mut touched: Vec<usize> = Vec::new();
let mut in_touched = vec![false; n];
let a_row_ptr = a.row_ptr();
let a_col = a.col_indices();
let a_val = a.values();
let b_row_ptr = b.row_ptr();
let b_col = b.col_indices();
let b_val = b.values();
for r in 0..m {
for ka in a_row_ptr[r] as usize..a_row_ptr[r + 1] as usize {
let k = a_col[ka] as usize;
let av = a_val[ka];
for kb in b_row_ptr[k] as usize..b_row_ptr[k + 1] as usize {
let j = b_col[kb] as usize;
let bv = b_val[kb];
let prev = dense[j];
dense[j] = prev.add(av.mul(bv));
if !in_touched[j] {
touched.push(j);
in_touched[j] = true;
}
}
}
touched.sort_unstable();
for &col in &touched {
if dense[col] != T::zero() {
out_col.push(col as u32);
out_val.push(dense[col]);
}
dense[col] = T::zero();
in_touched[col] = false;
}
touched.clear();
out_row_ptr[r + 1] =
u32::try_from(out_col.len()).map_err(|_| DimensionMismatch::DimensionOverflow)?;
}
Ok(SortedCsrMatrix::from_sorted_unchecked(CsrMatrix::new_raw(
m,
n,
out_row_ptr,
out_col,
out_val,
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn s2_spmm_exact_cancellation_zeros() {
let a = CsrMatrix::new(1, 2, vec![0, 2], vec![0, 1], vec![1.0_f64, -1.0]).unwrap();
let b = CsrMatrix::new(2, 1, vec![0, 1, 2], vec![0, 0], vec![1.0_f64, 1.0]).unwrap();
let c = spmm_csr(&a, &b).unwrap();
assert_eq!(c.nnz(), 0);
}
}