From 4a6022d9bfa3154bcb87311351fb5d71639a73a6 Mon Sep 17 00:00:00 2001 From: Nathan Kent Date: Wed, 19 Aug 2020 01:52:26 -0400 Subject: [PATCH 1/5] Add methods for in-place reshaping of matrices There are two major additions in this commit. The first is a new storage trait, `ReshapableStorage`, that can be implemented for storage types that can be reshaped in-place. I have implemented this for both the `ArrayStorage` and `VecStorage` types, as they are the most common and they are just interpretations of a flat list. The second is a `Matrix::reshape_generic` method that allows matrices to be in-place reshaped provided that the underlying storage can handle it. In practice, this means that the standard matrix types (`MatrixMN` and `DMatrix`) can be resized to any size that has the same element count. Resizing between array and vector storage is not implemented due to `Storage` only being implemented for `VecStorage` variants where at least one dimension is `Dynamic`. Additionally, only the generic reshape function is added as it can be a basis for other reshaping functions (see the resizing functions) and I am not particularly in the mood to implement a variety of reshaping methods. --- examples/matrix_construction.rs | 2 +- examples/reshaping.rs | 33 ++++++++++++++ src/base/array_storage.rs | 23 +++++++++- src/base/edition.rs | 29 ++++++++++++- src/base/storage.rs | 20 ++++++++- src/base/vec_storage.rs | 76 ++++++++++++++++++++++++++++++++- 6 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 examples/reshaping.rs diff --git a/examples/matrix_construction.rs b/examples/matrix_construction.rs index bb78458f..06f4c30b 100644 --- a/examples/matrix_construction.rs +++ b/examples/matrix_construction.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DMatrix, Matrix2x3, RowVector3, Vector2}; +use na::{DMatrix, Matrix2x3, Matrix3x2, RowVector3, Vector2}; fn main() { // All the following matrices are equal but constructed in different ways. diff --git a/examples/reshaping.rs b/examples/reshaping.rs new file mode 100644 index 00000000..bf02e769 --- /dev/null +++ b/examples/reshaping.rs @@ -0,0 +1,33 @@ +extern crate nalgebra as na; + +use na::{DMatrix, Dynamic, Matrix2x3, Matrix3x2, U2, U3}; + +fn main() { + // Matrices can be reshaped in-place without moving or copying values. + let m1 = Matrix2x3::new(1.1, 1.2, 1.3, 2.1, 2.2, 2.3); + let m2 = Matrix3x2::new(1.1, 2.2, 2.1, 1.3, 1.2, 2.3); + + let m3 = m1.reshape_generic(U3, U2); + assert_eq!(m3, m2); + + // Note that, for statically sized matrices, invalid reshapes will not compile: + //let m4 = m3.reshape_generic(U3, U3); + + // If dynamically sized matrices are used, the reshaping is checked at run-time. + let dm1 = DMatrix::from_vec( + 4, + 3, + vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + ); + let dm2 = DMatrix::from_vec( + 6, + 2, + vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + ); + + let dm3 = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); + assert_eq!(dm3, dm2); + + // Invalid reshapings of dynamic matrices will panic at run-time. + //let dm4 = dm3.reshape_generic(Dynamic::new(6), Dynamic::new(6)); +} diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index 1743f06b..b86e3a80 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -24,7 +24,9 @@ use typenum::Prod; use crate::base::allocator::Allocator; use crate::base::default_allocator::DefaultAllocator; use crate::base::dimension::{DimName, U1}; -use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut}; +use crate::base::storage::{ + ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut, +}; use crate::base::Scalar; /* @@ -267,6 +269,25 @@ where { } +impl ReshapableStorage for ArrayStorage +where + N: Scalar, + R1: DimName, + C1: DimName, + R1::Value: Mul, + Prod: ArrayLength, + R2: DimName, + C2: DimName, + R2::Value: Mul>, + Prod: ArrayLength, +{ + type Output = ArrayStorage; + + fn reshape_generic(self, _: R2, _: C2) -> Self::Output { + ArrayStorage { data: self.data } + } +} + /* * * Allocation-less serde impls. diff --git a/src/base/edition.rs b/src/base/edition.rs index b3133648..896e6635 100644 --- a/src/base/edition.rs +++ b/src/base/edition.rs @@ -13,7 +13,7 @@ use crate::base::dimension::Dynamic; use crate::base::dimension::{ Dim, DimAdd, DimDiff, DimMin, DimMinimum, DimName, DimSub, DimSum, U1, }; -use crate::base::storage::{Storage, StorageMut}; +use crate::base::storage::{ReshapableStorage, Storage, StorageMut}; #[cfg(any(feature = "std", feature = "alloc"))] use crate::base::DMatrix; use crate::base::{DefaultAllocator, Matrix, MatrixMN, RowVector, Scalar, Vector}; @@ -745,7 +745,7 @@ impl> Matrix { self.resize_generic(R2::name(), C2::name(), val) } - /// Resizes `self` such that it has dimensions `new_nrows × now_ncols`. + /// Resizes `self` such that it has dimensions `new_nrows × new_ncols`. /// /// The values are copied such that `self[(i, j)] == result[(i, j)]`. If the result has more /// rows and/or columns than `self`, then the extra rows or columns are filled with `val`. @@ -813,6 +813,31 @@ impl> Matrix { } } +impl Matrix +where + N: Scalar, + R: Dim, + C: Dim, +{ + /// Reshapes `self` in-place such that it has dimensions `new_nrows × new_ncols`. + /// + /// The values are not copied or moved. This function will panic if dynamic sizes are provided + /// and not compatible. + pub fn reshape_generic( + self, + new_nrows: R2, + new_ncols: C2, + ) -> Matrix + where + R2: Dim, + C2: Dim, + S: ReshapableStorage, + { + let data = self.data.reshape_generic(new_nrows, new_ncols); + Matrix::from_data(data) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl DMatrix { /// Resizes this matrix in-place. diff --git a/src/base/storage.rs b/src/base/storage.rs index 9f039ba2..7b197861 100644 --- a/src/base/storage.rs +++ b/src/base/storage.rs @@ -171,7 +171,7 @@ pub unsafe trait StorageMut: Storage { /// A matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorage: @@ -181,10 +181,26 @@ pub unsafe trait ContiguousStorage: /// A mutable matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorageMut: ContiguousStorage + StorageMut { } + +/// A matrix storage that can be reshaped in-place. +pub trait ReshapableStorage: Storage +where + N: Scalar, + R1: Dim, + C1: Dim, + R2: Dim, + C2: Dim, +{ + /// The reshaped storage type. + type Output: Storage; + + /// Reshapes the storage into the output storage type. + fn reshape_generic(self, nrows: R2, ncols: C2) -> Self::Output; +} diff --git a/src/base/vec_storage.rs b/src/base/vec_storage.rs index 40909c32..37f31213 100644 --- a/src/base/vec_storage.rs +++ b/src/base/vec_storage.rs @@ -8,7 +8,9 @@ use crate::base::allocator::Allocator; use crate::base::constraint::{SameNumberOfRows, ShapeConstraint}; use crate::base::default_allocator::DefaultAllocator; use crate::base::dimension::{Dim, DimName, Dynamic, U1}; -use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut}; +use crate::base::storage::{ + ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut, +}; use crate::base::{Scalar, Vector}; #[cfg(feature = "abomonation-serialize")] @@ -225,6 +227,42 @@ unsafe impl ContiguousStorageMut for VecStorag { } +impl ReshapableStorage for VecStorage +where + N: Scalar, + C1: Dim, + C2: Dim, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + +impl ReshapableStorage for VecStorage +where + N: Scalar, + C1: Dim, + R2: DimName, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + unsafe impl StorageMut for VecStorage where DefaultAllocator: Allocator, @@ -240,6 +278,42 @@ where } } +impl ReshapableStorage for VecStorage +where + N: Scalar, + R1: DimName, + C2: Dim, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + +impl ReshapableStorage for VecStorage +where + N: Scalar, + R1: DimName, + R2: DimName, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + #[cfg(feature = "abomonation-serialize")] impl Abomonation for VecStorage { unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { From 5b3da9e2ebac1cd2d31c98905acb29caf126188e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:02:31 +0100 Subject: [PATCH 2/5] Fix typo in comment. --- src/base/storage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/storage.rs b/src/base/storage.rs index 7b197861..598cb061 100644 --- a/src/base/storage.rs +++ b/src/base/storage.rs @@ -171,7 +171,7 @@ pub unsafe trait StorageMut: Storage { /// A matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorage: @@ -181,7 +181,7 @@ pub unsafe trait ContiguousStorage: /// A mutable matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorageMut: From 7af509ee8d76e4710b4deab652f32047aa61d8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:02:43 +0100 Subject: [PATCH 3/5] Reformat the reshaping example. --- examples/reshaping.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/examples/reshaping.rs b/examples/reshaping.rs index bf02e769..f164e192 100644 --- a/examples/reshaping.rs +++ b/examples/reshaping.rs @@ -1,11 +1,20 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] + extern crate nalgebra as na; use na::{DMatrix, Dynamic, Matrix2x3, Matrix3x2, U2, U3}; fn main() { // Matrices can be reshaped in-place without moving or copying values. - let m1 = Matrix2x3::new(1.1, 1.2, 1.3, 2.1, 2.2, 2.3); - let m2 = Matrix3x2::new(1.1, 2.2, 2.1, 1.3, 1.2, 2.3); + let m1 = Matrix2x3::new( + 1.1, 1.2, 1.3, + 2.1, 2.2, 2.3 + ); + let m2 = Matrix3x2::new( + 1.1, 2.2, + 2.1, 1.3, + 1.2, 2.3 + ); let m3 = m1.reshape_generic(U3, U2); assert_eq!(m3, m2); @@ -14,15 +23,23 @@ fn main() { //let m4 = m3.reshape_generic(U3, U3); // If dynamically sized matrices are used, the reshaping is checked at run-time. - let dm1 = DMatrix::from_vec( + let dm1 = DMatrix::from_row_slice( 4, 3, - vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + &[ + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ], ); - let dm2 = DMatrix::from_vec( + let dm2 = DMatrix::from_row_slice( 6, 2, - vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + &[ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + ], ); let dm3 = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); From d7cb138e2269806397149a45be0a8bdc26a3bbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:03:07 +0100 Subject: [PATCH 4/5] Fix warnings. --- examples/matrix_construction.rs | 2 +- tests/linalg/exp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/matrix_construction.rs b/examples/matrix_construction.rs index 06f4c30b..bb78458f 100644 --- a/examples/matrix_construction.rs +++ b/examples/matrix_construction.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DMatrix, Matrix2x3, Matrix3x2, RowVector3, Vector2}; +use na::{DMatrix, Matrix2x3, RowVector3, Vector2}; fn main() { // All the following matrices are equal but constructed in different ways. diff --git a/tests/linalg/exp.rs b/tests/linalg/exp.rs index 93859934..f5b5243a 100644 --- a/tests/linalg/exp.rs +++ b/tests/linalg/exp.rs @@ -129,7 +129,7 @@ mod tests { #[test] fn exp_complex() { - use nalgebra::{Complex, ComplexField, DMatrix, DVector, Matrix2, RealField}; + use nalgebra::{Complex, DMatrix, DVector, Matrix2, RealField}; { let z = Matrix2::>::zeros(); From e89a26cbd02cd08e280f9db4471909ccec3b0b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:03:18 +0100 Subject: [PATCH 5/5] Add doc-tests for reshape_generic. --- src/base/edition.rs | 51 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/base/edition.rs b/src/base/edition.rs index 896e6635..06f64f7a 100644 --- a/src/base/edition.rs +++ b/src/base/edition.rs @@ -819,10 +819,55 @@ where R: Dim, C: Dim, { - /// Reshapes `self` in-place such that it has dimensions `new_nrows × new_ncols`. + /// Reshapes `self` such that it has dimensions `new_nrows × new_ncols`. /// - /// The values are not copied or moved. This function will panic if dynamic sizes are provided - /// and not compatible. + /// This will reinterpret `self` as if it is a matrix with `new_nrows` rows and `new_ncols` + /// columns. The arrangements of the component in the output matrix are the same as what + /// would be obtained by `Matrix::from_slice_generic(self.as_slice(), new_nrows, new_ncols)`. + /// + /// If `self` is a dynamically-sized matrix, then its components are neither copied nor moved. + /// If `self` is staticyll-sized, then a copy may happen in some situations. + /// This function will panic if the given dimensions are such that the number of elements of + /// the input matrix are not equal to the number of elements of the output matrix. + /// + /// # Examples + /// + /// ``` + /// # use nalgebra::{Matrix3x2, Matrix2x3, DMatrix, U2, U3, Dynamic}; + /// + /// let m1 = Matrix2x3::new( + /// 1.1, 1.2, 1.3, + /// 2.1, 2.2, 2.3 + /// ); + /// let m2 = Matrix3x2::new( + /// 1.1, 2.2, + /// 2.1, 1.3, + /// 1.2, 2.3 + /// ); + /// let reshaped = m1.reshape_generic(U3, U2); + /// assert_eq!(reshaped, m2); + /// + /// let dm1 = DMatrix::from_row_slice( + /// 4, + /// 3, + /// &[ + /// 1.0, 0.0, 0.0, + /// 0.0, 0.0, 1.0, + /// 0.0, 0.0, 0.0, + /// 0.0, 1.0, 0.0 + /// ], + /// ); + /// let dm2 = DMatrix::from_row_slice( + /// 6, + /// 2, + /// &[ + /// 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + /// 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + /// ], + /// ); + /// let reshaped = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); + /// assert_eq!(reshaped, dm2); + /// ``` pub fn reshape_generic( self, new_nrows: R2,