From 85a64fb517c9783663ba1c241092fa7b2060276f Mon Sep 17 00:00:00 2001 From: Jenan Wise Date: Mon, 22 Jun 2020 15:29:13 -0700 Subject: [PATCH 01/55] More verbose DMatrix dim asserts where possible. Previously, most dimension mismatch asserts used raw `assert!` and did not include the mismatching dimensions in the panic message. When using dynamic matrices, this led to somewhat-opaque panics such as: ```rust let m1 = DMatrix::::zeros(2, 3); let m2 = DMatrix::::zeros(5, 10); m1 + m2 // panic: Matrix addition/subtraction dimensions mismatch. ``` This patch adds dimension information in the panic messages wherever doing so did not add additional bounds checks, mostly by simply changing `assert!(a == b, ...)` cases to `assert_eq!`. After: ```rust // panic: assertion failed: `(left == right)` // left: `(2, 3)`, // right: `(5, 10)`: Matrix addition/subtraction dimensions mismatch. ``` Note that the `gemv` and `ger` were not updated, as they are called from within other functions on subset matricies -- e.g., `gemv` is called from `gemm` which is called from `mul_to` . Including dimension information in the `gemv` panic messages would be confusing to `mul` / `mul_to` users, because it would include dimensions of the column vectors that `gemm` passes to `gemv` rather than of the original `mul` arguments. A fix would be to add bounds checks to `mul_to`, but that may have performance and redundancy implications, so is left to another patch. --- src/base/blas.rs | 9 ++++-- src/base/matrix.rs | 70 ++++++++++++++++++++++++++++------------------ src/base/ops.rs | 28 +++++++++++++------ 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/base/blas.rs b/src/base/blas.rs index add17af2..1d61b9d2 100644 --- a/src/base/blas.rs +++ b/src/base/blas.rs @@ -284,7 +284,9 @@ where { assert!( self.nrows() == rhs.nrows(), - "Dot product dimensions mismatch." + "Dot product dimensions mismatch for shapes {:?} and {:?}: left rows != right rows.", + self.shape(), + rhs.shape(), ); // So we do some special cases for common fixed-size vectors of dimension lower than 8 @@ -496,8 +498,9 @@ where ShapeConstraint: DimEq + DimEq, { let (nrows, ncols) = self.shape(); - assert!( - (ncols, nrows) == rhs.shape(), + assert_eq!( + (ncols, nrows), + rhs.shape(), "Transposed dot product dimension mismatch." ); diff --git a/src/base/matrix.rs b/src/base/matrix.rs index e4821bf8..d30773a3 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -538,8 +538,9 @@ impl> Matrix { let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) }; - assert!( - (nrows.value(), ncols.value()) == rhs.shape(), + assert_eq!( + (nrows.value(), ncols.value()), + rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); @@ -578,9 +579,14 @@ impl> Matrix { let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) }; - assert!( - (nrows.value(), ncols.value()) == b.shape() - && (nrows.value(), ncols.value()) == c.shape(), + assert_eq!( + (nrows.value(), ncols.value()), + b.shape(), + "Matrix simultaneous traversal error: dimension mismatch." + ); + assert_eq!( + (nrows.value(), ncols.value()), + c.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); @@ -636,8 +642,9 @@ impl> Matrix { let mut res = init; - assert!( - (nrows.value(), ncols.value()) == rhs.shape(), + assert_eq!( + (nrows.value(), ncols.value()), + rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); @@ -884,8 +891,9 @@ impl> Matrix { { let (nrows, ncols) = self.shape(); - assert!( - (nrows, ncols) == rhs.shape(), + assert_eq!( + (nrows, ncols), + rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); @@ -922,12 +930,14 @@ impl> Matrix { { let (nrows, ncols) = self.shape(); - assert!( - (nrows, ncols) == b.shape(), + assert_eq!( + (nrows, ncols), + b.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); - assert!( - (nrows, ncols) == c.shape(), + assert_eq!( + (nrows, ncols), + c.shape(), "Matrix simultaneous traversal error: dimension mismatch." ); @@ -1427,8 +1437,9 @@ where #[inline] fn lt(&self, right: &Self) -> bool { - assert!( - self.shape() == right.shape(), + assert_eq!( + self.shape(), + right.shape(), "Matrix comparison error: dimensions mismatch." ); self.iter().zip(right.iter()).all(|(a, b)| a.lt(b)) @@ -1436,8 +1447,9 @@ where #[inline] fn le(&self, right: &Self) -> bool { - assert!( - self.shape() == right.shape(), + assert_eq!( + self.shape(), + right.shape(), "Matrix comparison error: dimensions mismatch." ); self.iter().zip(right.iter()).all(|(a, b)| a.le(b)) @@ -1445,8 +1457,9 @@ where #[inline] fn gt(&self, right: &Self) -> bool { - assert!( - self.shape() == right.shape(), + assert_eq!( + self.shape(), + right.shape(), "Matrix comparison error: dimensions mismatch." ); self.iter().zip(right.iter()).all(|(a, b)| a.gt(b)) @@ -1454,8 +1467,9 @@ where #[inline] fn ge(&self, right: &Self) -> bool { - assert!( - self.shape() == right.shape(), + assert_eq!( + self.shape(), + right.shape(), "Matrix comparison error: dimensions mismatch." ); self.iter().zip(right.iter()).all(|(a, b)| a.ge(b)) @@ -1602,7 +1616,11 @@ impl + SameNumberOfColumns, { - assert!(self.shape() == (2, 1), "2D perpendicular product "); + assert!( + self.shape() == (2, 1), + "2D perpendicular product requires (2, 1) vector but found {:?}", + self.shape() + ); unsafe { self.get_unchecked((0, 0)).inlined_clone() * b.get_unchecked((1, 0)).inlined_clone() @@ -1626,13 +1644,11 @@ impl + SameNumberOfColumns, { let shape = self.shape(); - assert!( - shape == b.shape(), - "Vector cross product dimension mismatch." - ); + assert_eq!(shape, b.shape(), "Vector cross product dimension mismatch."); assert!( (shape.0 == 3 && shape.1 == 1) || (shape.0 == 1 && shape.1 == 3), - "Vector cross product dimension mismatch." + "Vector cross product dimension mismatch: must be (3, 1) or (1, 3) but found {:?}.", + shape ); if shape.0 == 3 { diff --git a/src/base/ops.rs b/src/base/ops.rs index 12b26d1a..7f0c6e4e 100644 --- a/src/base/ops.rs +++ b/src/base/ops.rs @@ -154,8 +154,8 @@ macro_rules! componentwise_binop_impl( out: &mut Matrix) where SB: Storage, SC: StorageMut { - assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); - assert!(self.shape() == out.shape(), "Matrix addition/subtraction output dimensions mismatch."); + assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + assert_eq!(self.shape(), out.shape(), "Matrix addition/subtraction output dimensions mismatch."); // This is the most common case and should be deduced at compile-time. // FIXME: use specialization instead? @@ -188,7 +188,7 @@ macro_rules! componentwise_binop_impl( C2: Dim, SA: StorageMut, SB: Storage { - assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); // This is the most common case and should be deduced at compile-time. // FIXME: use specialization instead? @@ -218,7 +218,7 @@ macro_rules! componentwise_binop_impl( where R2: Dim, C2: Dim, SB: StorageMut { - assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); // This is the most common case and should be deduced at compile-time. // FIXME: use specialization instead? @@ -277,7 +277,7 @@ macro_rules! componentwise_binop_impl( #[inline] fn $method(self, rhs: &'b Matrix) -> Self::Output { - assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); let mut res = self.into_owned_sum::(); res.$method_assign_statically_unchecked(rhs); res @@ -296,7 +296,7 @@ macro_rules! componentwise_binop_impl( #[inline] fn $method(self, rhs: Matrix) -> Self::Output { let mut rhs = rhs.into_owned_sum::(); - assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + assert_eq!(self.shape(), rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); self.$method_assign_statically_unchecked_rhs(&mut rhs); rhs } @@ -728,11 +728,21 @@ where assert!( nrows1 == nrows2, - "Matrix multiplication dimensions mismatch." + "Matrix multiplication dimensions mismatch {:?} and {:?}: left rows != right rows.", + self.shape(), + rhs.shape() ); assert!( - nrows3 == ncols1 && ncols3 == ncols2, - "Matrix multiplication output dimensions mismatch." + ncols1 == nrows3, + "Matrix multiplication output dimensions mismatch {:?} and {:?}: left cols != right rows.", + self.shape(), + out.shape() + ); + assert!( + ncols2 == ncols3, + "Matrix multiplication output dimensions mismatch {:?} and {:?}: left cols != right cols", + rhs.shape(), + out.shape() ); for i in 0..ncols1 { From eca17586a62304995fc9d774f6d25ae3bbf0c6c6 Mon Sep 17 00:00:00 2001 From: Jenan Wise Date: Mon, 22 Jun 2020 19:13:43 -0700 Subject: [PATCH 02/55] Remove rustfmt.toml. The CI rustfmt checker uses `stable` rust, not `nightly`, but the config options in `rustfmt.toml` only work on nightly -- on stable, there are ignored with warnings. This means that devs running stable get a lot of warnings and devs running nightly will accidentally format in a way that the CI checker forbids. (specifically, using `where_single_line`). E.g.: ``` $ cargo +stable fmt -- --check Warning: can't set `indent_style = Block`, unstable features are only available in nightly channel. Warning: can't set `where_single_line = true`, unstable features are only available in nightly channel. ...etc $ cargo +nightly fmt -- --check -where - N: RealField, -{ +where N: RealField { ...etc ``` Just removing the toml fixes this problem. --- rustfmt.toml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 8dc8e61a..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -unstable_features = true -indent_style = "Block" -where_single_line = true \ No newline at end of file From 7a42513c1ae18317a5c3f971c5bbf6ef9d597eda Mon Sep 17 00:00:00 2001 From: Jenan Wise Date: Tue, 23 Jun 2020 09:48:23 -0700 Subject: [PATCH 03/55] Add empty rustfmt.toml. Previous commit removed it entirely, but we should have an empty config file instead to indicate that we use the base config. --- rustfmt.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..e69de29b From f6730dac1fe2c911ba1022e0347fd520f77c2e18 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Sun, 11 Aug 2019 18:26:12 +0200 Subject: [PATCH 04/55] Basic matrixcompare functionality --- Cargo.toml | 5 +++ examples/matrixcompare.rs | 79 ++++++++++++++++++++++++++++++++++++ src/base/matrix.rs | 24 +++++++++++ tests/core/matrixcompare.rs | 81 +++++++++++++++++++++++++++++++++++++ tests/core/mod.rs | 3 ++ 5 files changed, 192 insertions(+) create mode 100644 examples/matrixcompare.rs create mode 100644 tests/core/matrixcompare.rs diff --git a/Cargo.toml b/Cargo.toml index 4adf2ab1..fe347ed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ sparse = [ ] debug = [ "approx/num-complex", "rand/std" ] alloc = [ ] io = [ "pest", "pest_derive" ] +compare = [ "matrixcompare-core" ] [dependencies] typenum = "1.11" @@ -50,6 +51,7 @@ mint = { version = "0.5", optional = true } quickcheck = { version = "0.9", optional = true } pest = { version = "2.0", optional = true } pest_derive = { version = "2.0", optional = true } +matrixcompare-core = { version = "0.1", optional = true } [dev-dependencies] serde_json = "1.0" @@ -61,6 +63,9 @@ rand_isaac = "0.2" ### https://github.com/rust-lang/cargo/issues/4866 #criterion = "0.2.10" +# For matrix comparison macro +matrixcompare = "0.1" + [workspace] members = [ "nalgebra-lapack", "nalgebra-glm" ] diff --git a/examples/matrixcompare.rs b/examples/matrixcompare.rs new file mode 100644 index 00000000..60c632d3 --- /dev/null +++ b/examples/matrixcompare.rs @@ -0,0 +1,79 @@ +extern crate nalgebra as na; + +#[macro_use] +extern crate quickcheck; + +use na::{U3, U4, MatrixMN}; +use matrixcompare::compare_matrices; +use matrixcompare::comparators::{ExactElementwiseComparator, AbsoluteElementwiseComparator}; + +fn compare_integers_fail() { + println!("Comparing two integer matrices."); + + let a = MatrixMN::<_, U3, U4>::from_row_slice(&[ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, -2, 11 + ]); + + let b = MatrixMN::<_, U3, U4>::from_row_slice(&[ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11 + ]); + + if let Some(msg) = compare_matrices(a, b, &ExactElementwiseComparator).panic_message() { + println!("{}", msg); + } +} + +fn compare_different_size() { + println!("Comparing matrices of different size."); + let a = MatrixMN::<_, U3, U3>::from_row_slice(&[ + 0, 1, 2, + 4, 5, 6, + 8, 9, 10, + ]); + + let b = MatrixMN::<_, U3, U4>::from_row_slice(&[ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11 + ]); + + if let Some(msg) = compare_matrices(a, b, &ExactElementwiseComparator).panic_message() { + println!("{}", msg); + } +} + +fn compare_f64_abs_tol_fail() { + println!("Comparing two f64 matrices."); + + let a = MatrixMN::::from_row_slice(&[ + 0.0, 1.0, 2.0 + 1e-10, + 4.0, 5.0, 6.0, + 8.0, 9.0, 10.0, + ]); + + let b = MatrixMN::<_, U3, U3>::from_row_slice(&[ + 0.0, 1.0, 2.0, + 4.0, 5.0, 6.0, + 8.0, 9.0, 10.0 + ]); + + let cmp = AbsoluteElementwiseComparator { tol: 1e-12 }; + if let Some(msg) = compare_matrices(a, b, &cmp).panic_message() { + println!("{}", msg); + } +} + +fn main() { + // This example mostly serves the purpose of demonstrating the kind of error messages + // that are given upon comparison failure. + // The more typical use case is using `assert_matrix_eq!` in tests. + compare_integers_fail(); + println!("======================================================"); + compare_f64_abs_tol_fail(); + println!("======================================================"); + compare_different_size(); +} \ No newline at end of file diff --git a/src/base/matrix.rs b/src/base/matrix.rs index d30773a3..8107a1dc 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -159,6 +159,30 @@ impl Abomonation for Matrix> + matrixcompare_core::Matrix for Matrix { + fn rows(&self) -> usize { + self.nrows() + } + + fn cols(&self) -> usize { + self.ncols() + } + + fn access(&self) -> matrixcompare_core::Access { + matrixcompare_core::Access::Dense(self) + } +} + +#[cfg(feature = "compare")] +impl> + matrixcompare_core::DenseAccess for Matrix { + fn fetch_single(&self, row: usize, col: usize) -> N { + self.index((row, col)).clone() + } +} + impl Matrix { /// Creates a new matrix with the given data without statically checking that the matrix /// dimension matches the storage dimension. diff --git a/tests/core/matrixcompare.rs b/tests/core/matrixcompare.rs new file mode 100644 index 00000000..d4b6f142 --- /dev/null +++ b/tests/core/matrixcompare.rs @@ -0,0 +1,81 @@ +//! Tests for `matrixcompare` integration. +//! +//! The `matrixcompare` crate itself is responsible for testing the actual comparison. +//! The tests here only check that the necessary trait implementations are correctly implemented, +//! in addition to some sanity checks with example input. + +use nalgebra::{U4, U5, MatrixMN, DMatrix}; + +use matrixcompare::{assert_matrix_eq, DenseAccess}; + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn fetch_single_is_equivalent_to_index_f64(matrix: DMatrix) -> bool { + for i in 0 .. matrix.nrows() { + for j in 0 .. matrix.ncols() { + if matrix.fetch_single(i, j) != *matrix.index((i, j)) { + return false; + } + } + } + + true + } + + fn matrixcompare_shape_agrees_with_matrix(matrix: DMatrix) -> bool { + matrix.nrows() == as matrixcompare::Matrix>::rows(&matrix) + && + matrix.ncols() == as matrixcompare::Matrix>::cols(&matrix) + } +} + +#[test] +fn assert_matrix_eq_dense_positive_comparison() { + let a = MatrixMN::<_, U4, U5>::from_row_slice(&[ + 1210, 1320, 1430, 1540, 1650, + 2310, 2420, 2530, 2640, 2750, + 3410, 3520, 3630, 3740, 3850, + 4510, 4620, 4730, 4840, 4950, + ]); + + let b = MatrixMN::<_, U4, U5>::from_row_slice(&[ + 1210, 1320, 1430, 1540, 1650, + 2310, 2420, 2530, 2640, 2750, + 3410, 3520, 3630, 3740, 3850, + 4510, 4620, 4730, 4840, 4950, + ]); + + // Test matrices of static size + assert_matrix_eq!(a, b); + assert_matrix_eq!(&a, b); + assert_matrix_eq!(a, &b); + assert_matrix_eq!(&a, &b); + + // Test matrices of dynamic size + let a_dyn = a.index((0..4, 0..5)); + let b_dyn = b.index((0..4, 0..5)); + assert_matrix_eq!(a_dyn, b_dyn); + assert_matrix_eq!(a_dyn, &b_dyn); + assert_matrix_eq!(&a_dyn, b_dyn); + assert_matrix_eq!(&a_dyn, &b_dyn); +} + +#[test] +#[should_panic] +fn assert_matrix_eq_dense_negative_comparison() { + let a = MatrixMN::<_, U4, U5>::from_row_slice(&[ + 1210, 1320, 1430, 1540, 1650, + 2310, 2420, 2530, 2640, 2750, + 3410, 3520, 3630, 3740, 3850, + 4510, 4620, -4730, 4840, 4950, + ]); + + let b = MatrixMN::<_, U4, U5>::from_row_slice(&[ + 1210, 1320, 1430, 1540, 1650, + 2310, 2420, 2530, 2640, 2750, + 3410, 3520, 3630, 3740, 3850, + 4510, 4620, 4730, 4840, 4950, + ]); + + assert_matrix_eq!(a, b); +} \ No newline at end of file diff --git a/tests/core/mod.rs b/tests/core/mod.rs index ec1c4e3e..1b3c7861 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -10,5 +10,8 @@ mod matrix_slice; mod mint; mod serde; +#[cfg(feature = "compare")] +mod matrixcompare; + #[cfg(feature = "arbitrary")] pub mod helper; From 9196759bc2f0d04d2ba0ce76d9f8acede2f1c82d Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 29 Jun 2020 19:03:20 +0200 Subject: [PATCH 05/55] Improve matrixcompare example --- examples/matrixcompare.rs | 27 +++++++++++++++------------ src/base/matrix.rs | 10 ++++++---- tests/core/matrixcompare.rs | 8 ++++++-- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/examples/matrixcompare.rs b/examples/matrixcompare.rs index 60c632d3..713be25f 100644 --- a/examples/matrixcompare.rs +++ b/examples/matrixcompare.rs @@ -1,60 +1,63 @@ extern crate nalgebra as na; -#[macro_use] -extern crate quickcheck; - -use na::{U3, U4, MatrixMN}; +use matrixcompare::comparators::{AbsoluteElementwiseComparator, ExactElementwiseComparator}; use matrixcompare::compare_matrices; -use matrixcompare::comparators::{ExactElementwiseComparator, AbsoluteElementwiseComparator}; +use na::{MatrixMN, U3, U4}; fn compare_integers_fail() { println!("Comparing two integer matrices."); + #[rustfmt::skip] let a = MatrixMN::<_, U3, U4>::from_row_slice(&[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -2, 11 ]); + #[rustfmt::skip] let b = MatrixMN::<_, U3, U4>::from_row_slice(&[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]); - if let Some(msg) = compare_matrices(a, b, &ExactElementwiseComparator).panic_message() { - println!("{}", msg); + if let Err(err) = compare_matrices(a, b, &ExactElementwiseComparator) { + println!("{}", err); } } fn compare_different_size() { println!("Comparing matrices of different size."); + #[rustfmt::skip] let a = MatrixMN::<_, U3, U3>::from_row_slice(&[ 0, 1, 2, 4, 5, 6, 8, 9, 10, ]); + #[rustfmt::skip] let b = MatrixMN::<_, U3, U4>::from_row_slice(&[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]); - if let Some(msg) = compare_matrices(a, b, &ExactElementwiseComparator).panic_message() { - println!("{}", msg); + if let Err(err) = compare_matrices(a, b, &ExactElementwiseComparator) { + println!("{}", err); } } fn compare_f64_abs_tol_fail() { println!("Comparing two f64 matrices."); + #[rustfmt::skip] let a = MatrixMN::::from_row_slice(&[ 0.0, 1.0, 2.0 + 1e-10, 4.0, 5.0, 6.0, 8.0, 9.0, 10.0, ]); + #[rustfmt::skip] let b = MatrixMN::<_, U3, U3>::from_row_slice(&[ 0.0, 1.0, 2.0, 4.0, 5.0, 6.0, @@ -62,8 +65,8 @@ fn compare_f64_abs_tol_fail() { ]); let cmp = AbsoluteElementwiseComparator { tol: 1e-12 }; - if let Some(msg) = compare_matrices(a, b, &cmp).panic_message() { - println!("{}", msg); + if let Err(err) = compare_matrices(a, b, &cmp) { + println!("{}", err); } } @@ -76,4 +79,4 @@ fn main() { compare_f64_abs_tol_fail(); println!("======================================================"); compare_different_size(); -} \ No newline at end of file +} diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 8107a1dc..6e6aaba5 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -160,8 +160,9 @@ impl Abomonation for Matrix> - matrixcompare_core::Matrix for Matrix { +impl> matrixcompare_core::Matrix + for Matrix +{ fn rows(&self) -> usize { self.nrows() } @@ -176,8 +177,9 @@ impl> } #[cfg(feature = "compare")] -impl> - matrixcompare_core::DenseAccess for Matrix { +impl> matrixcompare_core::DenseAccess + for Matrix +{ fn fetch_single(&self, row: usize, col: usize) -> N { self.index((row, col)).clone() } diff --git a/tests/core/matrixcompare.rs b/tests/core/matrixcompare.rs index d4b6f142..df112173 100644 --- a/tests/core/matrixcompare.rs +++ b/tests/core/matrixcompare.rs @@ -4,7 +4,7 @@ //! The tests here only check that the necessary trait implementations are correctly implemented, //! in addition to some sanity checks with example input. -use nalgebra::{U4, U5, MatrixMN, DMatrix}; +use nalgebra::{DMatrix, MatrixMN, U4, U5}; use matrixcompare::{assert_matrix_eq, DenseAccess}; @@ -31,6 +31,7 @@ quickcheck! { #[test] fn assert_matrix_eq_dense_positive_comparison() { + #[rustfmt::skip] let a = MatrixMN::<_, U4, U5>::from_row_slice(&[ 1210, 1320, 1430, 1540, 1650, 2310, 2420, 2530, 2640, 2750, @@ -38,6 +39,7 @@ fn assert_matrix_eq_dense_positive_comparison() { 4510, 4620, 4730, 4840, 4950, ]); + #[rustfmt::skip] let b = MatrixMN::<_, U4, U5>::from_row_slice(&[ 1210, 1320, 1430, 1540, 1650, 2310, 2420, 2530, 2640, 2750, @@ -63,6 +65,7 @@ fn assert_matrix_eq_dense_positive_comparison() { #[test] #[should_panic] fn assert_matrix_eq_dense_negative_comparison() { + #[rustfmt::skip] let a = MatrixMN::<_, U4, U5>::from_row_slice(&[ 1210, 1320, 1430, 1540, 1650, 2310, 2420, 2530, 2640, 2750, @@ -70,6 +73,7 @@ fn assert_matrix_eq_dense_negative_comparison() { 4510, 4620, -4730, 4840, 4950, ]); + #[rustfmt::skip] let b = MatrixMN::<_, U4, U5>::from_row_slice(&[ 1210, 1320, 1430, 1540, 1650, 2310, 2420, 2530, 2640, 2750, @@ -78,4 +82,4 @@ fn assert_matrix_eq_dense_negative_comparison() { ]); assert_matrix_eq!(a, b); -} \ No newline at end of file +} From 217a2bf312e2661824ac50d3af5ea51cccb7e99d Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Mon, 29 Jun 2020 19:07:44 +0200 Subject: [PATCH 06/55] Add compare feature to tests run by Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 01707964..0af9f3f2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ all: - cargo test --features "debug arbitrary serde-serialize abomonation-serialize" + cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare" # cargo check --features "debug arbitrary serde-serialize" doc: @@ -9,4 +9,4 @@ bench: cargo bench test: - cargo test --features "debug arbitrary serde-serialize abomonation-serialize" + cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare" From 4653f772bdfee342e5bd94bdd1f957316785bc6c Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Thu, 2 Jul 2020 10:16:18 -0700 Subject: [PATCH 07/55] added new_nonuniform_scaling_wrt_point to Matrix3 & Matrix4 --- src/base/cg.rs | 26 +++++++++++++++++++-- tests/core/cg.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++ tests/core/mod.rs | 1 + 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/core/cg.rs diff --git a/src/base/cg.rs b/src/base/cg.rs index 6a7db4bd..02aeade8 100644 --- a/src/base/cg.rs +++ b/src/base/cg.rs @@ -11,11 +11,11 @@ use crate::base::allocator::Allocator; use crate::base::dimension::{DimName, DimNameDiff, DimNameSub, U1}; use crate::base::storage::{Storage, StorageMut}; use crate::base::{ - DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector3, + DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector2, Vector3, VectorN, }; use crate::geometry::{ - Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point3, Rotation2, Rotation3, + Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point2, Point3, Rotation2, Rotation3, Translation2, Translation3, }; use simba::scalar::{ClosedAdd, ClosedMul, RealField}; @@ -70,6 +70,17 @@ impl Matrix3 { pub fn new_rotation(angle: N) -> Self { Rotation2::new(angle).to_homogeneous() } + + /// Creates a new homogeneous matrix that applies a scaling factor for each dimension with respect to point. + /// + /// Can be used to implement "zoom_to" functionality. + #[inline] + pub fn new_nonuniform_scaling_wrt_point(scaling: Vector2, pt: Point2) -> Self { + let translate = Translation2::new(pt.x, pt.y).to_homogeneous(); + let scale = Matrix3::new_nonuniform_scaling(&scaling); + let translate_inv = Translation2::new(-pt.x, -pt.y).to_homogeneous(); + translate * scale * translate_inv + } } impl Matrix4 { @@ -90,6 +101,17 @@ impl Matrix4 { Isometry::rotation_wrt_point(rot, pt).to_homogeneous() } + /// Creates a new homogeneous matrix that applies a scaling factor for each dimension with respect to point. + /// + /// Can be used to implement "zoom_to" functionality. + #[inline] + pub fn new_nonuniform_scaling_wrt_point(scaling: Vector3, pt: Point3) -> Self { + let translate = Translation3::new(pt.x, pt.y, pt.z).to_homogeneous(); + let scale = Matrix4::new_nonuniform_scaling(&scaling); + let translate_inv = Translation3::new(-pt.x, -pt.y, -pt.z).to_homogeneous(); + translate * scale * translate_inv + } + /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). /// /// Returns the identity matrix if the given argument is zero. diff --git a/tests/core/cg.rs b/tests/core/cg.rs new file mode 100644 index 00000000..17df4eb1 --- /dev/null +++ b/tests/core/cg.rs @@ -0,0 +1,59 @@ +use na::{Vector2, Vector3, Matrix3, Matrix4, Point2, Point3}; + +/// See Example 3.4 of "Graphics and Visualization: Principles & Algorithms" +/// by Theoharis, Papaioannou, Platis, Patrikalakis. +#[test] +fn test_scaling_wrt_point_1() { + let a = Point2::new(0.0, 0.0); + let b = Point2::new(1.0, 1.0); + let c = Point2::new(5.0, 2.0); + + let scaling = Vector2::new(2.0, 2.0); + let scale_about = Matrix3::new_nonuniform_scaling_wrt_point(scaling, c); + + let expected_a = Point2::new(-5.0, -2.0); + let expected_b = Point2::new(-3.0, 0.0); + let result_a = scale_about.transform_point(&a); + let result_b = scale_about.transform_point(&b); + let result_c = scale_about.transform_point(&c); + + assert!(expected_a == result_a); + assert!(expected_b == result_b); + assert!(c == result_c); +} + +/// Based on the same example as the test above. +#[test] +fn test_scaling_wrt_point_2() { + let a = Point3::new(0.0, 0.0, 1.0); + let b = Point3::new(1.0, 1.0, 1.0); + let c = Point3::new(5.0, 2.0, 1.0); + + let scaling = Vector3::new(2.0, 2.0, 1.0); + let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(scaling, c); + + let expected_a = Point3::new(-5.0, -2.0, 1.0); + let expected_b = Point3::new(-3.0, 0.0, 1.0); + + let result_a = scale_about.transform_point(&a); + let result_b = scale_about.transform_point(&b); + let result_c = scale_about.transform_point(&c); + + assert!(expected_a == result_a); + assert!(expected_b == result_b); + assert!(c == result_c); +} + +/// Based on https://github.com/emlowry/AiE/blob/50bae4068edb686cf8ffacdf6fab8e7cb22e7eb1/Year%201%20Classwork/MathTest/Matrix4x4TestGroup.cpp#L145 +#[test] +fn test_scaling_wrt_point_3() { + let about = Point3::new(2.0, 1.0, -2.0); + let scale = Vector3::new(2.0, 0.5, -1.0); + let pt = Point3::new(1.0, 2.0, 3.0); + let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(scale, about); + + let expected = Point3::new(0.0, 1.5, -7.0); + let result = scale_about.transform_point(&pt); + + assert!(result == expected); +} \ No newline at end of file diff --git a/tests/core/mod.rs b/tests/core/mod.rs index ec1c4e3e..7c738368 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -1,6 +1,7 @@ #[cfg(feature = "abomonation-serialize")] mod abomonation; mod blas; +mod cg; mod conversion; mod edition; mod empty; From 6a1c4f84af61a4208c2137afc31f2b3726e1e741 Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Thu, 2 Jul 2020 10:31:30 -0700 Subject: [PATCH 08/55] cargo fmt --- src/base/cg.rs | 7 ++++--- tests/core/cg.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/base/cg.rs b/src/base/cg.rs index 02aeade8..6a324bd2 100644 --- a/src/base/cg.rs +++ b/src/base/cg.rs @@ -11,11 +11,12 @@ use crate::base::allocator::Allocator; use crate::base::dimension::{DimName, DimNameDiff, DimNameSub, U1}; use crate::base::storage::{Storage, StorageMut}; use crate::base::{ - DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector2, Vector3, - VectorN, + DefaultAllocator, Matrix3, Matrix4, MatrixN, Scalar, SquareMatrix, Unit, Vector, Vector2, + Vector3, VectorN, }; use crate::geometry::{ - Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point2, Point3, Rotation2, Rotation3, Translation2, Translation3, + Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point2, Point3, Rotation2, + Rotation3, Translation2, Translation3, }; use simba::scalar::{ClosedAdd, ClosedMul, RealField}; diff --git a/tests/core/cg.rs b/tests/core/cg.rs index 17df4eb1..f1969658 100644 --- a/tests/core/cg.rs +++ b/tests/core/cg.rs @@ -1,4 +1,4 @@ -use na::{Vector2, Vector3, Matrix3, Matrix4, Point2, Point3}; +use na::{Matrix3, Matrix4, Point2, Point3, Vector2, Vector3}; /// See Example 3.4 of "Graphics and Visualization: Principles & Algorithms" /// by Theoharis, Papaioannou, Platis, Patrikalakis. @@ -56,4 +56,4 @@ fn test_scaling_wrt_point_3() { let result = scale_about.transform_point(&pt); assert!(result == expected); -} \ No newline at end of file +} From 1cf7d12695dacd2b55777612a4e4234b6600c3da Mon Sep 17 00:00:00 2001 From: Adam Nemecek Date: Sun, 5 Jul 2020 13:29:08 -0700 Subject: [PATCH 09/55] unrolled new_nonuniform_scaling_wrt_point --- src/base/cg.rs | 47 ++++++++++++++++++++++++++++++++++++----------- tests/core/cg.rs | 6 +++--- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/base/cg.rs b/src/base/cg.rs index 6a324bd2..935388af 100644 --- a/src/base/cg.rs +++ b/src/base/cg.rs @@ -16,7 +16,7 @@ use crate::base::{ }; use crate::geometry::{ Isometry, IsometryMatrix3, Orthographic3, Perspective3, Point, Point2, Point3, Rotation2, - Rotation3, Translation2, Translation3, + Rotation3, }; use simba::scalar::{ClosedAdd, ClosedMul, RealField}; @@ -76,11 +76,20 @@ impl Matrix3 { /// /// Can be used to implement "zoom_to" functionality. #[inline] - pub fn new_nonuniform_scaling_wrt_point(scaling: Vector2, pt: Point2) -> Self { - let translate = Translation2::new(pt.x, pt.y).to_homogeneous(); - let scale = Matrix3::new_nonuniform_scaling(&scaling); - let translate_inv = Translation2::new(-pt.x, -pt.y).to_homogeneous(); - translate * scale * translate_inv + pub fn new_nonuniform_scaling_wrt_point(scaling: &Vector2, pt: &Point2) -> Self { + let _0 = N::zero(); + let _1 = N::one(); + Matrix3::new( + scaling.x, + _0, + pt.x - pt.x * scaling.x, + _0, + scaling.y, + pt.y - pt.y * scaling.y, + _0, + _0, + _1, + ) } } @@ -106,11 +115,27 @@ impl Matrix4 { /// /// Can be used to implement "zoom_to" functionality. #[inline] - pub fn new_nonuniform_scaling_wrt_point(scaling: Vector3, pt: Point3) -> Self { - let translate = Translation3::new(pt.x, pt.y, pt.z).to_homogeneous(); - let scale = Matrix4::new_nonuniform_scaling(&scaling); - let translate_inv = Translation3::new(-pt.x, -pt.y, -pt.z).to_homogeneous(); - translate * scale * translate_inv + pub fn new_nonuniform_scaling_wrt_point(scaling: &Vector3, pt: &Point3) -> Self { + let _0 = N::zero(); + let _1 = N::one(); + Matrix4::new( + scaling.x, + _0, + _0, + pt.x - pt.x * scaling.x, + _0, + scaling.y, + _0, + pt.y - pt.y * scaling.y, + _0, + _0, + scaling.z, + pt.z - pt.z * scaling.z, + _0, + _0, + _0, + _1, + ) } /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). diff --git a/tests/core/cg.rs b/tests/core/cg.rs index f1969658..b061efbb 100644 --- a/tests/core/cg.rs +++ b/tests/core/cg.rs @@ -9,7 +9,7 @@ fn test_scaling_wrt_point_1() { let c = Point2::new(5.0, 2.0); let scaling = Vector2::new(2.0, 2.0); - let scale_about = Matrix3::new_nonuniform_scaling_wrt_point(scaling, c); + let scale_about = Matrix3::new_nonuniform_scaling_wrt_point(&scaling, &c); let expected_a = Point2::new(-5.0, -2.0); let expected_b = Point2::new(-3.0, 0.0); @@ -30,7 +30,7 @@ fn test_scaling_wrt_point_2() { let c = Point3::new(5.0, 2.0, 1.0); let scaling = Vector3::new(2.0, 2.0, 1.0); - let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(scaling, c); + let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(&scaling, &c); let expected_a = Point3::new(-5.0, -2.0, 1.0); let expected_b = Point3::new(-3.0, 0.0, 1.0); @@ -50,7 +50,7 @@ fn test_scaling_wrt_point_3() { let about = Point3::new(2.0, 1.0, -2.0); let scale = Vector3::new(2.0, 0.5, -1.0); let pt = Point3::new(1.0, 2.0, 3.0); - let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(scale, about); + let scale_about = Matrix4::new_nonuniform_scaling_wrt_point(&scale, &about); let expected = Point3::new(0.0, 1.5, -7.0); let result = scale_about.transform_point(&pt); From bc70258e5c6c0057efdc659ea533119c028e7e96 Mon Sep 17 00:00:00 2001 From: CGMossa Date: Thu, 16 Jul 2020 09:27:06 +0200 Subject: [PATCH 10/55] Why Option<_> (#746) Add a comment about why `UnitQuaternion::rotation_between` returns an Option. --- src/geometry/quaternion_construction.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 175275d4..a59d3030 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -374,7 +374,8 @@ where } /// The unit quaternion needed to make `a` and `b` be collinear and point toward the same - /// direction. + /// direction. Returns `None` if both `a` and `b` are collinear and point to opposite directions, as then the + /// rotation desired is not unique. /// /// # Example /// ``` From f9f7169558fb7701d23c7ee19182a5304ecd2e42 Mon Sep 17 00:00:00 2001 From: danielschlaugies <46607696+danielschlaugies@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:29:52 +0200 Subject: [PATCH 11/55] Add matrix exponential for complex matrices (#744) Added matrix exponential for complex matrices. --- src/linalg/exp.rs | 83 ++++++++++++++++++++++++++------------------- tests/linalg/exp.rs | 47 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/linalg/exp.rs b/src/linalg/exp.rs index c11fd757..6c43568d 100644 --- a/src/linalg/exp.rs +++ b/src/linalg/exp.rs @@ -10,10 +10,12 @@ use crate::{ convert, try_convert, ComplexField, MatrixN, RealField, }; +use crate::num::Zero; + // https://github.com/scipy/scipy/blob/c1372d8aa90a73d8a52f135529293ff4edb98fc8/scipy/sparse/linalg/matfuncs.py struct ExpmPadeHelper where - N: RealField, + N: ComplexField, D: DimMin, DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, { @@ -27,20 +29,20 @@ where a8: Option>, a10: Option>, - d4_exact: Option, - d6_exact: Option, - d8_exact: Option, - d10_exact: Option, + d4_exact: Option, + d6_exact: Option, + d8_exact: Option, + d10_exact: Option, - d4_approx: Option, - d6_approx: Option, - d8_approx: Option, - d10_approx: Option, + d4_approx: Option, + d6_approx: Option, + d8_approx: Option, + d10_approx: Option, } impl ExpmPadeHelper where - N: RealField, + N: ComplexField, D: DimMin, DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, { @@ -110,7 +112,7 @@ where } } - fn d4_tight(&mut self) -> N { + fn d4_tight(&mut self) -> N::RealField { if self.d4_exact.is_none() { self.calc_a4(); self.d4_exact = Some(one_norm(self.a4.as_ref().unwrap()).powf(convert(0.25))); @@ -118,7 +120,7 @@ where self.d4_exact.unwrap() } - fn d6_tight(&mut self) -> N { + fn d6_tight(&mut self) -> N::RealField { if self.d6_exact.is_none() { self.calc_a6(); self.d6_exact = Some(one_norm(self.a6.as_ref().unwrap()).powf(convert(1.0 / 6.0))); @@ -126,7 +128,7 @@ where self.d6_exact.unwrap() } - fn d8_tight(&mut self) -> N { + fn d8_tight(&mut self) -> N::RealField { if self.d8_exact.is_none() { self.calc_a8(); self.d8_exact = Some(one_norm(self.a8.as_ref().unwrap()).powf(convert(1.0 / 8.0))); @@ -134,7 +136,7 @@ where self.d8_exact.unwrap() } - fn d10_tight(&mut self) -> N { + fn d10_tight(&mut self) -> N::RealField { if self.d10_exact.is_none() { self.calc_a10(); self.d10_exact = Some(one_norm(self.a10.as_ref().unwrap()).powf(convert(1.0 / 10.0))); @@ -142,7 +144,7 @@ where self.d10_exact.unwrap() } - fn d4_loose(&mut self) -> N { + fn d4_loose(&mut self) -> N::RealField { if self.use_exact_norm { return self.d4_tight(); } @@ -159,7 +161,7 @@ where self.d4_approx.unwrap() } - fn d6_loose(&mut self) -> N { + fn d6_loose(&mut self) -> N::RealField { if self.use_exact_norm { return self.d6_tight(); } @@ -176,7 +178,7 @@ where self.d6_approx.unwrap() } - fn d8_loose(&mut self) -> N { + fn d8_loose(&mut self) -> N::RealField { if self.use_exact_norm { return self.d8_tight(); } @@ -193,7 +195,7 @@ where self.d8_approx.unwrap() } - fn d10_loose(&mut self) -> N { + fn d10_loose(&mut self) -> N::RealField { if self.use_exact_norm { return self.d10_tight(); } @@ -359,15 +361,20 @@ where fn ell(a: &MatrixN, m: u64) -> u64 where - N: RealField, + N: ComplexField, D: Dim, - DefaultAllocator: Allocator + Allocator, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, { // 2m choose m = (2m)!/(m! * (2m-m)!) - let a_abs_onenorm = onenorm_matrix_power_nonm(&a.abs(), 2 * m + 1); + let a_abs = a.map(|x| x.abs()); - if a_abs_onenorm == N::zero() { + let a_abs_onenorm = onenorm_matrix_power_nonm(&a_abs, 2 * m + 1); + + if a_abs_onenorm == ::RealField::zero() { return 0; } @@ -399,27 +406,33 @@ where q.lu().solve(&p).unwrap() } -fn one_norm(m: &MatrixN) -> N +fn one_norm(m: &MatrixN) -> N::RealField where - N: RealField, + N: ComplexField, D: Dim, DefaultAllocator: Allocator, { - let mut max = N::zero(); + let mut max = ::RealField::zero(); for i in 0..m.ncols() { let col = m.column(i); - max = max.max(col.iter().fold(N::zero(), |a, b| a + b.abs())); + max = max.max( + col.iter() + .fold(::RealField::zero(), |a, b| a + b.abs()), + ); } max } -impl MatrixN +impl MatrixN where D: DimMin, - DefaultAllocator: - Allocator + Allocator<(usize, usize), DimMinimum> + Allocator, + DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> + + Allocator + + Allocator + + Allocator, { /// Computes exponential of this matrix pub fn exp(&self) -> Self { @@ -430,19 +443,19 @@ where let mut h = ExpmPadeHelper::new(self.clone(), true); - let eta_1 = N::max(h.d4_loose(), h.d6_loose()); + let eta_1 = N::RealField::max(h.d4_loose(), h.d6_loose()); if eta_1 < convert(1.495585217958292e-002) && ell(&h.a, 3) == 0 { let (u, v) = h.pade3(); return solve_p_q(u, v); } - let eta_2 = N::max(h.d4_tight(), h.d6_loose()); + let eta_2 = N::RealField::max(h.d4_tight(), h.d6_loose()); if eta_2 < convert(2.539398330063230e-001) && ell(&h.a, 5) == 0 { let (u, v) = h.pade5(); return solve_p_q(u, v); } - let eta_3 = N::max(h.d6_tight(), h.d8_loose()); + let eta_3 = N::RealField::max(h.d6_tight(), h.d8_loose()); if eta_3 < convert(9.504178996162932e-001) && ell(&h.a, 7) == 0 { let (u, v) = h.pade7(); return solve_p_q(u, v); @@ -452,11 +465,11 @@ where return solve_p_q(u, v); } - let eta_4 = N::max(h.d8_loose(), h.d10_loose()); - let eta_5 = N::min(eta_3, eta_4); + let eta_4 = N::RealField::max(h.d8_loose(), h.d10_loose()); + let eta_5 = N::RealField::min(eta_3, eta_4); let theta_13 = convert(4.25); - let mut s = if eta_5 == N::zero() { + let mut s = if eta_5 == N::RealField::zero() { 0 } else { let l2 = try_convert((eta_5 / theta_13).log2().ceil()).unwrap(); diff --git a/tests/linalg/exp.rs b/tests/linalg/exp.rs index 75122107..93859934 100644 --- a/tests/linalg/exp.rs +++ b/tests/linalg/exp.rs @@ -126,4 +126,51 @@ mod tests { assert!(relative_eq!(f, m.exp(), epsilon = 1.0e-7)); } + + #[test] + fn exp_complex() { + use nalgebra::{Complex, ComplexField, DMatrix, DVector, Matrix2, RealField}; + + { + let z = Matrix2::>::zeros(); + + let identity = Matrix2::>::identity(); + + assert!((z.exp() - identity).norm() < 1e-7); + } + + { + let a = Matrix2::>::new( + Complex::::new(0.0, 1.0), + Complex::::new(0.0, 2.0), + Complex::::new(0.0, -1.0), + Complex::::new(0.0, 3.0), + ); + + let b = Matrix2::>::new( + Complex::::new(0.42645929666726, 1.89217550966333), + Complex::::new(-2.13721484276556, -0.97811251808259), + Complex::::new(1.06860742138278, 0.48905625904129), + Complex::::new(-1.7107555460983, 0.91406299158075), + ); + + assert!((a.exp() - b).norm() < 1.0e-07); + } + + { + let d1 = Complex::::new(0.0, ::pi()); + let d2 = Complex::::new(0.0, ::frac_pi_2()); + let d3 = Complex::::new(0.0, ::frac_pi_4()); + + let m = DMatrix::>::from_diagonal(&DVector::from_row_slice(&[d1, d2, d3])); + + let res = DMatrix::>::from_diagonal(&DVector::from_row_slice(&[ + d1.exp(), + d2.exp(), + d3.exp(), + ])); + + assert!((m.exp() - res).norm() < 1e-07); + } + } } From d13b3de4e441dcf71173e00c30cd23bcb73c5bb7 Mon Sep 17 00:00:00 2001 From: Andreas Longva Date: Fri, 17 Jul 2020 09:54:30 +0200 Subject: [PATCH 12/55] Use matrixcompare 0.1.3 for tests (fixes no-std test issues) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fe347ed0..36047af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ rand_isaac = "0.2" #criterion = "0.2.10" # For matrix comparison macro -matrixcompare = "0.1" +matrixcompare = "0.1.3" [workspace] members = [ "nalgebra-lapack", "nalgebra-glm" ] From 0f6c0e52339bb040f37e8e58a3c8500b7a609600 Mon Sep 17 00:00:00 2001 From: Frans Skarman Date: Fri, 31 Jul 2020 14:14:39 +0200 Subject: [PATCH 13/55] Update version in docs header --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9db176bf..05c01e2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ Simply add the following to your `Cargo.toml` file: ```.ignore [dependencies] -nalgebra = "0.18" +nalgebra = "0.21" ``` From 0a0799f76a8cfd1cc46561807677c612ef8731ea Mon Sep 17 00:00:00 2001 From: Michael Stevens Date: Mon, 3 Aug 2020 18:03:01 +0200 Subject: [PATCH 14/55] Remove unnecessary Trait bound DimSub in fn cholesky(self) --- src/linalg/cholesky.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linalg/cholesky.rs b/src/linalg/cholesky.rs index d9f33f27..17187fdf 100644 --- a/src/linalg/cholesky.rs +++ b/src/linalg/cholesky.rs @@ -8,7 +8,7 @@ use simba::simd::SimdComplexField; use crate::allocator::Allocator; use crate::base::{DefaultAllocator, Matrix, MatrixMN, MatrixN, SquareMatrix, Vector}; use crate::constraint::{SameNumberOfRows, ShapeConstraint}; -use crate::dimension::{Dim, DimAdd, DimDiff, DimSub, DimSum, Dynamic, U1}; +use crate::dimension::{Dim, DimAdd, DimDiff, DimSub, DimSum, U1}; use crate::storage::{Storage, StorageMut}; /// The Cholesky decomposition of a symmetric-definite-positive matrix. @@ -364,7 +364,7 @@ where } } -impl, S: Storage> SquareMatrix +impl> SquareMatrix where DefaultAllocator: Allocator, { From d81a895a87e2e5a94a847c8033a7c2cce2fbed9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 19 Aug 2020 19:48:16 +0200 Subject: [PATCH 15/55] Switch license to Apache v2.0 + update sponsor link. --- .github/FUNDING.yml | 4 +- Cargo.toml | 2 +- LICENSE | 218 +++++++++++++++++++++++++++++++++++++++----- README.md | 16 +--- 4 files changed, 203 insertions(+), 37 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 356ddf23..5ee5fec9 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms -github: [ "sebcrozet" ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: sebcrozet # Replace with a single Patreon username +github: [ "dimforge" ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel diff --git a/Cargo.toml b/Cargo.toml index 36047af0..7ea6f8ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/rustsim/nalgebra" readme = "README.md" categories = [ "science" ] keywords = [ "linear", "algebra", "matrix", "vector", "math" ] -license = "BSD-3-Clause" +license = "Apache-2.0" edition = "2018" exclude = ["/ci/*", "/.travis.yml", "/Makefile"] diff --git a/LICENSE b/LICENSE index f7f80044..97f4383a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,201 @@ -Copyright (c) 2013, Sébastien Crozet -All rights reserved. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + 1. Definitions. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -3. Neither the name of the author nor the names of its contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Sébastien Crozet + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index df3a82f3..72d058b5 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ - - Build status + + Build status crates.io - - + +

@@ -29,11 +29,3 @@

----- - -

-  Click this button if you wish to donate to support the development of nalgebra: -

- -

- Become a Patron! -

From 6300d343566390120defa1190e84e92a457d31c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sat, 13 Jun 2020 10:06:48 +0200 Subject: [PATCH 16/55] Add the ::ith constructor for vectors. This initializes a vectors to zero except the i-th element set to a given value. --- src/base/construction.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/base/construction.rs b/src/base/construction.rs index ae8c10d4..f810acdf 100644 --- a/src/base/construction.rs +++ b/src/base/construction.rs @@ -1011,6 +1011,14 @@ where N: Scalar + Zero + One, DefaultAllocator: Allocator, { + /// The column vector with `val` as its i-th component. + #[inline] + pub fn ith(i: usize, val: N) -> Self { + let mut res = Self::zeros(); + res[i] = val; + res + } + /// The column vector with a 1 as its first component, and zero elsewhere. #[inline] pub fn x() -> Self From 7c69cbf3261bb6e30cd3bf8aaf4f44bf7edbc47f Mon Sep 17 00:00:00 2001 From: sebcrozet Date: Sun, 12 Jul 2020 10:47:16 +0200 Subject: [PATCH 17/55] Don't depend on serde_derive explicitly. --- Cargo.toml | 5 ++--- src/lib.rs | 14 ++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7ea6f8ba..d7576d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ default = [ "std" ] std = [ "matrixmultiply", "rand/std", "rand_distr", "simba/std" ] stdweb = [ "rand/stdweb" ] arbitrary = [ "quickcheck" ] -serde-serialize = [ "serde", "serde_derive", "num-complex/serde" ] +serde-serialize = [ "serde", "num-complex/serde" ] abomonation-serialize = [ "abomonation" ] sparse = [ ] debug = [ "approx/num-complex", "rand/std" ] @@ -44,8 +44,7 @@ simba = { version = "0.1", default-features = false } alga = { version = "0.9", default-features = false, optional = true } rand_distr = { version = "0.2", optional = true } matrixmultiply = { version = "0.2", optional = true } -serde = { version = "1.0", optional = true } -serde_derive = { version = "1.0", optional = true } +serde = { version = "1.0", features = [ "derive" ], optional = true } abomonation = { version = "0.7", optional = true } mint = { version = "0.5", optional = true } quickcheck = { version = "0.9", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 9db176bf..fbc0a27d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,11 +90,9 @@ an optimized set of tools for computer graphics and physics. Those features incl #[cfg(feature = "arbitrary")] extern crate quickcheck; -#[cfg(feature = "serde")] -extern crate serde; -#[cfg(feature = "serde")] +#[cfg(feature = "serde-serialize")] #[macro_use] -extern crate serde_derive; +extern crate serde; #[cfg(feature = "abomonation-serialize")] extern crate abomonation; @@ -190,9 +188,7 @@ pub fn zero() -> T { /// The range must not be empty. #[inline] pub fn wrap(mut val: T, min: T, max: T) -> T -where - T: Copy + PartialOrd + ClosedAdd + ClosedSub, -{ +where T: Copy + PartialOrd + ClosedAdd + ClosedSub { assert!(min < max, "Invalid wrapping bounds."); let width = max - min; @@ -392,9 +388,7 @@ pub fn partial_sort2<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> Option<(&'a T, &' /// * [distance_squared](fn.distance_squared.html) #[inline] pub fn center(p1: &Point, p2: &Point) -> Point -where - DefaultAllocator: Allocator, -{ +where DefaultAllocator: Allocator { ((&p1.coords + &p2.coords) * convert::<_, N>(0.5)).into() } From 46d1cf22314f85cc4f8ad83481b61c090a8ab3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 20:44:03 +0200 Subject: [PATCH 18/55] Add a libm and libm-force feature to transitively enable the corresponding simba feature. --- Cargo.toml | 6 +++--- src/linalg/mod.rs | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d7576d62..9ed76264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ debug = [ "approx/num-complex", "rand/std" ] alloc = [ ] io = [ "pest", "pest_derive" ] compare = [ "matrixcompare-core" ] +libm = [ "simba/libm" ] +libm-force = [ "simba/libm_force" ] + [dependencies] typenum = "1.11" @@ -75,6 +78,3 @@ path = "benches/lib.rs" [profile.bench] lto = true - -#[patch.crates-io] -#simba = { path = "../simba" } \ No newline at end of file diff --git a/src/linalg/mod.rs b/src/linalg/mod.rs index f96cef0c..4a1edbcf 100644 --- a/src/linalg/mod.rs +++ b/src/linalg/mod.rs @@ -5,6 +5,10 @@ mod bidiagonal; mod cholesky; mod convolution; mod determinant; +// FIXME: this should not be needed. However, the exp uses +// explicit float operations on `f32` and `f64`. We need to +// get rid of these to allow exp to be used on a no-std context. +#[cfg(feature = "std")] mod exp; mod full_piv_lu; pub mod givens; @@ -27,6 +31,7 @@ mod symmetric_tridiagonal; pub use self::bidiagonal::*; pub use self::cholesky::*; pub use self::convolution::*; +#[cfg(feature = "std")] pub use self::exp::*; pub use self::full_piv_lu::*; pub use self::hessenberg::*; From a6962dfadcefb4b45279760c5e2b3e5ce783347c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 20:45:25 +0200 Subject: [PATCH 19/55] Bump the simba dependency version. --- Cargo.toml | 2 +- nalgebra-glm/Cargo.toml | 2 +- nalgebra-lapack/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ed76264..6705c222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ num-traits = { version = "0.2", default-features = false } num-complex = { version = "0.2", default-features = false } num-rational = { version = "0.2", default-features = false } approx = { version = "0.3", default-features = false } -simba = { version = "0.1", default-features = false } +simba = { version = "0.2", default-features = false } alga = { version = "0.9", default-features = false, optional = true } rand_distr = { version = "0.2", optional = true } matrixmultiply = { version = "0.2", optional = true } diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index b72e2988..2bc29613 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -24,5 +24,5 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ] [dependencies] num-traits = { version = "0.2", default-features = false } approx = { version = "0.3", default-features = false } -simba = { version = "0.1", default-features = false } +simba = { version = "0.2", default-features = false } nalgebra = { path = "..", version = "0.21", default-features = false } diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index b262e8b9..712c19e7 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -26,7 +26,7 @@ intel-mkl = ["lapack-src/intel-mkl"] nalgebra = { version = "0.21", path = ".." } num-traits = "0.2" num-complex = { version = "0.2", default-features = false } -simba = "0.1" +simba = "0.2" serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } lapack = { version = "0.16", default-features = false } From a8f73cb7b20fd67cd6caccf439b32211e98e762b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 20:47:07 +0200 Subject: [PATCH 20/55] Run cargo fmt. --- src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fbc0a27d..68116334 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,7 +188,9 @@ pub fn zero() -> T { /// The range must not be empty. #[inline] pub fn wrap(mut val: T, min: T, max: T) -> T -where T: Copy + PartialOrd + ClosedAdd + ClosedSub { +where + T: Copy + PartialOrd + ClosedAdd + ClosedSub, +{ assert!(min < max, "Invalid wrapping bounds."); let width = max - min; @@ -388,7 +390,9 @@ pub fn partial_sort2<'a, T: PartialOrd>(a: &'a T, b: &'a T) -> Option<(&'a T, &' /// * [distance_squared](fn.distance_squared.html) #[inline] pub fn center(p1: &Point, p2: &Point) -> Point -where DefaultAllocator: Allocator { +where + DefaultAllocator: Allocator, +{ ((&p1.coords + &p2.coords) * convert::<_, N>(0.5)).into() } From b96181f6c40ed0a4b95f5c54b0824ef81796157b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 21:04:37 +0200 Subject: [PATCH 21/55] CI: don't eanble --all-features when running the tests. This would enable the libm_force feature which results in less accurate results causing some tests to fail. --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ee64d771..b21798d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,10 +47,10 @@ jobs: - checkout - run: name: test - command: cargo test --all-features + command: cargo test --features arbitrary --features serde-serialize --features abomonation-serialize --features sparse --features debug --features io --features compare --features libm - run: name: test nalgebra-glm - command: cargo test -p nalgebra-glm --all-features + command: cargo test -p nalgebra-glm --features arbitrary --features serde-serialize --features abomonation-serialize --features sparse --features debug --features io --features compare --features libm build-wasm: executor: rust-executor steps: From d63500189869a39cc600f49c583f9cb359c48f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 21:15:56 +0200 Subject: [PATCH 22/55] Release v0.22.0 --- CHANGELOG.md | 10 ++++++++-- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f18f25..db0cdc6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,21 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). -## [0.22.0] - WIP +## [0.22.0] +In this release, we are using the new version 0.2 of simba. One major change of that version is that the +use of `libm` is now opt-in when building targetting `no-std` environment. If you are using floating-point +operations with nalgebra in a `no-std` environment, you will need to enable the new `libm` feature +of nalgebra for your code to compile again. ### Added + * The `libm` feature that enables `libm` when building for `no-std` environment. + * The `libm-force` feature that enables `libm` even when building for a not `no-std` environment. * `Cholesky::new_unchecked` which build a Cholesky decomposition without checking that its input is positive-definite. It can be use with SIMD types. * The `Default` trait is now implemented for matrices, and quaternions. They are all filled with zeros, except for `UnitQuaternion` which is initialized with the identity. * Matrix exponential `matrix.exp()`. - + * The `Vector::ith(i, x)` that builds a vector filled with zeros except for the `i`-th component set to `x`. ## [0.21.0] In this release, we are no longer relying on traits from the __alga__ crate for our generic code. diff --git a/Cargo.toml b/Cargo.toml index 6705c222..e4be2462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.21.1" +version = "0.22.0" authors = [ "Sébastien Crozet " ] description = "Linear algebra library with transformations and statically-sized or dynamically-sized matrices." From 1e65fb948d7eb74fd11327cad2a982c38bf78cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 21:16:06 +0200 Subject: [PATCH 23/55] Release nalgebra-glm v0.8.0 --- nalgebra-glm/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index 2bc29613..50f45eae 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-glm" -version = "0.7.0" +version = "0.8.0" authors = ["sebcrozet "] description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library." @@ -25,4 +25,4 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ] num-traits = { version = "0.2", default-features = false } approx = { version = "0.3", default-features = false } simba = { version = "0.2", default-features = false } -nalgebra = { path = "..", version = "0.21", default-features = false } +nalgebra = { path = "..", version = "0.22", default-features = false } From c1eaac2fae30d7269e693b1dacab93af4d5a6248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Tue, 25 Aug 2020 21:16:18 +0200 Subject: [PATCH 24/55] Release nalgebra-lapack 0.14.0 --- nalgebra-lapack/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index 712c19e7..c5e397fb 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-lapack" -version = "0.13.0" +version = "0.14.0" authors = [ "Sébastien Crozet ", "Andrew Straw " ] description = "Linear algebra library with transformations and satically-sized or dynamically-sized matrices." @@ -23,7 +23,7 @@ accelerate = ["lapack-src/accelerate"] intel-mkl = ["lapack-src/intel-mkl"] [dependencies] -nalgebra = { version = "0.21", path = ".." } +nalgebra = { version = "0.22", path = ".." } num-traits = "0.2" num-complex = { version = "0.2", default-features = false } simba = "0.2" @@ -34,7 +34,7 @@ lapack-src = { version = "0.5", default-features = false } # clippy = "*" [dev-dependencies] -nalgebra = { version = "0.21", path = "..", features = [ "arbitrary" ] } +nalgebra = { version = "0.22", path = "..", features = [ "arbitrary" ] } quickcheck = "0.9" approx = "0.3" rand = "0.7" From c6d5d8a1a67d9eff236088e7a5284e0970fdf6ed Mon Sep 17 00:00:00 2001 From: Michael Morgan Date: Fri, 9 Oct 2020 14:14:14 -0400 Subject: [PATCH 25/55] Remove unsafe statement in Point::deref by forwarding to Vector. Since both impls are #[inline], this should have no performance impact. --- src/geometry/point_coordinates.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/geometry/point_coordinates.rs b/src/geometry/point_coordinates.rs index b56e120e..268c9b0d 100644 --- a/src/geometry/point_coordinates.rs +++ b/src/geometry/point_coordinates.rs @@ -1,4 +1,3 @@ -use std::mem; use std::ops::{Deref, DerefMut}; use crate::base::allocator::Allocator; @@ -22,7 +21,7 @@ macro_rules! deref_impl( #[inline] fn deref(&self) -> &Self::Target { - unsafe { mem::transmute(self) } + &*self.coords } } @@ -30,7 +29,7 @@ macro_rules! deref_impl( where DefaultAllocator: Allocator { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { mem::transmute(self) } + &mut *self.coords } } } From fb15658cc977d992db2ebd6c1549292611375e87 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 11 Oct 2020 10:41:25 +0200 Subject: [PATCH 26/55] fix typo: apsect should be aspect --- nalgebra-glm/src/ext/matrix_clip_space.rs | 8 ++++---- src/geometry/orthographic.rs | 2 +- src/geometry/perspective.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nalgebra-glm/src/ext/matrix_clip_space.rs b/nalgebra-glm/src/ext/matrix_clip_space.rs index 260c5196..5ee725ae 100644 --- a/nalgebra-glm/src/ext/matrix_clip_space.rs +++ b/nalgebra-glm/src/ext/matrix_clip_space.rs @@ -530,7 +530,7 @@ pub fn perspective_lh_no(aspect: N, fovy: N, near: N, far: N) -> T ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let one = N::one(); @@ -566,7 +566,7 @@ pub fn perspective_lh_zo(aspect: N, fovy: N, near: N, far: N) -> T ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let one = N::one(); @@ -632,7 +632,7 @@ pub fn perspective_rh_no(aspect: N, fovy: N, near: N, far: N) -> T ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let negone = -N::one(); @@ -669,7 +669,7 @@ pub fn perspective_rh_zo(aspect: N, fovy: N, near: N, far: N) -> T ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let negone = -N::one(); diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index a7d5372f..c9bc76e6 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -151,7 +151,7 @@ impl Orthographic3 { ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let half: N = crate::convert(0.5); diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index 325a450c..ed7649fd 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -75,7 +75,7 @@ impl Perspective3 { ); assert!( !relative_eq!(aspect, N::zero()), - "The apsect ratio must not be zero." + "The aspect ratio must not be zero." ); let matrix = Matrix4::identity(); From 6293d3375bb0095c0ba7fb89fa78c340f378ccfd Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 11 Oct 2020 10:57:26 +0200 Subject: [PATCH 27/55] clippy: fix #redundant_field_names --- src/base/dimension.rs | 2 +- src/base/matrix.rs | 2 +- src/base/vec_storage.rs | 6 +----- src/geometry/orthographic.rs | 2 +- src/geometry/perspective.rs | 2 +- src/geometry/point.rs | 2 +- src/geometry/rotation.rs | 2 +- src/geometry/transform.rs | 2 +- src/geometry/translation.rs | 2 +- src/linalg/full_piv_lu.rs | 12 ++---------- src/linalg/lu.rs | 4 ++-- src/linalg/qr.rs | 10 ++-------- src/linalg/schur.rs | 6 ++---- 13 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/base/dimension.rs b/src/base/dimension.rs index c171ff23..3f77998e 100644 --- a/src/base/dimension.rs +++ b/src/base/dimension.rs @@ -23,7 +23,7 @@ impl Dynamic { /// A dynamic size equal to `value`. #[inline] pub fn new(value: usize) -> Self { - Self { value: value } + Self { value } } } diff --git a/src/base/matrix.rs b/src/base/matrix.rs index 6e6aaba5..ca7fa77b 100644 --- a/src/base/matrix.rs +++ b/src/base/matrix.rs @@ -191,7 +191,7 @@ impl Matrix { #[inline] pub unsafe fn from_data_statically_unchecked(data: S) -> Matrix { Matrix { - data: data, + data, _phantoms: PhantomData, } } diff --git a/src/base/vec_storage.rs b/src/base/vec_storage.rs index 26948693..40909c32 100644 --- a/src/base/vec_storage.rs +++ b/src/base/vec_storage.rs @@ -41,11 +41,7 @@ impl VecStorage { nrows.value() * ncols.value() == data.len(), "Data storage buffer dimension mismatch." ); - Self { - data: data, - nrows: nrows, - ncols: ncols, - } + Self { data, nrows, ncols } } /// The underlying data storage. diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index c9bc76e6..9f0eadaf 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -139,7 +139,7 @@ impl Orthographic3 { /// ``` #[inline] pub fn from_matrix_unchecked(matrix: Matrix4) -> Self { - Self { matrix: matrix } + Self { matrix } } /// Creates a new orthographic projection matrix from an aspect ratio and the vertical field of view. diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index ed7649fd..db283609 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -97,7 +97,7 @@ impl Perspective3 { /// projection. #[inline] pub fn from_matrix_unchecked(matrix: Matrix4) -> Self { - Self { matrix: matrix } + Self { matrix } } /// Retrieves the inverse of the underlying homogeneous matrix. diff --git a/src/geometry/point.rs b/src/geometry/point.rs index ab26589b..d12b9212 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -135,7 +135,7 @@ where #[deprecated(note = "Use Point::from(vector) instead.")] #[inline] pub fn from_coordinates(coords: VectorN) -> Self { - Self { coords: coords } + Self { coords } } /// The dimension of this point. diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index c6cd3d86..bb3fc235 100755 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -255,7 +255,7 @@ where "Unable to create a rotation from a non-square matrix." ); - Self { matrix: matrix } + Self { matrix } } /// Transposes `self`. diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index 42586509..80b72771 100755 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -245,7 +245,7 @@ where #[inline] pub fn from_matrix_unchecked(matrix: MatrixN>) -> Self { Transform { - matrix: matrix, + matrix, _phantom: PhantomData, } } diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 88d52ce4..26bbfd12 100755 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -119,7 +119,7 @@ where #[inline] #[deprecated(note = "Use `::from` instead.")] pub fn from_vector(vector: VectorN) -> Translation { - Translation { vector: vector } + Translation { vector } } /// Inverts `self`. diff --git a/src/linalg/full_piv_lu.rs b/src/linalg/full_piv_lu.rs index bfa6854f..146b2cb7 100644 --- a/src/linalg/full_piv_lu.rs +++ b/src/linalg/full_piv_lu.rs @@ -60,11 +60,7 @@ where let mut q = PermutationSequence::identity_generic(min_nrows_ncols); if min_nrows_ncols.value() == 0 { - return Self { - lu: matrix, - p: p, - q: q, - }; + return Self { lu: matrix, p, q }; } for i in 0..min_nrows_ncols.value() { @@ -90,11 +86,7 @@ where } } - Self { - lu: matrix, - p: p, - q: q, - } + Self { lu: matrix, p, q } } #[doc(hidden)] diff --git a/src/linalg/lu.rs b/src/linalg/lu.rs index b3acaaa0..6aa84d62 100644 --- a/src/linalg/lu.rs +++ b/src/linalg/lu.rs @@ -96,7 +96,7 @@ where let mut p = PermutationSequence::identity_generic(min_nrows_ncols); if min_nrows_ncols.value() == 0 { - return LU { lu: matrix, p: p }; + return LU { lu: matrix, p }; } for i in 0..min_nrows_ncols.value() { @@ -117,7 +117,7 @@ where } } - LU { lu: matrix, p: p } + LU { lu: matrix, p } } #[doc(hidden)] diff --git a/src/linalg/qr.rs b/src/linalg/qr.rs index f218d47b..48d7a896 100644 --- a/src/linalg/qr.rs +++ b/src/linalg/qr.rs @@ -57,20 +57,14 @@ where let mut diag = unsafe { MatrixMN::new_uninitialized_generic(min_nrows_ncols, U1) }; if min_nrows_ncols.value() == 0 { - return QR { - qr: matrix, - diag: diag, - }; + return QR { qr: matrix, diag }; } for ite in 0..min_nrows_ncols.value() { householder::clear_column_unchecked(&mut matrix, &mut diag[ite], ite, 0, None); } - QR { - qr: matrix, - diag: diag, - } + QR { qr: matrix, diag } } /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. diff --git a/src/linalg/schur.rs b/src/linalg/schur.rs index 99e73ea1..acdcd3dd 100644 --- a/src/linalg/schur.rs +++ b/src/linalg/schur.rs @@ -73,10 +73,8 @@ where pub fn try_new(m: MatrixN, eps: N::RealField, max_niter: usize) -> Option { let mut work = unsafe { VectorN::new_uninitialized_generic(m.data.shape().0, U1) }; - Self::do_decompose(m, &mut work, eps, max_niter, true).map(|(q, t)| Schur { - q: q.unwrap(), - t: t, - }) + Self::do_decompose(m, &mut work, eps, max_niter, true) + .map(|(q, t)| Schur { q: q.unwrap(), t }) } fn do_decompose( From 1624a87efa9eab6d6a996ffde0da507074b7e805 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 11 Oct 2020 11:01:20 +0200 Subject: [PATCH 28/55] clippy: fix #needless_return --- src/geometry/quaternion_construction.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index a59d3030..33e05867 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -492,18 +492,18 @@ where // The cosinus may be out of [-1, 1] because of inaccuracies. if cos <= -N::one() { - return None; + None } else if cos >= N::one() { - return Some(Self::identity()); + Some(Self::identity()) } else { - return Some(Self::from_axis_angle(&axis, cos.acos() * s)); + Some(Self::from_axis_angle(&axis, cos.acos() * s)) } } else if na.dot(&nb) < N::zero() { // PI // // The rotation axis is undefined but the angle not zero. This is not a // simple rotation. - return None; + None } else { // Zero Some(Self::identity()) From 0b0f248267e3822fe40878e783e49de636c0eac7 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 11 Oct 2020 11:23:05 +0200 Subject: [PATCH 29/55] clippy: fix #identity_op --- src/base/blas.rs | 4 ++-- src/linalg/schur.rs | 8 ++++---- src/linalg/svd.rs | 6 +++--- src/linalg/symmetric_eigen.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/base/blas.rs b/src/base/blas.rs index 1d61b9d2..c0615066 100644 --- a/src/base/blas.rs +++ b/src/base/blas.rs @@ -363,8 +363,8 @@ where while self.nrows() - i >= 8 { acc0 += unsafe { - conjugate(self.get_unchecked((i + 0, j)).inlined_clone()) - * rhs.get_unchecked((i + 0, j)).inlined_clone() + conjugate(self.get_unchecked((i, j)).inlined_clone()) + * rhs.get_unchecked((i, j)).inlined_clone() }; acc1 += unsafe { conjugate(self.get_unchecked((i + 1, j)).inlined_clone()) diff --git a/src/linalg/schur.rs b/src/linalg/schur.rs index acdcd3dd..8d8be72d 100644 --- a/src/linalg/schur.rs +++ b/src/linalg/schur.rs @@ -136,9 +136,9 @@ where let m = end - 1; let n = end; - let h11 = t[(start + 0, start + 0)]; - let h12 = t[(start + 0, start + 1)]; - let h21 = t[(start + 1, start + 0)]; + let h11 = t[(start, start)]; + let h12 = t[(start, start + 1)]; + let h21 = t[(start + 1, start)]; let h22 = t[(start + 1, start + 1)]; let h32 = t[(start + 2, start + 1)]; @@ -161,7 +161,7 @@ where if not_zero { if k > start { - t[(k + 0, k - 1)] = norm; + t[(k, k - 1)] = norm; t[(k + 1, k - 1)] = N::zero(); t[(k + 2, k - 1)] = N::zero(); } diff --git a/src/linalg/svd.rs b/src/linalg/svd.rs index 4be0ecdb..94ec4d96 100644 --- a/src/linalg/svd.rs +++ b/src/linalg/svd.rs @@ -218,9 +218,9 @@ where } } - diagonal[k + 0] = subm[(0, 0)]; + diagonal[k] = subm[(0, 0)]; diagonal[k + 1] = subm[(1, 1)]; - off_diagonal[k + 0] = subm[(0, 1)]; + off_diagonal[k] = subm[(0, 1)]; if k != n - 1 { off_diagonal[k + 1] = subm[(1, 2)]; @@ -244,7 +244,7 @@ where let u2 = u2.map(|u2| GivensRotation::new_unchecked(u2.c(), N::from_real(u2.s()))); let v2 = v2.map(|v2| GivensRotation::new_unchecked(v2.c(), N::from_real(v2.s()))); - diagonal[start + 0] = s[0]; + diagonal[start] = s[0]; diagonal[start + 1] = s[1]; off_diagonal[start] = N::RealField::zero(); diff --git a/src/linalg/symmetric_eigen.rs b/src/linalg/symmetric_eigen.rs index 70f57cdf..2d62816e 100644 --- a/src/linalg/symmetric_eigen.rs +++ b/src/linalg/symmetric_eigen.rs @@ -192,7 +192,7 @@ where let eigvals = m.eigenvalues().unwrap(); let basis = Vector2::new(eigvals.x - diag[start + 1], off_diag[start]); - diag[start + 0] = eigvals[0]; + diag[start] = eigvals[0]; diag[start + 1] = eigvals[1]; if let Some(ref mut q) = q { From 822f114254652c6744d6404f9cd194c80095db65 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 11 Oct 2020 11:57:43 +0200 Subject: [PATCH 30/55] perspective: fix copy/paste error --- src/geometry/perspective.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index db283609..f550bcc6 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -294,7 +294,7 @@ impl Arbitrary for Perspective3 { impl From> for Matrix4 { #[inline] - fn from(orth: Perspective3) -> Self { - orth.into_inner() + fn from(pers: Perspective3) -> Self { + pers.into_inner() } } From 343fb2f24f1486ec01fb70c1b415de1d9544e470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Tue, 13 Oct 2020 10:12:14 +0200 Subject: [PATCH 31/55] Ensure Isometry implements Copy when targeting no-std. Fix #774. --- src/geometry/isometry.rs | 10 ++++++---- src/geometry/isometry_alias.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 2e4b7cd5..a4e16abe 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -82,21 +82,23 @@ where } } -impl + Copy> Copy - for Isometry +impl Copy for Isometry where DefaultAllocator: Allocator, Owned: Copy, { } -impl + Clone> Clone for Isometry +impl Clone for Isometry where DefaultAllocator: Allocator, { #[inline] fn clone(&self) -> Self { - Self::from_parts(self.translation.clone(), self.rotation.clone()) + Self { + rotation: self.rotation.clone(), + translation: self.translation.clone(), + } } } diff --git a/src/geometry/isometry_alias.rs b/src/geometry/isometry_alias.rs index ba9a69e7..69d33cb4 100644 --- a/src/geometry/isometry_alias.rs +++ b/src/geometry/isometry_alias.rs @@ -13,3 +13,15 @@ pub type IsometryMatrix2 = Isometry>; /// A 3-dimensional direct isometry using a rotation matrix for its rotational part. Also known as a rigid-body motion, or as an element of SE(3). pub type IsometryMatrix3 = Isometry>; + +// This tests that the types correctly implement `Copy`, without having to run tests +// (when targeting no-std for example). +#[allow(dead_code)] +fn ensure_copy() { + fn is_copy() {} + + is_copy::>(); + is_copy::>(); + is_copy::>(); + is_copy::>(); +} From 07dc799bbd840c114e352be9dcb52795263fdad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Tue, 13 Oct 2020 10:28:30 +0200 Subject: [PATCH 32/55] Release v0.22.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e4be2462..c98c13dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.22.0" +version = "0.22.1" authors = [ "Sébastien Crozet " ] description = "Linear algebra library with transformations and statically-sized or dynamically-sized matrices." From 4da2bfcb717764fb6e97df3ea0e9c168776f2c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Tue, 13 Oct 2020 17:23:08 +0200 Subject: [PATCH 33/55] Add missing dot-product dimension check. Fix #776 --- src/base/blas.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/base/blas.rs b/src/base/blas.rs index c0615066..f245f24a 100644 --- a/src/base/blas.rs +++ b/src/base/blas.rs @@ -289,6 +289,13 @@ where rhs.shape(), ); + assert!( + self.ncols() == rhs.ncols(), + "Dot product dimensions mismatch for shapes {:?} and {:?}: left cols != right cols.", + self.shape(), + rhs.shape(), + ); + // So we do some special cases for common fixed-size vectors of dimension lower than 8 // because the `for` loop below won't be very efficient on those. if (R::is::() || R2::is::()) && (C::is::() || C2::is::()) { From 3d82c4335e1802290ce2b845dbc2ce3147f22aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:23:34 +0100 Subject: [PATCH 34/55] Add inverse_transform_unit_vector to rotations and isometries. --- src/geometry/abstract_rotation.rs | 17 ++++++++++++++++- src/geometry/isometry.rs | 25 ++++++++++++++++++++++++- src/geometry/quaternion.rs | 20 ++++++++++++++++++++ src/geometry/rotation.rs | 21 ++++++++++++++++++++- src/geometry/unit_complex.rs | 16 ++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/geometry/abstract_rotation.rs b/src/geometry/abstract_rotation.rs index ff057a09..52471851 100644 --- a/src/geometry/abstract_rotation.rs +++ b/src/geometry/abstract_rotation.rs @@ -1,6 +1,6 @@ use crate::allocator::Allocator; use crate::geometry::{Rotation, UnitComplex, UnitQuaternion}; -use crate::{DefaultAllocator, DimName, Point, Scalar, SimdRealField, VectorN, U2, U3}; +use crate::{DefaultAllocator, DimName, Point, Scalar, SimdRealField, Unit, VectorN, U2, U3}; use simba::scalar::ClosedMul; @@ -24,6 +24,13 @@ pub trait AbstractRotation: PartialEq + ClosedMul + Clone fn inverse_transform_vector(&self, v: &VectorN) -> VectorN where DefaultAllocator: Allocator; + /// Apply the inverse rotation to the given unit vector. + fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> + where + DefaultAllocator: Allocator, + { + Unit::new_unchecked(self.inverse_transform_vector(&**v)) + } /// Apply the inverse rotation to the given point. fn inverse_transform_point(&self, p: &Point) -> Point where @@ -74,6 +81,14 @@ where self.inverse_transform_vector(v) } + #[inline] + fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> + where + DefaultAllocator: Allocator, + { + self.inverse_transform_unit_vector(v) + } + #[inline] fn inverse_transform_point(&self, p: &Point) -> Point where diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index a4e16abe..b59c8b60 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -16,7 +16,7 @@ use simba::simd::SimdRealField; use crate::base::allocator::Allocator; use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1}; use crate::base::storage::Owned; -use crate::base::{DefaultAllocator, MatrixN, Scalar, VectorN}; +use crate::base::{DefaultAllocator, MatrixN, Scalar, Unit, VectorN}; use crate::geometry::{AbstractRotation, Point, Translation}; /// A direct isometry, i.e., a rotation followed by a translation, aka. a rigid-body motion, aka. an element of a Special Euclidean (SE) group. @@ -350,6 +350,29 @@ where pub fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.rotation.inverse_transform_vector(v) } + + /// Transform the given unit vector by the inverse of this isometry, ignoring the + /// translation component of the isometry. This may be + /// less expensive than computing the entire isometry inverse and then + /// transforming the point. + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate approx; + /// # use std::f32; + /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3}; + /// let tra = Translation3::new(0.0, 0.0, 3.0); + /// let rot = UnitQuaternion::from_scaled_axis(Vector3::z() * f32::consts::FRAC_PI_2); + /// let iso = Isometry3::from_parts(tra, rot); + /// + /// let transformed_point = iso.inverse_transform_unit_vector(&Vector3::x_axis()); + /// assert_relative_eq!(transformed_point, -Vector3::y_axis(), epsilon = 1.0e-6); + /// ``` + #[inline] + pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { + self.rotation.inverse_transform_unit_vector(v) + } } // NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 0c996ede..22ee5309 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -1542,6 +1542,26 @@ where pub fn inverse_transform_vector(&self, v: &Vector3) -> Vector3 { self.inverse() * v } + + /// Rotate a vector by the inverse of this unit quaternion. This may be + /// cheaper than inverting the unit quaternion and transforming the + /// vector. + /// + /// # Example + /// + /// ``` + /// # #[macro_use] extern crate approx; + /// # use std::f32; + /// # use nalgebra::{UnitQuaternion, Vector3}; + /// let rot = UnitQuaternion::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_2); + /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector3::x_axis()); + /// + /// assert_relative_eq!(transformed_vector, -Vector3::y_axis(), epsilon = 1.0e-6); + /// ``` + #[inline] + pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { + self.inverse() * v + } } impl Default for UnitQuaternion { diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index bb3fc235..c30a3a97 100755 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -19,7 +19,7 @@ use simba::simd::SimdRealField; use crate::base::allocator::Allocator; use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use crate::base::{DefaultAllocator, MatrixN, Scalar, VectorN}; +use crate::base::{DefaultAllocator, MatrixN, Scalar, Unit, VectorN}; use crate::geometry::Point; /// A rotation matrix. @@ -441,6 +441,25 @@ where pub fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.matrix().tr_mul(v) } + + /// Rotate the given vector by the inverse of this rotation. This may be + /// cheaper than inverting the rotation and then transforming the given + /// vector. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use std::f32; + /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3}; + /// let rot = Rotation3::new(Vector3::z() * f32::consts::FRAC_PI_2); + /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector3::x_axis()); + /// + /// assert_relative_eq!(transformed_vector, -Vector3::y_axis(), epsilon = 1.0e-6); + /// ``` + #[inline] + pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { + Unit::new_unchecked(self.inverse_transform_vector(&**v)) + } } impl Eq for Rotation where DefaultAllocator: Allocator {} diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index a8ac5bcd..723ac6c0 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -360,6 +360,22 @@ where pub fn inverse_transform_vector(&self, v: &Vector2) -> Vector2 { self.inverse() * v } + + /// Rotate the given vector by the inverse of this unit complex number. + /// + /// # Example + /// ``` + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{UnitComplex, Vector2}; + /// # use std::f32; + /// let rot = UnitComplex::new(f32::consts::FRAC_PI_2); + /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector2::x_axis()); + /// assert_relative_eq!(transformed_vector, -Vector2::y_axis(), epsilon = 1.0e-6); + /// ``` + #[inline] + pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { + self.inverse() * v + } } impl fmt::Display for UnitComplex { From a623e63d08b1e11a73df0237c06e011987d1606a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:23:51 +0100 Subject: [PATCH 35/55] Add the conversion of an array of four elements to a quaternion. --- src/geometry/quaternion_conversion.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs index 37a8bebd..330c97e4 100644 --- a/src/geometry/quaternion_conversion.rs +++ b/src/geometry/quaternion_conversion.rs @@ -265,6 +265,15 @@ impl From> for Quaternion { } } +impl From<[N; 4]> for Quaternion { + #[inline] + fn from(coords: [N; 4]) -> Self { + Self { + coords: coords.into(), + } + } +} + impl From<[Quaternion; 2]> for Quaternion where N: From<[::Element; 2]>, From 503040b8bce4956b1d54e824819fcf7b89976763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:24:05 +0100 Subject: [PATCH 36/55] Add Point.map(f) and Point.apply(f). --- src/geometry/point.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/geometry/point.rs b/src/geometry/point.rs index d12b9212..e0c26054 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -102,6 +102,45 @@ impl Point where DefaultAllocator: Allocator, { + /// Returns a point containing the result of `f` applied to each of its entries. + /// + /// # Example + /// ``` + /// # use nalgebra::{Point2, Point3}; + /// let p = Point2::new(1.0, 2.0); + /// assert_eq!(p.map(|e| e * 10.0), Point2::new(10.0, 20.0)); + /// + /// // This works in any dimension. + /// let p = Point3::new(1.1, 2.1, 3.1); + /// assert_eq!(p.map(|e| e as u32), Point3::new(1, 2, 3)); + /// ``` + #[inline] + pub fn map N2>(&self, f: F) -> Point + where + DefaultAllocator: Allocator, + { + self.coords.map(f).into() + } + + /// Replaces each component of `self` by the result of a closure `f` applied on it. + /// + /// # Example + /// ``` + /// # use nalgebra::{Point2, Point3}; + /// let mut p = Point2::new(1.0, 2.0); + /// p.apply(|e| e * 10.0); + /// assert_eq!(p, Point2::new(10.0, 20.0)); + /// + /// // This works in any dimension. + /// let mut p = Point3::new(1.0, 2.0, 3.0); + /// p.apply(|e| e * 10.0); + /// assert_eq!(p, Point3::new(10.0, 20.0, 30.0)); + /// ``` + #[inline] + pub fn apply N>(&mut self, f: F) { + self.coords.apply(f) + } + /// Converts this point into a vector in homogeneous coordinates, i.e., appends a `1` at the /// end of it. /// From 55689c238ae8f4933f29f2b253a811e5ca18b1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:24:20 +0100 Subject: [PATCH 37/55] Add the conversion of a translation to an isometry. --- src/geometry/isometry_conversion.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/geometry/isometry_conversion.rs b/src/geometry/isometry_conversion.rs index 7cb6fe85..bdb128f9 100644 --- a/src/geometry/isometry_conversion.rs +++ b/src/geometry/isometry_conversion.rs @@ -151,6 +151,17 @@ where } } +impl> From> + for Isometry +where + DefaultAllocator: Allocator, +{ + #[inline] + fn from(tra: Translation) -> Self { + Self::from_parts(tra, R::identity()) + } +} + impl From> for MatrixN> where D: DimNameAdd, From 9c93a58b5d648697806075dc2cc41cf33cf33464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:25:38 +0100 Subject: [PATCH 38/55] Add Vector::ith_axis to build an unit vector with its i-th component set to 1. --- src/base/construction.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/base/construction.rs b/src/base/construction.rs index f810acdf..aa4cf956 100644 --- a/src/base/construction.rs +++ b/src/base/construction.rs @@ -1019,6 +1019,12 @@ where res } + /// The column unit vector with `N::one()` as its i-th component. + #[inline] + pub fn ith_axis(i: usize) -> Unit { + Unit::new_unchecked(Self::ith(i, N::one())) + } + /// The column vector with a 1 as its first component, and zero elsewhere. #[inline] pub fn x() -> Self From 097ae44efad1b52f6f1e31df99f6510e52b34979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:39:27 +0100 Subject: [PATCH 39/55] Add `lerp_slerp` to isometries for interpolation. --- src/geometry/abstract_rotation.rs | 44 +++++++++++++++++++++ src/geometry/isometry.rs | 66 +++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/geometry/abstract_rotation.rs b/src/geometry/abstract_rotation.rs index 52471851..e1cf7c10 100644 --- a/src/geometry/abstract_rotation.rs +++ b/src/geometry/abstract_rotation.rs @@ -35,6 +35,14 @@ pub trait AbstractRotation: PartialEq + ClosedMul + Clone fn inverse_transform_point(&self, p: &Point) -> Point where DefaultAllocator: Allocator; + /// Perfom a spherical interpolation between two rolations. + fn slerp(&self, other: &Self, t: N) -> Self + where + DefaultAllocator: Allocator; + /// Attempts to perfom a spherical interpolation between two rolations. + fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option + where + DefaultAllocator: Allocator; } impl AbstractRotation for Rotation @@ -96,6 +104,22 @@ where { self.inverse_transform_point(p) } + + #[inline] + fn slerp(&self, other: &Self, t: N) -> Self + where + DefaultAllocator: Allocator, + { + self.slerp(other, t) + } + + #[inline] + fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option + where + DefaultAllocator: Allocator, + { + self.try_slerp(other, t, epsilon) + } } impl AbstractRotation for UnitQuaternion @@ -136,6 +160,16 @@ where fn inverse_transform_point(&self, p: &Point) -> Point { self.inverse_transform_point(p) } + + #[inline] + fn slerp(&self, other: &Self, t: N) -> Self { + self.slerp(other, t) + } + + #[inline] + fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option { + self.try_slerp(other, t, epsilon) + } } impl AbstractRotation for UnitComplex @@ -176,4 +210,14 @@ where fn inverse_transform_point(&self, p: &Point) -> Point { self.inverse_transform_point(p) } + + #[inline] + fn slerp(&self, other: &Self, t: N) -> Self { + self.slerp(other, t) + } + + #[inline] + fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option { + self.try_slerp(other, t, epsilon) + } } diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index b59c8b60..5b7e5dc7 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -373,6 +373,72 @@ where pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { self.rotation.inverse_transform_unit_vector(v) } + + /// Interpolates between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Translation3, UnitQuaternion}; + /// + /// let t1 = Translation3::new(1.0, 2.0, 3.0); + /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); + /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); + /// let iso1 = Isometry3::from_parts(t1, q1); + /// let iso2 = Isometry3::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); + /// ``` + #[inline] + pub fn lerp_slerp(&self, other: &Self, t: N) -> Self + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.slerp(&other.rotation, t); + Self::from_parts(tr.into(), rot) + } + + /// Attempts to interpolate between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Retuns `None` if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Translation3, UnitQuaternion}; + /// + /// let t1 = Translation3::new(1.0, 2.0, 3.0); + /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); + /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); + /// let iso1 = Isometry3::from_parts(t1, q1); + /// let iso2 = Isometry3::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); + /// ``` + #[inline] + pub fn try_lerp_slerp(&self, other: &Self, t: N, epsilon: N) -> Option + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.try_slerp(&other.rotation, t, epsilon)?; + Some(Self::from_parts(tr.into(), rot)) + } } // NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation From 22b8fc92254a71f551d30c14ef9f5e657ea1fc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 11:39:38 +0100 Subject: [PATCH 40/55] Update che changelog. --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db0cdc6b..1c158452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## [0.23.0] - WIP + +### Added + * The `.inverse_transform_unit_vector(v)` was added to `Rotation2/3`, `Isometry2/3`, `UnitQuaternion`, and `UnitComplex`. + It applies the corresponding rotation to a unit vector `Unit`. + * The `Point.map(f)` and `Point.apply(f)` to apply a function to each component of the point, similarly to `Vector.map(f)` + and `Vector.apply(f)`. + * The `Quaternion::from([N; 4])` conversion to build a quaternion from an array of four elements. + * The `Isometry::from(Translation)` conversion to build an isometry from a translation. + * The `Vector::ith_axis(i)` which build a unit vector, e.g., `Unit>` with its i-th component set to 1.0 and the + others set to zero. + * The `Isometry.lerp_slerp` and `Isometry.try_lerp_slerp` methods to interpolate between two isometries using linear + interpolation for the translational part, and spherical interpolation for the rotational part. + ## [0.22.0] In this release, we are using the new version 0.2 of simba. One major change of that version is that the use of `libm` is now opt-in when building targetting `no-std` environment. If you are using floating-point From 93f361cba8dbc5dad8445d712374d7ff11903156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 14:00:47 +0100 Subject: [PATCH 41/55] Add missing slerp implementations. --- CHANGELOG.md | 2 + src/geometry/abstract_rotation.rs | 44 ------- src/geometry/isometry.rs | 150 +++++++++++++++++++++++- src/geometry/rotation_specialization.rs | 71 +++++++++++ src/geometry/unit_complex.rs | 20 ++++ 5 files changed, 239 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c158452..853529f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](https://semver.org/). others set to zero. * The `Isometry.lerp_slerp` and `Isometry.try_lerp_slerp` methods to interpolate between two isometries using linear interpolation for the translational part, and spherical interpolation for the rotational part. + * The `Rotation2.slerp`, `Rotation3.slerp`, and `UnitQuaternion.slerp` method for + spherical interpolation. ## [0.22.0] In this release, we are using the new version 0.2 of simba. One major change of that version is that the diff --git a/src/geometry/abstract_rotation.rs b/src/geometry/abstract_rotation.rs index e1cf7c10..52471851 100644 --- a/src/geometry/abstract_rotation.rs +++ b/src/geometry/abstract_rotation.rs @@ -35,14 +35,6 @@ pub trait AbstractRotation: PartialEq + ClosedMul + Clone fn inverse_transform_point(&self, p: &Point) -> Point where DefaultAllocator: Allocator; - /// Perfom a spherical interpolation between two rolations. - fn slerp(&self, other: &Self, t: N) -> Self - where - DefaultAllocator: Allocator; - /// Attempts to perfom a spherical interpolation between two rolations. - fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option - where - DefaultAllocator: Allocator; } impl AbstractRotation for Rotation @@ -104,22 +96,6 @@ where { self.inverse_transform_point(p) } - - #[inline] - fn slerp(&self, other: &Self, t: N) -> Self - where - DefaultAllocator: Allocator, - { - self.slerp(other, t) - } - - #[inline] - fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option - where - DefaultAllocator: Allocator, - { - self.try_slerp(other, t, epsilon) - } } impl AbstractRotation for UnitQuaternion @@ -160,16 +136,6 @@ where fn inverse_transform_point(&self, p: &Point) -> Point { self.inverse_transform_point(p) } - - #[inline] - fn slerp(&self, other: &Self, t: N) -> Self { - self.slerp(other, t) - } - - #[inline] - fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option { - self.try_slerp(other, t, epsilon) - } } impl AbstractRotation for UnitComplex @@ -210,14 +176,4 @@ where fn inverse_transform_point(&self, p: &Point) -> Point { self.inverse_transform_point(p) } - - #[inline] - fn slerp(&self, other: &Self, t: N) -> Self { - self.slerp(other, t) - } - - #[inline] - fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option { - self.try_slerp(other, t, epsilon) - } } diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 5b7e5dc7..c8eb34fb 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -14,10 +14,12 @@ use simba::scalar::{RealField, SubsetOf}; use simba::simd::SimdRealField; use crate::base::allocator::Allocator; -use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1}; +use crate::base::dimension::{DimName, DimNameAdd, DimNameSum, U1, U2, U3}; use crate::base::storage::Owned; use crate::base::{DefaultAllocator, MatrixN, Scalar, Unit, VectorN}; -use crate::geometry::{AbstractRotation, Point, Translation}; +use crate::geometry::{ + AbstractRotation, Point, Rotation2, Rotation3, Translation, UnitComplex, UnitQuaternion, +}; /// A direct isometry, i.e., a rotation followed by a translation, aka. a rigid-body motion, aka. an element of a Special Euclidean (SE) group. #[repr(C)] @@ -373,7 +375,9 @@ where pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { self.rotation.inverse_transform_unit_vector(v) } +} +impl Isometry> { /// Interpolates between two isometries using a linear interpolation for the translation part, /// and a spherical interpolation for the rotation part. /// @@ -383,7 +387,7 @@ where /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Translation3, UnitQuaternion}; + /// # use nalgebra::geometry::{Vector3, Translation3, UnitQuaternion}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); /// let t2 = Translation3::new(3.0, 6.0, 9.0); @@ -416,7 +420,7 @@ where /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Translation3, UnitQuaternion}; + /// # use nalgebra::geometry::{Vector3, Translation3, UnitQuaternion}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); /// let t2 = Translation3::new(3.0, 6.0, 9.0); @@ -441,6 +445,144 @@ where } } +impl Isometry> { + /// Interpolates between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Vector3, Translation3, Rotation3}; + /// + /// let t1 = Translation3::new(1.0, 2.0, 3.0); + /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); + /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); + /// let iso1 = Isometry3::from_parts(t1, q1); + /// let iso2 = Isometry3::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); + /// ``` + #[inline] + pub fn lerp_slerp(&self, other: &Self, t: N) -> Self + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.slerp(&other.rotation, t); + Self::from_parts(tr.into(), rot) + } + + /// Attempts to interpolate between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Retuns `None` if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Vector3, Translation3, Rotation3}; + /// + /// let t1 = Translation3::new(1.0, 2.0, 3.0); + /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); + /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); + /// let iso1 = Isometry3::from_parts(t1, q1); + /// let iso2 = Isometry3::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); + /// ``` + #[inline] + pub fn try_lerp_slerp(&self, other: &Self, t: N, epsilon: N) -> Option + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.try_slerp(&other.rotation, t, epsilon)?; + Some(Self::from_parts(tr.into(), rot)) + } +} + +impl Isometry> { + /// Interpolates between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Vector2, Translation2, UnitComplex, Isometry2}; + /// + /// let t1 = Translation2::new(1.0, 2.0); + /// let t2 = Translation2::new(3.0, 6.0); + /// let q1 = UnitComplex::new(std::f32::consts::FRAC_PI_4); + /// let q2 = UnitComplex::new(-std::f32::consts::PI); + /// let iso1 = Isometry2::from_parts(t1, q1); + /// let iso2 = Isometry2::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector2::new(2.0, 4.0)); + /// assert_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); + /// ``` + #[inline] + pub fn slerp(&self, other: &Self, t: N) -> Self + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.slerp(&other.rotation, t); + Self::from_parts(tr.into(), rot) + } +} + +impl Isometry> { + /// Interpolates between two isometries using a linear interpolation for the translation part, + /// and a spherical interpolation for the rotation part. + /// + /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). Use `.try_lerp_slerp` instead to avoid the panic. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::{Vector2, Translation2, Rotation2, Isometry2}; + /// + /// let t1 = Translation2::new(1.0, 2.0); + /// let t2 = Translation2::new(3.0, 6.0); + /// let q1 = Rotation2::new(std::f32::consts::FRAC_PI_4); + /// let q2 = Rotation2::new(-std::f32::consts::PI); + /// let iso1 = Isometry2::from_parts(t1, q1); + /// let iso2 = Isometry2::from_parts(t2, q2); + /// + /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); + /// + /// assert_eq!(iso3.translation_vector, Vector2::new(2.0, 4.0)); + /// assert_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); + /// ``` + #[inline] + pub fn slerp(&self, other: &Self, t: N) -> Self + where + N: RealField, + { + let tr = self.translation.vector.lerp(&other.translation.vector, t); + let rot = self.rotation.slerp(&other.rotation, t); + Self::from_parts(tr.into(), rot) + } +} + // NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation // and makes it hard to use it, e.g., for Transform × Isometry implementation. // This is OK since all constructors of the isometry enforce the Rotation bound already (and diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 1ee4a9df..7f140c8b 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -236,6 +236,30 @@ impl Rotation2 { pub fn scaled_axis(&self) -> VectorN { Vector1::new(self.angle()) } + + /// Spherical linear interpolation between two rotation matrices. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::Rotation2; + /// + /// let rot1 = Rotation2::new(std::f32::consts::FRAC_PI_4); + /// let rot2 = Rotation2::new(-std::f32::consts::PI); + /// + /// let rot = rot1.slerp(&rot2, 1.0 / 3.0); + /// + /// assert_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); + /// ``` + #[inline] + pub fn slerp(&self, other: &Self, t: N) -> Self + where + N::Element: SimdRealField, + { + let c1 = UnitComplex::from(*self); + let c2 = UnitComplex::from(*other); + c1.slerp(&c2, t).into() + } } impl Distribution> for Standard @@ -862,6 +886,53 @@ where Self::identity() } } + + /// Spherical linear interpolation between two rotation matrices. + /// + /// Panics if the angle between both rotations is 180 degrees (in which case the interpolation + /// is not well-defined). Use `.try_slerp` instead to avoid the panic. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::Rotation3; + /// + /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); + /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); + /// + /// let q = q1.slerp(&q2, 1.0 / 3.0); + /// + /// assert_eq!(q.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); + /// ``` + #[inline] + pub fn slerp(&self, other: &Self, t: N) -> Self + where + N: RealField, + { + let q1 = UnitQuaternion::from(*self); + let q2 = UnitQuaternion::from(*other); + q1.slerp(&q2, t).into() + } + + /// Computes the spherical linear interpolation between two rotation matrices or returns `None` + /// if both rotations are approximately 180 degrees apart (in which case the interpolation is + /// not well-defined). + /// + /// # Arguments + /// * `self`: the first rotation to interpolate from. + /// * `other`: the second rotation to interpolate toward. + /// * `t`: the interpolation parameter. Should be between 0 and 1. + /// * `epsilon`: the value below which the sinus of the angle separating both rotations + /// must be to return `None`. + #[inline] + pub fn try_slerp(&self, other: &Self, t: N, epsilon: N) -> Option + where + N: RealField, + { + let q1 = Rotation3::from(*self); + let q2 = Rotation3::from(*other); + q1.try_slerp(&q2, t, epsilon).map(|q| q.into()) + } } impl Distribution> for Standard diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 723ac6c0..f6a4f619 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -376,6 +376,26 @@ where pub fn inverse_transform_unit_vector(&self, v: &Unit>) -> Unit> { self.inverse() * v } + + /// Spherical linear interpolation between two rotations represented as unit complex numbers. + /// + /// # Examples: + /// + /// ``` + /// # use nalgebra::geometry::UnitComplex; + /// + /// let rot1 = UnitComplex::new(std::f32::consts::FRAC_PI_4); + /// let rot2 = UnitComplex::new(-std::f32::consts::PI); + /// + /// let rot = rot1.slerp(&rot2, 1.0 / 3.0); + /// + /// assert_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); + /// ``` + + #[inline] + pub fn slerp(&self, other: &Self, t: N) -> Self { + Self::new(self.angle() * (N::one() - t) + other.angle() * t) + } } impl fmt::Display for UnitComplex { From c410a32c8279d2d3b10c654a893d431025d6135c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 14:15:26 +0100 Subject: [PATCH 42/55] Fix doc-tests. --- src/geometry/isometry.rs | 58 +++++++++++++------------ src/geometry/rotation_specialization.rs | 3 +- src/geometry/unit_complex.rs | 3 +- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index c8eb34fb..edb27b5a 100755 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -387,10 +387,10 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector3, Translation3, UnitQuaternion}; + /// # use nalgebra::{Vector3, Translation3, Isometry3, UnitQuaternion}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); - /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let t2 = Translation3::new(4.0, 8.0, 12.0); /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); /// let iso1 = Isometry3::from_parts(t1, q1); @@ -398,7 +398,7 @@ impl Isometry> { /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0)); /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); /// ``` #[inline] @@ -420,10 +420,10 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector3, Translation3, UnitQuaternion}; + /// # use nalgebra::{Vector3, Translation3, Isometry3, UnitQuaternion}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); - /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let t2 = Translation3::new(4.0, 8.0, 12.0); /// let q1 = UnitQuaternion::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); /// let q2 = UnitQuaternion::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); /// let iso1 = Isometry3::from_parts(t1, q1); @@ -431,7 +431,7 @@ impl Isometry> { /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0)); /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); /// ``` #[inline] @@ -455,18 +455,18 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector3, Translation3, Rotation3}; + /// # use nalgebra::{Vector3, Translation3, Rotation3, IsometryMatrix3}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); - /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let t2 = Translation3::new(4.0, 8.0, 12.0); /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); - /// let iso1 = Isometry3::from_parts(t1, q1); - /// let iso2 = Isometry3::from_parts(t2, q2); + /// let iso1 = IsometryMatrix3::from_parts(t1, q1); + /// let iso2 = IsometryMatrix3::from_parts(t2, q2); /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0)); /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); /// ``` #[inline] @@ -488,18 +488,18 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector3, Translation3, Rotation3}; + /// # use nalgebra::{Vector3, Translation3, Rotation3, IsometryMatrix3}; /// /// let t1 = Translation3::new(1.0, 2.0, 3.0); - /// let t2 = Translation3::new(3.0, 6.0, 9.0); + /// let t2 = Translation3::new(4.0, 8.0, 12.0); /// let q1 = Rotation3::from_euler_angles(std::f32::consts::FRAC_PI_4, 0.0, 0.0); /// let q2 = Rotation3::from_euler_angles(-std::f32::consts::PI, 0.0, 0.0); - /// let iso1 = Isometry3::from_parts(t1, q1); - /// let iso2 = Isometry3::from_parts(t2, q2); + /// let iso1 = IsometryMatrix3::from_parts(t1, q1); + /// let iso2 = IsometryMatrix3::from_parts(t2, q2); /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector3::new(2.0, 4.0, 6.0)); + /// assert_eq!(iso3.translation.vector, Vector3::new(2.0, 4.0, 6.0)); /// assert_eq!(iso3.rotation.euler_angles(), (std::f32::consts::FRAC_PI_2, 0.0, 0.0)); /// ``` #[inline] @@ -523,10 +523,11 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector2, Translation2, UnitComplex, Isometry2}; + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{Vector2, Translation2, UnitComplex, Isometry2}; /// /// let t1 = Translation2::new(1.0, 2.0); - /// let t2 = Translation2::new(3.0, 6.0); + /// let t2 = Translation2::new(4.0, 8.0); /// let q1 = UnitComplex::new(std::f32::consts::FRAC_PI_4); /// let q2 = UnitComplex::new(-std::f32::consts::PI); /// let iso1 = Isometry2::from_parts(t1, q1); @@ -534,11 +535,11 @@ impl Isometry> { /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector2::new(2.0, 4.0)); - /// assert_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); + /// assert_eq!(iso3.translation.vector, Vector2::new(2.0, 4.0)); + /// assert_relative_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); /// ``` #[inline] - pub fn slerp(&self, other: &Self, t: N) -> Self + pub fn lerp_slerp(&self, other: &Self, t: N) -> Self where N: RealField, { @@ -558,22 +559,23 @@ impl Isometry> { /// # Examples: /// /// ``` - /// # use nalgebra::geometry::{Vector2, Translation2, Rotation2, Isometry2}; + /// # #[macro_use] extern crate approx; + /// # use nalgebra::{Vector2, Translation2, Rotation2, IsometryMatrix2}; /// /// let t1 = Translation2::new(1.0, 2.0); - /// let t2 = Translation2::new(3.0, 6.0); + /// let t2 = Translation2::new(4.0, 8.0); /// let q1 = Rotation2::new(std::f32::consts::FRAC_PI_4); /// let q2 = Rotation2::new(-std::f32::consts::PI); - /// let iso1 = Isometry2::from_parts(t1, q1); - /// let iso2 = Isometry2::from_parts(t2, q2); + /// let iso1 = IsometryMatrix2::from_parts(t1, q1); + /// let iso2 = IsometryMatrix2::from_parts(t2, q2); /// /// let iso3 = iso1.lerp_slerp(&iso2, 1.0 / 3.0); /// - /// assert_eq!(iso3.translation_vector, Vector2::new(2.0, 4.0)); - /// assert_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); + /// assert_eq!(iso3.translation.vector, Vector2::new(2.0, 4.0)); + /// assert_relative_eq!(iso3.rotation.angle(), std::f32::consts::FRAC_PI_2); /// ``` #[inline] - pub fn slerp(&self, other: &Self, t: N) -> Self + pub fn lerp_slerp(&self, other: &Self, t: N) -> Self where N: RealField, { diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 7f140c8b..085cb2d8 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -242,6 +242,7 @@ impl Rotation2 { /// # Examples: /// /// ``` + /// # #[macro_use] extern crate approx; /// # use nalgebra::geometry::Rotation2; /// /// let rot1 = Rotation2::new(std::f32::consts::FRAC_PI_4); @@ -249,7 +250,7 @@ impl Rotation2 { /// /// let rot = rot1.slerp(&rot2, 1.0 / 3.0); /// - /// assert_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); + /// assert_relative_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); /// ``` #[inline] pub fn slerp(&self, other: &Self, t: N) -> Self diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index f6a4f619..a31f3ddf 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -382,6 +382,7 @@ where /// # Examples: /// /// ``` + /// # #[macro_use] extern crate approx; /// # use nalgebra::geometry::UnitComplex; /// /// let rot1 = UnitComplex::new(std::f32::consts::FRAC_PI_4); @@ -389,7 +390,7 @@ where /// /// let rot = rot1.slerp(&rot2, 1.0 / 3.0); /// - /// assert_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); + /// assert_relative_eq!(rot.angle(), std::f32::consts::FRAC_PI_2); /// ``` #[inline] From a473a3365c45a6f748d3b3d07dd569d538a1b61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 14:50:33 +0100 Subject: [PATCH 43/55] Bump dependencies. --- Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c98c13dc..378bf0c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,23 +36,23 @@ libm-force = [ "simba/libm_force" ] [dependencies] -typenum = "1.11" -generic-array = "0.13" +typenum = "1.12" +generic-array = "0.14" rand = { version = "0.7", default-features = false } num-traits = { version = "0.2", default-features = false } -num-complex = { version = "0.2", default-features = false } -num-rational = { version = "0.2", default-features = false } -approx = { version = "0.3", default-features = false } -simba = { version = "0.2", default-features = false } +num-complex = { version = "0.3", default-features = false } +num-rational = { version = "0.3", default-features = false } +approx = { version = "0.4", default-features = false } +simba = { version = "0.3", default-features = false } alga = { version = "0.9", default-features = false, optional = true } -rand_distr = { version = "0.2", optional = true } +rand_distr = { version = "0.3", optional = true } matrixmultiply = { version = "0.2", optional = true } serde = { version = "1.0", features = [ "derive" ], optional = true } abomonation = { version = "0.7", optional = true } mint = { version = "0.5", optional = true } quickcheck = { version = "0.9", optional = true } -pest = { version = "2.0", optional = true } -pest_derive = { version = "2.0", optional = true } +pest = { version = "2", optional = true } +pest_derive = { version = "2", optional = true } matrixcompare-core = { version = "0.1", optional = true } [dev-dependencies] From 3b723d7b9c20acd2b1447f92a19fffed7a7b4e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 15:13:56 +0100 Subject: [PATCH 44/55] Bump dependencies for nalgebra-glm and nalgebra-lapack. --- nalgebra-glm/Cargo.toml | 4 ++-- nalgebra-lapack/Cargo.toml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index 50f45eae..0400d91a 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -23,6 +23,6 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ] [dependencies] num-traits = { version = "0.2", default-features = false } -approx = { version = "0.3", default-features = false } -simba = { version = "0.2", default-features = false } +approx = { version = "0.4", default-features = false } +simba = { version = "0.3", default-features = false } nalgebra = { path = "..", version = "0.22", default-features = false } diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index c5e397fb..e0fe4310 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -25,16 +25,16 @@ intel-mkl = ["lapack-src/intel-mkl"] [dependencies] nalgebra = { version = "0.22", path = ".." } num-traits = "0.2" -num-complex = { version = "0.2", default-features = false } -simba = "0.2" +num-complex = { version = "0.3", default-features = false } +simba = "0.3" serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } lapack = { version = "0.16", default-features = false } -lapack-src = { version = "0.5", default-features = false } +lapack-src = { version = "0.6", default-features = false } # clippy = "*" [dev-dependencies] nalgebra = { version = "0.22", path = "..", features = [ "arbitrary" ] } quickcheck = "0.9" -approx = "0.3" +approx = "0.4" rand = "0.7" From ee05a8f4d91b83734770cd239172c89983c21025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 15:25:13 +0100 Subject: [PATCH 45/55] Revert dependencies bump for nalgebra-lapack because lapack needs to be updated first. --- nalgebra-lapack/Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml index e0fe4310..e70359cc 100644 --- a/nalgebra-lapack/Cargo.toml +++ b/nalgebra-lapack/Cargo.toml @@ -23,18 +23,18 @@ accelerate = ["lapack-src/accelerate"] intel-mkl = ["lapack-src/intel-mkl"] [dependencies] -nalgebra = { version = "0.22", path = ".." } +nalgebra = { version = "0.22" } # , path = ".." } num-traits = "0.2" -num-complex = { version = "0.3", default-features = false } -simba = "0.3" +num-complex = { version = "0.2", default-features = false } +simba = "0.2" serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } lapack = { version = "0.16", default-features = false } -lapack-src = { version = "0.6", default-features = false } +lapack-src = { version = "0.5", default-features = false } # clippy = "*" [dev-dependencies] -nalgebra = { version = "0.22", path = "..", features = [ "arbitrary" ] } +nalgebra = { version = "0.22", features = [ "arbitrary" ] } # path = ".." } quickcheck = "0.9" -approx = "0.4" +approx = "0.3" rand = "0.7" From 4a6022d9bfa3154bcb87311351fb5d71639a73a6 Mon Sep 17 00:00:00 2001 From: Nathan Kent Date: Wed, 19 Aug 2020 01:52:26 -0400 Subject: [PATCH 46/55] Add methods for in-place reshaping of matrices There are two major additions in this commit. The first is a new storage trait, `ReshapableStorage`, that can be implemented for storage types that can be reshaped in-place. I have implemented this for both the `ArrayStorage` and `VecStorage` types, as they are the most common and they are just interpretations of a flat list. The second is a `Matrix::reshape_generic` method that allows matrices to be in-place reshaped provided that the underlying storage can handle it. In practice, this means that the standard matrix types (`MatrixMN` and `DMatrix`) can be resized to any size that has the same element count. Resizing between array and vector storage is not implemented due to `Storage` only being implemented for `VecStorage` variants where at least one dimension is `Dynamic`. Additionally, only the generic reshape function is added as it can be a basis for other reshaping functions (see the resizing functions) and I am not particularly in the mood to implement a variety of reshaping methods. --- examples/matrix_construction.rs | 2 +- examples/reshaping.rs | 33 ++++++++++++++ src/base/array_storage.rs | 23 +++++++++- src/base/edition.rs | 29 ++++++++++++- src/base/storage.rs | 20 ++++++++- src/base/vec_storage.rs | 76 ++++++++++++++++++++++++++++++++- 6 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 examples/reshaping.rs diff --git a/examples/matrix_construction.rs b/examples/matrix_construction.rs index bb78458f..06f4c30b 100644 --- a/examples/matrix_construction.rs +++ b/examples/matrix_construction.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DMatrix, Matrix2x3, RowVector3, Vector2}; +use na::{DMatrix, Matrix2x3, Matrix3x2, RowVector3, Vector2}; fn main() { // All the following matrices are equal but constructed in different ways. diff --git a/examples/reshaping.rs b/examples/reshaping.rs new file mode 100644 index 00000000..bf02e769 --- /dev/null +++ b/examples/reshaping.rs @@ -0,0 +1,33 @@ +extern crate nalgebra as na; + +use na::{DMatrix, Dynamic, Matrix2x3, Matrix3x2, U2, U3}; + +fn main() { + // Matrices can be reshaped in-place without moving or copying values. + let m1 = Matrix2x3::new(1.1, 1.2, 1.3, 2.1, 2.2, 2.3); + let m2 = Matrix3x2::new(1.1, 2.2, 2.1, 1.3, 1.2, 2.3); + + let m3 = m1.reshape_generic(U3, U2); + assert_eq!(m3, m2); + + // Note that, for statically sized matrices, invalid reshapes will not compile: + //let m4 = m3.reshape_generic(U3, U3); + + // If dynamically sized matrices are used, the reshaping is checked at run-time. + let dm1 = DMatrix::from_vec( + 4, + 3, + vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + ); + let dm2 = DMatrix::from_vec( + 6, + 2, + vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + ); + + let dm3 = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); + assert_eq!(dm3, dm2); + + // Invalid reshapings of dynamic matrices will panic at run-time. + //let dm4 = dm3.reshape_generic(Dynamic::new(6), Dynamic::new(6)); +} diff --git a/src/base/array_storage.rs b/src/base/array_storage.rs index 1743f06b..b86e3a80 100644 --- a/src/base/array_storage.rs +++ b/src/base/array_storage.rs @@ -24,7 +24,9 @@ use typenum::Prod; use crate::base::allocator::Allocator; use crate::base::default_allocator::DefaultAllocator; use crate::base::dimension::{DimName, U1}; -use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut}; +use crate::base::storage::{ + ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut, +}; use crate::base::Scalar; /* @@ -267,6 +269,25 @@ where { } +impl ReshapableStorage for ArrayStorage +where + N: Scalar, + R1: DimName, + C1: DimName, + R1::Value: Mul, + Prod: ArrayLength, + R2: DimName, + C2: DimName, + R2::Value: Mul>, + Prod: ArrayLength, +{ + type Output = ArrayStorage; + + fn reshape_generic(self, _: R2, _: C2) -> Self::Output { + ArrayStorage { data: self.data } + } +} + /* * * Allocation-less serde impls. diff --git a/src/base/edition.rs b/src/base/edition.rs index b3133648..896e6635 100644 --- a/src/base/edition.rs +++ b/src/base/edition.rs @@ -13,7 +13,7 @@ use crate::base::dimension::Dynamic; use crate::base::dimension::{ Dim, DimAdd, DimDiff, DimMin, DimMinimum, DimName, DimSub, DimSum, U1, }; -use crate::base::storage::{Storage, StorageMut}; +use crate::base::storage::{ReshapableStorage, Storage, StorageMut}; #[cfg(any(feature = "std", feature = "alloc"))] use crate::base::DMatrix; use crate::base::{DefaultAllocator, Matrix, MatrixMN, RowVector, Scalar, Vector}; @@ -745,7 +745,7 @@ impl> Matrix { self.resize_generic(R2::name(), C2::name(), val) } - /// Resizes `self` such that it has dimensions `new_nrows × now_ncols`. + /// Resizes `self` such that it has dimensions `new_nrows × new_ncols`. /// /// The values are copied such that `self[(i, j)] == result[(i, j)]`. If the result has more /// rows and/or columns than `self`, then the extra rows or columns are filled with `val`. @@ -813,6 +813,31 @@ impl> Matrix { } } +impl Matrix +where + N: Scalar, + R: Dim, + C: Dim, +{ + /// Reshapes `self` in-place such that it has dimensions `new_nrows × new_ncols`. + /// + /// The values are not copied or moved. This function will panic if dynamic sizes are provided + /// and not compatible. + pub fn reshape_generic( + self, + new_nrows: R2, + new_ncols: C2, + ) -> Matrix + where + R2: Dim, + C2: Dim, + S: ReshapableStorage, + { + let data = self.data.reshape_generic(new_nrows, new_ncols); + Matrix::from_data(data) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl DMatrix { /// Resizes this matrix in-place. diff --git a/src/base/storage.rs b/src/base/storage.rs index 9f039ba2..7b197861 100644 --- a/src/base/storage.rs +++ b/src/base/storage.rs @@ -171,7 +171,7 @@ pub unsafe trait StorageMut: Storage { /// A matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorage: @@ -181,10 +181,26 @@ pub unsafe trait ContiguousStorage: /// A mutable matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorageMut: ContiguousStorage + StorageMut { } + +/// A matrix storage that can be reshaped in-place. +pub trait ReshapableStorage: Storage +where + N: Scalar, + R1: Dim, + C1: Dim, + R2: Dim, + C2: Dim, +{ + /// The reshaped storage type. + type Output: Storage; + + /// Reshapes the storage into the output storage type. + fn reshape_generic(self, nrows: R2, ncols: C2) -> Self::Output; +} diff --git a/src/base/vec_storage.rs b/src/base/vec_storage.rs index 40909c32..37f31213 100644 --- a/src/base/vec_storage.rs +++ b/src/base/vec_storage.rs @@ -8,7 +8,9 @@ use crate::base::allocator::Allocator; use crate::base::constraint::{SameNumberOfRows, ShapeConstraint}; use crate::base::default_allocator::DefaultAllocator; use crate::base::dimension::{Dim, DimName, Dynamic, U1}; -use crate::base::storage::{ContiguousStorage, ContiguousStorageMut, Owned, Storage, StorageMut}; +use crate::base::storage::{ + ContiguousStorage, ContiguousStorageMut, Owned, ReshapableStorage, Storage, StorageMut, +}; use crate::base::{Scalar, Vector}; #[cfg(feature = "abomonation-serialize")] @@ -225,6 +227,42 @@ unsafe impl ContiguousStorageMut for VecStorag { } +impl ReshapableStorage for VecStorage +where + N: Scalar, + C1: Dim, + C2: Dim, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + +impl ReshapableStorage for VecStorage +where + N: Scalar, + C1: Dim, + R2: DimName, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + unsafe impl StorageMut for VecStorage where DefaultAllocator: Allocator, @@ -240,6 +278,42 @@ where } } +impl ReshapableStorage for VecStorage +where + N: Scalar, + R1: DimName, + C2: Dim, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: Dynamic, ncols: C2) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + +impl ReshapableStorage for VecStorage +where + N: Scalar, + R1: DimName, + R2: DimName, +{ + type Output = VecStorage; + + fn reshape_generic(self, nrows: R2, ncols: Dynamic) -> Self::Output { + assert_eq!(nrows.value() * ncols.value(), self.data.len()); + VecStorage { + data: self.data, + nrows, + ncols, + } + } +} + #[cfg(feature = "abomonation-serialize")] impl Abomonation for VecStorage { unsafe fn entomb(&self, writer: &mut W) -> IOResult<()> { From 5b3da9e2ebac1cd2d31c98905acb29caf126188e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:02:31 +0100 Subject: [PATCH 47/55] Fix typo in comment. --- src/base/storage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/storage.rs b/src/base/storage.rs index 7b197861..598cb061 100644 --- a/src/base/storage.rs +++ b/src/base/storage.rs @@ -171,7 +171,7 @@ pub unsafe trait StorageMut: Storage { /// A matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorage: @@ -181,7 +181,7 @@ pub unsafe trait ContiguousStorage: /// A mutable matrix storage that is stored contiguously in memory. /// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols]`, the value +/// The storage requirement means that for any value of `i` in `[0, nrows * ncols - 1]`, the value /// `.get_unchecked_linear` returns one of the matrix component. This trait is unsafe because /// failing to comply to this may cause Undefined Behaviors. pub unsafe trait ContiguousStorageMut: From 7af509ee8d76e4710b4deab652f32047aa61d8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:02:43 +0100 Subject: [PATCH 48/55] Reformat the reshaping example. --- examples/reshaping.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/examples/reshaping.rs b/examples/reshaping.rs index bf02e769..f164e192 100644 --- a/examples/reshaping.rs +++ b/examples/reshaping.rs @@ -1,11 +1,20 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] + extern crate nalgebra as na; use na::{DMatrix, Dynamic, Matrix2x3, Matrix3x2, U2, U3}; fn main() { // Matrices can be reshaped in-place without moving or copying values. - let m1 = Matrix2x3::new(1.1, 1.2, 1.3, 2.1, 2.2, 2.3); - let m2 = Matrix3x2::new(1.1, 2.2, 2.1, 1.3, 1.2, 2.3); + let m1 = Matrix2x3::new( + 1.1, 1.2, 1.3, + 2.1, 2.2, 2.3 + ); + let m2 = Matrix3x2::new( + 1.1, 2.2, + 2.1, 1.3, + 1.2, 2.3 + ); let m3 = m1.reshape_generic(U3, U2); assert_eq!(m3, m2); @@ -14,15 +23,23 @@ fn main() { //let m4 = m3.reshape_generic(U3, U3); // If dynamically sized matrices are used, the reshaping is checked at run-time. - let dm1 = DMatrix::from_vec( + let dm1 = DMatrix::from_row_slice( 4, 3, - vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + &[ + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0 + ], ); - let dm2 = DMatrix::from_vec( + let dm2 = DMatrix::from_row_slice( 6, 2, - vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + &[ + 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + ], ); let dm3 = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); From d7cb138e2269806397149a45be0a8bdc26a3bbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:03:07 +0100 Subject: [PATCH 49/55] Fix warnings. --- examples/matrix_construction.rs | 2 +- tests/linalg/exp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/matrix_construction.rs b/examples/matrix_construction.rs index 06f4c30b..bb78458f 100644 --- a/examples/matrix_construction.rs +++ b/examples/matrix_construction.rs @@ -1,6 +1,6 @@ extern crate nalgebra as na; -use na::{DMatrix, Matrix2x3, Matrix3x2, RowVector3, Vector2}; +use na::{DMatrix, Matrix2x3, RowVector3, Vector2}; fn main() { // All the following matrices are equal but constructed in different ways. diff --git a/tests/linalg/exp.rs b/tests/linalg/exp.rs index 93859934..f5b5243a 100644 --- a/tests/linalg/exp.rs +++ b/tests/linalg/exp.rs @@ -129,7 +129,7 @@ mod tests { #[test] fn exp_complex() { - use nalgebra::{Complex, ComplexField, DMatrix, DVector, Matrix2, RealField}; + use nalgebra::{Complex, DMatrix, DVector, Matrix2, RealField}; { let z = Matrix2::>::zeros(); From e89a26cbd02cd08e280f9db4471909ccec3b0b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:03:18 +0100 Subject: [PATCH 50/55] Add doc-tests for reshape_generic. --- src/base/edition.rs | 51 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/base/edition.rs b/src/base/edition.rs index 896e6635..06f64f7a 100644 --- a/src/base/edition.rs +++ b/src/base/edition.rs @@ -819,10 +819,55 @@ where R: Dim, C: Dim, { - /// Reshapes `self` in-place such that it has dimensions `new_nrows × new_ncols`. + /// Reshapes `self` such that it has dimensions `new_nrows × new_ncols`. /// - /// The values are not copied or moved. This function will panic if dynamic sizes are provided - /// and not compatible. + /// This will reinterpret `self` as if it is a matrix with `new_nrows` rows and `new_ncols` + /// columns. The arrangements of the component in the output matrix are the same as what + /// would be obtained by `Matrix::from_slice_generic(self.as_slice(), new_nrows, new_ncols)`. + /// + /// If `self` is a dynamically-sized matrix, then its components are neither copied nor moved. + /// If `self` is staticyll-sized, then a copy may happen in some situations. + /// This function will panic if the given dimensions are such that the number of elements of + /// the input matrix are not equal to the number of elements of the output matrix. + /// + /// # Examples + /// + /// ``` + /// # use nalgebra::{Matrix3x2, Matrix2x3, DMatrix, U2, U3, Dynamic}; + /// + /// let m1 = Matrix2x3::new( + /// 1.1, 1.2, 1.3, + /// 2.1, 2.2, 2.3 + /// ); + /// let m2 = Matrix3x2::new( + /// 1.1, 2.2, + /// 2.1, 1.3, + /// 1.2, 2.3 + /// ); + /// let reshaped = m1.reshape_generic(U3, U2); + /// assert_eq!(reshaped, m2); + /// + /// let dm1 = DMatrix::from_row_slice( + /// 4, + /// 3, + /// &[ + /// 1.0, 0.0, 0.0, + /// 0.0, 0.0, 1.0, + /// 0.0, 0.0, 0.0, + /// 0.0, 1.0, 0.0 + /// ], + /// ); + /// let dm2 = DMatrix::from_row_slice( + /// 6, + /// 2, + /// &[ + /// 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, + /// 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + /// ], + /// ); + /// let reshaped = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); + /// assert_eq!(reshaped, dm2); + /// ``` pub fn reshape_generic( self, new_nrows: R2, From 8e483a5434e251590a21a286753f1d95d93fc59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Sun, 25 Oct 2020 16:20:27 +0100 Subject: [PATCH 51/55] Fix reshaping test. --- examples/reshaping.rs | 8 ++++++-- src/base/edition.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/reshaping.rs b/examples/reshaping.rs index f164e192..60cb9bdd 100644 --- a/examples/reshaping.rs +++ b/examples/reshaping.rs @@ -37,8 +37,12 @@ fn main() { 6, 2, &[ - 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + 1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0, + 0.0, 1.0, + 0.0, 0.0, + 0.0, 0.0, ], ); diff --git a/src/base/edition.rs b/src/base/edition.rs index 06f64f7a..d69582d9 100644 --- a/src/base/edition.rs +++ b/src/base/edition.rs @@ -861,8 +861,12 @@ where /// 6, /// 2, /// &[ - /// 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, - /// 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 + /// 1.0, 0.0, + /// 0.0, 1.0, + /// 0.0, 0.0, + /// 0.0, 1.0, + /// 0.0, 0.0, + /// 0.0, 0.0, /// ], /// ); /// let reshaped = dm1.reshape_generic(Dynamic::new(6), Dynamic::new(2)); From 74f01d2538123616b05e9eb58934c35cdd50b4dc Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 25 Oct 2020 18:23:24 +0100 Subject: [PATCH 52/55] clippy: fix suspicious_arithmetic_impl errors (false positives) --- src/geometry/isometry_ops.rs | 26 ++++--- src/geometry/quaternion_ops.rs | 124 ++++++++++++++----------------- src/geometry/rotation_ops.rs | 16 ++-- src/geometry/similarity_ops.rs | 43 +++++------ src/geometry/transform_ops.rs | 57 +++++++------- src/geometry/translation_ops.rs | 110 +++++++-------------------- src/geometry/unit_complex_ops.rs | 8 +- 7 files changed, 162 insertions(+), 222 deletions(-) diff --git a/src/geometry/isometry_ops.rs b/src/geometry/isometry_ops.rs index 8b00fcda..e676d492 100644 --- a/src/geometry/isometry_ops.rs +++ b/src/geometry/isometry_ops.rs @@ -149,6 +149,7 @@ isometry_binop_impl_all!( [ref ref] => { let shift = self.rotation.transform_vector(&rhs.translation.vector); + #[allow(clippy::suspicious_arithmetic_impl)] Isometry::from_parts(Translation::from(&self.translation.vector + shift), self.rotation.clone() * rhs.rotation.clone()) // FIXME: too bad we have to clone. }; @@ -157,10 +158,10 @@ isometry_binop_impl_all!( isometry_binop_impl_all!( Div, div; self: Isometry, rhs: Isometry, Output = Isometry; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Isometry ×= Translation @@ -289,6 +290,7 @@ isometry_binop_impl_all!( [ref val] => self * &right; [val ref] => &self * right; [ref ref] => { + #[allow(clippy::suspicious_arithmetic_impl)] let new_tr = &self.translation.vector + self.rotation.transform_vector(&right.vector); Isometry::from_parts(Translation::from(new_tr), self.rotation.clone()) }; @@ -427,10 +429,10 @@ isometry_from_composition_impl_all!( self: Rotation, right: Isometry>, Output = Isometry>; // FIXME: don't call inverse explicitly? - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Isometry × UnitQuaternion @@ -479,10 +481,10 @@ isometry_from_composition_impl_all!( self: UnitQuaternion, right: Isometry>, Output = Isometry>; // FIXME: don't call inverse explicitly? - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Translation × Rotation diff --git a/src/geometry/quaternion_ops.rs b/src/geometry/quaternion_ops.rs index e18c1b16..d91d7fd8 100644 --- a/src/geometry/quaternion_ops.rs +++ b/src/geometry/quaternion_ops.rs @@ -121,11 +121,10 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Add, add; -(U4, U1), (U4, U1); -self: Quaternion, rhs: Quaternion, Output = Quaternion; -Quaternion::from(self.coords + rhs.coords); -); + Add, add; + (U4, U1), (U4, U1); + self: Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from(self.coords + rhs.coords); ); // Quaternion - Quaternion quaternion_op_impl!( @@ -150,11 +149,10 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Sub, sub; -(U4, U1), (U4, U1); -self: Quaternion, rhs: Quaternion, Output = Quaternion; -Quaternion::from(self.coords - rhs.coords); -); + Sub, sub; + (U4, U1), (U4, U1); + self: Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from(self.coords - rhs.coords); ); // Quaternion × Quaternion quaternion_op_impl!( @@ -183,11 +181,10 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U4, U1); -self: Quaternion, rhs: Quaternion, Output = Quaternion; -&self * &rhs; -); + Mul, mul; + (U4, U1), (U4, U1); + self: Quaternion, rhs: Quaternion, Output = Quaternion; + &self * &rhs; ); // UnitQuaternion × UnitQuaternion quaternion_op_impl!( @@ -212,18 +209,17 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U4, U1); -self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; -&self * &rhs; -); + Mul, mul; + (U4, U1), (U4, U1); + self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; + &self * &rhs; ); // UnitQuaternion ÷ UnitQuaternion quaternion_op_impl!( Div, div; (U4, U1), (U4, U1); self: &'a UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion; - self * rhs.inverse(); + #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; 'a, 'b); quaternion_op_impl!( @@ -241,11 +237,10 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Div, div; -(U4, U1), (U4, U1); -self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; -&self / &rhs; -); + Div, div; + (U4, U1), (U4, U1); + self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; + &self / &rhs; ); // UnitQuaternion × Rotation quaternion_op_impl!( @@ -274,12 +269,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U3, U3); -self: UnitQuaternion, rhs: Rotation, -Output = UnitQuaternion => U3, U3; -self * UnitQuaternion::::from_rotation_matrix(&rhs); -); + Mul, mul; + (U4, U1), (U3, U3); + self: UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self * UnitQuaternion::::from_rotation_matrix(&rhs); ); // UnitQuaternion ÷ Rotation quaternion_op_impl!( @@ -308,12 +302,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Div, div; -(U4, U1), (U3, U3); -self: UnitQuaternion, rhs: Rotation, -Output = UnitQuaternion => U3, U3; -self / UnitQuaternion::::from_rotation_matrix(&rhs); -); + Div, div; + (U4, U1), (U3, U3); + self: UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self / UnitQuaternion::::from_rotation_matrix(&rhs); ); // Rotation × UnitQuaternion quaternion_op_impl!( @@ -342,12 +335,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U3, U3), (U4, U1); -self: Rotation, rhs: UnitQuaternion, -Output = UnitQuaternion => U3, U3; -UnitQuaternion::::from_rotation_matrix(&self) * rhs; -); + Mul, mul; + (U3, U3), (U4, U1); + self: Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) * rhs; ); // Rotation ÷ UnitQuaternion quaternion_op_impl!( @@ -376,12 +368,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Div, div; -(U3, U3), (U4, U1); -self: Rotation, rhs: UnitQuaternion, -Output = UnitQuaternion => U3, U3; -UnitQuaternion::::from_rotation_matrix(&self) / rhs; -); + Div, div; + (U3, U3), (U4, U1); + self: Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) / rhs; ); // UnitQuaternion × Vector quaternion_op_impl!( @@ -415,12 +406,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U3, U1) for SB: Storage ; -self: UnitQuaternion, rhs: Vector, -Output = Vector3 => U3, U4; -&self * &rhs; -); + Mul, mul; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: Vector, + Output = Vector3 => U3, U4; + &self * &rhs; ); // UnitQuaternion × Point quaternion_op_impl!( @@ -448,12 +438,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U3, U1); -self: UnitQuaternion, rhs: Point3, -Output = Point3 => U3, U4; -Point3::from(self * rhs.coords); -); + Mul, mul; + (U4, U1), (U3, U1); + self: UnitQuaternion, rhs: Point3, + Output = Point3 => U3, U4; + Point3::from(self * rhs.coords); ); // UnitQuaternion × Unit quaternion_op_impl!( @@ -481,12 +470,11 @@ quaternion_op_impl!( 'b); quaternion_op_impl!( -Mul, mul; -(U4, U1), (U3, U1) for SB: Storage ; -self: UnitQuaternion, rhs: Unit>, -Output = Unit> => U3, U4; -Unit::new_unchecked(self * rhs.into_inner()); -); + Mul, mul; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: Unit>, + Output = Unit> => U3, U4; + Unit::new_unchecked(self * rhs.into_inner()); ); macro_rules! scalar_op_impl( ($($Op: ident, $op: ident, $OpAssign: ident, $op_assign: ident);* $(;)*) => {$( diff --git a/src/geometry/rotation_ops.rs b/src/geometry/rotation_ops.rs index b3330877..9b90c856 100644 --- a/src/geometry/rotation_ops.rs +++ b/src/geometry/rotation_ops.rs @@ -59,10 +59,10 @@ md_impl_all!( Div, div; (D, D), (D, D) for D: DimName; self: Rotation, right: Rotation, Output = Rotation; - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Rotation × Matrix @@ -98,10 +98,10 @@ md_impl_all!( where DefaultAllocator: Allocator where ShapeConstraint: AreMultipliable; self: Matrix, right: Rotation, Output = MatrixMN; - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Rotation × Point diff --git a/src/geometry/similarity_ops.rs b/src/geometry/similarity_ops.rs index 9043a667..d011871d 100644 --- a/src/geometry/similarity_ops.rs +++ b/src/geometry/similarity_ops.rs @@ -158,15 +158,14 @@ similarity_binop_impl_all!( similarity_binop_impl_all!( Div, div; self: Similarity, rhs: Similarity, Output = Similarity; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Similarity ×= Translation similarity_binop_assign_impl_all!( - MulAssign, mul_assign; self: Similarity, rhs: Translation; [val] => *self *= &rhs; @@ -281,6 +280,7 @@ similarity_binop_impl_all!( [ref ref] => { let shift = self.isometry.rotation.transform_vector(&rhs.translation.vector) * self.scaling(); Similarity::from_parts( + #[allow(clippy::suspicious_arithmetic_impl)] Translation::from(&self.isometry.translation.vector + shift), self.isometry.rotation.clone() * rhs.rotation.clone(), self.scaling()) @@ -290,10 +290,10 @@ similarity_binop_impl_all!( similarity_binop_impl_all!( Div, div; self: Similarity, rhs: Isometry, Output = Similarity; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Isometry × Similarity @@ -322,10 +322,10 @@ similarity_binop_impl_all!( similarity_binop_impl_all!( Div, div; self: Isometry, rhs: Similarity, Output = Similarity; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Similarity × Point @@ -364,6 +364,7 @@ similarity_binop_impl_all!( [ref ref] => { let shift = self.isometry.rotation.transform_vector(&right.vector) * self.scaling(); Similarity::from_parts( + #[allow(clippy::suspicious_arithmetic_impl)] Translation::from(&self.isometry.translation.vector + shift), self.isometry.rotation.clone(), self.scaling()) @@ -495,10 +496,10 @@ similarity_from_composition_impl_all!( self: Rotation, right: Similarity>, Output = Similarity>; // FIXME: don't call inverse explicitly? - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Similarity × UnitQuaternion @@ -556,10 +557,10 @@ similarity_from_composition_impl_all!( self: UnitQuaternion, right: Similarity>, Output = Similarity>; // FIXME: don't call inverse explicitly? - [val val] => self * right.inverse(); - [ref val] => self * right.inverse(); - [val ref] => self * right.inverse(); - [ref ref] => self * right.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * right.inverse() }; ); // Similarity × UnitComplex diff --git a/src/geometry/transform_ops.rs b/src/geometry/transform_ops.rs index 5773782c..6986a90a 100644 --- a/src/geometry/transform_ops.rs +++ b/src/geometry/transform_ops.rs @@ -143,6 +143,7 @@ md_impl_all!( if C::has_normalizer() { let normalizer = self.matrix().fixed_slice::(D::dim(), 0); + #[allow(clippy::suspicious_arithmetic_impl)] let n = normalizer.tr_dot(&rhs.coords) + unsafe { *self.matrix().get_unchecked((D::dim(), D::dim())) }; if !n.is_zero() { @@ -293,10 +294,10 @@ md_impl_all!( Div, div where N: RealField; (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategoryMul, CB: SubTCategoryOf; self: Transform, rhs: Transform, Output = Transform; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.clone().inverse(); - [ref ref] => self * rhs.clone().inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.clone().inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.clone().inverse() }; ); // Transform ÷ Rotation @@ -304,10 +305,10 @@ md_impl_all!( Div, div where N: RealField; (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul; self: Transform, rhs: Rotation, Output = Transform; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Rotation ÷ Transform @@ -315,10 +316,10 @@ md_impl_all!( Div, div where N: RealField; (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul; self: Rotation, rhs: Transform, Output = Transform; - [val val] => self.inverse() * rhs; - [ref val] => self.inverse() * rhs; - [val ref] => self.inverse() * rhs; - [ref ref] => self.inverse() * rhs; + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; ); // Transform ÷ UnitQuaternion @@ -326,10 +327,10 @@ md_impl_all!( Div, div where N: RealField; (U4, U4), (U4, U1) for C: TCategoryMul; self: Transform, rhs: UnitQuaternion, Output = Transform; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // UnitQuaternion ÷ Transform @@ -337,10 +338,10 @@ md_impl_all!( Div, div where N: RealField; (U4, U1), (U4, U4) for C: TCategoryMul; self: UnitQuaternion, rhs: Transform, Output = Transform; - [val val] => self.inverse() * rhs; - [ref val] => self.inverse() * rhs; - [val ref] => self.inverse() * rhs; - [ref ref] => self.inverse() * rhs; + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; ); // // Transform ÷ Isometry @@ -402,10 +403,10 @@ md_impl_all!( Div, div where N: RealField; (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul; self: Transform, rhs: Translation, Output = Transform; - [val val] => self * rhs.inverse(); - [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.inverse(); - [ref ref] => self * rhs.inverse(); + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * rhs.inverse() }; ); // Translation ÷ Transform @@ -414,10 +415,10 @@ md_impl_all!( (D, U1), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul; self: Translation, rhs: Transform, Output = Transform; - [val val] => self.inverse() * rhs; - [ref val] => self.inverse() * rhs; - [val ref] => self.inverse() * rhs; - [ref ref] => self.inverse() * rhs; + [val val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref val] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [val ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self.inverse() * rhs }; ); // Transform ×= Transform diff --git a/src/geometry/translation_ops.rs b/src/geometry/translation_ops.rs index c00b25bc..8d543c20 100644 --- a/src/geometry/translation_ops.rs +++ b/src/geometry/translation_ops.rs @@ -13,44 +13,50 @@ use crate::geometry::{Point, Translation}; add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: &'b Translation, Output = Translation; - Translation::from(&self.vector + &right.vector); 'a, 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(&self.vector + &right.vector) }; + 'a, 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: Translation, Output = Translation; - Translation::from(&self.vector + right.vector); 'a); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(&self.vector + right.vector) }; + 'a); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: &'b Translation, Output = Translation; - Translation::from(self.vector + &right.vector); 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(self.vector + &right.vector) }; + 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: Translation, Output = Translation; - Translation::from(self.vector + right.vector); ); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(self.vector + right.vector) }; ); // Translation ÷ Translation // FIXME: instead of calling inverse explicitly, could we just add a `mul_tr` or `mul_inv` method? add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: &'b Translation, Output = Translation; - Translation::from(&self.vector - &right.vector); 'a, 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(&self.vector - &right.vector) }; + 'a, 'b); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: Translation, Output = Translation; - Translation::from(&self.vector - right.vector); 'a); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(&self.vector - right.vector) }; + 'a); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: &'b Translation, Output = Translation; - Translation::from(self.vector - &right.vector); 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(self.vector - &right.vector) }; + 'b); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: Translation, Output = Translation; - Translation::from(self.vector - right.vector); ); + #[allow(clippy::suspicious_arithmetic_impl)] { Translation::from(self.vector - right.vector) }; ); // Translation × Point // FIXME: we don't handle properly non-zero origins here. Do we want this to be the intended @@ -58,107 +64,45 @@ add_sub_impl!(Div, div, ClosedSub; add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: &'b Point, Output = Point; - right + &self.vector; 'a, 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { right + &self.vector }; + 'a, 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: &'a Translation, right: Point, Output = Point; - right + &self.vector; 'a); + #[allow(clippy::suspicious_arithmetic_impl)] { right + &self.vector }; + 'a); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: &'b Point, Output = Point; - right + self.vector; 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { right + self.vector }; + 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; self: Translation, right: Point, Output = Point; - right + self.vector; ); + #[allow(clippy::suspicious_arithmetic_impl)] { right + self.vector }; ); // Translation *= Translation add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; (D, U1), (D, U1) for D: DimName; self: Translation, right: &'b Translation; - self.vector += &right.vector; 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { self.vector += &right.vector }; + 'b); add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; (D, U1), (D, U1) for D: DimName; self: Translation, right: Translation; - self.vector += right.vector; ); + #[allow(clippy::suspicious_arithmetic_impl)] { self.vector += right.vector }; ); add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; (D, U1), (D, U1) for D: DimName; self: Translation, right: &'b Translation; - self.vector -= &right.vector; 'b); + #[allow(clippy::suspicious_arithmetic_impl)] { self.vector -= &right.vector }; + 'b); add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; (D, U1), (D, U1) for D: DimName; self: Translation, right: Translation; - self.vector -= right.vector; ); - -/* -// Translation × Matrix -add_sub_impl!(Mul, mul; - (D1, D1), (R2, C2) for D1, R2, C2; - self: &'a Translation, right: &'b Matrix, Output = MatrixMN; - self.vector() * right; 'a, 'b); - -add_sub_impl!(Mul, mul; - (D1, D1), (R2, C2) for D1, R2, C2; - self: &'a Translation, right: Matrix, Output = MatrixMN; - self.vector() * right; 'a); - -add_sub_impl!(Mul, mul; - (D1, D1), (R2, C2) for D1, R2, C2; - self: Translation, right: &'b Matrix, Output = MatrixMN; - self.unwrap() * right; 'b); - -add_sub_impl!(Mul, mul; - (D1, D1), (R2, C2) for D1, R2, C2; - self: Translation, right: Matrix, Output = MatrixMN; - self.unwrap() * right; ); - -// Matrix × Translation -add_sub_impl!(Mul, mul; - (R1, C1), (D2, D2) for R1, C1, D2; - self: &'a Matrix, right: &'b Translation, Output = MatrixMN; - self * right.vector(); 'a, 'b); - -add_sub_impl!(Mul, mul; - (R1, C1), (D2, D2) for R1, C1, D2; - self: &'a Matrix, right: Translation, Output = MatrixMN; - self * right.unwrap(); 'a); - -add_sub_impl!(Mul, mul; - (R1, C1), (D2, D2) for R1, C1, D2; - self: Matrix, right: &'b Translation, Output = MatrixMN; - self * right.vector(); 'b); - - -add_sub_impl!(Mul, mul; - (R1, C1), (D2, D2) for R1, C1, D2; - self: Matrix, right: Translation, Output = MatrixMN; - self * right.unwrap(); ); - -// Matrix *= Translation -md_assign_impl!(MulAssign, mul_assign; - (R1, C1), (C1, C1) for R1, C1; - self: Matrix, right: &'b Translation; - self.mul_assign(right.vector()); 'b); - -md_assign_impl!(MulAssign, mul_assign; - (R1, C1), (C1, C1) for R1, C1; - self: Matrix, right: Translation; - self.mul_assign(right.unwrap()); ); - - -md_assign_impl!(DivAssign, div_assign; - (R1, C1), (C1, C1) for R1, C1; - self: Matrix, right: &'b Translation; - self.mul_assign(right.inverse().vector()); 'b); - -md_assign_impl!(DivAssign, div_assign; - (R1, C1), (C1, C1) for R1, C1; - self: Matrix, right: Translation; - self.mul_assign(right.inverse().unwrap()); ); -*/ + #[allow(clippy::suspicious_arithmetic_impl)] { self.vector -= right.vector }; ); diff --git a/src/geometry/unit_complex_ops.rs b/src/geometry/unit_complex_ops.rs index f7af7088..af1a32f1 100644 --- a/src/geometry/unit_complex_ops.rs +++ b/src/geometry/unit_complex_ops.rs @@ -96,6 +96,7 @@ where #[inline] fn div(self, rhs: Self) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] Unit::new_unchecked(self.into_inner() * rhs.conjugate().into_inner()) } } @@ -108,6 +109,7 @@ where #[inline] fn div(self, rhs: UnitComplex) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] Unit::new_unchecked(self.complex() * rhs.conjugate().into_inner()) } } @@ -120,6 +122,7 @@ where #[inline] fn div(self, rhs: &'b UnitComplex) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] Unit::new_unchecked(self.into_inner() * rhs.conjugate().into_inner()) } } @@ -132,6 +135,7 @@ where #[inline] fn div(self, rhs: &'b UnitComplex) -> Self::Output { + #[allow(clippy::suspicious_arithmetic_impl)] Unit::new_unchecked(self.complex() * rhs.conjugate().into_inner()) } } @@ -206,7 +210,7 @@ complex_op_impl_all!( [val val] => &self / &rhs; [ref val] => self / &rhs; [val ref] => &self / rhs; - [ref ref] => self * UnitComplex::from_rotation_matrix(rhs).inverse(); + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { self * UnitComplex::from_rotation_matrix(rhs).inverse() }; ); // Rotation × UnitComplex @@ -228,7 +232,7 @@ complex_op_impl_all!( [val val] => &self / &rhs; [ref val] => self / &rhs; [val ref] => &self / rhs; - [ref ref] => UnitComplex::from_rotation_matrix(self) * rhs.inverse(); + [ref ref] => #[allow(clippy::suspicious_arithmetic_impl)] { UnitComplex::from_rotation_matrix(self) * rhs.inverse() }; ); // UnitComplex × Point From d990aff44e68869719251d564ffe9a4beac9c1f8 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 25 Oct 2020 18:38:50 +0100 Subject: [PATCH 53/55] clippy: fix clippy::eq_op error (false positive) --- src/geometry/quaternion.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 22ee5309..69d54967 100755 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -389,6 +389,7 @@ where /// ``` #[inline] pub fn outer(&self, other: &Self) -> Self { + #[allow(clippy::eq_op)] (self * other - other * self).half() } From 1c8435c910e5cd34aab413ba1624b0dfa595f3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Mon, 26 Oct 2020 09:40:49 +0100 Subject: [PATCH 54/55] Release v0.23.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 378bf0c8..a36df272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra" -version = "0.22.1" +version = "0.23.0" authors = [ "Sébastien Crozet " ] description = "Linear algebra library with transformations and statically-sized or dynamically-sized matrices." From a2b128c4e4978d284cbc7fb5100fcaa2ecfe8767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Crozet=20S=C3=A9bastien?= Date: Mon, 26 Oct 2020 09:41:02 +0100 Subject: [PATCH 55/55] Release nalgebra-glm v0.9.0 --- nalgebra-glm/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nalgebra-glm/Cargo.toml b/nalgebra-glm/Cargo.toml index 0400d91a..f4e12f8e 100644 --- a/nalgebra-glm/Cargo.toml +++ b/nalgebra-glm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nalgebra-glm" -version = "0.8.0" +version = "0.9.0" authors = ["sebcrozet "] description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library." @@ -25,4 +25,4 @@ abomonation-serialize = [ "nalgebra/abomonation-serialize" ] num-traits = { version = "0.2", default-features = false } approx = { version = "0.4", default-features = false } simba = { version = "0.3", default-features = false } -nalgebra = { path = "..", version = "0.22", default-features = false } +nalgebra = { path = "..", version = "0.23", default-features = false }