Skip to main content

diffsol_la/matrix/
sparse_faer.rs

1use std::fmt::Debug;
2use std::ops::{Add, Mul, Sub};
3
4use super::extract_block::CscBlock;
5use super::sparsity::MatrixSparsityRef;
6use super::{Matrix, MatrixCommon, MatrixSparsity};
7use crate::context::broadcast_batch;
8use crate::error::{LaError, MatrixError};
9use crate::{Context, FaerContext, FaerVec, FaerVecIndex, Vector, VectorIndex};
10use crate::{DefaultSolver, FaerScalar, FaerSparseLU, IndexType, Scale};
11
12use faer::reborrow::{Reborrow, ReborrowMut};
13use faer::sparse::linalg::matmul::sparse_dense_matmul;
14use faer::sparse::ops::{ternary_op_assign_into, union_symbolic};
15use faer::sparse::{Pair, SparseColMat, SymbolicSparseColMat, SymbolicSparseColMatRef, Triplet};
16use faer::Accum;
17
18#[derive(Clone, Debug)]
19pub struct FaerSparseMat<T: FaerScalar> {
20    pub(crate) data: Vec<SparseColMat<IndexType, T>>,
21    pub(crate) context: FaerContext,
22}
23
24impl<T: FaerScalar> FaerSparseMat<T> {
25    /// This matrix's own batch `batch`.
26    ///
27    /// The dense backends return a column index here; a batch of a sparse matrix is a
28    /// whole `SparseColMat`, so this returns that instead.
29    #[inline]
30    pub(crate) fn batch(&self, batch: usize) -> &SparseColMat<IndexType, T> {
31        &self.data[batch]
32    }
33    /// Batch feeding batch `batch` of a `nbatch`-batch destination, broadcasting this matrix
34    /// over contiguous groups of destination batches (see [`broadcast_batch`]).
35    #[inline]
36    pub(crate) fn batch_bcast(&self, batch: usize, nbatch: usize) -> &SparseColMat<IndexType, T> {
37        self.batch(broadcast_batch(batch, self.data.len(), nbatch))
38    }
39}
40
41impl<T: FaerScalar> DefaultSolver for FaerSparseMat<T> {
42    type LS = FaerSparseLU<T>;
43}
44
45impl<T: FaerScalar> MatrixCommon for FaerSparseMat<T> {
46    type T = T;
47    type V = FaerVec<T>;
48    type C = FaerContext;
49    type Inner = Vec<SparseColMat<IndexType, T>>;
50
51    fn nrows(&self) -> IndexType {
52        self.data[0].nrows()
53    }
54    fn ncols(&self) -> IndexType {
55        self.data[0].ncols()
56    }
57    fn inner(&self) -> &Self::Inner {
58        &self.data
59    }
60}
61
62macro_rules! impl_mul_scalar {
63    ($mat_type:ty, $out:ty) => {
64        impl<'a, T: FaerScalar> Mul<Scale<T>> for $mat_type {
65            type Output = $out;
66
67            fn mul(self, rhs: Scale<T>) -> Self::Output {
68                let scale: faer::Scale<T> = rhs.into();
69                Self::Output {
70                    data: self.data.iter().map(|data| data * scale).collect(),
71                    context: self.context,
72                }
73            }
74        }
75    };
76}
77
78impl_mul_scalar!(FaerSparseMat<T>, FaerSparseMat<T>);
79impl_mul_scalar!(&FaerSparseMat<T>, FaerSparseMat<T>);
80
81macro_rules! sparse_binary {
82    ($trait:ident, $method:ident, $binary:tt) => {
83        impl<T: FaerScalar> $trait<&FaerSparseMat<T>> for FaerSparseMat<T> {
84            type Output = FaerSparseMat<T>;
85
86            fn $method(self, rhs: &FaerSparseMat<T>) -> Self::Output {
87                // `self` is the owned operand, so the result carries its batch count and `rhs`
88                // broadcasts into it (a new allocation either way: the sparsity is the union)
89                self.context
90                    .assert_broadcastable_into(rhs.context.nbatch(), stringify!($method));
91                let nbatch = self.data.len();
92                FaerSparseMat {
93                    data: (0..nbatch)
94                        .map(|b| self.batch(b) $binary rhs.batch_bcast(b, nbatch))
95                        .collect(),
96                    context: self.context,
97                }
98            }
99        }
100    };
101}
102sparse_binary!(Add, add, +);
103sparse_binary!(Sub, sub, -);
104
105impl<T: FaerScalar> MatrixSparsity<FaerSparseMat<T>> for SymbolicSparseColMat<IndexType> {
106    fn union(
107        self,
108        other: SymbolicSparseColMatRef<IndexType>,
109    ) -> Result<SymbolicSparseColMat<IndexType>, LaError> {
110        union_symbolic(self.rb(), other).map_err(|e| LaError::Other(e.to_string()))
111    }
112
113    fn as_ref(&self) -> SymbolicSparseColMatRef<'_, IndexType> {
114        self.rb()
115    }
116
117    fn nrows(&self) -> IndexType {
118        self.nrows()
119    }
120
121    fn ncols(&self) -> IndexType {
122        self.ncols()
123    }
124
125    fn is_sparse() -> bool {
126        true
127    }
128
129    fn indices(&self) -> Vec<(IndexType, IndexType)> {
130        let mut indices = Vec::with_capacity(self.compute_nnz());
131        for col_i in 0..self.ncols() {
132            for pos in self.col_range(col_i) {
133                indices.push((self.row_idx()[pos], col_i));
134            }
135        }
136        indices
137    }
138
139    fn new_diagonal(n: IndexType) -> Self {
140        let indices = (0..n).map(|i| Pair::new(i, i)).collect::<Vec<_>>();
141        SymbolicSparseColMat::try_new_from_indices(n, n, indices.as_slice())
142            .unwrap()
143            .0
144    }
145
146    fn try_from_indices(
147        nrows: IndexType,
148        ncols: IndexType,
149        indices: Vec<(IndexType, IndexType)>,
150    ) -> Result<Self, LaError> {
151        let indices = indices
152            .iter()
153            .map(|(i, j)| Pair::new(*i, *j))
154            .collect::<Vec<_>>();
155        match Self::try_new_from_indices(nrows, ncols, indices.as_slice()) {
156            Ok((sparsity, _)) => Ok(sparsity),
157            Err(e) => Err(LaError::Other(e.to_string())),
158        }
159    }
160
161    fn get_index(
162        &self,
163        indices: &[(IndexType, IndexType)],
164        ctx: FaerContext,
165    ) -> <<FaerSparseMat<T> as MatrixCommon>::V as Vector>::Index {
166        let col_ptrs = self.col_ptr();
167        let row_indices = self.row_idx();
168        let mut ret = Vec::with_capacity(indices.len());
169        for &(i, j) in indices.iter() {
170            let col_ptr = col_ptrs[j];
171            let next_col_ptr = col_ptrs[j + 1];
172            for (ii, &ri) in row_indices
173                .iter()
174                .enumerate()
175                .take(next_col_ptr)
176                .skip(col_ptr)
177            {
178                if ri == i {
179                    ret.push(ii);
180                    break;
181                }
182            }
183        }
184        FaerVecIndex {
185            data: ret,
186            context: ctx,
187        }
188    }
189}
190
191impl<'a, T: FaerScalar> MatrixSparsityRef<'a, FaerSparseMat<T>>
192    for SymbolicSparseColMatRef<'a, IndexType>
193{
194    fn to_owned(&self) -> SymbolicSparseColMat<IndexType> {
195        self.to_owned().unwrap()
196    }
197    fn nrows(&self) -> IndexType {
198        self.nrows()
199    }
200
201    fn ncols(&self) -> IndexType {
202        self.ncols()
203    }
204
205    fn is_sparse() -> bool {
206        true
207    }
208
209    fn split(
210        &self,
211        indices: &<<FaerSparseMat<T> as MatrixCommon>::V as Vector>::Index,
212    ) -> [(
213        SymbolicSparseColMat<IndexType>,
214        <<FaerSparseMat<T> as MatrixCommon>::V as Vector>::Index,
215    ); 4] {
216        let (_ni, _nj, col_ptrs, _col_nnz, row_idx) = self.parts();
217        let ctx = indices.context();
218        let (ul_blk, ur_blk, ll_blk, lr_blk) = CscBlock::split(row_idx, col_ptrs, indices);
219        let ul_sym = SymbolicSparseColMat::new_checked(
220            ul_blk.nrows,
221            ul_blk.ncols,
222            ul_blk.col_pointers,
223            None,
224            ul_blk.row_indices,
225        );
226        let ur_sym = SymbolicSparseColMat::new_checked(
227            ur_blk.nrows,
228            ur_blk.ncols,
229            ur_blk.col_pointers,
230            None,
231            ur_blk.row_indices,
232        );
233        let ll_sym = SymbolicSparseColMat::new_checked(
234            ll_blk.nrows,
235            ll_blk.ncols,
236            ll_blk.col_pointers,
237            None,
238            ll_blk.row_indices,
239        );
240        let lr_sym = SymbolicSparseColMat::new_checked(
241            lr_blk.nrows,
242            lr_blk.ncols,
243            lr_blk.col_pointers,
244            None,
245            lr_blk.row_indices,
246        );
247        [
248            (
249                ul_sym,
250                FaerVecIndex {
251                    data: ul_blk.src_indices,
252                    context: *ctx,
253                },
254            ),
255            (
256                ur_sym,
257                FaerVecIndex {
258                    data: ur_blk.src_indices,
259                    context: *ctx,
260                },
261            ),
262            (
263                ll_sym,
264                FaerVecIndex {
265                    data: ll_blk.src_indices,
266                    context: *ctx,
267                },
268            ),
269            (
270                lr_sym,
271                FaerVecIndex {
272                    data: lr_blk.src_indices,
273                    context: *ctx,
274                },
275            ),
276        ]
277    }
278
279    fn indices(&self) -> Vec<(IndexType, IndexType)> {
280        let mut indices = Vec::with_capacity(self.compute_nnz());
281        for col_i in 0..self.ncols() {
282            for pos in self.col_range(col_i) {
283                indices.push((self.row_idx()[pos], col_i));
284            }
285        }
286        indices
287    }
288}
289
290impl<T: FaerScalar> Matrix for FaerSparseMat<T> {
291    type Sparsity = SymbolicSparseColMat<IndexType>;
292    type SparsityRef<'a> = SymbolicSparseColMatRef<'a, IndexType>;
293
294    fn sparsity(&self) -> Option<Self::SparsityRef<'_>> {
295        Some(self.data[0].symbolic())
296    }
297    fn context(&self) -> &FaerContext {
298        &self.context
299    }
300    fn inner_mut(&mut self) -> &mut Self::Inner {
301        &mut self.data
302    }
303
304    fn gather(&mut self, other: &Self, indices: &<Self::V as Vector>::Index) {
305        self.context
306            .assert_broadcastable_into(other.context.nbatch(), "gather");
307        let nb = self.data.len();
308        for (batch, data) in self.data.iter_mut().enumerate() {
309            let src = other.batch_bcast(batch, nb).val();
310            for (dst, index) in data.val_mut().iter_mut().zip(&indices.data) {
311                *dst = src[*index];
312            }
313        }
314    }
315
316    fn set_data_with_indices(
317        &mut self,
318        dst_indices: &<Self::V as Vector>::Index,
319        src_indices: &<Self::V as Vector>::Index,
320        data: &Self::V,
321    ) {
322        self.context
323            .assert_broadcastable_into(data.context.nbatch(), "set_data_with_indices");
324        let nb = self.data.len();
325        for (batch, matrix) in self.data.iter_mut().enumerate() {
326            let values = matrix.val_mut();
327            let column = data.data.rb().col(data.batch(batch, nb));
328            for (dst, src) in dst_indices.data.iter().zip(&src_indices.data) {
329                values[*dst] = column[*src];
330            }
331        }
332    }
333
334    fn add_column_to_vector(&self, j: IndexType, v: &mut Self::V) {
335        v.context
336            .assert_broadcastable_into(self.context.nbatch(), "add_column_to_vector");
337        let nb = v.data.ncols();
338        for batch in 0..nb {
339            let matrix = self.batch_bcast(batch, nb);
340            let mut column = v.data.rb_mut().col_mut(batch);
341            for i in matrix.col_range(j) {
342                column[matrix.row_idx()[i]] += matrix.val()[i];
343            }
344        }
345    }
346
347    fn add_columns_to_batched_vector(&self, v: &mut Self::V) {
348        assert_eq!(v.len(), self.nrows(), "row count mismatch");
349        let ncols = self.ncols();
350        assert_eq!(
351            v.context.nbatch(),
352            self.context.nbatch() * ncols,
353            "batch count mismatch: the destination holds one lane per (batch, column)"
354        );
355        for batch in 0..self.context.nbatch() {
356            let matrix = self.batch(batch);
357            for j in 0..ncols {
358                let mut column = v.data.rb_mut().col_mut(batch * ncols + j);
359                for i in matrix.col_range(j) {
360                    column[matrix.row_idx()[i]] += matrix.val()[i];
361                }
362            }
363        }
364    }
365    fn triplet_iter(
366        &self,
367    ) -> (
368        impl Iterator<Item = (IndexType, IndexType)> + '_,
369        impl Iterator<Item = Self::T> + '_,
370    ) {
371        let indices: Vec<_> = (0..self.ncols())
372            .flat_map(move |j| {
373                self.data[0]
374                    .col_range(j)
375                    .map(move |i| (self.data[0].row_idx()[i], j))
376            })
377            .collect();
378        let values: Vec<_> = self
379            .data
380            .iter()
381            .flat_map(|data| data.val().iter().copied())
382            .collect();
383        (indices.into_iter(), values.into_iter())
384    }
385
386    fn try_from_triplets(
387        nrows: IndexType,
388        ncols: IndexType,
389        indices: Vec<(IndexType, IndexType)>,
390        values: Vec<Self::T>,
391        ctx: Self::C,
392    ) -> Result<Self, LaError> {
393        assert_eq!(values.len(), indices.len() * ctx.nbatch());
394        if indices.is_empty() {
395            // `empty pattern is just a zero matrix
396            return Ok(Self::zeros(nrows, ncols, ctx));
397        }
398        let data = values
399            .chunks(indices.len())
400            .map(|values| {
401                let triplets = indices
402                    .iter()
403                    .zip(values)
404                    .map(|((i, j), v)| Triplet::new(*i, *j, *v))
405                    .collect::<Vec<_>>();
406                faer::sparse::SparseColMat::try_new_from_triplets(nrows, ncols, &triplets)
407                    .map_err(|e| LaError::from(MatrixError::FailedToCreateMatrixFromTriplets(e)))
408            })
409            .collect::<Result<_, _>>()?;
410        Ok(Self { data, context: ctx })
411    }
412    fn gemv(&self, alpha: Self::T, x: &Self::V, beta: Self::T, y: &mut Self::V) {
413        y.context
414            .assert_broadcastable_into(self.context.nbatch(), "gemv");
415        y.context
416            .assert_broadcastable_into(x.context.nbatch(), "gemv");
417        let nb = y.data.ncols();
418        let par = self.context.par;
419        for batch in 0..nb {
420            let mut ycol = y.data.rb_mut().col_mut(batch);
421            let accum = if beta.is_zero() {
422                Accum::Replace
423            } else {
424                if !beta.is_one() {
425                    ycol *= faer::Scale(beta);
426                }
427                Accum::Add
428            };
429            sparse_dense_matmul(
430                ycol.as_mat_mut(),
431                accum,
432                self.batch_bcast(batch, nb).rb(),
433                x.data.rb().col(x.batch(batch, nb)).as_mat(),
434                alpha,
435                par,
436            );
437        }
438    }
439    fn zeros(nrows: IndexType, ncols: IndexType, ctx: Self::C) -> Self {
440        Self {
441            data: (0..ctx.nbatch())
442                .map(|_| SparseColMat::try_new_from_triplets(nrows, ncols, &[]).unwrap())
443                .collect(),
444            context: ctx,
445        }
446    }
447    fn copy_from(&mut self, other: &Self) {
448        self.context
449            .assert_broadcastable_into(other.context.nbatch(), "copy_from");
450        let nb = self.data.len();
451        for (batch, data) in self.data.iter_mut().enumerate() {
452            let other = other.batch_bcast(batch, nb);
453            *data = SparseColMat::new(other.symbolic().to_owned().unwrap(), other.val().to_vec());
454        }
455    }
456    fn from_diagonal(v: &FaerVec<T>) -> Self {
457        let dim = v.len();
458        let data = (0..v.data.ncols())
459            .map(|b| {
460                let column = v.data.rb().col(b);
461                let triplets = (0..dim)
462                    .map(|i| Triplet::new(i, i, column[i]))
463                    .collect::<Vec<_>>();
464                SparseColMat::try_new_from_triplets(dim, dim, &triplets).unwrap()
465            })
466            .collect();
467        Self {
468            data,
469            context: *v.context(),
470        }
471    }
472
473    fn partition_indices_by_zero_diagonal(
474        &self,
475    ) -> (<Self::V as Vector>::Index, <Self::V as Vector>::Index) {
476        let mut indices_zero_diag = vec![];
477        let mut indices_non_zero_diag = vec![];
478        'outer: for j in 0..self.ncols() {
479            for (i, v) in self.data[0]
480                .row_idx_of_col(j)
481                .zip(self.data[0].val_of_col(j))
482            {
483                if i == j && *v != T::zero() {
484                    indices_non_zero_diag.push(j);
485                    continue 'outer;
486                } else if i > j {
487                    break;
488                }
489            }
490            indices_zero_diag.push(j);
491        }
492        (
493            <Self::V as Vector>::Index::from_vec(indices_zero_diag, self.context),
494            <Self::V as Vector>::Index::from_vec(indices_non_zero_diag, self.context),
495        )
496    }
497
498    fn set_column(&mut self, j: IndexType, v: &Self::V) {
499        assert_eq!(v.len(), self.nrows());
500        self.context
501            .assert_broadcastable_into(v.context.nbatch(), "set_column");
502        let nb = self.data.len();
503        for (batch, data) in self.data.iter_mut().enumerate() {
504            let column = v.data.rb().col(v.batch(batch, nb));
505            for i in data.col_range(j) {
506                data.val_mut()[i] = column[data.row_idx()[i]];
507            }
508        }
509    }
510
511    fn scale_add_and_assign(&mut self, x: &Self, beta: Self::T, y: &Self) {
512        self.context
513            .assert_broadcastable_into(x.context.nbatch(), "scale_add_and_assign");
514        self.context
515            .assert_broadcastable_into(y.context.nbatch(), "scale_add_and_assign");
516        let nb = self.data.len();
517        for (batch, data) in self.data.iter_mut().enumerate() {
518            ternary_op_assign_into(
519                data.rb_mut(),
520                x.batch_bcast(batch, nb).rb(),
521                y.batch_bcast(batch, nb).rb(),
522                |s, x, y| *s = *x.unwrap_or(&T::zero()) + beta * *y.unwrap_or(&T::zero()),
523            );
524        }
525    }
526
527    fn new_from_sparsity(
528        nrows: IndexType,
529        ncols: IndexType,
530        sparsity: Option<Self::Sparsity>,
531        ctx: Self::C,
532    ) -> Self {
533        let sparsity = sparsity.expect("Sparsity pattern required for sparse matrix");
534        assert_eq!(sparsity.nrows(), nrows);
535        assert_eq!(sparsity.ncols(), ncols);
536        let nnz = sparsity.row_idx().len();
537        Self {
538            data: (0..ctx.nbatch())
539                .map(|_| SparseColMat::new(sparsity.clone(), vec![T::zero(); nnz]))
540                .collect(),
541            context: ctx,
542        }
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    use crate::{FaerContext, FaerSparseMat, Matrix, MatrixSparsity, MatrixSparsityRef};
549    use faer::sparse::SymbolicSparseColMat;
550
551    #[test]
552    fn sparsity_indices_round_trip() {
553        let indices = vec![(0, 0), (3, 0), (1, 2), (2, 2)];
554        let sparsity =
555            <SymbolicSparseColMat<usize> as MatrixSparsity<FaerSparseMat<f64>>>::try_from_indices(
556                4,
557                3,
558                indices.clone(),
559            )
560            .unwrap();
561
562        let mut round_tripped =
563            <SymbolicSparseColMat<usize> as MatrixSparsity<FaerSparseMat<f64>>>::indices(&sparsity);
564        round_tripped.sort_unstable();
565        let mut expected = indices;
566        expected.sort_unstable();
567
568        assert_eq!(round_tripped, expected);
569
570        let sparsity_ref =
571            <SymbolicSparseColMat<usize> as MatrixSparsity<FaerSparseMat<f64>>>::as_ref(&sparsity);
572        let mut ref_round_tripped = MatrixSparsityRef::<FaerSparseMat<f64>>::indices(&sparsity_ref);
573        ref_round_tripped.sort_unstable();
574
575        assert_eq!(ref_round_tripped, expected);
576    }
577
578    #[test]
579    fn test_triplet_iter() {
580        let indices = vec![(0, 0), (1, 0), (2, 2), (3, 2)];
581        let values = vec![1.0, 2.0, 3.0, 4.0];
582        let mat = FaerSparseMat::<f64>::try_from_triplets(
583            4,
584            3,
585            indices.clone(),
586            values.clone(),
587            Default::default(),
588        )
589        .unwrap();
590        let (iter_indices, iter_values): (Vec<_>, Vec<_>) = {
591            let (i, v) = mat.triplet_iter();
592            (i.collect(), v.collect())
593        };
594        assert_eq!(iter_indices, indices);
595        assert_eq!(iter_values, values);
596    }
597
598    #[test]
599    fn test_set_data_with_indices() {
600        use crate::{FaerVec, FaerVecIndex, Vector, VectorIndex};
601
602        // CSC data-array order: column-major, ascending row within each column.
603        let triplets_full = [
604            (0, 0, 1.0),
605            (2, 0, 2.0),
606            (1, 1, 3.0),
607            (0, 2, 4.0),
608            (2, 2, 5.0),
609        ];
610        let (indices, init_values): (Vec<_>, Vec<_>) =
611            triplets_full.iter().map(|&(i, j, v)| ((i, j), v)).unzip();
612        let mut mat = FaerSparseMat::<f64>::try_from_triplets(
613            3,
614            3,
615            indices.clone(),
616            init_values,
617            Default::default(),
618        )
619        .unwrap();
620
621        assert_eq!(mat.inner_mut()[0].val_mut().len(), indices.len());
622
623        let new_values = [10.0, 11.0, 12.0, 13.0, 14.0];
624        mat.inner_mut()[0].val_mut().copy_from_slice(&new_values);
625        let (indices_iter, values_iter) = mat.triplet_iter();
626        let got: Vec<(usize, usize, f64)> = indices_iter
627            .zip(values_iter)
628            .map(|((i, j), v)| (i, j, v))
629            .collect();
630        let expected: Vec<(usize, usize, f64)> = triplets_full
631            .iter()
632            .zip(new_values.iter())
633            .map(|(&(i, j, _), &v)| (i, j, v))
634            .collect();
635        assert_eq!(got, expected);
636
637        let mut via_set_data = FaerSparseMat::<f64>::try_from_triplets(
638            3,
639            3,
640            indices,
641            new_values.iter().map(|_| 0.0).collect(),
642            Default::default(),
643        )
644        .unwrap();
645        let nnz = new_values.len();
646        let identity = FaerVecIndex::from_vec((0..nnz).collect(), Default::default());
647        let data = FaerVec::from_vec(new_values.to_vec(), Default::default());
648        via_set_data.set_data_with_indices(&identity, &identity, &data);
649        assert_eq!(
650            mat.inner_mut()[0].val_mut(),
651            via_set_data.inner_mut()[0].val_mut()
652        );
653    }
654
655    #[test]
656    fn test_partition_indices_by_zero_diagonal() {
657        super::super::tests::test_partition_indices_by_zero_diagonal::<FaerSparseMat<f64>>();
658    }
659
660    super::super::generate_matrix_tests_nonbatched!(faer_sparse, FaerSparseMat<f64>);
661    super::super::generate_matrix_tests_nonbatched!(faer_sparse_f32, FaerSparseMat<f32>);
662    super::super::generate_matrix_tests_batched!(
663        faer_sparse,
664        FaerSparseMat<f64>,
665        FaerContext::default(),
666        FaerContext::with_nbatch(2)
667    );
668    super::super::generate_matrix_tests_batched!(
669        faer_sparse_f32,
670        FaerSparseMat<f32>,
671        FaerContext::default(),
672        FaerContext::with_nbatch(2)
673    );
674}