Implement CSC matrix basic API
The CSC matrix API mirrors the CSR matrix API. However, there are subtle differences throughout (both in the available methods and the implementation) that I believe makes any attempt to avoid the duplicate effort futile.
This commit is contained in:
parent
082416e3ec
commit
ec339f9108
|
@ -0,0 +1,546 @@
|
||||||
|
//! An implementation of the CSC sparse matrix format.
|
||||||
|
|
||||||
|
use crate::{SparseFormatError, SparseFormatErrorKind};
|
||||||
|
use crate::pattern::{SparsityPattern, SparsityPatternFormatError, SparsityPatternIter};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::slice::{IterMut, Iter};
|
||||||
|
use std::ops::Range;
|
||||||
|
use num_traits::Zero;
|
||||||
|
use std::ptr::slice_from_raw_parts_mut;
|
||||||
|
|
||||||
|
/// A CSC representation of a sparse matrix.
|
||||||
|
///
|
||||||
|
/// The Compressed Sparse Column (CSC) format is well-suited as a general-purpose storage format
|
||||||
|
/// for many sparse matrix applications.
|
||||||
|
///
|
||||||
|
/// TODO: Storage explanation and examples
|
||||||
|
///
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CscMatrix<T> {
|
||||||
|
// Cols are major, rows are minor in the sparsity pattern
|
||||||
|
sparsity_pattern: Arc<SparsityPattern>,
|
||||||
|
values: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CscMatrix<T> {
|
||||||
|
/// Create a zero CSC matrix with no explicitly stored entries.
|
||||||
|
pub fn new(nrows: usize, ncols: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
sparsity_pattern: Arc::new(SparsityPattern::new(ncols, nrows)),
|
||||||
|
values: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of rows in the matrix.
|
||||||
|
#[inline]
|
||||||
|
pub fn nrows(&self) -> usize {
|
||||||
|
self.sparsity_pattern.minor_dim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of columns in the matrix.
|
||||||
|
#[inline]
|
||||||
|
pub fn ncols(&self) -> usize {
|
||||||
|
self.sparsity_pattern.major_dim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of non-zeros in the matrix.
|
||||||
|
///
|
||||||
|
/// Note that this corresponds to the number of explicitly stored entries, *not* the actual
|
||||||
|
/// number of algebraically zero entries in the matrix. Explicitly stored entries can still
|
||||||
|
/// be zero. Corresponds to the number of entries in the sparsity pattern.
|
||||||
|
#[inline]
|
||||||
|
pub fn nnz(&self) -> usize {
|
||||||
|
self.sparsity_pattern.nnz()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The column offsets defining part of the CSC format.
|
||||||
|
#[inline]
|
||||||
|
pub fn col_offsets(&self) -> &[usize] {
|
||||||
|
self.sparsity_pattern.major_offsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The row indices defining part of the CSC format.
|
||||||
|
#[inline]
|
||||||
|
pub fn row_indices(&self) -> &[usize] {
|
||||||
|
self.sparsity_pattern.minor_indices()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The non-zero values defining part of the CSC format.
|
||||||
|
#[inline]
|
||||||
|
pub fn values(&self) -> &[T] {
|
||||||
|
&self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable access to the non-zero values.
|
||||||
|
#[inline]
|
||||||
|
pub fn values_mut(&mut self) -> &mut [T] {
|
||||||
|
&mut self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to construct a CSC matrix from raw CSC data.
|
||||||
|
///
|
||||||
|
/// It is assumed that each column contains unique and sorted row indices that are in
|
||||||
|
/// bounds with respect to the number of rows in the matrix. If this is not the case,
|
||||||
|
/// an error is returned to indicate the failure.
|
||||||
|
///
|
||||||
|
/// An error is returned if the data given does not conform to the CSC storage format.
|
||||||
|
/// See the documentation for [CscMatrix](struct.CscMatrix.html) for more information.
|
||||||
|
pub fn try_from_csc_data(
|
||||||
|
num_rows: usize,
|
||||||
|
num_cols: usize,
|
||||||
|
col_offsets: Vec<usize>,
|
||||||
|
row_indices: Vec<usize>,
|
||||||
|
values: Vec<T>,
|
||||||
|
) -> Result<Self, SparseFormatError> {
|
||||||
|
let pattern = SparsityPattern::try_from_offsets_and_indices(
|
||||||
|
num_cols, num_rows, col_offsets, row_indices)
|
||||||
|
.map_err(pattern_format_error_to_csc_error)?;
|
||||||
|
Self::try_from_pattern_and_values(Arc::new(pattern), values)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to construct a CSC matrix from a sparsity pattern and associated non-zero values.
|
||||||
|
///
|
||||||
|
/// Returns an error if the number of values does not match the number of minor indices
|
||||||
|
/// in the pattern.
|
||||||
|
pub fn try_from_pattern_and_values(pattern: Arc<SparsityPattern>, values: Vec<T>)
|
||||||
|
-> Result<Self, SparseFormatError> {
|
||||||
|
if pattern.nnz() == values.len() {
|
||||||
|
Ok(Self {
|
||||||
|
sparsity_pattern: pattern,
|
||||||
|
values,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(SparseFormatError::from_kind_and_msg(
|
||||||
|
SparseFormatErrorKind::InvalidStructure,
|
||||||
|
"Number of values and row indices must be the same"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// An iterator over non-zero triplets (i, j, v).
|
||||||
|
///
|
||||||
|
/// The iteration happens in column-major fashion, meaning that j increases monotonically,
|
||||||
|
/// and i increases monotonically within each row.
|
||||||
|
///
|
||||||
|
/// Examples
|
||||||
|
/// --------
|
||||||
|
/// ```
|
||||||
|
/// # use nalgebra_sparse::csc::CscMatrix;
|
||||||
|
/// let col_offsets = vec![0, 2, 3, 4];
|
||||||
|
/// let row_indices = vec![0, 2, 1, 0];
|
||||||
|
/// let values = vec![1, 3, 2, 4];
|
||||||
|
/// let mut csc = CscMatrix::try_from_csc_data(4, 3, col_offsets, row_indices, values)
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// let triplets: Vec<_> = csc.triplet_iter().map(|(i, j, v)| (i, j, *v)).collect();
|
||||||
|
/// assert_eq!(triplets, vec![(0, 0, 1), (2, 0, 3), (1, 1, 2), (0, 2, 4)]);
|
||||||
|
/// ```
|
||||||
|
pub fn triplet_iter(&self) -> CscTripletIter<T> {
|
||||||
|
CscTripletIter {
|
||||||
|
pattern_iter: self.sparsity_pattern.entries(),
|
||||||
|
values_iter: self.values.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mutable iterator over non-zero triplets (i, j, v).
|
||||||
|
///
|
||||||
|
/// Iteration happens in the same order as for [triplet_iter](#method.triplet_iter).
|
||||||
|
///
|
||||||
|
/// Examples
|
||||||
|
/// --------
|
||||||
|
/// ```
|
||||||
|
/// # use nalgebra_sparse::csc::CscMatrix;
|
||||||
|
/// let col_offsets = vec![0, 2, 3, 4];
|
||||||
|
/// let row_indices = vec![0, 2, 1, 0];
|
||||||
|
/// let values = vec![1, 3, 2, 4];
|
||||||
|
/// // Using the same data as in the `triplet_iter` example
|
||||||
|
/// let mut csc = CscMatrix::try_from_csc_data(4, 3, col_offsets, row_indices, values)
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// // Zero out lower-triangular terms
|
||||||
|
/// csc.triplet_iter_mut()
|
||||||
|
/// .filter(|(i, j, _)| j < i)
|
||||||
|
/// .for_each(|(_, _, v)| *v = 0);
|
||||||
|
///
|
||||||
|
/// let triplets: Vec<_> = csc.triplet_iter().map(|(i, j, v)| (i, j, *v)).collect();
|
||||||
|
/// assert_eq!(triplets, vec![(0, 0, 1), (2, 0, 0), (1, 1, 2), (0, 2, 4)]);
|
||||||
|
/// ```
|
||||||
|
pub fn triplet_iter_mut(&mut self) -> CscTripletIterMut<T> {
|
||||||
|
CscTripletIterMut {
|
||||||
|
pattern_iter: self.sparsity_pattern.entries(),
|
||||||
|
values_mut_iter: self.values.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the column at the given column index.
|
||||||
|
///
|
||||||
|
/// Panics
|
||||||
|
/// ------
|
||||||
|
/// Panics if column index is out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn col(&self, index: usize) -> CscCol<T> {
|
||||||
|
self.get_col(index)
|
||||||
|
.expect("Row index must be in bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable column access for the given column index.
|
||||||
|
///
|
||||||
|
/// Panics
|
||||||
|
/// ------
|
||||||
|
/// Panics if column index is out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn col_mut(&mut self, index: usize) -> CscColMut<T> {
|
||||||
|
self.get_col_mut(index)
|
||||||
|
.expect("Row index must be in bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the column at the given column index, or `None` if out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_col(&self, index: usize) -> Option<CscCol<T>> {
|
||||||
|
let range = self.get_index_range(index)?;
|
||||||
|
Some(CscCol {
|
||||||
|
row_indices: &self.sparsity_pattern.minor_indices()[range.clone()],
|
||||||
|
values: &self.values[range],
|
||||||
|
nrows: self.nrows()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable column access for the given column index, or `None` if out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_col_mut(&mut self, index: usize) -> Option<CscColMut<T>> {
|
||||||
|
let range = self.get_index_range(index)?;
|
||||||
|
Some(CscColMut {
|
||||||
|
nrows: self.nrows(),
|
||||||
|
row_indices: &self.sparsity_pattern.minor_indices()[range.clone()],
|
||||||
|
values: &mut self.values[range]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal method for simplifying access to a column's data.
|
||||||
|
fn get_index_range(&self, col_index: usize) -> Option<Range<usize>> {
|
||||||
|
let col_begin = *self.sparsity_pattern.major_offsets().get(col_index)?;
|
||||||
|
let col_end = *self.sparsity_pattern.major_offsets().get(col_index + 1)?;
|
||||||
|
Some(col_begin .. col_end)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over columns in the matrix.
|
||||||
|
pub fn col_iter(&self) -> CscColIter<T> {
|
||||||
|
CscColIter {
|
||||||
|
current_col_idx: 0,
|
||||||
|
matrix: self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mutable iterator over columns in the matrix.
|
||||||
|
pub fn col_iter_mut(&mut self) -> CscColIterMut<T> {
|
||||||
|
CscColIterMut {
|
||||||
|
current_col_idx: 0,
|
||||||
|
pattern: &self.sparsity_pattern,
|
||||||
|
remaining_values: self.values.as_mut_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying vector containing the values for the explicitly stored entries.
|
||||||
|
pub fn take_values(self) -> Vec<T> {
|
||||||
|
self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disassembles the CSC matrix into its underlying offset, index and value arrays.
|
||||||
|
///
|
||||||
|
/// If the matrix contains the sole reference to the sparsity pattern,
|
||||||
|
/// then the data is returned as-is. Otherwise, the sparsity pattern is cloned.
|
||||||
|
///
|
||||||
|
/// Examples
|
||||||
|
/// --------
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use nalgebra_sparse::csc::CscMatrix;
|
||||||
|
/// let col_offsets = vec![0, 2, 3, 4];
|
||||||
|
/// let row_indices = vec![0, 2, 1, 0];
|
||||||
|
/// let values = vec![1, 3, 2, 4];
|
||||||
|
/// let mut csc = CscMatrix::try_from_csc_data(
|
||||||
|
/// 4,
|
||||||
|
/// 3,
|
||||||
|
/// col_offsets.clone(),
|
||||||
|
/// row_indices.clone(),
|
||||||
|
/// values.clone())
|
||||||
|
/// .unwrap();
|
||||||
|
/// let (col_offsets2, row_indices2, values2) = csc.disassemble();
|
||||||
|
/// assert_eq!(col_offsets2, col_offsets);
|
||||||
|
/// assert_eq!(row_indices2, row_indices);
|
||||||
|
/// assert_eq!(values2, values);
|
||||||
|
/// ```
|
||||||
|
pub fn disassemble(self) -> (Vec<usize>, Vec<usize>, Vec<T>) {
|
||||||
|
// Take an Arc to the pattern, which might be the sole reference to the data after
|
||||||
|
// taking the values. This is important, because it might let us avoid cloning the data
|
||||||
|
// further below.
|
||||||
|
let pattern = self.pattern();
|
||||||
|
let values = self.take_values();
|
||||||
|
|
||||||
|
// Try to take the pattern out of the `Arc` if possible,
|
||||||
|
// otherwise clone the pattern.
|
||||||
|
let owned_pattern = Arc::try_unwrap(pattern)
|
||||||
|
.unwrap_or_else(|arc| SparsityPattern::clone(&*arc));
|
||||||
|
let (offsets, indices) = owned_pattern.disassemble();
|
||||||
|
|
||||||
|
(offsets, indices, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying sparsity pattern.
|
||||||
|
///
|
||||||
|
/// The sparsity pattern is stored internally inside an `Arc`. This allows users to re-use
|
||||||
|
/// the same sparsity pattern for multiple matrices without storing the same pattern multiple
|
||||||
|
/// times in memory.
|
||||||
|
pub fn pattern(&self) -> Arc<SparsityPattern> {
|
||||||
|
Arc::clone(&self.sparsity_pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Zero> CscMatrix<T> {
|
||||||
|
/// Return the value in the matrix at the given global row/col indices, or `None` if out of
|
||||||
|
/// bounds.
|
||||||
|
///
|
||||||
|
/// If the indices are in bounds, but no explicitly stored entry is associated with it,
|
||||||
|
/// `T::zero()` is returned. Note that this method offers no way of distinguishing
|
||||||
|
/// explicitly stored zero entries from zero values that are only implicitly represented.
|
||||||
|
///
|
||||||
|
/// Each call to this function incurs the cost of a binary search among the explicitly
|
||||||
|
/// stored column entries for the given row.
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self, row_index: usize, col_index: usize) -> Option<T> {
|
||||||
|
self.get_col(row_index)?.get(col_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `get`, but panics if indices are out of bounds.
|
||||||
|
///
|
||||||
|
/// Panics
|
||||||
|
/// ------
|
||||||
|
/// Panics if either index is out of bounds.
|
||||||
|
#[inline]
|
||||||
|
pub fn index(&self, row_index: usize, col_index: usize) -> T {
|
||||||
|
self.get(row_index, col_index).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert pattern format errors into more meaningful CSC-specific errors.
|
||||||
|
///
|
||||||
|
/// This ensures that the terminology is consistent: we are talking about rows and columns,
|
||||||
|
/// not lanes, major and minor dimensions.
|
||||||
|
fn pattern_format_error_to_csc_error(err: SparsityPatternFormatError) -> SparseFormatError {
|
||||||
|
use SparsityPatternFormatError::*;
|
||||||
|
use SparsityPatternFormatError::DuplicateEntry as PatternDuplicateEntry;
|
||||||
|
use SparseFormatError as E;
|
||||||
|
use SparseFormatErrorKind as K;
|
||||||
|
|
||||||
|
match err {
|
||||||
|
InvalidOffsetArrayLength => E::from_kind_and_msg(
|
||||||
|
K::InvalidStructure,
|
||||||
|
"Length of col offset array is not equal to ncols + 1."),
|
||||||
|
InvalidOffsetFirstLast => E::from_kind_and_msg(
|
||||||
|
K::InvalidStructure,
|
||||||
|
"First or last col offset is inconsistent with format specification."),
|
||||||
|
NonmonotonicOffsets => E::from_kind_and_msg(
|
||||||
|
K::InvalidStructure,
|
||||||
|
"Col offsets are not monotonically increasing."),
|
||||||
|
NonmonotonicMinorIndices => E::from_kind_and_msg(
|
||||||
|
K::InvalidStructure,
|
||||||
|
"Row indices are not monotonically increasing (sorted) within each column."),
|
||||||
|
MinorIndexOutOfBounds => E::from_kind_and_msg(
|
||||||
|
K::IndexOutOfBounds,
|
||||||
|
"Row indices are out of bounds."),
|
||||||
|
PatternDuplicateEntry => E::from_kind_and_msg(
|
||||||
|
K::DuplicateEntry,
|
||||||
|
"Matrix data contains duplicate entries."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator type for iterating over triplets in a CSC matrix.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CscTripletIter<'a, T> {
|
||||||
|
pattern_iter: SparsityPatternIter<'a>,
|
||||||
|
values_iter: Iter<'a, T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for CscTripletIter<'a, T> {
|
||||||
|
type Item = (usize, usize, &'a T);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next_entry = self.pattern_iter.next();
|
||||||
|
let next_value = self.values_iter.next();
|
||||||
|
|
||||||
|
match (next_entry, next_value) {
|
||||||
|
(Some((i, j)), Some(v)) => Some((j, i, v)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator type for mutably iterating over triplets in a CSC matrix.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CscTripletIterMut<'a, T> {
|
||||||
|
pattern_iter: SparsityPatternIter<'a>,
|
||||||
|
values_mut_iter: IterMut<'a, T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for CscTripletIterMut<'a, T> {
|
||||||
|
type Item = (usize, usize, &'a mut T);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next_entry = self.pattern_iter.next();
|
||||||
|
let next_value = self.values_mut_iter.next();
|
||||||
|
|
||||||
|
match (next_entry, next_value) {
|
||||||
|
(Some((i, j)), Some(v)) => Some((j, i, v)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An immutable representation of a column in a CSC matrix.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CscCol<'a, T> {
|
||||||
|
nrows: usize,
|
||||||
|
row_indices: &'a [usize],
|
||||||
|
values: &'a [T],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mutable representation of a column in a CSC matrix.
|
||||||
|
///
|
||||||
|
/// Note that only explicitly stored entries can be mutated. The sparsity pattern belonging
|
||||||
|
/// to the column cannot be modified.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct CscColMut<'a, T> {
|
||||||
|
nrows: usize,
|
||||||
|
row_indices: &'a [usize],
|
||||||
|
values: &'a mut [T]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement the methods common to both CscCol and CscColMut
|
||||||
|
macro_rules! impl_csc_col_common_methods {
|
||||||
|
($name:ty) => {
|
||||||
|
impl<'a, T> $name {
|
||||||
|
/// The number of global rows in the column.
|
||||||
|
#[inline]
|
||||||
|
pub fn nrows(&self) -> usize {
|
||||||
|
self.nrows
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of non-zeros in this column.
|
||||||
|
#[inline]
|
||||||
|
pub fn nnz(&self) -> usize {
|
||||||
|
self.row_indices.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The row indices corresponding to explicitly stored entries in this column.
|
||||||
|
#[inline]
|
||||||
|
pub fn row_indices(&self) -> &[usize] {
|
||||||
|
self.row_indices
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The values corresponding to explicitly stored entries in this column.
|
||||||
|
#[inline]
|
||||||
|
pub fn values(&self) -> &[T] {
|
||||||
|
self.values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Clone + Zero> $name {
|
||||||
|
/// Return the value in the matrix at the given global row index, or `None` if out of
|
||||||
|
/// bounds.
|
||||||
|
///
|
||||||
|
/// If the index is in bounds, but no explicitly stored entry is associated with it,
|
||||||
|
/// `T::zero()` is returned. Note that this method offers no way of distinguishing
|
||||||
|
/// explicitly stored zero entries from zero values that are only implicitly represented.
|
||||||
|
///
|
||||||
|
/// Each call to this function incurs the cost of a binary search among the explicitly
|
||||||
|
/// stored row entries for the current column.
|
||||||
|
pub fn get(&self, global_row_index: usize) -> Option<T> {
|
||||||
|
let local_index = self.row_indices().binary_search(&global_row_index);
|
||||||
|
if let Ok(local_index) = local_index {
|
||||||
|
Some(self.values[local_index].clone())
|
||||||
|
} else if global_row_index < self.nrows {
|
||||||
|
Some(T::zero())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_csc_col_common_methods!(CscCol<'a, T>);
|
||||||
|
impl_csc_col_common_methods!(CscColMut<'a, T>);
|
||||||
|
|
||||||
|
impl<'a, T> CscColMut<'a, T> {
|
||||||
|
/// Mutable access to the values corresponding to explicitly stored entries in this column.
|
||||||
|
pub fn values_mut(&mut self) -> &mut [T] {
|
||||||
|
self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides simultaneous access to row indices and mutable values corresponding to the
|
||||||
|
/// explicitly stored entries in this column.
|
||||||
|
///
|
||||||
|
/// This method primarily facilitates low-level access for methods that process data stored
|
||||||
|
/// in CSC format directly.
|
||||||
|
pub fn rows_and_values_mut(&mut self) -> (&[usize], &mut [T]) {
|
||||||
|
(self.row_indices, self.values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Column iterator for [CscMatrix](struct.CscMatrix.html).
|
||||||
|
pub struct CscColIter<'a, T> {
|
||||||
|
// The index of the row that will be returned on the next
|
||||||
|
current_col_idx: usize,
|
||||||
|
matrix: &'a CscMatrix<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for CscColIter<'a, T> {
|
||||||
|
type Item = CscCol<'a, T>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let col = self.matrix.get_col(self.current_col_idx);
|
||||||
|
self.current_col_idx += 1;
|
||||||
|
col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable column iterator for [CscMatrix](struct.CscMatrix.html).
|
||||||
|
pub struct CscColIterMut<'a, T> {
|
||||||
|
current_col_idx: usize,
|
||||||
|
pattern: &'a SparsityPattern,
|
||||||
|
remaining_values: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for CscColIterMut<'a, T>
|
||||||
|
where
|
||||||
|
T: 'a
|
||||||
|
{
|
||||||
|
type Item = CscColMut<'a, T>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let lane = self.pattern.get_lane(self.current_col_idx);
|
||||||
|
let nrows = self.pattern.minor_dim();
|
||||||
|
|
||||||
|
if let Some(row_indices) = lane {
|
||||||
|
let count = row_indices.len();
|
||||||
|
|
||||||
|
// Note: I can't think of any way to construct this iterator without unsafe.
|
||||||
|
let values_in_row;
|
||||||
|
unsafe {
|
||||||
|
values_in_row = &mut *slice_from_raw_parts_mut(self.remaining_values, count);
|
||||||
|
self.remaining_values = self.remaining_values.add(count);
|
||||||
|
}
|
||||||
|
self.current_col_idx += 1;
|
||||||
|
|
||||||
|
Some(CscColMut {
|
||||||
|
nrows,
|
||||||
|
row_indices,
|
||||||
|
values: values_in_row
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ use std::ptr::slice_from_raw_parts_mut;
|
||||||
|
|
||||||
/// A CSR representation of a sparse matrix.
|
/// A CSR representation of a sparse matrix.
|
||||||
///
|
///
|
||||||
/// The Compressed Row Storage (CSR) format is well-suited as a general-purpose storage format
|
/// The Compressed Sparse Row (CSR) format is well-suited as a general-purpose storage format
|
||||||
/// for many sparse matrix applications.
|
/// for many sparse matrix applications.
|
||||||
///
|
///
|
||||||
/// TODO: Storage explanation and examples
|
/// TODO: Storage explanation and examples
|
||||||
|
|
|
@ -28,7 +28,15 @@
|
||||||
//! - [x] "Disassemble" the CSR matrix into the raw CSR data arrays.
|
//! - [x] "Disassemble" the CSR matrix into the raw CSR data arrays.
|
||||||
//!
|
//!
|
||||||
//! - CSC matrix type. Functionality:
|
//! - CSC matrix type. Functionality:
|
||||||
//! - Same as CSR, but with columns instead of rows.
|
//! - [x] Access to CSC data as slices.
|
||||||
|
//! - [x] Return number of nnz
|
||||||
|
//! - [x] Access a given column, which gives convenient access to the data associated
|
||||||
|
//! with a particular column
|
||||||
|
//! - [x] Construct from valid CSC data
|
||||||
|
//! - [ ] Construct from unsorted CSC data
|
||||||
|
//! - [x] Iterate over entries (i, j, v) in the matrix (+mutable).
|
||||||
|
//! - [x] Iterate over rows in the matrix (+ mutable).
|
||||||
|
//! - [x] "Disassemble" the CSC matrix into the raw CSC data arrays.
|
||||||
//! - COO matrix type. Functionality:
|
//! - COO matrix type. Functionality:
|
||||||
//! - [x] Construct new "empty" COO matrix
|
//! - [x] Construct new "empty" COO matrix
|
||||||
//! - [x] Construct from triplet arrays.
|
//! - [x] Construct from triplet arrays.
|
||||||
|
@ -68,6 +76,7 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
pub mod coo;
|
pub mod coo;
|
||||||
|
pub mod csc;
|
||||||
pub mod csr;
|
pub mod csr;
|
||||||
pub mod pattern;
|
pub mod pattern;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
use nalgebra_sparse::csc::CscMatrix;
|
||||||
|
use nalgebra_sparse::SparseFormatErrorKind;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csc_matrix_valid_data() {
|
||||||
|
// Construct matrix from valid data and check that selected methods return results
|
||||||
|
// that agree with expectations.
|
||||||
|
|
||||||
|
{
|
||||||
|
// A CSC matrix with zero explicitly stored entries
|
||||||
|
let offsets = vec![0, 0, 0, 0];
|
||||||
|
let indices = vec![];
|
||||||
|
let values = Vec::<i32>::new();
|
||||||
|
let mut matrix = CscMatrix::try_from_csc_data(2, 3, offsets, indices, values).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(matrix, CscMatrix::new(2, 3));
|
||||||
|
|
||||||
|
assert_eq!(matrix.nrows(), 2);
|
||||||
|
assert_eq!(matrix.ncols(), 3);
|
||||||
|
assert_eq!(matrix.nnz(), 0);
|
||||||
|
assert_eq!(matrix.col_offsets(), &[0, 0, 0, 0]);
|
||||||
|
assert_eq!(matrix.row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.values(), &[]);
|
||||||
|
|
||||||
|
assert!(matrix.triplet_iter().next().is_none());
|
||||||
|
assert!(matrix.triplet_iter_mut().next().is_none());
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(0).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col(0).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col(0).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col(0).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(0).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col_mut(0).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col_mut(0).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(0).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(0).values_mut(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(0).rows_and_values_mut(), ([].as_ref(), [].as_mut()));
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(1).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col(1).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col(1).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col(1).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col_mut(1).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col_mut(1).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).values_mut(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).rows_and_values_mut(), ([].as_ref(), [].as_mut()));
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(2).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col(2).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col(2).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col(2).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(2).nrows(), 2);
|
||||||
|
assert_eq!(matrix.col_mut(2).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col_mut(2).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(2).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(2).values_mut(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(2).rows_and_values_mut(), ([].as_ref(), [].as_mut()));
|
||||||
|
|
||||||
|
assert!(matrix.get_col(3).is_none());
|
||||||
|
assert!(matrix.get_col_mut(3).is_none());
|
||||||
|
|
||||||
|
let (offsets, indices, values) = matrix.disassemble();
|
||||||
|
|
||||||
|
assert_eq!(offsets, vec![0, 0, 0, 0]);
|
||||||
|
assert_eq!(indices, vec![]);
|
||||||
|
assert_eq!(values, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// An arbitrary CSC matrix
|
||||||
|
let offsets = vec![0, 2, 2, 5];
|
||||||
|
let indices = vec![0, 5, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let mut matrix = CscMatrix::try_from_csc_data(6,
|
||||||
|
3,
|
||||||
|
offsets.clone(),
|
||||||
|
indices.clone(),
|
||||||
|
values.clone()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(matrix.nrows(), 6);
|
||||||
|
assert_eq!(matrix.ncols(), 3);
|
||||||
|
assert_eq!(matrix.nnz(), 5);
|
||||||
|
assert_eq!(matrix.col_offsets(), &[0, 2, 2, 5]);
|
||||||
|
assert_eq!(matrix.row_indices(), &[0, 5, 1, 2, 3]);
|
||||||
|
assert_eq!(matrix.values(), &[0, 1, 2, 3, 4]);
|
||||||
|
|
||||||
|
let expected_triplets = vec![(0, 0, 0), (5, 0, 1), (1, 2, 2), (2, 2, 3), (3, 2, 4)];
|
||||||
|
assert_eq!(matrix.triplet_iter().map(|(i, j, v)| (i, j, *v)).collect::<Vec<_>>(),
|
||||||
|
expected_triplets);
|
||||||
|
assert_eq!(matrix.triplet_iter_mut().map(|(i, j, v)| (i, j, *v)).collect::<Vec<_>>(),
|
||||||
|
expected_triplets);
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(0).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col(0).nnz(), 2);
|
||||||
|
assert_eq!(matrix.col(0).row_indices(), &[0, 5]);
|
||||||
|
assert_eq!(matrix.col(0).values(), &[0, 1]);
|
||||||
|
assert_eq!(matrix.col_mut(0).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col_mut(0).nnz(), 2);
|
||||||
|
assert_eq!(matrix.col_mut(0).row_indices(), &[0, 5]);
|
||||||
|
assert_eq!(matrix.col_mut(0).values(), &[0, 1]);
|
||||||
|
assert_eq!(matrix.col_mut(0).values_mut(), &[0, 1]);
|
||||||
|
assert_eq!(matrix.col_mut(0).rows_and_values_mut(), ([0, 5].as_ref(), [0, 1].as_mut()));
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(1).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col(1).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col(1).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col(1).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col_mut(1).nnz(), 0);
|
||||||
|
assert_eq!(matrix.col_mut(1).row_indices(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).values(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).values_mut(), &[]);
|
||||||
|
assert_eq!(matrix.col_mut(1).rows_and_values_mut(), ([].as_ref(), [].as_mut()));
|
||||||
|
|
||||||
|
assert_eq!(matrix.col(2).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col(2).nnz(), 3);
|
||||||
|
assert_eq!(matrix.col(2).row_indices(), &[1, 2, 3]);
|
||||||
|
assert_eq!(matrix.col(2).values(), &[2, 3, 4]);
|
||||||
|
assert_eq!(matrix.col_mut(2).nrows(), 6);
|
||||||
|
assert_eq!(matrix.col_mut(2).nnz(), 3);
|
||||||
|
assert_eq!(matrix.col_mut(2).row_indices(), &[1, 2, 3]);
|
||||||
|
assert_eq!(matrix.col_mut(2).values(), &[2, 3, 4]);
|
||||||
|
assert_eq!(matrix.col_mut(2).values_mut(), &[2, 3, 4]);
|
||||||
|
assert_eq!(matrix.col_mut(2).rows_and_values_mut(), ([1, 2, 3].as_ref(), [2, 3, 4].as_mut()));
|
||||||
|
|
||||||
|
assert!(matrix.get_col(3).is_none());
|
||||||
|
assert!(matrix.get_col_mut(3).is_none());
|
||||||
|
|
||||||
|
let (offsets2, indices2, values2) = matrix.disassemble();
|
||||||
|
|
||||||
|
assert_eq!(offsets2, offsets);
|
||||||
|
assert_eq!(indices2, indices);
|
||||||
|
assert_eq!(values2, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csc_matrix_try_from_invalid_csc_data() {
|
||||||
|
|
||||||
|
{
|
||||||
|
// Empty offset array (invalid length)
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(0, 0, Vec::new(), Vec::new(), Vec::<u32>::new());
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Offset array invalid length for arbitrary data
|
||||||
|
let offsets = vec![0, 3, 5];
|
||||||
|
let indices = vec![0, 1, 2, 3, 5];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid first entry in offsets array
|
||||||
|
let offsets = vec![1, 2, 2, 5];
|
||||||
|
let indices = vec![0, 5, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid last entry in offsets array
|
||||||
|
let offsets = vec![0, 2, 2, 4];
|
||||||
|
let indices = vec![0, 5, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Invalid length of offsets array
|
||||||
|
let offsets = vec![0, 2, 2];
|
||||||
|
let indices = vec![0, 5, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Nonmonotonic offsets
|
||||||
|
let offsets = vec![0, 3, 2, 5];
|
||||||
|
let indices = vec![0, 1, 2, 3, 4];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Nonmonotonic minor indices
|
||||||
|
let offsets = vec![0, 2, 2, 5];
|
||||||
|
let indices = vec![0, 2, 3, 1, 4];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Minor index out of bounds
|
||||||
|
let offsets = vec![0, 2, 2, 5];
|
||||||
|
let indices = vec![0, 6, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::IndexOutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Duplicate entry
|
||||||
|
let offsets = vec![0, 2, 2, 5];
|
||||||
|
let indices = vec![0, 5, 2, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values);
|
||||||
|
assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::DuplicateEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csc_disassemble_avoids_clone_when_owned() {
|
||||||
|
// Test that disassemble avoids cloning the sparsity pattern when it holds the sole reference
|
||||||
|
// to the pattern. We do so by checking that the pointer to the data is unchanged.
|
||||||
|
|
||||||
|
let offsets = vec![0, 2, 2, 5];
|
||||||
|
let indices = vec![0, 5, 1, 2, 3];
|
||||||
|
let values = vec![0, 1, 2, 3, 4];
|
||||||
|
let offsets_ptr = offsets.as_ptr();
|
||||||
|
let indices_ptr = indices.as_ptr();
|
||||||
|
let values_ptr = values.as_ptr();
|
||||||
|
let matrix = CscMatrix::try_from_csc_data(6,
|
||||||
|
3,
|
||||||
|
offsets,
|
||||||
|
indices,
|
||||||
|
values).unwrap();
|
||||||
|
|
||||||
|
let (offsets, indices, values) = matrix.disassemble();
|
||||||
|
assert_eq!(offsets.as_ptr(), offsets_ptr);
|
||||||
|
assert_eq!(indices.as_ptr(), indices_ptr);
|
||||||
|
assert_eq!(values.as_ptr(), values_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csc_matrix_get_index() {
|
||||||
|
// TODO: Implement tests for ::get() and index()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csc_matrix_col_iter() {
|
||||||
|
// TODO
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod coo;
|
mod coo;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
mod csr;
|
mod csr;
|
||||||
|
mod csc;
|
Loading…
Reference in New Issue