2020-09-23 15:34:19 +08:00
|
|
|
//! An implementation of the COO sparse matrix format.
|
|
|
|
|
2020-07-14 00:44:40 +08:00
|
|
|
use crate::SparseFormatError;
|
|
|
|
|
|
|
|
/// A COO representation of a sparse matrix.
|
|
|
|
///
|
|
|
|
/// A COO matrix stores entries in coordinate-form, that is triplets `(i, j, v)`, where `i` and `j`
|
|
|
|
/// correspond to row and column indices of the entry, and `v` to the value of the entry.
|
2021-01-25 21:17:48 +08:00
|
|
|
/// The format is of limited use for standard matrix operations. Its main purpose is to facilitate
|
2020-07-14 00:44:40 +08:00
|
|
|
/// easy construction of other, more efficient matrix formats (such as CSR/COO), and the
|
|
|
|
/// conversion between different formats.
|
|
|
|
///
|
2021-01-25 21:17:48 +08:00
|
|
|
/// # Format
|
2020-07-14 00:44:40 +08:00
|
|
|
///
|
|
|
|
/// For given dimensions `nrows` and `ncols`, the matrix is represented by three same-length
|
|
|
|
/// arrays `row_indices`, `col_indices` and `values` that constitute the coordinate triplets
|
|
|
|
/// of the matrix. The indices must be in bounds, but *duplicate entries are explicitly allowed*.
|
|
|
|
/// Upon conversion to other formats, the duplicate entries may be summed together. See the
|
|
|
|
/// documentation for the respective conversion functions.
|
|
|
|
///
|
2021-01-25 21:17:48 +08:00
|
|
|
/// # Examples
|
2020-07-14 00:44:40 +08:00
|
|
|
///
|
2021-07-07 10:05:25 +08:00
|
|
|
/// ```
|
2021-01-25 21:17:48 +08:00
|
|
|
/// use nalgebra_sparse::{coo::CooMatrix, csr::CsrMatrix, csc::CscMatrix};
|
|
|
|
///
|
|
|
|
/// // Initialize a matrix with all zeros (no explicitly stored entries).
|
2020-07-14 00:44:40 +08:00
|
|
|
/// let mut coo = CooMatrix::new(4, 4);
|
|
|
|
/// // Or initialize it with a set of triplets
|
|
|
|
/// coo = CooMatrix::try_from_triplets(4, 4, vec![1, 2], vec![0, 1], vec![3.0, 4.0]).unwrap();
|
|
|
|
///
|
2021-01-25 21:17:48 +08:00
|
|
|
/// // Push a few triplets
|
2020-07-14 00:44:40 +08:00
|
|
|
/// coo.push(2, 0, 1.0);
|
2021-01-25 21:17:48 +08:00
|
|
|
/// coo.push(0, 1, 2.0);
|
2020-07-14 00:44:40 +08:00
|
|
|
///
|
2021-01-25 21:17:48 +08:00
|
|
|
/// // Convert to other matrix formats
|
|
|
|
/// let csr = CsrMatrix::from(&coo);
|
|
|
|
/// let csc = CscMatrix::from(&coo);
|
2020-07-14 00:44:40 +08:00
|
|
|
/// ```
|
2020-11-18 20:54:14 +08:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub struct CooMatrix<T> {
|
|
|
|
nrows: usize,
|
|
|
|
ncols: usize,
|
|
|
|
row_indices: Vec<usize>,
|
|
|
|
col_indices: Vec<usize>,
|
|
|
|
values: Vec<T>,
|
|
|
|
}
|
|
|
|
|
2021-06-07 22:13:43 +08:00
|
|
|
impl<T: na::Scalar> CooMatrix<T> {
|
|
|
|
/// Pushes a dense matrix into the sparse one.
|
|
|
|
///
|
|
|
|
/// This adds the dense matrix `m` starting at the `r`th row and `c`th column
|
|
|
|
/// to the matrix.
|
|
|
|
///
|
|
|
|
/// Panics
|
|
|
|
/// ------
|
|
|
|
///
|
|
|
|
/// Panics if any part of the dense matrix is out of bounds of the sparse matrix
|
|
|
|
/// when inserted at `(r, c)`.
|
|
|
|
#[inline]
|
2021-08-03 00:41:46 +08:00
|
|
|
pub fn push_matrix<R: na::Dim, C: na::Dim, S: nalgebra::storage::RawStorage<T, R, C>>(
|
2021-06-07 22:13:43 +08:00
|
|
|
&mut self,
|
|
|
|
r: usize,
|
|
|
|
c: usize,
|
|
|
|
m: &na::Matrix<T, R, C, S>,
|
|
|
|
) {
|
|
|
|
let block_nrows = m.nrows();
|
|
|
|
let block_ncols = m.ncols();
|
|
|
|
let max_row_with_block = r + block_nrows - 1;
|
|
|
|
let max_col_with_block = c + block_ncols - 1;
|
|
|
|
assert!(max_row_with_block < self.nrows);
|
|
|
|
assert!(max_col_with_block < self.ncols);
|
|
|
|
|
|
|
|
self.reserve(block_ncols * block_nrows);
|
|
|
|
|
|
|
|
for (col_idx, col) in m.column_iter().enumerate() {
|
|
|
|
for (row_idx, v) in col.iter().enumerate() {
|
|
|
|
self.row_indices.push(r + row_idx);
|
|
|
|
self.col_indices.push(c + col_idx);
|
|
|
|
self.values.push(v.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-26 00:26:27 +08:00
|
|
|
impl<T> CooMatrix<T> {
|
2020-07-14 00:44:40 +08:00
|
|
|
/// Construct a zero COO matrix of the given dimensions.
|
|
|
|
///
|
|
|
|
/// Specifically, the collection of triplets - corresponding to explicitly stored entries -
|
|
|
|
/// is empty, so that the matrix (implicitly) represented by the COO matrix consists of all
|
|
|
|
/// zero entries.
|
|
|
|
pub fn new(nrows: usize, ncols: usize) -> Self {
|
|
|
|
Self {
|
|
|
|
nrows,
|
|
|
|
ncols,
|
|
|
|
row_indices: Vec::new(),
|
|
|
|
col_indices: Vec::new(),
|
|
|
|
values: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-25 18:11:29 +08:00
|
|
|
/// Construct a zero COO matrix of the given dimensions.
|
|
|
|
///
|
|
|
|
/// Specifically, the collection of triplets - corresponding to explicitly stored entries -
|
|
|
|
/// is empty, so that the matrix (implicitly) represented by the COO matrix consists of all
|
|
|
|
/// zero entries.
|
|
|
|
pub fn zeros(nrows: usize, ncols: usize) -> Self {
|
|
|
|
Self::new(nrows, ncols)
|
|
|
|
}
|
|
|
|
|
2020-07-14 00:44:40 +08:00
|
|
|
/// Try to construct a COO matrix from the given dimensions and a collection of
|
|
|
|
/// (i, j, v) triplets.
|
|
|
|
///
|
|
|
|
/// Returns an error if either row or column indices contain indices out of bounds,
|
|
|
|
/// or if the data arrays do not all have the same length. Note that the COO format
|
|
|
|
/// inherently supports duplicate entries.
|
|
|
|
pub fn try_from_triplets(
|
|
|
|
nrows: usize,
|
|
|
|
ncols: usize,
|
|
|
|
row_indices: Vec<usize>,
|
|
|
|
col_indices: Vec<usize>,
|
|
|
|
values: Vec<T>,
|
|
|
|
) -> Result<Self, SparseFormatError> {
|
2020-09-22 23:50:47 +08:00
|
|
|
use crate::SparseFormatErrorKind::*;
|
2020-07-14 00:44:40 +08:00
|
|
|
if row_indices.len() != col_indices.len() {
|
2020-09-22 23:50:47 +08:00
|
|
|
return Err(SparseFormatError::from_kind_and_msg(
|
2021-01-26 00:26:27 +08:00
|
|
|
InvalidStructure,
|
2021-12-28 19:12:31 +08:00
|
|
|
"Number of row and col indices must be the same.",
|
2020-07-14 00:44:40 +08:00
|
|
|
));
|
|
|
|
} else if col_indices.len() != values.len() {
|
2020-09-22 23:50:47 +08:00
|
|
|
return Err(SparseFormatError::from_kind_and_msg(
|
2021-01-26 00:26:27 +08:00
|
|
|
InvalidStructure,
|
2021-12-28 19:12:31 +08:00
|
|
|
"Number of col indices and values must be the same.",
|
2020-07-14 00:44:40 +08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let row_indices_in_bounds = row_indices.iter().all(|i| *i < nrows);
|
|
|
|
let col_indices_in_bounds = col_indices.iter().all(|j| *j < ncols);
|
|
|
|
|
|
|
|
if !row_indices_in_bounds {
|
2021-01-26 00:26:27 +08:00
|
|
|
Err(SparseFormatError::from_kind_and_msg(
|
|
|
|
IndexOutOfBounds,
|
2021-12-28 19:12:31 +08:00
|
|
|
"Row index out of bounds.",
|
2021-01-26 00:26:27 +08:00
|
|
|
))
|
2020-07-14 00:44:40 +08:00
|
|
|
} else if !col_indices_in_bounds {
|
2021-01-26 00:26:27 +08:00
|
|
|
Err(SparseFormatError::from_kind_and_msg(
|
|
|
|
IndexOutOfBounds,
|
2021-12-28 19:12:31 +08:00
|
|
|
"Col index out of bounds.",
|
2021-01-26 00:26:27 +08:00
|
|
|
))
|
2020-07-14 00:44:40 +08:00
|
|
|
} else {
|
|
|
|
Ok(Self {
|
|
|
|
nrows,
|
|
|
|
ncols,
|
|
|
|
row_indices,
|
|
|
|
col_indices,
|
|
|
|
values,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An iterator over triplets (i, j, v).
|
|
|
|
// TODO: Consider giving the iterator a concrete type instead of impl trait...?
|
|
|
|
pub fn triplet_iter(&self) -> impl Iterator<Item = (usize, usize, &T)> {
|
|
|
|
self.row_indices
|
|
|
|
.iter()
|
|
|
|
.zip(&self.col_indices)
|
|
|
|
.zip(&self.values)
|
|
|
|
.map(|((i, j), v)| (*i, *j, v))
|
|
|
|
}
|
|
|
|
|
2021-05-02 19:43:16 +08:00
|
|
|
/// Reserves capacity for COO matrix by at least `additional` elements.
|
|
|
|
///
|
|
|
|
/// This increase the capacities of triplet holding arrays by reserving more space to avoid
|
2021-05-04 11:29:25 +08:00
|
|
|
/// frequent reallocations in `push` operations.
|
2021-05-02 19:43:16 +08:00
|
|
|
///
|
|
|
|
/// ## Panics
|
|
|
|
///
|
2021-05-04 11:29:25 +08:00
|
|
|
/// Panics if any of the individual allocation of triplet arrays fails.
|
2021-05-02 19:43:16 +08:00
|
|
|
///
|
|
|
|
/// ## Example
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use nalgebra_sparse::coo::CooMatrix;
|
|
|
|
/// let mut coo = CooMatrix::new(4, 4);
|
2021-05-04 11:29:25 +08:00
|
|
|
/// // Reserve capacity in advance
|
2021-05-02 19:43:16 +08:00
|
|
|
/// coo.reserve(10);
|
|
|
|
/// coo.push(1, 0, 3.0);
|
|
|
|
/// ```
|
|
|
|
pub fn reserve(&mut self, additional: usize) {
|
|
|
|
self.row_indices.reserve(additional);
|
|
|
|
self.col_indices.reserve(additional);
|
|
|
|
self.values.reserve(additional);
|
|
|
|
}
|
|
|
|
|
2020-07-14 00:44:40 +08:00
|
|
|
/// Push a single triplet to the matrix.
|
|
|
|
///
|
|
|
|
/// This adds the value `v` to the `i`th row and `j`th column in the matrix.
|
|
|
|
///
|
|
|
|
/// Panics
|
|
|
|
/// ------
|
|
|
|
///
|
|
|
|
/// Panics if `i` or `j` is out of bounds.
|
2020-07-17 23:59:19 +08:00
|
|
|
#[inline]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn push(&mut self, i: usize, j: usize, v: T) {
|
|
|
|
assert!(i < self.nrows);
|
|
|
|
assert!(j < self.ncols);
|
|
|
|
self.row_indices.push(i);
|
|
|
|
self.col_indices.push(j);
|
|
|
|
self.values.push(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The number of rows in the matrix.
|
2020-07-17 23:59:19 +08:00
|
|
|
#[inline]
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn nrows(&self) -> usize {
|
|
|
|
self.nrows
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The number of columns in the matrix.
|
2020-07-17 23:59:19 +08:00
|
|
|
#[inline]
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn ncols(&self) -> usize {
|
|
|
|
self.ncols
|
|
|
|
}
|
|
|
|
|
2020-11-23 22:58:02 +08:00
|
|
|
/// The number of explicitly stored entries in the matrix.
|
|
|
|
///
|
|
|
|
/// This number *includes* duplicate entries. For example, if the `CooMatrix` contains duplicate
|
|
|
|
/// entries, then it may have a different number of non-zeros as reported by `nnz()` compared
|
|
|
|
/// to its CSR representation.
|
|
|
|
#[inline]
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-11-23 22:58:02 +08:00
|
|
|
pub fn nnz(&self) -> usize {
|
|
|
|
self.values.len()
|
|
|
|
}
|
|
|
|
|
2020-07-14 00:44:40 +08:00
|
|
|
/// The row indices of the explicitly stored entries.
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn row_indices(&self) -> &[usize] {
|
|
|
|
&self.row_indices
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The column indices of the explicitly stored entries.
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn col_indices(&self) -> &[usize] {
|
|
|
|
&self.col_indices
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The values of the explicitly stored entries.
|
2021-06-07 22:34:03 +08:00
|
|
|
#[must_use]
|
2020-07-14 00:44:40 +08:00
|
|
|
pub fn values(&self) -> &[T] {
|
|
|
|
&self.values
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Disassembles the matrix into individual triplet arrays.
|
|
|
|
///
|
|
|
|
/// Examples
|
|
|
|
/// --------
|
|
|
|
///
|
|
|
|
/// ```
|
2020-09-23 15:34:19 +08:00
|
|
|
/// # use nalgebra_sparse::coo::CooMatrix;
|
2020-07-14 00:44:40 +08:00
|
|
|
/// let row_indices = vec![0, 1];
|
|
|
|
/// let col_indices = vec![1, 2];
|
|
|
|
/// let values = vec![1.0, 2.0];
|
|
|
|
/// let coo = CooMatrix::try_from_triplets(2, 3, row_indices, col_indices, values)
|
|
|
|
/// .unwrap();
|
|
|
|
///
|
|
|
|
/// let (row_idx, col_idx, val) = coo.disassemble();
|
|
|
|
/// assert_eq!(row_idx, vec![0, 1]);
|
|
|
|
/// assert_eq!(col_idx, vec![1, 2]);
|
|
|
|
/// assert_eq!(val, vec![1.0, 2.0]);
|
|
|
|
/// ```
|
|
|
|
pub fn disassemble(self) -> (Vec<usize>, Vec<usize>, Vec<T>) {
|
|
|
|
(self.row_indices, self.col_indices, self.values)
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 17:30:02 +08:00
|
|
|
|
|
|
|
#[cfg(feature = "serde-serialize")]
|
2021-12-15 18:48:51 +08:00
|
|
|
mod serde_serialize {
|
|
|
|
use super::CooMatrix;
|
|
|
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
2021-11-09 17:30:02 +08:00
|
|
|
|
2021-12-28 19:36:52 +08:00
|
|
|
/// This is an intermediate type for (de)serializing `CooMatrix`.
|
|
|
|
///
|
|
|
|
/// Deserialization requires using a `try_from_*` function for validation. We could have used
|
|
|
|
/// the `remote = "Self"` trick (https://github.com/serde-rs/serde/issues/1220) which allows
|
|
|
|
/// to directly serialize/deserialize the original fields and combine it with validation.
|
|
|
|
/// However, this would lead to nested serialization of the `CsMatrix` and `SparsityPattern`
|
|
|
|
/// types. Instead, we decided that we want a more human-readable serialization format using
|
|
|
|
/// field names like `row_indices` and `col_indices`. The easiest way to achieve this is to
|
|
|
|
/// introduce an intermediate type. It also allows the serialization format to stay constant
|
|
|
|
/// even if the internal layout in `nalgebra` changes.
|
|
|
|
///
|
|
|
|
/// We want to avoid unnecessary copies when serializing (i.e. cloning slices into owned
|
|
|
|
/// storage). Therefore, we use generic arguments to allow using slices during serialization and
|
|
|
|
/// owned storage (i.e. `Vec`) during deserialization. Without a major update of serde, slices
|
|
|
|
/// and `Vec`s should always (de)serialize identically.
|
2021-12-15 18:48:51 +08:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct CooMatrixSerializationData<Indices, Values> {
|
|
|
|
nrows: usize,
|
|
|
|
ncols: usize,
|
|
|
|
row_indices: Indices,
|
|
|
|
col_indices: Indices,
|
|
|
|
values: Values,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Serialize for CooMatrix<T>
|
2021-11-09 17:30:02 +08:00
|
|
|
where
|
2021-12-15 18:48:51 +08:00
|
|
|
T: Serialize + Clone,
|
2021-11-09 17:30:02 +08:00
|
|
|
{
|
2021-12-15 18:48:51 +08:00
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
CooMatrixSerializationData::<&[usize], &[T]> {
|
|
|
|
nrows: self.nrows(),
|
|
|
|
ncols: self.ncols(),
|
|
|
|
row_indices: self.row_indices(),
|
|
|
|
col_indices: self.col_indices(),
|
|
|
|
values: self.values(),
|
|
|
|
}
|
|
|
|
.serialize(serializer)
|
2021-11-09 17:30:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-15 18:48:51 +08:00
|
|
|
impl<'de, T> Deserialize<'de> for CooMatrix<T>
|
2021-11-09 17:30:02 +08:00
|
|
|
where
|
2021-12-15 18:48:51 +08:00
|
|
|
T: Deserialize<'de> + Clone,
|
2021-11-09 17:30:02 +08:00
|
|
|
{
|
2021-12-15 18:48:51 +08:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<CooMatrix<T>, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
let de = CooMatrixSerializationData::<Vec<usize>, Vec<T>>::deserialize(deserializer)?;
|
|
|
|
CooMatrix::try_from_triplets(
|
|
|
|
de.nrows,
|
|
|
|
de.ncols,
|
|
|
|
de.row_indices,
|
|
|
|
de.col_indices,
|
|
|
|
de.values,
|
|
|
|
)
|
|
|
|
.map_err(|e| de::Error::custom(e))
|
|
|
|
}
|
2021-11-09 17:30:02 +08:00
|
|
|
}
|
|
|
|
}
|