ruSPARSE 0.1.1

Ruda's sparse-computing library, reusing shared tensors, runtimes, and device backends.
Documentation
use super::*;
use crate::{Operation, symbolic::{symbolic_product, symbolic_sum}};

pub fn csr_sum_pattern(a: CsrMatrix<'_>, b: CsrMatrix<'_>) -> Result<(CsrMatrixOwned, Vec<u32>, Vec<u32>), SparseError> {
    if a.rows() != b.rows() || a.columns() != b.columns() {
        return Err(SparseError::DimensionMismatch("CSR sum operands must have the same shape"));
    }
    let symbolic = symbolic_sum(a.into(), b.into())?;
    let left = crate::symbolic::sum_entry_mapping(a.into(), &symbolic, a.index_base())?;
    let right = crate::symbolic::sum_entry_mapping(b.into(), &symbolic, a.index_base())?;
    let values = vec![0.0f32; symbolic.column_indices.len()];
    let matrix = CsrMatrixOwned::new(a.rows(), a.columns(), symbolic.row_offsets, symbolic.column_indices, values, a.index_base())?;
    Ok((matrix, left, right))
}

pub fn csrgeam(
    operation_a: Operation, operation_b: Operation,
    alpha: f32, a: CsrMatrix<'_>, beta: f32, b: CsrMatrix<'_>,
) -> Result<CsrMatrixOwned, SparseError> {
    let transposed_a = transpose(a, operation_a)?;
    let transposed_b = transpose(b, operation_b)?;
    let a = transposed_a.as_ref().map_or(a, CsrMatrixOwned::as_ref);
    let b = transposed_b.as_ref().map_or(b, CsrMatrixOwned::as_ref);
    if a.rows() != b.rows() || a.columns() != b.columns() {
        return Err(SparseError::DimensionMismatch("op(A) and op(B) must have the same shape"));
    }
    let symbolic = symbolic_sum(a.into(), b.into())?;
    let mut values = vec![0.0_f32; symbolic.column_indices.len()];
    let output_base = a.index_base().value();
    for row in 0..a.rows() {
        let start = (symbolic.row_offsets[row] - output_base) as usize;
        let end = (symbolic.row_offsets[row + 1] - output_base) as usize;
        let columns = &symbolic.column_indices[start..end];
        for (matrix, scale) in [(a, alpha), (b, beta)] {
            let base = matrix.index_base().value();
            for entry in (matrix.row_offsets()[row] - base) as usize..(matrix.row_offsets()[row + 1] - base) as usize {
                let column = matrix.column_indices()[entry] - base + output_base;
                let destination = start + columns.binary_search(&column)
                    .map_err(|_| SparseError::InvalidColumnIndex)?;
                values[destination] = scale.mul_add(matrix.values()[entry], values[destination]);
            }
        }
    }
    CsrMatrixOwned::new(a.rows(), a.columns(), symbolic.row_offsets, symbolic.column_indices, values, a.index_base())
}

pub fn csrgemm(
    operation_a: Operation, operation_b: Operation,
    alpha: f32, a: CsrMatrix<'_>, b: CsrMatrix<'_>,
) -> Result<CsrMatrixOwned, SparseError> {
    let transposed_a = transpose(a, operation_a)?;
    let transposed_b = transpose(b, operation_b)?;
    let a = transposed_a.as_ref().map_or(a, CsrMatrixOwned::as_ref);
    let b = transposed_b.as_ref().map_or(b, CsrMatrixOwned::as_ref);
    if a.columns() != b.rows() {
        return Err(SparseError::DimensionMismatch("op(A) columns must equal op(B) rows"));
    }
    let symbolic = symbolic_product(a.into(), b.into())?;
    let mut values = vec![0.0_f32; symbolic.column_indices.len()];
    let a_base = a.index_base().value();
    let b_base = b.index_base().value();
    for row in 0..a.rows() {
        let start = (symbolic.row_offsets[row] - a_base) as usize;
        let end = (symbolic.row_offsets[row + 1] - a_base) as usize;
        let columns = &symbolic.column_indices[start..end];
        for entry in (a.row_offsets()[row] - a_base) as usize..(a.row_offsets()[row + 1] - a_base) as usize {
            let inner = (a.column_indices()[entry] - a_base) as usize;
            for other in (b.row_offsets()[inner] - b_base) as usize..(b.row_offsets()[inner + 1] - b_base) as usize {
                let column = b.column_indices()[other] - b_base + a_base;
                let destination = start + columns.binary_search(&column)
                    .map_err(|_| SparseError::InvalidColumnIndex)?;
                values[destination] = a.values()[entry].mul_add(b.values()[other], values[destination]);
            }
        }
    }
    for value in &mut values { *value *= alpha; }
    CsrMatrixOwned::new(a.rows(), b.columns(), symbolic.row_offsets, symbolic.column_indices, values, a.index_base())
}

pub(super) fn transpose(matrix: CsrMatrix<'_>, operation: Operation) -> Result<Option<CsrMatrixOwned>, SparseError> {
    match operation {
        Operation::None => Ok(None),
        Operation::Transpose | Operation::ConjugateTranspose => matrix.transpose().map(Some),
    }
}

pub fn csr_product_pattern(a: CsrMatrix<'_>, b: CsrMatrix<'_>) -> Result<CsrMatrixOwned, SparseError> {
    if a.columns() != b.rows() {
        return Err(SparseError::DimensionMismatch("CSR product inner dimensions differ"));
    }
    let symbolic = symbolic_product(a.into(), b.into())?;
    let values = vec![0.0f32; symbolic.column_indices.len()];
    CsrMatrixOwned::new(a.rows(), b.columns(), symbolic.row_offsets, symbolic.column_indices, values, a.index_base())
}