1use crate::prelude::*;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum SparseFormat {
25 Csr,
28 Csc,
31}
32
33impl SparseFormat {
34 #[inline]
40 pub fn flipped(&self) -> Self {
41 match self {
42 SparseFormat::Csr => SparseFormat::Csc,
43 SparseFormat::Csc => SparseFormat::Csr,
44 }
45 }
46}
47
48#[derive(Clone, Debug)]
58pub struct CompressedSparse<T> {
59 pub data: Vec<T>,
61 pub indices: Vec<u32>,
63 pub indptr: Vec<u32>,
65 pub format: SparseFormat,
67 pub shape: (usize, usize),
69}
70
71impl<T: Copy> CompressedSparse<T> {
72 pub fn from_parts(
86 data: Vec<T>,
87 indices: Vec<u32>,
88 indptr: Vec<u32>,
89 format: SparseFormat,
90 shape: (usize, usize),
91 ) -> Result<Self, EdgeErrors> {
92 let out = Self {
93 data,
94 indices,
95 indptr,
96 format,
97 shape,
98 };
99 out.validate()?;
100 Ok(out)
101 }
102
103 pub fn validate(&self) -> Result<(), EdgeErrors> {
113 if self.data.len() != self.indices.len() {
114 return Err(EdgeErrors::MalformedSparse(format!(
115 "data has {} values but indices has {}",
116 self.data.len(),
117 self.indices.len()
118 )));
119 }
120
121 let expected_indptr = self.outer_len() + 1;
122 if self.indptr.len() != expected_indptr {
123 return Err(EdgeErrors::MalformedSparse(format!(
124 "indptr has length {} but the {:?} layout of a {:?} matrix needs {}",
125 self.indptr.len(),
126 self.format,
127 self.shape,
128 expected_indptr
129 )));
130 }
131
132 if self.indptr[0] != 0 {
133 return Err(EdgeErrors::MalformedSparse(format!(
134 "indptr must start at 0, starts at {}",
135 self.indptr[0]
136 )));
137 }
138
139 for w in self.indptr.windows(2) {
140 if w[1] < w[0] {
141 return Err(EdgeErrors::MalformedSparse(format!(
142 "indptr is not non-decreasing: {} follows {}",
143 w[1], w[0]
144 )));
145 }
146 }
147
148 let nnz = *self.indptr.last().unwrap_or(&0) as usize;
149 if nnz != self.data.len() {
150 return Err(EdgeErrors::MalformedSparse(format!(
151 "indptr ends at {} but data has {} values",
152 nnz,
153 self.data.len()
154 )));
155 }
156
157 let inner = self.inner_len();
158 if let Some(&bad) = self.indices.iter().find(|&&i| i as usize >= inner) {
159 return Err(EdgeErrors::MalformedSparse(format!(
160 "index {bad} is out of range for a non-compressed axis of length {inner}"
161 )));
162 }
163
164 Ok(())
165 }
166
167 #[inline]
173 pub fn nrows(&self) -> usize {
174 self.shape.0
175 }
176
177 #[inline]
183 pub fn ncols(&self) -> usize {
184 self.shape.1
185 }
186
187 #[inline]
193 pub fn nnz(&self) -> usize {
194 self.data.len()
195 }
196
197 #[inline]
203 pub fn outer_len(&self) -> usize {
204 match self.format {
205 SparseFormat::Csr => self.shape.0,
206 SparseFormat::Csc => self.shape.1,
207 }
208 }
209
210 #[inline]
216 pub fn inner_len(&self) -> usize {
217 match self.format {
218 SparseFormat::Csr => self.shape.1,
219 SparseFormat::Csc => self.shape.0,
220 }
221 }
222
223 #[inline]
236 pub fn outer(&self, outer: usize) -> (&[u32], &[T]) {
237 let start = self.indptr[outer] as usize;
238 let end = self.indptr[outer + 1] as usize;
239 (&self.indices[start..end], &self.data[start..end])
240 }
241
242 pub fn transpose(self) -> Self {
250 Self {
251 data: self.data,
252 indices: self.indices,
253 indptr: self.indptr,
254 format: self.format.flipped(),
255 shape: (self.shape.1, self.shape.0),
256 }
257 }
258
259 pub fn convert(&self) -> Self
268 where
269 T: Default,
270 {
271 let outer_new = self.inner_len();
272 let nnz = self.nnz();
273
274 let mut counts = vec![0u32; outer_new + 1];
276 for &idx in &self.indices {
277 counts[idx as usize + 1] += 1;
278 }
279 for i in 0..outer_new {
280 counts[i + 1] += counts[i];
281 }
282 let indptr_new = counts.clone();
283
284 let mut data_new = vec![T::default(); nnz];
285 let mut indices_new = vec![0u32; nnz];
286 let mut cursor = counts;
287
288 for outer in 0..self.outer_len() {
289 let start = self.indptr[outer] as usize;
290 let end = self.indptr[outer + 1] as usize;
291 for k in start..end {
292 let inner = self.indices[k] as usize;
293 let dst = cursor[inner] as usize;
294 data_new[dst] = self.data[k];
295 indices_new[dst] = outer as u32;
296 cursor[inner] += 1;
297 }
298 }
299
300 Self {
301 data: data_new,
302 indices: indices_new,
303 indptr: indptr_new,
304 format: self.format.flipped(),
305 shape: self.shape,
306 }
307 }
308
309 pub fn from_dense<F>(
326 dense: &[T],
327 n_rows: usize,
328 n_cols: usize,
329 format: SparseFormat,
330 is_zero: F,
331 ) -> Result<Self, EdgeErrors>
332 where
333 F: Fn(&T) -> bool,
334 {
335 if dense.len() != n_rows * n_cols {
336 return Err(EdgeErrors::LengthMismatch {
337 name: "dense",
338 expected: n_rows * n_cols,
339 got: dense.len(),
340 });
341 }
342
343 let (outer_len, inner_len) = match format {
344 SparseFormat::Csr => (n_rows, n_cols),
345 SparseFormat::Csc => (n_cols, n_rows),
346 };
347
348 let mut data = Vec::new();
349 let mut indices = Vec::new();
350 let mut indptr = Vec::with_capacity(outer_len + 1);
351 indptr.push(0u32);
352
353 for o in 0..outer_len {
354 for i in 0..inner_len {
355 let flat = match format {
356 SparseFormat::Csr => o * n_cols + i,
357 SparseFormat::Csc => i * n_cols + o,
358 };
359 let v = dense[flat];
360 if !is_zero(&v) {
361 data.push(v);
362 indices.push(i as u32);
363 }
364 }
365 indptr.push(data.len() as u32);
366 }
367
368 Self::from_parts(data, indices, indptr, format, (n_rows, n_cols))
369 }
370
371 pub fn to_dense(&self, zero: T) -> Vec<T> {
383 let mut out = vec![zero; self.nrows() * self.ncols()];
384 for o in 0..self.outer_len() {
385 let (idx, vals) = self.outer(o);
386 for (&i, &v) in idx.iter().zip(vals.iter()) {
387 let flat = match self.format {
388 SparseFormat::Csr => o * self.ncols() + i as usize,
389 SparseFormat::Csc => i as usize * self.ncols() + o,
390 };
391 out[flat] = v;
392 }
393 }
394 out
395 }
396}
397
398#[cfg(test)]
403mod tests {
404 use super::*;
405
406 fn dense_fixture() -> (Vec<f64>, usize, usize) {
408 let dense = vec![
409 1.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 4.0, 5.0, ];
413 (dense, 3, 4)
414 }
415
416 #[test]
417 fn test_from_dense_csr_round_trips() {
418 let (dense, r, c) = dense_fixture();
419 let m =
420 CompressedSparse::from_dense(&dense, r, c, SparseFormat::Csr, |v| *v == 0.0).unwrap();
421 assert_eq!(m.nnz(), 5);
422 assert_eq!(m.to_dense(0.0), dense);
423 }
424
425 #[test]
426 fn test_from_dense_csc_round_trips() {
427 let (dense, r, c) = dense_fixture();
428 let m =
429 CompressedSparse::from_dense(&dense, r, c, SparseFormat::Csc, |v| *v == 0.0).unwrap();
430 assert_eq!(m.nnz(), 5);
431 assert_eq!(m.to_dense(0.0), dense);
432 }
433
434 #[test]
435 fn test_convert_preserves_the_matrix() {
436 let (dense, r, c) = dense_fixture();
437 let csr =
438 CompressedSparse::from_dense(&dense, r, c, SparseFormat::Csr, |v| *v == 0.0).unwrap();
439 let csc = csr.convert();
440 assert_eq!(csc.format, SparseFormat::Csc);
441 assert_eq!(csc.shape, (r, c));
442 assert_eq!(csc.to_dense(0.0), dense);
443 assert_eq!(csc.convert().to_dense(0.0), dense);
445 }
446
447 #[test]
450 fn test_transpose_is_a_relabel_not_a_copy() {
451 let (dense, r, c) = dense_fixture();
452 let csr =
453 CompressedSparse::from_dense(&dense, r, c, SparseFormat::Csr, |v| *v == 0.0).unwrap();
454 let data_before = csr.data.clone();
455 let t = csr.transpose();
456
457 assert_eq!(t.format, SparseFormat::Csc);
458 assert_eq!(t.shape, (c, r));
459 assert_eq!(t.data, data_before);
460
461 let dense_t = t.to_dense(0.0);
463 for i in 0..c {
464 for j in 0..r {
465 assert_eq!(dense_t[i * r + j], dense[j * c + i]);
466 }
467 }
468 }
469
470 #[test]
471 fn test_outer_borrows_a_gene_slice() {
472 let (dense, r, c) = dense_fixture();
473 let csr =
474 CompressedSparse::from_dense(&dense, r, c, SparseFormat::Csr, |v| *v == 0.0).unwrap();
475
476 let (idx, vals) = csr.outer(0);
477 assert_eq!(idx, &[0, 2]);
478 assert_eq!(vals, &[1.0, 2.0]);
479
480 let (idx, vals) = csr.outer(1);
482 assert!(idx.is_empty());
483 assert!(vals.is_empty());
484
485 let (idx, vals) = csr.outer(2);
486 assert_eq!(idx, &[1, 2, 3]);
487 assert_eq!(vals, &[3.0, 4.0, 5.0]);
488 }
489
490 #[test]
491 fn test_validate_rejects_out_of_range_index() {
492 let err = CompressedSparse::from_parts(
493 vec![1.0_f64],
494 vec![99],
495 vec![0, 1, 1, 1],
496 SparseFormat::Csr,
497 (3, 4),
498 )
499 .unwrap_err();
500 assert!(matches!(err, EdgeErrors::MalformedSparse(_)));
501 }
502
503 #[test]
504 fn test_validate_rejects_bad_indptr_length() {
505 let err = CompressedSparse::from_parts(
506 vec![1.0_f64],
507 vec![0],
508 vec![0, 1],
509 SparseFormat::Csr,
510 (3, 4),
511 )
512 .unwrap_err();
513 assert!(matches!(err, EdgeErrors::MalformedSparse(_)));
514 }
515
516 #[test]
517 fn test_convert_handles_empty_matrix() {
518 let m: CompressedSparse<f64> =
519 CompressedSparse::from_parts(vec![], vec![], vec![0; 4], SparseFormat::Csr, (3, 4))
520 .unwrap();
521 let c = m.convert();
522 assert_eq!(c.nnz(), 0);
523 assert_eq!(c.indptr.len(), 5);
524 assert_eq!(c.to_dense(0.0), vec![0.0; 12]);
525 }
526}