Skip to main content

yui_matrix/sparse/
sp_mat.rs

1//! [`SpMat<R>`]: a sparse matrix in CSC form, over `nalgebra_sparse::CscMatrix`.
2//! The carrier of every differential in this workspace.
3
4use std::ops::{Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign, Range};
5use std::fmt::{Display, Debug};
6use delegate::delegate;
7use itertools::{Itertools, repeat_n};
8use nalgebra_sparse::na::{Scalar, ClosedAddAssign, ClosedSubAssign, ClosedMulAssign};
9use nalgebra_sparse::{CscMatrix, CooMatrix};
10use num_traits::{Zero, One, ToPrimitive};
11use auto_impl_ops::auto_ops;
12use yui_core::abst::{Ring, RingOps};
13use crate::Perm;
14use crate::dense::*;
15use super::sp_vec::SpVec;
16use super::triang::TriangularType;
17
18/// Sparse matrix in compressed sparse column (CSC) format, backed by
19/// `nalgebra_sparse::CscMatrix`.
20///
21/// The fundamental matrix type for all differential maps in the homology
22/// pipeline. Generic over the element ring `R`; bounds are applied per-method.
23///
24/// Note: the underlying CSC may carry explicit zero entries (e.g. after an
25/// in-place subtraction). [`iter`](Self::iter) walks all stored triplets;
26/// [`iter_nz`](Self::iter_nz) filters them.
27#[derive(Clone)]
28pub struct SpMat<R> {
29    inner: CscMatrix<R>
30}
31
32impl<R> SpMat<R> {
33    pub fn try_from_csc_data(
34        num_rows: usize,
35        num_cols: usize,
36        col_offsets: Vec<usize>,
37        row_indices: Vec<usize>,
38        values: Vec<R>,
39    ) -> Option<Self> {
40        let csc = CscMatrix::try_from_csc_data(num_rows, num_cols, col_offsets, row_indices, values);
41        csc.ok().map(SpMat::from)
42    }
43
44    pub(crate) fn inner(&self) -> &CscMatrix<R> {
45        &self.inner
46    }
47
48    pub(crate) fn into_inner(self) -> CscMatrix<R> {
49        self.inner
50    }
51
52    pub fn csc_data(&self) -> (&[usize], &[usize], &[R]) {
53        self.inner.csc_data()
54    }
55
56    pub fn disassemble(self) -> (Vec<usize>, Vec<usize>, Vec<R>) {
57        self.inner.disassemble()
58    }
59
60    pub fn zero(shape: (usize, usize)) -> Self {
61        let csc = CscMatrix::zeros(shape.0, shape.1);
62        Self::from(csc)
63    }
64
65    pub fn is_zero(&self) -> bool
66    where R: Zero {
67        self.inner.values().iter().all(|a| a.is_zero())
68    }
69
70    pub fn id(n: usize) -> Self
71    where R: Scalar + One {
72        let csc = CscMatrix::identity(n);
73        Self::from(csc)
74    }
75
76    pub fn is_id(&self) -> bool
77    where R: Scalar + One + Zero {
78        self.is_square() && self.iter().all(|(i, j, a)|
79            (i == j && a.is_one()) || (i != j && a.is_zero())
80        )
81    }
82
83    pub fn is_triang(&self, t: TriangularType) -> bool
84    where R: Zero {
85        if self.n_rows() != self.n_cols() {
86            return false
87        }
88
89        if t.is_upper() {
90            self.iter_nz().all(|(i, j, _)| i <= j )
91        } else {
92            self.iter_nz().all(|(i, j, _)| i >= j )
93        }
94    }
95
96    /// Iterates the stored `(row, col, value)` triplets — may include explicit zeros.
97    pub fn iter(&self) -> impl Iterator<Item = (usize, usize, &R)> {
98        self.inner.triplet_iter()
99    }
100
101    /// [`iter`](Self::iter), filtered to non-zero values.
102    pub fn iter_nz(&self) -> impl Iterator<Item = (usize, usize, &R)>
103    where R: Zero {
104        self.iter().filter(|e| !e.2.is_zero())
105    }
106
107    pub fn into_dense(self) -> Mat<R>
108    where R: Scalar + Zero + ClosedAddAssign {
109        self.into()
110    }
111
112    pub fn nnz(&self) -> usize {
113        self.inner.nnz()
114    }
115
116    pub fn density(&self) -> f64 {
117        let (m, n) = self.shape();
118        if m == 0 || n == 0 {
119            return 0.0
120        }
121
122        let nnz = self.nnz().to_f64().unwrap();
123        let total = (m * n).to_f64().unwrap();
124
125        nnz / total
126    }
127
128    pub fn redundancy(&self) -> f64
129    where R: Zero {
130        let nnz = self.nnz().to_f64().unwrap();
131        let red = self.iter().filter(|(_, _, a)| a.is_zero()).count().to_f64().unwrap();
132        red / nnz
133    }
134
135    pub fn mean_weight(&self) -> f64
136    where R: Ring, for<'x> &'x R: RingOps<R> {
137        let nnz = self.nnz().to_f64().unwrap();
138        let w = self.iter().map(|(_, _, a)| a.c_weight()).sum::<f64>();
139        w / nnz
140    }
141
142    pub fn block_diag<I>(blocks: I) -> SpMat<R>
143    where I: IntoIterator<Item = SpMat<R>> {
144        let mut shape = (0, 0);
145        let mut col_offsets: Vec<usize> = vec![];
146        let mut row_indices: Vec<usize> = vec![];
147        let mut values: Vec<R> = vec![];
148
149        for a in blocks {
150            let a_shape = a.shape();
151            let (a_cols, a_rows, mut a_vals) = a.disassemble();
152
153            col_offsets.extend(a_cols.iter().map(|i| i + values.len()));
154            col_offsets.pop(); // remove last offset
155
156            row_indices.extend(a_rows.iter().map(|i| i + shape.0));
157            values.append(&mut a_vals);
158
159            shape.0 += a_shape.0;
160            shape.1 += a_shape.1;
161        }
162        col_offsets.push(values.len());
163
164        SpMat::try_from_csc_data(shape.0, shape.1, col_offsets, row_indices, values).unwrap()
165    }
166
167    pub fn map<F, S>(self, f: F) -> SpMat<S>
168    where F: Fn(R) -> S {
169        let (m, n) = self.shape();
170        let (cols, rows, vals) = self.disassemble();
171        let vals = vals.into_iter().map(f).collect_vec();
172        SpMat::<S>::try_from_csc_data(m, n, cols, rows, vals).unwrap()
173    }
174
175    /// Returns the raw `(row_indices, values)` slices of column `j`.
176    /// Borrow-only — no allocation, no value clones.
177    pub(crate) fn col_data(&self, j: usize) -> (&[usize], &[R]) {
178        let (col_offsets, row_indices, values) = self.inner.csc_data();
179        let range = col_offsets[j]..col_offsets[j + 1];
180        (&row_indices[range.clone()], &values[range])
181    }
182}
183
184impl<R> SpMat<R>
185where R: Scalar + Clone + Zero + ClosedAddAssign {
186    /// Builds an `SpMat` of `shape` from `(row, col, value)` triplets. Zero
187    /// values are skipped; duplicates at the same position are summed.
188    pub fn from_entries<T>(shape: (usize, usize), entries: T) -> Self
189    where T: IntoIterator<Item = (usize, usize, R)> {
190        let mut coo = CooMatrix::new(shape.0, shape.1);
191        for (i, j, a) in entries {
192            if a.is_zero() {
193                continue;
194            }
195            coo.push(i, j, a)
196        }
197        let csc = CscMatrix::from(&coo);
198        Self::from(csc)
199    }
200
201    // note: explicitly-stored zeros are dropped (CSC arithmetic keeps cancellation zeros).
202    pub fn from_col_vecs<I>(nrows: usize, vecs: I) -> Self
203    where I: IntoIterator<Item = SpVec<R>> {
204        let mut col_offsets = vec![0];
205        let mut row_indices = vec![];
206        let mut values = vec![];
207
208        for v in vecs.into_iter() {
209            assert_eq!(nrows, v.dim());
210            let (_, mut v_rows, mut v_values) = v.drop_zeros().into_inner().disassemble();
211
212            row_indices.append(&mut v_rows);
213            values.append(&mut v_values);
214            col_offsets.push(row_indices.len());
215        }
216
217        let ncols = col_offsets.len() - 1;
218        SpMat::try_from_csc_data(nrows, ncols, col_offsets, row_indices, values).unwrap()
219    }
220
221    pub fn from_row_major<I>(shape: (usize, usize), data: I) -> Self
222    where I: IntoIterator<Item = R> {
223        let n = shape.1;
224        Self::from_entries(
225            shape,
226            data.into_iter().enumerate().map(|(k, a)| {
227                let (i, j) = (k / n, k % n);
228                (i, j, a)
229            })
230        )
231    }
232
233    pub fn scalar(n: usize, a: &R) -> Self {
234        Self::from_entries((n, n), (0..n).map(|i| (i, i, a.clone())))
235    }
236
237    pub fn diag<I>(shape: (usize, usize), entries: I) -> Self
238    where I: IntoIterator<Item = R> {
239        Self::from_entries(shape, entries.into_iter().enumerate().map(|(i, a)| (i, i, a)))
240    }
241
242    pub fn is_diag(&self) -> bool {
243        self.iter_nz().all(|(i, j, _)| i == j)
244    }
245
246    pub fn col_vec(&self, j: usize) -> SpVec<R>
247    where R: Scalar + Zero + ClosedAddAssign {
248        let col = self.inner.col(j);
249        let row_indices = col.row_indices().to_vec();
250        let values = col.values().to_vec();
251        SpVec::try_from_csc_data(self.n_rows(), row_indices, values).unwrap()
252    }
253
254    pub fn transpose(&self) -> Self {
255        self.inner.transpose().into()
256    }
257
258    /// New `shape`-d matrix whose entry at `f(i, j)` (if `Some`) is `self[(i, j)]`.
259    /// Entries where `f` returns `None` are dropped.
260    pub fn extract<F>(&self, shape: (usize, usize), f: F) -> SpMat<R>
261    where F: Fn(usize, usize) -> Option<(usize, usize)> {
262        SpMat::from_entries(shape, self.iter().filter_map(|(i, j, a)|
263            f(i, j).map(|(i, j)| (i, j, a.clone()))
264        ))
265    }
266
267    pub fn permute(&self, p: &Perm, q: &Perm) -> SpMat<R> {
268        self.extract(self.shape(), |i, j| Some((p.at(i), q.at(j))))
269    }
270
271    pub fn permute_rows(&self, p: &Perm) -> SpMat<R> {
272        let id = Perm::id(self.n_cols());
273        self.permute(p, &id)
274    }
275
276    pub fn permute_cols(&self, q: &Perm) -> SpMat<R> {
277        let id = Perm::id(self.n_rows());
278        self.permute(&id, q)
279    }
280
281    /// Applies the permutations `(p, q)` to `self` and partitions the result
282    /// into four blocks at row/col `r`:
283    ///
284    /// ```text
285    ///   paq = [[a0 | a1],   a0: r×r,     a1: r×(n-r)
286    ///          [a2 | a3]]   a2: (m-r)×r, a3: (m-r)×(n-r)
287    /// ```
288    pub fn permute_and_split(&self, p: &Perm, q: &Perm, r: usize) -> [SpMat<R>; 4] {
289        use std::cmp::Ordering::Less;
290
291        let (m, n) = self.shape();
292        assert!(r <= m && r <= n);
293
294        let [mut a0, mut a1, mut a2, mut a3] = [vec![], vec![], vec![], vec![]];
295
296        for (i, j, v) in self.iter() {
297            let (pi, qj) = (p.at(i), q.at(j));
298            let v = v.clone();
299            match (pi.cmp(&r), qj.cmp(&r)) {
300                (Less, Less) => a0.push((pi,     qj,     v)),
301                (Less, _   ) => a1.push((pi,     qj - r, v)),
302                (_   , Less) => a2.push((pi - r, qj,     v)),
303                (_   , _   ) => a3.push((pi - r, qj - r, v)),
304            }
305        }
306        [
307            SpMat::from_entries((r,     r    ), a0),
308            SpMat::from_entries((r,     n - r), a1),
309            SpMat::from_entries((m - r, r    ), a2),
310            SpMat::from_entries((m - r, n - r), a3),
311        ]
312    }
313
314    pub fn submat(&self, rows: Range<usize>, cols: Range<usize>) -> SpMat<R> {
315        let (i0, i1) = (rows.start, rows.end);
316        let (j0, j1) = (cols.start, cols.end);
317
318        assert!(i0 <= i1 && i1 <= self.n_rows());
319        assert!(j0 <= j1 && j1 <= self.n_cols());
320
321        let shape = (i1 - i0, j1 - j0);
322        self.extract(shape, |i, j|
323            (rows.contains(&i) && cols.contains(&j)).then( ||
324                (i - i0, j - j0)
325            )
326        )
327    }
328
329    pub fn submat_rows(&self, rows: Range<usize>) -> SpMat<R> {
330        let n = self.n_cols();
331        self.submat(rows, 0 .. n)
332    }
333
334    pub fn submat_cols(&self, cols: Range<usize>) -> SpMat<R> {
335        let m = self.n_rows();
336        self.submat(0 .. m, cols)
337    }
338
339    pub fn block_split(self, point: (usize, usize)) -> [SpMat<R>; 4] {
340        let (m, n) = self.shape();
341        let (k, l) = point;
342        assert!(k <= m);
343        assert!(l <= n);
344
345        let (offsets, rows, vals) = self.disassemble();
346
347        let (mut a_rows, mut a_vals, mut a_offs) = (vec![], vec![], vec![0]);
348        let (mut b_rows, mut b_vals, mut b_offs) = (vec![], vec![], vec![0]);
349        let (mut c_rows, mut c_vals, mut c_offs) = (vec![], vec![], vec![0]);
350        let (mut d_rows, mut d_vals, mut d_offs) = (vec![], vec![], vec![0]);
351
352        let mut vals_iter = vals.into_iter();
353
354        for j in 0..n {
355            let range = offsets[j]..offsets[j + 1];
356            let col_rows = &rows[range];
357            let split = col_rows.partition_point(|&i| i < k);
358            let (top_rows_src, bot_rows_src) = col_rows.split_at(split);
359
360            let (top_rows, top_vals, top_offs, bot_rows, bot_vals, bot_offs) = if j < l {
361                (&mut a_rows, &mut a_vals, &mut a_offs, &mut c_rows, &mut c_vals, &mut c_offs)
362            } else {
363                (&mut b_rows, &mut b_vals, &mut b_offs, &mut d_rows, &mut d_vals, &mut d_offs)
364            };
365
366            top_rows.extend_from_slice(top_rows_src);
367            top_vals.extend(vals_iter.by_ref().take(top_rows_src.len()));
368            top_offs.push(top_rows.len());
369
370            bot_rows.extend(bot_rows_src.iter().map(|&i| i - k));
371            bot_vals.extend(vals_iter.by_ref().take(bot_rows_src.len()));
372            bot_offs.push(bot_rows.len());
373        }
374
375        [
376            SpMat::try_from_csc_data(k,     l,     a_offs, a_rows, a_vals).unwrap(),
377            SpMat::try_from_csc_data(k,     n - l, b_offs, b_rows, b_vals).unwrap(),
378            SpMat::try_from_csc_data(m - k, l,     c_offs, c_rows, c_vals).unwrap(),
379            SpMat::try_from_csc_data(m - k, n - l, d_offs, d_rows, d_vals).unwrap(),
380        ]
381    }
382
383    pub fn h_split(self, k: usize) -> [SpMat<R>; 2] {
384        let (m, n) = self.shape();
385        assert!(k <= n);
386
387        let [a, b, ..] = self.block_split((m, k));
388        [a, b]
389    }
390
391    pub fn v_split(self, k: usize) -> [SpMat<R>; 2] {
392        let (m, n) = self.shape();
393        assert!(k <= m);
394
395        let [a, _, b, _] = self.block_split((k, n));
396        [a, b]
397    }
398
399    pub fn block_combine(blocks: [SpMat<R>; 4]) -> SpMat<R> {
400        let [a, b, c, d] = blocks;
401
402        assert_eq!(a.n_rows(), b.n_rows());
403        assert_eq!(c.n_rows(), d.n_rows());
404        assert_eq!(a.n_cols(), c.n_cols());
405        assert_eq!(b.n_cols(), d.n_cols());
406
407        let (m0, m1) = (a.n_rows(), c.n_rows());
408        let m = m0 + m1;
409        let (n0, n1) = (a.n_cols(), b.n_cols());
410        let n = n0 + n1;
411        let nnz = a.nnz() + b.nnz() + c.nnz() + d.nnz();
412
413        let mut a = ColSource::from(a);
414        let mut b = ColSource::from(b);
415        let mut c = ColSource::from(c);
416        let mut d = ColSource::from(d);
417
418        let mut col_offsets = Vec::with_capacity(n + 1);
419        let mut row_indices = Vec::with_capacity(nnz);
420        let mut values = Vec::with_capacity(nnz);
421        col_offsets.push(0);
422
423        let mut push_col = |top: &mut ColSource<R>, bot: &mut ColSource<R>, j: usize| {
424            let (top_rows, top_vals) = top.take_col(j);
425            row_indices.extend_from_slice(top_rows);
426            values.extend(top_vals);
427
428            let (bot_rows, bot_vals) = bot.take_col(j);
429            row_indices.extend(bot_rows.iter().map(|i| i + m0));
430            values.extend(bot_vals);
431
432            col_offsets.push(row_indices.len());
433        };
434
435        for j in 0..n0 { push_col(&mut a, &mut c, j); }
436        for j in 0..n1 { push_col(&mut b, &mut d, j); }
437
438        SpMat::try_from_csc_data(m, n, col_offsets, row_indices, values).unwrap()
439    }
440
441    pub fn h_stack(left: Self, right: Self) -> Self {
442        assert_eq!(left.n_rows(), right.n_rows());
443        let (l_cols, r_cols) = (left.n_cols(), right.n_cols());
444        Self::block_combine([
445            left,
446            right,
447            SpMat::zero((0, l_cols)),
448            SpMat::zero((0, r_cols)),
449        ])
450    }
451
452    pub fn v_stack(top: Self, bot: Self) -> Self {
453        assert_eq!(top.n_cols(), bot.n_cols());
454        let (t_rows, b_rows) = (top.n_rows(), bot.n_rows());
455        Self::block_combine([
456            top,
457            SpMat::zero((t_rows, 0)),
458            bot,
459            SpMat::zero((b_rows, 0)),
460        ])
461    }
462
463    pub fn extend_by_zero(&mut self, add_rows: usize, add_cols: usize) {
464        let (m, n) = self.shape();
465        let l = std::mem::take(&mut self.inner);
466        let (mut col_offsets, row_indices, values) = l.disassemble();
467        let last = *col_offsets.last().unwrap();
468        col_offsets.extend(repeat_n(last, add_cols));
469
470        self.inner = CscMatrix::try_from_csc_data(
471            m + add_rows, n + add_cols,
472            col_offsets, row_indices, values
473        ).unwrap();
474    }
475
476    // row_perm(p) * a == a.permute_rows(p)
477    pub fn row_perm_mat(p: &Perm) -> Self
478    where R: One {
479        let n = p.len();
480        Self::from_entries((n, n), (0..n).map(|i|
481            (p.at(i), i, R::one())
482        ))
483    }
484
485    // a * col_perm(p) == a.permute_cols(p)
486    pub fn col_perm_mat(p: &Perm) -> Self
487    where R: One {
488        let n = p.len();
489        Self::from_entries((n, n), (0..n).map(|i|
490            (i, p.at(i), R::one())
491        ))
492    }
493}
494
495// A column-major view of a disassembled matrix that yields one column at a
496// time, moving values out without cloning. Used by `block_combine`.
497struct ColSource<R> {
498    offsets: Vec<usize>,
499    rows: Vec<usize>,
500    vals: std::vec::IntoIter<R>,
501    pos: usize,
502}
503
504impl<R> ColSource<R> {
505    fn from(m: SpMat<R>) -> Self {
506        let (offsets, rows, vals) = m.disassemble();
507        Self { offsets, rows, vals: vals.into_iter(), pos: 0 }
508    }
509
510    // Returns `(row_indices, values)` for column `j`. Must be called with
511    // monotonically increasing `j` since values are moved out lazily.
512    fn take_col(&mut self, j: usize) -> (&[usize], impl Iterator<Item = R> + '_) {
513        debug_assert_eq!(self.pos, self.offsets[j]);
514        let range = self.offsets[j]..self.offsets[j + 1];
515        let count = range.len();
516        self.pos += count;
517        (&self.rows[range], self.vals.by_ref().take(count))
518    }
519}
520
521impl<R> MatTrait for SpMat<R> {
522    fn shape(&self) -> (usize, usize) {
523        (self.inner.nrows(), self.inner.ncols())
524    }
525}
526
527impl<R> From<CscMatrix<R>> for SpMat<R> {
528    fn from(inner: CscMatrix<R>) -> Self {
529        Self { inner }
530    }
531}
532
533impl<R> From<Mat<R>> for SpMat<R>
534where R: Scalar + Zero {
535    fn from(value: Mat<R>) -> Self {
536        let csc = CscMatrix::from(value.inner());
537        Self::from(csc)
538    }
539}
540
541impl<R> Default for SpMat<R> {
542    fn default() -> Self {
543        Self::zero((0, 0))
544    }
545}
546
547impl<R: PartialEq + Zero> PartialEq for SpMat<R> {
548    fn eq(&self, other: &Self) -> bool {
549        self.shape() == other.shape() && self.iter_nz().eq(other.iter_nz())
550    }
551}
552
553impl<R: Eq + Zero> Eq for SpMat<R> {}
554
555impl<R> Neg for SpMat<R>
556where R: Scalar + Neg<Output = R> {
557    type Output = Self;
558    fn neg(self) -> Self::Output {
559        Self::from(-self.inner)
560    }
561}
562
563impl<R> Neg for &SpMat<R>
564where R: Scalar + Neg<Output = R> {
565    type Output = SpMat<R>;
566    fn neg(self) -> Self::Output {
567        SpMat::from(-&self.inner)
568    }
569}
570
571// see: nalgebra_sparse::ops::impl_std_ops.
572macro_rules! impl_binop {
573    ($trait:ident, $method:ident) => {
574        #[auto_ops]
575        impl<R> $trait<&SpMat<R>> for &SpMat<R>
576        where R: Scalar + ClosedAddAssign + ClosedSubAssign + ClosedMulAssign + Zero + One + Neg<Output = R> {
577            type Output = SpMat<R>;
578            fn $method(self, rhs: &SpMat<R>) -> Self::Output {
579                let res = (&self.inner).$method(&rhs.inner);
580                SpMat::from(res)
581            }
582        }
583    };
584}
585
586impl_binop!(Add, add);
587impl_binop!(Sub, sub);
588impl_binop!(Mul, mul);
589
590impl<R> Display for SpMat<R>
591where R: Display + Debug {
592    delegate! { to self.inner {
593        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
594    }}
595}
596
597impl<R> Debug for SpMat<R>
598where R: Display + Debug {
599    delegate! { to self.inner {
600        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
601    }}
602}
603
604#[cfg(feature = "serde")]
605impl<R> serde::Serialize for SpMat<R>
606where R: Clone + serde::Serialize {
607    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
608    where S: serde::Serializer {
609        self.inner.serialize(serializer)
610    }
611}
612
613#[cfg(feature = "serde")]
614impl<'de, R> serde::Deserialize<'de> for SpMat<R>
615where R: Clone + serde::Deserialize<'de> {
616    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
617    where D: serde::Deserializer<'de> {
618        let inner = CscMatrix::deserialize(deserializer)?;
619        let res = Self::from(inner);
620        Ok(res)
621    }
622}
623
624#[cfg(test)]
625impl<R> SpMat<R>
626where R: Scalar + Zero + One + ClosedAddAssign {
627    pub fn rand(shape: (usize, usize), density: f64) -> Self {
628        use itertools::iproduct;
629        use rand::RngExt;
630
631        let (m, n) = shape;
632        let range = iproduct!(0..m, 0..n);
633        let mut rng = rand::rng();
634
635        Self::from_entries(shape, range.filter_map(|(i, j)|
636            if rng.random::<f64>() < density {
637                Some((i, j, R::one()))
638            } else {
639                None
640            }
641        ))
642    }
643}
644
645#[cfg(test)]
646pub(super) mod tests {
647    use itertools::Itertools;
648    use yui_core::num::Ratio;
649    use crate::Perm;
650
651    use super::*;
652
653    #[test]
654    fn init() {
655        let a = SpMat::from_entries((2, 2), [
656            (0, 0, 1),
657            (0, 1, 2),
658            (1, 0, 3),
659            (1, 1, 4)
660        ]);
661        assert_eq!(a.disassemble(), (vec![0, 2, 4], vec![0, 1, 0, 1], vec![1, 3, 2, 4]));
662    }
663
664    #[test]
665    fn init_ratio() {
666        type R = Ratio<i64>;
667        let vals = (0..4).map(|i| R::new(i + 1, 5)).collect_vec();
668        let a = SpMat::from_entries((2, 2), [
669            (0, 0, vals[0]),
670            (0, 1, vals[2]),
671            (1, 0, vals[1]),
672            (1, 1, vals[3])
673        ]);
674        assert_eq!(a.disassemble(), (vec![0, 2, 4], vec![0, 1, 0, 1], vals));
675    }
676
677    #[test]
678    fn from_row_major() {
679        let a = SpMat::from_row_major((2, 2), [1,2,3,4]);
680        assert_eq!(a.disassemble(), (vec![0, 2, 4], vec![0, 1, 0, 1], vec![1, 3, 2, 4]));
681    }
682
683    #[test]
684    fn to_dense() {
685        let a = SpMat::from_entries((2, 2), [
686            (0, 0, 1),
687            (0, 1, 2),
688            (1, 0, 3),
689            (1, 1, 4)
690        ]);
691        assert_eq!(a.into_dense(), Mat::from_row_major((2, 2), [1,2,3,4]));
692    }
693
694    #[test]
695    fn permute() {
696        let p = Perm::new(vec![1,2,3,0]);
697        let q = Perm::new(vec![3,0,2,1]);
698        let a = SpMat::from_row_major((4,4), 0..16);
699        let b = a.permute(&p, &q);
700        assert_eq!(b, SpMat::from_row_major((4,4), vec![
701            13, 15, 14, 12,
702             1,  3,  2,  0,
703             5,  7,  6,  4,
704             9, 11, 10,  8,
705        ]));
706    }
707
708    #[test]
709    fn permute_and_split_identity() {
710        // a = [[1,2],[3,4]], r=1, identity perms → paq = a, partition at row/col 1:
711        // a0=[[1]], a1=[[2]], a2=[[3]], a3=[[4]]
712        let a = SpMat::from_row_major((2, 2), [1, 2, 3, 4]);
713        let id = Perm::id(2);
714        let [a0, a1, a2, a3] = a.permute_and_split(&id, &id, 1);
715        assert_eq!(a0, SpMat::from_row_major((1, 1), [1]));
716        assert_eq!(a1, SpMat::from_row_major((1, 1), [2]));
717        assert_eq!(a2, SpMat::from_row_major((1, 1), [3]));
718        assert_eq!(a3, SpMat::from_row_major((1, 1), [4]));
719    }
720
721    #[test]
722    fn permute_and_split_with_perm() {
723        // a (3×3) with row perm p = [2,0,1] (sends row 0→2, 1→0, 2→1)
724        // and col perm q = [1,2,0] (sends col 0→1, 1→2, 2→0).
725        // PAQ⁻¹[p(i), q(j)] = A[i, j]. Splitting at r=2 yields the 2×2 top-left,
726        // 2×1 top-right, 1×2 bottom-left, 1×1 bottom-right blocks.
727        let a = SpMat::from_row_major((3, 3), [
728            1, 2, 3,
729            4, 5, 6,
730            7, 8, 9,
731        ]);
732        let p = Perm::new(vec![2, 0, 1]);
733        let q = Perm::new(vec![1, 2, 0]);
734        let [a0, a1, a2, a3] = a.permute_and_split(&p, &q, 2);
735        // PAQ⁻¹ rows in image order = [row 1, row 2, row 0], cols = [col 2, col 0, col 1]:
736        //   [[6, 4, 5],
737        //    [9, 7, 8],
738        //    [3, 1, 2]]
739        assert_eq!(a0, SpMat::from_row_major((2, 2), [6, 4, 9, 7]));
740        assert_eq!(a1, SpMat::from_row_major((2, 1), [5, 8]));
741        assert_eq!(a2, SpMat::from_row_major((1, 2), [3, 1]));
742        assert_eq!(a3, SpMat::from_row_major((1, 1), [2]));
743    }
744
745    #[test]
746    fn submat() {
747        let a = SpMat::from_row_major((5, 6), 0..30);
748        let b = a.submat(1..3, 2..5);
749        assert_eq!(b, SpMat::from_row_major((2,3), vec![
750             8,  9, 10,
751            14, 15, 16
752        ]));
753    }
754
755    #[test]
756    fn transpose() {
757        let a = SpMat::from_row_major((3,4), 0..12);
758        let b = a.transpose();
759
760        assert_eq!(b, SpMat::from_row_major((4,3), vec![
761            0, 4, 8,
762            1, 5, 9,
763            2, 6, 10,
764            3, 7, 11,
765        ]));
766    }
767
768    #[test]
769    fn h_stack() {
770        let a = SpMat::from_row_major((4, 3), 0..12);
771        let b = SpMat::from_row_major((4, 2), 12..20);
772        let c = SpMat::h_stack(a, b);
773
774        assert_eq!(c, SpMat::from_row_major((4,5), vec![
775            0,  1,  2, 12, 13,
776            3,  4,  5, 14, 15,
777            6,  7,  8, 16, 17,
778            9, 10, 11, 18, 19,
779        ]));
780    }
781
782    #[test]
783    fn v_stack() {
784        let a = SpMat::from_row_major((2, 3), 0..6);
785        let b = SpMat::from_row_major((3, 3), 6..15);
786        let c = SpMat::v_stack(a, b);
787
788        assert_eq!(c, SpMat::from_row_major((5, 3), vec![
789            0,  1,  2,
790            3,  4,  5,
791            6,  7,  8,
792            9, 10, 11,
793           12, 13, 14,
794        ]));
795    }
796
797    #[test]
798    fn extend_by_zero() {
799        // [[1,2],[3,4]] extended by 1 row and 2 cols → [[1,2,0,0],[3,4,0,0],[0,0,0,0]]
800        let mut a = SpMat::from_row_major((2, 2), [1,2,3,4]);
801        a.extend_by_zero(1, 2);
802        assert_eq!(a.shape(), (3, 4));
803        assert_eq!(a, SpMat::from_row_major((3, 4), [1,2,0,0, 3,4,0,0, 0,0,0,0]));
804    }
805
806    #[test]
807    fn row_perm() {
808        let a = SpMat::from_row_major((3, 4), 0..12);
809        let p = Perm::new(vec![2,0,1]);
810        let q = SpMat::row_perm_mat(&p);
811        assert!(q * &a == a.permute_rows(&p))
812    }
813
814    #[test]
815    fn col_perm() {
816        let a = SpMat::from_row_major((3, 4), 0..12);
817        let p = Perm::new(vec![2,0,1,3]);
818        let q = SpMat::col_perm_mat(&p);
819        assert!(&a * q == a.permute_cols(&p))
820    }
821
822    #[test]
823    fn diag() {
824        let d = SpMat::diag((3, 4), [1, 2, 3]);
825        assert_eq!(d, SpMat::from_row_major((3, 4), [
826            1, 0, 0, 0,
827            0, 2, 0, 0,
828            0, 0, 3, 0,
829        ]));
830        assert!(d.is_diag());
831    }
832
833    #[test]
834    fn is_diag_false() {
835        let a = SpMat::from_row_major((2, 2), [1, 2, 0, 3]);
836        assert!(!a.is_diag());
837    }
838
839    #[test]
840    fn block_diag() {
841        let a = SpMat::from_row_major((2, 2), 1..=4);
842        let b = SpMat::from_row_major((1, 3), 5..=7);
843        let c = SpMat::from_row_major((2, 1), 8..=9);
844        let d = SpMat::block_diag([a, b, c]);
845        assert_eq!(d, SpMat::from_row_major((5, 6), [
846            1,2,0,0,0,0,
847            3,4,0,0,0,0,
848            0,0,5,6,7,0,
849            0,0,0,0,0,8,
850            0,0,0,0,0,9
851        ]))
852    }
853
854    #[test]
855    #[cfg(feature = "serde")]
856    fn serialize() {
857        let a = SpMat::from_row_major((3, 4), (0..12).map(|x| x % 5));
858        let ser = serde_json::to_string(&a).unwrap();
859        let des = serde_json::from_str(&ser).unwrap();
860        assert_eq!(a, des);
861    }
862}