rlst/doc/sparse_matrices.rs
1//! Sparse matrix operations
2//!
3//! RLST contains a complete sparse matrix algebra. Sparse matrices
4//! can defined either on a single process or via MPI distributed across
5//! multiple processes. Typical matrix operations such as addition, componentwise
6//! operations and matrix vector products are supported for sparse matrices.
7//!
8//! # The default sparse matrix format
9//!
10//! RLST uses the CSR format for sparse matrices with `nnz` nonzero entries. A sparse matrix in this format is defined
11//! by three arrays.
12//!
13//! - `indices`: This array contains the column indices of all nonzero elements. It is of length `nnz`.
14//! - `indptr`: The entries belonging to row `i` occupy the half-open range
15//! `indptr[i]..indptr[i + 1]` in `indices` and `data`. It has `1 + m`
16//! elements for an `m`-row matrix, starts at zero, is nondecreasing, and ends at `nnz`.
17//! - `data`: The data entries of the sparse matrix.
18//!
19//! CSR constructors produce a canonical representation: column indices are sorted within each
20//! row, duplicate coordinates are summed, and explicit zeros are removed. Both raw
21//! [`CsrMatrix::new`](crate::sparse::csr_mat::CsrMatrix::new) construction and AIJ construction
22//! enforce this construction-time invariant. [`CsrMatrix::new`](crate::sparse::csr_mat::CsrMatrix::new)
23//! panics if the pointer length, pointer endpoints, pointer ordering, data/index lengths, or column
24//! bounds are invalid. AIJ construction requires equally long slices and in-bounds row and column
25//! indices. Mutable entry access can subsequently introduce a stored zero; `map_stored` applies its
26//! function to every currently stored entry and filters zero results when the mapped matrix is assembled.
27//!
28//! ```
29//! use rlst::{FromAij, Nonzeros};
30//! use rlst::sparse::csr_mat::CsrMatrix;
31//!
32//! // Duplicates are summed, rows are sorted, and explicit zeros are discarded.
33//! let matrix = CsrMatrix::from_aij(
34//! [2, 3],
35//! &[1, 0, 0, 0],
36//! &[2, 1, 1, 2],
37//! &[4.0, 2.0, 3.0, 0.0],
38//! );
39//! assert_eq!(matrix.nnz(), 2);
40//! assert_eq!(matrix.indptr().data().unwrap(), &[0, 1, 2]);
41//! assert_eq!(matrix.indices().data().unwrap(), &[1, 2]);
42//! assert_eq!(matrix.data().data().unwrap(), &[5.0, 4.0]);
43//! ```
44//!
45//! On a single node the [CsrMatrix](crate::sparse::csr_mat::CsrMatrix) defines this structure. To initialize a new sparse
46//! matrix the easiest is to use the [CsrMatrix::from_aij](crate::traits::FromAij::from_aij) method. It creates a sparse
47//! matrix from a `rows`, `cols`, and `data` array. Repeated entries for the same row/col index are summed up in the sparse matrix assembly.
48//! Zero entries are filtered out. The following example creates a sparse matrix `sparse_mat` and multiplies it with a vector `x`.
49//!
50//! ```
51//! # use rlst;
52//! # use rlst::sparse::csr_mat::CsrMatrix;
53//! # extern crate blas_src;
54//! use rlst::FromAij;
55//! let rows: Vec<usize> = vec![1, 4, 4];
56//! let cols: Vec<usize> = vec![2, 5, 6];
57//! let data: Vec<f64> = vec![1.0, 2.0, 3.0];
58//!
59//! let shape = [8, 13];
60//! let sparse_mat = CsrMatrix::from_aij(shape, &rows, &cols, &data);
61//!
62//! let mut x = rlst::DynArray::<f64, 1>::from_shape([shape[1]]);
63//! x.fill_from_seed_equally_distributed(0);
64//!
65//! let y = rlst::dot!(sparse_mat, x);
66//! # let expected = rlst::dot_blas!(sparse_mat.todense(), x);
67//!
68//! # rlst::assert_array_relative_eq!(y, expected, 1E-10);
69//! ```
70//!
71//! # Matrix-vector products
72//!
73//! The `CsrMatrix` type in RLST supports the [`AsMatrixApply`](crate::AsMatrixApply) trait for
74//! multiplication with one- and two-dimensional dense arrays. The [`dot!`](crate::dot) macro
75//! allocates the result of a sparse matrix-vector or matrix-matrix product. Its inner dimension
76//! must equal the sparse matrix column count.
77//!
78//! ```
79//! # use rlst;
80//! # use rlst::sparse::csr_mat::CsrMatrix;
81//! # use rlst::FromAij;
82//! # extern crate blas_src;
83//! # let rows: Vec<usize> = vec![1, 4, 4];
84//! # let cols: Vec<usize> = vec![2, 5, 6];
85//! # let data: Vec<f64> = vec![1.0, 2.0, 3.0];
86//! # let shape = [8, 13];
87//! # let sparse_mat = CsrMatrix::from_aij(shape, &rows, &cols, &data);
88//! # let mut x = rlst::DynArray::<f64, 1>::from_shape([shape[1]]);
89//! # x.fill_from_seed_equally_distributed(0);
90//! let y = rlst::dot!(sparse_mat, x);
91//! # let expected = rlst::dot_blas!(sparse_mat.todense(), x);
92//! # rlst::assert_array_relative_eq!(y, expected, 1E-10);
93//! ```
94//!
95//! # Componentwise operations on sparse matrices
96//!
97//! Sparse matrices support addition, subtraction, scalar multiplication,
98//! componentwise matrix multiplication, and zero-preserving unary operations
99//! such as [Abs](crate::Abs), [Square](crate::Square), [Sqrt](crate::Sqrt), and
100//! [Sin](crate::Sin). Binary matrix operations require equal shapes.
101//!
102//! Functions that do not preserve implicit zero (for example `exp`, `cos`, and
103//! `recip`) deliberately do not implement the ordinary numerical traits for
104//! sparse matrices. Use
105//! [`SparseMatOpIterator::map_stored`](crate::sparse::mat_operations::SparseMatOpIterator::map_stored)
106//! when applying such a function only to explicitly stored entries is intended.
107//! Sparse matrix/matrix division is also unsupported because division involving
108//! implicit zeros generally cannot be represented by a sparse result.
109//!
110//! To access componentwise operations each sparse matrix supports the [op](crate::sparse::csr_mat::CsrMatrix::op). This returns a
111//! [SparseMatOpIterator](crate::sparse::mat_operations::SparseMatOpIterator). Componentwise sparse matrix operations are defined
112//! through this iterator. Operations such as adding two sparse matrices or componentwise multiplication of two sparse matrices
113//! are executed by merging the corresponding iterators. The following code gives an example.
114//!
115//!
116//! ```
117//! # use rlst;
118//! # use rlst::sparse::csr_mat::CsrMatrix;
119//! use rlst::FromAij;
120//! use std::ops::Mul;
121//! let rows1: Vec<usize> = vec![1, 4, 4];
122//! let cols1: Vec<usize> = vec![2, 5, 6];
123//! let data1: Vec<f64> = vec![1.0, 2.0, 3.0];
124//!
125//! let rows2: Vec<usize> = vec![2, ];
126//! let cols2: Vec<usize> = vec![2, ];
127//! let data2: Vec<f64> = vec![1.0];
128//!
129//!
130//! let shape = [8, 13];
131//! let a = CsrMatrix::from_aij(shape, &rows1, &cols1, &data1);
132//! let b = CsrMatrix::from_aij(shape, &rows2, &cols2, &data2);
133//!
134//! let c = (5.0 * a.op().map_stored(f64::cosh) + b.op()).into_csr();
135//!
136//! ```
137//! In the above example we apply `cosh` to each stored element in `a`, multiply the result by `5.0` and add `b`.
138//! The result is then turned back into a sparse matrix. The implementation uses lazy evalution. The operations are only executed
139//! when `into_csr` is called.
140//!
141//! # Direct sparse solves with UMFPACK
142//!
143//! The companion `rlst-suitesparse` crate, added in RLST 0.7, factorizes real `f64`
144//! and complex `c64` CSR matrices with SuiteSparse UMFPACK. Call `factorize()` once and reuse the
145//! returned numeric factorization for multiple contiguous vector right-hand sides. Matrices must
146//! be nonempty and square; singularity is exposed through the factorization status.
147//!
148//! This integration requires a native SuiteSparse installation. UMFPACK is GPL-2.0-or-later, so
149//! linking it can affect distribution licensing even though the Rust crates are dual-licensed.
150//! See the companion crate's [real and complex solve tutorial](https://codeberg.org/linalg-rs/rlst/src/branch/main/rlst-suitesparse/README.md#real-and-complex-solves)
151//! for setup, factorization reuse, status handling, and `SUITESPARSE_PATH` discovery.
152//!
153//! # Matrix Market files
154//!
155//! [`write_coordinate_mm`](crate::io::matrix_market::write_coordinate_mm) writes stored entries
156//! using Matrix Market's one-based coordinate indices. [`read_coordinate_mm`](crate::io::matrix_market::read_coordinate_mm)
157//! accepts general real or complex coordinate files and assembles duplicates with the same CSR
158//! canonicalization described above. Dense [`write_array_mm`](crate::io::matrix_market::write_array_mm)
159//! and [`read_array_mm`](crate::io::matrix_market::read_array_mm) use Matrix Market column-major
160//! record order, regardless of the source array's internal layout. Readers reject unsupported
161//! integer and symmetric variants, malformed records, count mismatches, and out-of-range indices
162//! with an [`RlstError`](crate::RlstError).
163//!
164//! # Distributed multiplication
165//!
166//! With the `mpi` feature, `dot!(a, x)` also accepts a distributed CSR matrix and
167//! a distributed vector or matrix. The input must share `a.domain_layout()`;
168//! the result shares `a.range_layout()` and owns its local dense data. Matrix
169//! inputs retain their number of RHS columns. Borrowed views and lazy distributed
170//! expressions are supported. The equivalent method call is `a.dot(&x)`.
171//!
172//! Every rank in the matrix communicator must call the operation, even with no
173//! local rows or a globally empty input. Input layouts, local row counts, and
174//! agreement on RHS dimensions are checked collectively before ghost exchange.
175//!
176//! ```no_run
177//! # #[cfg(feature = "mpi")]
178//! # {
179//! use std::rc::Rc;
180//! use rlst::{FromAijDistributed, distributed_tools::IndexLayout};
181//! use rlst::sparse::distributed_csr_mat::DistributedCsrMatrix;
182//! let universe = mpi::initialize().unwrap();
183//! let world = universe.world();
184//! let layout = Rc::new(IndexLayout::from_local_counts(1, &world));
185//! let row = layout.local_range().0;
186//! let a = DistributedCsrMatrix::from_aij(
187//! layout.clone(), layout.clone(), &[row], &[row], &[2.0_f64],
188//! );
189//! let mut x = rlst::dist_vec!(f64, layout.clone());
190//! x.local.fill_with_value(3.0);
191//! let y = rlst::dot!(a, x.r());
192//! assert_eq!(y.local[[0]], 6.0);
193//! # }
194//! ```
195//!
196//! # Matrix Market round trip
197//!
198//! A coordinate-file round trip is:
199//!
200//! ```no_run
201//! use rlst::{FromAij, Nonzeros};
202//! use rlst::io::matrix_market::{read_coordinate_mm, write_coordinate_mm};
203//! use rlst::sparse::csr_mat::CsrMatrix;
204//!
205//! let matrix = CsrMatrix::from_aij([2, 2], &[0, 1], &[1, 0], &[2.0_f64, 3.0]);
206//! let path = std::env::temp_dir().join("rlst-matrix-market-example.mtx");
207//! write_coordinate_mm(&matrix, path.to_str().unwrap())?;
208//! let restored = read_coordinate_mm::<f64>(path.to_str().unwrap())?;
209//! assert_eq!(restored.nnz(), 2);
210//! std::fs::remove_file(path)?;
211//! # Ok::<(), Box<dyn std::error::Error>>(())
212//! ```