use crate::{UtilsError, UtilsResult};
use scirs2_core::ndarray::{Array2, ArrayView1};
use scirs2_core::numeric::{Float, Zero};
type SparseAddResult<T> = UtilsResult<(Vec<T>, Vec<(usize, usize)>)>;
pub fn safe_sparse_dot<T>(
sparse_indices: &[usize],
sparse_values: &[T],
dense: &ArrayView1<T>,
) -> UtilsResult<T>
where
T: Float + Clone,
{
if sparse_indices.len() != sparse_values.len() {
return Err(UtilsError::ShapeMismatch {
expected: vec![sparse_indices.len()],
actual: vec![sparse_values.len()],
});
}
let mut result = T::zero();
for (&idx, &val) in sparse_indices.iter().zip(sparse_values.iter()) {
if idx >= dense.len() {
return Err(UtilsError::InvalidParameter(format!(
"Sparse index {} out of bounds for dense array of length {}",
idx,
dense.len()
)));
}
result = result + val * dense[idx];
}
Ok(result)
}
pub fn safe_sparse_dot_f64(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> UtilsResult<f64> {
if a.len() != b.len() {
return Err(UtilsError::ShapeMismatch {
expected: vec![a.len()],
actual: vec![b.len()],
});
}
let mut result = 0.0;
for (ai, bi) in a.iter().zip(b.iter()) {
result += ai * bi;
}
Ok(result)
}
pub fn safe_sparse_dot_f32(a: &ArrayView1<f32>, b: &ArrayView1<f32>) -> UtilsResult<f32> {
if a.len() != b.len() {
return Err(UtilsError::ShapeMismatch {
expected: vec![a.len()],
actual: vec![b.len()],
});
}
let mut result = 0.0;
for (ai, bi) in a.iter().zip(b.iter()) {
result += ai * bi;
}
Ok(result)
}
pub fn sparse_transpose<T: Clone + Zero + PartialEq>(
values: &[T],
row_indices: &[usize],
col_indices: &[usize],
shape: (usize, usize),
) -> UtilsResult<(Vec<T>, Vec<usize>, Vec<usize>)> {
let (_nrows, ncols) = shape;
let mut col_counts = vec![0; ncols];
for &col_idx in col_indices {
if col_idx >= ncols {
return Err(UtilsError::InvalidParameter(format!(
"Column index {} exceeds matrix width {}",
col_idx, ncols
)));
}
col_counts[col_idx] += 1;
}
let mut transpose_values = Vec::with_capacity(values.len());
let mut transpose_rows = Vec::with_capacity(values.len());
let mut transpose_cols = Vec::with_capacity(values.len());
for (idx, (value, &row_idx)) in values.iter().zip(row_indices.iter()).enumerate() {
let col_idx = col_indices[idx];
transpose_values.push(value.clone());
transpose_rows.push(col_idx); transpose_cols.push(row_idx); }
Ok((transpose_values, transpose_rows, transpose_cols))
}
pub fn sparse_add<T>(
a_values: &[T],
a_indices: &[(usize, usize)],
b_values: &[T],
b_indices: &[(usize, usize)],
) -> SparseAddResult<T>
where
T: Float + Clone,
{
let mut result_map = std::collections::HashMap::new();
for (idx, &value) in a_indices.iter().zip(a_values.iter()) {
*result_map.entry(idx).or_insert(T::zero()) =
*result_map.get(&idx).unwrap_or(&T::zero()) + value;
}
for (idx, &value) in b_indices.iter().zip(b_values.iter()) {
*result_map.entry(idx).or_insert(T::zero()) =
*result_map.get(&idx).unwrap_or(&T::zero()) + value;
}
let mut result_values = Vec::new();
let mut result_indices = Vec::new();
for (idx, value) in result_map {
if value.abs() > T::from(1e-12).expect("operation should succeed") {
result_values.push(value);
result_indices.push(*idx);
}
}
Ok((result_values, result_indices))
}
pub fn sparse_diag<T: Clone + Zero>(
diagonal: &[T],
) -> UtilsResult<(Vec<T>, Vec<usize>, Vec<usize>)> {
let _n = diagonal.len();
let mut values = Vec::new();
let mut row_indices = Vec::new();
let mut col_indices = Vec::new();
for (i, value) in diagonal.iter().enumerate() {
if !value.is_zero() {
values.push(value.clone());
row_indices.push(i);
col_indices.push(i);
}
}
Ok((values, row_indices, col_indices))
}
pub fn densify_threshold<T>(
values: &[T],
indices: &[(usize, usize)],
shape: (usize, usize),
threshold: f64,
) -> UtilsResult<Option<Array2<T>>>
where
T: Clone + Zero,
{
let (nrows, ncols) = shape;
let total_elements = nrows * ncols;
let nnz = values.len();
let density = nnz as f64 / total_elements as f64;
if density > threshold {
let mut dense = Array2::zeros(shape);
for (idx, value) in indices.iter().zip(values.iter()) {
let (row, col) = idx;
if *row >= nrows || *col >= ncols {
return Err(UtilsError::InvalidParameter(format!(
"Index ({}, {}) out of bounds for shape ({}, {})",
row, col, nrows, ncols
)));
}
dense[[*row, *col]] = value.clone();
}
Ok(Some(dense))
} else {
Ok(None)
}
}