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}