use crate::coo::CooMatrix;
use crate::error::SparseError;
use crate::validate::validate_csr_invariants;
#[derive(Debug, Clone)]
pub struct CsrMatrix<T> {
rows: usize,
cols: usize,
offsets: Vec<u32>,
col_indices: Vec<u32>,
values: Vec<T>,
}
impl<T: Clone + Default> CsrMatrix<T> {
pub fn new(
rows: usize,
cols: usize,
offsets: Vec<u32>,
col_indices: Vec<u32>,
values: Vec<T>,
) -> Result<Self, SparseError> {
validate_csr_invariants(rows, cols, &offsets, &col_indices, values.len())?;
Ok(Self {
rows,
cols,
offsets,
col_indices,
values,
})
}
#[must_use]
pub fn from_coo(coo: &CooMatrix<T>) -> Self
where
T: std::ops::AddAssign + Copy,
{
let rows = coo.rows;
let cols = coo.cols;
let nnz = coo.nnz();
if nnz == 0 {
return Self {
rows,
cols,
offsets: vec![0; rows + 1],
col_indices: Vec::new(),
values: Vec::new(),
};
}
let mut row_counts = vec![0u32; rows];
for &r in &coo.row_indices {
row_counts[r as usize] += 1;
}
let mut offsets = vec![0u32; rows + 1];
for i in 0..rows {
offsets[i + 1] = offsets[i] + row_counts[i];
}
let mut col_indices = vec![0u32; nnz];
let mut values = vec![T::default(); nnz];
let mut write_pos = offsets.clone();
for idx in 0..nnz {
let r = coo.row_indices[idx] as usize;
let pos = write_pos[r] as usize;
col_indices[pos] = coo.col_indices[idx];
values[pos] = coo.values[idx];
write_pos[r] += 1;
}
for i in 0..rows {
let start = offsets[i] as usize;
let end = offsets[i + 1] as usize;
if end - start > 1 {
for j in (start + 1)..end {
let mut k = j;
while k > start && col_indices[k - 1] > col_indices[k] {
col_indices.swap(k - 1, k);
values.swap(k - 1, k);
k -= 1;
}
}
}
}
Self {
rows,
cols,
offsets,
col_indices,
values,
}
}
#[must_use]
pub fn identity(n: usize) -> Self
where
T: From<f32>,
{
let offsets: Vec<u32> = (0..=n).map(|i| i as u32).collect();
let col_indices: Vec<u32> = (0..n).map(|i| i as u32).collect();
let values: Vec<T> = (0..n).map(|_| T::from(1.0)).collect();
Self {
rows: n,
cols: n,
offsets,
col_indices,
values,
}
}
#[must_use]
pub fn rows(&self) -> usize {
self.rows
}
#[must_use]
pub fn cols(&self) -> usize {
self.cols
}
#[must_use]
pub fn nnz(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn offsets(&self) -> &[u32] {
&self.offsets
}
#[must_use]
pub fn col_indices(&self) -> &[u32] {
&self.col_indices
}
#[must_use]
pub fn values(&self) -> &[T] {
&self.values
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn avg_nnz_per_row(&self) -> f64 {
if self.rows == 0 {
0.0
} else {
self.nnz() as f64 / self.rows as f64
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn row_length_variance(&self) -> f64 {
if self.rows == 0 {
return 0.0;
}
let mean = self.avg_nnz_per_row();
let sum_sq: f64 = (0..self.rows)
.map(|i| {
let len = f64::from(self.offsets[i + 1] - self.offsets[i]);
(len - mean) * (len - mean)
})
.sum();
sum_sq / self.rows as f64
}
#[must_use]
pub fn to_dense(&self) -> Vec<T>
where
T: Copy + std::ops::AddAssign,
{
let mut dense = vec![T::default(); self.rows * self.cols];
for i in 0..self.rows {
let start = self.offsets[i] as usize;
let end = self.offsets[i + 1] as usize;
for idx in start..end {
let j = self.col_indices[idx] as usize;
dense[i * self.cols + j] += self.values[idx];
}
}
dense
}
}