diff --git a/nalgebra-sparse/src/csr.rs b/nalgebra-sparse/src/csr.rs index 8c4be726..c612e5b4 100644 --- a/nalgebra-sparse/src/csr.rs +++ b/nalgebra-sparse/src/csr.rs @@ -62,7 +62,7 @@ impl CsrMatrix { /// The column indices defining part of the CSR format. #[inline] - pub fn column_indices(&self) -> &[usize] { + pub fn col_indices(&self) -> &[usize] { self.sparsity_pattern.minor_indices() } @@ -520,7 +520,7 @@ where type Item = CsrRowMut<'a, T>; fn next(&mut self) -> Option { - let lane = self.pattern.lane(self.current_row_idx); + let lane = self.pattern.get_lane(self.current_row_idx); let ncols = self.pattern.minor_dim(); if let Some(col_indices) = lane { diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index 872126a8..9621d973 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -103,7 +103,7 @@ impl SparseFormatError { } /// The type of format error described by a [SparseFormatError](struct.SparseFormatError.html). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum SparseFormatErrorKind { /// Indicates that the index data associated with the format contains at least one index /// out of bounds. diff --git a/nalgebra-sparse/src/pattern.rs b/nalgebra-sparse/src/pattern.rs index 54e0f2e8..f95f23c8 100644 --- a/nalgebra-sparse/src/pattern.rs +++ b/nalgebra-sparse/src/pattern.rs @@ -66,8 +66,19 @@ impl SparsityPattern { } /// Get the lane at the given index. + /// + /// Panics + /// ------ + /// + /// Panics if `major_index` is out of bounds. #[inline] - pub fn lane(&self, major_index: usize) -> Option<&[usize]> { + pub fn lane(&self, major_index: usize) -> &[usize] { + self.get_lane(major_index).unwrap() + } + + /// Get the lane at the given index, or `None` if out of bounds. + #[inline] + pub fn get_lane(&self, major_index: usize) -> Option<&[usize]> { let offset_begin = *self.major_offsets().get(major_index)?; let offset_end = *self.major_offsets().get(major_index + 1)?; Some(&self.minor_indices()[offset_begin..offset_end]) @@ -124,7 +135,7 @@ impl SparsityPattern { let mut prev = None; while let Some(next) = iter.next().copied() { - if next > minor_dim { + if next >= minor_dim { return Err(MinorIndexOutOfBounds); } @@ -197,7 +208,7 @@ impl SparsityPattern { /// Error type for `SparsityPattern` format errors. #[non_exhaustive] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum SparsityPatternFormatError { /// Indicates an invalid number of offsets. /// diff --git a/nalgebra-sparse/tests/unit_tests/csr.rs b/nalgebra-sparse/tests/unit_tests/csr.rs new file mode 100644 index 00000000..693e092f --- /dev/null +++ b/nalgebra-sparse/tests/unit_tests/csr.rs @@ -0,0 +1,231 @@ +use nalgebra_sparse::csr::CsrMatrix; +use nalgebra_sparse::SparseFormatErrorKind; + +#[test] +fn csr_matrix_valid_data() { + // Construct matrix from valid data and check that selected methods return results + // that agree with expectations. + + { + // A CSR matrix with zero explicitly stored entries + let offsets = vec![0, 0, 0, 0]; + let indices = vec![]; + let values = Vec::::new(); + let mut matrix = CsrMatrix::try_from_csr_data(3, 2, offsets, indices, values).unwrap(); + + assert_eq!(matrix, CsrMatrix::new(3, 2)); + + assert_eq!(matrix.nrows(), 3); + assert_eq!(matrix.ncols(), 2); + assert_eq!(matrix.nnz(), 0); + assert_eq!(matrix.row_offsets(), &[0, 0, 0, 0]); + assert_eq!(matrix.col_indices(), &[]); + assert_eq!(matrix.values(), &[]); + + assert!(matrix.triplet_iter().next().is_none()); + assert!(matrix.triplet_iter_mut().next().is_none()); + + assert_eq!(matrix.row(0).ncols(), 2); + assert_eq!(matrix.row(0).nnz(), 0); + assert_eq!(matrix.row(0).col_indices(), &[]); + assert_eq!(matrix.row(0).values(), &[]); + assert_eq!(matrix.row_mut(0).ncols(), 2); + assert_eq!(matrix.row_mut(0).nnz(), 0); + assert_eq!(matrix.row_mut(0).col_indices(), &[]); + assert_eq!(matrix.row_mut(0).values(), &[]); + assert_eq!(matrix.row_mut(0).values_mut(), &[]); + assert_eq!(matrix.row_mut(0).cols_and_values_mut(), ([].as_ref(), [].as_mut())); + + assert_eq!(matrix.row(1).ncols(), 2); + assert_eq!(matrix.row(1).nnz(), 0); + assert_eq!(matrix.row(1).col_indices(), &[]); + assert_eq!(matrix.row(1).values(), &[]); + assert_eq!(matrix.row_mut(1).ncols(), 2); + assert_eq!(matrix.row_mut(1).nnz(), 0); + assert_eq!(matrix.row_mut(1).col_indices(), &[]); + assert_eq!(matrix.row_mut(1).values(), &[]); + assert_eq!(matrix.row_mut(1).values_mut(), &[]); + assert_eq!(matrix.row_mut(1).cols_and_values_mut(), ([].as_ref(), [].as_mut())); + + assert_eq!(matrix.row(2).ncols(), 2); + assert_eq!(matrix.row(2).nnz(), 0); + assert_eq!(matrix.row(2).col_indices(), &[]); + assert_eq!(matrix.row(2).values(), &[]); + assert_eq!(matrix.row_mut(2).ncols(), 2); + assert_eq!(matrix.row_mut(2).nnz(), 0); + assert_eq!(matrix.row_mut(2).col_indices(), &[]); + assert_eq!(matrix.row_mut(2).values(), &[]); + assert_eq!(matrix.row_mut(2).values_mut(), &[]); + assert_eq!(matrix.row_mut(2).cols_and_values_mut(), ([].as_ref(), [].as_mut())); + + assert!(matrix.get_row(3).is_none()); + assert!(matrix.get_row_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 CSR 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 = CsrMatrix::try_from_csr_data(3, + 6, + offsets.clone(), + indices.clone(), + values.clone()).unwrap(); + + assert_eq!(matrix.nrows(), 3); + assert_eq!(matrix.ncols(), 6); + assert_eq!(matrix.nnz(), 5); + assert_eq!(matrix.row_offsets(), &[0, 2, 2, 5]); + assert_eq!(matrix.col_indices(), &[0, 5, 1, 2, 3]); + assert_eq!(matrix.values(), &[0, 1, 2, 3, 4]); + + let expected_triplets = vec![(0, 0, 0), (0, 5, 1), (2, 1, 2), (2, 2, 3), (2, 3, 4)]; + assert_eq!(matrix.triplet_iter().map(|(i, j, v)| (i, j, *v)).collect::>(), + expected_triplets); + assert_eq!(matrix.triplet_iter_mut().map(|(i, j, v)| (i, j, *v)).collect::>(), + expected_triplets); + + assert_eq!(matrix.row(0).ncols(), 6); + assert_eq!(matrix.row(0).nnz(), 2); + assert_eq!(matrix.row(0).col_indices(), &[0, 5]); + assert_eq!(matrix.row(0).values(), &[0, 1]); + assert_eq!(matrix.row_mut(0).ncols(), 6); + assert_eq!(matrix.row_mut(0).nnz(), 2); + assert_eq!(matrix.row_mut(0).col_indices(), &[0, 5]); + assert_eq!(matrix.row_mut(0).values(), &[0, 1]); + assert_eq!(matrix.row_mut(0).values_mut(), &[0, 1]); + assert_eq!(matrix.row_mut(0).cols_and_values_mut(), ([0, 5].as_ref(), [0, 1].as_mut())); + + assert_eq!(matrix.row(1).ncols(), 6); + assert_eq!(matrix.row(1).nnz(), 0); + assert_eq!(matrix.row(1).col_indices(), &[]); + assert_eq!(matrix.row(1).values(), &[]); + assert_eq!(matrix.row_mut(1).ncols(), 6); + assert_eq!(matrix.row_mut(1).nnz(), 0); + assert_eq!(matrix.row_mut(1).col_indices(), &[]); + assert_eq!(matrix.row_mut(1).values(), &[]); + assert_eq!(matrix.row_mut(1).values_mut(), &[]); + assert_eq!(matrix.row_mut(1).cols_and_values_mut(), ([].as_ref(), [].as_mut())); + + assert_eq!(matrix.row(2).ncols(), 6); + assert_eq!(matrix.row(2).nnz(), 3); + assert_eq!(matrix.row(2).col_indices(), &[1, 2, 3]); + assert_eq!(matrix.row(2).values(), &[2, 3, 4]); + assert_eq!(matrix.row_mut(2).ncols(), 6); + assert_eq!(matrix.row_mut(2).nnz(), 3); + assert_eq!(matrix.row_mut(2).col_indices(), &[1, 2, 3]); + assert_eq!(matrix.row_mut(2).values(), &[2, 3, 4]); + assert_eq!(matrix.row_mut(2).values_mut(), &[2, 3, 4]); + assert_eq!(matrix.row_mut(2).cols_and_values_mut(), ([1, 2, 3].as_ref(), [2, 3, 4].as_mut())); + + assert!(matrix.get_row(3).is_none()); + assert!(matrix.get_row_mut(3).is_none()); + + let (offsets2, indices2, values2) = matrix.disassemble(); + + assert_eq!(offsets2, offsets); + assert_eq!(indices2, indices); + assert_eq!(values2, values); + } +} + +#[test] +fn csr_matrix_try_from_invalid_csr_data() { + + { + // Empty offset array (invalid length) + let matrix = CsrMatrix::try_from_csr_data(0, 0, Vec::new(), Vec::new(), Vec::::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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, 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 = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values); + assert_eq!(matrix.unwrap_err().kind(), &SparseFormatErrorKind::DuplicateEntry); + } + +} + +#[test] +fn csr_matrix_get_index() { + // TODO: Implement tests for ::get() and index() +} + +#[test] +fn csr_matrix_row_iter() { + +} \ No newline at end of file diff --git a/nalgebra-sparse/tests/unit_tests/mod.rs b/nalgebra-sparse/tests/unit_tests/mod.rs index 0c1631c4..2db7ab12 100644 --- a/nalgebra-sparse/tests/unit_tests/mod.rs +++ b/nalgebra-sparse/tests/unit_tests/mod.rs @@ -1,2 +1,4 @@ mod coo; -mod ops; \ No newline at end of file +mod ops; +mod pattern; +mod csr; \ No newline at end of file diff --git a/nalgebra-sparse/tests/unit_tests/pattern.rs b/nalgebra-sparse/tests/unit_tests/pattern.rs new file mode 100644 index 00000000..4664ad74 --- /dev/null +++ b/nalgebra-sparse/tests/unit_tests/pattern.rs @@ -0,0 +1,130 @@ +use nalgebra_sparse::pattern::{SparsityPattern, SparsityPatternFormatError}; + +#[test] +fn sparsity_pattern_valid_data() { + // Construct pattern from valid data and check that selected methods return results + // that agree with expectations. + + { + // A pattern with zero explicitly stored entries + let pattern = SparsityPattern::try_from_offsets_and_indices(3, + 2, + vec![0, 0, 0, 0], + Vec::new()) + .unwrap(); + + assert_eq!(pattern.major_dim(), 3); + assert_eq!(pattern.minor_dim(), 2); + assert_eq!(pattern.nnz(), 0); + assert_eq!(pattern.major_offsets(), &[0, 0, 0, 0]); + assert_eq!(pattern.minor_indices(), &[]); + assert_eq!(pattern.lane(0), &[]); + assert_eq!(pattern.lane(1), &[]); + assert_eq!(pattern.lane(2), &[]); + assert!(pattern.entries().next().is_none()); + + assert_eq!(pattern, SparsityPattern::new(3, 2)); + + let (offsets, indices) = pattern.disassemble(); + assert_eq!(offsets, vec![0, 0, 0, 0]); + assert_eq!(indices, vec![]); + } + + { + // Arbitrary pattern + let offsets = vec![0, 2, 2, 5]; + let indices = vec![0, 5, 1, 2, 3]; + let pattern = + SparsityPattern::try_from_offsets_and_indices(3, 6, offsets.clone(), indices.clone()) + .unwrap(); + + assert_eq!(pattern.major_dim(), 3); + assert_eq!(pattern.minor_dim(), 6); + assert_eq!(pattern.major_offsets(), offsets.as_slice()); + assert_eq!(pattern.minor_indices(), indices.as_slice()); + assert_eq!(pattern.nnz(), 5); + assert_eq!(pattern.lane(0), &[0, 5]); + assert_eq!(pattern.lane(1), &[]); + assert_eq!(pattern.lane(2), &[1, 2, 3]); + assert_eq!(pattern.entries().collect::>(), + vec![(0, 0), (0, 5), (2, 1), (2, 2), (2, 3)]); + + let (offsets2, indices2) = pattern.disassemble(); + assert_eq!(offsets2, offsets); + assert_eq!(indices2, indices); + } +} + +#[test] +fn sparsity_pattern_try_from_invalid_data() { + { + // Empty offset array (invalid length) + let pattern = SparsityPattern::try_from_offsets_and_indices(0, 0, Vec::new(), Vec::new()); + assert_eq!(pattern, Err(SparsityPatternFormatError::InvalidOffsetArrayLength)); + } + + { + // Offset array invalid length for arbitrary data + let offsets = vec![0, 3, 5]; + let indices = vec![0, 1, 2, 3, 5]; + + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert!(matches!(pattern, Err(SparsityPatternFormatError::InvalidOffsetArrayLength))); + } + + { + // Invalid first entry in offsets array + let offsets = vec![1, 2, 2, 5]; + let indices = vec![0, 5, 1, 2, 3]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert!(matches!(pattern, Err(SparsityPatternFormatError::InvalidOffsetFirstLast))); + } + + { + // Invalid last entry in offsets array + let offsets = vec![0, 2, 2, 4]; + let indices = vec![0, 5, 1, 2, 3]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert!(matches!(pattern, Err(SparsityPatternFormatError::InvalidOffsetFirstLast))); + } + + { + // Invalid length of offsets array + let offsets = vec![0, 2, 2]; + let indices = vec![0, 5, 1, 2, 3]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert!(matches!(pattern, Err(SparsityPatternFormatError::InvalidOffsetArrayLength))); + } + + { + // Nonmonotonic offsets + let offsets = vec![0, 3, 2, 5]; + let indices = vec![0, 1, 2, 3, 4]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert_eq!(pattern, Err(SparsityPatternFormatError::NonmonotonicOffsets)); + } + + { + // Nonmonotonic minor indices + let offsets = vec![0, 2, 2, 5]; + let indices = vec![0, 2, 3, 1, 4]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert_eq!(pattern, Err(SparsityPatternFormatError::NonmonotonicMinorIndices)); + } + + { + // Minor index out of bounds + let offsets = vec![0, 2, 2, 5]; + let indices = vec![0, 6, 1, 2, 3]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert_eq!(pattern, Err(SparsityPatternFormatError::MinorIndexOutOfBounds)); + } + + { + // Duplicate entry + let offsets = vec![0, 2, 2, 5]; + let indices = vec![0, 5, 2, 2, 3]; + let pattern = SparsityPattern::try_from_offsets_and_indices(3, 6, offsets, indices); + assert_eq!(pattern, Err(SparsityPatternFormatError::DuplicateEntry)); + } +} \ No newline at end of file