diff --git a/.github/workflows/nalgebra-ci-build.yml b/.github/workflows/nalgebra-ci-build.yml index 3a56df13..c00b6cbc 100644 --- a/.github/workflows/nalgebra-ci-build.yml +++ b/.github/workflows/nalgebra-ci-build.yml @@ -38,8 +38,12 @@ jobs: run: cargo build --features serde-serialize - name: Build nalgebra-lapack run: cd nalgebra-lapack; cargo build; - - name: Build nalgebra-sparse + - name: Build nalgebra-sparse --no-default-features + run: cd nalgebra-sparse; cargo build --no-default-features; + - name: Build nalgebra-sparse (default features) run: cd nalgebra-sparse; cargo build; + - name: Build nalgebra-sparse --all-features + run: cd nalgebra-sparse; cargo build --all-features; # Run this on it’s own job because it alone takes a lot of time. # So it’s best to let it run in parallel to the other jobs. build-nalgebra-all-features: @@ -57,13 +61,13 @@ jobs: steps: - uses: actions/checkout@v2 - name: test - run: cargo test --features arbitrary,rand,serde-serialize,abomonation-serialize,sparse,debug,io,compare,libm,proptest-support,slow-tests; + run: cargo test --features arbitrary,rand,serde-serialize,sparse,debug,io,compare,libm,proptest-support,slow-tests; test-nalgebra-glm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: test nalgebra-glm - run: cargo test -p nalgebra-glm --features arbitrary,serde-serialize,abomonation-serialize; + run: cargo test -p nalgebra-glm --features arbitrary,serde-serialize; test-nalgebra-sparse: runs-on: ubuntu-latest steps: @@ -71,10 +75,10 @@ jobs: - name: test nalgebra-sparse # Manifest-path is necessary because cargo otherwise won't correctly forward features # We increase number of proptest cases to hopefully catch more potential bugs - run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io + run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize - name: test nalgebra-sparse (slow tests) # Unfortunately, the "slow-tests" take so much time that we need to run them with --release - run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,slow-tests slow + run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,serde-serialize,slow-tests slow test-nalgebra-macros: runs-on: ubuntu-latest steps: @@ -120,6 +124,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: Jimver/cuda-toolkit@v0.2.4 + with: + cuda: '11.2.2' - name: Install nightly-2021-12-04 uses: actions-rs/toolchain@v1 with: @@ -128,4 +134,6 @@ jobs: - uses: actions/checkout@v2 - run: rustup target add nvptx64-nvidia-cuda - run: cargo build --no-default-features --features cuda - - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda \ No newline at end of file + - run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda + env: + CUDA_ARCH: "350" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 88006c2e..c00c01fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,42 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.31.0] (30 Apr. 2022) + +### Breaking changes +- Switch to `cust` 0.3 (for CUDA support). +- Switch to `rkyv` 0.7 +- Remove support for serialization based on `abomonation`. +- Remove support for conversions between `nalgebra` types and `glam` 0.13. + +### Modified +- The aliases for `Const` types have been simplified to help `rust-analyzer`. + +### Added +- Add `TryFrom` conversion between `UnitVector2/3/4` and `glam`’s `Vec2/3/4`. +- `nalgebra-sparse`: added support for serialization of sparse matrices with `serde`. +- `nalgebra-sparse`: add a CSC matrix constructor from unsorted (but valid) data. +- `nalgebra-lapack`: add generalized eigenvalues/eigenvectors calculation + QZ decomposition. + +### Fixed +- Improve stability of SVD. +- Fix slerp for `UnitComplex`. + +## [0.30.1] (09 Jan. 2022) + +### Added +- Add conversion from/to types of `glam` 0.19 and 0.20. + ## [0.30.0] (02 Jan. 2022) ### Breaking changes - The `Dim` trait is now marked as unsafe. - The `Matrix::pow` and `Matrix::pow_mut` methods only allow positive integer exponents now. To compute negative exponents, the user is free to invert the matrix before calling `pow` with the exponent’s absolute value. +- Remove the `Bounded` requirement from `RealField`. Replace it by methods returning `Option` so that they can + still be implemented by unbounded types (by returning `None`). +- The `ComplexField` trait derives from `FromPrimitive` again. We can actually keep this because all its methods + return `Option`, meaning that it could be implemented by any type. ### Modified - Use more concise debug impls for matrices and geometric transformation types. diff --git a/Cargo.toml b/Cargo.toml index 8294398e..732676ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.30.0" +version = "0.31.0" authors = [ "Sébastien Crozet " ] description = "General-purpose linear algebra library with transformations and statically-sized or dynamically-sized matrices." @@ -32,17 +32,18 @@ compare = [ "matrixcompare-core" ] libm = [ "simba/libm" ] libm-force = [ "simba/libm_force" ] macros = [ "nalgebra-macros" ] -cuda = [ "cust", "simba/cuda" ] +cuda = [ "cust_core", "simba/cuda" ] # Conversion convert-mint = [ "mint" ] convert-bytemuck = [ "bytemuck" ] -convert-glam013 = [ "glam013" ] convert-glam014 = [ "glam014" ] convert-glam015 = [ "glam015" ] convert-glam016 = [ "glam016" ] convert-glam017 = [ "glam017" ] convert-glam018 = [ "glam018" ] +convert-glam019 = [ "glam019" ] +convert-glam020 = [ "glam020" ] # Serialization ## To use serde in a #[no-std] environment, enable the @@ -51,9 +52,8 @@ convert-glam018 = [ "glam018" ] ## `serde-serialize`. serde-serialize-no-std = [ "serde", "num-complex/serde" ] serde-serialize = [ "serde-serialize-no-std", "serde/std" ] -abomonation-serialize = [ "abomonation" ] rkyv-serialize-no-std = [ "rkyv" ] -rkyv-serialize = [ "rkyv-serialize-no-std", "rkyv/std" ] +rkyv-serialize = [ "rkyv-serialize-no-std", "rkyv/std", "bytecheck" ] # Randomness ## To use rand in a #[no-std] environment, enable the @@ -79,8 +79,8 @@ alga = { version = "0.9", default-features = false, optional = true } rand_distr = { version = "0.4", default-features = false, optional = true } matrixmultiply = { version = "0.3", optional = true } serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } -abomonation = { version = "0.7", optional = true } -rkyv = { version = "~0.6.4", default-features = false, features = ["const_generics"], optional = true } +rkyv = { version = "~0.7.1", optional = true } +bytecheck = { version = "~0.6.1", optional = true } mint = { version = "0.5", optional = true } quickcheck = { version = "1", optional = true } pest = { version = "2", optional = true } @@ -88,15 +88,14 @@ pest_derive = { version = "2", optional = true } bytemuck = { version = "1.5", optional = true } matrixcompare-core = { version = "0.1", optional = true } proptest = { version = "1", optional = true, default-features = false, features = ["std"] } -glam013 = { package = "glam", version = "0.13", optional = true } glam014 = { package = "glam", version = "0.14", optional = true } glam015 = { package = "glam", version = "0.15", optional = true } glam016 = { package = "glam", version = "0.16", optional = true } glam017 = { package = "glam", version = "0.17", optional = true } glam018 = { package = "glam", version = "0.18", optional = true } - -[target.'cfg(not(target_os = "cuda"))'.dependencies] -cust = { version = "0.2", optional = true } +glam019 = { package = "glam", version = "0.19", optional = true } +glam020 = { package = "glam", version = "0.20", optional = true } +cust_core = { version = "0.1", optional = true } [dev-dependencies] diff --git a/README.md b/README.md index fa1e0904..62ab4759 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ And our gold sponsors:

- + + + +

\ No newline at end of file diff --git a/examples/cargo/Cargo.toml b/examples/cargo/Cargo.toml index 501ae23e..63e70aab 100644 --- a/examples/cargo/Cargo.toml +++ b/examples/cargo/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" authors = [ "You" ] [dependencies] -nalgebra = "0.30.0" +nalgebra = "0.31.0" [[bin]] name = "example" diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index 287bb8c7..e700af37 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-glm" -version = "0.16.0" +version = "0.17.0" authors = ["sebcrozet "] description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library." @@ -21,13 +21,11 @@ default = [ "std" ] std = [ "nalgebra/std", "simba/std" ] arbitrary = [ "nalgebra/arbitrary" ] serde-serialize = [ "nalgebra/serde-serialize-no-std" ] -abomonation-serialize = [ "nalgebra/abomonation-serialize" ] cuda = [ "nalgebra/cuda" ] # Conversion convert-mint = [ "nalgebra/mint" ] convert-bytemuck = [ "nalgebra/bytemuck" ] -convert-glam013 = [ "nalgebra/glam013" ] convert-glam014 = [ "nalgebra/glam014" ] convert-glam015 = [ "nalgebra/glam015" ] convert-glam016 = [ "nalgebra/glam016" ] @@ -38,4 +36,4 @@ convert-glam018 = [ "nalgebra/glam018" ] num-traits = { version = "0.2", default-features = false } approx = { version = "0.5", default-features = false } simba = { version = "0.7", default-features = false } -nalgebra = { path = "..", version = "0.30", default-features = false } +nalgebra = { path = "..", version = "0.31", default-features = false } diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index 7cdf9f78..91517a8d 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-lapack" -version = "0.21.0" +version = "0.22.0" authors = [ "Sébastien Crozet ", "Andrew Straw " ] description = "Matrix decompositions using nalgebra matrices and Lapack bindings." @@ -29,7 +29,7 @@ accelerate = ["lapack-src/accelerate"] intel-mkl = ["lapack-src/intel-mkl"] [dependencies] -nalgebra = { version = "0.30", path = ".." } +nalgebra = { version = "0.31", path = ".." } num-traits = "0.2" num-complex = { version = "0.4", default-features = false } simba = "0.7" @@ -39,7 +39,7 @@ lapack-src = { version = "0.8", default-features = false } # clippy = "*" [dev-dependencies] -nalgebra = { version = "0.30", features = [ "arbitrary", "rand" ], path = ".." } +nalgebra = { version = "0.31", features = [ "arbitrary", "rand" ], path = ".." } proptest = { version = "1", default-features = false, features = ["std"] } quickcheck = "1" approx = "0.5" diff --git a/nalgebra-lapack/src/generalized_eigenvalues.rs b/nalgebra-lapack/src/generalized_eigenvalues.rs new file mode 100644 index 00000000..5d1e3ace --- /dev/null +++ b/nalgebra-lapack/src/generalized_eigenvalues.rs @@ -0,0 +1,350 @@ +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + +use num::Zero; +use num_complex::Complex; + +use simba::scalar::RealField; + +use crate::ComplexHelper; +use na::allocator::Allocator; +use na::dimension::{Const, Dim}; +use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; + +use lapack; + +/// Generalized eigenvalues and generalized eigenvectors (left and right) of a pair of N*N real square matrices. +/// +/// Each generalized eigenvalue (lambda) satisfies determinant(A - lambda*B) = 0 +/// +/// The right eigenvector v(j) corresponding to the eigenvalue lambda(j) +/// of (A,B) satisfies +/// +/// A * v(j) = lambda(j) * B * v(j). +/// +/// The left eigenvector u(j) corresponding to the eigenvalue lambda(j) +/// of (A,B) satisfies +/// +/// u(j)**H * A = lambda(j) * u(j)**H * B . +/// where u(j)**H is the conjugate-transpose of u(j). +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(serialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Serialize") + ) +)] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(deserialize = "DefaultAllocator: Allocator + Allocator, + OVector: Deserialize<'de>, + OMatrix: Deserialize<'de>") + ) +)] +#[derive(Clone, Debug)] +pub struct GeneralizedEigen +where + DefaultAllocator: Allocator + Allocator, +{ + alphar: OVector, + alphai: OVector, + beta: OVector, + vsl: OMatrix, + vsr: OMatrix, +} + +impl Copy for GeneralizedEigen +where + DefaultAllocator: Allocator + Allocator, + OMatrix: Copy, + OVector: Copy, +{ +} + +impl GeneralizedEigen +where + DefaultAllocator: Allocator + Allocator, +{ + /// Attempts to compute the generalized eigenvalues, and left and right associated eigenvectors + /// via the raw returns from LAPACK's dggev and sggev routines + /// + /// Panics if the method did not converge. + pub fn new(a: OMatrix, b: OMatrix) -> Self { + Self::try_new(a, b).expect("Calculation of generalized eigenvalues failed.") + } + + /// Attempts to compute the generalized eigenvalues (and eigenvectors) via the raw returns from LAPACK's + /// dggev and sggev routines + /// + /// Returns `None` if the method did not converge. + pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { + assert!( + a.is_square() && b.is_square(), + "Unable to compute the generalized eigenvalues of non-square matrices." + ); + + assert!( + a.shape_generic() == b.shape_generic(), + "Unable to compute the generalized eigenvalues of two square matrices of different dimensions." + ); + + let (nrows, ncols) = a.shape_generic(); + let n = nrows.value(); + + let mut info = 0; + + let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); + let mut alphai = Matrix::zeros_generic(nrows, Const::<1>); + let mut beta = Matrix::zeros_generic(nrows, Const::<1>); + let mut vsl = Matrix::zeros_generic(nrows, ncols); + let mut vsr = Matrix::zeros_generic(nrows, ncols); + + let lwork = T::xggev_work_size( + b'V', + b'V', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut info, + ); + lapack_check!(info); + + let mut work = vec![T::zero(); lwork as usize]; + + T::xggev( + b'V', + b'V', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut work, + lwork, + &mut info, + ); + lapack_check!(info); + + Some(GeneralizedEigen { + alphar, + alphai, + beta, + vsl, + vsr, + }) + } + + /// Calculates the generalized eigenvectors (left and right) associated with the generalized eigenvalues + /// + /// Outputs two matrices. + /// The first output matrix contains the left eigenvectors of the generalized eigenvalues + /// as columns. + /// The second matrix contains the right eigenvectors of the generalized eigenvalues + /// as columns. + pub fn eigenvectors(&self) -> (OMatrix, D, D>, OMatrix, D, D>) + where + DefaultAllocator: + Allocator, D, D> + Allocator, D> + Allocator<(Complex, T), D>, + { + /* + How the eigenvectors are built up: + + Since the input entries are all real, the generalized eigenvalues if complex come in pairs + as a consequence of the [complex conjugate root thorem](https://en.wikipedia.org/wiki/Complex_conjugate_root_theorem) + The Lapack routine output reflects this by expecting the user to unpack the real and complex eigenvalues associated + eigenvectors from the real matrix output via the following procedure + + (Note: VL stands for the lapack real matrix output containing the left eigenvectors as columns, + VR stands for the lapack real matrix output containing the right eigenvectors as columns) + + If the j-th and (j+1)-th eigenvalues form a complex conjugate pair, + then + + u(j) = VL(:,j)+i*VL(:,j+1) + u(j+1) = VL(:,j)-i*VL(:,j+1) + + and + + u(j) = VR(:,j)+i*VR(:,j+1) + v(j+1) = VR(:,j)-i*VR(:,j+1). + */ + + let n = self.vsl.shape().0; + + let mut l = self.vsl.map(|x| Complex::new(x, T::RealField::zero())); + + let mut r = self.vsr.map(|x| Complex::new(x, T::RealField::zero())); + + let eigenvalues = self.raw_eigenvalues(); + + let mut c = 0; + + while c < n { + if eigenvalues[c].0.im.abs() != T::RealField::zero() && c + 1 < n { + // taking care of the left eigenvector matrix + l.column_mut(c).zip_apply(&self.vsl.column(c + 1), |r, i| { + *r = Complex::new(r.re.clone(), i.clone()); + }); + l.column_mut(c + 1).zip_apply(&self.vsl.column(c), |i, r| { + *i = Complex::new(r.clone(), -i.re.clone()); + }); + + // taking care of the right eigenvector matrix + r.column_mut(c).zip_apply(&self.vsr.column(c + 1), |r, i| { + *r = Complex::new(r.re.clone(), i.clone()); + }); + r.column_mut(c + 1).zip_apply(&self.vsr.column(c), |i, r| { + *i = Complex::new(r.clone(), -i.re.clone()); + }); + + c += 2; + } else { + c += 1; + } + } + + (l, r) + } + + /// Outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alphai), beta) + /// straight from LAPACK + #[must_use] + pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> + where + DefaultAllocator: Allocator<(Complex, T), D>, + { + let mut out = Matrix::from_element_generic( + self.vsl.shape_generic().0, + Const::<1>, + (Complex::zero(), T::RealField::zero()), + ); + + for i in 0..out.len() { + out[i] = (Complex::new(self.alphar[i], self.alphai[i]), self.beta[i]) + } + + out + } +} + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the RealField GeneralizedEigen decomposition. +pub trait GeneralizedEigenScalar: Scalar { + #[allow(missing_docs)] + fn xggev( + jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + work: &mut [Self], + lwork: i32, + info: &mut i32, + ); + + #[allow(missing_docs)] + fn xggev_work_size( + jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + info: &mut i32, + ) -> i32; +} + +macro_rules! generalized_eigen_scalar_impl ( + ($N: ty, $xggev: path) => ( + impl GeneralizedEigenScalar for $N { + #[inline] + fn xggev(jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + work: &mut [$N], + lwork: i32, + info: &mut i32) { + unsafe { $xggev(jobvsl, jobvsr, n, a, lda, b, ldb, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, work, lwork, info); } + } + + + #[inline] + fn xggev_work_size(jobvsl: u8, + jobvsr: u8, + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + info: &mut i32) + -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + unsafe { $xggev(jobvsl, jobvsr, n, a, lda, b, ldb, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, &mut work, lwork, info); } + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +generalized_eigen_scalar_impl!(f32, lapack::sggev); +generalized_eigen_scalar_impl!(f64, lapack::dggev); diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs index 9cf0d73d..ea2e2b53 100644 --- a/nalgebra-lapack/src/lib.rs +++ b/nalgebra-lapack/src/lib.rs @@ -83,9 +83,11 @@ mod lapack_check; mod cholesky; mod eigen; +mod generalized_eigenvalues; mod hessenberg; mod lu; mod qr; +mod qz; mod schur; mod svd; mod symmetric_eigen; @@ -94,9 +96,11 @@ use num_complex::Complex; pub use self::cholesky::{Cholesky, CholeskyScalar}; pub use self::eigen::Eigen; +pub use self::generalized_eigenvalues::GeneralizedEigen; pub use self::hessenberg::Hessenberg; pub use self::lu::{LUScalar, LU}; pub use self::qr::QR; +pub use self::qz::QZ; pub use self::schur::Schur; pub use self::svd::SVD; pub use self::symmetric_eigen::SymmetricEigen; diff --git a/nalgebra-lapack/src/qz.rs b/nalgebra-lapack/src/qz.rs new file mode 100644 index 00000000..99f3c374 --- /dev/null +++ b/nalgebra-lapack/src/qz.rs @@ -0,0 +1,321 @@ +#[cfg(feature = "serde-serialize")] +use serde::{Deserialize, Serialize}; + +use num::Zero; +use num_complex::Complex; + +use simba::scalar::RealField; + +use crate::ComplexHelper; +use na::allocator::Allocator; +use na::dimension::{Const, Dim}; +use na::{DefaultAllocator, Matrix, OMatrix, OVector, Scalar}; + +use lapack; + +/// QZ decomposition of a pair of N*N square matrices. +/// +/// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) +/// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the +/// decomposed input matrix `a` equals `VSL * S * VSL.transpose()` and +/// decomposed input matrix `b` equals `VSL * T * VSL.transpose()`. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(serialize = "DefaultAllocator: Allocator + Allocator, + OVector: Serialize, + OMatrix: Serialize") + ) +)] +#[cfg_attr( + feature = "serde-serialize", + serde( + bound(deserialize = "DefaultAllocator: Allocator + Allocator, + OVector: Deserialize<'de>, + OMatrix: Deserialize<'de>") + ) +)] +#[derive(Clone, Debug)] +pub struct QZ +where + DefaultAllocator: Allocator + Allocator, +{ + alphar: OVector, + alphai: OVector, + beta: OVector, + vsl: OMatrix, + s: OMatrix, + vsr: OMatrix, + t: OMatrix, +} + +impl Copy for QZ +where + DefaultAllocator: Allocator + Allocator, + OMatrix: Copy, + OVector: Copy, +{ +} + +impl QZ +where + DefaultAllocator: Allocator + Allocator, +{ + /// Attempts to compute the QZ decomposition of input real square matrices `a` and `b`. + /// + /// i.e retrieves the left and right matrices of Schur Vectors (VSL and VSR) + /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the + /// decomposed matrix `a` equals `VSL * S * VSL.transpose()` and + /// decomposed matrix `b` equals `VSL * T * VSL.transpose()`. + /// + /// Panics if the method did not converge. + pub fn new(a: OMatrix, b: OMatrix) -> Self { + Self::try_new(a, b).expect("QZ decomposition: convergence failed.") + } + + /// Computes the decomposition of input matrices `a` and `b` into a pair of matrices of Schur vectors + /// , a quasi-upper triangular matrix and an upper-triangular matrix . + /// + /// Returns `None` if the method did not converge. + pub fn try_new(mut a: OMatrix, mut b: OMatrix) -> Option { + assert!( + a.is_square() && b.is_square(), + "Unable to compute the qz decomposition of non-square matrices." + ); + + assert!( + a.shape_generic() == b.shape_generic(), + "Unable to compute the qz decomposition of two square matrices of different dimensions." + ); + + let (nrows, ncols) = a.shape_generic(); + let n = nrows.value(); + + let mut info = 0; + + let mut alphar = Matrix::zeros_generic(nrows, Const::<1>); + let mut alphai = Matrix::zeros_generic(nrows, Const::<1>); + let mut beta = Matrix::zeros_generic(nrows, Const::<1>); + let mut vsl = Matrix::zeros_generic(nrows, ncols); + let mut vsr = Matrix::zeros_generic(nrows, ncols); + // Placeholders: + let mut bwork = [0i32]; + let mut unused = 0; + + let lwork = T::xgges_work_size( + b'V', + b'V', + b'N', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + &mut unused, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut bwork, + &mut info, + ); + lapack_check!(info); + + let mut work = vec![T::zero(); lwork as usize]; + + T::xgges( + b'V', + b'V', + b'N', + n as i32, + a.as_mut_slice(), + n as i32, + b.as_mut_slice(), + n as i32, + &mut unused, + alphar.as_mut_slice(), + alphai.as_mut_slice(), + beta.as_mut_slice(), + vsl.as_mut_slice(), + n as i32, + vsr.as_mut_slice(), + n as i32, + &mut work, + lwork, + &mut bwork, + &mut info, + ); + lapack_check!(info); + + Some(QZ { + alphar, + alphai, + beta, + vsl, + s: a, + vsr, + t: b, + }) + } + + /// Retrieves the left and right matrices of Schur Vectors (VSL and VSR) + /// the upper-quasitriangular matrix `S` and upper triangular matrix `T` such that the + /// decomposed input matrix `a` equals `VSL * S * VSL.transpose()` and + /// decomposed input matrix `b` equals `VSL * T * VSL.transpose()`. + pub fn unpack( + self, + ) -> ( + OMatrix, + OMatrix, + OMatrix, + OMatrix, + ) { + (self.vsl, self.s, self.t, self.vsr) + } + + /// outputs the unprocessed (almost) version of generalized eigenvalues ((alphar, alpai), beta) + /// straight from LAPACK + #[must_use] + pub fn raw_eigenvalues(&self) -> OVector<(Complex, T), D> + where + DefaultAllocator: Allocator<(Complex, T), D>, + { + let mut out = Matrix::from_element_generic( + self.vsl.shape_generic().0, + Const::<1>, + (Complex::zero(), T::RealField::zero()), + ); + + for i in 0..out.len() { + out[i] = ( + Complex::new(self.alphar[i].clone(), self.alphai[i].clone()), + self.beta[i].clone(), + ) + } + + out + } +} + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the RealField QZ decomposition. +pub trait QZScalar: Scalar { + #[allow(missing_docs)] + fn xgges( + jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + work: &mut [Self], + lwork: i32, + bwork: &mut [i32], + info: &mut i32, + ); + + #[allow(missing_docs)] + fn xgges_work_size( + jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + b: &mut [Self], + ldb: i32, + sdim: &mut i32, + alphar: &mut [Self], + alphai: &mut [Self], + beta: &mut [Self], + vsl: &mut [Self], + ldvsl: i32, + vsr: &mut [Self], + ldvsr: i32, + bwork: &mut [i32], + info: &mut i32, + ) -> i32; +} + +macro_rules! qz_scalar_impl ( + ($N: ty, $xgges: path) => ( + impl QZScalar for $N { + #[inline] + fn xgges(jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + sdim: &mut i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + work: &mut [$N], + lwork: i32, + bwork: &mut [i32], + info: &mut i32) { + unsafe { $xgges(jobvsl, jobvsr, sort, None, n, a, lda, b, ldb, sdim, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, work, lwork, bwork, info); } + } + + + #[inline] + fn xgges_work_size(jobvsl: u8, + jobvsr: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + b: &mut [$N], + ldb: i32, + sdim: &mut i32, + alphar: &mut [$N], + alphai: &mut [$N], + beta : &mut [$N], + vsl: &mut [$N], + ldvsl: i32, + vsr: &mut [$N], + ldvsr: i32, + bwork: &mut [i32], + info: &mut i32) + -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + unsafe { $xgges(jobvsl, jobvsr, sort, None, n, a, lda, b, ldb, sdim, alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, &mut work, lwork, bwork, info); } + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +qz_scalar_impl!(f32, lapack::sgges); +qz_scalar_impl!(f64, lapack::dgges); diff --git a/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs new file mode 100644 index 00000000..b0d9777c --- /dev/null +++ b/nalgebra-lapack/tests/linalg/generalized_eigenvalues.rs @@ -0,0 +1,72 @@ +use na::dimension::Const; +use na::{DMatrix, OMatrix}; +use nl::GeneralizedEigen; +use num_complex::Complex; +use simba::scalar::ComplexField; + +use crate::proptest::*; +use proptest::{prop_assert, prop_compose, proptest}; + +prop_compose! { + fn f64_dynamic_dim_squares() + (n in PROPTEST_MATRIX_DIM) + (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + (a,b) +}} + +proptest! { + #[test] + fn ge((a,b) in f64_dynamic_dim_squares()){ + + let a_c = a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let n = a.shape_generic().0; + + let ge = GeneralizedEigen::new(a.clone(), b.clone()); + let (vsl,vsr) = ge.clone().eigenvectors(); + + + for (i,(alpha,beta)) in ge.raw_eigenvalues().iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; + + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(n, Const::<1>), + epsilon = 1.0e-5)); + + prop_assert!( + relative_eq!( + (vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, n), + epsilon = 1.0e-5)) + }; + } + + #[test] + fn ge_static(a in matrix4(), b in matrix4()) { + + let ge = GeneralizedEigen::new(a.clone(), b.clone()); + let a_c =a.clone().map(|x| Complex::new(x, 0.0)); + let b_c = b.clone().map(|x| Complex::new(x, 0.0)); + let (vsl,vsr) = ge.eigenvectors(); + let eigenvalues = ge.raw_eigenvalues(); + + for (i,(alpha,beta)) in eigenvalues.iter().enumerate() { + let l_a = a_c.clone() * Complex::new(*beta, 0.0); + let l_b = b_c.clone() * *alpha; + + prop_assert!( + relative_eq!( + ((&l_a - &l_b)*vsr.column(i)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<4>, Const::<1>), + epsilon = 1.0e-5)); + prop_assert!( + relative_eq!((vsl.column(i).adjoint()*(&l_a - &l_b)).map(|x| x.modulus()), + OMatrix::zeros_generic(Const::<1>, Const::<4>), + epsilon = 1.0e-5)) + } + } + +} diff --git a/nalgebra-lapack/tests/linalg/mod.rs b/nalgebra-lapack/tests/linalg/mod.rs index a6742217..251bbe7b 100644 --- a/nalgebra-lapack/tests/linalg/mod.rs +++ b/nalgebra-lapack/tests/linalg/mod.rs @@ -1,6 +1,8 @@ mod cholesky; +mod generalized_eigenvalues; mod lu; mod qr; +mod qz; mod real_eigensystem; mod schur; mod svd; diff --git a/nalgebra-lapack/tests/linalg/qz.rs b/nalgebra-lapack/tests/linalg/qz.rs new file mode 100644 index 00000000..c7a702ca --- /dev/null +++ b/nalgebra-lapack/tests/linalg/qz.rs @@ -0,0 +1,34 @@ +use na::DMatrix; +use nl::QZ; + +use crate::proptest::*; +use proptest::{prop_assert, prop_compose, proptest}; + +prop_compose! { + fn f64_dynamic_dim_squares() + (n in PROPTEST_MATRIX_DIM) + (a in matrix(PROPTEST_F64,n,n), b in matrix(PROPTEST_F64,n,n)) -> (DMatrix, DMatrix){ + (a,b) +}} + +proptest! { + #[test] + fn qz((a,b) in f64_dynamic_dim_squares()) { + + let qz = QZ::new(a.clone(), b.clone()); + let (vsl,s,t,vsr) = qz.clone().unpack(); + + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); + + } + + #[test] + fn qz_static(a in matrix4(), b in matrix4()) { + let qz = QZ::new(a.clone(), b.clone()); + let (vsl,s,t,vsr) = qz.unpack(); + + prop_assert!(relative_eq!(&vsl * s * vsr.transpose(), a, epsilon = 1.0e-7)); + prop_assert!(relative_eq!(vsl * t * vsr.transpose(), b, epsilon = 1.0e-7)); + } +} diff --git a/nalgebra-macros/Cargo.toml b/nalgebra-macros/Cargo.toml index bd37b689..6e35f9e9 100644 --- a/nalgebra-macros/Cargo.toml +++ b/nalgebra-macros/Cargo.toml @@ -21,5 +21,5 @@ quote = "1.0" proc-macro2 = "1.0" [dev-dependencies] -nalgebra = { version = "0.30.0", path = ".." } +nalgebra = { version = "0.31.0", path = ".." } trybuild = "1.0.42" diff --git a/nalgebra-macros/src/lib.rs b/nalgebra-macros/src/lib.rs index 9a403e0d..0d7889ae 100644 --- a/nalgebra-macros/src/lib.rs +++ b/nalgebra-macros/src/lib.rs @@ -111,7 +111,7 @@ impl Parse for Matrix { /// Construct a fixed-size matrix directly from data. /// -/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// **Note: Requires the `macros` feature to be enabled (enabled by default)**. /// /// This macro facilitates easy construction of matrices when the entries of the matrix are known /// (either as constants or expressions). This macro produces an instance of `SMatrix`. This means @@ -125,7 +125,6 @@ impl Parse for Matrix { /// (`;`) designates that a new row begins. /// /// # Examples -/// /// ``` /// use nalgebra::matrix; /// @@ -164,12 +163,13 @@ pub fn matrix(stream: TokenStream) -> TokenStream { /// Construct a dynamic matrix directly from data. /// -/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// **Note: Requires the `macros` feature to be enabled (enabled by default)**. /// /// The syntax is exactly the same as for [`matrix!`], but instead of producing instances of /// `SMatrix`, it produces instances of `DMatrix`. At the moment it is not usable /// in `const fn` contexts. /// +/// # Example /// ``` /// use nalgebra::dmatrix; /// @@ -233,7 +233,7 @@ impl Parse for Vector { /// Construct a fixed-size column vector directly from data. /// -/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// **Note: Requires the `macros` feature to be enabled (enabled by default)**. /// /// Similarly to [`matrix!`], this macro facilitates easy construction of fixed-size vectors. /// However, whereas the [`matrix!`] macro expects each row to be separated by a semi-colon, @@ -243,8 +243,7 @@ impl Parse for Vector { /// `vector!` is intended to be the most readable and performant way of constructing small, /// fixed-size vectors, and it is usable in `const fn` contexts. /// -/// ## Examples -/// +/// # Example /// ``` /// use nalgebra::vector; /// @@ -265,12 +264,13 @@ pub fn vector(stream: TokenStream) -> TokenStream { /// Construct a dynamic column vector directly from data. /// -/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// **Note: Requires the `macros` feature to be enabled (enabled by default)**. /// /// The syntax is exactly the same as for [`vector!`], but instead of producing instances of /// `SVector`, it produces instances of `DVector`. At the moment it is not usable /// in `const fn` contexts. /// +/// # Example /// ``` /// use nalgebra::dvector; /// @@ -294,15 +294,14 @@ pub fn dvector(stream: TokenStream) -> TokenStream { /// Construct a fixed-size point directly from data. /// -/// **Note: Requires the `macro` feature to be enabled (enabled by default)**. +/// **Note: Requires the `macros` feature to be enabled (enabled by default)**. /// /// Similarly to [`vector!`], this macro facilitates easy construction of points. /// /// `point!` is intended to be the most readable and performant way of constructing small, /// points, and it is usable in `const fn` contexts. /// -/// ## Examples -/// +/// # Example /// ``` /// use nalgebra::point; /// diff --git a/nalgebra-sparse/Cargo.toml b/nalgebra-sparse/Cargo.toml index 6f7a7b4a..7220373e 100644 --- a/nalgebra-sparse/Cargo.toml +++ b/nalgebra-sparse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-sparse" -version = "0.6.0" +version = "0.7.0" authors = [ "Andreas Longva", "Sébastien Crozet " ] edition = "2018" description = "Sparse matrix computation based on nalgebra." @@ -15,6 +15,7 @@ license = "Apache-2.0" [features] proptest-support = ["proptest", "nalgebra/proptest-support"] compare = [ "matrixcompare-core" ] +serde-serialize = [ "serde/std" ] # Enable matrix market I/O io = [ "pest", "pest_derive" ] @@ -23,18 +24,20 @@ io = [ "pest", "pest_derive" ] slow-tests = [] [dependencies] -nalgebra = { version="0.30", path = "../" } +nalgebra = { version="0.31", path = "../" } num-traits = { version = "0.2", default-features = false } proptest = { version = "1.0", optional = true } matrixcompare-core = { version = "0.1.0", optional = true } pest = { version = "2", optional = true } pest_derive = { version = "2", optional = true } +serde = { version = "1.0", default-features = false, features = [ "derive" ], optional = true } [dev-dependencies] itertools = "0.10" matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] } -nalgebra = { version="0.30", path = "../", features = ["compare"] } +nalgebra = { version="0.31", 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/convert/serial.rs b/nalgebra-sparse/src/convert/serial.rs index ecbe1dab..50fc50e4 100644 --- a/nalgebra-sparse/src/convert/serial.rs +++ b/nalgebra-sparse/src/convert/serial.rs @@ -14,6 +14,7 @@ use crate::coo::CooMatrix; use crate::cs; use crate::csc::CscMatrix; use crate::csr::CsrMatrix; +use crate::utils::{apply_permutation, compute_sort_permutation}; /// Converts a dense matrix to [`CooMatrix`]. pub fn convert_dense_coo(dense: &Matrix) -> CooMatrix @@ -376,29 +377,12 @@ fn sort_lane( assert_eq!(values.len(), workspace.len()); let permutation = workspace; - // Set permutation to identity - for (i, p) in permutation.iter_mut().enumerate() { - *p = i; - } - - // Compute permutation needed to bring minor indices into sorted order - // Note: Using sort_unstable here avoids internal allocations, which is crucial since - // each lane might have a small number of elements - permutation.sort_unstable_by_key(|idx| minor_idx[*idx]); + compute_sort_permutation(permutation, minor_idx); apply_permutation(minor_idx_result, minor_idx, permutation); apply_permutation(values_result, values, permutation); } -// TODO: Move this into `utils` or something? -fn apply_permutation(out_slice: &mut [T], in_slice: &[T], permutation: &[usize]) { - assert_eq!(out_slice.len(), in_slice.len()); - assert_eq!(out_slice.len(), permutation.len()); - for (out_element, old_pos) in out_slice.iter_mut().zip(permutation) { - *out_element = in_slice[*old_pos].clone(); - } -} - /// Given *sorted* indices and corresponding scalar values, combines duplicates with the given /// associative combiner and calls the provided produce methods with combined indices and values. fn combine_duplicates( diff --git a/nalgebra-sparse/src/coo.rs b/nalgebra-sparse/src/coo.rs index 34e5ceec..2b302e37 100644 --- a/nalgebra-sparse/src/coo.rs +++ b/nalgebra-sparse/src/coo.rs @@ -1,5 +1,8 @@ //! An implementation of the COO sparse matrix format. +#[cfg(feature = "serde-serialize")] +mod coo_serde; + use crate::SparseFormatError; /// A COO representation of a sparse matrix. diff --git a/nalgebra-sparse/src/coo/coo_serde.rs b/nalgebra-sparse/src/coo/coo_serde.rs new file mode 100644 index 00000000..7ffcdf4a --- /dev/null +++ b/nalgebra-sparse/src/coo/coo_serde.rs @@ -0,0 +1,65 @@ +use crate::coo::CooMatrix; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// This is an intermediate type for (de)serializing `CooMatrix`. +/// +/// Deserialization requires using a `try_from_*` function for validation. We could have used +/// the `remote = "Self"` trick (https://github.com/serde-rs/serde/issues/1220) which allows +/// to directly serialize/deserialize the original fields and combine it with validation. +/// However, this would lead to nested serialization of the `CsMatrix` and `SparsityPattern` +/// types. Instead, we decided that we want a more human-readable serialization format using +/// field names like `row_indices` and `col_indices`. The easiest way to achieve this is to +/// introduce an intermediate type. It also allows the serialization format to stay constant +/// even if the internal layout in `nalgebra` changes. +/// +/// We want to avoid unnecessary copies when serializing (i.e. cloning slices into owned +/// storage). Therefore, we use generic arguments to allow using slices during serialization and +/// owned storage (i.e. `Vec`) during deserialization. Without a major update of serde, slices +/// and `Vec`s should always (de)serialize identically. +#[derive(Serialize, Deserialize)] +struct CooMatrixSerializationData { + nrows: usize, + ncols: usize, + row_indices: Indices, + col_indices: Indices, + values: Values, +} + +impl Serialize for CooMatrix +where + T: Serialize + Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + CooMatrixSerializationData::<&[usize], &[T]> { + nrows: self.nrows(), + ncols: self.ncols(), + row_indices: self.row_indices(), + col_indices: self.col_indices(), + values: self.values(), + } + .serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for CooMatrix +where + T: Deserialize<'de> + Clone, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let de = CooMatrixSerializationData::, Vec>::deserialize(deserializer)?; + CooMatrix::try_from_triplets( + de.nrows, + de.ncols, + de.row_indices, + de.col_indices, + de.values, + ) + .map_err(|e| de::Error::custom(e)) + } +} diff --git a/nalgebra-sparse/src/cs.rs b/nalgebra-sparse/src/cs.rs index cffdd6c7..474eb2c0 100644 --- a/nalgebra-sparse/src/cs.rs +++ b/nalgebra-sparse/src/cs.rs @@ -6,7 +6,8 @@ use num_traits::One; use nalgebra::Scalar; use crate::pattern::SparsityPattern; -use crate::{SparseEntry, SparseEntryMut}; +use crate::utils::{apply_permutation, compute_sort_permutation}; +use crate::{SparseEntry, SparseEntryMut, SparseFormatError, SparseFormatErrorKind}; /// An abstract compressed matrix. /// @@ -543,3 +544,151 @@ pub fn convert_counts_to_offsets(counts: &mut [usize]) { offset += count; } } + +/// Validates cs data, optionally sorts minor indices and values +pub(crate) fn validate_and_optionally_sort_cs_data( + major_dim: usize, + minor_dim: usize, + major_offsets: &[usize], + minor_indices: &mut [usize], + values: Option<&mut [T]>, + sort: bool, +) -> Result<(), SparseFormatError> +where + T: Scalar, +{ + let mut values_option = values; + + if let Some(values) = values_option.as_mut() { + if minor_indices.len() != values.len() { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "Number of values and minor indices must be the same.", + )); + } + } else if sort { + unreachable!("Internal error: Sorting currently not supported if no values are present."); + } + if major_offsets.len() == 0 { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "Number of offsets should be greater than 0.", + )); + } + if major_offsets.len() != major_dim + 1 { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "Length of offset array is not equal to (major_dim + 1).", + )); + } + + // Check that the first and last offsets conform to the specification + { + let first_offset_ok = *major_offsets.first().unwrap() == 0; + let last_offset_ok = *major_offsets.last().unwrap() == minor_indices.len(); + if !first_offset_ok || !last_offset_ok { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "First or last offset is incompatible with format.", + )); + } + } + + // Set up required buffers up front + let mut minor_idx_buffer: Vec = Vec::new(); + let mut values_buffer: Vec = Vec::new(); + let mut minor_index_permutation: Vec = Vec::new(); + + // Test that each lane has strictly monotonically increasing minor indices, i.e. + // minor indices within a lane are sorted, unique. Sort minor indices within a lane if needed. + // In addition, each minor index must be in bounds with respect to the minor dimension. + { + for lane_idx in 0..major_dim { + let range_start = major_offsets[lane_idx]; + let range_end = major_offsets[lane_idx + 1]; + + // Test that major offsets are monotonically increasing + if range_start > range_end { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "Offsets are not monotonically increasing.", + )); + } + + let minor_idx_in_lane = minor_indices.get(range_start..range_end).ok_or( + SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::IndexOutOfBounds, + "A major offset is out of bounds.", + ), + )?; + + // We test for in-bounds, uniqueness and monotonicity at the same time + // to ensure that we only visit each minor index once + let mut prev = None; + let mut monotonic = true; + + for &minor_idx in minor_idx_in_lane { + if minor_idx >= minor_dim { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::IndexOutOfBounds, + "A minor index is out of bounds.", + )); + } + + if let Some(prev) = prev { + if prev >= minor_idx { + if !sort { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::InvalidStructure, + "Minor indices are not strictly monotonically increasing in each lane.", + )); + } + monotonic = false; + } + } + prev = Some(minor_idx); + } + + // sort if indices are not monotonic and sorting is expected + if !monotonic && sort { + let range_size = range_end - range_start; + minor_index_permutation.resize(range_size, 0); + compute_sort_permutation(&mut minor_index_permutation, &minor_idx_in_lane); + minor_idx_buffer.clear(); + minor_idx_buffer.extend_from_slice(&minor_idx_in_lane); + apply_permutation( + &mut minor_indices[range_start..range_end], + &minor_idx_buffer, + &minor_index_permutation, + ); + + // check duplicates + prev = None; + for &minor_idx in &minor_indices[range_start..range_end] { + if let Some(prev) = prev { + if prev == minor_idx { + return Err(SparseFormatError::from_kind_and_msg( + SparseFormatErrorKind::DuplicateEntry, + "Input data contains duplicate entries.", + )); + } + } + prev = Some(minor_idx); + } + + // sort values if they exist + if let Some(values) = values_option.as_mut() { + values_buffer.clear(); + values_buffer.extend_from_slice(&values[range_start..range_end]); + apply_permutation( + &mut values[range_start..range_end], + &values_buffer, + &minor_index_permutation, + ); + } + } + } + } + + Ok(()) +} diff --git a/nalgebra-sparse/src/csc.rs b/nalgebra-sparse/src/csc.rs index 607cc0cf..d926dafb 100644 --- a/nalgebra-sparse/src/csc.rs +++ b/nalgebra-sparse/src/csc.rs @@ -3,6 +3,10 @@ //! This is the module-level documentation. See [`CscMatrix`] for the main documentation of the //! CSC implementation. +#[cfg(feature = "serde-serialize")] +mod csc_serde; + +use crate::cs; use crate::cs::{CsLane, CsLaneIter, CsLaneIterMut, CsLaneMut, CsMatrix}; use crate::csr::CsrMatrix; use crate::pattern::{SparsityPattern, SparsityPatternFormatError, SparsityPatternIter}; @@ -170,6 +174,50 @@ impl CscMatrix { Self::try_from_pattern_and_values(pattern, values) } + /// Try to construct a CSC matrix from raw CSC data with unsorted row indices. + /// + /// It is assumed that each column contains unique 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 + /// with the exception of having unsorted row indices and values. + /// See the documentation for [CscMatrix](struct.CscMatrix.html) for more information. + pub fn try_from_unsorted_csc_data( + num_rows: usize, + num_cols: usize, + col_offsets: Vec, + mut row_indices: Vec, + mut values: Vec, + ) -> Result + where + T: Scalar, + { + let result = cs::validate_and_optionally_sort_cs_data( + num_cols, + num_rows, + &col_offsets, + &mut row_indices, + Some(&mut values), + true, + ); + + match result { + Ok(()) => { + let pattern = unsafe { + SparsityPattern::from_offset_and_indices_unchecked( + num_cols, + num_rows, + col_offsets, + row_indices, + ) + }; + Self::try_from_pattern_and_values(pattern, values) + } + Err(err) => Err(err), + } + } + /// 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 diff --git a/nalgebra-sparse/src/csc/csc_serde.rs b/nalgebra-sparse/src/csc/csc_serde.rs new file mode 100644 index 00000000..aab12d47 --- /dev/null +++ b/nalgebra-sparse/src/csc/csc_serde.rs @@ -0,0 +1,65 @@ +use crate::CscMatrix; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// This is an intermediate type for (de)serializing `CscMatrix`. +/// +/// Deserialization requires using a `try_from_*` function for validation. We could have used +/// the `remote = "Self"` trick (https://github.com/serde-rs/serde/issues/1220) which allows +/// to directly serialize/deserialize the original fields and combine it with validation. +/// However, this would lead to nested serialization of the `CsMatrix` and `SparsityPattern` +/// types. Instead, we decided that we want a more human-readable serialization format using +/// field names like `col_offsets` and `row_indices`. The easiest way to achieve this is to +/// introduce an intermediate type. It also allows the serialization format to stay constant +/// even if the internal layout in `nalgebra` changes. +/// +/// We want to avoid unnecessary copies when serializing (i.e. cloning slices into owned +/// storage). Therefore, we use generic arguments to allow using slices during serialization and +/// owned storage (i.e. `Vec`) during deserialization. Without a major update of serde, slices +/// and `Vec`s should always (de)serialize identically. +#[derive(Serialize, Deserialize)] +struct CscMatrixSerializationData { + nrows: usize, + ncols: usize, + col_offsets: Indices, + row_indices: Indices, + values: Values, +} + +impl Serialize for CscMatrix +where + T: Serialize + Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + CscMatrixSerializationData::<&[usize], &[T]> { + nrows: self.nrows(), + ncols: self.ncols(), + col_offsets: self.col_offsets(), + row_indices: self.row_indices(), + values: self.values(), + } + .serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for CscMatrix +where + T: Deserialize<'de> + Clone, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let de = CscMatrixSerializationData::, Vec>::deserialize(deserializer)?; + CscMatrix::try_from_csc_data( + de.nrows, + de.ncols, + de.col_offsets, + de.row_indices, + de.values, + ) + .map_err(|e| de::Error::custom(e)) + } +} diff --git a/nalgebra-sparse/src/csr.rs b/nalgebra-sparse/src/csr.rs index 4324d18d..90be35f1 100644 --- a/nalgebra-sparse/src/csr.rs +++ b/nalgebra-sparse/src/csr.rs @@ -2,6 +2,11 @@ //! //! This is the module-level documentation. See [`CsrMatrix`] for the main documentation of the //! CSC implementation. + +#[cfg(feature = "serde-serialize")] +mod csr_serde; + +use crate::cs; use crate::cs::{CsLane, CsLaneIter, CsLaneIterMut, CsLaneMut, CsMatrix}; use crate::csc::CscMatrix; use crate::pattern::{SparsityPattern, SparsityPatternFormatError, SparsityPatternIter}; @@ -10,7 +15,6 @@ use crate::{SparseEntry, SparseEntryMut, SparseFormatError, SparseFormatErrorKin use nalgebra::Scalar; use num_traits::One; -use std::iter::FromIterator; use std::slice::{Iter, IterMut}; /// A CSR representation of a sparse matrix. @@ -184,62 +188,35 @@ impl CsrMatrix { num_rows: usize, num_cols: usize, row_offsets: Vec, - col_indices: Vec, - values: Vec, + mut col_indices: Vec, + mut values: Vec, ) -> Result where T: Scalar, { - use SparsityPatternFormatError::*; - let count = col_indices.len(); - let mut p: Vec = (0..count).collect(); - - if col_indices.len() != values.len() { - return Err(SparseFormatError::from_kind_and_msg( - SparseFormatErrorKind::InvalidStructure, - "Number of values and column indices must be the same", - )); - } - - if row_offsets.len() == 0 { - return Err(SparseFormatError::from_kind_and_msg( - SparseFormatErrorKind::InvalidStructure, - "Number of offsets should be greater than 0", - )); - } - - for (index, &offset) in row_offsets[0..row_offsets.len() - 1].iter().enumerate() { - let next_offset = row_offsets[index + 1]; - if next_offset > count { - return Err(SparseFormatError::from_kind_and_msg( - SparseFormatErrorKind::InvalidStructure, - "No row offset should be greater than the number of column indices", - )); - } - if offset > next_offset { - return Err(NonmonotonicOffsets).map_err(pattern_format_error_to_csr_error); - } - p[offset..next_offset].sort_by(|a, b| { - let x = &col_indices[*a]; - let y = &col_indices[*b]; - x.partial_cmp(y).unwrap() - }); - } - - // permute indices - let sorted_col_indices: Vec = - Vec::from_iter((p.iter().map(|i| &col_indices[*i])).cloned()); - - // permute values - let sorted_values: Vec = Vec::from_iter((p.iter().map(|i| &values[*i])).cloned()); - - return Self::try_from_csr_data( + let result = cs::validate_and_optionally_sort_cs_data( num_rows, num_cols, - row_offsets, - sorted_col_indices, - sorted_values, + &row_offsets, + &mut col_indices, + Some(&mut values), + true, ); + + match result { + Ok(()) => { + let pattern = unsafe { + SparsityPattern::from_offset_and_indices_unchecked( + num_rows, + num_cols, + row_offsets, + col_indices, + ) + }; + Self::try_from_pattern_and_values(pattern, values) + } + Err(err) => Err(err), + } } /// Try to construct a CSR matrix from a sparsity pattern and associated non-zero values. diff --git a/nalgebra-sparse/src/csr/csr_serde.rs b/nalgebra-sparse/src/csr/csr_serde.rs new file mode 100644 index 00000000..1b33fda0 --- /dev/null +++ b/nalgebra-sparse/src/csr/csr_serde.rs @@ -0,0 +1,65 @@ +use crate::CsrMatrix; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// This is an intermediate type for (de)serializing `CsrMatrix`. +/// +/// Deserialization requires using a `try_from_*` function for validation. We could have used +/// the `remote = "Self"` trick (https://github.com/serde-rs/serde/issues/1220) which allows +/// to directly serialize/deserialize the original fields and combine it with validation. +/// However, this would lead to nested serialization of the `CsMatrix` and `SparsityPattern` +/// types. Instead, we decided that we want a more human-readable serialization format using +/// field names like `row_offsets` and `cal_indices`. The easiest way to achieve this is to +/// introduce an intermediate type. It also allows the serialization format to stay constant +/// even if the internal layout in `nalgebra` changes. +/// +/// We want to avoid unnecessary copies when serializing (i.e. cloning slices into owned +/// storage). Therefore, we use generic arguments to allow using slices during serialization and +/// owned storage (i.e. `Vec`) during deserialization. Without a major update of serde, slices +/// and `Vec`s should always (de)serialize identically. +#[derive(Serialize, Deserialize)] +struct CsrMatrixSerializationData { + nrows: usize, + ncols: usize, + row_offsets: Indices, + col_indices: Indices, + values: Values, +} + +impl Serialize for CsrMatrix +where + T: Serialize + Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + CsrMatrixSerializationData::<&[usize], &[T]> { + nrows: self.nrows(), + ncols: self.ncols(), + row_offsets: self.row_offsets(), + col_indices: self.col_indices(), + values: self.values(), + } + .serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for CsrMatrix +where + T: Deserialize<'de> + Clone, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let de = CsrMatrixSerializationData::, Vec>::deserialize(deserializer)?; + CsrMatrix::try_from_csr_data( + de.nrows, + de.ncols, + de.row_offsets, + de.col_indices, + de.values, + ) + .map_err(|e| de::Error::custom(e)) + } +} diff --git a/nalgebra-sparse/src/lib.rs b/nalgebra-sparse/src/lib.rs index edbf83bd..8567261a 100644 --- a/nalgebra-sparse/src/lib.rs +++ b/nalgebra-sparse/src/lib.rs @@ -160,6 +160,7 @@ pub mod ops; pub mod pattern; pub(crate) mod cs; +pub(crate) mod utils; #[cfg(feature = "proptest-support")] pub mod proptest; diff --git a/nalgebra-sparse/src/pattern.rs b/nalgebra-sparse/src/pattern.rs index 85f6bc1a..c51945b7 100644 --- a/nalgebra-sparse/src/pattern.rs +++ b/nalgebra-sparse/src/pattern.rs @@ -1,4 +1,8 @@ //! Sparsity patterns for CSR and CSC matrices. + +#[cfg(feature = "serde-serialize")] +mod pattern_serde; + use crate::cs::transpose_cs; use crate::SparseFormatError; use std::error::Error; @@ -184,6 +188,35 @@ impl SparsityPattern { }) } + /// Try to construct a sparsity pattern from the given dimensions, major offsets + /// and minor indices. + /// + /// # Panics + /// + /// Panics if the number of major offsets is not exactly one greater than the major dimension + /// or if major offsets do not start with 0 and end with the number of minor indices. + pub unsafe fn from_offset_and_indices_unchecked( + major_dim: usize, + minor_dim: usize, + major_offsets: Vec, + minor_indices: Vec, + ) -> Self { + assert_eq!(major_offsets.len(), major_dim + 1); + + // Check that the first and last offsets conform to the specification + { + let first_offset_ok = *major_offsets.first().unwrap() == 0; + let last_offset_ok = *major_offsets.last().unwrap() == minor_indices.len(); + assert!(first_offset_ok && last_offset_ok); + } + + Self { + major_offsets, + minor_indices, + minor_dim, + } + } + /// An iterator over the explicitly stored "non-zero" entries (i, j). /// /// The iteration happens in a lane-major fashion, meaning that the lane index i diff --git a/nalgebra-sparse/src/pattern/pattern_serde.rs b/nalgebra-sparse/src/pattern/pattern_serde.rs new file mode 100644 index 00000000..e11a550a --- /dev/null +++ b/nalgebra-sparse/src/pattern/pattern_serde.rs @@ -0,0 +1,56 @@ +use crate::pattern::SparsityPattern; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// This is an intermediate type for (de)serializing `SparsityPattern`. +/// +/// Deserialization requires using a `try_from_*` function for validation. We could have used +/// the `remote = "Self"` trick (https://github.com/serde-rs/serde/issues/1220) which allows +/// to directly serialize/deserialize the original fields and combine it with validation. +/// However, this would lead to nested serialization of the `CsMatrix` and `SparsityPattern` +/// types. Instead, we decided that we want a more human-readable serialization format using +/// field names like `major_offsets` and `minor_indices`. The easiest way to achieve this is to +/// introduce an intermediate type. It also allows the serialization format to stay constant +/// even when the internal layout in `nalgebra` changes. +/// +/// We want to avoid unnecessary copies when serializing (i.e. cloning slices into owned +/// storage). Therefore, we use generic arguments to allow using slices during serialization and +/// owned storage (i.e. `Vec`) during deserialization. Without a major update of serde, slices +/// and `Vec`s should always (de)serialize identically. +#[derive(Serialize, Deserialize)] +struct SparsityPatternSerializationData { + major_dim: usize, + minor_dim: usize, + major_offsets: Indices, + minor_indices: Indices, +} + +impl Serialize for SparsityPattern { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SparsityPatternSerializationData::<&[usize]> { + major_dim: self.major_dim(), + minor_dim: self.minor_dim(), + major_offsets: self.major_offsets(), + minor_indices: self.minor_indices(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for SparsityPattern { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let de = SparsityPatternSerializationData::>::deserialize(deserializer)?; + SparsityPattern::try_from_offsets_and_indices( + de.major_dim, + de.minor_dim, + de.major_offsets, + de.minor_indices, + ) + .map_err(|e| de::Error::custom(e)) + } +} diff --git a/nalgebra-sparse/src/utils.rs b/nalgebra-sparse/src/utils.rs new file mode 100644 index 00000000..73d4e967 --- /dev/null +++ b/nalgebra-sparse/src/utils.rs @@ -0,0 +1,26 @@ +//! Helper functions for sparse matrix computations + +/// permutes entries of in_slice according to permutation slice and puts them to out_slice +#[inline] +pub fn apply_permutation(out_slice: &mut [T], in_slice: &[T], permutation: &[usize]) { + assert_eq!(out_slice.len(), in_slice.len()); + assert_eq!(out_slice.len(), permutation.len()); + for (out_element, old_pos) in out_slice.iter_mut().zip(permutation) { + *out_element = in_slice[*old_pos].clone(); + } +} + +/// computes permutation by using provided indices as keys +#[inline] +pub fn compute_sort_permutation(permutation: &mut [usize], indices: &[usize]) { + assert_eq!(permutation.len(), indices.len()); + // Set permutation to identity + for (i, p) in permutation.iter_mut().enumerate() { + *p = i; + } + + // Compute permutation needed to bring minor indices into sorted order + // Note: Using sort_unstable here avoids internal allocations, which is crucial since + // each lane might have a small number of elements + permutation.sort_unstable_by_key(|idx| indices[*idx]); +} diff --git a/nalgebra-sparse/tests/serde.rs b/nalgebra-sparse/tests/serde.rs new file mode 100644 index 00000000..1ce1953f --- /dev/null +++ b/nalgebra-sparse/tests/serde.rs @@ -0,0 +1,206 @@ +#![cfg(feature = "serde-serialize")] +//! Serialization tests +#[cfg(any(not(feature = "proptest-support"), not(feature = "compare")))] +compile_error!("Tests must be run with features `proptest-support` and `compare`"); + +#[macro_use] +pub mod common; + +use nalgebra_sparse::coo::CooMatrix; +use nalgebra_sparse::csc::CscMatrix; +use nalgebra_sparse::csr::CsrMatrix; +use nalgebra_sparse::pattern::SparsityPattern; + +use proptest::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::common::{csc_strategy, csr_strategy}; + +fn json_roundtrip Deserialize<'a>>(csr: &T) -> T { + let serialized = serde_json::to_string(csr).unwrap(); + let deserialized: T = serde_json::from_str(&serialized).unwrap(); + deserialized +} + +#[test] +fn pattern_roundtrip() { + { + // 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!(json_roundtrip(&pattern), pattern); + } + + { + // 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!(json_roundtrip(&pattern), pattern); + } +} + +#[test] +#[rustfmt::skip] +fn pattern_deserialize_invalid() { + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0,2,2,5],"minor_indices":[0,5,1,2,3]}"#).is_ok()); + assert!(serde_json::from_str::(r#"{"major_dim":0,"minor_dim":0,"major_offsets":[],"minor_indices":[]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 3, 5],"minor_indices":[0, 1, 2, 3, 5]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[1, 2, 2, 5],"minor_indices":[0, 5, 1, 2, 3]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 2, 2, 4],"minor_indices":[0, 5, 1, 2, 3]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 2, 2],"minor_indices":[0, 5, 1, 2, 3]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 3, 2, 5],"minor_indices":[0, 1, 2, 3, 4]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 2, 2, 5],"minor_indices":[0, 2, 3, 1, 4]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 2, 2, 5],"minor_indices":[0, 6, 1, 2, 3]}"#).is_err()); + assert!(serde_json::from_str::(r#"{"major_dim":3,"minor_dim":6,"major_offsets":[0, 2, 2, 5],"minor_indices":[0, 5, 2, 2, 3]}"#).is_err()); +} + +#[test] +fn coo_roundtrip() { + { + // A COO matrix without entries + let matrix = + CooMatrix::::try_from_triplets(3, 2, Vec::new(), Vec::new(), Vec::new()).unwrap(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } + + { + // Arbitrary COO matrix, no duplicates + let i = vec![0, 1, 0, 0, 2]; + let j = vec![0, 2, 1, 3, 3]; + let v = vec![2, 3, 7, 3, 1]; + let matrix = + CooMatrix::::try_from_triplets(3, 5, i.clone(), j.clone(), v.clone()).unwrap(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } +} + +#[test] +fn coo_deserialize_invalid() { + // Valid matrix: {"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,1]} + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,1]}"#).is_ok()); + assert!(serde_json::from_str::>(r#"{"nrows":0,"ncols":0,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":-3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,4,5]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,8,3],"values":[2,3,7,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0],"col_indices":[0,2,1,8,3],"values":[2,3,7,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,10,0,0,2],"col_indices":[0,2,1,3,3],"values":[2,3,7,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2],"col_indices":[0,2,1,30,3],"values":[2,3,7,3,4]}"#).is_err()); +} + +#[test] +fn coo_deserialize_duplicates() { + assert_eq!( + serde_json::from_str::>( + r#"{"nrows":3,"ncols":5,"row_indices":[0,1,0,0,2,0,1],"col_indices":[0,2,1,3,3,0,2],"values":[2,3,7,3,1,5,6]}"# + ).unwrap(), + CooMatrix::::try_from_triplets( + 3, + 5, + vec![0, 1, 0, 0, 2, 0, 1], + vec![0, 2, 1, 3, 3, 0, 2], + vec![2, 3, 7, 3, 1, 5, 6] + ) + .unwrap() + ); +} + +#[test] +fn csc_roundtrip() { + { + // A CSC matrix with zero explicitly stored entries + let offsets = vec![0, 0, 0, 0]; + let indices = vec![]; + let values = Vec::::new(); + let matrix = CscMatrix::try_from_csc_data(2, 3, offsets, indices, values).unwrap(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } + + { + // 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(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } +} + +#[test] +fn csc_deserialize_invalid() { + // Valid matrix: {"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]} + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_ok()); + assert!(serde_json::from_str::>(r#"{"nrows":0,"ncols":0,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":-6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4,5]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,8,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3,1,1],"values":[0,1,2,3,4]}"#).is_err()); + // The following actually panics ('range end index 10 out of range for slice of length 5', nalgebra-sparse\src\pattern.rs:156:38) + //assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,10,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); +} + +#[test] +fn csr_roundtrip() { + { + // A CSR matrix with zero explicitly stored entries + let offsets = vec![0, 0, 0, 0]; + let indices = vec![]; + let values = Vec::::new(); + let matrix = CsrMatrix::try_from_csr_data(3, 2, offsets, indices, values).unwrap(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } + + { + // 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 matrix = + CsrMatrix::try_from_csr_data(3, 6, offsets.clone(), indices.clone(), values.clone()) + .unwrap(); + + assert_eq!(json_roundtrip(&matrix), matrix); + } +} + +#[test] +fn csr_deserialize_invalid() { + // Valid matrix: {"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]} + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_ok()); + assert!(serde_json::from_str::>(r#"{"nrows":0,"ncols":0,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":-3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4,5]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,8,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,2,2,5],"col_indices":[0,5,1,2,3,1,1],"values":[0,1,2,3,4]}"#).is_err()); + // The following actually panics ('range end index 10 out of range for slice of length 5', nalgebra-sparse\src\pattern.rs:156:38) + //assert!(serde_json::from_str::>(r#"{"nrows":3,"ncols":6,"row_offsets":[0,10,2,5],"col_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); + assert!(serde_json::from_str::>(r#"{"nrows":6,"ncols":3,"col_offsets":[0,2,2,5],"row_indices":[0,5,1,2,3],"values":[0,1,2,3,4]}"#).is_err()); +} + +proptest! { + #[test] + fn csc_roundtrip_proptest(csc in csc_strategy()) { + prop_assert_eq!(json_roundtrip(&csc), csc); + } + + #[test] + fn csr_roundtrip_proptest(csr in csr_strategy()) { + prop_assert_eq!(json_roundtrip(&csr), csr); + } +} diff --git a/nalgebra-sparse/tests/unit_tests/csc.rs b/nalgebra-sparse/tests/unit_tests/csc.rs index 7fb0de54..1554b8a6 100644 --- a/nalgebra-sparse/tests/unit_tests/csc.rs +++ b/nalgebra-sparse/tests/unit_tests/csc.rs @@ -5,6 +5,8 @@ use nalgebra_sparse::{SparseEntry, SparseEntryMut, SparseFormatErrorKind}; use proptest::prelude::*; use proptest::sample::subsequence; +use super::test_data_examples::{InvalidCsDataExamples, ValidCsDataExamples}; + use crate::assert_panics; use crate::common::csc_strategy; @@ -171,11 +173,26 @@ fn csc_matrix_valid_data() { } } +#[test] +fn csc_matrix_valid_data_unsorted_column_indices() { + let valid_data: ValidCsDataExamples = ValidCsDataExamples::new(); + + let (offsets, indices, values) = valid_data.valid_unsorted_cs_data; + let csc = CscMatrix::try_from_unsorted_csc_data(5, 4, offsets, indices, values).unwrap(); + + let (offsets2, indices2, values2) = valid_data.valid_cs_data; + let expected_csc = CscMatrix::try_from_csc_data(5, 4, offsets2, indices2, values2).unwrap(); + + assert_eq!(csc, expected_csc); +} + #[test] fn csc_matrix_try_from_invalid_csc_data() { + let invalid_data: InvalidCsDataExamples = InvalidCsDataExamples::new(); { // Empty offset array (invalid length) - let matrix = CscMatrix::try_from_csc_data(0, 0, Vec::new(), Vec::new(), Vec::::new()); + let (offsets, indices, values) = invalid_data.empty_offset_array; + let matrix = CscMatrix::try_from_csc_data(0, 0, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), &SparseFormatErrorKind::InvalidStructure @@ -184,10 +201,8 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = + invalid_data.offset_array_invalid_length_for_arbitrary_data; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -197,9 +212,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.invalid_first_entry_in_offsets_array; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -209,9 +222,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.invalid_last_entry_in_offsets_array; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -221,9 +232,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.invalid_length_of_offsets_array; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -233,9 +242,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.nonmonotonic_offsets; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -257,9 +264,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.minor_index_out_of_bounds; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -269,9 +274,7 @@ fn csc_matrix_try_from_invalid_csc_data() { { // 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 (offsets, indices, values) = invalid_data.duplicate_entry; let matrix = CscMatrix::try_from_csc_data(6, 3, offsets, indices, values); assert_eq!( matrix.unwrap_err().kind(), @@ -280,6 +283,121 @@ fn csc_matrix_try_from_invalid_csc_data() { } } +#[test] +fn csc_matrix_try_from_unsorted_invalid_csc_data() { + let invalid_data: InvalidCsDataExamples = InvalidCsDataExamples::new(); + { + // Empty offset array (invalid length) + let (offsets, indices, values) = invalid_data.empty_offset_array; + let matrix = CscMatrix::try_from_unsorted_csc_data(0, 0, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Offset array invalid length for arbitrary data + let (offsets, indices, values) = + invalid_data.offset_array_invalid_length_for_arbitrary_data; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Invalid first entry in offsets array + let (offsets, indices, values) = invalid_data.invalid_first_entry_in_offsets_array; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Invalid last entry in offsets array + let (offsets, indices, values) = invalid_data.invalid_last_entry_in_offsets_array; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Invalid length of offsets array + let (offsets, indices, values) = invalid_data.invalid_length_of_offsets_array; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Nonmonotonic offsets + let (offsets, indices, values) = invalid_data.nonmonotonic_offsets; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } + + { + // Major offset out of bounds + let (offsets, indices, values) = invalid_data.major_offset_out_of_bounds; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::IndexOutOfBounds + ); + } + + { + // Minor index out of bounds + let (offsets, indices, values) = invalid_data.minor_index_out_of_bounds; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::IndexOutOfBounds + ); + } + + { + // Duplicate entry + let (offsets, indices, values) = invalid_data.duplicate_entry; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::DuplicateEntry + ); + } + + { + // Duplicate entry in unsorted lane + let (offsets, indices, values) = invalid_data.duplicate_entry_unsorted; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::DuplicateEntry + ); + } + + { + // Wrong values length + let (offsets, indices, values) = invalid_data.wrong_values_length; + let matrix = CscMatrix::try_from_unsorted_csc_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } +} + #[test] fn csc_disassemble_avoids_clone_when_owned() { // Test that disassemble avoids cloning the sparsity pattern when it holds the sole reference diff --git a/nalgebra-sparse/tests/unit_tests/csr.rs b/nalgebra-sparse/tests/unit_tests/csr.rs index 3ca2f0dc..a00470d5 100644 --- a/nalgebra-sparse/tests/unit_tests/csr.rs +++ b/nalgebra-sparse/tests/unit_tests/csr.rs @@ -5,7 +5,7 @@ use nalgebra_sparse::{SparseEntry, SparseEntryMut, SparseFormatErrorKind}; use proptest::prelude::*; use proptest::sample::subsequence; -use super::test_data_examples::InvalidCsrDataExamples; +use super::test_data_examples::{InvalidCsDataExamples, ValidCsDataExamples}; use crate::assert_panics; use crate::common::csr_strategy; @@ -175,30 +175,20 @@ fn csr_matrix_valid_data() { #[test] fn csr_matrix_valid_data_unsorted_column_indices() { - let csr = CsrMatrix::try_from_unsorted_csr_data( - 4, - 5, - vec![0, 3, 5, 8, 11], - vec![4, 1, 3, 3, 1, 2, 3, 0, 3, 4, 1], - vec![5, 1, 4, 7, 4, 2, 3, 1, 8, 9, 6], - ) - .unwrap(); + let valid_data: ValidCsDataExamples = ValidCsDataExamples::new(); - let expected_csr = CsrMatrix::try_from_csr_data( - 4, - 5, - vec![0, 3, 5, 8, 11], - vec![1, 3, 4, 1, 3, 0, 2, 3, 1, 3, 4], - vec![1, 4, 5, 4, 7, 1, 2, 3, 6, 8, 9], - ) - .unwrap(); + let (offsets, indices, values) = valid_data.valid_unsorted_cs_data; + let csr = CsrMatrix::try_from_unsorted_csr_data(4, 5, offsets, indices, values).unwrap(); + + let (offsets2, indices2, values2) = valid_data.valid_cs_data; + let expected_csr = CsrMatrix::try_from_csr_data(4, 5, offsets2, indices2, values2).unwrap(); assert_eq!(csr, expected_csr); } #[test] fn csr_matrix_try_from_invalid_csr_data() { - let invalid_data: InvalidCsrDataExamples = InvalidCsrDataExamples::new(); + let invalid_data: InvalidCsDataExamples = InvalidCsDataExamples::new(); { // Empty offset array (invalid length) let (offsets, indices, values) = invalid_data.empty_offset_array; @@ -293,7 +283,7 @@ fn csr_matrix_try_from_invalid_csr_data() { #[test] fn csr_matrix_try_from_unsorted_invalid_csr_data() { - let invalid_data: InvalidCsrDataExamples = InvalidCsrDataExamples::new(); + let invalid_data: InvalidCsDataExamples = InvalidCsDataExamples::new(); { // Empty offset array (invalid length) let (offsets, indices, values) = invalid_data.empty_offset_array; @@ -355,6 +345,16 @@ fn csr_matrix_try_from_unsorted_invalid_csr_data() { ); } + { + // Major offset out of bounds + let (offsets, indices, values) = invalid_data.major_offset_out_of_bounds; + let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::IndexOutOfBounds + ); + } + { // Minor index out of bounds let (offsets, indices, values) = invalid_data.minor_index_out_of_bounds; @@ -374,6 +374,26 @@ fn csr_matrix_try_from_unsorted_invalid_csr_data() { &SparseFormatErrorKind::DuplicateEntry ); } + + { + // Duplicate entry in unsorted lane + let (offsets, indices, values) = invalid_data.duplicate_entry_unsorted; + let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::DuplicateEntry + ); + } + + { + // Wrong values length + let (offsets, indices, values) = invalid_data.wrong_values_length; + let matrix = CsrMatrix::try_from_unsorted_csr_data(6, 3, offsets, indices, values); + assert_eq!( + matrix.unwrap_err().kind(), + &SparseFormatErrorKind::InvalidStructure + ); + } } #[test] diff --git a/nalgebra-sparse/tests/unit_tests/test_data_examples.rs b/nalgebra-sparse/tests/unit_tests/test_data_examples.rs index 20721087..a80b5064 100644 --- a/nalgebra-sparse/tests/unit_tests/test_data_examples.rs +++ b/nalgebra-sparse/tests/unit_tests/test_data_examples.rs @@ -1,5 +1,31 @@ -/// Examples of *invalid* raw CSR data `(offsets, indices, values)`. -pub struct InvalidCsrDataExamples { +/// Examples of *valid* raw CS data `(offsets, indices, values)`. +pub struct ValidCsDataExamples { + pub valid_cs_data: (Vec, Vec, Vec), + pub valid_unsorted_cs_data: (Vec, Vec, Vec), +} + +impl ValidCsDataExamples { + pub fn new() -> Self { + let valid_cs_data = ( + vec![0, 3, 5, 8, 11], + vec![1, 3, 4, 1, 3, 0, 2, 3, 1, 3, 4], + vec![1, 4, 5, 4, 7, 1, 2, 3, 6, 8, 9], + ); + let valid_unsorted_cs_data = ( + vec![0, 3, 5, 8, 11], + vec![4, 1, 3, 3, 1, 2, 3, 0, 3, 4, 1], + vec![5, 1, 4, 7, 4, 2, 3, 1, 8, 9, 6], + ); + + return Self { + valid_cs_data, + valid_unsorted_cs_data, + }; + } +} + +/// Examples of *invalid* raw CS data `(offsets, indices, values)`. +pub struct InvalidCsDataExamples { pub empty_offset_array: (Vec, Vec, Vec), pub offset_array_invalid_length_for_arbitrary_data: (Vec, Vec, Vec), pub invalid_first_entry_in_offsets_array: (Vec, Vec, Vec), @@ -7,11 +33,14 @@ pub struct InvalidCsrDataExamples { pub invalid_length_of_offsets_array: (Vec, Vec, Vec), pub nonmonotonic_offsets: (Vec, Vec, Vec), pub nonmonotonic_minor_indices: (Vec, Vec, Vec), + pub major_offset_out_of_bounds: (Vec, Vec, Vec), pub minor_index_out_of_bounds: (Vec, Vec, Vec), pub duplicate_entry: (Vec, Vec, Vec), + pub duplicate_entry_unsorted: (Vec, Vec, Vec), + pub wrong_values_length: (Vec, Vec, Vec), } -impl InvalidCsrDataExamples { +impl InvalidCsDataExamples { pub fn new() -> Self { let empty_offset_array = (Vec::::new(), Vec::::new(), Vec::::new()); let offset_array_invalid_length_for_arbitrary_data = @@ -25,9 +54,13 @@ impl InvalidCsrDataExamples { let nonmonotonic_offsets = (vec![0, 3, 2, 5], vec![0, 1, 2, 3, 4], vec![0, 1, 2, 3, 4]); let nonmonotonic_minor_indices = (vec![0, 2, 2, 5], vec![0, 2, 3, 1, 4], vec![0, 1, 2, 3, 4]); + let major_offset_out_of_bounds = + (vec![0, 7, 2, 5], vec![0, 2, 3, 1, 4], vec![0, 1, 2, 3, 4]); let minor_index_out_of_bounds = (vec![0, 2, 2, 5], vec![0, 6, 1, 2, 3], vec![0, 1, 2, 3, 4]); - let duplicate_entry = (vec![0, 2, 2, 5], vec![0, 5, 2, 2, 3], vec![0, 1, 2, 3, 4]); + let duplicate_entry = (vec![0, 1, 2, 5], vec![1, 3, 2, 3, 3], vec![0, 1, 2, 3, 4]); + let duplicate_entry_unsorted = (vec![0, 1, 4, 5], vec![1, 3, 2, 3, 3], vec![0, 1, 2, 3, 4]); + let wrong_values_length = (vec![0, 1, 2, 5], vec![1, 3, 2, 3, 0], vec![5, 4]); return Self { empty_offset_array, @@ -37,8 +70,11 @@ impl InvalidCsrDataExamples { invalid_length_of_offsets_array, nonmonotonic_minor_indices, nonmonotonic_offsets, + major_offset_out_of_bounds, minor_index_out_of_bounds, duplicate_entry, + duplicate_entry_unsorted, + wrong_values_length, }; } } diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index b46d442f..3bc71e1a 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -1,7 +1,5 @@ use std::fmt::{self, Debug, Formatter}; // use std::hash::{Hash, Hasher}; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; use std::ops::Mul; #[cfg(feature = "serde-serialize-no-std")] @@ -13,9 +11,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde-serialize-no-std")] use std::marker::PhantomData; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use crate::base::allocator::Allocator; use crate::base::default_allocator::DefaultAllocator; use crate::base::dimension::{Const, ToTypenum}; @@ -32,10 +27,12 @@ use std::mem; /// A array-based statically sized matrix data storage. #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct ArrayStorage(pub [[T; R]; C]); impl ArrayStorage { @@ -281,71 +278,3 @@ unsafe impl by for ArrayStorage { } - -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for ArrayStorage -where - T: Scalar + Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - for element in self.as_slice() { - element.entomb(writer)?; - } - - Ok(()) - } - - unsafe fn exhume<'a, 'b>(&'a mut self, mut bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - for element in self.as_mut_slice() { - let temp = bytes; - bytes = element.exhume(temp)? - } - Some(bytes) - } - - fn extent(&self) -> usize { - self.as_slice().iter().fold(0, |acc, e| acc + e.extent()) - } -} - -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::ArrayStorage; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for ArrayStorage { - type Archived = ArrayStorage; - type Resolver = <[[T; R]; C] as Archive>::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.0.resolve( - pos + offset_of!(Self::Archived, 0), - resolver, - project_struct!(out: Self::Archived => 0), - ); - } - } - - impl, S: Fallible + ?Sized, const R: usize, const C: usize> Serialize - for ArrayStorage - { - fn serialize(&self, serializer: &mut S) -> Result { - self.0.serialize(serializer) - } - } - - impl - Deserialize, D> for ArrayStorage - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(ArrayStorage(self.0.deserialize(deserializer)?)) - } - } -} diff --git a/src/base/blas.rs b/src/base/blas.rs index 4f56a70e..e65304b5 100644 --- a/src/base/blas.rs +++ b/src/base/blas.rs @@ -175,8 +175,7 @@ where /// Note that this is **not** the matrix multiplication as in, e.g., numpy. For matrix /// multiplication, use one of: `.gemm`, `.mul_to`, `.mul`, the `*` operator. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector3, Matrix2x3}; /// let vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -207,8 +206,7 @@ where /// Note that this is **not** the matrix multiplication as in, e.g., numpy. For matrix /// multiplication, use one of: `.gemm`, `.mul_to`, `.mul`, the `*` operator. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector2, Complex}; /// let vec1 = Vector2::new(Complex::new(1.0, 2.0), Complex::new(3.0, 4.0)); @@ -232,8 +230,7 @@ where /// The dot product between the transpose of `self` and `rhs`. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Vector3, RowVector3, Matrix2x3, Matrix3x2}; /// let vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -285,8 +282,7 @@ where /// /// If `b` is zero, `self` is never read from. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Vector3; /// let mut vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -308,8 +304,7 @@ where /// /// If `b` is zero, `self` is never read from. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Vector3; /// let mut vec1 = Vector3::new(1.0, 2.0, 3.0); @@ -333,8 +328,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut vec1 = Vector2::new(1.0, 2.0); @@ -425,8 +419,7 @@ where /// If `beta` is zero, `self` is never read. If `self` is read, only its lower-triangular part /// (including the diagonal) is actually read. /// - /// # Examples: - /// + /// # Examples /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mat = Matrix2::new(1.0, 2.0, @@ -468,8 +461,7 @@ where /// If `beta` is zero, `self` is never read. If `self` is read, only its lower-triangular part /// (including the diagonal) is actually read. /// - /// # Examples: - /// + /// # Examples /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mat = Matrix2::new(Complex::new(1.0, 0.0), Complex::new(2.0, -0.1), @@ -552,8 +544,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mat = Matrix2::new(1.0, 3.0, @@ -587,8 +578,7 @@ where /// For real matrices, this is the same as `.gemv_tr`. /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mat = Matrix2::new(Complex::new(1.0, 2.0), Complex::new(3.0, 4.0), @@ -656,8 +646,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2x3, Vector2, Vector3}; /// let mut mat = Matrix2x3::repeat(4.0); @@ -688,8 +677,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2x3, Vector2, Vector3, Complex}; @@ -722,8 +710,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2x3, Matrix3x4, Matrix2x4}; @@ -763,8 +750,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix3x2, Matrix3x4, Matrix2x4}; @@ -821,8 +807,7 @@ where /// /// If `beta` is zero, `self` is never read. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix3x2, Matrix3x4, Matrix2x4, Complex}; @@ -921,8 +906,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut mat = Matrix2::identity(); @@ -934,6 +918,7 @@ where /// mat.ger_symm(10.0, &vec1, &vec2, 5.0); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, 99999.99999); // This was untouched. + /// ``` #[inline] #[deprecated(note = "This is renamed `syger` to match the original BLAS terminology.")] pub fn ger_symm( @@ -958,8 +943,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2}; /// let mut mat = Matrix2::identity(); @@ -971,6 +955,7 @@ where /// mat.syger(10.0, &vec1, &vec2, 5.0); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, 99999.99999); // This was untouched. + /// ``` #[inline] pub fn syger( &mut self, @@ -993,8 +978,7 @@ where /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular /// (including the diagonal) part of `self` is read/written. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::{Matrix2, Vector2, Complex}; /// let mut mat = Matrix2::identity(); @@ -1006,6 +990,7 @@ where /// mat.hegerc(Complex::new(10.0, 20.0), &vec1, &vec2, Complex::new(5.0, 15.0)); /// assert_eq!(mat.lower_triangle(), expected.lower_triangle()); /// assert_eq!(mat.m12, Complex::new(99999.99999, 88888.88888)); // This was untouched. + /// ``` #[inline] pub fn hegerc( &mut self, @@ -1031,8 +1016,7 @@ where /// /// This uses the provided workspace `work` to avoid allocations for intermediate results. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{DMatrix, DVector}; @@ -1053,6 +1037,7 @@ where /// /// mat.quadform_tr_with_workspace(&mut workspace, 10.0, &lhs, &mid, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_tr_with_workspace( &mut self, work: &mut Vector, @@ -1085,8 +1070,7 @@ where /// If `D1` is a type-level integer, then the allocation is performed on the stack. /// Use `.quadform_tr_with_workspace(...)` instead to avoid allocations. /// - /// # Examples: - /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2, Matrix3, Matrix2x3, Vector2}; @@ -1100,6 +1084,7 @@ where /// /// mat.quadform_tr(10.0, &lhs, &mid, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_tr( &mut self, alpha: T, @@ -1124,6 +1109,7 @@ where /// /// This uses the provided workspace `work` to avoid allocations for intermediate results. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{DMatrix, DVector}; @@ -1145,6 +1131,7 @@ where /// /// mat.quadform_with_workspace(&mut workspace, 10.0, &mid, &rhs, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform_with_workspace( &mut self, work: &mut Vector, @@ -1180,6 +1167,7 @@ where /// If `D2` is a type-level integer, then the allocation is performed on the stack. /// Use `.quadform_with_workspace(...)` instead to avoid allocations. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix2, Matrix3x2, Matrix3}; @@ -1194,6 +1182,7 @@ where /// /// mat.quadform(10.0, &mid, &rhs, 5.0); /// assert_relative_eq!(mat, expected); + /// ``` pub fn quadform( &mut self, alpha: T, diff --git a/src/base/dimension.rs b/src/base/dimension.rs index 86006f3d..4be97586 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -13,10 +13,12 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Dim of dynamically-sized algebraic entities. #[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Dynamic { value: usize, } @@ -202,9 +204,11 @@ dim_ops!( #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Const; /// Trait implemented exclusively by type-level integers. @@ -239,37 +243,6 @@ impl<'de, const D: usize> Deserialize<'de> for Const { } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Const; - use rkyv::{Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Const { - type Archived = Self; - type Resolver = (); - - fn resolve( - &self, - _: usize, - _: Self::Resolver, - _: &mut core::mem::MaybeUninit, - ) { - } - } - - impl Serialize for Const { - fn serialize(&self, _: &mut S) -> Result { - Ok(()) - } - } - - impl Deserialize for Const { - fn deserialize(&self, _: &mut D) -> Result { - Ok(Const) - } - } -} - pub trait ToConst { type Const: DimName; } @@ -309,24 +282,24 @@ impl DimName for Const { pub type U1 = Const<1>; -impl ToTypenum for Const<{ typenum::U1::USIZE }> { +impl ToTypenum for Const<1> { type Typenum = typenum::U1; } impl ToConst for typenum::U1 { - type Const = Const<{ typenum::U1::USIZE }>; + type Const = Const<1>; } macro_rules! from_to_typenum ( - ($($D: ident),* $(,)*) => {$( - pub type $D = Const<{ typenum::$D::USIZE }>; + ($($D: ident, $VAL: expr);* $(;)*) => {$( + pub type $D = Const<$VAL>; - impl ToTypenum for Const<{ typenum::$D::USIZE }> { + impl ToTypenum for Const<$VAL> { type Typenum = typenum::$D; } impl ToConst for typenum::$D { - type Const = Const<{ typenum::$D::USIZE }>; + type Const = Const<$VAL>; } impl IsNotStaticOne for $D { } @@ -334,12 +307,12 @@ macro_rules! from_to_typenum ( ); from_to_typenum!( - U0, /*U1,*/ U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16, U17, U18, - U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32, U33, U34, U35, U36, U37, - U38, U39, U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, U50, U51, U52, U53, U54, U55, U56, - U57, U58, U59, U60, U61, U62, U63, U64, U65, U66, U67, U68, U69, U70, U71, U72, U73, U74, U75, - U76, U77, U78, U79, U80, U81, U82, U83, U84, U85, U86, U87, U88, U89, U90, U91, U92, U93, U94, - U95, U96, U97, U98, U99, U100, U101, U102, U103, U104, U105, U106, U107, U108, U109, U110, - U111, U112, U113, U114, U115, U116, U117, U118, U119, U120, U121, U122, U123, U124, U125, U126, - U127 + U0, 0; /*U1,1;*/ U2, 2; U3, 3; U4, 4; U5, 5; U6, 6; U7, 7; U8, 8; U9, 9; U10, 10; U11, 11; U12, 12; U13, 13; U14, 14; U15, 15; U16, 16; U17, 17; U18, 18; + U19, 19; U20, 20; U21, 21; U22, 22; U23, 23; U24, 24; U25, 25; U26, 26; U27, 27; U28, 28; U29, 29; U30, 30; U31, 31; U32, 32; U33, 33; U34, 34; U35, 35; U36, 36; U37, 37; + U38, 38; U39, 39; U40, 40; U41, 41; U42, 42; U43, 43; U44, 44; U45, 45; U46, 46; U47, 47; U48, 48; U49, 49; U50, 50; U51, 51; U52, 52; U53, 53; U54, 54; U55, 55; U56, 56; + U57, 57; U58, 58; U59, 59; U60, 60; U61, 61; U62, 62; U63, 63; U64, 64; U65, 65; U66, 66; U67, 67; U68, 68; U69, 69; U70, 70; U71, 71; U72, 72; U73, 73; U74, 74; U75, 75; + U76, 76; U77, 77; U78, 78; U79, 79; U80, 80; U81, 81; U82, 82; U83, 83; U84, 84; U85, 85; U86, 86; U87, 87; U88, 88; U89, 89; U90, 90; U91, 91; U92, 92; U93, 93; U94, 94; + U95, 95; U96, 96; U97, 97; U98, 98; U99, 99; U100, 100; U101, 101; U102, 102; U103, 103; U104, 104; U105, 105; U106, 106; U107, 107; U108, 108; U109, 109; U110, 110; + U111, 111; U112, 112; U113, 113; U114, 114; U115, 115; U116, 116; U117, 117; U118, 118; U119, 119; U120, 120; U121, 121; U122, 122; U123, 123; U124, 124; U125, 125; U126, 126; + U127, 127 ); diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 652eace1..8f8786c1 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -1,6 +1,4 @@ use num::{One, Zero}; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use std::any::TypeId; @@ -13,9 +11,6 @@ use std::mem; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::{ClosedAdd, ClosedMul, ClosedSub, Field, SupersetOf}; use simba::simd::SimdPartialOrd; @@ -155,10 +150,12 @@ pub type MatrixCross = /// some concrete types for `T` and a compatible data storage type `S`). #[repr(C)] #[derive(Clone, Copy)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Matrix { /// The data storage that contains all the matrix components. Disappointed? /// @@ -254,21 +251,6 @@ where } } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Matrix { - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.data.entomb(writer) - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.data.exhume(bytes) - } - - fn extent(&self) -> usize { - self.data.extent() - } -} - #[cfg(feature = "compare")] impl> matrixcompare_core::Matrix for Matrix @@ -311,53 +293,6 @@ where { } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Matrix; - use core::marker::PhantomData; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Matrix { - type Archived = Matrix; - type Resolver = S::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.data.resolve( - pos + offset_of!(Self::Archived, data), - resolver, - project_struct!(out: Self::Archived => data), - ); - } - } - - impl, _S: Fallible + ?Sized> Serialize<_S> - for Matrix - { - fn serialize(&self, serializer: &mut _S) -> Result { - self.data.serialize(serializer) - } - } - - impl - Deserialize, D> - for Matrix - where - S::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Matrix { - data: self.data.deserialize(deserializer)?, - _phantoms: PhantomData, - }) - } - } -} - impl Matrix { /// Creates a new matrix with the given data without statically checking that the matrix /// dimension matches the storage dimension. @@ -434,8 +369,6 @@ where { /// Assumes a matrix's entries to be initialized. This operation should be near zero-cost. /// - /// For the similar method that operates on matrix slices, see [`slice_assume_init`]. - /// /// # Safety /// The user must make sure that every single entry of the buffer has been initialized, /// or Undefined Behavior will immediately occur. @@ -456,12 +389,12 @@ impl> Matrix { /// The shape of this matrix returned as the tuple (number of rows, number of columns). /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.shape(), (3, 4)); + /// ``` #[inline] #[must_use] pub fn shape(&self) -> (usize, usize) { @@ -478,12 +411,12 @@ impl> Matrix { /// The number of rows of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.nrows(), 3); + /// ``` #[inline] #[must_use] pub fn nrows(&self) -> usize { @@ -492,12 +425,12 @@ impl> Matrix { /// The number of columns of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix3x4; /// let mat = Matrix3x4::::zeros(); /// assert_eq!(mat.ncols(), 4); + /// ``` #[inline] #[must_use] pub fn ncols(&self) -> usize { @@ -506,14 +439,14 @@ impl> Matrix { /// The strides (row stride, column stride) of this matrix. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::DMatrix; /// let mat = DMatrix::::zeros(10, 10); /// let slice = mat.slice_with_steps((0, 0), (5, 3), (1, 2)); /// // The column strides is the number of steps (here 2) multiplied by the corresponding dimension. /// assert_eq!(mat.strides(), (1, 10)); + /// ``` #[inline] #[must_use] pub fn strides(&self) -> (usize, usize) { @@ -1108,8 +1041,7 @@ impl> Matrix { impl> Matrix { /// Iterates through this matrix coordinates in column-major order. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::Matrix2x3; /// let mat = Matrix2x3::new(11, 12, 13, @@ -1122,6 +1054,7 @@ impl> Matrix { /// assert_eq!(*it.next().unwrap(), 13); /// assert_eq!(*it.next().unwrap(), 23); /// assert!(it.next().is_none()); + /// ``` #[inline] pub fn iter(&self) -> MatrixIter<'_, T, R, C, S> { MatrixIter::new(&self.data) @@ -1144,6 +1077,7 @@ impl> Matrix { } /// Iterate through the columns of this matrix. + /// /// # Example /// ``` /// # use nalgebra::Matrix2x3; diff --git a/src/base/unit.rs b/src/base/unit.rs index 60281b8f..6fc00092 100644 --- a/src/base/unit.rs +++ b/src/base/unit.rs @@ -1,14 +1,9 @@ use std::fmt; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; use std::ops::Deref; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use crate::allocator::Allocator; use crate::base::DefaultAllocator; use crate::storage::RawStorage; @@ -26,10 +21,12 @@ use crate::{Dim, Matrix, OMatrix, RealField, Scalar, SimdComplexField, SimdRealF /// in their documentation, read their dedicated pages directly. #[repr(transparent)] #[derive(Clone, Hash, Copy)] -// #[cfg_attr( -// all(not(target_os = "cuda"), feature = "cuda"), -// derive(cust::DeviceCopy) -// )] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +// #[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Unit { pub(crate) value: T, } @@ -66,65 +63,8 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Unit { } } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Unit { - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.value.entomb(writer) - } - - fn extent(&self) -> usize { - self.value.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.value.exhume(bytes) - } -} - -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Unit; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Unit { - type Archived = Unit; - type Resolver = T::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut ::core::mem::MaybeUninit, - ) { - self.value.resolve( - pos + offset_of!(Self::Archived, value), - resolver, - project_struct!(out: Self::Archived => value), - ); - } - } - - impl, S: Fallible + ?Sized> Serialize for Unit { - fn serialize(&self, serializer: &mut S) -> Result { - self.value.serialize(serializer) - } - } - - impl Deserialize, D> for Unit - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Unit { - value: self.value.deserialize(deserializer)?, - }) - } - } -} - -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy - for Unit> +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for Unit> where T: Scalar, R: Dim, diff --git a/src/base/vec_storage.rs b/src/base/vec_storage.rs index bf73661d..414354cd 100644 --- a/src/base/vec_storage.rs +++ b/src/base/vec_storage.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; - #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; @@ -18,8 +15,6 @@ use serde::{ }; use crate::Storage; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; use std::mem::MaybeUninit; /* @@ -402,21 +397,6 @@ where } } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for VecStorage { - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.data.entomb(writer) - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.data.exhume(bytes) - } - - fn extent(&self) -> usize { - self.data.extent() - } -} - impl Extend for VecStorage { /// Extends the number of columns of the `VecStorage` with elements /// from the given iterator. diff --git a/src/geometry/dual_quaternion.rs b/src/geometry/dual_quaternion.rs index 4280668a..6f1b7053 100644 --- a/src/geometry/dual_quaternion.rs +++ b/src/geometry/dual_quaternion.rs @@ -19,6 +19,7 @@ use simba::scalar::{ClosedNeg, RealField}; /// `DualQuaternions` are stored as \[..real, ..dual\]. /// Both of the quaternion components are laid out in `i, j, k, w` order. /// +/// # Example /// ``` /// # use nalgebra::{DualQuaternion, Quaternion}; /// @@ -39,10 +40,12 @@ use simba::scalar::{ClosedNeg, RealField}; /// See #[repr(C)] #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct DualQuaternion { /// The real component of the quaternion pub real: Quaternion, @@ -623,6 +626,7 @@ where /// dq.rotation().euler_angles().0, std::f32::consts::FRAC_PI_2, epsilon = 1.0e-6 /// ); /// assert_relative_eq!(dq.translation().vector.y, 3.0, epsilon = 1.0e-6); + /// ``` #[inline] #[must_use] pub fn sclerp(&self, other: &Self, t: T) -> Self @@ -713,6 +717,7 @@ where /// Return the rotation part of this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -733,6 +738,7 @@ where /// Return the translation part of this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -758,6 +764,7 @@ where /// Builds an isometry from this unit dual quaternion. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -783,6 +790,7 @@ where /// /// This is the same as the multiplication `self * pt`. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -807,6 +815,7 @@ where /// /// This is the same as the multiplication `self * v`. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -831,6 +840,7 @@ where /// This may be cheaper than inverting the unit dual quaternion and /// transforming the point. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -856,6 +866,7 @@ where /// This may be cheaper than inverting the unit dual quaternion and /// transforming the vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3}; @@ -880,6 +891,7 @@ where /// cheaper than inverting the unit dual quaternion and transforming the /// vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Unit, Vector3}; @@ -909,6 +921,7 @@ where /// Converts this unit dual quaternion interpreted as an isometry /// into its equivalent homogeneous transformation matrix. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Matrix4, UnitDualQuaternion, UnitQuaternion, Vector3}; diff --git a/src/geometry/dual_quaternion_construction.rs b/src/geometry/dual_quaternion_construction.rs index 94bbc04f..ae7b5c97 100644 --- a/src/geometry/dual_quaternion_construction.rs +++ b/src/geometry/dual_quaternion_construction.rs @@ -27,7 +27,6 @@ impl DualQuaternion { /// The dual quaternion multiplicative identity. /// /// # Example - /// /// ``` /// # use nalgebra::{DualQuaternion, Quaternion}; /// @@ -134,6 +133,7 @@ impl UnitDualQuaternion { /// The unit dual quaternion multiplicative identity, which also represents /// the identity transformation as an isometry. /// + /// # Example /// ``` /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; /// let ident = UnitDualQuaternion::identity(); @@ -171,6 +171,7 @@ where /// Return a dual quaternion representing the translation and orientation /// given by the provided rotation quaternion and translation vector. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; @@ -196,6 +197,7 @@ where /// Return a unit dual quaternion representing the translation and orientation /// given by the provided isometry. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::{Isometry3, UnitDualQuaternion, UnitQuaternion, Vector3, Point3}; diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index f020c0e9..92169742 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -1,15 +1,10 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::{RealField, SubsetOf}; use simba::simd::SimdRealField; @@ -55,10 +50,7 @@ use crate::geometry::{AbstractRotation, Point, Translation}; /// #[repr(C)] #[derive(Debug, Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", @@ -74,6 +66,11 @@ use crate::geometry::{AbstractRotation, Point, Translation}; Owned>: Deserialize<'de>, T: Scalar")) )] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct Isometry { /// The pure rotational part of this isometry. pub rotation: R, @@ -81,89 +78,6 @@ pub struct Isometry { pub translation: Translation, } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Isometry -where - T: SimdRealField, - R: Abomonation, - Translation: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.rotation.entomb(writer)?; - self.translation.entomb(writer) - } - - fn extent(&self) -> usize { - self.rotation.extent() + self.translation.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.rotation - .exhume(bytes) - .and_then(|bytes| self.translation.exhume(bytes)) - } -} - -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Isometry; - use crate::{base::Scalar, geometry::Translation}; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Isometry - where - T::Archived: Scalar, - { - type Archived = Isometry; - type Resolver = (R::Resolver, as Archive>::Resolver); - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.rotation.resolve( - pos + offset_of!(Self::Archived, rotation), - resolver.0, - project_struct!(out: Self::Archived => rotation), - ); - self.translation.resolve( - pos + offset_of!(Self::Archived, translation), - resolver.1, - project_struct!(out: Self::Archived => translation), - ); - } - } - - impl, R: Serialize, S: Fallible + ?Sized, const D: usize> - Serialize for Isometry - where - T::Archived: Scalar, - { - fn serialize(&self, serializer: &mut S) -> Result { - Ok(( - self.rotation.serialize(serializer)?, - self.translation.serialize(serializer)?, - )) - } - } - - impl - Deserialize, _D> for Isometry - where - T::Archived: Scalar + Deserialize, - R::Archived: Scalar + Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Isometry { - rotation: self.rotation.deserialize(deserializer)?, - translation: self.translation.deserialize(deserializer)?, - }) - } - } -} - impl hash::Hash for Isometry where Owned>: hash::Hash, diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index 18a7852d..7348f676 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -19,10 +19,12 @@ use crate::geometry::{Point3, Projective3}; /// A 3D orthographic projection stored as a homogeneous 4x4 matrix. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Orthographic3 { matrix: Matrix4, @@ -319,6 +321,7 @@ impl Orthographic3 { /// The left offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -336,6 +339,7 @@ impl Orthographic3 { /// The right offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -353,6 +357,7 @@ impl Orthographic3 { /// The bottom offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -370,6 +375,7 @@ impl Orthographic3 { /// The top offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -387,6 +393,7 @@ impl Orthographic3 { /// The near plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -404,6 +411,7 @@ impl Orthographic3 { /// The far plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -526,6 +534,7 @@ impl Orthographic3 { /// Sets the left offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -545,6 +554,7 @@ impl Orthographic3 { /// Sets the right offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -564,6 +574,7 @@ impl Orthographic3 { /// Sets the bottom offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -583,6 +594,7 @@ impl Orthographic3 { /// Sets the top offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -602,6 +614,7 @@ impl Orthographic3 { /// Sets the near plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -621,6 +634,7 @@ impl Orthographic3 { /// Sets the far plane offset of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -640,6 +654,7 @@ impl Orthographic3 { /// Sets the view cuboid offsets along the `x` axis. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -665,6 +680,7 @@ impl Orthographic3 { /// Sets the view cuboid offsets along the `y` axis. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; @@ -690,6 +706,7 @@ impl Orthographic3 { /// Sets the near and far plane offsets of the view cuboid. /// + /// # Example /// ``` /// # #[macro_use] extern crate approx; /// # use nalgebra::Orthographic3; diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index 59b7f9f2..351960bb 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -20,10 +20,12 @@ use crate::geometry::{Point3, Projective3}; /// A 3D perspective projection stored as a homogeneous 4x4 matrix. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Perspective3 { matrix: Matrix4, diff --git a/src/geometry/point.rs b/src/geometry/point.rs index 0953a9ce..306c18e5 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -3,15 +3,10 @@ use num::One; use std::cmp::Ordering; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::simd::SimdPartialOrd; use crate::base::allocator::Allocator; @@ -41,6 +36,11 @@ use std::mem::MaybeUninit; /// of said transformations for details. #[repr(C)] #[derive(Clone)] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct OPoint where DefaultAllocator: Allocator, @@ -74,12 +74,11 @@ where { } -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy - for OPoint +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for OPoint where DefaultAllocator: Allocator, - OVector: cust::memory::DeviceCopy, + OVector: cust_core::DeviceCopy, { } @@ -130,26 +129,6 @@ where } } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for OPoint -where - T: Scalar, - OVector: Abomonation, - DefaultAllocator: Allocator, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.coords.entomb(writer) - } - - fn extent(&self) -> usize { - self.coords.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.coords.exhume(bytes) - } -} - impl OPoint where DefaultAllocator: Allocator, @@ -292,6 +271,7 @@ where /// assert_eq!(it.next(), Some(2.0)); /// assert_eq!(it.next(), Some(3.0)); /// assert_eq!(it.next(), None); + /// ``` #[inline] pub fn iter( &self, @@ -318,6 +298,7 @@ where /// } /// /// assert_eq!(p, Point3::new(10.0, 20.0, 30.0)); + /// ``` #[inline] pub fn iter_mut( &mut self, diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index cae06251..f38dca6f 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -2,17 +2,12 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use num::Zero; use std::fmt; use std::hash::{Hash, Hasher}; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use crate::base::storage::Owned; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::{ClosedNeg, RealField}; use simba::simd::{SimdBool, SimdOption, SimdRealField}; @@ -28,10 +23,12 @@ use crate::geometry::{Point3, Rotation}; /// that may be used as a rotation. #[repr(C)] #[derive(Copy, Clone)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub struct Quaternion { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. pub coords: Vector4, @@ -77,24 +74,6 @@ where { } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Quaternion -where - Vector4: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.coords.entomb(writer) - } - - fn extent(&self) -> usize { - self.coords.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.coords.exhume(bytes) - } -} - #[cfg(feature = "serde-serialize-no-std")] impl Serialize for Quaternion where @@ -123,48 +102,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Quaternion; - use crate::base::Vector4; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Quaternion { - type Archived = Quaternion; - type Resolver = as Archive>::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.coords.resolve( - pos + offset_of!(Self::Archived, coords), - resolver, - project_struct!(out: Self::Archived => coords), - ); - } - } - - impl, S: Fallible + ?Sized> Serialize for Quaternion { - fn serialize(&self, serializer: &mut S) -> Result { - self.coords.serialize(serializer) - } - } - - impl Deserialize, D> for Quaternion - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { - Ok(Quaternion { - coords: self.coords.deserialize(deserializer)?, - }) - } - } -} - impl Quaternion where T::Element: SimdRealField, @@ -428,6 +365,7 @@ where /// let expected = Quaternion::new(-20.0, 0.0, 0.0, 0.0); /// let result = a.inner(&b); /// assert_relative_eq!(expected, result, epsilon = 1.0e-5); + /// ``` #[inline] #[must_use] pub fn inner(&self, other: &Self) -> Self { @@ -1068,8 +1006,8 @@ impl fmt::Display for Quaternion { /// A unit quaternions. May be used to represent a rotation. pub type UnitQuaternion = Unit>; -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy for UnitQuaternion {} +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for UnitQuaternion {} impl PartialEq for UnitQuaternion { #[inline] @@ -1253,8 +1191,7 @@ where /// Panics if the angle between both quaternion is 180 degrees (in which case the interpolation /// is not well-defined). Use `.try_slerp` instead to avoid the panic. /// - /// # Examples: - /// + /// # Example /// ``` /// # use nalgebra::geometry::UnitQuaternion; /// @@ -1476,7 +1413,6 @@ where /// Builds a rotation matrix from this unit quaternion. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1559,7 +1495,6 @@ where /// Converts this unit quaternion into its equivalent homogeneous transformation matrix. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1583,7 +1518,6 @@ where /// This is the same as the multiplication `self * pt`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1604,7 +1538,6 @@ where /// This is the same as the multiplication `self * v`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1625,7 +1558,6 @@ where /// point. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1648,7 +1580,6 @@ where /// vector. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -1669,7 +1600,6 @@ where /// vector. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index 2d5e5d24..2a8bf427 100755 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -2,8 +2,6 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use num::{One, Zero}; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -11,9 +9,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde-serialize-no-std")] use crate::base::storage::Owned; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::RealField; use simba::simd::SimdRealField; @@ -54,10 +49,12 @@ use crate::geometry::Point; /// * [Conversion to a matrix `matrix`, `to_homogeneous`…](#conversion-to-a-matrix) /// #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Rotation { matrix: SMatrix, @@ -94,25 +91,6 @@ where { } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Rotation -where - T: Scalar, - SMatrix: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.matrix.entomb(writer) - } - - fn extent(&self) -> usize { - self.matrix.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.matrix.exhume(bytes) - } -} - #[cfg(feature = "serde-serialize-no-std")] impl Serialize for Rotation where diff --git a/src/geometry/scale.rs b/src/geometry/scale.rs index b1d278d6..37da1ef0 100755 --- a/src/geometry/scale.rs +++ b/src/geometry/scale.rs @@ -2,15 +2,10 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use num::{One, Zero}; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use crate::base::allocator::Allocator; use crate::base::dimension::{DimNameAdd, DimNameSum, U1}; use crate::base::storage::Owned; @@ -22,10 +17,12 @@ use crate::geometry::Point; /// A scale which supports non-uniform scaling. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Scale { /// The scale coordinates, i.e., how much is multiplied to a point's coordinates when it is @@ -64,25 +61,6 @@ where { } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Scale -where - T: Scalar, - SVector: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.vector.entomb(writer) - } - - fn extent(&self) -> usize { - self.vector.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.vector.exhume(bytes) - } -} - #[cfg(feature = "serde-serialize-no-std")] impl Serialize for Scale where @@ -111,49 +89,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Scale; - use crate::base::SVector; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Scale { - type Archived = Scale; - type Resolver = as Archive>::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.vector.resolve( - pos + offset_of!(Self::Archived, vector), - resolver, - project_struct!(out: Self::Archived => vector), - ); - } - } - - impl, S: Fallible + ?Sized, const D: usize> Serialize for Scale { - fn serialize(&self, serializer: &mut S) -> Result { - self.vector.serialize(serializer) - } - } - - impl Deserialize, _D> - for Scale - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Scale { - vector: self.vector.deserialize(deserializer)?, - }) - } - } -} - impl Scale { /// Inverts `self`. /// diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index fd6b1d36..8c38ff1e 100755 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -3,15 +3,9 @@ use num::Zero; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; - #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::{RealField, SubsetOf}; use simba::simd::SimdRealField; @@ -24,10 +18,7 @@ use crate::geometry::{AbstractRotation, Isometry, Point, Translation}; /// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. #[repr(C)] #[derive(Debug, Copy, Clone)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", @@ -43,30 +34,17 @@ use crate::geometry::{AbstractRotation, Isometry, Point, Translation}; DefaultAllocator: Allocator>, Owned>: Deserialize<'de>")) )] +#[cfg_attr( + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] pub struct Similarity { /// The part of this similarity that does not include the scaling factor. pub isometry: Isometry, scaling: T, } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Similarity -where - Isometry: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.isometry.entomb(writer) - } - - fn extent(&self) -> usize { - self.isometry.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.isometry.exhume(bytes) - } -} - impl hash::Hash for Similarity where Owned>: hash::Hash, diff --git a/src/geometry/similarity_construction.rs b/src/geometry/similarity_construction.rs index 8d1d38b8..cabb1676 100644 --- a/src/geometry/similarity_construction.rs +++ b/src/geometry/similarity_construction.rs @@ -38,7 +38,6 @@ where /// Creates a new identity similarity. /// /// # Example - /// /// ``` /// # use nalgebra::{Similarity2, Point2, Similarity3, Point3}; /// @@ -95,7 +94,6 @@ where /// its axis passing through the point `p`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -146,7 +144,6 @@ where /// Creates a new similarity from a translation, a rotation, and an uniform scaling factor. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -188,7 +185,6 @@ where /// Creates a new similarity from a translation and a rotation angle. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -232,7 +228,6 @@ macro_rules! similarity_construction_impl( /// factor. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -288,7 +283,6 @@ macro_rules! similarity_construction_impl( /// to `eye - at`. Non-collinearity is not checked. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -316,7 +310,7 @@ macro_rules! similarity_construction_impl( Self::from_isometry(Isometry::<_, $Rot, 3>::face_towards(eye, target, up), scaling) } - /// Deprecated: Use [`SimilarityMatrix3::face_towards`] instead. + /// Deprecated: Use [`SimilarityMatrix3::face_towards`](Self::face_towards) instead. #[deprecated(note="renamed to `face_towards`")] pub fn new_observer_frames(eye: &Point3, target: &Point3, @@ -338,7 +332,6 @@ macro_rules! similarity_construction_impl( /// requirement of this parameter is to not be collinear to `target - eye`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; @@ -376,7 +369,6 @@ macro_rules! similarity_construction_impl( /// requirement of this parameter is to not be collinear to `target - eye`. /// /// # Example - /// /// ``` /// # #[macro_use] extern crate approx; /// # use std::f32; diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index b0b5cced..2a7ca112 100755 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -60,26 +60,17 @@ where /// Tag representing the most general (not necessarily inversible) `Transform` type. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TGeneral {} /// Tag representing the most general inversible `Transform` type. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TProjective {} /// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`. #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -#[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) -)] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] pub enum TAffine {} impl TCategory for TGeneral { @@ -207,13 +198,13 @@ where { } -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl - cust::memory::DeviceCopy for Transform +#[cfg(feature = "cuda")] +unsafe impl + cust_core::DeviceCopy for Transform where Const: DimNameAdd, DefaultAllocator: Allocator, U1>, DimNameSum, U1>>, - Owned, U1>, DimNameSum, U1>>: cust::memory::DeviceCopy, + Owned, U1>, DimNameSum, U1>>: cust_core::DeviceCopy, { } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 1ce8cba3..bef66f68 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -2,15 +2,10 @@ use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use num::{One, Zero}; use std::fmt; use std::hash; -#[cfg(feature = "abomonation-serialize")] -use std::io::{Result as IOResult, Write}; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "abomonation-serialize")] -use abomonation::Abomonation; - use simba::scalar::{ClosedAdd, ClosedNeg, ClosedSub}; use crate::base::allocator::Allocator; @@ -22,10 +17,12 @@ use crate::geometry::Point; /// A translation. #[repr(C)] +#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))] #[cfg_attr( - all(not(target_os = "cuda"), feature = "cuda"), - derive(cust::DeviceCopy) + feature = "rkyv-serialize-no-std", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))] #[derive(Copy, Clone)] pub struct Translation { /// The translation coordinates, i.e., how much is added to a point's coordinates when it is @@ -64,25 +61,6 @@ where { } -#[cfg(feature = "abomonation-serialize")] -impl Abomonation for Translation -where - T: Scalar, - SVector: Abomonation, -{ - unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { - self.vector.entomb(writer) - } - - fn extent(&self) -> usize { - self.vector.extent() - } - - unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { - self.vector.exhume(bytes) - } -} - #[cfg(feature = "serde-serialize-no-std")] impl Serialize for Translation where @@ -111,49 +89,6 @@ where } } -#[cfg(feature = "rkyv-serialize-no-std")] -mod rkyv_impl { - use super::Translation; - use crate::base::SVector; - use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize}; - - impl Archive for Translation { - type Archived = Translation; - type Resolver = as Archive>::Resolver; - - fn resolve( - &self, - pos: usize, - resolver: Self::Resolver, - out: &mut core::mem::MaybeUninit, - ) { - self.vector.resolve( - pos + offset_of!(Self::Archived, vector), - resolver, - project_struct!(out: Self::Archived => vector), - ); - } - } - - impl, S: Fallible + ?Sized, const D: usize> Serialize for Translation { - fn serialize(&self, serializer: &mut S) -> Result { - self.vector.serialize(serializer) - } - } - - impl Deserialize, _D> - for Translation - where - T::Archived: Deserialize, - { - fn deserialize(&self, deserializer: &mut _D) -> Result, _D::Error> { - Ok(Translation { - vector: self.vector.deserialize(deserializer)?, - }) - } - } -} - impl Translation { /// Creates a new translation from the given vector. #[inline] @@ -255,6 +190,7 @@ impl Translation { /// let t = Translation3::new(1.0, 2.0, 3.0); /// let transformed_point = t.transform_point(&Point3::new(4.0, 5.0, 6.0)); /// assert_eq!(transformed_point, Point3::new(5.0, 7.0, 9.0)); + /// ``` #[inline] #[must_use] pub fn transform_point(&self, pt: &Point) -> Point { @@ -271,6 +207,7 @@ impl Translation { /// let t = Translation3::new(1.0, 2.0, 3.0); /// let transformed_point = t.inverse_transform_point(&Point3::new(4.0, 5.0, 6.0)); /// assert_eq!(transformed_point, Point3::new(3.0, 3.0, 3.0)); + /// ``` #[inline] #[must_use] pub fn inverse_transform_point(&self, pt: &Point) -> Point { diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 48405dd4..efe0dac2 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -31,8 +31,8 @@ use std::cmp::{Eq, PartialEq}; /// * [Conversion to a matrix `to_rotation_matrix`, `to_homogeneous`…](#conversion-to-a-matrix) pub type UnitComplex = Unit>; -#[cfg(all(not(target_os = "cuda"), feature = "cuda"))] -unsafe impl cust::memory::DeviceCopy for UnitComplex {} +#[cfg(feature = "cuda")] +unsafe impl cust_core::DeviceCopy for UnitComplex {} impl PartialEq for UnitComplex { #[inline] @@ -410,7 +410,8 @@ where #[inline] #[must_use] pub fn slerp(&self, other: &Self, t: T) -> Self { - Self::new(self.angle() * (T::one() - t.clone()) + other.angle() * t) + let delta = other / self; + self * Self::new(delta.angle() * t) } } diff --git a/src/lib.rs b/src/lib.rs index 28701cfa..1ee1a3ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,12 +78,13 @@ an optimized set of tools for computer graphics and physics. Those features incl unused_mut, unused_parens, unused_qualifications, - unused_results, rust_2018_idioms, rust_2018_compatibility, future_incompatible, missing_copy_implementations )] +#![cfg_attr(feature = "rkyv-serialize-no-std", warn(unused_results))] // TODO: deny this once bytecheck stops generating warnings. +#![cfg_attr(not(feature = "rkyv-serialize-no-std"), deny(unused_results))] #![doc( html_favicon_url = "https://nalgebra.org/img/favicon.ico", html_root_url = "https://docs.rs/nalgebra/0.25.0" @@ -246,8 +247,8 @@ pub fn min(a: T, b: T) -> T { /// The absolute value of `a`. /// -/// Deprecated: Use [`Matrix::abs`] or [`RealField::abs`] instead. -#[deprecated(note = "use the inherent method `Matrix::abs` or `RealField::abs` instead")] +/// Deprecated: Use [`Matrix::abs`] or [`ComplexField::abs`] instead. +#[deprecated(note = "use the inherent method `Matrix::abs` or `ComplexField::abs` instead")] #[inline] pub fn abs(a: &T) -> T { a.abs() diff --git a/src/linalg/svd.rs b/src/linalg/svd.rs index 3f945a65..06bae4a3 100644 --- a/src/linalg/svd.rs +++ b/src/linalg/svd.rs @@ -98,7 +98,7 @@ where matrix, compute_u, compute_v, - T::RealField::default_epsilon(), + T::RealField::default_epsilon() * crate::convert(5.0), 0, ) .unwrap() @@ -888,13 +888,13 @@ fn compute_2x2_uptrig_svd( v_t = Some(csv.clone()); } - if compute_u { - let cu = (m11.scale(csv.c()) + m12 * csv.s()) / v1.clone(); - let su = (m22 * csv.s()) / v1.clone(); - let (csu, sgn_u) = GivensRotation::new(cu, su); + let cu = (m11.scale(csv.c()) + m12 * csv.s()) / v1.clone(); + let su = (m22 * csv.s()) / v1.clone(); + let (csu, sgn_u) = GivensRotation::new(cu, su); + v1 *= sgn_u.clone(); + v2 *= sgn_u; - v1 *= sgn_u.clone(); - v2 *= sgn_u; + if compute_u { u = Some(csu); } } diff --git a/src/third_party/glam/common/glam_isometry.rs b/src/third_party/glam/common/glam_isometry.rs index 3a8d4961..7b188c39 100644 --- a/src/third_party/glam/common/glam_isometry.rs +++ b/src/third_party/glam/common/glam_isometry.rs @@ -1,5 +1,5 @@ use super::glam::{DMat3, DMat4, DQuat, DVec2, DVec3, Mat3, Mat4, Quat, Vec2, Vec3}; -use crate::{Isometry2, Isometry3, Matrix3, Matrix4, Translation3, UnitQuaternion, Vector2}; +use crate::{Isometry2, Isometry3, Matrix3, Matrix4, Translation3, UnitQuaternion}; use std::convert::TryFrom; impl From> for Mat3 { @@ -36,18 +36,18 @@ impl From> for (DVec3, DQuat) { } } -impl From> for (Vec3, Quat) { - fn from(iso: Isometry2) -> (Vec3, Quat) { - let tra = Vec3::new(iso.translation.x, iso.translation.y, 0.0); - let rot = Quat::from_axis_angle(Vec3::Z, iso.rotation.angle()); +impl From> for (Vec2, f32) { + fn from(iso: Isometry2) -> (Vec2, f32) { + let tra = Vec2::new(iso.translation.x, iso.translation.y); + let rot = iso.rotation.angle(); (tra, rot) } } -impl From> for (DVec3, DQuat) { - fn from(iso: Isometry2) -> (DVec3, DQuat) { - let tra = DVec3::new(iso.translation.x, iso.translation.y, 0.0); - let rot = DQuat::from_axis_angle(DVec3::Z, iso.rotation.angle()); +impl From> for (DVec2, f64) { + fn from(iso: Isometry2) -> (DVec2, f64) { + let tra = DVec2::new(iso.translation.x, iso.translation.y); + let rot = iso.rotation.angle(); (tra, rot) } } @@ -64,30 +64,6 @@ impl From<(DVec3, DQuat)> for Isometry3 { } } -impl From<(Vec3, Quat)> for Isometry2 { - fn from((tra, rot): (Vec3, Quat)) -> Self { - Isometry2::new([tra.x, tra.y].into(), rot.to_axis_angle().1) - } -} - -impl From<(DVec3, DQuat)> for Isometry2 { - fn from((tra, rot): (DVec3, DQuat)) -> Self { - Isometry2::new([tra.x, tra.y].into(), rot.to_axis_angle().1) - } -} - -impl From<(Vec2, Quat)> for Isometry2 { - fn from((tra, rot): (Vec2, Quat)) -> Self { - Isometry2::new(tra.into(), rot.to_axis_angle().1) - } -} - -impl From<(DVec2, DQuat)> for Isometry2 { - fn from((tra, rot): (DVec2, DQuat)) -> Self { - Isometry2::new(tra.into(), rot.to_axis_angle().1) - } -} - impl From<(Vec2, f32)> for Isometry2 { fn from((tra, rot): (Vec2, f32)) -> Self { Isometry2::new(tra.into(), rot) @@ -112,18 +88,6 @@ impl From for Isometry3 { } } -impl From for Isometry2 { - fn from(rot: Quat) -> Self { - Isometry2::new(Vector2::zeros(), rot.to_axis_angle().1) - } -} - -impl From for Isometry2 { - fn from(rot: DQuat) -> Self { - Isometry2::new(Vector2::zeros(), rot.to_axis_angle().1) - } -} - impl From for Isometry3 { fn from(tra: Vec3) -> Self { Isometry3::from_parts(tra.into(), UnitQuaternion::identity()) @@ -148,18 +112,6 @@ impl From for Isometry2 { } } -impl From for Isometry2 { - fn from(tra: Vec3) -> Self { - Isometry2::new([tra.x, tra.y].into(), 0.0) - } -} - -impl From for Isometry2 { - fn from(tra: DVec3) -> Self { - Isometry2::new([tra.x, tra.y].into(), 0.0) - } -} - impl TryFrom for Isometry2 { type Error = (); diff --git a/src/third_party/glam/common/glam_matrix.rs b/src/third_party/glam/common/glam_matrix.rs index 80f88054..fa9f713f 100644 --- a/src/third_party/glam/common/glam_matrix.rs +++ b/src/third_party/glam/common/glam_matrix.rs @@ -3,7 +3,11 @@ use super::glam::{ Mat4, UVec2, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec4, }; use crate::storage::RawStorage; -use crate::{Matrix, Matrix2, Matrix3, Matrix4, Vector, Vector2, Vector3, Vector4, U2, U3, U4}; +use crate::{ + Matrix, Matrix2, Matrix3, Matrix4, Unit, UnitVector2, UnitVector3, UnitVector4, Vector, + Vector2, Vector3, Vector4, U2, U3, U4, +}; +use std::convert::TryFrom; macro_rules! impl_vec_conversion( ($N: ty, $Vec2: ty, $Vec3: ty, $Vec4: ty) => { @@ -66,6 +70,63 @@ impl_vec_conversion!(i32, IVec2, IVec3, IVec4); impl_vec_conversion!(u32, UVec2, UVec3, UVec4); impl_vec_conversion!(bool, BVec2, BVec3, BVec4); +const ERR: &'static str = "Normalization failed."; + +macro_rules! impl_unit_vec_conversion( + ($N: ty, $Vec2: ty, $Vec3: ty, $Vec4: ty) => { + impl TryFrom<$Vec2> for UnitVector2<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec2) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec2 + { + #[inline] + fn from(e: UnitVector2<$N>) -> $Vec2 { + e.into_inner().into() + } + } + + impl TryFrom<$Vec3> for UnitVector3<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec3) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec3 + { + #[inline] + fn from(e: UnitVector3<$N>) -> $Vec3 { + e.into_inner().into() + } + } + + impl TryFrom<$Vec4> for UnitVector4<$N> { + type Error = &'static str; + #[inline] + fn try_from(e: $Vec4) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } + } + + impl From> for $Vec4 + { + #[inline] + fn from(e: UnitVector4<$N>) -> $Vec4 { + e.into_inner().into() + } + } + } +); + +impl_unit_vec_conversion!(f32, Vec2, Vec3, Vec4); +impl_unit_vec_conversion!(f64, DVec2, DVec3, DVec4); + impl From for Vector3 { #[inline] fn from(e: Vec3A) -> Vector3 { @@ -83,6 +144,21 @@ where } } +impl TryFrom for UnitVector3 { + type Error = &'static str; + #[inline] + fn try_from(e: Vec3A) -> Result { + Unit::try_new(e.into(), 0.0).ok_or(ERR) + } +} + +impl From> for Vec3A { + #[inline] + fn from(e: UnitVector3) -> Vec3A { + e.into_inner().into() + } +} + impl From for Matrix2 { #[inline] fn from(e: Mat2) -> Matrix2 { diff --git a/src/third_party/glam/mod.rs b/src/third_party/glam/mod.rs index b1006e91..ae2c4514 100644 --- a/src/third_party/glam/mod.rs +++ b/src/third_party/glam/mod.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "glam013")] -mod v013; #[cfg(feature = "glam014")] mod v014; #[cfg(feature = "glam015")] @@ -10,3 +8,7 @@ mod v016; mod v017; #[cfg(feature = "glam018")] mod v018; +#[cfg(feature = "glam019")] +mod v019; +#[cfg(feature = "glam020")] +mod v020; diff --git a/src/third_party/glam/v013/mod.rs b/src/third_party/glam/v019/mod.rs similarity index 93% rename from src/third_party/glam/v013/mod.rs rename to src/third_party/glam/v019/mod.rs index 4787fb21..8b155607 100644 --- a/src/third_party/glam/v013/mod.rs +++ b/src/third_party/glam/v019/mod.rs @@ -15,4 +15,4 @@ mod glam_translation; #[path = "../common/glam_unit_complex.rs"] mod glam_unit_complex; -pub(self) use glam013 as glam; +pub(self) use glam019 as glam; diff --git a/src/third_party/glam/v020/mod.rs b/src/third_party/glam/v020/mod.rs new file mode 100644 index 00000000..99fbc602 --- /dev/null +++ b/src/third_party/glam/v020/mod.rs @@ -0,0 +1,18 @@ +#[path = "../common/glam_isometry.rs"] +mod glam_isometry; +#[path = "../common/glam_matrix.rs"] +mod glam_matrix; +#[path = "../common/glam_point.rs"] +mod glam_point; +#[path = "../common/glam_quaternion.rs"] +mod glam_quaternion; +#[path = "../common/glam_rotation.rs"] +mod glam_rotation; +#[path = "../common/glam_similarity.rs"] +mod glam_similarity; +#[path = "../common/glam_translation.rs"] +mod glam_translation; +#[path = "../common/glam_unit_complex.rs"] +mod glam_unit_complex; + +pub(self) use glam020 as glam; diff --git a/tests/core/abomonation.rs b/tests/core/abomonation.rs deleted file mode 100644 index 5148db81..00000000 --- a/tests/core/abomonation.rs +++ /dev/null @@ -1,51 +0,0 @@ -use abomonation::{decode, encode, Abomonation}; -use na::{ - DMatrix, Isometry3, IsometryMatrix3, Matrix3x4, Point3, Quaternion, Rotation3, Similarity3, - SimilarityMatrix3, Translation3, -}; -use rand::random; - -#[test] -fn abomonate_dmatrix() { - assert_encode_and_decode(DMatrix::::new_random(3, 5)); -} - -macro_rules! test_abomonation( - ($($test: ident, $ty: ty);* $(;)*) => {$( - #[test] - fn $test() { - assert_encode_and_decode(random::<$ty>()); - } - )*} -); - -test_abomonation! { - abomonate_matrix3x4, Matrix3x4; - abomonate_point3, Point3; - abomonate_translation3, Translation3; - abomonate_rotation3, Rotation3; - abomonate_isometry3, Isometry3; - abomonate_isometry_matrix3, IsometryMatrix3; - abomonate_similarity3, Similarity3; - abomonate_similarity_matrix3, SimilarityMatrix3; - abomonate_quaternion, Quaternion; -} - -fn assert_encode_and_decode(original_data: T) { - // Hold on to a clone for later comparison - let data = original_data.clone(); - - // Encode - let mut bytes = Vec::new(); - unsafe { - let _ = encode(&original_data, &mut bytes); - } - - // Drop the original, so that dangling pointers are revealed by the test - drop(original_data); - - if let Some((result, rest)) = unsafe { decode::(&mut bytes) } { - assert!(result == &data); - assert!(rest.len() == 0, "binary data was not decoded completely"); - } -} diff --git a/tests/core/mod.rs b/tests/core/mod.rs index c03461dc..5fb8c82b 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "abomonation-serialize")] -mod abomonation; mod blas; mod cg; mod conversion; diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 9a29772e..84bba676 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -32,7 +32,9 @@ fn quaternion_euler_angles_issue_494() { #[cfg(feature = "proptest-support")] mod proptest_tests { + use approx::AbsDiffEq; use na::{self, Rotation2, Rotation3, Unit}; + use na::{UnitComplex, UnitQuaternion}; use simba::scalar::RealField; use std::f64; @@ -229,5 +231,74 @@ mod proptest_tests { prop_assert_eq!(r, Rotation3::identity()) } } + + // + //In general, `slerp(a,b,t)` should equal `(b/a)^t * a` even though in practice, + //we may not use that formula directly for complex numbers or quaternions + // + + #[test] + fn slerp_powf_agree_2(a in unit_complex(), b in unit_complex(), t in PROPTEST_F64) { + let z1 = a.slerp(&b, t); + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + + #[test] + fn slerp_powf_agree_3(a in unit_quaternion(), b in unit_quaternion(), t in PROPTEST_F64) { + if let Some(z1) = a.try_slerp(&b, t, f64::default_epsilon()) { + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + } + + // + //when not antipodal, slerp should always take the shortest path between two orientations + // + + #[test] + fn slerp_takes_shortest_path_2( + z in unit_complex(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if dtheta.abs() != f64::pi() { + + //make two complex numbers separated by an angle between -pi and pi + let (z1, z2) = (z, z * UnitComplex::new(dtheta)); + let z3 = z1.slerp(&z2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let a1 = z3.angle(); + let a2 = na::wrap(z1.angle() + dtheta*t, -f64::pi(), f64::pi()); + + prop_assert!(relative_eq!(a1, a2, epsilon=1e-10)); + } + + } + + #[test] + fn slerp_takes_shortest_path_3( + q in unit_quaternion(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if let Some(axis) = q.axis() { + + //make two quaternions separated by an angle between -pi and pi + let (q1, q2) = (q, q * UnitQuaternion::from_axis_angle(&axis, dtheta)); + let q3 = q1.slerp(&q2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let q4 = q1 * UnitQuaternion::from_axis_angle(&axis, dtheta*t); + prop_assert!(relative_eq!(q3, q4, epsilon=1e-10)); + + } + + } + + } } diff --git a/tests/lib.rs b/tests/lib.rs index 11c1e23f..546aa8a7 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -9,8 +9,6 @@ compile_error!( Example: `cargo test --features debug,compare,rand,macros`" ); -#[cfg(feature = "abomonation-serialize")] -extern crate abomonation; #[cfg(all(feature = "debug", feature = "compare", feature = "rand"))] #[macro_use] extern crate approx; diff --git a/tests/linalg/svd.rs b/tests/linalg/svd.rs index deb3d38d..900901ad 100644 --- a/tests/linalg/svd.rs +++ b/tests/linalg/svd.rs @@ -460,3 +460,42 @@ fn svd_sorted() { epsilon = 1.0e-5 ); } + +#[test] +// Exercises bug reported in issue #983 of nalgebra (https://github.com/dimforge/nalgebra/issues/983) +fn svd_regression_issue_983() { + let m = nalgebra::dmatrix![ + 10.74785316637712f64, -5.994983325167452, -6.064492921857296; + -4.149751381521569, 20.654504205822462, -4.470436210703133; + -22.772715014220207, -1.4554372570788008, 18.108113992170573 + ] + .transpose(); + let svd1 = m.clone().svd(true, true); + let svd2 = m.clone().svd(false, true); + let svd3 = m.clone().svd(true, false); + let svd4 = m.svd(false, false); + + assert_relative_eq!(svd1.singular_values, svd2.singular_values, epsilon = 1e-9); + assert_relative_eq!(svd1.singular_values, svd3.singular_values, epsilon = 1e-9); + assert_relative_eq!(svd1.singular_values, svd4.singular_values, epsilon = 1e-9); + assert_relative_eq!( + svd1.singular_values, + nalgebra::dvector![3.16188022e+01, 2.23811978e+01, 0.], + epsilon = 1e-6 + ); +} + +#[test] +// Exercises bug reported in issue #1072 of nalgebra (https://github.com/dimforge/nalgebra/issues/1072) +fn svd_regression_issue_1072() { + let x = nalgebra::dmatrix![-6.206610118536945f64, -3.67612186839874; -1.2755730783423473, 6.047238193479124]; + let mut x_svd = x.svd(true, true); + x_svd.singular_values = nalgebra::dvector![1.0, 0.0]; + let y = x_svd.recompose().unwrap(); + let y_svd = y.svd(true, true); + assert_relative_eq!( + y_svd.singular_values, + nalgebra::dvector![1.0, 0.0], + epsilon = 1e-9 + ); +}