Skip to main content

igraph/linalg/
sparsemat.rs

1//! Sparse matrices (`igraph_sparsemat.h`).
2
3use super::{
4    arpack::{ArpackNonSymmetricResult, ArpackOptions, ArpackSymmetricResult},
5    check_finite,
6};
7use crate::{
8    error::{Error, Result, catch_panic_or, check, ensure_init},
9    ffi::*,
10    igraph_call,
11    matrix::Matrix,
12    vector::{Vector, VectorInt},
13};
14use std::{
15    borrow::Cow,
16    ffi::{CStr, c_char, c_void},
17    fmt,
18    marker::PhantomData,
19    mem::MaybeUninit,
20    ops::{Add, Mul, Sub},
21    ptr,
22};
23
24/// An owned sparse matrix of reals (`igraph_sparsemat_t`), backed by the
25/// CXSparse library bundled with igraph.
26///
27/// A sparse matrix is stored in one of two formats (see [`SparseMatType`]):
28///
29/// - **triplet** (a.k.a. coordinate) format: a list of `(row, col, value)`
30///   entries. It is the format in which matrices are *built*: new entries are
31///   appended with [`entry`](Self::entry), and entries at the same position are
32///   summed. Constructors such as [`new`](Self::new),
33///   [`from_triplets`](Self::from_triplets) and [`from_dense`](Self::from_dense)
34///   create triplet matrices.
35/// - **column-compressed** (CSC) format: the format in which most computations
36///   happen. Convert with [`compress`](Self::compress).
37///
38/// Most read-only operations accept both formats: when the C function
39/// requires a column-compressed matrix, the wrapper compresses a temporary
40/// copy. Mutating operations that need the compressed format (such as
41/// [`dupl`](Self::dupl) or [`fkeep`](Self::fkeep)) convert `self` in place.
42///
43/// Row and column indices are zero-based. The matrix owns its storage and frees
44/// it on [`Drop`]; it is [`Clone`] (`igraph_sparsemat_init_copy`) and
45/// [`Send`]/[`Sync`].
46///
47/// See the [igraph documentation on sparse
48/// matrices](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_init).
49///
50/// See also the graph matrices of other modules, which convert naturally
51/// with [`SparseMat::from_triplets`] (casting the indices between
52/// [`VertexId`](crate::VertexId) and `usize`):
53/// [`Graph::get_adjacency_sparse`](crate::Graph::get_adjacency_sparse),
54/// [`Graph::get_stochastic_sparse`](crate::Graph::get_stochastic_sparse)
55/// (conversion) and
56/// [`Graph::get_laplacian_sparse`](crate::Graph::get_laplacian_sparse)
57/// (structural); in the other direction
58/// [`Graph::sparse_adjacency`](crate::Graph::sparse_adjacency) and
59/// [`Graph::sparse_weighted_adjacency`](crate::Graph::sparse_weighted_adjacency)
60/// (constructors) build a graph from [`triplets`](SparseMat::triplets).
61///
62/// # Examples
63///
64/// ```
65/// use igraph::linalg::SparseMat;
66///
67/// // [ 4 1 0 ]
68/// // [ 1 3 0 ]
69/// // [ 0 0 2 ]
70/// let a = SparseMat::from_triplets(3, 3, &[(0, 0, 4.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0), (2, 2, 2.0)])
71///     .unwrap()
72///     .compress()
73///     .unwrap();
74/// assert_eq!(a.shape(), (3, 3));
75/// assert_eq!(a.get(0, 1), 1.0);
76/// assert_eq!(a.mul_vec(&[1.0, 1.0, 1.0]).unwrap(), vec![5.0, 4.0, 2.0]);
77///
78/// // Solve A x = b by Cholesky factorization (A is symmetric positive definite).
79/// let x = a.cholsol(&[5.0, 4.0, 2.0], igraph::linalg::SparseOrdering::Natural).unwrap();
80/// for xi in x {
81///     assert!((xi - 1.0).abs() < 1e-12);
82/// }
83///
84/// // Arithmetic with operators returns `Result`s.
85/// let twice = (&a + &a).unwrap();
86/// assert_eq!(twice.get(0, 0), 8.0);
87/// ```
88pub type SparseMat = igraph_sparsemat_t;
89
90crate::ffi_enum! {
91    /// Storage format of a [`SparseMat`] (`igraph_sparsemat_type_t`).
92    pub enum SparseMatType: igraph_sparsemat_type_t {
93        /// Triplet (coordinate) format: easy to build, see [`SparseMat::entry`].
94        Triplet = igraph_sparsemat_type_t_IGRAPH_SPARSEMAT_TRIPLET,
95        /// Column-compressed format: the one used for computations.
96        ColumnCompressed = igraph_sparsemat_type_t_IGRAPH_SPARSEMAT_CC,
97    }
98}
99
100crate::ffi_enum! {
101    /// How the linear systems of the shift-and-invert mode of
102    /// [`SparseMat::arpack_rssolve`] are solved (`igraph_sparsemat_solve_t`).
103    pub enum SparseSolveMethod: igraph_sparsemat_solve_t {
104        /// LU decomposition.
105        Lu = igraph_sparsemat_solve_t_IGRAPH_SPARSEMAT_SOLVE_LU,
106        /// QR decomposition.
107        Qr = igraph_sparsemat_solve_t_IGRAPH_SPARSEMAT_SOLVE_QR,
108    }
109}
110
111/// Fill-reducing ordering used by the sparse factorizations
112/// ([`SparseMat::cholsol`], [`SparseMat::lusol`], [`SparseMat::lu`],
113/// [`SparseMat::qr`]); the integer `order` argument of the C functions.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
115pub enum SparseOrdering {
116    /// Natural ordering (no permutation), `order = 0`.
117    #[default]
118    Natural,
119    /// Approximate minimum degree ordering of `A + A'`, `order = 1`; the
120    /// usual choice for Cholesky and LU of (nearly) symmetric matrices.
121    MinDegreeSymmetric,
122    /// Minimum degree ordering of `A' A` after removing the dense rows of `A`,
123    /// `order = 2`; good for LU of unsymmetric matrices.
124    MinDegreeNoDenseRows,
125    /// Minimum degree ordering of `A' A`, `order = 3`; the usual choice for QR.
126    MinDegreeAtA,
127}
128
129impl SparseOrdering {
130    fn raw(self) -> igraph_int_t {
131        match self {
132            Self::Natural => 0,
133            Self::MinDegreeSymmetric => 1,
134            Self::MinDegreeNoDenseRows => 2,
135            Self::MinDegreeAtA => 3,
136        }
137    }
138}
139
140/// The elements of a sparse matrix as returned by
141/// [`SparseMat::getelements`] (the raw CXSparse arrays).
142#[derive(Debug, Clone, PartialEq)]
143pub struct SparseElements {
144    /// Row index of each stored element.
145    pub i: Vec<i64>,
146    /// For a triplet matrix: the column index of each stored element. For a
147    /// column-compressed matrix: the column pointers, of length `ncol + 1`;
148    /// the elements of column `k` are at positions `j[k]..j[k + 1]` of
149    /// [`i`](Self::i) and [`x`](Self::x).
150    pub j: Vec<i64>,
151    /// The value of each stored element.
152    pub x: Vec<f64>,
153}
154
155// A sparse matrix uniquely owns its CXSparse storage.
156unsafe impl Send for igraph_sparsemat_t {}
157unsafe impl Sync for igraph_sparsemat_t {}
158
159impl Drop for igraph_sparsemat_t {
160    /// Frees the matrix with `igraph_sparsemat_destroy`.
161    fn drop(&mut self) {
162        if !self.cs.is_null() {
163            unsafe { igraph_sparsemat_destroy(self) };
164            self.cs = ptr::null_mut();
165        }
166    }
167}
168
169impl Clone for igraph_sparsemat_t {
170    /// Deep copy with `igraph_sparsemat_init_copy`, keeping the storage format.
171    fn clone(&self) -> Self {
172        Self::init_with(|m| unsafe { igraph_sparsemat_init_copy(m, self) })
173            .expect("igraph failed to copy a sparse matrix")
174    }
175}
176
177impl fmt::Display for igraph_sparsemat_t {
178    /// Prints the stored entries in igraph's format, see [`SparseMat::print_to_string`].
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        match self.print_to_string() {
181            Ok(s) => f.write_str(&s),
182            Err(_) => Err(fmt::Error),
183        }
184    }
185}
186
187fn check_index(value: usize, bound: usize, what: &str) -> Result<()> {
188    if value >= bound {
189        Err(Error::invalid(format!(
190            "{what} {value} out of bounds (size {bound})"
191        )))
192    } else {
193        Ok(())
194    }
195}
196
197fn check_len(len: usize, expected: usize, what: &str) -> Result<()> {
198    if len != expected {
199        Err(Error::invalid(format!(
200            "{what} has length {len}, expected {expected}"
201        )))
202    } else {
203        Ok(())
204    }
205}
206
207fn to_int(value: usize, what: &str) -> Result<igraph_int_t> {
208    igraph_int_t::try_from(value)
209        .map_err(|_| Error::invalid(format!("{what} ({value}) is too large")))
210}
211
212fn check_permutation(p: &[usize], n: usize, what: &str) -> Result<VectorInt> {
213    check_len(p.len(), n, what)?;
214    let mut seen = vec![false; n];
215    for &x in p {
216        if x >= n || seen[x] {
217            return Err(Error::invalid(format!(
218                "{what} is not a permutation of 0..{n}"
219            )));
220        }
221        seen[x] = true;
222    }
223    Ok(p.iter().map(|&x| x as igraph_int_t).collect())
224}
225
226/// A factorized linear solver `b -> A^-1 b`.
227type Solver = Box<dyn Fn(&[f64]) -> Result<Vec<f64>>>;
228
229struct FkeepData<F> {
230    f: F,
231    panicked: bool,
232}
233
234unsafe extern "C" fn fkeep_trampoline<F: FnMut(usize, usize, f64) -> bool>(
235    row: igraph_int_t,
236    col: igraph_int_t,
237    value: igraph_real_t,
238    extra: *mut c_void,
239) -> igraph_int_t {
240    let data = unsafe { &mut *(extra as *mut FkeepData<F>) };
241    if data.panicked {
242        return 1;
243    }
244    let mut failed = true;
245    // The closure runs in its own level of igraph's "finally" stack, so that
246    // a failing igraph call made by it cannot free the temporaries of the
247    // running `igraph_sparsemat_fkeep` (see `arpack::matvec_trampoline`).
248    // SAFETY: the matching EXIT runs below, as `catch_panic_or` never unwinds.
249    unsafe { IGRAPH_FINALLY_ENTER() };
250    let keep = catch_panic_or(1, || {
251        let keep = (data.f)(row as usize, col as usize, value) as igraph_int_t;
252        failed = false;
253        keep
254    });
255    unsafe { IGRAPH_FINALLY_EXIT() };
256    if failed {
257        data.panicked = true;
258    }
259    keep
260}
261
262impl igraph_sparsemat_t {
263    /// Runs an igraph function initializing a sparse matrix.
264    ///
265    /// On failure the partially built matrix is leaked rather than risking a
266    /// double free (igraph may already have released it).
267    fn init_with(f: impl FnOnce(*mut igraph_sparsemat_t) -> igraph_error_t) -> Result<Self> {
268        ensure_init();
269        let mut raw = MaybeUninit::<igraph_sparsemat_t>::zeroed();
270        check(f(raw.as_mut_ptr()))?;
271        let m = unsafe { raw.assume_init() };
272        if m.cs.is_null() {
273            std::mem::forget(m);
274            return Err(Error::new(
275                crate::ErrorKind::Failure,
276                "igraph returned an empty sparse matrix",
277            ));
278        }
279        Ok(m)
280    }
281
282    /// A column-compressed view of `self`: borrowed if already compressed,
283    /// otherwise a compressed copy.
284    fn cc(&self) -> Result<Cow<'_, SparseMat>> {
285        if self.is_cc() {
286            Ok(Cow::Borrowed(self))
287        } else {
288            self.compress().map(Cow::Owned)
289        }
290    }
291
292    /// Converts `self` to column-compressed format in place, if needed.
293    fn make_cc(&mut self) -> Result<()> {
294        if self.is_triplet() {
295            *self = self.compress()?;
296        }
297        Ok(())
298    }
299
300    /// Creates an empty `nrow` × `ncol` sparse matrix in triplet format
301    /// (`igraph_sparsemat_init`), ready to receive entries with
302    /// [`entry`](Self::entry).
303    ///
304    /// Binds [`igraph_sparsemat_init`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_init).
305    pub fn new(nrow: usize, ncol: usize) -> Result<Self> {
306        Self::with_capacity(nrow, ncol, 0)
307    }
308
309    /// Creates an empty `nrow` × `ncol` triplet matrix with room for `nzmax`
310    /// entries (`igraph_sparsemat_init`). The capacity is only a hint: the
311    /// matrix grows as needed.
312    ///
313    /// Binds [`igraph_sparsemat_init`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_init).
314    pub fn with_capacity(nrow: usize, ncol: usize, nzmax: usize) -> Result<Self> {
315        let (r, c, z) = (
316            to_int(nrow, "nrow")?,
317            to_int(ncol, "ncol")?,
318            to_int(nzmax, "nzmax")?,
319        );
320        Self::init_with(|m| unsafe { igraph_sparsemat_init(m, r, c, z) })
321    }
322
323    /// Builds a triplet matrix from `(row, col, value)` entries; entries at the
324    /// same position are summed.
325    ///
326    /// # Errors
327    /// If an index is out of bounds.
328    pub fn from_triplets(
329        nrow: usize,
330        ncol: usize,
331        triplets: &[(usize, usize, f64)],
332    ) -> Result<Self> {
333        let mut m = Self::with_capacity(nrow, ncol, triplets.len())?;
334        for &(i, j, x) in triplets {
335            check_index(i, nrow, "row")?;
336            check_index(j, ncol, "column")?;
337            m.entry(i, j, x)?;
338        }
339        Ok(m)
340    }
341
342    /// Creates the `n` × `n` diagonal matrix with `value` on the diagonal
343    /// (`igraph_sparsemat_init_eye`), in column-compressed format if
344    /// `compress` is true, in triplet format otherwise. Time complexity: O(n).
345    ///
346    /// Binds [`igraph_sparsemat_init_eye`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_init_eye).
347    ///
348    /// ```
349    /// use igraph::linalg::SparseMat;
350    /// let i3 = SparseMat::eye(3, 1.0, true).unwrap();
351    /// assert!(i3.is_cc());
352    /// assert_eq!(i3.to_dense().unwrap().to_rows(), vec![vec![1.0, 0.0, 0.0], vec![0.0, 1.0, 0.0], vec![0.0, 0.0, 1.0]]);
353    /// ```
354    pub fn eye(n: usize, value: f64, compress: bool) -> Result<Self> {
355        let n = to_int(n, "n")?;
356        Self::init_with(|m| unsafe { igraph_sparsemat_init_eye(m, n, n, value, compress) })
357    }
358
359    /// The `n` × `n` identity matrix, in column-compressed format.
360    pub fn identity(n: usize) -> Result<Self> {
361        Self::eye(n, 1.0, true)
362    }
363
364    /// Creates a diagonal matrix with the given diagonal
365    /// (`igraph_sparsemat_init_diag`), column-compressed if `compress` is
366    /// true. Time complexity: O(n).
367    ///
368    /// Binds [`igraph_sparsemat_init_diag`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_init_diag).
369    pub fn diag(values: &[f64], compress: bool) -> Result<Self> {
370        let v = Vector::view(values);
371        let nzmax = to_int(values.len(), "diagonal length")?;
372        Self::init_with(|m| unsafe { igraph_sparsemat_init_diag(m, nzmax, v.as_ptr(), compress) })
373    }
374
375    /// Converts a dense matrix to a triplet sparse matrix, keeping only the
376    /// elements whose absolute value is larger than `tol`
377    /// (`igraph_matrix_as_sparsemat`). Time complexity: O(mn).
378    ///
379    /// Binds [`igraph_matrix_as_sparsemat`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_matrix_as_sparsemat).
380    ///
381    /// ```
382    /// use igraph::{linalg::SparseMat, prelude::*};
383    /// let m = Matrix::from_rows(&[[1.0, 1e-12], [0.0, 2.0]]).unwrap();
384    /// let s = SparseMat::from_dense(&m, 1e-9).unwrap();
385    /// assert_eq!(s.nonzero_storage(), 2);
386    /// ```
387    pub fn from_dense(m: &Matrix, tol: f64) -> Result<Self> {
388        Self::init_with(|s| unsafe { igraph_matrix_as_sparsemat(s, m, tol) })
389    }
390
391    /// Converts to a dense [`Matrix`] (`igraph_sparsemat_as_matrix`); works
392    /// with both formats, duplicate entries are summed. Time complexity: O(mn).
393    ///
394    /// Binds [`igraph_sparsemat_as_matrix`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_as_matrix).
395    pub fn to_dense(&self) -> Result<Matrix> {
396        let mut res = Matrix::new();
397        igraph_call!(igraph_sparsemat_as_matrix(&mut res, self))?;
398        Ok(res)
399    }
400
401    /// Changes the capacity (maximum number of stored entries) of the matrix
402    /// (`igraph_sparsemat_realloc`). Rarely needed: matrices grow automatically.
403    ///
404    /// Binds [`igraph_sparsemat_realloc`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_realloc).
405    pub fn realloc(&mut self, nzmax: usize) -> Result<()> {
406        let z = to_int(nzmax.max(self.nonzero_storage()), "nzmax")?;
407        igraph_call!(igraph_sparsemat_realloc(self, z))
408    }
409
410    /// Number of rows (`igraph_sparsemat_nrow`).
411    pub fn nrow(&self) -> usize {
412        unsafe { igraph_sparsemat_nrow(self) as usize }
413    }
414
415    /// Number of columns (`igraph_sparsemat_ncol`).
416    pub fn ncol(&self) -> usize {
417        unsafe { igraph_sparsemat_ncol(self) as usize }
418    }
419
420    /// `(nrow, ncol)`.
421    pub fn shape(&self) -> (usize, usize) {
422        (self.nrow(), self.ncol())
423    }
424
425    /// The storage format (`igraph_sparsemat_type`).
426    pub fn sparse_type(&self) -> SparseMatType {
427        match unsafe { igraph_sparsemat_type(self) } {
428            igraph_sparsemat_type_t_IGRAPH_SPARSEMAT_CC => SparseMatType::ColumnCompressed,
429            _ => SparseMatType::Triplet,
430        }
431    }
432
433    /// Whether the matrix is in triplet format (`igraph_sparsemat_is_triplet`).
434    pub fn is_triplet(&self) -> bool {
435        unsafe { igraph_sparsemat_is_triplet(self) }
436    }
437
438    /// Whether the matrix is column-compressed (`igraph_sparsemat_is_cc`).
439    pub fn is_cc(&self) -> bool {
440        unsafe { igraph_sparsemat_is_cc(self) }
441    }
442
443    /// Number of stored entries (`igraph_sparsemat_nonzero_storage`); they may
444    /// include zeros and duplicates, see [`dupl`](Self::dupl) and
445    /// [`dropzeros`](Self::dropzeros).
446    pub fn nonzero_storage(&self) -> usize {
447        unsafe { igraph_sparsemat_nonzero_storage(self) as usize }
448    }
449
450    /// The allocated capacity for entries (`igraph_sparsemat_nzmax`).
451    pub fn nzmax(&self) -> usize {
452        unsafe { igraph_sparsemat_nzmax(self) as usize }
453    }
454
455    /// Appends an entry to a triplet matrix (`igraph_sparsemat_entry`).
456    /// Entries at the same position are summed. Time complexity: O(1)
457    /// amortized.
458    ///
459    /// Binds [`igraph_sparsemat_entry`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_entry).
460    ///
461    /// # Errors
462    /// If the matrix is column-compressed, or the position is out of bounds
463    /// (use [`add_rows`](Self::add_rows)/[`add_cols`](Self::add_cols) to grow it).
464    pub fn entry(&mut self, row: usize, col: usize, value: f64) -> Result<()> {
465        check_index(row, self.nrow(), "row")?;
466        check_index(col, self.ncol(), "column")?;
467        igraph_call!(igraph_sparsemat_entry(
468            self,
469            row as igraph_int_t,
470            col as igraph_int_t,
471            value
472        ))
473    }
474
475    /// The value at `(row, col)` (`igraph_sparsemat_get`), summing duplicate
476    /// entries; zero if nothing is stored there or the position is out of
477    /// bounds. Time complexity: O(entries in the column) for a
478    /// column-compressed matrix, O(nz) for a triplet matrix.
479    ///
480    /// For triplet matrices the lookup is done on the Rust side: igraph 1.0.0 and 1.0.1
481    /// scans them with its sparse matrix iterator, which reads past the end
482    /// of the column index array (see [`iter`](Self::iter)).
483    ///
484    /// Binds [`igraph_sparsemat_get`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_get).
485    pub fn get(&self, row: usize, col: usize) -> f64 {
486        if row >= self.nrow() || col >= self.ncol() {
487            return 0.0;
488        }
489        if self.is_triplet() {
490            return self
491                .iter()
492                .filter(|&(i, j, _)| i == row && j == col)
493                .map(|(_, _, x)| x)
494                .sum();
495        }
496        unsafe { igraph_sparsemat_get(self, row as igraph_int_t, col as igraph_int_t) }
497    }
498
499    /// Returns a column-compressed copy of the matrix
500    /// (`igraph_sparsemat_compress`); if the matrix is already compressed, a
501    /// plain copy. Time complexity: O(nz).
502    ///
503    /// Binds [`igraph_sparsemat_compress`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_compress).
504    pub fn compress(&self) -> Result<SparseMat> {
505        if self.is_cc() {
506            return Ok(self.clone());
507        }
508        Self::init_with(|res| unsafe { igraph_sparsemat_compress(self, res) })
509    }
510
511    /// The transposed matrix (`igraph_sparsemat_transpose`), in the same
512    /// format as `self`.
513    ///
514    /// In igraph 1.0.0 and 1.0.1 the transpose of a *non-square triplet*
515    /// matrix swaps the indices but forgets to swap the dimensions; this
516    /// wrapper builds that case entry by entry instead.
517    ///
518    /// Binds [`igraph_sparsemat_transpose`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_transpose).
519    pub fn transpose(&self) -> Result<SparseMat> {
520        if self.is_triplet() && self.nrow() != self.ncol() {
521            let mut t = Self::with_capacity(self.ncol(), self.nrow(), self.nonzero_storage())?;
522            for (i, j, x) in self.iter() {
523                t.entry(j, i, x)?;
524            }
525            return Ok(t);
526        }
527        Self::init_with(|res| unsafe { igraph_sparsemat_transpose(self, res) })
528    }
529
530    /// Whether the matrix is symmetric (`igraph_sparsemat_is_symmetric`);
531    /// non-square matrices are not.
532    ///
533    /// Duplicates are summed first, but the comparison is otherwise
534    /// structural and exact: an explicitly stored zero at `(i, j)` without a
535    /// stored counterpart at `(j, i)` makes the matrix non-symmetric (use
536    /// [`dropzeros`](Self::dropzeros) first), and so do values differing by
537    /// rounding errors.
538    ///
539    /// Binds [`igraph_sparsemat_is_symmetric`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_is_symmetric).
540    pub fn is_symmetric(&self) -> Result<bool> {
541        let mut res = false;
542        igraph_call!(igraph_sparsemat_is_symmetric(self, &mut res))?;
543        Ok(res)
544    }
545
546    /// Sums duplicate entries of the same position into one
547    /// (`igraph_sparsemat_dupl`). A triplet matrix is first converted to
548    /// column-compressed format in place.
549    ///
550    /// Binds [`igraph_sparsemat_dupl`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_dupl).
551    pub fn dupl(&mut self) -> Result<()> {
552        self.make_cc()?;
553        igraph_call!(igraph_sparsemat_dupl(self))
554    }
555
556    /// Keeps only the stored entries for which `keep(row, col, value)` returns
557    /// true (`igraph_sparsemat_fkeep`). A triplet matrix is first converted
558    /// to column-compressed format in place.
559    ///
560    /// Binds [`igraph_sparsemat_fkeep`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_fkeep).
561    ///
562    /// ```
563    /// use igraph::linalg::SparseMat;
564    /// // Keep the upper triangle of a 3x3 matrix of ones.
565    /// let mut m = SparseMat::from_dense(&igraph::prelude::Matrix::from_rows(&[[1.0; 3]; 3]).unwrap(), 0.0).unwrap();
566    /// m.fkeep(|i, j, _| i <= j).unwrap();
567    /// assert_eq!(m.nonzero_storage(), 6);
568    /// assert_eq!(m.get(2, 0), 0.0);
569    /// ```
570    pub fn fkeep<F: FnMut(usize, usize, f64) -> bool>(&mut self, keep: F) -> Result<()> {
571        self.make_cc()?;
572        let mut data = FkeepData {
573            f: keep,
574            panicked: false,
575        };
576        igraph_call!(igraph_sparsemat_fkeep(
577            self,
578            Some(fkeep_trampoline::<F>),
579            &mut data as *mut FkeepData<F> as *mut c_void
580        ))
581    }
582
583    /// Removes the stored entries that are exactly zero
584    /// (`igraph_sparsemat_dropzeros`); a triplet matrix is first compressed
585    /// in place.
586    ///
587    /// Binds [`igraph_sparsemat_dropzeros`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_dropzeros).
588    pub fn dropzeros(&mut self) -> Result<()> {
589        self.make_cc()?;
590        igraph_call!(igraph_sparsemat_dropzeros(self))
591    }
592
593    /// Removes the stored entries whose absolute value is at most `tol`
594    /// (`igraph_sparsemat_droptol`); a triplet matrix is first compressed in
595    /// place.
596    ///
597    /// Binds [`igraph_sparsemat_droptol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_droptol).
598    pub fn droptol(&mut self, tol: f64) -> Result<()> {
599        self.make_cc()?;
600        igraph_call!(igraph_sparsemat_droptol(self, tol))
601    }
602
603    /// Matrix product `self * other` (`igraph_sparsemat_multiply`), a
604    /// column-compressed matrix. Also available as `&a * &b`.
605    ///
606    /// Binds [`igraph_sparsemat_multiply`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_multiply).
607    ///
608    /// # Errors
609    /// If the inner dimensions do not match.
610    pub fn multiply(&self, other: &SparseMat) -> Result<SparseMat> {
611        if self.ncol() != other.nrow() {
612            return Err(Error::invalid(format!(
613                "cannot multiply a {:?} matrix by a {:?} matrix",
614                self.shape(),
615                other.shape()
616            )));
617        }
618        let (a, b) = (self.cc()?, other.cc()?);
619        Self::init_with(|res| unsafe { igraph_sparsemat_multiply(&*a, &*b, res) })
620    }
621
622    /// Linear combination `alpha * self + beta * other`
623    /// (`igraph_sparsemat_add`), a column-compressed matrix. `&a + &b` and
624    /// `&a - &b` are shorthands.
625    ///
626    /// Binds [`igraph_sparsemat_add`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_add).
627    ///
628    /// # Errors
629    /// If the shapes differ.
630    pub fn add(&self, other: &SparseMat, alpha: f64, beta: f64) -> Result<SparseMat> {
631        if self.shape() != other.shape() {
632            return Err(Error::invalid(format!(
633                "cannot add a {:?} matrix and a {:?} matrix",
634                self.shape(),
635                other.shape()
636            )));
637        }
638        let (a, b) = (self.cc()?, other.cc()?);
639        Self::init_with(|res| unsafe { igraph_sparsemat_add(&*a, &*b, alpha, beta, res) })
640    }
641
642    /// Computes `y + A x` (`igraph_sparsemat_gaxpy`, "generalized A x plus y").
643    ///
644    /// Binds [`igraph_sparsemat_gaxpy`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_gaxpy).
645    ///
646    /// # Errors
647    /// If `x.len() != ncol` or `y.len() != nrow`.
648    pub fn gaxpy(&self, x: &[f64], y: &[f64]) -> Result<Vec<f64>> {
649        check_len(x.len(), self.ncol(), "x")?;
650        check_len(y.len(), self.nrow(), "y")?;
651        let a = self.cc()?;
652        let xv = Vector::view(x);
653        let mut res = Vector::from_slice(y);
654        igraph_call!(igraph_sparsemat_gaxpy(&*a, xv.as_ptr(), &mut res))?;
655        Ok(res.into())
656    }
657
658    /// The matrix-vector product `A x` (via [`gaxpy`](Self::gaxpy)).
659    pub fn mul_vec(&self, x: &[f64]) -> Result<Vec<f64>> {
660        self.gaxpy(x, &vec![0.0; self.nrow()])
661    }
662
663    /// Makes a column-compressed, duplicate-free, row-sorted copy suitable
664    /// for the triangular solvers, and checks that it is triangular with a
665    /// stored diagonal.
666    fn triangular(&self, lower: bool, what: &str) -> Result<SparseMat> {
667        let (n, m) = self.shape();
668        if n != m {
669            return Err(Error::invalid(format!(
670                "{what} needs a square matrix, got {:?}",
671                self.shape()
672            )));
673        }
674        let mut t = self.sort()?;
675        t.dupl()?;
676        let t = t.sort()?;
677        let e = t.getelements()?;
678        for col in 0..n {
679            let (start, end) = (e.j[col] as usize, e.j[col + 1] as usize);
680            let diag = if lower { start } else { end.wrapping_sub(1) };
681            if start == end || e.i[diag] as usize != col {
682                return Err(Error::invalid(format!(
683                    "{what}: diagonal element {col} is not stored (the matrix must be triangular with a stored diagonal)"
684                )));
685            }
686            let ok = e.i[start..end].iter().all(|&r| {
687                if lower {
688                    r as usize >= col
689                } else {
690                    r as usize <= col
691                }
692            });
693            if !ok {
694                return Err(Error::invalid(format!(
695                    "{what}: the matrix is not {} triangular",
696                    if lower { "lower" } else { "upper" }
697                )));
698            }
699        }
700        Ok(t)
701    }
702
703    fn solve_with(
704        &self,
705        b: &[f64],
706        lower: bool,
707        what: &str,
708        f: unsafe extern "C" fn(
709            *const igraph_sparsemat_t,
710            *const igraph_vector_t,
711            *mut igraph_vector_t,
712        ) -> igraph_error_t,
713    ) -> Result<Vec<f64>> {
714        check_len(b.len(), self.nrow(), "b")?;
715        let t = self.triangular(lower, what)?;
716        let bv = Vector::view(b);
717        let mut res = Vector::new();
718        igraph_call!(f(&t, bv.as_ptr(), &mut res))?;
719        Ok(res.into())
720    }
721
722    /// Solves the lower triangular system `L x = b` (`igraph_sparsemat_lsolve`).
723    ///
724    /// The matrix must be square, lower triangular, and have all its diagonal
725    /// elements stored (a zero on the diagonal gives infinite or NaN values).
726    ///
727    /// Binds [`igraph_sparsemat_lsolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_lsolve).
728    pub fn lsolve(&self, b: &[f64]) -> Result<Vec<f64>> {
729        self.solve_with(b, true, "lsolve", igraph_sparsemat_lsolve)
730    }
731
732    /// Solves `L' x = b` where `L` (this matrix) is lower triangular
733    /// (`igraph_sparsemat_ltsolve`); requirements as in [`lsolve`](Self::lsolve).
734    ///
735    /// Binds [`igraph_sparsemat_ltsolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_ltsolve).
736    pub fn ltsolve(&self, b: &[f64]) -> Result<Vec<f64>> {
737        self.solve_with(b, true, "ltsolve", igraph_sparsemat_ltsolve)
738    }
739
740    /// Solves the upper triangular system `U x = b` (`igraph_sparsemat_usolve`).
741    ///
742    /// The matrix must be square, upper triangular, with a stored diagonal.
743    ///
744    /// Binds [`igraph_sparsemat_usolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_usolve).
745    pub fn usolve(&self, b: &[f64]) -> Result<Vec<f64>> {
746        self.solve_with(b, false, "usolve", igraph_sparsemat_usolve)
747    }
748
749    /// Solves `U' x = b` where `U` (this matrix) is upper triangular
750    /// (`igraph_sparsemat_utsolve`); requirements as in [`usolve`](Self::usolve).
751    ///
752    /// Binds [`igraph_sparsemat_utsolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_utsolve).
753    pub fn utsolve(&self, b: &[f64]) -> Result<Vec<f64>> {
754        self.solve_with(b, false, "utsolve", igraph_sparsemat_utsolve)
755    }
756
757    /// Solves `A x = b` for a symmetric positive definite `A` via a sparse
758    /// Cholesky factorization (`igraph_sparsemat_cholsol`). Only the upper
759    /// triangular part of `A` is used.
760    ///
761    /// Binds [`igraph_sparsemat_cholsol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_cholsol).
762    ///
763    /// # Errors
764    /// If `A` is not square, `b` has the wrong length, or `A` is not positive
765    /// definite.
766    pub fn cholsol(&self, b: &[f64], order: SparseOrdering) -> Result<Vec<f64>> {
767        self.check_square_rhs(b, "cholsol")?;
768        let a = self.cc()?;
769        let bv = Vector::view(b);
770        let mut res = Vector::new();
771        igraph_call!(igraph_sparsemat_cholsol(
772            &*a,
773            bv.as_ptr(),
774            &mut res,
775            order.raw()
776        ))?;
777        Ok(res.into())
778    }
779
780    /// Solves `A x = b` via a sparse LU factorization
781    /// (`igraph_sparsemat_lusol`). `tol` is the partial pivoting threshold:
782    /// `1.0` means classic partial pivoting, smaller values (e.g. `0.001`)
783    /// favor sparsity, together with a fill-reducing `order`.
784    ///
785    /// Binds [`igraph_sparsemat_lusol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_lusol).
786    ///
787    /// # Errors
788    /// If `A` is not square, `b` has the wrong length or `A` is singular.
789    pub fn lusol(&self, b: &[f64], order: SparseOrdering, tol: f64) -> Result<Vec<f64>> {
790        self.check_square_rhs(b, "lusol")?;
791        let a = self.cc()?;
792        let bv = Vector::view(b);
793        let mut res = Vector::new();
794        igraph_call!(igraph_sparsemat_lusol(
795            &*a,
796            bv.as_ptr(),
797            &mut res,
798            order.raw(),
799            tol
800        ))?;
801        Ok(res.into())
802    }
803
804    fn check_square_rhs(&self, b: &[f64], what: &str) -> Result<()> {
805        if self.nrow() != self.ncol() {
806            return Err(Error::invalid(format!(
807                "{what} needs a square matrix, got {:?}",
808                self.shape()
809            )));
810        }
811        check_len(b.len(), self.nrow(), "b")
812    }
813
814    /// Prints the stored entries into a string (`igraph_sparsemat_print`).
815    ///
816    /// Triplet matrices print one `row col : value` line per entry;
817    /// column-compressed ones print, for each column, a `col j: locations a
818    /// to b` header followed by `row : value` lines. Also used by the
819    /// [`Display`](fmt::Display) implementation.
820    ///
821    /// Binds [`igraph_sparsemat_print`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_print).
822    pub fn print_to_string(&self) -> Result<String> {
823        ensure_init();
824        let mut buf: *mut c_char = ptr::null_mut();
825        let mut size: usize = 0;
826        let stream = unsafe { open_memstream(&mut buf, &mut size) };
827        if stream.is_null() {
828            return Err(Error::new(
829                crate::ErrorKind::File,
830                "cannot open a memory stream",
831            ));
832        }
833        let res = check(unsafe { igraph_sparsemat_print(self, stream) });
834        unsafe { fclose(stream) };
835        let out = if buf.is_null() {
836            String::new()
837        } else {
838            let s = unsafe { CStr::from_ptr(buf) }
839                .to_string_lossy()
840                .into_owned();
841            unsafe { free(buf as *mut c_void) };
842            s
843        };
844        res.map(|()| out)
845    }
846
847    /// Permutes rows and columns (`igraph_sparsemat_permute`): row `i` of the
848    /// result is row `p[i]` of `self`, and column `j` of the result is column
849    /// `q[j]` of `self`. The result is column-compressed. Time complexity:
850    /// O(m + n + nz).
851    ///
852    /// Binds [`igraph_sparsemat_permute`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_permute).
853    ///
854    /// # Errors
855    /// If `p` (resp. `q`) is not a permutation of `0..nrow` (resp. `0..ncol`).
856    pub fn permute(&self, p: &[usize], q: &[usize]) -> Result<SparseMat> {
857        let pv = check_permutation(p, self.nrow(), "row permutation")?;
858        let qv = check_permutation(q, self.ncol(), "column permutation")?;
859        let a = self.cc()?;
860        Self::init_with(|res| unsafe { igraph_sparsemat_permute(&*a, &pv, &qv, res) })
861    }
862
863    /// Extracts the submatrix made of the given rows and columns
864    /// (`igraph_sparsemat_index`); `None` selects all rows (resp. columns).
865    /// Indices may repeat. The result is column-compressed.
866    ///
867    /// Binds [`igraph_sparsemat_index`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_index).
868    ///
869    /// # Errors
870    /// If an index is out of bounds.
871    pub fn index(&self, rows: Option<&[usize]>, cols: Option<&[usize]>) -> Result<SparseMat> {
872        let conv = |idx: &[usize], bound: usize, what: &str| -> Result<VectorInt> {
873            for &x in idx {
874                check_index(x, bound, what)?;
875            }
876            Ok(idx.iter().map(|&x| x as igraph_int_t).collect())
877        };
878        let a = self.cc()?;
879        let all_rows: Vec<usize>;
880        let rows = match (rows, cols) {
881            // igraph needs at least one index vector.
882            (None, None) => {
883                all_rows = (0..self.nrow()).collect();
884                Some(&all_rows[..])
885            }
886            (r, _) => r,
887        };
888        let p = rows.map(|r| conv(r, self.nrow(), "row")).transpose()?;
889        let q = cols.map(|c| conv(c, self.ncol(), "column")).transpose()?;
890        let pp = p.as_ref().map_or(ptr::null(), |v| v as *const VectorInt);
891        let qp = q.as_ref().map_or(ptr::null(), |v| v as *const VectorInt);
892        Self::init_with(|res| unsafe { igraph_sparsemat_index(&*a, pp, qp, res, ptr::null_mut()) })
893    }
894
895    /// Computes the symbolic analysis and numeric LU factorization of a
896    /// square matrix (`igraph_sparsemat_symblu` + `igraph_sparsemat_lu`), to
897    /// solve many systems with the same coefficient matrix via
898    /// [`SparseLu::solve`]. `tol` is the partial pivoting threshold, as in
899    /// [`lusol`](Self::lusol).
900    ///
901    /// Binds [`igraph_sparsemat_symblu`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_symblu)
902    /// and [`igraph_sparsemat_lu`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_lu).
903    ///
904    /// ```
905    /// use igraph::linalg::{SparseMat, SparseOrdering};
906    /// let a = SparseMat::from_triplets(2, 2, &[(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)]).unwrap();
907    /// let lu = a.lu(SparseOrdering::Natural, 1.0).unwrap();
908    /// let x = lu.solve(&[3.0, 4.0]).unwrap();
909    /// assert!((x[0] - 1.0).abs() < 1e-12 && (x[1] - 1.0).abs() < 1e-12);
910    /// ```
911    ///
912    /// # Errors
913    /// If the matrix is not square or is singular.
914    pub fn lu(&self, order: SparseOrdering, tol: f64) -> Result<SparseLu> {
915        let n = self.square_dim("LU decomposition")?;
916        let a = self.cc()?;
917        let symbolic =
918            SymbolicGuard::new(|s| unsafe { igraph_sparsemat_symblu(order.raw(), &*a, s) })?;
919        let numeric =
920            NumericGuard::new(|d| unsafe { igraph_sparsemat_lu(&*a, &symbolic.0, d, tol) })?;
921        Ok(SparseLu {
922            symbolic,
923            numeric,
924            n,
925        })
926    }
927
928    /// Computes the symbolic analysis and numeric QR factorization of a
929    /// square matrix (`igraph_sparsemat_symbqr` + `igraph_sparsemat_qr`), to
930    /// solve many systems with the same coefficient matrix via
931    /// [`SparseQr::solve`].
932    ///
933    /// Binds [`igraph_sparsemat_symbqr`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_symbqr)
934    /// and [`igraph_sparsemat_qr`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_qr).
935    ///
936    /// # Errors
937    /// If the matrix is not square or the factorization fails.
938    pub fn qr(&self, order: SparseOrdering) -> Result<SparseQr> {
939        let n = self.square_dim("QR decomposition")?;
940        let a = self.cc()?;
941        let symbolic =
942            SymbolicGuard::new(|s| unsafe { igraph_sparsemat_symbqr(order.raw(), &*a, s) })?;
943        let numeric = NumericGuard::new(|d| unsafe { igraph_sparsemat_qr(&*a, &symbolic.0, d) })?;
944        Ok(SparseQr {
945            symbolic,
946            numeric,
947            n,
948        })
949    }
950
951    fn square_dim(&self, what: &str) -> Result<usize> {
952        let (n, m) = self.shape();
953        if n != m || n == 0 {
954            return Err(Error::invalid(format!(
955                "{what} needs a non-empty square matrix, got {:?}",
956                self.shape()
957            )));
958        }
959        Ok(n)
960    }
961
962    /// Eigenvalues and eigenvectors of a *symmetric* sparse matrix with
963    /// ARPACK (`igraph_sparsemat_arpack_rssolve`).
964    ///
965    /// With [`ArpackMode::Regular`](super::ArpackMode::Regular) ARPACK works
966    /// with products `A x` (this is igraph's driver). With
967    /// [`ArpackMode::ShiftInvert`](super::ArpackMode::ShiftInvert)`{ sigma }`
968    /// it works with `(A - sigma I)^-1 x`, computed by factorizing `A - sigma
969    /// I` once with the given `method` (an LU decomposition with partial
970    /// pivoting, or a QR decomposition); combined with
971    /// [`ArpackWhich::LargestMagnitude`](super::ArpackWhich::LargestMagnitude)
972    /// this finds the eigenvalues closest to `sigma`. `method` is ignored in
973    /// regular mode.
974    ///
975    /// As with [`arpack_rssolve`](super::arpack_rssolve), 2 × 2 matrices in
976    /// regular mode are solved in closed form, with the eigenpairs selected
977    /// and normalized on the Rust side.
978    ///
979    /// The shift-and-invert mode is driven from Rust (with
980    /// [`lu`](Self::lu)/[`qr`](Self::qr) and [`arpack_rssolve`](super::arpack_rssolve)):
981    /// igraph's own driver (1.0.0 and 1.0.1) factorizes without pivoting, which breaks down —
982    /// and makes ARPACK abort the process — whenever `A - sigma I` has a zero
983    /// on the diagonal.
984    ///
985    /// Binds [`igraph_sparsemat_arpack_rssolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_arpack_rssolve).
986    ///
987    /// # Errors
988    /// If the matrix is not square, contains non-finite values, the options
989    /// are invalid, `A - sigma I` is singular, or ARPACK fails.
990    pub fn arpack_rssolve(
991        &self,
992        options: &ArpackOptions,
993        method: SparseSolveMethod,
994    ) -> Result<ArpackSymmetricResult> {
995        let n = self.square_dim("ARPACK")?;
996        let a = self.cc()?;
997        check_finite(&a.getelements()?.x, "the matrix")?;
998        match options.mode {
999            super::ArpackMode::Regular => {
1000                let mut raw = options.to_raw(n, true)?;
1001                let shortcut = super::arpack::uses_2x2_shortcut(n, options);
1002                if shortcut {
1003                    raw.nev = 2;
1004                }
1005                let mut values = Vector::new();
1006                let mut vectors = options.start_matrix(n)?;
1007                let _arpack = super::arpack::ArpackGuard::enter()?;
1008                igraph_call!(igraph_sparsemat_arpack_rssolve(
1009                    &*a,
1010                    &mut raw,
1011                    ptr::null_mut(),
1012                    &mut values,
1013                    &mut vectors,
1014                    method.into()
1015                ))?;
1016                let res = ArpackSymmetricResult::from_raw(values, vectors, &raw);
1017                if shortcut {
1018                    res.select_2x2(options.which, options.nev)
1019                } else {
1020                    Ok(res)
1021                }
1022            }
1023            super::ArpackMode::ShiftInvert { sigma } => {
1024                if !sigma.is_finite() {
1025                    return Err(Error::invalid("the shift must be finite"));
1026                }
1027                // Validate the options before factorizing.
1028                options.to_raw(n, true)?;
1029                let shifted = a.add(&SparseMat::identity(n)?, 1.0, -sigma)?;
1030                let solver: Solver = match method {
1031                    SparseSolveMethod::Lu => {
1032                        let lu = shifted.lu(SparseOrdering::MinDegreeSymmetric, 1.0)?;
1033                        Box::new(move |b| lu.solve(b))
1034                    }
1035                    SparseSolveMethod::Qr => {
1036                        let qr = shifted.qr(SparseOrdering::MinDegreeAtA)?;
1037                        Box::new(move |b| qr.solve(b))
1038                    }
1039                };
1040                let mut failure = None;
1041                let res = super::arpack_rssolve(
1042                    n,
1043                    |x: &[f64], y: &mut [f64]| match solver(x) {
1044                        Ok(v) => y.copy_from_slice(&v),
1045                        Err(e) => {
1046                            failure = Some(e);
1047                            // Stops ARPACK (see `arpack_rssolve`).
1048                            y.fill(f64::NAN);
1049                        }
1050                    },
1051                    options,
1052                    None,
1053                );
1054                match failure {
1055                    Some(e) => Err(e),
1056                    None => res,
1057                }
1058            }
1059        }
1060    }
1061
1062    /// Eigenvalues and eigenvectors of a general (non-symmetric) sparse
1063    /// matrix with ARPACK (`igraph_sparsemat_arpack_rnsolve`). Only
1064    /// [`ArpackMode::Regular`](super::ArpackMode::Regular) is supported.
1065    ///
1066    /// Binds [`igraph_sparsemat_arpack_rnsolve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_arpack_rnsolve).
1067    pub fn arpack_rnsolve(&self, options: &ArpackOptions) -> Result<ArpackNonSymmetricResult> {
1068        let n = self.square_dim("ARPACK")?;
1069        if options.mode != super::ArpackMode::Regular {
1070            return Err(Error::invalid(
1071                "the sparse non-symmetric ARPACK solver supports only the regular mode",
1072            ));
1073        }
1074        let a = self.cc()?;
1075        check_finite(&a.getelements()?.x, "the matrix")?;
1076        let mut raw = options.to_raw(n, false)?;
1077        let mut values = Matrix::new();
1078        let mut vectors = options.start_matrix(n)?;
1079        let _arpack = super::arpack::ArpackGuard::enter()?;
1080        igraph_call!(igraph_sparsemat_arpack_rnsolve(
1081            &*a,
1082            &mut raw,
1083            ptr::null_mut(),
1084            &mut values,
1085            &mut vectors
1086        ))?;
1087        ArpackNonSymmetricResult::from_raw(values, vectors, &raw, options.nev)
1088    }
1089
1090    fn stored_values(&mut self) -> Result<Vec<f64>> {
1091        self.dupl()?;
1092        Ok(self.getelements()?.x)
1093    }
1094
1095    /// The largest *stored* value, after summing duplicates
1096    /// (`igraph_sparsemat_max`); `-inf` if nothing is stored. Implicit zeros
1097    /// are not considered. A triplet matrix is compressed in place.
1098    ///
1099    /// In igraph 1.0.0 and 1.0.1, `igraph_sparsemat_max` skips the last stored element, so
1100    /// this wrapper computes the maximum over the entries it reads with
1101    /// [`getelements`](Self::getelements) after `igraph_sparsemat_dupl`.
1102    pub fn max(&mut self) -> Result<f64> {
1103        Ok(self
1104            .stored_values()?
1105            .into_iter()
1106            .fold(f64::NEG_INFINITY, f64::max))
1107    }
1108
1109    /// The smallest *stored* value, after summing duplicates
1110    /// (`igraph_sparsemat_min`); `+inf` if nothing is stored. See
1111    /// [`max`](Self::max) for the caveats.
1112    pub fn min(&mut self) -> Result<f64> {
1113        Ok(self
1114            .stored_values()?
1115            .into_iter()
1116            .fold(f64::INFINITY, f64::min))
1117    }
1118
1119    /// `(min, max)` of the stored values (`igraph_sparsemat_minmax`), see
1120    /// [`max`](Self::max).
1121    pub fn minmax(&mut self) -> Result<(f64, f64)> {
1122        let v = self.stored_values()?;
1123        Ok((
1124            v.iter().copied().fold(f64::INFINITY, f64::min),
1125            v.iter().copied().fold(f64::NEG_INFINITY, f64::max),
1126        ))
1127    }
1128
1129    /// Number of stored entries that are not zero, after summing duplicates
1130    /// (`igraph_sparsemat_count_nonzero`). A triplet matrix is compressed in
1131    /// place.
1132    ///
1133    /// Binds [`igraph_sparsemat_count_nonzero`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_count_nonzero).
1134    pub fn count_nonzero(&mut self) -> Result<usize> {
1135        self.dupl()?;
1136        Ok(unsafe { igraph_sparsemat_count_nonzero(self) } as usize)
1137    }
1138
1139    /// Number of stored entries whose absolute value exceeds `tol`, after
1140    /// summing duplicates (`igraph_sparsemat_count_nonzerotol`).
1141    ///
1142    /// Binds [`igraph_sparsemat_count_nonzerotol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_count_nonzerotol).
1143    pub fn count_nonzerotol(&mut self, tol: f64) -> Result<usize> {
1144        self.dupl()?;
1145        Ok(unsafe { igraph_sparsemat_count_nonzerotol(self, tol) } as usize)
1146    }
1147
1148    /// Row sums (`igraph_sparsemat_rowsums`). Time complexity: O(nz).
1149    ///
1150    /// Binds [`igraph_sparsemat_rowsums`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_rowsums).
1151    pub fn rowsums(&self) -> Result<Vec<f64>> {
1152        let mut res = Vector::new();
1153        igraph_call!(igraph_sparsemat_rowsums(self, &mut res))?;
1154        Ok(res.into())
1155    }
1156
1157    /// Column sums (`igraph_sparsemat_colsums`).
1158    ///
1159    /// Binds [`igraph_sparsemat_colsums`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_colsums).
1160    pub fn colsums(&self) -> Result<Vec<f64>> {
1161        let mut res = Vector::new();
1162        igraph_call!(igraph_sparsemat_colsums(self, &mut res))?;
1163        Ok(res.into())
1164    }
1165
1166    fn reduce(
1167        &mut self,
1168        f: unsafe extern "C" fn(*mut igraph_sparsemat_t, *mut igraph_vector_t) -> igraph_error_t,
1169    ) -> Result<Vec<f64>> {
1170        self.make_cc()?;
1171        let mut res = Vector::new();
1172        igraph_call!(f(self, &mut res))?;
1173        Ok(res.into())
1174    }
1175
1176    /// Minimum of the *stored* values of each row (`igraph_sparsemat_rowmins`);
1177    /// `+inf` for rows without stored values. Implicit zeros are not
1178    /// considered. A triplet matrix is compressed in place first, and
1179    /// duplicate entries are summed.
1180    pub fn rowmins(&mut self) -> Result<Vec<f64>> {
1181        self.reduce(igraph_sparsemat_rowmins)
1182    }
1183
1184    /// Minimum of the stored values of each column (`igraph_sparsemat_colmins`),
1185    /// see [`rowmins`](Self::rowmins).
1186    pub fn colmins(&mut self) -> Result<Vec<f64>> {
1187        self.reduce(igraph_sparsemat_colmins)
1188    }
1189
1190    /// Maximum of the stored values of each row (`igraph_sparsemat_rowmaxs`);
1191    /// `-inf` for rows without stored values.
1192    pub fn rowmaxs(&mut self) -> Result<Vec<f64>> {
1193        self.reduce(igraph_sparsemat_rowmaxs)
1194    }
1195
1196    /// Maximum of the stored values of each column (`igraph_sparsemat_colmaxs`),
1197    /// see [`rowmaxs`](Self::rowmaxs).
1198    pub fn colmaxs(&mut self) -> Result<Vec<f64>> {
1199        self.reduce(igraph_sparsemat_colmaxs)
1200    }
1201
1202    fn which_min(
1203        &mut self,
1204        f: unsafe extern "C" fn(
1205            *mut igraph_sparsemat_t,
1206            *mut igraph_vector_t,
1207            *mut igraph_vector_int_t,
1208        ) -> igraph_error_t,
1209    ) -> Result<(Vec<f64>, Vec<usize>)> {
1210        self.make_cc()?;
1211        let mut res = Vector::new();
1212        let mut pos = VectorInt::new();
1213        igraph_call!(f(self, &mut res, &mut pos))?;
1214        Ok((res.into(), pos.iter().map(|&p| p as usize).collect()))
1215    }
1216
1217    /// For each row, the minimum stored value and the column where it is
1218    /// found (`igraph_sparsemat_which_min_rows`); rows without stored values
1219    /// give `(+inf, 0)`. A triplet matrix is compressed in place first.
1220    pub fn which_min_rows(&mut self) -> Result<(Vec<f64>, Vec<usize>)> {
1221        self.which_min(igraph_sparsemat_which_min_rows)
1222    }
1223
1224    /// For each column, the minimum stored value and the row where it is
1225    /// found (`igraph_sparsemat_which_min_cols`); columns without stored
1226    /// values give `(+inf, 0)`. A triplet matrix is compressed in place first.
1227    pub fn which_min_cols(&mut self) -> Result<(Vec<f64>, Vec<usize>)> {
1228        self.which_min(igraph_sparsemat_which_min_cols)
1229    }
1230
1231    /// Multiplies every element by `by` (`igraph_sparsemat_scale`). Time
1232    /// complexity: O(nz).
1233    ///
1234    /// Binds [`igraph_sparsemat_scale`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_scale).
1235    pub fn scale(&mut self, by: f64) -> Result<()> {
1236        igraph_call!(igraph_sparsemat_scale(self, by))
1237    }
1238
1239    /// Multiplies row `i` by `factors[i]` (`igraph_sparsemat_scale_rows`),
1240    /// i.e. computes `diag(factors) * A`.
1241    ///
1242    /// # Errors
1243    /// If `factors.len() != nrow`.
1244    pub fn scale_rows(&mut self, factors: &[f64]) -> Result<()> {
1245        check_len(factors.len(), self.nrow(), "row factors")?;
1246        let f = Vector::view(factors);
1247        igraph_call!(igraph_sparsemat_scale_rows(self, f.as_ptr()))
1248    }
1249
1250    /// Multiplies column `j` by `factors[j]` (`igraph_sparsemat_scale_cols`),
1251    /// i.e. computes `A * diag(factors)`.
1252    ///
1253    /// # Errors
1254    /// If `factors.len() != ncol`.
1255    pub fn scale_cols(&mut self, factors: &[f64]) -> Result<()> {
1256        check_len(factors.len(), self.ncol(), "column factors")?;
1257        let f = Vector::view(factors);
1258        igraph_call!(igraph_sparsemat_scale_cols(self, f.as_ptr()))
1259    }
1260
1261    /// Negates every element in place (`igraph_sparsemat_neg`).
1262    pub fn neg(&mut self) -> Result<()> {
1263        igraph_call!(igraph_sparsemat_neg(self))
1264    }
1265
1266    /// Appends `n` zero rows (`igraph_sparsemat_add_rows`). Time complexity: O(1).
1267    ///
1268    /// Binds [`igraph_sparsemat_add_rows`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_add_rows).
1269    pub fn add_rows(&mut self, n: usize) -> Result<()> {
1270        let total = self
1271            .nrow()
1272            .checked_add(n)
1273            .ok_or_else(|| Error::invalid("number of rows overflows"))?;
1274        to_int(total, "number of rows")?;
1275        igraph_call!(igraph_sparsemat_add_rows(self, n as igraph_int_t))
1276    }
1277
1278    /// Appends `n` zero columns (`igraph_sparsemat_add_cols`).
1279    ///
1280    /// Binds [`igraph_sparsemat_add_cols`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_add_cols).
1281    pub fn add_cols(&mut self, n: usize) -> Result<()> {
1282        let total = self
1283            .ncol()
1284            .checked_add(n)
1285            .ok_or_else(|| Error::invalid("number of columns overflows"))?;
1286        to_int(total, "number of columns")?;
1287        igraph_call!(igraph_sparsemat_add_cols(self, n as igraph_int_t))
1288    }
1289
1290    /// Resizes to `nrow` × `ncol` and **removes all the entries**
1291    /// (`igraph_sparsemat_resize`); the result is an empty triplet matrix with
1292    /// room for `nzmax` entries.
1293    ///
1294    /// Binds [`igraph_sparsemat_resize`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_resize).
1295    pub fn resize(&mut self, nrow: usize, ncol: usize, nzmax: usize) -> Result<()> {
1296        let (r, c, z) = (
1297            to_int(nrow, "nrow")?,
1298            to_int(ncol, "ncol")?,
1299            to_int(nzmax.max(1), "nzmax")?,
1300        );
1301        igraph_call!(igraph_sparsemat_resize(self, r, c, z))
1302    }
1303
1304    /// The raw stored elements (`igraph_sparsemat_getelements`), see
1305    /// [`SparseElements`] for the layout, which depends on the format.
1306    ///
1307    /// Binds [`igraph_sparsemat_getelements`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_getelements).
1308    pub fn getelements(&self) -> Result<SparseElements> {
1309        let (mut i, mut j, mut x) = (VectorInt::new(), VectorInt::new(), Vector::new());
1310        igraph_call!(igraph_sparsemat_getelements(self, &mut i, &mut j, &mut x))?;
1311        Ok(SparseElements {
1312            i: i.into(),
1313            j: j.into(),
1314            x: x.into(),
1315        })
1316    }
1317
1318    /// Like [`getelements`](Self::getelements), with the elements sorted by
1319    /// column, then by row (`igraph_sparsemat_getelements_sorted`).
1320    ///
1321    /// Binds [`igraph_sparsemat_getelements_sorted`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_getelements_sorted).
1322    pub fn getelements_sorted(&self) -> Result<SparseElements> {
1323        let (mut i, mut j, mut x) = (VectorInt::new(), VectorInt::new(), Vector::new());
1324        igraph_call!(igraph_sparsemat_getelements_sorted(
1325            self, &mut i, &mut j, &mut x
1326        ))?;
1327        Ok(SparseElements {
1328            i: i.into(),
1329            j: j.into(),
1330            x: x.into(),
1331        })
1332    }
1333
1334    /// A copy whose entries are sorted by column, then by row
1335    /// (`igraph_sparsemat_sort`), in the same format as `self`.
1336    ///
1337    /// Binds [`igraph_sparsemat_sort`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_sort).
1338    pub fn sort(&self) -> Result<SparseMat> {
1339        Self::init_with(|res| unsafe { igraph_sparsemat_sort(self, res) })
1340    }
1341
1342    /// The dense product `self * b` of this sparse matrix with a dense matrix
1343    /// (`igraph_sparsemat_multiply_by_dense`).
1344    ///
1345    /// # Errors
1346    /// If `b.nrow() != self.ncol()`.
1347    pub fn multiply_by_dense(&self, b: &Matrix) -> Result<Matrix> {
1348        if b.nrow() != self.ncol() {
1349            return Err(Error::invalid(
1350                "invalid dimensions in sparse-dense matrix product",
1351            ));
1352        }
1353        let a = self.cc()?;
1354        let mut res = Matrix::new();
1355        igraph_call!(igraph_sparsemat_multiply_by_dense(&*a, b, &mut res))?;
1356        Ok(res)
1357    }
1358
1359    /// Divides each column by its sum (`igraph_sparsemat_normalize_cols`),
1360    /// making the matrix column-stochastic. The matrix must be square
1361    /// (igraph sizes the sums by the number of rows).
1362    ///
1363    /// # Errors
1364    /// If a column sums to zero and `allow_zeros` is false, or the matrix is
1365    /// not square.
1366    pub fn normalize_cols(&mut self, allow_zeros: bool) -> Result<()> {
1367        if self.nrow() != self.ncol() {
1368            return Err(Error::invalid("normalize_cols needs a square matrix"));
1369        }
1370        igraph_call!(igraph_sparsemat_normalize_cols(self, allow_zeros))
1371    }
1372
1373    /// Divides each row by its sum (`igraph_sparsemat_normalize_rows`),
1374    /// making the matrix row-stochastic.
1375    ///
1376    /// # Errors
1377    /// If a row sums to zero and `allow_zeros` is false.
1378    pub fn normalize_rows(&mut self, allow_zeros: bool) -> Result<()> {
1379        igraph_call!(igraph_sparsemat_normalize_rows(self, allow_zeros))
1380    }
1381
1382    /// Iterates over the stored entries as `(row, col, value)`, in storage
1383    /// order (`igraph_sparsemat_iterator_*`); duplicates are not merged.
1384    ///
1385    /// Column-compressed matrices are walked with igraph's iterator. For
1386    /// triplet matrices, `igraph_sparsemat_iterator_next` of igraph 1.0.0 and 1.0.1 reads
1387    /// the column array as if it had `ncol + 1` entries (it has `nzmax`), an
1388    /// out-of-bounds read as soon as `nzmax <= ncol`; the iterator then walks
1389    /// a copy of the entries made with
1390    /// [`getelements`](Self::getelements) instead.
1391    ///
1392    /// # Panics
1393    /// If copying the entries of a triplet matrix runs out of memory.
1394    ///
1395    /// ```
1396    /// use igraph::linalg::SparseMat;
1397    /// let m = SparseMat::diag(&[1.0, 2.0, 3.0], true).unwrap();
1398    /// let entries: Vec<_> = m.iter().collect();
1399    /// assert_eq!(entries, vec![(0, 0, 1.0), (1, 1, 2.0), (2, 2, 3.0)]);
1400    /// ```
1401    pub fn iter(&self) -> SparseMatIter<'_> {
1402        SparseMatIter::new(self)
1403    }
1404
1405    /// The stored entries as `(row, col, value)` triplets, in storage order.
1406    pub fn triplets(&self) -> Vec<(usize, usize, f64)> {
1407        self.iter().collect()
1408    }
1409}
1410
1411/// The dense product `a * b` of a dense matrix with a sparse one
1412/// (`igraph_sparsemat_dense_multiply`).
1413///
1414/// # Errors
1415/// If `a.ncol() != b.nrow()`.
1416///
1417/// ```
1418/// use igraph::{linalg::{SparseMat, dense_multiply}, prelude::*};
1419/// let a = Matrix::from_rows(&[[1.0, 2.0]]).unwrap();
1420/// let b = SparseMat::eye(2, 3.0, true).unwrap();
1421/// assert_eq!(dense_multiply(&a, &b).unwrap().to_rows(), vec![vec![3.0, 6.0]]);
1422/// ```
1423pub fn dense_multiply(a: &Matrix, b: &SparseMat) -> Result<Matrix> {
1424    if a.ncol() != b.nrow() {
1425        return Err(Error::invalid(
1426            "invalid dimensions in dense-sparse matrix product",
1427        ));
1428    }
1429    let bc = b.cc()?;
1430    let mut res = Matrix::new();
1431    igraph_call!(igraph_sparsemat_dense_multiply(a, &*bc, &mut res))?;
1432    Ok(res)
1433}
1434
1435/// Iterator over the stored entries of a [`SparseMat`], see [`SparseMat::iter`].
1436///
1437/// Wraps `igraph_sparsemat_iterator_t` for column-compressed matrices; it
1438/// yields `(row, col, value)`.
1439pub struct SparseMatIter<'a> {
1440    inner: IterInner,
1441    _borrow: PhantomData<&'a SparseMat>,
1442}
1443
1444enum IterInner {
1445    /// igraph's iterator over a column-compressed matrix.
1446    Compressed(igraph_sparsemat_iterator_t),
1447    /// A copy of the entries of a triplet matrix, and the next position.
1448    Triplet(SparseElements, usize),
1449}
1450
1451impl<'a> SparseMatIter<'a> {
1452    fn new(m: &'a SparseMat) -> Self {
1453        let inner = if m.is_cc() {
1454            let mut raw = MaybeUninit::<igraph_sparsemat_iterator_t>::uninit();
1455            // Always succeeds.
1456            unsafe { igraph_sparsemat_iterator_init(raw.as_mut_ptr(), m) };
1457            IterInner::Compressed(unsafe { raw.assume_init() })
1458        } else {
1459            let elements = m
1460                .getelements()
1461                .expect("out of memory while copying the entries of a sparse matrix");
1462            IterInner::Triplet(elements, 0)
1463        };
1464        Self {
1465            inner,
1466            _borrow: PhantomData,
1467        }
1468    }
1469
1470    /// Restarts the iteration from the first entry (`igraph_sparsemat_iterator_reset`).
1471    pub fn reset(&mut self) {
1472        match &mut self.inner {
1473            IterInner::Compressed(raw) => unsafe {
1474                igraph_sparsemat_iterator_reset(raw);
1475            },
1476            IterInner::Triplet(_, pos) => *pos = 0,
1477        }
1478    }
1479
1480    /// Position of the next entry in the element arrays
1481    /// (`igraph_sparsemat_iterator_idx`).
1482    pub fn index(&self) -> usize {
1483        match &self.inner {
1484            IterInner::Compressed(raw) => unsafe { igraph_sparsemat_iterator_idx(raw) as usize },
1485            IterInner::Triplet(_, pos) => *pos,
1486        }
1487    }
1488}
1489
1490impl Iterator for SparseMatIter<'_> {
1491    type Item = (usize, usize, f64);
1492
1493    fn next(&mut self) -> Option<Self::Item> {
1494        match &mut self.inner {
1495            IterInner::Compressed(raw) => unsafe {
1496                if igraph_sparsemat_iterator_end(raw) {
1497                    return None;
1498                }
1499                let item = (
1500                    igraph_sparsemat_iterator_row(raw) as usize,
1501                    igraph_sparsemat_iterator_col(raw) as usize,
1502                    igraph_sparsemat_iterator_get(raw),
1503                );
1504                igraph_sparsemat_iterator_next(raw);
1505                Some(item)
1506            },
1507            IterInner::Triplet(e, pos) => {
1508                let k = *pos;
1509                if k >= e.x.len() {
1510                    return None;
1511                }
1512                *pos += 1;
1513                Some((e.i[k] as usize, e.j[k] as usize, e.x[k]))
1514            }
1515        }
1516    }
1517}
1518
1519impl fmt::Debug for SparseMatIter<'_> {
1520    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1521        f.debug_struct("SparseMatIter")
1522            .field("index", &self.index())
1523            .finish()
1524    }
1525}
1526
1527/// Owned result of a symbolic analysis (`igraph_sparsemat_symbolic_t`).
1528struct SymbolicGuard(igraph_sparsemat_symbolic_t);
1529
1530impl SymbolicGuard {
1531    fn new(f: impl FnOnce(*mut igraph_sparsemat_symbolic_t) -> igraph_error_t) -> Result<Self> {
1532        ensure_init();
1533        let mut raw = igraph_sparsemat_symbolic_t {
1534            symbolic: ptr::null_mut(),
1535        };
1536        check(f(&mut raw))?;
1537        Ok(Self(raw))
1538    }
1539}
1540
1541impl Drop for SymbolicGuard {
1542    fn drop(&mut self) {
1543        if !self.0.symbolic.is_null() {
1544            unsafe { igraph_sparsemat_symbolic_destroy(&mut self.0) };
1545        }
1546    }
1547}
1548
1549/// Owned result of a numeric factorization (`igraph_sparsemat_numeric_t`).
1550struct NumericGuard(igraph_sparsemat_numeric_t);
1551
1552impl NumericGuard {
1553    fn new(f: impl FnOnce(*mut igraph_sparsemat_numeric_t) -> igraph_error_t) -> Result<Self> {
1554        ensure_init();
1555        let mut raw = igraph_sparsemat_numeric_t {
1556            numeric: ptr::null_mut(),
1557        };
1558        check(f(&mut raw))?;
1559        Ok(Self(raw))
1560    }
1561}
1562
1563impl Drop for NumericGuard {
1564    fn drop(&mut self) {
1565        if !self.0.numeric.is_null() {
1566            unsafe { igraph_sparsemat_numeric_destroy(&mut self.0) };
1567        }
1568    }
1569}
1570
1571/// A sparse LU factorization, see [`SparseMat::lu`]. It owns igraph's
1572/// symbolic and numeric decompositions and frees them on drop
1573/// (`igraph_sparsemat_symbolic_destroy`, `igraph_sparsemat_numeric_destroy`).
1574pub struct SparseLu {
1575    symbolic: SymbolicGuard,
1576    numeric: NumericGuard,
1577    n: usize,
1578}
1579
1580/// A sparse QR factorization of a square matrix, see [`SparseMat::qr`].
1581pub struct SparseQr {
1582    symbolic: SymbolicGuard,
1583    numeric: NumericGuard,
1584    n: usize,
1585}
1586
1587// Both own their CXSparse structures exclusively and only read them in `solve`.
1588unsafe impl Send for SparseLu {}
1589unsafe impl Sync for SparseLu {}
1590unsafe impl Send for SparseQr {}
1591unsafe impl Sync for SparseQr {}
1592
1593impl SparseLu {
1594    /// Order of the factorized matrix.
1595    pub fn dim(&self) -> usize {
1596        self.n
1597    }
1598
1599    /// Solves `A x = b` with the precomputed factorization
1600    /// (`igraph_sparsemat_luresol`).
1601    ///
1602    /// Binds [`igraph_sparsemat_luresol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_luresol).
1603    pub fn solve(&self, b: &[f64]) -> Result<Vec<f64>> {
1604        check_len(b.len(), self.n, "b")?;
1605        let bv = Vector::view(b);
1606        let mut res = Vector::new();
1607        igraph_call!(igraph_sparsemat_luresol(
1608            &self.symbolic.0,
1609            &self.numeric.0,
1610            bv.as_ptr(),
1611            &mut res
1612        ))?;
1613        Ok(res.into())
1614    }
1615}
1616
1617impl SparseQr {
1618    /// Order of the factorized matrix.
1619    pub fn dim(&self) -> usize {
1620        self.n
1621    }
1622
1623    /// Solves `A x = b` with the precomputed factorization
1624    /// (`igraph_sparsemat_qrresol`).
1625    ///
1626    /// Binds [`igraph_sparsemat_qrresol`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_sparsemat_qrresol).
1627    pub fn solve(&self, b: &[f64]) -> Result<Vec<f64>> {
1628        check_len(b.len(), self.n, "b")?;
1629        let bv = Vector::view(b);
1630        let mut res = Vector::new();
1631        igraph_call!(igraph_sparsemat_qrresol(
1632            &self.symbolic.0,
1633            &self.numeric.0,
1634            bv.as_ptr(),
1635            &mut res
1636        ))?;
1637        Ok(res.into())
1638    }
1639}
1640
1641impl fmt::Debug for SparseLu {
1642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1643        f.debug_struct("SparseLu")
1644            .field("n", &self.n)
1645            .finish_non_exhaustive()
1646    }
1647}
1648
1649impl fmt::Debug for SparseQr {
1650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1651        f.debug_struct("SparseQr")
1652            .field("n", &self.n)
1653            .finish_non_exhaustive()
1654    }
1655}
1656
1657impl Add for &SparseMat {
1658    type Output = Result<SparseMat>;
1659    /// `a + b`, see [`SparseMat::add`].
1660    fn add(self, rhs: &SparseMat) -> Result<SparseMat> {
1661        SparseMat::add(self, rhs, 1.0, 1.0)
1662    }
1663}
1664
1665impl Sub for &SparseMat {
1666    type Output = Result<SparseMat>;
1667    /// `a - b`, see [`SparseMat::add`].
1668    fn sub(self, rhs: &SparseMat) -> Result<SparseMat> {
1669        SparseMat::add(self, rhs, 1.0, -1.0)
1670    }
1671}
1672
1673impl Mul for &SparseMat {
1674    type Output = Result<SparseMat>;
1675    /// `a * b`, see [`SparseMat::multiply`].
1676    fn mul(self, rhs: &SparseMat) -> Result<SparseMat> {
1677        self.multiply(rhs)
1678    }
1679}
1680
1681impl TryFrom<&Matrix> for SparseMat {
1682    type Error = Error;
1683    /// Keeps the non-zero elements, see [`SparseMat::from_dense`].
1684    fn try_from(m: &Matrix) -> Result<Self> {
1685        SparseMat::from_dense(m, 0.0)
1686    }
1687}
1688
1689impl TryFrom<&SparseMat> for Matrix {
1690    type Error = Error;
1691    /// See [`SparseMat::to_dense`].
1692    fn try_from(m: &SparseMat) -> Result<Self> {
1693        m.to_dense()
1694    }
1695}