diff --git a/nalgebra-sparse/src/convert/impl_std_ops.rs b/nalgebra-sparse/src/convert/impl_std_ops.rs new file mode 100644 index 00000000..1ba52bd0 --- /dev/null +++ b/nalgebra-sparse/src/convert/impl_std_ops.rs @@ -0,0 +1,26 @@ +use crate::coo::CooMatrix; +use crate::convert::serial::{convert_dense_coo, convert_coo_dense}; +use nalgebra::{Matrix, Scalar, Dim, ClosedAdd, DMatrix}; +use nalgebra::storage::{Storage}; +use num_traits::Zero; + +impl<'a, T, R, C, S> From<&'a Matrix> for CooMatrix +where + T: Scalar + Zero, + R: Dim, + C: Dim, + S: Storage +{ + fn from(matrix: &'a Matrix) -> Self { + convert_dense_coo(matrix) + } +} + +impl<'a, T> From<&'a CooMatrix> for DMatrix +where + T: Scalar + Zero + ClosedAdd, +{ + fn from(coo: &'a CooMatrix) -> Self { + convert_coo_dense(coo) + } +} \ No newline at end of file diff --git a/nalgebra-sparse/src/convert/mod.rs b/nalgebra-sparse/src/convert/mod.rs new file mode 100644 index 00000000..cf627dfa --- /dev/null +++ b/nalgebra-sparse/src/convert/mod.rs @@ -0,0 +1,5 @@ +//! TODO + +pub mod serial; + +mod impl_std_ops; diff --git a/nalgebra-sparse/src/convert/serial.rs b/nalgebra-sparse/src/convert/serial.rs new file mode 100644 index 00000000..e8121b30 --- /dev/null +++ b/nalgebra-sparse/src/convert/serial.rs @@ -0,0 +1,48 @@ +//! TODO +use nalgebra::{DMatrix, Scalar, Matrix, Dim}; +use crate::coo::CooMatrix; +use crate::csr::CsrMatrix; +use num_traits::Zero; +use std::ops::{Add, AddAssign}; +use nalgebra::storage::Storage; + +/// TODO +pub fn convert_dense_coo(dense: &Matrix) -> CooMatrix +where + T: Scalar + Zero, + R: Dim, + C: Dim, + S: Storage +{ + let mut coo = CooMatrix::new(dense.nrows(), dense.ncols()); + + for (index, v) in dense.iter().enumerate() { + if v != &T::zero() { + // We use the fact that matrix iteration is guaranteed to be column-major + let i = index % dense.nrows(); + let j = index / dense.nrows(); + coo.push(i, j, v.inlined_clone()); + } + } + + coo +} + +/// TODO +/// +/// TODO: What should the actual trait bounds be? +pub fn convert_coo_dense(coo: &CooMatrix) -> DMatrix +where + T: Scalar + Zero + Add + AddAssign, +{ + let mut output = DMatrix::repeat(coo.nrows(), coo.ncols(), T::zero()); + for (i, j, v) in coo.triplet_iter() { + output[(i, j)] += v.inlined_clone(); + } + output +} + +/// TODO +pub fn convert_coo_csr(_coo: &CooMatrix) -> CsrMatrix { + todo!() +} \ No newline at end of file diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 9869d502..8206cb09 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -186,6 +186,8 @@ where /// Construct the dense representation of the COO matrix. /// /// Duplicate entries are summed together. + /// + /// TODO: Remove? pub fn to_dense(&self) -> DMatrix where T: ClosedAdd + Zero, diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index 53bc846a..d1376b55 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -68,6 +68,11 @@ //! - Cholesky factorization (port existing factorization from nalgebra's sparse module) //! //! +//! TODO: Write docs on the following: +//! +//! - Overall design ("easy API" vs. "expert" API etc.) +//! - Conversions (From, explicit "expert" API etc.) +//! - Matrix ops design #![deny(non_camel_case_types)] #![deny(unused_parens)] #![deny(non_upper_case_globals)] @@ -80,6 +85,7 @@ pub mod csc; pub mod csr; pub mod pattern; pub mod ops; +pub mod convert; #[cfg(feature = "proptest-support")] pub mod proptest; diff --git a/nalgebra-sparse/tests/unit_tests/convert_serial.rs b/nalgebra-sparse/tests/unit_tests/convert_serial.rs new file mode 100644 index 00000000..21a93364 --- /dev/null +++ b/nalgebra-sparse/tests/unit_tests/convert_serial.rs @@ -0,0 +1,84 @@ +use nalgebra_sparse::coo::CooMatrix; +use nalgebra_sparse::convert::serial::{convert_coo_dense, convert_dense_coo}; +use nalgebra_sparse::proptest::coo_with_duplicates; +use nalgebra::proptest::matrix; +use proptest::prelude::*; +use nalgebra::DMatrix; + +#[test] +fn test_convert_dense_coo() { + // No duplicates + { + #[rustfmt::skip] + let entries = &[1, 0, 3, + 0, 5, 0]; + // The COO representation of a dense matrix is not unique. + // Here we implicitly test that the coo matrix is indeed constructed from column-major + // iteration of the dense matrix. + let dense = DMatrix::from_row_slice(2, 3, entries); + let coo = CooMatrix::try_from_triplets(2, 3, vec![0, 1, 0], vec![0, 1, 2], vec![1, 5, 3]) + .unwrap(); + + assert_eq!(CooMatrix::from(&dense), coo); + assert_eq!(DMatrix::from(&coo), dense); + } + + // Duplicates + // No duplicates + { + #[rustfmt::skip] + let entries = &[1, 0, 3, + 0, 5, 0]; + // The COO representation of a dense matrix is not unique. + // Here we implicitly test that the coo matrix is indeed constructed from column-major + // iteration of the dense matrix. + let dense = DMatrix::from_row_slice(2, 3, entries); + let coo_no_dup = CooMatrix::try_from_triplets(2, 3, + vec![0, 1, 0], + vec![0, 1, 2], + vec![1, 5, 3]) + .unwrap(); + let coo_dup = CooMatrix::try_from_triplets(2, 3, + vec![0, 1, 0, 1], + vec![0, 1, 2, 1], + vec![1, -2, 3, 7]) + .unwrap(); + + assert_eq!(CooMatrix::from(&dense), coo_no_dup); + assert_eq!(DMatrix::from(&coo_dup), dense); + } +} + +fn coo_strategy() -> impl Strategy> { + coo_with_duplicates(-5 ..= 5, 0..=6usize, 0..=6usize, 40, 2) +} + +proptest! { + + #[test] + fn convert_dense_coo_roundtrip(dense in matrix(-5 ..= 5, 0 ..=6, 0..=6)) { + let coo = convert_dense_coo(&dense); + let dense2 = convert_coo_dense(&coo); + prop_assert_eq!(&dense, &dense2); + } + + #[test] + fn convert_coo_dense_coo_roundtrip(coo in coo_strategy()) { + // We cannot compare the result of the roundtrip coo -> dense -> coo directly for + // two reasons: + // 1. the COO matrices will generally have different ordering of elements + // 2. explicitly stored zero entries in the original matrix will be discarded + // when converting back to COO + // Therefore we instead compare the results of converting the COO matrix + // at the end of the roundtrip with its dense representation + let dense = convert_coo_dense(&coo); + let coo2 = convert_dense_coo(&dense); + let dense2 = convert_coo_dense(&coo2); + prop_assert_eq!(dense, dense2); + } + + #[test] + fn from_dense_coo_roundtrip(dense in matrix(-5..=5, 0..=6, 0..=6)) { + prop_assert_eq!(&dense, &DMatrix::from(&CooMatrix::from(&dense))); + } +} \ 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 f5b6d935..cb32cd71 100644 --- a/nalgebra-sparse/tests/unit_tests/mod.rs +++ b/nalgebra-sparse/tests/unit_tests/mod.rs @@ -3,4 +3,5 @@ mod ops; mod pattern; mod csr; mod csc; +mod convert_serial; mod proptest; \ No newline at end of file