From ffc77407616b21342cdff2833f107bd1cc63fef5 Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 11:17:45 -0500 Subject: [PATCH 1/6] Fix tests with missing type annotations Compiler was emitting errors on these lines. --- nalgebra-sparse/tests/unit_tests/csc.rs | 48 ++++++++++----------- nalgebra-sparse/tests/unit_tests/csr.rs | 48 ++++++++++----------- nalgebra-sparse/tests/unit_tests/pattern.rs | 12 +++--- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/nalgebra-sparse/tests/unit_tests/csc.rs b/nalgebra-sparse/tests/unit_tests/csc.rs index 7fb0de540..741fb1c74 100644 --- a/nalgebra-sparse/tests/unit_tests/csc.rs +++ b/nalgebra-sparse/tests/unit_tests/csc.rs @@ -28,21 +28,21 @@ fn csc_matrix_valid_data() { 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_eq!(matrix.row_indices(), &[0; 0]); + assert_eq!(matrix.values(), &[0; 0]); 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(0).row_indices(), &[0; 0]); + assert_eq!(matrix.col(0).values(), &[0; 0]); 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).row_indices(), &[0; 0]); + assert_eq!(matrix.col_mut(0).values(), &[0; 0]); + assert_eq!(matrix.col_mut(0).values_mut(), &[0; 0]); assert_eq!( matrix.col_mut(0).rows_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -50,13 +50,13 @@ fn csc_matrix_valid_data() { 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(1).row_indices(), &[0; 0]); + assert_eq!(matrix.col(1).values(), &[0; 0]); 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).row_indices(), &[0; 0]); + assert_eq!(matrix.col_mut(1).values(), &[0; 0]); + assert_eq!(matrix.col_mut(1).values_mut(), &[0; 0]); assert_eq!( matrix.col_mut(1).rows_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -64,13 +64,13 @@ fn csc_matrix_valid_data() { 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(2).row_indices(), &[0; 0]); + assert_eq!(matrix.col(2).values(), &[0; 0]); 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).row_indices(), &[0; 0]); + assert_eq!(matrix.col_mut(2).values(), &[0; 0]); + assert_eq!(matrix.col_mut(2).values_mut(), &[0; 0]); assert_eq!( matrix.col_mut(2).rows_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -82,8 +82,8 @@ fn csc_matrix_valid_data() { let (offsets, indices, values) = matrix.disassemble(); assert_eq!(offsets, vec![0, 0, 0, 0]); - assert_eq!(indices, vec![]); - assert_eq!(values, vec![]); + assert_eq!(indices, Vec::::new()); + assert_eq!(values, Vec::::new()); } { @@ -134,13 +134,13 @@ fn csc_matrix_valid_data() { 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(1).row_indices(), &[0; 0]); + assert_eq!(matrix.col(1).values(), &[0; 0]); 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).row_indices(), &[0; 0]); + assert_eq!(matrix.col_mut(1).values(), &[0; 0]); + assert_eq!(matrix.col_mut(1).values_mut(), &[0; 0]); assert_eq!( matrix.col_mut(1).rows_and_values_mut(), ([].as_ref(), [].as_mut()) diff --git a/nalgebra-sparse/tests/unit_tests/csr.rs b/nalgebra-sparse/tests/unit_tests/csr.rs index 3ca2f0dc0..11cc4c0e4 100644 --- a/nalgebra-sparse/tests/unit_tests/csr.rs +++ b/nalgebra-sparse/tests/unit_tests/csr.rs @@ -30,21 +30,21 @@ fn csr_matrix_valid_data() { 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_eq!(matrix.col_indices(), &[0; 0]); + assert_eq!(matrix.values(), &[0; 0]); 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(0).col_indices(), &[0; 0]); + assert_eq!(matrix.row(0).values(), &[0; 0]); 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).col_indices(), &[0; 0]); + assert_eq!(matrix.row_mut(0).values(), &[0; 0]); + assert_eq!(matrix.row_mut(0).values_mut(), &[0; 0]); assert_eq!( matrix.row_mut(0).cols_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -52,13 +52,13 @@ fn csr_matrix_valid_data() { 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(1).col_indices(), &[0; 0]); + assert_eq!(matrix.row(1).values(), &[0; 0]); 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).col_indices(), &[0; 0]); + assert_eq!(matrix.row_mut(1).values(), &[0; 0]); + assert_eq!(matrix.row_mut(1).values_mut(), &[0; 0]); assert_eq!( matrix.row_mut(1).cols_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -66,13 +66,13 @@ fn csr_matrix_valid_data() { 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(2).col_indices(), &[0; 0]); + assert_eq!(matrix.row(2).values(), &[0; 0]); 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).col_indices(), &[0; 0]); + assert_eq!(matrix.row_mut(2).values(), &[0; 0]); + assert_eq!(matrix.row_mut(2).values_mut(), &[0; 0]); assert_eq!( matrix.row_mut(2).cols_and_values_mut(), ([].as_ref(), [].as_mut()) @@ -84,8 +84,8 @@ fn csr_matrix_valid_data() { let (offsets, indices, values) = matrix.disassemble(); assert_eq!(offsets, vec![0, 0, 0, 0]); - assert_eq!(indices, vec![]); - assert_eq!(values, vec![]); + assert_eq!(indices, Vec::::new()); + assert_eq!(values, Vec::::new()); } { @@ -136,13 +136,13 @@ fn csr_matrix_valid_data() { 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(1).col_indices(), &[0; 0]); + assert_eq!(matrix.row(1).values(), &[0; 0]); 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).col_indices(), &[0; 0]); + assert_eq!(matrix.row_mut(1).values(), &[0; 0]); + assert_eq!(matrix.row_mut(1).values_mut(), &[0; 0]); assert_eq!( matrix.row_mut(1).cols_and_values_mut(), ([].as_ref(), [].as_mut()) diff --git a/nalgebra-sparse/tests/unit_tests/pattern.rs b/nalgebra-sparse/tests/unit_tests/pattern.rs index 310cffae9..111176499 100644 --- a/nalgebra-sparse/tests/unit_tests/pattern.rs +++ b/nalgebra-sparse/tests/unit_tests/pattern.rs @@ -15,17 +15,17 @@ fn sparsity_pattern_valid_data() { 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_eq!(pattern.minor_indices(), &[0; 0]); + assert_eq!(pattern.lane(0), &[0; 0]); + assert_eq!(pattern.lane(1), &[0; 0]); + assert_eq!(pattern.lane(2), &[0; 0]); assert!(pattern.entries().next().is_none()); assert_eq!(pattern, SparsityPattern::zeros(3, 2)); let (offsets, indices) = pattern.disassemble(); assert_eq!(offsets, vec![0, 0, 0, 0]); - assert_eq!(indices, vec![]); + assert_eq!(indices, Vec::::new()); } { @@ -42,7 +42,7 @@ fn sparsity_pattern_valid_data() { 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(1), &[0; 0]); assert_eq!(pattern.lane(2), &[1, 2, 3]); assert_eq!( pattern.entries().collect::>(), From ab9816d27dc81acd81ecbe8cf3e16d56cbca5c22 Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 16:02:20 -0500 Subject: [PATCH 2/6] Add serde for SparsityPattern --- nalgebra-sparse/Cargo.toml | 9 +- nalgebra-sparse/src/pattern.rs | 126 ++++++++++++++++++++ nalgebra-sparse/tests/unit_tests/pattern.rs | 17 +++ 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 76fb408a4..d87ec2f6c 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -13,8 +13,12 @@ keywords = [ "linear", "algebra", "matrix", "vector", "math" ] license = "Apache-2.0" [features] +default = ["serde-serialize"] proptest-support = ["proptest", "nalgebra/proptest-support"] compare = [ "matrixcompare-core" ] +# Sparse uses std::collection::Vec, so no-std is a non-starter for now +# serde-serialize-no-std = [ "serde" ] +serde-serialize = [ "serde", "serde/std", "serde_test"] # Enable to enable running some tests that take a lot of time to run slow-tests = [] @@ -24,12 +28,15 @@ nalgebra = { version="0.29", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } matrixcompare-core = { version = "0.1.0", optional = true } +serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } +serde_test = { version = "1.0", optional = true } [dev-dependencies] itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } nalgebra = { version="0.29", path = "../", features = ["compare"] } +serde_json = "1.0" [package.metadata.docs.rs] # Enable certain features when building docs for docs.rs -features = [ "proptest-support", "compare" ] \ No newline at end of file +features = [ "proptest-support", "compare" ] diff --git a/nalgebra-sparse/src/pattern.rs b/nalgebra-sparse/src/pattern.rs index 85f6bc1a8..175dd986e 100644 --- a/nalgebra-sparse/src/pattern.rs +++ b/nalgebra-sparse/src/pattern.rs @@ -4,6 +4,9 @@ use crate::SparseFormatError; use std::error::Error; use std::fmt; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// A representation of the sparsity pattern of a CSR or CSC matrix. /// /// CSR and CSC matrices store matrices in a very similar fashion. In fact, in a certain sense, @@ -40,6 +43,11 @@ use std::fmt; /// as for `row_offsets` and `col_indices` in the [CSR](`crate::csr::CsrMatrix`) format /// specification. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "serde-serialize", + derive(Serialize, Deserialize), + serde(remote = "Self") +)] // TODO: Make SparsityPattern parametrized by index type // (need a solid abstraction for index types though) pub struct SparsityPattern { @@ -400,3 +408,121 @@ impl<'a> Iterator for SparsityPatternIter<'a> { } } } + +impl Serialize for SparsityPattern { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Self::serialize(self, serializer) + } +} + +impl<'de> Deserialize<'de> for SparsityPattern { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let unchecked = SparsityPattern::deserialize(deserializer)?; + if unchecked.major_offsets.len() == 0 { + return Err(serde::de::Error::custom( + "Deserializing from pattern with no major offset. + There should always be at least one major offset", + )); + } + let major_dim = unchecked.major_offsets.len() - 1; + let minor_dim = unchecked.minor_dim(); + let major_offsets = unchecked.major_offsets; + let minor_indices = unchecked.minor_indices; + SparsityPattern::try_from_offsets_and_indices( + major_dim, + minor_dim, + major_offsets, + minor_indices, + ) + .map_err(|e| serde::de::Error::custom(e.to_string())) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn pattern_serde() { + // Round trip + { + // 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(); + + let serialized = serde_json::to_string(&pattern).unwrap(); + let deserialized = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(pattern, deserialized); + } + + // Empty Major Offsets + { + let sp = SparsityPattern { + major_offsets: vec![0; 0], + minor_indices: vec![0; 0], + minor_dim: 0, + }; + let serialized = serde_json::to_string(&sp).unwrap(); + let deserialized = serde_json::from_str::(&serialized); + match deserialized { + Ok(_) => assert!(false, "Should error if major offsets is empty"), + Err(_e) => assert!(true, "asdf"), // TODO check message + } + } + // InvalidOffsetArrayLength + { + let sp = SparsityPattern { + major_offsets: vec![0, 1, 2, 3], + minor_indices: vec![0; 0], + minor_dim: 3, + }; + let serialized = serde_json::to_string(&sp).unwrap(); + let deserialized = serde_json::from_str::(&serialized); + match deserialized { + Ok(_) => assert!(false, "Should error if major offsets is empty"), + Err(_e) => assert!(true, "asdf"), // TODO check message + } + } + // Nonmonotonic Minor Indiices + { + let sp = SparsityPattern { + major_offsets: vec![0, 1, 2, 3], + minor_indices: vec![3, 2, 1, 0], + minor_dim: 3, + }; + let serialized = serde_json::to_string(&sp).unwrap(); + let deserialized = serde_json::from_str::(&serialized); + match deserialized { + Ok(_) => assert!(false, "Should error if major offsets is empty"), + Err(_e) => assert!(true, "asdf"), // TODO check message + } + } + // Duplicate Entries + { + let sp = SparsityPattern { + major_offsets: vec![0, 1, 2, 3], + minor_indices: vec![0, 1, 2, 2], + minor_dim: 3, + }; + let serialized = serde_json::to_string(&sp).unwrap(); + let deserialized = serde_json::from_str::(&serialized); + match deserialized { + Ok(_) => assert!(false, "Should error if major offsets is empty"), + Err(_e) => assert!(true, "asdf"), // TODO check message + } + } + } +} diff --git a/nalgebra-sparse/tests/unit_tests/pattern.rs b/nalgebra-sparse/tests/unit_tests/pattern.rs index 111176499..1feb4175a 100644 --- a/nalgebra-sparse/tests/unit_tests/pattern.rs +++ b/nalgebra-sparse/tests/unit_tests/pattern.rs @@ -152,3 +152,20 @@ fn sparsity_pattern_try_from_invalid_data() { assert_eq!(pattern, Err(SparsityPatternFormatError::DuplicateEntry)); } } + +#[test] +fn sparsity_pattern_serde() { + // Round trip + { + // 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(); + + let serialized = serde_json::to_string(&pattern).unwrap(); + let deserialized = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(pattern, deserialized); + } +} From d2442a45fd1d318907b81533ff46a7c7809c0b80 Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 16:03:14 -0500 Subject: [PATCH 3/6] Add serde for CsMatrix --- nalgebra-sparse/src/cs.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/nalgebra-sparse/src/cs.rs b/nalgebra-sparse/src/cs.rs index cffdd6c78..cc99069b2 100644 --- a/nalgebra-sparse/src/cs.rs +++ b/nalgebra-sparse/src/cs.rs @@ -8,6 +8,9 @@ use nalgebra::Scalar; use crate::pattern::SparsityPattern; use crate::{SparseEntry, SparseEntryMut}; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// An abstract compressed matrix. /// /// For the time being, this is only used internally to share implementation between @@ -16,6 +19,11 @@ use crate::{SparseEntry, SparseEntryMut}; /// A CSR matrix is obtained by associating rows with the major dimension, while a CSC matrix /// is obtained by associating columns with the major dimension. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "serde-serialize", + derive(Serialize, Deserialize), + serde(remote = "Self") +)] pub struct CsMatrix { sparsity_pattern: SparsityPattern, values: Vec, @@ -543,3 +551,30 @@ pub fn convert_counts_to_offsets(counts: &mut [usize]) { offset += count; } } + +impl Serialize for CsMatrix { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Self::serialize(self, serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for CsMatrix { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let unchecked = Self::deserialize(deserializer)?; + if unchecked.sparsity_pattern.nnz() != unchecked.values.len() { + return Err(serde::de::Error::custom( + "Pattern non-zero count doesn't match number of values", + )); + } + Ok(Self::from_pattern_and_values( + unchecked.sparsity_pattern, + unchecked.values, + )) + } +} From 1b728b5e628211df40762d73d3fa2993c8dea825 Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 16:04:25 -0500 Subject: [PATCH 4/6] Add serde for CscMatrix --- nalgebra-sparse/src/csc.rs | 4 +++ nalgebra-sparse/tests/unit_tests/csc.rs | 37 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/nalgebra-sparse/src/csc.rs b/nalgebra-sparse/src/csc.rs index 607cc0cfd..824280a1d 100644 --- a/nalgebra-sparse/src/csc.rs +++ b/nalgebra-sparse/src/csc.rs @@ -12,6 +12,9 @@ use nalgebra::Scalar; use num_traits::One; use std::slice::{Iter, IterMut}; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + /// A CSC representation of a sparse matrix. /// /// The Compressed Sparse Column (CSC) format is well-suited as a general-purpose storage format @@ -121,6 +124,7 @@ use std::slice::{Iter, IterMut}; /// /// [Wikipedia article]: https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_column_(CSC_or_CCS) #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct CscMatrix { // Cols are major, rows are minor in the sparsity pattern pub(crate) cs: CsMatrix, diff --git a/nalgebra-sparse/tests/unit_tests/csc.rs b/nalgebra-sparse/tests/unit_tests/csc.rs index 741fb1c74..aa529d7b6 100644 --- a/nalgebra-sparse/tests/unit_tests/csc.rs +++ b/nalgebra-sparse/tests/unit_tests/csc.rs @@ -528,6 +528,43 @@ fn csc_matrix_col_iter() { } } +#[test] +fn csc_matrix_serde_roundtrip() { + // 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 matrix = + CscMatrix::try_from_csc_data(6, 3, offsets.clone(), indices.clone(), values.clone()) + .unwrap(); + let serialized = serde_json::to_string(&matrix).unwrap(); + let deserialized = serde_json::from_str::>(&serialized).unwrap(); + assert_eq!(matrix, deserialized); +} + +#[test] +#[should_panic(expected = "Pattern non-zero count doesn't match number of values")] +fn csc_matrix_serde() { + let offsets = vec![0, 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.clone(), indices.clone(), values.clone()) + .unwrap(); + let mut serialized = serde_json::to_value(&matrix).unwrap(); + let obj = serialized.as_object_mut().unwrap(); + // Patch the JSON to introduce + let values_array = obj + .get_mut("cs") + .unwrap() + .get_mut("values") + .unwrap() + .as_array_mut() + .unwrap(); + values_array.pop(); + let _deserialized = serde_json::from_value::>(serialized).unwrap(); +} + proptest! { #[test] fn csc_double_transpose_is_identity(csc in csc_strategy()) { From d35e583dd774ced0dcdbe071d08a2cdc53f363af Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 16:04:52 -0500 Subject: [PATCH 5/6] Add serde for CsrMatrix --- nalgebra-sparse/src/csr.rs | 4 +++ nalgebra-sparse/tests/unit_tests/csr.rs | 36 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/nalgebra-sparse/src/csr.rs b/nalgebra-sparse/src/csr.rs index 4324d18d8..d436cfccd 100644 --- a/nalgebra-sparse/src/csr.rs +++ b/nalgebra-sparse/src/csr.rs @@ -13,6 +13,9 @@ use num_traits::One; use std::iter::FromIterator; use std::slice::{Iter, IterMut}; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + /// A CSR representation of a sparse matrix. /// /// The Compressed Sparse Row (CSR) format is well-suited as a general-purpose storage format @@ -122,6 +125,7 @@ use std::slice::{Iter, IterMut}; /// /// [Wikipedia article]: https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format) #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct CsrMatrix { // Rows are major, cols are minor in the sparsity pattern pub(crate) cs: CsMatrix, diff --git a/nalgebra-sparse/tests/unit_tests/csr.rs b/nalgebra-sparse/tests/unit_tests/csr.rs index 11cc4c0e4..f967708ac 100644 --- a/nalgebra-sparse/tests/unit_tests/csr.rs +++ b/nalgebra-sparse/tests/unit_tests/csr.rs @@ -620,6 +620,42 @@ fn csr_matrix_row_iter() { } } +#[test] +fn csr_matrix_serde_roundtrip() { + let offsets = vec![0, 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.clone(), indices.clone(), values.clone()) + .unwrap(); + let serialized = serde_json::to_string(&matrix).unwrap(); + let deserialized = serde_json::from_str::>(&serialized).unwrap(); + assert_eq!(matrix, deserialized); +} + +#[test] +#[should_panic(expected = "Pattern non-zero count doesn't match number of values")] +fn csr_matrix_serde() { + let offsets = vec![0, 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.clone(), indices.clone(), values.clone()) + .unwrap(); + let mut serialized = serde_json::to_value(&matrix).unwrap(); + let obj = serialized.as_object_mut().unwrap(); + // Patch the JSON to introduce + let values_array = obj + .get_mut("cs") + .unwrap() + .get_mut("values") + .unwrap() + .as_array_mut() + .unwrap(); + values_array.pop(); + let _deserialized = serde_json::from_value::>(serialized).unwrap(); +} + proptest! { #[test] fn csr_double_transpose_is_identity(csr in csr_strategy()) { From 0bcb3b399c809da70b8d1309fc8850ed85648203 Mon Sep 17 00:00:00 2001 From: Paul Jakob Schroeder Date: Tue, 9 Nov 2021 16:37:37 -0500 Subject: [PATCH 6/6] Add serde for CooMatrix --- nalgebra-sparse/src/coo.rs | 34 +++++++++++++++++++++++++ nalgebra-sparse/tests/unit_tests/coo.rs | 13 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 34e5ceecc..a4d76b634 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -2,6 +2,9 @@ use crate::SparseFormatError; +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + /// 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` @@ -37,6 +40,11 @@ use crate::SparseFormatError; /// let csc = CscMatrix::from(&coo); /// ``` #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "serde-serialize", + derive(Serialize, Deserialize), + serde(remote = "Self") +)] pub struct CooMatrix { nrows: usize, ncols: usize, @@ -273,3 +281,29 @@ impl CooMatrix { (self.row_indices, self.col_indices, self.values) } } + +impl Serialize for CooMatrix { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Self::serialize(self, serializer) + } +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for CooMatrix { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let unchecked = Self::deserialize(deserializer)?; + Self::try_from_triplets( + unchecked.nrows, + unchecked.ncols, + unchecked.row_indices, + unchecked.col_indices, + unchecked.values, + ) + .map_err(|e| serde::de::Error::custom(e.to_string())) + } +} diff --git a/nalgebra-sparse/tests/unit_tests/coo.rs b/nalgebra-sparse/tests/unit_tests/coo.rs index c70c5f97a..8fc0152e2 100644 --- a/nalgebra-sparse/tests/unit_tests/coo.rs +++ b/nalgebra-sparse/tests/unit_tests/coo.rs @@ -344,3 +344,16 @@ fn coo_push_matrix_out_of_bounds_entries() { assert_panics!(CooMatrix::new(3, 3).push_matrix(2, 2, &inserted)); } } + +#[test] +fn coo_serde() { + // Arbitrary matrix, with duplicates + let i = vec![0, 1, 0, 0, 0, 0, 2, 1]; + let j = vec![0, 2, 0, 1, 0, 3, 3, 2]; + let v = vec![2, 3, 4, 7, 1, 3, 1, 5]; + let coo = CooMatrix::::try_from_triplets(3, 5, i.clone(), j.clone(), v.clone()).unwrap(); + + let serialized = serde_json::to_string(&coo).unwrap(); + let deserialized = serde_json::from_str::>(&serialized).unwrap(); + assert_eq!(coo, deserialized); +}