diff --git a/.travis.yml b/.travis.yml index 16e2fbde..ac5dcd74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,12 @@ matrix: allow_failures: - rust: nightly - rust: beta +addons: + apt: + packages: + - gfortran + - libblas3gf + - liblapack3gf script: - rustc --version - cargo --version @@ -19,3 +25,8 @@ script: - cargo build --verbose --features serde-serialize - cargo build --verbose --features abomonation-serialize - cargo test --verbose --features "arbitrary serde-serialize abomonation-serialize" + - cd nalgebra-lapack; cargo test --verbose + +env: + matrix: + - CARGO_FEATURE_SYSTEM_NETLIB=1 CARGO_FEATURE_EXCLUDE_LAPACKE=1 CARGO_FEATURE_EXCLUDE_CBLAS=1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9575d0..9cdabd87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,117 @@ documented here. This project adheres to [Semantic Versioning](http://semver.org/). -## [0.13.0] - WIP + +## [0.13.0] + +The **nalgebra-lapack** crate has been updated. This now includes a broad range +matrix decompositions using LAPACK bindings. + +### Breaking semantic change + * The implementation of slicing with steps now matches the documentation. + Before, step identified the number to add to pass from one column/row index + to the next one. This made 0 step invalid. Now (and on the documentation so + far), the step is the number of ignored row/columns between each + row/column. Thus, a step of 0 means that no row/column is ignored. For + example, a step of, say, 3 on previous versions should now bet set to 2. + +### Modified + * The trait `Axpy` has been replaced by a metod `.axpy`. + * The alias `MatrixNM` is now deprecated. Use `MatrixMN` instead (we + reordered M and N to be in alphabetical order). + * In-place componentwise multiplication and division + `.component_mul_mut(...)` and `.component_div_mut(...)` have bee deprecated + for a future renaming. Use `.component_mul_assign(...)` and + `.component_div_assign(...)` instead. + ### Added + * `alga::general::Real` is now re-exported by nalgebra. + elements.) + * `::zeros(...)` that creates a matrix filled with zeroes. + * `::from_partial_diagonal(...)` that creates a matrix from diagonal elements. + The matrix can be rectangular. If not enough elements are provided, the rest + of the diagonal is set to 0. + * `.conjugate_transpose()` computes the transposed conjugate of a + complex matrix. + * `.conjugate_transpose_to(...)` computes the transposed conjugate of a + complex matrix. The result written into a user-provided matrix. + * `.transpose_to(...)` is the same as `.transpose()` but stores the result in + the provided matrix. + * `.conjugate_transpose_to(...)` is the same as `.conjugate_transpose()` but + stores the result in the provided matrix. + * Implements `IntoIterator` for `&Matrix`, `&mut Matrix` and `Matrix`. + * `.mul_to(...)` multiplies two matrices and stores the result to the given buffer. + * `.tr_mul_to(...)` left-multiplies `self.transpose()` to another matrix and stores the result to the given buffer. + * `.add_scalar(...)` that adds a scalar to each component of a matrix. + * `.add_scalar_mut(...)` that adds in-place a scalar to each component of a matrix. * `.kronecker(a, b)` computes the kronecker product (i.e. matrix tensor product) of two matrices. - * `.set_row(i, row)` sets the i-th row of the matrix. - * `.set_column(j, column)` sets the i-th column of the matrix. + * `.apply(f)` replaces each component of a matrix with the results of the + closure `f` called on each of them. +Pure Rust implementation of some Blas operations: + + * `.iamax()` retuns the index of the maximum value of a vector. + * `.axpy(...)` computes `self = a * x + b * self`. + * `.gemv(...)` computes `self = alpha * a * x + beta * self` with a matrix and vector `a` and `x`. + * `.ger(...)` computes `self = alpha * x^t * y + beta * self` where `x` and `y` are vectors. + * `.gemm(...)` computes `self = alpha * a * b + beta * self` where `a` and `b` are matrices. + * `.gemv_symm(...)` is the same as `.gemv` except that `self` is assumed symmetric. + * `.ger_symm(...)` is the same as `.ger` except that `self` is assumed symmetric. + +New slicing methods: + * `.rows_range(...)` that retrieves a reference to a range of rows. + * `.rows_range_mut(...)` that retrieves a mutable reference to a range of rows. + * `.columns_range(...)` that retrieves a reference to a range of columns. + * `.columns_range_mut(...)` that retrieves a mutable reference to a range of columns. + +Matrix decompositions implemented in pure Rust: + * Cholesky, SVD, LU, QR, Hessenberg, Schur, Symmetric eigendecompositions, + Bidiagonal, Symmetric tridiagonal + * Computation of householder reflectors and givens rotations. + +Matrix edition: + * `.upper_triangle()` extracts the upper triangle of a matrix, including the diagonal. + * `.lower_triangle()` extracts the lower triangle of a matrix, including the diagonal. + * `.fill(...)` fills the matrix with a single value. + * `.fill_with_identity(...)` fills the matrix with the identity. + * `.fill_diagonal(...)` fills the matrix diagonal with a single value. + * `.fill_row(...)` fills a selected matrix row with a single value. + * `.fill_column(...)` fills a selected matrix column with a single value. + * `.set_diagonal(...)` sets the matrix diagonal. + * `.set_row(...)` sets a selected row. + * `.set_column(...)` sets a selected column. + * `.fill_lower_triangle(...)` fills some sub-diagonals bellow the main diagonal with a value. + * `.fill_upper_triangle(...)` fills some sub-diagonals above the main diagonal with a value. + * `.swap_rows(...)` swaps two rows. + * `.swap_columns(...)` swaps two columns. + +Column removal: + * `.remove_column(...)` removes one column. + * `.remove_fixed_columns(...)` removes `D` columns. + * `.remove_columns(...)` removes a number of columns known at run-time. + +Row removal: + * `.remove_row(...)` removes one row. + * `.remove_fixed_rows(...)` removes `D` rows. + * `.remove_rows(...)` removes a number of rows known at run-time. + +Column insertion: + * `.insert_column(...)` adds one column at the given position. + * `.insert_fixed_columns(...)` adds `D` columns at the given position. + * `.insert_columns(...)` adds at the given position a number of columns known at run-time. + +Row insertion: + * `.insert_row(...)` adds one row at the given position. + * `.insert_fixed_rows(...)` adds `D` rows at the given position. + * `.insert_rows(...)` adds at the given position a number of rows known at run-time. ## [0.12.0] The main change of this release is the update of the dependency serde to 1.0. ### Added - * `.trace()` that computes the trace of a matrix (i.e., the sum of its - diagonal elements.) + * `.trace()` that computes the trace of a matrix (the sum of its diagonal + elements.) ## [0.11.0] The [website](http://nalgebra.org) has been fully rewritten and gives a good diff --git a/Cargo.toml b/Cargo.toml index 9110c8c1..7551baae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,22 +16,23 @@ name = "nalgebra" path = "src/lib.rs" [features] -arbitrary = [ "quickcheck" ] +arbitrary = [ "quickcheck" ] serde-serialize = [ "serde", "serde_derive", "num-complex/serde" ] abomonation-serialize = [ "abomonation" ] +debug = [ ] [dependencies] -typenum = "1.4" -generic-array = "0.8" -rand = "0.3" -num-traits = "0.1" -num-complex = "0.1" -approx = "0.1" -alga = "0.5" -serde = { version = "1.0", optional = true } -serde_derive = { version = "1.0", optional = true } +typenum = "1.7" +generic-array = "0.8" +rand = "0.3" +num-traits = "0.1" +num-complex = "0.1" +approx = "0.1" +alga = "0.5" +matrixmultiply = "0.1" +serde = { version = "1.0", optional = true } +serde_derive = { version = "1.0", optional = true } abomonation = { version = "0.4", optional = true } -# clippy = "*" [dependencies.quickcheck] optional = true @@ -39,3 +40,6 @@ version = "0.4" [dev-dependencies] serde_json = "1.0" + +[workspace] +members = [ "nalgebra-lapack" ] diff --git a/Makefile b/Makefile index b0eb255d..ad1fafcc 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ all: - CARGO_INCREMENTAL=1 cargo build --features "arbitrary serde-serialize" + cargo check --features "debug arbitrary serde-serialize" doc: - CARGO_INCREMENTAL=1 cargo doc --no-deps --features "arbitrary serde-serialize" + cargo doc --no-deps --features "debug arbitrary serde-serialize" bench: cargo bench test: - cargo test --features "arbitrary serde-serialize" + cargo test --features "debug arbitrary serde-serialize" diff --git a/README.md b/README.md index c7eeeabb..49815cb2 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,13 @@ Linear algebra library for the Rust programming language.

+ +----- + +

+  Click this button if you which to donate to support the development of nalgebra: +

+ +

+ Become a Patron! +

diff --git a/benches/common/macros.rs b/benches/common/macros.rs index 74b0e0cc..55d6ba3c 100644 --- a/benches/common/macros.rs +++ b/benches/common/macros.rs @@ -4,20 +4,12 @@ macro_rules! bench_binop( ($name: ident, $t1: ty, $t2: ty, $binop: ident) => { #[bench] fn $name(bh: &mut Bencher) { - const LEN: usize = 1 << 13; - let mut rng = IsaacRng::new_unseeded(); - - let elems1: Vec<$t1> = (0usize .. LEN).map(|_| rng.gen::<$t1>()).collect(); - let elems2: Vec<$t2> = (0usize .. LEN).map(|_| rng.gen::<$t2>()).collect(); - let mut i = 0; + let a = rng.gen::<$t1>(); + let b = rng.gen::<$t2>(); bh.iter(|| { - i = (i + 1) & (LEN - 1); - - unsafe { - test::black_box(elems1.get_unchecked(i).$binop(*elems2.get_unchecked(i))) - } + a.$binop(b) }) } } @@ -27,43 +19,27 @@ macro_rules! bench_binop_ref( ($name: ident, $t1: ty, $t2: ty, $binop: ident) => { #[bench] fn $name(bh: &mut Bencher) { - const LEN: usize = 1 << 13; - let mut rng = IsaacRng::new_unseeded(); - - let elems1: Vec<$t1> = (0usize .. LEN).map(|_| rng.gen::<$t1>()).collect(); - let elems2: Vec<$t2> = (0usize .. LEN).map(|_| rng.gen::<$t2>()).collect(); - let mut i = 0; + let a = rng.gen::<$t1>(); + let b = rng.gen::<$t2>(); bh.iter(|| { - i = (i + 1) & (LEN - 1); - - unsafe { - test::black_box(elems1.get_unchecked(i).$binop(elems2.get_unchecked(i))) - } + a.$binop(&b) }) } } ); -macro_rules! bench_binop_na( - ($name: ident, $t1: ty, $t2: ty, $binop: ident) => { +macro_rules! bench_binop_fn( + ($name: ident, $t1: ty, $t2: ty, $binop: path) => { #[bench] fn $name(bh: &mut Bencher) { - const LEN: usize = 1 << 13; - let mut rng = IsaacRng::new_unseeded(); - - let elems1: Vec<$t1> = (0usize .. LEN).map(|_| rng.gen::<$t1>()).collect(); - let elems2: Vec<$t2> = (0usize .. LEN).map(|_| rng.gen::<$t2>()).collect(); - let mut i = 0; + let a = rng.gen::<$t1>(); + let b = rng.gen::<$t2>(); bh.iter(|| { - i = (i + 1) & (LEN - 1); - - unsafe { - test::black_box(na::$binop(elems1.get_unchecked(i), elems2.get_unchecked(i))) - } + $binop(&a, &b) }) } } diff --git a/benches/core/matrix.rs b/benches/core/matrix.rs new file mode 100644 index 00000000..442adafe --- /dev/null +++ b/benches/core/matrix.rs @@ -0,0 +1,192 @@ +use rand::{IsaacRng, Rng}; +use test::{self, Bencher}; +use na::{Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4, + MatrixN, U10, + DMatrix, DVector}; +use std::ops::{Add, Sub, Mul, Div}; + +#[path="../common/macros.rs"] +mod macros; + +bench_binop!(mat2_mul_m, Matrix2, Matrix2, mul); +bench_binop!(mat3_mul_m, Matrix3, Matrix3, mul); +bench_binop!(mat4_mul_m, Matrix4, Matrix4, mul); + +bench_binop_ref!(mat2_tr_mul_m, Matrix2, Matrix2, tr_mul); +bench_binop_ref!(mat3_tr_mul_m, Matrix3, Matrix3, tr_mul); +bench_binop_ref!(mat4_tr_mul_m, Matrix4, Matrix4, tr_mul); + +bench_binop!(mat2_add_m, Matrix2, Matrix2, add); +bench_binop!(mat3_add_m, Matrix3, Matrix3, add); +bench_binop!(mat4_add_m, Matrix4, Matrix4, add); + +bench_binop!(mat2_sub_m, Matrix2, Matrix2, sub); +bench_binop!(mat3_sub_m, Matrix3, Matrix3, sub); +bench_binop!(mat4_sub_m, Matrix4, Matrix4, sub); + +bench_binop!(mat2_mul_v, Matrix2, Vector2, mul); +bench_binop!(mat3_mul_v, Matrix3, Vector3, mul); +bench_binop!(mat4_mul_v, Matrix4, Vector4, mul); + +bench_binop_ref!(mat2_tr_mul_v, Matrix2, Vector2, tr_mul); +bench_binop_ref!(mat3_tr_mul_v, Matrix3, Vector3, tr_mul); +bench_binop_ref!(mat4_tr_mul_v, Matrix4, Vector4, tr_mul); + +bench_binop!(mat2_mul_s, Matrix2, f32, mul); +bench_binop!(mat3_mul_s, Matrix3, f32, mul); +bench_binop!(mat4_mul_s, Matrix4, f32, mul); + +bench_binop!(mat2_div_s, Matrix2, f32, div); +bench_binop!(mat3_div_s, Matrix3, f32, div); +bench_binop!(mat4_div_s, Matrix4, f32, div); + +bench_unop!(mat2_inv, Matrix2, try_inverse); +bench_unop!(mat3_inv, Matrix3, try_inverse); +bench_unop!(mat4_inv, Matrix4, try_inverse); + +bench_unop!(mat2_transpose, Matrix2, transpose); +bench_unop!(mat3_transpose, Matrix3, transpose); +bench_unop!(mat4_transpose, Matrix4, transpose); + +#[bench] +fn mat_div_scalar(b: &mut Bencher) { + let a = DMatrix::from_row_slice(1000, 1000, &vec![2.0;1000000]); + let n = 42.0; + + b.iter(|| { + let mut aa = a.clone(); + let mut b = aa.slice_mut((0, 0), (1000, 1000)); + b /= n + }) +} + +#[bench] +fn mat100_add_mat100(bench: &mut Bencher) { + let a = DMatrix::::new_random(100, 100); + let b = DMatrix::::new_random(100, 100); + + bench.iter(|| { &a + &b }) +} + +#[bench] +fn mat4_mul_mat4(bench: &mut Bencher) { + let a = DMatrix::::new_random(4, 4); + let b = DMatrix::::new_random(4, 4); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat5_mul_mat5(bench: &mut Bencher) { + let a = DMatrix::::new_random(5, 5); + let b = DMatrix::::new_random(5, 5); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat6_mul_mat6(bench: &mut Bencher) { + let a = DMatrix::::new_random(6, 6); + let b = DMatrix::::new_random(6, 6); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat7_mul_mat7(bench: &mut Bencher) { + let a = DMatrix::::new_random(7, 7); + let b = DMatrix::::new_random(7, 7); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat8_mul_mat8(bench: &mut Bencher) { + let a = DMatrix::::new_random(8, 8); + let b = DMatrix::::new_random(8, 8); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat9_mul_mat9(bench: &mut Bencher) { + let a = DMatrix::::new_random(9, 9); + let b = DMatrix::::new_random(9, 9); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat10_mul_mat10(bench: &mut Bencher) { + let a = DMatrix::::new_random(10, 10); + let b = DMatrix::::new_random(10, 10); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat10_mul_mat10_static(bench: &mut Bencher) { + let a = MatrixN::::new_random(); + let b = MatrixN::::new_random(); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat100_mul_mat100(bench: &mut Bencher) { + let a = DMatrix::::new_random(100, 100); + let b = DMatrix::::new_random(100, 100); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn mat500_mul_mat500(bench: &mut Bencher) { + let a = DMatrix::::from_element(500, 500, 5f64); + let b = DMatrix::::from_element(500, 500, 6f64); + + bench.iter(|| { &a * &b }) +} + +#[bench] +fn copy_from(bench: &mut Bencher) { + let a = DMatrix::::new_random(1000, 1000); + let mut b = DMatrix::::new_random(1000, 1000); + + bench.iter(|| { + b.copy_from(&a); + }) +} + +#[bench] +fn axpy(bench: &mut Bencher) { + let x = DVector::::from_element(100000, 2.0); + let mut y = DVector::::from_element(100000, 3.0); + let a = 42.0; + + bench.iter(|| { + y.axpy(a, &x, 1.0); + }) +} + +#[bench] +fn tr_mul_to(bench: &mut Bencher) { + let a = DMatrix::::new_random(1000, 1000); + let b = DVector::::new_random(1000); + let mut c = DVector::from_element(1000, 0.0); + + bench.iter(|| { + a.tr_mul_to(&b, &mut c) + }) +} + +#[bench] +fn mat_mul_mat(bench: &mut Bencher) { + let a = DMatrix::::new_random(100, 100); + let b = DMatrix::::new_random(100, 100); + let mut ab = DMatrix::::from_element(100, 100, 0.0); + + bench.iter(|| { + test::black_box(a.mul_to(&b, &mut ab)); + }) +} diff --git a/benches/core/mod.rs b/benches/core/mod.rs new file mode 100644 index 00000000..9699a728 --- /dev/null +++ b/benches/core/mod.rs @@ -0,0 +1,2 @@ +mod matrix; +mod vector; diff --git a/benches/core/vector.rs b/benches/core/vector.rs new file mode 100644 index 00000000..fb94de36 --- /dev/null +++ b/benches/core/vector.rs @@ -0,0 +1,128 @@ +use rand::{IsaacRng, Rng}; +use test::{self, Bencher}; +use typenum::U10000; +use na::{Vector2, Vector3, Vector4, VectorN, DVector}; +use std::ops::{Add, Sub, Mul, Div}; + +#[path="../common/macros.rs"] +mod macros; + +bench_binop!(vec2_add_v_f32, Vector2, Vector2, add); +bench_binop!(vec3_add_v_f32, Vector3, Vector3, add); +bench_binop!(vec4_add_v_f32, Vector4, Vector4, add); + +bench_binop!(vec2_add_v_f64, Vector2, Vector2, add); +bench_binop!(vec3_add_v_f64, Vector3, Vector3, add); +bench_binop!(vec4_add_v_f64, Vector4, Vector4, add); + +bench_binop!(vec2_sub_v, Vector2, Vector2, sub); +bench_binop!(vec3_sub_v, Vector3, Vector3, sub); +bench_binop!(vec4_sub_v, Vector4, Vector4, sub); + +bench_binop!(vec2_mul_s, Vector2, f32, mul); +bench_binop!(vec3_mul_s, Vector3, f32, mul); +bench_binop!(vec4_mul_s, Vector4, f32, mul); + +bench_binop!(vec2_div_s, Vector2, f32, div); +bench_binop!(vec3_div_s, Vector3, f32, div); +bench_binop!(vec4_div_s, Vector4, f32, div); + +bench_binop_ref!(vec2_dot_f32, Vector2, Vector2, dot); +bench_binop_ref!(vec3_dot_f32, Vector3, Vector3, dot); +bench_binop_ref!(vec4_dot_f32, Vector4, Vector4, dot); + +bench_binop_ref!(vec2_dot_f64, Vector2, Vector2, dot); +bench_binop_ref!(vec3_dot_f64, Vector3, Vector3, dot); +bench_binop_ref!(vec4_dot_f64, Vector4, Vector4, dot); + +bench_binop_ref!(vec3_cross, Vector3, Vector3, cross); + +bench_unop!(vec2_norm, Vector2, norm); +bench_unop!(vec3_norm, Vector3, norm); +bench_unop!(vec4_norm, Vector4, norm); + +bench_unop!(vec2_normalize, Vector2, normalize); +bench_unop!(vec3_normalize, Vector3, normalize); +bench_unop!(vec4_normalize, Vector4, normalize); + +bench_binop_ref!(vec10000_dot_f64, VectorN, VectorN, dot); +bench_binop_ref!(vec10000_dot_f32, VectorN, VectorN, dot); + +#[bench] +fn vec10000_axpy_f64(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = DVector::new_random(10000); + let b = DVector::new_random(10000); + let n = rng.gen::(); + + bh.iter(|| { + a.axpy(n, &b, 1.0) + }) +} + +#[bench] +fn vec10000_axpy_beta_f64(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = DVector::new_random(10000); + let b = DVector::new_random(10000); + let n = rng.gen::(); + let beta = rng.gen::(); + + bh.iter(|| { + a.axpy(n, &b, beta) + }) +} + +#[bench] +fn vec10000_axpy_f64_slice(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = DVector::new_random(10000); + let b = DVector::new_random(10000); + let n = rng.gen::(); + + bh.iter(|| { + let mut a = a.fixed_rows_mut::(0); + let b = b.fixed_rows::(0); + + a.axpy(n, &b, 1.0) + }) +} + +#[bench] +fn vec10000_axpy_f64_static(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = VectorN::::new_random(); + let b = VectorN::::new_random(); + let n = rng.gen::(); + + // NOTE: for some reasons, it is much faster if the arument are boxed (Box::new(VectorN...)). + bh.iter(|| { + a.axpy(n, &b, 1.0) + }) +} + + +#[bench] +fn vec10000_axpy_f32(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = DVector::new_random(10000); + let b = DVector::new_random(10000); + let n = rng.gen::(); + + bh.iter(|| { + a.axpy(n, &b, 1.0) + }) +} + +#[bench] +fn vec10000_axpy_beta_f32(bh: &mut Bencher) { + let mut rng = IsaacRng::new_unseeded(); + let mut a = DVector::new_random(10000); + let b = DVector::new_random(10000); + let n = rng.gen::(); + let beta = rng.gen::(); + + bh.iter(|| { + a.axpy(n, &b, beta) + }) +} diff --git a/benches/geometry/mod.rs b/benches/geometry/mod.rs new file mode 100644 index 00000000..0f9eb371 --- /dev/null +++ b/benches/geometry/mod.rs @@ -0,0 +1 @@ +mod quaternion; diff --git a/benches/geometry/quaternion.rs b/benches/geometry/quaternion.rs new file mode 100644 index 00000000..0740ee63 --- /dev/null +++ b/benches/geometry/quaternion.rs @@ -0,0 +1,22 @@ +use rand::{IsaacRng, Rng}; +use test::{self, Bencher}; +use na::{Quaternion, UnitQuaternion, Vector3}; +use std::ops::{Add, Sub, Mul, Div}; + +#[path="../common/macros.rs"] +mod macros; + +bench_binop!(quaternion_add_q, Quaternion, Quaternion, add); +bench_binop!(quaternion_sub_q, Quaternion, Quaternion, sub); +bench_binop!(quaternion_mul_q, Quaternion, Quaternion, mul); + +bench_binop!(unit_quaternion_mul_v, UnitQuaternion, Vector3, mul); + +bench_binop!(quaternion_mul_s, Quaternion, f32, mul); +bench_binop!(quaternion_div_s, Quaternion, f32, div); + +bench_unop!(quaternion_inv, Quaternion, try_inverse); +bench_unop!(unit_quaternion_inv, UnitQuaternion, inverse); + +// bench_unop_self!(quaternion_conjugate, Quaternion, conjugate); +// bench_unop!(quaternion_normalize, Quaternion, normalize); diff --git a/benches/lib.rs b/benches/lib.rs new file mode 100644 index 00000000..5f5ad373 --- /dev/null +++ b/benches/lib.rs @@ -0,0 +1,21 @@ +#![feature(test)] +#![allow(unused_macros)] + +extern crate test; +extern crate rand; +extern crate typenum; +extern crate nalgebra as na; + + +use rand::{Rng, IsaacRng}; +use na::DMatrix; + + +mod core; +mod linalg; +mod geometry; + +fn reproductible_dmatrix(nrows: usize, ncols: usize) -> DMatrix { + let mut rng = IsaacRng::new_unseeded(); + DMatrix::::from_fn(nrows, ncols, |_, _| rng.gen()) +} diff --git a/benches/linalg/bidiagonal.rs b/benches/linalg/bidiagonal.rs new file mode 100644 index 00000000..e35ae109 --- /dev/null +++ b/benches/linalg/bidiagonal.rs @@ -0,0 +1,75 @@ +use test::{self, Bencher}; +use na::{Matrix4, DMatrix, Bidiagonal}; + +#[path="../common/macros.rs"] +mod macros; + +// Without unpack. +#[bench] +fn bidiagonalize_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(Bidiagonal::new(m.clone()))) +} + +#[bench] +fn bidiagonalize_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| test::black_box(Bidiagonal::new(m.clone()))) +} + +#[bench] +fn bidiagonalize_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(Bidiagonal::new(m.clone()))) +} + +#[bench] +fn bidiagonalize_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| test::black_box(Bidiagonal::new(m.clone()))) +} + +#[bench] +fn bidiagonalize_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(Bidiagonal::new(m.clone()))) +} + + +// With unpack. +#[bench] +fn bidiagonalize_unpack_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| { + let bidiag = Bidiagonal::new(m.clone()); + let _ = bidiag.unpack(); + }) +} + +#[bench] +fn bidiagonalize_unpack_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| { + let bidiag = Bidiagonal::new(m.clone()); + let _ = bidiag.unpack(); + }) +} + +#[bench] +fn bidiagonalize_unpack_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| { + let bidiag = Bidiagonal::new(m.clone()); + let _ = bidiag.unpack(); + }) +} + +#[bench] +fn bidiagonalize_unpack_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| { + let bidiag = Bidiagonal::new(m.clone()); + let _ = bidiag.unpack(); + }) +} + diff --git a/benches/linalg/cholesky.rs b/benches/linalg/cholesky.rs new file mode 100644 index 00000000..6337c226 --- /dev/null +++ b/benches/linalg/cholesky.rs @@ -0,0 +1,109 @@ +use test::{self, Bencher}; +use na::{DMatrix, DVector, Cholesky}; + +#[bench] +fn cholesky_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let m = &m * m.transpose(); + + bh.iter(|| test::black_box(Cholesky::new(m.clone()))) +} + +#[bench] +fn cholesky_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let m = &m * m.transpose(); + + bh.iter(|| test::black_box(Cholesky::new(m.clone()))) +} + +// With unpack. +#[bench] +fn cholesky_decompose_unpack_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let m = &m * m.transpose(); + + bh.iter(|| { + let chol = Cholesky::new(m.clone()).unwrap(); + let _ = chol.unpack(); + }) +} +#[bench] +fn cholesky_decompose_unpack_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let m = &m * m.transpose(); + + bh.iter(|| { + let chol = Cholesky::new(m.clone()).unwrap(); + let _ = chol.unpack(); + }) +} + +#[bench] +fn cholesky_solve_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let m = &m * m.transpose(); + let v = DVector::::new_random(10); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.solve(&v); + }) +} + +#[bench] +fn cholesky_solve_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let m = &m * m.transpose(); + let v = DVector::::new_random(100); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.solve(&v); + }) +} + +#[bench] +fn cholesky_solve_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let m = &m * m.transpose(); + let v = DVector::::new_random(500); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.solve(&v); + }) +} + +#[bench] +fn cholesky_inverse_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let m = &m * m.transpose(); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.inverse(); + }) +} + +#[bench] +fn cholesky_inverse_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let m = &m * m.transpose(); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.inverse(); + }) +} + +#[bench] +fn cholesky_inverse_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let m = &m * m.transpose(); + let chol = Cholesky::new(m.clone()).unwrap(); + + bh.iter(|| { + let _ = chol.inverse(); + }) +} diff --git a/benches/linalg/eigen.rs b/benches/linalg/eigen.rs new file mode 100644 index 00000000..54aa77a5 --- /dev/null +++ b/benches/linalg/eigen.rs @@ -0,0 +1,30 @@ +use test::Bencher; +use na::{DMatrix, Eigen}; + +#[bench] +fn eigen_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + + bh.iter(|| Eigen::new(m.clone(), 1.0e-7, 0)) +} + +#[bench] +fn eigen_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + + bh.iter(|| Eigen::new(m.clone(), 1.0e-7, 0)) +} + +#[bench] +fn eigenvalues_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + + bh.iter(|| m.clone().eigenvalues(1.0e-7, 0)) +} + +#[bench] +fn eigenvalues_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + + bh.iter(|| m.clone().eigenvalues(1.0e-7, 0)) +} diff --git a/benches/linalg/full_piv_lu.rs b/benches/linalg/full_piv_lu.rs new file mode 100644 index 00000000..e98e13dd --- /dev/null +++ b/benches/linalg/full_piv_lu.rs @@ -0,0 +1,114 @@ +use test::{self, Bencher}; +use na::{DMatrix, DVector, FullPivLU}; + +// Without unpack. +#[bench] +fn full_piv_lu_decompose_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + bh.iter(|| test::black_box(FullPivLU::new(m.clone()))) +} + +#[bench] +fn full_piv_lu_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(FullPivLU::new(m.clone()))) +} + +#[bench] +fn full_piv_lu_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(FullPivLU::new(m.clone()))) +} + +#[bench] +fn full_piv_lu_solve_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(10, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn full_piv_lu_solve_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(100, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn full_piv_lu_solve_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(500, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn full_piv_lu_inverse_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn full_piv_lu_inverse_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn full_piv_lu_inverse_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn full_piv_lu_determinant_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} + +#[bench] +fn full_piv_lu_determinant_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} + +#[bench] +fn full_piv_lu_determinant_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = FullPivLU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} diff --git a/benches/linalg/hessenberg.rs b/benches/linalg/hessenberg.rs new file mode 100644 index 00000000..90e00b98 --- /dev/null +++ b/benches/linalg/hessenberg.rs @@ -0,0 +1,60 @@ +use test::{self, Bencher}; +use na::{Matrix4, DMatrix, Hessenberg}; + +#[path="../common/macros.rs"] +mod macros; + +// Without unpack. +#[bench] +fn hessenberg_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + +#[bench] +fn hessenberg_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + +#[bench] +fn hessenberg_decompose_200x200(bh: &mut Bencher) { + let m = DMatrix::::new_random(200, 200); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + + +#[bench] +fn hessenberg_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + + +// With unpack. +#[bench] +fn hessenberg_decompose_unpack_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| { + let hess = Hessenberg::new(m.clone()); + let _ = hess.unpack(); + }) +} + +#[bench] +fn hessenberg_decompose_unpack_200x200(bh: &mut Bencher) { + let m = DMatrix::::new_random(200, 200); + bh.iter(|| { + let hess = Hessenberg::new(m.clone()); + let _ = hess.unpack(); + }) +} + +#[bench] +fn hessenberg_decompose_unpack_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| { + let hess = Hessenberg::new(m.clone()); + let _ = hess.unpack(); + }) +} diff --git a/benches/linalg/lu.rs b/benches/linalg/lu.rs new file mode 100644 index 00000000..33cbb3a5 --- /dev/null +++ b/benches/linalg/lu.rs @@ -0,0 +1,114 @@ +use test::{self, Bencher}; +use na::{DMatrix, DVector, LU}; + +// Without unpack. +#[bench] +fn lu_decompose_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_solve_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = LU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(10, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn lu_solve_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = LU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(100, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn lu_solve_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = LU::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(500, 1.0); + lu.solve(&mut b); + }) +} + +#[bench] +fn lu_inverse_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn lu_inverse_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn lu_inverse_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.try_inverse()) + }) +} + +#[bench] +fn lu_determinant_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} + +#[bench] +fn lu_determinant_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} + +#[bench] +fn lu_determinant_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let lu = LU::new(m.clone()); + + bh.iter(|| { + test::black_box(lu.determinant()) + }) +} diff --git a/benches/linalg/mod.rs b/benches/linalg/mod.rs new file mode 100644 index 00000000..c2cc4ade --- /dev/null +++ b/benches/linalg/mod.rs @@ -0,0 +1,11 @@ +mod solve; +mod cholesky; +mod qr; +mod hessenberg; +mod bidiagonal; +mod lu; +mod full_piv_lu; +mod svd; +mod schur; +mod symmetric_eigen; +// mod eigen; diff --git a/benches/linalg/qr.rs b/benches/linalg/qr.rs new file mode 100644 index 00000000..a2d455ea --- /dev/null +++ b/benches/linalg/qr.rs @@ -0,0 +1,137 @@ +use test::{self, Bencher}; +use na::{Matrix4, DMatrix, DVector, QR}; + +#[path="../common/macros.rs"] +mod macros; + +// Without unpack. +#[bench] +fn qr_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + + +// With unpack. +#[bench] +fn qr_decompose_unpack_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| { + let qr = QR::new(m.clone()); + let _ = qr.unpack(); + }) +} + +#[bench] +fn qr_decompose_unpack_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| { + let qr = QR::new(m.clone()); + let _ = qr.unpack(); + }) +} + +#[bench] +fn qr_decompose_unpack_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| { + let qr = QR::new(m.clone()); + let _ = qr.unpack(); + }) +} + +#[bench] +fn qr_decompose_unpack_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| { + let qr = QR::new(m.clone()); + let _ = qr.unpack(); + }) +} + +#[bench] +fn qr_solve_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let qr = QR::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(10, 1.0); + qr.solve(&mut b); + }) +} + +#[bench] +fn qr_solve_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let qr = QR::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(100, 1.0); + qr.solve(&mut b); + }) +} + +#[bench] +fn qr_solve_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let qr = QR::new(m.clone()); + + bh.iter(|| { + let mut b = DVector::::from_element(500, 1.0); + qr.solve(&mut b); + }) +} + +#[bench] +fn qr_inverse_10x10(bh: &mut Bencher) { + let m = DMatrix::::new_random(10, 10); + let qr = QR::new(m.clone()); + + bh.iter(|| { + test::black_box(qr.try_inverse()) + }) +} + +#[bench] +fn qr_inverse_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let qr = QR::new(m.clone()); + + bh.iter(|| { + test::black_box(qr.try_inverse()) + }) +} + +#[bench] +fn qr_inverse_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + let qr = QR::new(m.clone()); + + bh.iter(|| { + test::black_box(qr.try_inverse()) + }) +} diff --git a/benches/linalg/schur.rs b/benches/linalg/schur.rs new file mode 100644 index 00000000..0b16d0c1 --- /dev/null +++ b/benches/linalg/schur.rs @@ -0,0 +1,51 @@ +use test::{self, Bencher}; +use na::{Matrix4, RealSchur}; + +#[bench] +fn schur_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(RealSchur::new(m.clone()))) +} + +#[bench] +fn schur_decompose_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(RealSchur::new(m.clone()))) +} + + +#[bench] +fn schur_decompose_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(RealSchur::new(m.clone()))) +} + +#[bench] +fn schur_decompose_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(RealSchur::new(m.clone()))) +} + +#[bench] +fn eigenvalues_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(m.complex_eigenvalues())) +} + +#[bench] +fn eigenvalues_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(m.complex_eigenvalues())) +} + +#[bench] +fn eigenvalues_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(m.complex_eigenvalues())) +} + +#[bench] +fn eigenvalues_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(m.complex_eigenvalues())) +} diff --git a/benches/linalg/solve.rs b/benches/linalg/solve.rs new file mode 100644 index 00000000..3362549a --- /dev/null +++ b/benches/linalg/solve.rs @@ -0,0 +1,82 @@ +use test::Bencher; +use na::{DMatrix, DVector}; + +#[bench] +fn solve_l_triangular_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let v = DVector::::new_random(100); + + bh.iter(|| { + let _ = m.solve_lower_triangular(&v); + }) +} + +#[bench] +fn solve_l_triangular_1000x1000(bh: &mut Bencher) { + let m = DMatrix::::new_random(1000, 1000); + let v = DVector::::new_random(1000); + + bh.iter(|| { + let _ = m.solve_lower_triangular(&v); + }) +} + +#[bench] +fn tr_solve_l_triangular_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let v = DVector::::new_random(100); + + bh.iter(|| { + let _ = m.tr_solve_lower_triangular(&v); + }) +} + +#[bench] +fn tr_solve_l_triangular_1000x1000(bh: &mut Bencher) { + let m = DMatrix::::new_random(1000, 1000); + let v = DVector::::new_random(1000); + + bh.iter(|| { + let _ = m.tr_solve_lower_triangular(&v); + }) +} + +#[bench] +fn solve_u_triangular_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let v = DVector::::new_random(100); + + bh.iter(|| { + let _ = m.solve_upper_triangular(&v); + }) +} + +#[bench] +fn solve_u_triangular_1000x1000(bh: &mut Bencher) { + let m = DMatrix::::new_random(1000, 1000); + let v = DVector::::new_random(1000); + + bh.iter(|| { + let _ = m.solve_upper_triangular(&v); + }) +} + +#[bench] +fn tr_solve_u_triangular_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + let v = DVector::::new_random(100); + + bh.iter(|| { + let _ = m.tr_solve_upper_triangular(&v); + }) +} + +#[bench] +fn tr_solve_u_triangular_1000x1000(bh: &mut Bencher) { + let m = DMatrix::::new_random(1000, 1000); + let v = DVector::::new_random(1000); + + bh.iter(|| { + let _ = m.tr_solve_upper_triangular(&v); + }) +} diff --git a/benches/linalg/svd.rs b/benches/linalg/svd.rs new file mode 100644 index 00000000..74cdd344 --- /dev/null +++ b/benches/linalg/svd.rs @@ -0,0 +1,99 @@ +use test::{self, Bencher}; +use na::{Matrix4, SVD}; + +#[bench] +fn svd_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(SVD::new(m.clone(), true, true))) +} + +#[bench] +fn svd_decompose_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(SVD::new(m.clone(), true, true))) +} + +#[bench] +fn svd_decompose_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(SVD::new(m.clone(), true, true))) +} + +#[bench] +fn svd_decompose_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(SVD::new(m.clone(), true, true))) +} + +#[bench] +fn rank_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(m.rank(1.0e-10))) +} + +#[bench] +fn rank_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(m.rank(1.0e-10))) +} + +#[bench] +fn rank_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(m.rank(1.0e-10))) +} + +#[bench] +fn rank_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(m.rank(1.0e-10))) +} + +#[bench] +fn singular_values_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(m.singular_values())) +} + +#[bench] +fn singular_values_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(m.singular_values())) +} + +#[bench] +fn singular_values_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(m.singular_values())) +} + +#[bench] +fn singular_values_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(m.singular_values())) +} + + +#[bench] +fn pseudo_inverse_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(m.clone().pseudo_inverse(1.0e-10))) +} + +#[bench] +fn pseudo_inverse_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(m.clone().pseudo_inverse(1.0e-10))) +} + +#[bench] +fn pseudo_inverse_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(m.clone().pseudo_inverse(1.0e-10))) +} + +#[bench] +fn pseudo_inverse_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(m.clone().pseudo_inverse(1.0e-10))) +} diff --git a/benches/linalg/symmetric_eigen.rs b/benches/linalg/symmetric_eigen.rs new file mode 100644 index 00000000..07e7b3f3 --- /dev/null +++ b/benches/linalg/symmetric_eigen.rs @@ -0,0 +1,27 @@ +use test::{self, Bencher}; +use na::{Matrix4, SymmetricEigen}; + +#[bench] +fn symmetric_eigen_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(SymmetricEigen::new(m.clone()))) +} + +#[bench] +fn symmetric_eigen_decompose_10x10(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(10, 10); + bh.iter(|| test::black_box(SymmetricEigen::new(m.clone()))) +} + + +#[bench] +fn symmetric_eigen_decompose_100x100(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(100, 100); + bh.iter(|| test::black_box(SymmetricEigen::new(m.clone()))) +} + +#[bench] +fn symmetric_eigen_decompose_200x200(bh: &mut Bencher) { + let m = ::reproductible_dmatrix(200, 200); + bh.iter(|| test::black_box(SymmetricEigen::new(m.clone()))) +} diff --git a/benches/matrix.rs b/benches/matrix.rs deleted file mode 100644 index 8403b5b7..00000000 --- a/benches/matrix.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![feature(test)] - -extern crate test; -extern crate rand; -extern crate nalgebra as na; - -use rand::{IsaacRng, Rng}; -use test::Bencher; -use na::{Vector2, Vector3, Vector4, Matrix2, Matrix3, Matrix4}; -use std::ops::{Add, Sub, Mul, Div}; - -#[path="common/macros.rs"] -mod macros; - -bench_binop!(_bench_mat2_mul_m, Matrix2, Matrix2, mul); -bench_binop!(_bench_mat3_mul_m, Matrix3, Matrix3, mul); -bench_binop!(_bench_mat4_mul_m, Matrix4, Matrix4, mul); - -bench_binop_ref!(_bench_mat2_tr_mul_m, Matrix2, Matrix2, tr_mul); -bench_binop_ref!(_bench_mat3_tr_mul_m, Matrix3, Matrix3, tr_mul); -bench_binop_ref!(_bench_mat4_tr_mul_m, Matrix4, Matrix4, tr_mul); - -bench_binop!(_bench_mat2_add_m, Matrix2, Matrix2, add); -bench_binop!(_bench_mat3_add_m, Matrix3, Matrix3, add); -bench_binop!(_bench_mat4_add_m, Matrix4, Matrix4, add); - -bench_binop!(_bench_mat2_sub_m, Matrix2, Matrix2, sub); -bench_binop!(_bench_mat3_sub_m, Matrix3, Matrix3, sub); -bench_binop!(_bench_mat4_sub_m, Matrix4, Matrix4, sub); - -bench_binop!(_bench_mat2_mul_v, Matrix2, Vector2, mul); -bench_binop!(_bench_mat3_mul_v, Matrix3, Vector3, mul); -bench_binop!(_bench_mat4_mul_v, Matrix4, Vector4, mul); - -bench_binop_ref!(_bench_mat2_tr_mul_v, Matrix2, Vector2, tr_mul); -bench_binop_ref!(_bench_mat3_tr_mul_v, Matrix3, Vector3, tr_mul); -bench_binop_ref!(_bench_mat4_tr_mul_v, Matrix4, Vector4, tr_mul); - -bench_binop!(_bench_mat2_mul_s, Matrix2, f32, mul); -bench_binop!(_bench_mat3_mul_s, Matrix3, f32, mul); -bench_binop!(_bench_mat4_mul_s, Matrix4, f32, mul); - -bench_binop!(_bench_mat2_div_s, Matrix2, f32, div); -bench_binop!(_bench_mat3_div_s, Matrix3, f32, div); -bench_binop!(_bench_mat4_div_s, Matrix4, f32, div); - -bench_unop!(_bench_mat2_inv, Matrix2, try_inverse); -bench_unop!(_bench_mat3_inv, Matrix3, try_inverse); -bench_unop!(_bench_mat4_inv, Matrix4, try_inverse); - -bench_unop!(_bench_mat2_transpose, Matrix2, transpose); -bench_unop!(_bench_mat3_transpose, Matrix3, transpose); -bench_unop!(_bench_mat4_transpose, Matrix4, transpose); diff --git a/benches/quaternion.rs b/benches/quaternion.rs deleted file mode 100644 index f1c66ef5..00000000 --- a/benches/quaternion.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![feature(test)] - -extern crate test; -extern crate rand; -extern crate nalgebra as na; - -use rand::{IsaacRng, Rng}; -use test::Bencher; -use na::{Quaternion, UnitQuaternion, Vector3}; -use std::ops::{Add, Sub, Mul, Div}; - -#[path="common/macros.rs"] -mod macros; - -bench_binop!(_bench_quaternion_add_q, Quaternion, Quaternion, add); -bench_binop!(_bench_quaternion_sub_q, Quaternion, Quaternion, sub); -bench_binop!(_bench_quaternion_mul_q, Quaternion, Quaternion, mul); - -bench_binop!(_bench_unit_quaternion_mul_v, UnitQuaternion, Vector3, mul); - -bench_binop!(_bench_quaternion_mul_s, Quaternion, f32, mul); -bench_binop!(_bench_quaternion_div_s, Quaternion, f32, div); - -bench_unop!(_bench_quaternion_inv, Quaternion, try_inverse); -bench_unop!(_bench_unit_quaternion_inv, UnitQuaternion, inverse); - -// bench_unop_self!(_bench_quaternion_conjugate, Quaternion, conjugate); -// bench_unop!(_bench_quaternion_normalize, Quaternion, normalize); diff --git a/benches/vector.rs b/benches/vector.rs deleted file mode 100644 index aa897145..00000000 --- a/benches/vector.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![feature(test)] - -extern crate test; -extern crate rand; -extern crate nalgebra as na; - -use rand::{IsaacRng, Rng}; -use test::Bencher; -use na::{Vector2, Vector3, Vector4}; -use std::ops::{Add, Sub, Mul, Div}; - -#[path="common/macros.rs"] -mod macros; - -bench_binop!(_bench_vec2_add_v, Vector2, Vector2, add); -bench_binop!(_bench_vec3_add_v, Vector3, Vector3, add); -bench_binop!(_bench_vec4_add_v, Vector4, Vector4, add); - -bench_binop!(_bench_vec2_sub_v, Vector2, Vector2, sub); -bench_binop!(_bench_vec3_sub_v, Vector3, Vector3, sub); -bench_binop!(_bench_vec4_sub_v, Vector4, Vector4, sub); - -bench_binop!(_bench_vec2_mul_s, Vector2, f32, mul); -bench_binop!(_bench_vec3_mul_s, Vector3, f32, mul); -bench_binop!(_bench_vec4_mul_s, Vector4, f32, mul); - -bench_binop!(_bench_vec2_div_s, Vector2, f32, div); -bench_binop!(_bench_vec3_div_s, Vector3, f32, div); -bench_binop!(_bench_vec4_div_s, Vector4, f32, div); - -bench_binop_ref!(_bench_vec2_dot, Vector2, Vector2, dot); -bench_binop_ref!(_bench_vec3_dot, Vector3, Vector3, dot); -bench_binop_ref!(_bench_vec4_dot, Vector4, Vector4, dot); - -bench_binop_ref!(_bench_vec3_cross, Vector3, Vector3, cross); - -bench_unop!(_bench_vec2_norm, Vector2, norm); -bench_unop!(_bench_vec3_norm, Vector3, norm); -bench_unop!(_bench_vec4_norm, Vector4, norm); - -bench_unop!(_bench_vec2_normalize, Vector2, normalize); -bench_unop!(_bench_vec3_normalize, Vector3, normalize); -bench_unop!(_bench_vec4_normalize, Vector4, normalize); diff --git a/examples/dimensional_genericity.rs b/examples/dimensional_genericity.rs index df22b945..70a37a04 100644 --- a/examples/dimensional_genericity.rs +++ b/examples/dimensional_genericity.rs @@ -1,11 +1,10 @@ extern crate alga; extern crate nalgebra as na; -use alga::general::Real; use alga::linear::FiniteDimInnerSpace; -use na::{Unit, ColumnVector, OwnedColumnVector, Vector2, Vector3}; -use na::storage::Storage; -use na::dimension::{DimName, U1}; +use na::{Real, DefaultAllocator, Unit, VectorN, Vector2, Vector3}; +use na::allocator::Allocator; +use na::dimension::Dim; /// Reflects a vector wrt. the hyperplane with normal `plane_normal`. fn reflect_wrt_hyperplane_with_algebraic_genericity(plane_normal: &Unit, vector: &V) -> V @@ -16,12 +15,12 @@ fn reflect_wrt_hyperplane_with_algebraic_genericity(plane_normal: &Unit, v /// Reflects a vector wrt. the hyperplane with normal `plane_normal`. -fn reflect_wrt_hyperplane_with_structural_genericity(plane_normal: &Unit>, - vector: &ColumnVector) - -> OwnedColumnVector +fn reflect_wrt_hyperplane_with_dimensional_genericity(plane_normal: &Unit>, + vector: &VectorN) + -> VectorN where N: Real, - D: DimName, - S: Storage { + D: Dim, + DefaultAllocator: Allocator { let n = plane_normal.as_ref(); // Get the underlying V. vector - n * (n.dot(vector) * na::convert(2.0)) } @@ -57,8 +56,8 @@ fn main() { assert_eq!(reflect_wrt_hyperplane_with_algebraic_genericity(&plane2, &v2).y, -2.0); assert_eq!(reflect_wrt_hyperplane_with_algebraic_genericity(&plane3, &v3).y, -2.0); - assert_eq!(reflect_wrt_hyperplane_with_structural_genericity(&plane2, &v2).y, -2.0); - assert_eq!(reflect_wrt_hyperplane_with_structural_genericity(&plane3, &v3).y, -2.0); + assert_eq!(reflect_wrt_hyperplane_with_dimensional_genericity(&plane2, &v2).y, -2.0); + assert_eq!(reflect_wrt_hyperplane_with_dimensional_genericity(&plane3, &v3).y, -2.0); // Call each specific implementation depending on the dimension. assert_eq!(reflect_wrt_hyperplane2(&plane2, &v2).y, -2.0); diff --git a/nalgebra-lapack/CHANGELOG.md b/nalgebra-lapack/CHANGELOG.md new file mode 100644 index 00000000..c003fcf7 --- /dev/null +++ b/nalgebra-lapack/CHANGELOG.md @@ -0,0 +1,22 @@ +# Change Log + +## [0.4.0] - 2016-09-07 + +* Made all traits use associated types for their output type parameters. This + simplifies usage of the traits and is consistent with the concept of + associated types used as output type parameters (not input type parameters) as + described in [the associated type + RFC](https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md). +* Implemented `check_info!` macro to check all LAPACK calls. +* Implemented error handling with [error_chain](https://crates.io/crates/error-chain). + +## [0.3.0] - 2016-09-06 + +* Documentation is hosted at https://docs.rs/nalgebra-lapack/ +* Updated `nalgebra` to 0.10. +* Rename traits `HasSVD` to `SVD` and `HasEigensystem` to `Eigensystem`. +* Added `Solve` trait for solving a linear matrix equation. +* Added `Inverse` for computing the multiplicative inverse of a matrix. +* Added `Cholesky` for decomposing a positive-definite matrix. +* The `Eigensystem` and `SVD` traits are now generic over types. The + associated types have been removed. diff --git a/nalgebra-lapack/Cargo.toml b/nalgebra-lapack/Cargo.toml new file mode 100644 index 00000000..8b252f09 --- /dev/null +++ b/nalgebra-lapack/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "nalgebra-lapack" +version = "0.11.2" +authors = [ "Sébastien Crozet ", "Andrew Straw " ] + +description = "Linear algebra library with transformations and satically-sized or dynamically-sized matrices." +documentation = "http://nalgebra.org/doc/nalgebra/index.html" +homepage = "http://nalgebra.org" +repository = "https://github.com/sebcrozet/nalgebra" +readme = "README.md" +keywords = [ "linear", "algebra", "matrix", "vector" ] +license = "BSD-3-Clause" + +[features] +serde-serialize = [ "serde", "serde_derive" ] + +# For BLAS/LAPACK +default = ["openblas"] +openblas = ["lapack/openblas"] +netlib = ["lapack/netlib"] +accelerate = ["lapack/accelerate"] + +[dependencies] +nalgebra = { version = "0.12", path = ".." } +num-traits = "0.1" +num-complex = "0.1" +alga = "0.5" +serde = { version = "0.9", optional = true } +serde_derive = { version = "0.9", optional = true } +# clippy = "*" + +[dependencies.lapack] +version = "0.11" +default-features = false + +[dev-dependencies] +nalgebra = { version = "0.12", path = "..", features = [ "arbitrary" ] } +quickcheck = "0.4" +approx = "0.1" +rand = "0.3" diff --git a/nalgebra-lapack/LICENSE.txt b/nalgebra-lapack/LICENSE.txt new file mode 100644 index 00000000..ec8f0063 --- /dev/null +++ b/nalgebra-lapack/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew D. Straw + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/nalgebra-lapack/Makefile b/nalgebra-lapack/Makefile new file mode 100644 index 00000000..7ff57c89 --- /dev/null +++ b/nalgebra-lapack/Makefile @@ -0,0 +1,11 @@ +all: + cargo build + +test: + cargo test + +doc: + cargo doc --all --no-deps + +bench: + cargo bench diff --git a/nalgebra-lapack/README.md b/nalgebra-lapack/README.md new file mode 100644 index 00000000..dc3d0b96 --- /dev/null +++ b/nalgebra-lapack/README.md @@ -0,0 +1,59 @@ +# nalgebra-lapack [![Version][version-img]][version-url] [![Status][status-img]][status-url] [![Doc][doc-img]][doc-url] + +Rust library for linear algebra using nalgebra and LAPACK. + +## Documentation + +Documentation is available [here](https://docs.rs/nalgebra-lapack/). + +## License + +MIT + +## Cargo features to select lapack provider + +Like the [lapack crate](https://crates.io/crates/lapack) from which this +behavior is inherited, nalgebra-lapack uses [cargo +features](http://doc.crates.io/manifest.html#the-[features]-section) to select +which lapack provider (or implementation) is used. Command line arguments to +cargo are the easiest way to do this, and the best provider depends on your +particular system. In some cases, the providers can be further tuned with +environment variables. + +Below are given examples of how to invoke `cargo build` on two different systems +using two different providers. The `--no-default-features --features "provider"` +arguments will be consistent for other `cargo` commands. + +### Ubuntu + +As tested on Ubuntu 12.04, do this to build the lapack package against +the system installation of netlib without LAPACKE (note the E) or +CBLAS: + + sudo apt-get install gfortran libblas3gf liblapack3gf + export CARGO_FEATURE_SYSTEM_NETLIB=1 + export CARGO_FEATURE_EXCLUDE_LAPACKE=1 + export CARGO_FEATURE_EXCLUDE_CBLAS=1 + + export CARGO_FEATURES='--no-default-features --features netlib' + cargo build ${CARGO_FEATURES} + +### Mac OS X + +On Mac OS X, do this to use Apple's Accelerate framework: + + export CARGO_FEATURES='--no-default-features --features accelerate' + cargo build ${CARGO_FEATURES} + +[version-img]: https://img.shields.io/crates/v/nalgebra-lapack.svg +[version-url]: https://crates.io/crates/nalgebra-lapack +[status-img]: https://travis-ci.org/strawlab/nalgebra-lapack.svg?branch=master +[status-url]: https://travis-ci.org/strawlab/nalgebra-lapack +[doc-img]: https://docs.rs/nalgebra-lapack/badge.svg +[doc-url]: https://docs.rs/nalgebra-lapack/ + +## Contributors +This integration of LAPACK on nalgebra was +[initiated](https://github.com/strawlab/nalgebra-lapack) by Andrew Straw. It +then became officially supported and integrated to the main nalgebra +repository. diff --git a/nalgebra-lapack/benches/lib.rs b/nalgebra-lapack/benches/lib.rs new file mode 100644 index 00000000..de0c170a --- /dev/null +++ b/nalgebra-lapack/benches/lib.rs @@ -0,0 +1,8 @@ +#![feature(test)] + +extern crate test; +extern crate rand; +extern crate nalgebra as na; +extern crate nalgebra_lapack as nl; + +mod linalg; diff --git a/nalgebra-lapack/benches/linalg/hessenberg.rs b/nalgebra-lapack/benches/linalg/hessenberg.rs new file mode 100644 index 00000000..90c97b8b --- /dev/null +++ b/nalgebra-lapack/benches/linalg/hessenberg.rs @@ -0,0 +1,21 @@ +use test::{self, Bencher}; +use na::{DMatrix, Matrix4}; +use nl::Hessenberg; + +#[bench] +fn hessenberg_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + +#[bench] +fn hessenberg_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} + +#[bench] +fn hessenberg_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(Hessenberg::new(m.clone()))) +} diff --git a/nalgebra-lapack/benches/linalg/lu.rs b/nalgebra-lapack/benches/linalg/lu.rs new file mode 100644 index 00000000..572c26b4 --- /dev/null +++ b/nalgebra-lapack/benches/linalg/lu.rs @@ -0,0 +1,34 @@ +use test::{self, Bencher}; +use na::{DMatrix, Matrix4}; +use nl::LU; + + +#[bench] +fn lu_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} + +#[bench] +fn lu_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(LU::new(m.clone()))) +} diff --git a/nalgebra-lapack/benches/linalg/mod.rs b/nalgebra-lapack/benches/linalg/mod.rs new file mode 100644 index 00000000..f42ec321 --- /dev/null +++ b/nalgebra-lapack/benches/linalg/mod.rs @@ -0,0 +1,3 @@ +mod qr; +mod lu; +mod hessenberg; diff --git a/nalgebra-lapack/benches/linalg/qr.rs b/nalgebra-lapack/benches/linalg/qr.rs new file mode 100644 index 00000000..07b830d9 --- /dev/null +++ b/nalgebra-lapack/benches/linalg/qr.rs @@ -0,0 +1,33 @@ +use test::{self, Bencher}; +use na::{DMatrix, Matrix4}; +use nl::QR; + +#[bench] +fn qr_decompose_100x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 100); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_100x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(100, 500); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_4x4(bh: &mut Bencher) { + let m = Matrix4::::new_random(); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_500x100(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 100); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} + +#[bench] +fn qr_decompose_500x500(bh: &mut Bencher) { + let m = DMatrix::::new_random(500, 500); + bh.iter(|| test::black_box(QR::new(m.clone()))) +} diff --git a/nalgebra-lapack/src/cholesky.rs b/nalgebra-lapack/src/cholesky.rs new file mode 100644 index 00000000..317b67a4 --- /dev/null +++ b/nalgebra-lapack/src/cholesky.rs @@ -0,0 +1,183 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num::Zero; +use num_complex::Complex; + +use na::{Scalar, DefaultAllocator, Matrix, MatrixN, MatrixMN}; +use na::dimension::Dim; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + +/// The cholesky decomposion of a symmetric-definite-positive matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Cholesky + where DefaultAllocator: Allocator { + l: MatrixN +} + +impl Copy for Cholesky + where DefaultAllocator: Allocator, + MatrixN: Copy { } + +impl Cholesky + where DefaultAllocator: Allocator { + + /// Complutes the cholesky decomposition of the given symmetric-definite-positive square + /// matrix. + /// + /// Only the lower-triangular part of the input matrix is considered. + #[inline] + pub fn new(mut m: MatrixN) -> Option { + // FIXME: check symmetry as well? + assert!(m.is_square(), "Unable to compute the cholesky decomposition of a non-square matrix."); + + let uplo = b'L'; + let dim = m.nrows() as i32; + let mut info = 0; + + N::xpotrf(uplo, dim, m.as_mut_slice(), dim, &mut info); + lapack_check!(info); + + Some(Cholesky { l: m }) + } + + /// Retrieves the lower-triangular factor of the cholesky decomposition. + pub fn unpack(mut self) -> MatrixN { + self.l.fill_upper_triangle(Zero::zero(), 1); + self.l + } + + /// Retrieves the lower-triangular factor of che cholesky decomposition, without zeroing-out + /// its strict upper-triangular part. + /// + /// This is an allocation-less version of `self.l()`. The values of the strict upper-triangular + /// part are garbage and should be ignored by further computations. + pub fn unpack_dirty(self) -> MatrixN { + self.l + } + + /// Retrieves the lower-triangular factor of the cholesky decomposition. + pub fn l(&self) -> MatrixN { + let mut res = self.l.clone(); + res.fill_upper_triangle(Zero::zero(), 1); + res + } + + /// Retrieves the lower-triangular factor of the cholesky decomposition, without zeroing-out + /// its strict upper-triangular part. + /// + /// This is an allocation-less version of `self.l()`. The values of the strict upper-triangular + /// part are garbage and should be ignored by further computations. + pub fn l_dirty(&self) -> &MatrixN { + &self.l + } + + /// Solves the symmetric-definite-positive linear system `self * x = b`, where `x` is the + /// unknown to be determined. + pub fn solve(&self, b: &Matrix) -> Option> + where S2: Storage, + DefaultAllocator: Allocator { + + let mut res = b.clone_owned(); + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves in-place the symmetric-definite-positive linear system `self * x = b`, where `x` is + /// the unknown to be determined. + pub fn solve_mut(&self, b: &mut MatrixMN) -> bool + where DefaultAllocator: Allocator { + + let dim = self.l.nrows(); + + assert!(b.nrows() == dim, "The number of rows of `b` must be equal to the dimension of the matrix `a`."); + + let nrhs = b.ncols() as i32; + let lda = dim as i32; + let ldb = dim as i32; + let mut info = 0; + + N::xpotrs(b'L', dim as i32, nrhs, self.l.as_slice(), lda, b.as_mut_slice(), ldb, &mut info); + lapack_test!(info) + } + + /// Computes the inverse of the decomposed matrix. + pub fn inverse(mut self) -> Option> { + let dim = self.l.nrows(); + let mut info = 0; + + N::xpotri(b'L', dim as i32, self.l.as_mut_slice(), dim as i32, &mut info); + lapack_check!(info); + + // Copy lower triangle to upper triangle. + for i in 0 .. dim { + for j in i + 1 .. dim { + unsafe { *self.l.get_unchecked_mut(i, j) = *self.l.get_unchecked(j, i) }; + } + } + + Some(self.l) + } +} + + + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by floats (`f32`, `f64`) and complex floats (`Complex`, `Complex`) +/// supported by the cholesky decompotition. +pub trait CholeskyScalar: Scalar { + #[allow(missing_docs)] + fn xpotrf(uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32); + #[allow(missing_docs)] + fn xpotrs(uplo: u8, n: i32, nrhs: i32, a: &[Self], lda: i32, b: &mut [Self], ldb: i32, info: &mut i32); + #[allow(missing_docs)] + fn xpotri(uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32); +} + +macro_rules! cholesky_scalar_impl( + ($N: ty, $xpotrf: path, $xpotrs: path, $xpotri: path) => ( + impl CholeskyScalar for $N { + #[inline] + fn xpotrf(uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32) { + $xpotrf(uplo, n, a, lda, info) + } + + #[inline] + fn xpotrs(uplo: u8, n: i32, nrhs: i32, a: &[Self], lda: i32, + b: &mut [Self], ldb: i32, info: &mut i32) { + $xpotrs(uplo, n, nrhs, a, lda, b, ldb, info) + } + + #[inline] + fn xpotri(uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32) { + $xpotri(uplo, n, a, lda, info) + } + } + ) +); + +cholesky_scalar_impl!(f32, interface::spotrf, interface::spotrs, interface::spotri); +cholesky_scalar_impl!(f64, interface::dpotrf, interface::dpotrs, interface::dpotri); +cholesky_scalar_impl!(Complex, interface::cpotrf, interface::cpotrs, interface::cpotri); +cholesky_scalar_impl!(Complex, interface::zpotrf, interface::zpotrs, interface::zpotri); diff --git a/nalgebra-lapack/src/eigen.rs b/nalgebra-lapack/src/eigen.rs new file mode 100644 index 00000000..4e0cc26e --- /dev/null +++ b/nalgebra-lapack/src/eigen.rs @@ -0,0 +1,253 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num::Zero; +use num_complex::Complex; + +use alga::general::Real; + +use ::ComplexHelper; +use na::{Scalar, DefaultAllocator, Matrix, VectorN, MatrixN}; +use na::dimension::{Dim, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + +/// Eigendecomposition of a real square matrix with real eigenvalues. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Eigen + where DefaultAllocator: Allocator + + Allocator { + /// The eigenvalues of the decomposed matrix. + pub eigenvalues: VectorN, + /// The (right) eigenvectors of the decomposed matrix. + pub eigenvectors: Option>, + /// The left eigenvectors of the decomposed matrix. + pub left_eigenvectors: Option> +} + +impl Copy for Eigen + where DefaultAllocator: Allocator + + Allocator, + VectorN: Copy, + MatrixN: Copy { } + + +impl Eigen + where DefaultAllocator: Allocator + + Allocator { + /// Computes the eigenvalues and eigenvectors of the square matrix `m`. + /// + /// If `eigenvectors` is `false` then, the eigenvectors are not computed explicitly. + pub fn new(mut m: MatrixN, left_eigenvectors: bool, eigenvectors: bool) + -> Option> { + + assert!(m.is_square(), "Unable to compute the eigenvalue decomposition of a non-square matrix."); + + let ljob = if left_eigenvectors { b'V' } else { b'N' }; + let rjob = if eigenvectors { b'V' } else { b'N' }; + + let (nrows, ncols) = m.data.shape(); + let n = nrows.value(); + + let lda = n as i32; + + let mut wr = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + // FIXME: Tap into the workspace. + let mut wi = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + + + let mut info = 0; + let mut placeholder1 = [ N::zero() ]; + let mut placeholder2 = [ N::zero() ]; + + let lwork = N::xgeev_work_size(ljob, rjob, n as i32, m.as_mut_slice(), lda, + wr.as_mut_slice(), wi.as_mut_slice(), &mut placeholder1, + n as i32, &mut placeholder2, n as i32, &mut info); + + lapack_check!(info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + match (left_eigenvectors, eigenvectors) { + (true, true) => { + let mut vl = unsafe { Matrix::new_uninitialized_generic(nrows, ncols) }; + let mut vr = unsafe { Matrix::new_uninitialized_generic(nrows, ncols) }; + + N::xgeev(ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), + wi.as_mut_slice(), &mut vl.as_mut_slice(), n as i32, &mut vr.as_mut_slice(), + n as i32, &mut work, lwork, &mut info); + lapack_check!(info); + + if wi.iter().all(|e| e.is_zero()) { + return Some(Eigen { + eigenvalues: wr, left_eigenvectors: Some(vl), eigenvectors: Some(vr) + }) + } + }, + (true, false) => { + let mut vl = unsafe { Matrix::new_uninitialized_generic(nrows, ncols) }; + + N::xgeev(ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), + wi.as_mut_slice(), &mut vl.as_mut_slice(), n as i32, &mut placeholder2, + 1 as i32, &mut work, lwork, &mut info); + lapack_check!(info); + + if wi.iter().all(|e| e.is_zero()) { + return Some(Eigen { + eigenvalues: wr, left_eigenvectors: Some(vl), eigenvectors: None + }); + } + }, + (false, true) => { + let mut vr = unsafe { Matrix::new_uninitialized_generic(nrows, ncols) }; + + N::xgeev(ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), + wi.as_mut_slice(), &mut placeholder1, 1 as i32, &mut vr.as_mut_slice(), + n as i32, &mut work, lwork, &mut info); + lapack_check!(info); + + if wi.iter().all(|e| e.is_zero()) { + return Some(Eigen { + eigenvalues: wr, left_eigenvectors: None, eigenvectors: Some(vr) + }); + } + }, + (false, false) => { + N::xgeev(ljob, rjob, n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), + wi.as_mut_slice(), &mut placeholder1, 1 as i32, &mut placeholder2, + 1 as i32, &mut work, lwork, &mut info); + lapack_check!(info); + + if wi.iter().all(|e| e.is_zero()) { + return Some(Eigen { + eigenvalues: wr, left_eigenvectors: None, eigenvectors: None + }); + } + } + } + + None + } + + /// The complex eigenvalues of the given matrix. + /// + /// Panics if the eigenvalue computation does not converge. + pub fn complex_eigenvalues(mut m: MatrixN) -> VectorN, D> + where DefaultAllocator: Allocator, D> { + assert!(m.is_square(), "Unable to compute the eigenvalue decomposition of a non-square matrix."); + + let nrows = m.data.shape().0; + let n = nrows.value(); + + let lda = n as i32; + + let mut wr = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + let mut wi = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + + + let mut info = 0; + let mut placeholder1 = [ N::zero() ]; + let mut placeholder2 = [ N::zero() ]; + + let lwork = N::xgeev_work_size(b'N', b'N', n as i32, m.as_mut_slice(), lda, + wr.as_mut_slice(), wi.as_mut_slice(), &mut placeholder1, + n as i32, &mut placeholder2, n as i32, &mut info); + + lapack_panic!(info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + N::xgeev(b'N', b'N', n as i32, m.as_mut_slice(), lda, wr.as_mut_slice(), + wi.as_mut_slice(), &mut placeholder1, 1 as i32, &mut placeholder2, + 1 as i32, &mut work, lwork, &mut info); + lapack_panic!(info); + + let mut res = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + + for i in 0 .. res.len() { + res[i] = Complex::new(wr[i], wi[i]); + } + + res + } + + /// The determinant of the decomposed matrix. + #[inline] + pub fn determinant(&self) -> N { + let mut det = N::one(); + for e in self.eigenvalues.iter() { + det *= *e; + } + + det + } +} + + + + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalar type for which Lapack funtion exist to compute the +/// eigendecomposition. +pub trait EigenScalar: Scalar { + #[allow(missing_docs)] + fn xgeev(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, + wr: &mut [Self], wi: &mut [Self], + vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, + work: &mut [Self], lwork: i32, info: &mut i32); + #[allow(missing_docs)] + fn xgeev_work_size(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, + wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, + vr: &mut [Self], ldvr: i32, info: &mut i32) -> i32; +} + +macro_rules! real_eigensystem_scalar_impl ( + ($N: ty, $xgeev: path) => ( + impl EigenScalar for $N { + #[inline] + fn xgeev(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, + wr: &mut [Self], wi: &mut [Self], + vl: &mut [Self], ldvl: i32, vr: &mut [Self], ldvr: i32, + work: &mut [Self], lwork: i32, info: &mut i32) { + $xgeev(jobvl, jobvr, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info) + } + + + #[inline] + fn xgeev_work_size(jobvl: u8, jobvr: u8, n: i32, a: &mut [Self], lda: i32, + wr: &mut [Self], wi: &mut [Self], vl: &mut [Self], ldvl: i32, + vr: &mut [Self], ldvr: i32, info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xgeev(jobvl, jobvr, n, a, lda, wr, wi, vl, ldvl, vr, ldvr, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +real_eigensystem_scalar_impl!(f32, interface::sgeev); +real_eigensystem_scalar_impl!(f64, interface::dgeev); + +//// FIXME: decomposition of complex matrix and matrices with complex eigenvalues. +// eigensystem_complex_impl!(f32, interface::cgeev); +// eigensystem_complex_impl!(f64, interface::zgeev); diff --git a/nalgebra-lapack/src/hessenberg.rs b/nalgebra-lapack/src/hessenberg.rs new file mode 100644 index 00000000..ae417445 --- /dev/null +++ b/nalgebra-lapack/src/hessenberg.rs @@ -0,0 +1,178 @@ +use num::Zero; +use num_complex::Complex; + +use ::ComplexHelper; +use na::{Scalar, Matrix, DefaultAllocator, VectorN, MatrixN}; +use na::dimension::{DimSub, DimDiff, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + + +/// The Hessenberg decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Hessenberg> + where DefaultAllocator: Allocator + + Allocator> { + h: MatrixN, + tau: VectorN> +} + + +impl> Copy for Hessenberg + where DefaultAllocator: Allocator + + Allocator>, + MatrixN: Copy, + VectorN>: Copy { } + +impl> Hessenberg + where DefaultAllocator: Allocator + + Allocator> { + /// Computes the hessenberg decomposition of the matrix `m`. + pub fn new(mut m: MatrixN) -> Hessenberg { + let nrows = m.data.shape().0; + let n = nrows.value() as i32; + + assert!(m.is_square(), "Unable to compute the hessenberg decomposition of a non-square matrix."); + assert!(!m.is_empty(), "Unable to compute the hessenberg decomposition of an empty matrix."); + + let mut tau = unsafe { Matrix::new_uninitialized_generic(nrows.sub(U1), U1) }; + + let mut info = 0; + let lwork = N::xgehrd_work_size(n, 1, n, m.as_mut_slice(), n, tau.as_mut_slice(), &mut info); + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + lapack_panic!(info); + + N::xgehrd(n, 1, n, m.as_mut_slice(), n, tau.as_mut_slice(), &mut work, lwork, &mut info); + lapack_panic!(info); + + Hessenberg { h: m, tau: tau } + } + + /// Computes the hessenberg matrix of this decomposition. + #[inline] + pub fn h(&self) -> MatrixN { + let mut h = self.h.clone_owned(); + h.fill_lower_triangle(N::zero(), 2); + + h + } +} + +impl> Hessenberg + where DefaultAllocator: Allocator + + Allocator> { + /// Computes the matrices `(Q, H)` of this decomposition. + #[inline] + pub fn unpack(self) -> (MatrixN, MatrixN) { + (self.q(), self.h()) + } + + /// Computes the unitary matrix `Q` of this decomposition. + #[inline] + pub fn q(&self) -> MatrixN { + let n = self.h.nrows() as i32; + let mut q = self.h.clone_owned(); + let mut info = 0; + + let lwork = N::xorghr_work_size(n, 1, n, q.as_mut_slice(), n, self.tau.as_slice(), &mut info); + let mut work = vec![ N::zero(); lwork as usize ]; + + N::xorghr(n, 1, n, q.as_mut_slice(), n, self.tau.as_slice(), &mut work, lwork, &mut info); + + q + } +} + + + + +/* + * + * Lapack functions dispatch. + * + */ +pub trait HessenbergScalar: Scalar { + fn xgehrd(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &mut [Self], work: &mut [Self], lwork: i32, info: &mut i32); + fn xgehrd_work_size(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &mut [Self], info: &mut i32) -> i32; +} + +/// Trait implemented by scalars for which Lapack implements the hessenberg decomposition. +pub trait HessenbergReal: HessenbergScalar { + #[allow(missing_docs)] + fn xorghr(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, tau: &[Self], + work: &mut [Self], lwork: i32, info: &mut i32); + #[allow(missing_docs)] + fn xorghr_work_size(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &[Self], info: &mut i32) -> i32; +} + +macro_rules! hessenberg_scalar_impl( + ($N: ty, $xgehrd: path) => ( + impl HessenbergScalar for $N { + #[inline] + fn xgehrd(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &mut [Self], work: &mut [Self], lwork: i32, info: &mut i32) { + $xgehrd(n, ilo, ihi, a, lda, tau, work, lwork, info) + } + + #[inline] + fn xgehrd_work_size(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &mut [Self], info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xgehrd(n, ilo, ihi, a, lda, tau, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +macro_rules! hessenberg_real_impl( + ($N: ty, $xorghr: path) => ( + impl HessenbergReal for $N { + #[inline] + fn xorghr(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, tau: &[Self], + work: &mut [Self], lwork: i32, info: &mut i32) { + $xorghr(n, ilo, ihi, a, lda, tau, work, lwork, info) + } + + #[inline] + fn xorghr_work_size(n: i32, ilo: i32, ihi: i32, a: &mut [Self], lda: i32, + tau: &[Self], info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xorghr(n, ilo, ihi, a, lda, tau, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +hessenberg_scalar_impl!(f32, interface::sgehrd); +hessenberg_scalar_impl!(f64, interface::dgehrd); +hessenberg_scalar_impl!(Complex, interface::cgehrd); +hessenberg_scalar_impl!(Complex, interface::zgehrd); + +hessenberg_real_impl!(f32, interface::sorghr); +hessenberg_real_impl!(f64, interface::dorghr); + diff --git a/nalgebra-lapack/src/lapack_check.rs b/nalgebra-lapack/src/lapack_check.rs new file mode 100644 index 00000000..217699dc --- /dev/null +++ b/nalgebra-lapack/src/lapack_check.rs @@ -0,0 +1,27 @@ +#![macro_use] + +macro_rules! lapack_check( + ($info: expr) => ( + // FIXME: return a richer error. + if $info != 0 { + return None; + } + // if $info < 0 { + // return Err(Error::from(ErrorKind::LapackIllegalArgument(-$info))); + // } else if $info > 0 { + // return Err(Error::from(ErrorKind::LapackFailure($info))); + // } + ); +); + +macro_rules! lapack_panic( + ($info: expr) => ( + assert!($info == 0, "Lapack error."); + ); +); + +macro_rules! lapack_test( + ($info: expr) => ( + $info == 0 + ); +); diff --git a/nalgebra-lapack/src/lib.rs b/nalgebra-lapack/src/lib.rs new file mode 100644 index 00000000..c36e7387 --- /dev/null +++ b/nalgebra-lapack/src/lib.rs @@ -0,0 +1,148 @@ +//! # nalgebra-lapack +//! +//! Rust library for linear algebra using nalgebra and LAPACK. +//! +//! ## Documentation +//! +//! Documentation is available [here](https://docs.rs/nalgebra-lapack/). +//! +//! ## License +//! +//! MIT +//! +//! ## Cargo features to select lapack provider +//! +//! Like the [lapack crate](https://crates.io/crates/lapack) from which this +//! behavior is inherited, nalgebra-lapack uses [cargo +//! features](http://doc.crates.io/manifest.html#the-[features]-section) to select +//! which lapack provider (or implementation) is used. Command line arguments to +//! cargo are the easiest way to do this, and the best provider depends on your +//! particular system. In some cases, the providers can be further tuned with +//! environment variables. +//! +//! Below are given examples of how to invoke `cargo build` on two different systems +//! using two different providers. The `--no-default-features --features "provider"` +//! arguments will be consistent for other `cargo` commands. +//! +//! ### Ubuntu +//! +//! As tested on Ubuntu 12.04, do this to build the lapack package against +//! the system installation of netlib without LAPACKE (note the E) or +//! CBLAS: +//! +//! ```.ignore +//! sudo apt-get install gfortran libblas3gf liblapack3gf +//! export CARGO_FEATURE_SYSTEM_NETLIB=1 +//! export CARGO_FEATURE_EXCLUDE_LAPACKE=1 +//! export CARGO_FEATURE_EXCLUDE_CBLAS=1 +//! +//! export CARGO_FEATURES='--no-default-features --features netlib' +//! cargo build ${CARGO_FEATURES} +//! ``` +//! +//! ### Mac OS X +//! +//! On Mac OS X, do this to use Apple's Accelerate framework: +//! +//! ```.ignore +//! export CARGO_FEATURES='--no-default-features --features accelerate' +//! cargo build ${CARGO_FEATURES} +//! ``` +//! +//! [version-img]: https://img.shields.io/crates/v/nalgebra-lapack.svg +//! [version-url]: https://crates.io/crates/nalgebra-lapack +//! [status-img]: https://travis-ci.org/strawlab/nalgebra-lapack.svg?branch=master +//! [status-url]: https://travis-ci.org/strawlab/nalgebra-lapack +//! [doc-img]: https://docs.rs/nalgebra-lapack/badge.svg +//! [doc-url]: https://docs.rs/nalgebra-lapack/ +//! +//! ## Contributors +//! This integration of LAPACK on nalgebra was +//! [initiated](https://github.com/strawlab/nalgebra-lapack) by Andrew Straw. It +//! then became officially supported and integrated to the main nalgebra +//! repository. + +#![deny(non_camel_case_types)] +#![deny(unused_parens)] +#![deny(non_upper_case_globals)] +#![deny(unused_qualifications)] +#![deny(unused_results)] +#![deny(missing_docs)] +#![doc(html_root_url = "http://nalgebra.org/rustdoc")] + +extern crate num_traits as num; +extern crate num_complex; +extern crate lapack; +extern crate alga; +extern crate nalgebra as na; + +mod lapack_check; +mod svd; +mod eigen; +mod symmetric_eigen; +mod cholesky; +mod lu; +mod qr; +mod hessenberg; +mod schur; + +use num_complex::Complex; + +pub use self::svd::SVD; +pub use self::cholesky::{Cholesky, CholeskyScalar}; +pub use self::lu::{LU, LUScalar}; +pub use self::eigen::Eigen; +pub use self::symmetric_eigen::SymmetricEigen; +pub use self::qr::QR; +pub use self::hessenberg::Hessenberg; +pub use self::schur::RealSchur; + + +trait ComplexHelper { + type RealPart; + + fn real_part(self) -> Self::RealPart; +} + +impl ComplexHelper for f32 { + type RealPart = f32; + + #[inline] + fn real_part(self) -> Self::RealPart { + self + } +} + +impl ComplexHelper for f64 { + type RealPart = f64; + + #[inline] + fn real_part(self) -> Self::RealPart { + self + } +} + +impl ComplexHelper for Complex { + type RealPart = f32; + + #[inline] + fn real_part(self) -> Self::RealPart { + self.re + } +} + +impl ComplexHelper for Complex { + type RealPart = f64; + + #[inline] + fn real_part(self) -> Self::RealPart { + self.re + } +} + +unsafe fn uninitialized_vec(n: usize) -> Vec { + let mut res = Vec::new(); + res.reserve_exact(n); + res.set_len(n); + res +} diff --git a/nalgebra-lapack/src/lu.rs b/nalgebra-lapack/src/lu.rs new file mode 100644 index 00000000..07de2d92 --- /dev/null +++ b/nalgebra-lapack/src/lu.rs @@ -0,0 +1,320 @@ +use num::{Zero, One}; +use num_complex::Complex; + +use ::ComplexHelper; +use na::{Scalar, DefaultAllocator, Matrix, MatrixMN, MatrixN, VectorN}; +use na::dimension::{Dim, DimMin, DimMinimum, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + +/// LU decomposition with partial pivoting. +/// +/// This decomposes a matrix `M` with m rows and n columns into three parts: +/// * `L` which is a `m × min(m, n)` lower-triangular matrix. +/// * `U` which is a `min(m, n) × n` upper-triangular matrix. +/// * `P` which is a `m * m` permutation matrix. +/// +/// Those are such that `M == P * L * U`. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Serialize, + PermutationSequence>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Deserialize<'de>, + PermutationSequence>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct LU, C: Dim> + where DefaultAllocator: Allocator> + + Allocator { + lu: MatrixMN, + p: VectorN> +} + +impl, C: Dim> Copy for LU + where DefaultAllocator: Allocator + + Allocator>, + MatrixMN: Copy, + VectorN>: Copy { } + +impl LU + where N: Zero + One, + R: DimMin, + DefaultAllocator: Allocator + + Allocator + + Allocator> + + Allocator, C> + + Allocator> { + + /// Computes the LU decomposition with partial (row) pivoting of `matrix`. + pub fn new(mut m: MatrixMN) -> Self { + let (nrows, ncols) = m.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + let nrows = nrows.value() as i32; + let ncols = ncols.value() as i32; + + let mut ipiv: VectorN = Matrix::zeros_generic(min_nrows_ncols, U1); + + let mut info = 0; + + N::xgetrf(nrows, ncols, m.as_mut_slice(), nrows, ipiv.as_mut_slice(), &mut info); + lapack_panic!(info); + + LU { lu: m, p: ipiv } + } + + /// Gets the lower-triangular matrix part of the decomposition. + #[inline] + pub fn l(&self) -> MatrixMN> { + let (nrows, ncols) = self.lu.data.shape(); + let mut res = self.lu.columns_generic(0, nrows.min(ncols)).into_owned(); + + res.fill_upper_triangle(Zero::zero(), 1); + res.fill_diagonal(One::one()); + + res + } + + /// Gets the upper-triangular matrix part of the decomposition. + #[inline] + pub fn u(&self) -> MatrixMN, C> { + let (nrows, ncols) = self.lu.data.shape(); + let mut res = self.lu.rows_generic(0, nrows.min(ncols)).into_owned(); + + res.fill_lower_triangle(Zero::zero(), 1); + + res + } + + /// Gets the row permutation matrix of this decomposition. + /// + /// Computing the permutation matrix explicitly is costly and usually not necessary. + /// To permute rows of a matrix or vector, use the method `self.permute(...)` instead. + #[inline] + pub fn p(&self) -> MatrixN { + let (dim, _) = self.lu.data.shape(); + let mut id = Matrix::identity_generic(dim, dim); + self.permute(&mut id); + + id + } + + // FIXME: when we support resizing a matrix, we could add unwrap_u/unwrap_l that would + // re-use the memory from the internal matrix! + + /// Gets the LAPACK permutation indices. + #[inline] + pub fn permutation_indices(&self) -> &VectorN> { + &self.p + } + + /// Applies the permutation matrix to a given matrix or vector in-place. + #[inline] + pub fn permute(&self, rhs: &mut MatrixMN) + where DefaultAllocator: Allocator { + + let (nrows, ncols) = rhs.shape(); + + N::xlaswp(ncols as i32, rhs.as_mut_slice(), nrows as i32, + 1, self.p.len() as i32, self.p.as_slice(), -1); + } + + fn generic_solve_mut(&self, trans: u8, b: &mut MatrixMN) -> bool + where DefaultAllocator: Allocator + + Allocator { + + let dim = self.lu.nrows(); + + assert!(self.lu.is_square(), "Unable to solve a set of under/over-determined equations."); + assert!(b.nrows() == dim, "The number of rows of `b` must be equal to the dimension of the matrix `a`."); + + let nrhs = b.ncols() as i32; + let lda = dim as i32; + let ldb = dim as i32; + let mut info = 0; + + N::xgetrs(trans, dim as i32, nrhs, self.lu.as_slice(), lda, self.p.as_slice(), + b.as_mut_slice(), ldb, &mut info); + lapack_test!(info) + } + + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + pub fn solve(&self, b: &Matrix) -> Option> + where S2: Storage, + DefaultAllocator: Allocator + + Allocator { + + let mut res = b.clone_owned(); + if self.generic_solve_mut(b'N', &mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self.transpose() * x = b`, where `x` is the unknown to be + /// determined. + pub fn solve_transpose(&self, b: &Matrix) + -> Option> + where S2: Storage, + DefaultAllocator: Allocator + + Allocator { + + let mut res = b.clone_owned(); + if self.generic_solve_mut(b'T', &mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self.conjugate_transpose() * x = b`, where `x` is the unknown to + /// be determined. + pub fn solve_conjugate_transpose(&self, b: &Matrix) + -> Option> + where S2: Storage, + DefaultAllocator: Allocator + + Allocator { + + let mut res = b.clone_owned(); + if self.generic_solve_mut(b'T', &mut res) { + Some(res) + } + else { + None + } + } + + /// Solves in-place the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// Retuns `false` if no solution was found (the decomposed matrix is singular). + pub fn solve_mut(&self, b: &mut MatrixMN) -> bool + where DefaultAllocator: Allocator + + Allocator { + + self.generic_solve_mut(b'N', b) + } + + /// Solves in-place the linear system `self.transpose() * x = b`, where `x` is the unknown to be + /// determined. + /// + /// Retuns `false` if no solution was found (the decomposed matrix is singular). + pub fn solve_transpose_mut(&self, b: &mut MatrixMN) -> bool + where DefaultAllocator: Allocator + + Allocator { + + self.generic_solve_mut(b'T', b) + } + + /// Solves in-place the linear system `self.conjugate_transpose() * x = b`, where `x` is the unknown to + /// be determined. + /// + /// Retuns `false` if no solution was found (the decomposed matrix is singular). + pub fn solve_conjugate_transpose_mut(&self, b: &mut MatrixMN) -> bool + where DefaultAllocator: Allocator + + Allocator { + + self.generic_solve_mut(b'T', b) + } +} + +impl LU + where N: Zero + One, + D: DimMin, + DefaultAllocator: Allocator + + Allocator { + /// Computes the inverse of the decomposed matrix. + pub fn inverse(mut self) -> Option> { + let dim = self.lu.nrows() as i32; + let mut info = 0; + let lwork = N::xgetri_work_size(dim, self.lu.as_mut_slice(), + dim, self.p.as_mut_slice(), + &mut info); + lapack_check!(info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + N::xgetri(dim, self.lu.as_mut_slice(), dim, self.p.as_mut_slice(), + &mut work, lwork, &mut info); + lapack_check!(info); + + Some(self.lu) + } +} + + + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the LU decomposition. +pub trait LUScalar: Scalar { + #[allow(missing_docs)] + fn xgetrf(m: i32, n: i32, a: &mut [Self], lda: i32, ipiv: &mut [i32], info: &mut i32); + #[allow(missing_docs)] + fn xlaswp(n: i32, a: &mut [Self], lda: i32, k1: i32, k2: i32, ipiv: &[i32], incx: i32); + #[allow(missing_docs)] + fn xgetrs(trans: u8, n: i32, nrhs: i32, a: &[Self], lda: i32, ipiv: &[i32], + b: &mut [Self], ldb: i32, info: &mut i32); + #[allow(missing_docs)] + fn xgetri(n: i32, a: &mut [Self], lda: i32, ipiv: &[i32], + work: &mut [Self], lwork: i32, info: &mut i32); + #[allow(missing_docs)] + fn xgetri_work_size(n: i32, a: &mut [Self], lda: i32, ipiv: &[i32], info: &mut i32) -> i32; +} + + +macro_rules! lup_scalar_impl( + ($N: ty, $xgetrf: path, $xlaswp: path, $xgetrs: path, $xgetri: path) => ( + impl LUScalar for $N { + #[inline] + fn xgetrf(m: i32, n: i32, a: &mut [Self], lda: i32, ipiv: &mut [i32], info: &mut i32) { + $xgetrf(m, n, a, lda, ipiv, info) + } + + #[inline] + fn xlaswp(n: i32, a: &mut [Self], lda: i32, k1: i32, k2: i32, ipiv: &[i32], incx: i32) { + $xlaswp(n, a, lda, k1, k2, ipiv, incx) + } + + #[inline] + fn xgetrs(trans: u8, n: i32, nrhs: i32, a: &[Self], lda: i32, ipiv: &[i32], + b: &mut [Self], ldb: i32, info: &mut i32) { + $xgetrs(trans, n, nrhs, a, lda, ipiv, b, ldb, info) + } + + #[inline] + fn xgetri(n: i32, a: &mut [Self], lda: i32, ipiv: &[i32], + work: &mut [Self], lwork: i32, info: &mut i32) { + $xgetri(n, a, lda, ipiv, work, lwork, info) + } + + #[inline] + fn xgetri_work_size(n: i32, a: &mut [Self], lda: i32, ipiv: &[i32], info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xgetri(n, a, lda, ipiv, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + + +lup_scalar_impl!(f32, interface::sgetrf, interface::slaswp, interface::sgetrs, interface::sgetri); +lup_scalar_impl!(f64, interface::dgetrf, interface::dlaswp, interface::dgetrs, interface::dgetri); +lup_scalar_impl!(Complex, interface::cgetrf, interface::claswp, interface::cgetrs, interface::cgetri); +lup_scalar_impl!(Complex, interface::zgetrf, interface::zlaswp, interface::zgetrs, interface::zgetri); diff --git a/nalgebra-lapack/src/qr.rs b/nalgebra-lapack/src/qr.rs new file mode 100644 index 00000000..d221b86b --- /dev/null +++ b/nalgebra-lapack/src/qr.rs @@ -0,0 +1,200 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num_complex::Complex; +use num::Zero; + +use ::ComplexHelper; +use na::{Scalar, DefaultAllocator, Matrix, VectorN, MatrixMN}; +use na::dimension::{Dim, DimMin, DimMinimum, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + + +/// The QR decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct QR, C: Dim> + where DefaultAllocator: Allocator + + Allocator> { + qr: MatrixMN, + tau: VectorN> +} + +impl, C: Dim> Copy for QR + where DefaultAllocator: Allocator + + Allocator>, + MatrixMN: Copy, + VectorN>: Copy { } + +impl, C: Dim> QR + where DefaultAllocator: Allocator + + Allocator> + + Allocator, C> + + Allocator> { + /// Computes the QR decomposition of the matrix `m`. + pub fn new(mut m: MatrixMN) -> QR { + let (nrows, ncols) = m.data.shape(); + + let mut info = 0; + let mut tau = unsafe { Matrix::new_uninitialized_generic(nrows.min(ncols), U1) }; + + if nrows.value() == 0 || ncols.value() == 0 { + return QR { qr: m, tau: tau }; + } + + let lwork = N::xgeqrf_work_size(nrows.value() as i32, ncols.value() as i32, + m.as_mut_slice(), nrows.value() as i32, + tau.as_mut_slice(), &mut info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + N::xgeqrf(nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), + nrows.value() as i32, tau.as_mut_slice(), &mut work, lwork, &mut info); + + QR { qr: m, tau: tau } + } + + /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. + #[inline] + pub fn r(&self) -> MatrixMN, C> { + let (nrows, ncols) = self.qr.data.shape(); + self.qr.rows_generic(0, nrows.min(ncols)).upper_triangle() + } +} + +impl, C: Dim> QR + where DefaultAllocator: Allocator + + Allocator> + + Allocator, C> + + Allocator> { + /// Retrieves the matrices `(Q, R)` of this decompositions. + pub fn unpack(self) -> (MatrixMN>, MatrixMN, C>) { + (self.q(), self.r()) + } + + + /// Computes the orthogonal matrix `Q` of this decomposition. + #[inline] + pub fn q(&self) -> MatrixMN> { + let (nrows, ncols) = self.qr.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + + if min_nrows_ncols.value() == 0 { + return MatrixMN::from_element_generic(nrows, min_nrows_ncols, N::zero()); + } + + let mut q = self.qr.generic_slice((0, 0), (nrows, min_nrows_ncols)).into_owned(); + + let mut info = 0; + let nrows = nrows.value() as i32; + + let lwork = N::xorgqr_work_size(nrows, min_nrows_ncols.value() as i32, + self.tau.len() as i32, q.as_mut_slice(), nrows, + self.tau.as_slice(), &mut info); + + let mut work = vec![ N::zero(); lwork as usize ]; + + N::xorgqr(nrows, min_nrows_ncols.value() as i32, self.tau.len() as i32, q.as_mut_slice(), + nrows, self.tau.as_slice(), &mut work, lwork, &mut info); + + q + } +} + + + + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalar types for which Lapack funtion exist to compute the +/// QR decomposition. +pub trait QRScalar: Scalar { + fn xgeqrf(m: i32, n: i32, a: &mut [Self], lda: i32, tau: &mut [Self], + work: &mut [Self], lwork: i32, info: &mut i32); + + fn xgeqrf_work_size(m: i32, n: i32, a: &mut [Self], lda: i32, + tau: &mut [Self], info: &mut i32) -> i32; +} + +/// Trait implemented by reals for which Lapack funtion exist to compute the +/// QR decomposition. +pub trait QRReal: QRScalar { + #[allow(missing_docs)] + fn xorgqr(m: i32, n: i32, k: i32, a: &mut [Self], lda: i32, tau: &[Self], work: &mut [Self], + lwork: i32, info: &mut i32); + + #[allow(missing_docs)] + fn xorgqr_work_size(m: i32, n: i32, k: i32, a: &mut [Self], lda: i32, + tau: &[Self], info: &mut i32) -> i32; +} + +macro_rules! qr_scalar_impl( + ($N: ty, $xgeqrf: path) => ( + impl QRScalar for $N { + #[inline] + fn xgeqrf(m: i32, n: i32, a: &mut [Self], lda: i32, tau: &mut [Self], + work: &mut [Self], lwork: i32, info: &mut i32) { + $xgeqrf(m, n, a, lda, tau, work, lwork, info) + } + + #[inline] + fn xgeqrf_work_size(m: i32, n: i32, a: &mut [Self], lda: i32, tau: &mut [Self], + info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xgeqrf(m, n, a, lda, tau, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +macro_rules! qr_real_impl( + ($N: ty, $xorgqr: path) => ( + impl QRReal for $N { + #[inline] + fn xorgqr(m: i32, n: i32, k: i32, a: &mut [Self], lda: i32, tau: &[Self], + work: &mut [Self], lwork: i32, info: &mut i32) { + $xorgqr(m, n, k, a, lda, tau, work, lwork, info) + } + + #[inline] + fn xorgqr_work_size(m: i32, n: i32, k: i32, a: &mut [Self], lda: i32, tau: &[Self], + info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xorgqr(m, n, k, a, lda, tau, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +qr_scalar_impl!(f32, interface::sgeqrf); +qr_scalar_impl!(f64, interface::dgeqrf); +qr_scalar_impl!(Complex, interface::cgeqrf); +qr_scalar_impl!(Complex, interface::zgeqrf); + +qr_real_impl!(f32, interface::sorgqr); +qr_real_impl!(f64, interface::dorgqr); diff --git a/nalgebra-lapack/src/schur.rs b/nalgebra-lapack/src/schur.rs new file mode 100644 index 00000000..8576b336 --- /dev/null +++ b/nalgebra-lapack/src/schur.rs @@ -0,0 +1,214 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num::Zero; +use num_complex::Complex; + +use alga::general::Real; + +use ::ComplexHelper; +use na::{Scalar, DefaultAllocator, Matrix, VectorN, MatrixN}; +use na::dimension::{Dim, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + +/// Eigendecomposition of a real square matrix with real eigenvalues. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct RealSchur + where DefaultAllocator: Allocator + + Allocator { + + re: VectorN, + im: VectorN, + t: MatrixN, + q: MatrixN +} + +impl Copy for RealSchur + where DefaultAllocator: Allocator + Allocator, + MatrixN: Copy, + VectorN: Copy { } + + +impl RealSchur + where DefaultAllocator: Allocator + + Allocator { + /// Computes the eigenvalues and real Schur foorm of the matrix `m`. + /// + /// Panics if the method did not converge. + pub fn new(m: MatrixN) -> Self { + Self::try_new(m).expect("RealSchur decomposition: convergence failed.") + } + + /// Computes the eigenvalues and real Schur foorm of the matrix `m`. + /// + /// Returns `None` if the method did not converge. + pub fn try_new(mut m: MatrixN) -> Option { + assert!(m.is_square(), "Unable to compute the eigenvalue decomposition of a non-square matrix."); + + let (nrows, ncols) = m.data.shape(); + let n = nrows.value(); + + let lda = n as i32; + + let mut info = 0; + + let mut wr = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + let mut wi = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + let mut q = unsafe { Matrix::new_uninitialized_generic(nrows, ncols) }; + // Placeholders: + let mut bwork = [ 0i32 ]; + let mut unused = 0; + + let lwork = N::xgees_work_size(b'V', b'N', n as i32, m.as_mut_slice(), lda, &mut unused, + wr.as_mut_slice(), wi.as_mut_slice(), q.as_mut_slice(), n as i32, + &mut bwork, &mut info); + lapack_check!(info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + N::xgees(b'V', b'N', n as i32, m.as_mut_slice(), lda, &mut unused, + wr.as_mut_slice(), wi.as_mut_slice(), q.as_mut_slice(), + n as i32, &mut work, lwork, &mut bwork, &mut info); + lapack_check!(info); + + Some(RealSchur { re: wr, im: wi, t: m, q: q }) + } + + /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the + /// decomposed matrix equals `Q * T * Q.transpose()`. + pub fn unpack(self) -> (MatrixN, MatrixN) { + (self.q, self.t) + } + + /// Computes the real eigenvalues of the decomposed matrix. + /// + /// Return `None` if some eigenvalues are complex. + pub fn eigenvalues(&self) -> Option> { + if self.im.iter().all(|e| e.is_zero()) { + Some(self.re.clone()) + } + else { + None + } + } + + /// Computes the complex eigenvalues of the decomposed matrix. + pub fn complex_eigenvalues(&self) -> VectorN, D> + where DefaultAllocator: Allocator, D> { + + let mut out = unsafe { VectorN::new_uninitialized_generic(self.t.data.shape().0, U1) }; + + for i in 0 .. out.len() { + out[i] = Complex::new(self.re[i], self.im[i]) + } + + out + } +} + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the Real Schur decomposition. +pub trait RealSchurScalar: Scalar { + #[allow(missing_docs)] + fn xgees(jobvs: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + sdim: &mut i32, + wr: &mut [Self], + wi: &mut [Self], + vs: &mut [Self], + ldvs: i32, + work: &mut [Self], + lwork: i32, + bwork: &mut [i32], + info: &mut i32); + + #[allow(missing_docs)] + fn xgees_work_size(jobvs: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [Self], + lda: i32, + sdim: &mut i32, + wr: &mut [Self], + wi: &mut [Self], + vs: &mut [Self], + ldvs: i32, + bwork: &mut [i32], + info: &mut i32) + -> i32; +} + +macro_rules! real_eigensystem_scalar_impl ( + ($N: ty, $xgees: path) => ( + impl RealSchurScalar for $N { + #[inline] + fn xgees(jobvs: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + sdim: &mut i32, + wr: &mut [$N], + wi: &mut [$N], + vs: &mut [$N], + ldvs: i32, + work: &mut [$N], + lwork: i32, + bwork: &mut [i32], + info: &mut i32) { + $xgees(jobvs, sort, None, n, a, lda, sdim, wr, wi, vs, ldvs, work, lwork, bwork, info); + } + + + #[inline] + fn xgees_work_size(jobvs: u8, + sort: u8, + // select: ??? + n: i32, + a: &mut [$N], + lda: i32, + sdim: &mut i32, + wr: &mut [$N], + wi: &mut [$N], + vs: &mut [$N], + ldvs: i32, + bwork: &mut [i32], + info: &mut i32) + -> i32 { + let mut work = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xgees(jobvs, sort, None, n, a, lda, sdim, wr, wi, vs, ldvs, &mut work, lwork, bwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +real_eigensystem_scalar_impl!(f32, interface::sgees); +real_eigensystem_scalar_impl!(f64, interface::dgees); diff --git a/nalgebra-lapack/src/svd.rs b/nalgebra-lapack/src/svd.rs new file mode 100644 index 00000000..a6587051 --- /dev/null +++ b/nalgebra-lapack/src/svd.rs @@ -0,0 +1,279 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use std::cmp; +use num::Signed; + +use na::{Scalar, Matrix, VectorN, MatrixN, MatrixMN, + DefaultAllocator}; +use na::dimension::{Dim, DimMin, DimMinimum, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + + +/// The SVD decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator> + + Allocator + + Allocator, + MatrixN: serde::Serialize, + MatrixN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator> + + Allocator + + Allocator, + MatrixN: serde::Deserialize<'de>, + MatrixN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct SVD, C: Dim> + where DefaultAllocator: Allocator + + Allocator> + + Allocator { + /// The left-singular vectors `U` of this SVD. + pub u: MatrixN, // FIXME: should be MatrixMN> + /// The right-singular vectors `V^t` of this SVD. + pub vt: MatrixN, // FIXME: should be MatrixMN, C> + /// The singular values of this SVD. + pub singular_values: VectorN> +} + +impl, C: Dim> Copy for SVD + where DefaultAllocator: Allocator + + Allocator + + Allocator>, + MatrixMN: Copy, + MatrixMN: Copy, + VectorN>: Copy { } + +/// Trait implemented by floats (`f32`, `f64`) and complex floats (`Complex`, `Complex`) +/// supported by the Singular Value Decompotition. +pub trait SVDScalar, C: Dim>: Scalar + where DefaultAllocator: Allocator + + Allocator + + Allocator> + + Allocator { + /// Computes the SVD decomposition of `m`. + fn compute(m: MatrixMN) -> Option>; +} + +impl, R: DimMin, C: Dim> SVD + where DefaultAllocator: Allocator + + Allocator + + Allocator> + + Allocator { + /// Computes the Singular Value Decomposition of `matrix`. + pub fn new(m: MatrixMN) -> Option { + N::compute(m) + } +} + +macro_rules! svd_impl( + ($t: ty, $lapack_func: path) => ( + impl SVDScalar for $t + where R: DimMin, + DefaultAllocator: Allocator<$t, R, C> + + Allocator<$t, R, R> + + Allocator<$t, C, C> + + Allocator<$t, DimMinimum> { + + fn compute(mut m: MatrixMN<$t, R, C>) -> Option> { + let (nrows, ncols) = m.data.shape(); + + if nrows.value() == 0 || ncols.value() == 0 { + return None; + } + + let job = b'A'; + + let lda = nrows.value() as i32; + + let mut u = unsafe { Matrix::new_uninitialized_generic(nrows, nrows) }; + let mut s = unsafe { Matrix::new_uninitialized_generic(nrows.min(ncols), U1) }; + let mut vt = unsafe { Matrix::new_uninitialized_generic(ncols, ncols) }; + + let ldu = nrows.value(); + let ldvt = ncols.value(); + + let mut work = [ 0.0 ]; + let mut lwork = -1 as i32; + let mut info = 0; + let mut iwork = unsafe { ::uninitialized_vec(8 * cmp::min(nrows.value(), ncols.value())) }; + + $lapack_func(job, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), + lda, &mut s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), + ldvt as i32, &mut work, lwork, &mut iwork, &mut info); + lapack_check!(info); + + lwork = work[0] as i32; + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + $lapack_func(job, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), + lda, &mut s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), + ldvt as i32, &mut work, lwork, &mut iwork, &mut info); + lapack_check!(info); + + Some(SVD { u: u, singular_values: s, vt: vt }) + } + } + + impl, C: Dim> SVD<$t, R, C> + // FIXME: All those bounds… + where DefaultAllocator: Allocator<$t, R, C> + + Allocator<$t, C, R> + + Allocator<$t, U1, R> + + Allocator<$t, U1, C> + + Allocator<$t, R, R> + + Allocator<$t, DimMinimum> + + Allocator<$t, DimMinimum, R> + + Allocator<$t, DimMinimum, C> + + Allocator<$t, R, DimMinimum> + + Allocator<$t, C, C> { + /// Reconstructs the matrix from its decomposition. + /// + /// Useful if some components (e.g. some singular values) of this decomposition have + /// been manually changed by the user. + #[inline] + pub fn recompose(self) -> MatrixMN<$t, R, C> { + let nrows = self.u.data.shape().0; + let ncols = self.vt.data.shape().1; + let min_nrows_ncols = nrows.min(ncols); + + let mut res: MatrixMN<_, R, C> = Matrix::zeros_generic(nrows, ncols); + + { + let mut sres = res.generic_slice_mut((0, 0), (min_nrows_ncols, ncols)); + sres.copy_from(&self.vt.rows_generic(0, min_nrows_ncols)); + + for i in 0 .. min_nrows_ncols.value() { + let eigval = self.singular_values[i]; + let mut row = sres.row_mut(i); + row *= eigval; + } + } + + self.u * res + } + + /// Computes the pseudo-inverse of the decomposed matrix. + /// + /// All singular value bellow epsilon will be set to zero instead of being inverted. + #[inline] + pub fn pseudo_inverse(&self, epsilon: $t) -> MatrixMN<$t, C, R> { + let nrows = self.u.data.shape().0; + let ncols = self.vt.data.shape().1; + let min_nrows_ncols = nrows.min(ncols); + + let mut res: MatrixMN<_, C, R> = Matrix::zeros_generic(ncols, nrows); + + { + let mut sres = res.generic_slice_mut((0, 0), (min_nrows_ncols, nrows)); + self.u.columns_generic(0, min_nrows_ncols).transpose_to(&mut sres); + + for i in 0 .. min_nrows_ncols.value() { + let eigval = self.singular_values[i]; + let mut row = sres.row_mut(i); + + if eigval.abs() > epsilon { + row /= eigval + } + else { + row.fill(0.0); + } + } + } + + self.vt.tr_mul(&res) + } + + /// The rank of the decomposed matrix. + /// + /// This is the number of singular values that are not too small (i.e. greater than + /// the given `epsilon`). + #[inline] + pub fn rank(&self, epsilon: $t) -> usize { + let mut i = 0; + + for e in self.singular_values.as_slice().iter() { + if e.abs() > epsilon { + i += 1; + } + } + + i + } + + // FIXME: add methods to retrieve the null-space and column-space? (Respectively + // corresponding to the zero and non-zero singular values). + } + ); +); + +/* +macro_rules! svd_complex_impl( + ($name: ident, $t: ty, $lapack_func: path) => ( + impl SVDScalar for Complex<$t> { + fn compute(mut m: Matrix<$t, R, C, S>) -> Option> + Option<(MatrixN, R, S::Alloc>, + VectorN<$t, DimMinimum, S::Alloc>, + MatrixN, C, S::Alloc>)> + where R: DimMin, + S: ContiguousStorage, R, C>, + S::Alloc: OwnedAllocator, R, C, S> + + Allocator, R, R> + + Allocator, C, C> + + Allocator<$t, DimMinimum> { + let (nrows, ncols) = m.data.shape(); + + if nrows.value() == 0 || ncols.value() == 0 { + return None; + } + + let jobu = b'A'; + let jobvt = b'A'; + + let lda = nrows.value() as i32; + let min_nrows_ncols = nrows.min(ncols); + + + let mut u = unsafe { Matrix::new_uninitialized_generic(nrows, nrows) }; + let mut s = unsafe { Matrix::new_uninitialized_generic(min_nrows_ncols, U1) }; + let mut vt = unsafe { Matrix::new_uninitialized_generic(ncols, ncols) }; + + let ldu = nrows.value(); + let ldvt = ncols.value(); + + let mut work = [ Complex::new(0.0, 0.0) ]; + let mut lwork = -1 as i32; + let mut rwork = vec![ 0.0; (5 * min_nrows_ncols.value()) ]; + let mut info = 0; + + $lapack_func(jobu, jobvt, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), + lda, s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), + ldvt as i32, &mut work, lwork, &mut rwork, &mut info); + lapack_check!(info); + + lwork = work[0].re as i32; + let mut work = vec![Complex::new(0.0, 0.0); lwork as usize]; + + $lapack_func(jobu, jobvt, nrows.value() as i32, ncols.value() as i32, m.as_mut_slice(), + lda, s.as_mut_slice(), u.as_mut_slice(), ldu as i32, vt.as_mut_slice(), + ldvt as i32, &mut work, lwork, &mut rwork, &mut info); + lapack_check!(info); + + Some((u, s, vt)) + } + ); +); +*/ + +svd_impl!(f32, interface::sgesdd); +svd_impl!(f64, interface::dgesdd); +// svd_complex_impl!(lapack_svd_complex_f32, f32, interface::cgesvd); +// svd_complex_impl!(lapack_svd_complex_f64, f64, interface::zgesvd); diff --git a/nalgebra-lapack/src/symmetric_eigen.rs b/nalgebra-lapack/src/symmetric_eigen.rs new file mode 100644 index 00000000..16d3f47b --- /dev/null +++ b/nalgebra-lapack/src/symmetric_eigen.rs @@ -0,0 +1,176 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num::Zero; +use std::ops::MulAssign; + +use alga::general::Real; + +use ::ComplexHelper; +use na::{Scalar, DefaultAllocator, Matrix, VectorN, MatrixN}; +use na::dimension::{Dim, U1}; +use na::storage::Storage; +use na::allocator::Allocator; + +use lapack::fortran as interface; + +/// Eigendecomposition of a real square symmetric matrix with real eigenvalues. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator, + VectorN: serde::Deserialize<'de>, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct SymmetricEigen + where DefaultAllocator: Allocator + + Allocator { + /// The eigenvectors of the decomposed matrix. + pub eigenvectors: MatrixN, + + /// The unsorted eigenvalues of the decomposed matrix. + pub eigenvalues: VectorN, +} + + +impl Copy for SymmetricEigen + where DefaultAllocator: Allocator + + Allocator, + MatrixN: Copy, + VectorN: Copy { } + +impl SymmetricEigen + where DefaultAllocator: Allocator + + Allocator { + + /// Computes the eigenvalues and eigenvectors of the symmetric matrix `m`. + /// + /// Only the lower-triangular part of `m` is read. If `eigenvectors` is `false` then, the + /// eigenvectors are not computed explicitly. Panics if the method did not converge. + pub fn new(m: MatrixN) -> Self { + let (vals, vecs) = Self::do_decompose(m, true).expect("SymmetricEigen: convergence failure."); + SymmetricEigen { eigenvalues: vals, eigenvectors: vecs.unwrap() } + } + + /// Computes the eigenvalues and eigenvectors of the symmetric matrix `m`. + /// + /// Only the lower-triangular part of `m` is read. If `eigenvectors` is `false` then, the + /// eigenvectors are not computed explicitly. Returns `None` if the method did not converge. + pub fn try_new(m: MatrixN) -> Option { + Self::do_decompose(m, true).map(|(vals, vecs)| { + SymmetricEigen { eigenvalues: vals, eigenvectors: vecs.unwrap() } + }) + } + + fn do_decompose(mut m: MatrixN, eigenvectors: bool) -> Option<(VectorN, Option>)> { + assert!(m.is_square(), "Unable to compute the eigenvalue decomposition of a non-square matrix."); + + let jobz = if eigenvectors { b'V' } else { b'N' }; + + let nrows = m.data.shape().0; + let n = nrows.value(); + + let lda = n as i32; + + let mut values = unsafe { Matrix::new_uninitialized_generic(nrows, U1) }; + let mut info = 0; + + let lwork = N::xsyev_work_size(jobz, b'L', n as i32, m.as_mut_slice(), lda, &mut info); + lapack_check!(info); + + let mut work = unsafe { ::uninitialized_vec(lwork as usize) }; + + N::xsyev(jobz, b'L', n as i32, m.as_mut_slice(), lda, values.as_mut_slice(), &mut work, lwork, &mut info); + lapack_check!(info); + + let vectors = if eigenvectors { Some(m) } else { None }; + Some((values, vectors)) + } + + /// Computes only the eigenvalues of the input matrix. + /// + /// Panics if the method does not converge. + pub fn eigenvalues(m: MatrixN) -> VectorN { + Self::do_decompose(m, false).expect("SymmetricEigen eigenvalues: convergence failure.").0 + } + + /// Computes only the eigenvalues of the input matrix. + /// + /// Returns `None` if the method does not converge. + pub fn try_eigenvalues(m: MatrixN) -> Option> { + Self::do_decompose(m, false).map(|res| res.0) + } + + /// The determinant of the decomposed matrix. + #[inline] + pub fn determinant(&self) -> N { + let mut det = N::one(); + for e in self.eigenvalues.iter() { + det *= *e; + } + + det + } + + /// Rebuild the original matrix. + /// + /// This is useful if some of the eigenvalues have been manually modified. + pub fn recompose(&self) -> MatrixN { + let mut u_t = self.eigenvectors.clone(); + for i in 0 .. self.eigenvalues.len() { + let val = self.eigenvalues[i]; + u_t.column_mut(i).mul_assign(val); + } + u_t.transpose_mut(); + &self.eigenvectors * u_t + } +} + + +/* + * + * Lapack functions dispatch. + * + */ +/// Trait implemented by scalars for which Lapack implements the eigendecomposition of symmetric +/// real matrices. +pub trait SymmetricEigenScalar: Scalar { + #[allow(missing_docs)] + fn xsyev(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, w: &mut [Self], work: &mut [Self], + lwork: i32, info: &mut i32); + #[allow(missing_docs)] + fn xsyev_work_size(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32) -> i32; +} + +macro_rules! real_eigensystem_scalar_impl ( + ($N: ty, $xsyev: path) => ( + impl SymmetricEigenScalar for $N { + #[inline] + fn xsyev(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, w: &mut [Self], work: &mut [Self], + lwork: i32, info: &mut i32) { + $xsyev(jobz, uplo, n, a, lda, w, work, lwork, info) + } + + + #[inline] + fn xsyev_work_size(jobz: u8, uplo: u8, n: i32, a: &mut [Self], lda: i32, info: &mut i32) -> i32 { + let mut work = [ Zero::zero() ]; + let mut w = [ Zero::zero() ]; + let lwork = -1 as i32; + + $xsyev(jobz, uplo, n, a, lda, &mut w, &mut work, lwork, info); + ComplexHelper::real_part(work[0]) as i32 + } + } + ) +); + +real_eigensystem_scalar_impl!(f32, interface::ssyev); +real_eigensystem_scalar_impl!(f64, interface::dsyev); diff --git a/nalgebra-lapack/tests/lib.rs b/nalgebra-lapack/tests/lib.rs new file mode 100644 index 00000000..4e5ceac2 --- /dev/null +++ b/nalgebra-lapack/tests/lib.rs @@ -0,0 +1,9 @@ +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate nalgebra as na; +extern crate nalgebra_lapack as nl; + + +mod linalg; diff --git a/nalgebra-lapack/tests/linalg/cholesky.rs b/nalgebra-lapack/tests/linalg/cholesky.rs new file mode 100644 index 00000000..c0102af5 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/cholesky.rs @@ -0,0 +1,101 @@ +use std::cmp; + +use nl::Cholesky; +use na::{DMatrix, DVector, Vector4, Matrix3, Matrix4x3, Matrix4}; + +quickcheck!{ + fn cholesky(m: DMatrix) -> bool { + if m.len() != 0 { + let m = &m * m.transpose(); + if let Some(chol) = Cholesky::new(m.clone()) { + let l = chol.unpack(); + let reconstructed_m = &l * l.transpose(); + + return relative_eq!(reconstructed_m, m, epsilon = 1.0e-7) + } + } + return true + } + + fn cholesky_static(m: Matrix3) -> bool { + let m = &m * m.transpose(); + if let Some(chol) = Cholesky::new(m) { + let l = chol.unpack(); + let reconstructed_m = &l * l.transpose(); + + relative_eq!(reconstructed_m, m, epsilon = 1.0e-7) + } + else { + false + } + } + + fn cholesky_solve(n: usize, nb: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 15); // To avoid slowing down the test too much. + let nb = cmp::min(nb, 15); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + let m = &m * m.transpose(); + + if let Some(chol) = Cholesky::new(m.clone()) { + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = chol.solve(&b1).unwrap(); + let sol2 = chol.solve(&b2).unwrap(); + + return relative_eq!(&m * sol1, b1, epsilon = 1.0e-6) && + relative_eq!(&m * sol2, b2, epsilon = 1.0e-6) + } + } + + return true; + } + + fn cholesky_solve_static(m: Matrix4) -> bool { + let m = &m * m.transpose(); + match Cholesky::new(m) { + Some(chol) => { + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + let sol1 = chol.solve(&b1).unwrap(); + let sol2 = chol.solve(&b2).unwrap(); + + relative_eq!(m * sol1, b1, epsilon = 1.0e-7) && + relative_eq!(m * sol2, b2, epsilon = 1.0e-7) + }, + None => true + } + } + + fn cholesky_inverse(n: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 15); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + let m = &m * m.transpose(); + + if let Some(m1) = Cholesky::new(m.clone()).unwrap().inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + return id1.is_identity(1.0e-6) && id2.is_identity(1.0e-6); + } + } + + return true; + } + + fn cholesky_inverse_static(m: Matrix4) -> bool { + let m = m * m.transpose(); + match Cholesky::new(m.clone()).unwrap().inverse() { + Some(m1) => { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + }, + None => true + } + } +} diff --git a/nalgebra-lapack/tests/linalg/hessenberg.rs b/nalgebra-lapack/tests/linalg/hessenberg.rs new file mode 100644 index 00000000..bb48633e --- /dev/null +++ b/nalgebra-lapack/tests/linalg/hessenberg.rs @@ -0,0 +1,38 @@ +use std::cmp; + +use nl::Hessenberg; +use na::{DMatrix, Matrix4}; + +quickcheck!{ + fn hessenberg(n: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 25); + let m = DMatrix::::new_random(n, n); + + match Hessenberg::new(m.clone()) { + Some(hess) => { + let h = hess.h(); + let p = hess.p(); + + relative_eq!(m, &p * h * p.transpose(), epsilon = 1.0e-7) + }, + None => true + } + } + else { + true + } + } + + fn hessenberg_static(m: Matrix4) -> bool { + match Hessenberg::new(m) { + Some(hess) => { + let h = hess.h(); + let p = hess.p(); + + relative_eq!(m, p * h * p.transpose(), epsilon = 1.0e-7) + }, + None => true + } + } +} diff --git a/nalgebra-lapack/tests/linalg/lu.rs b/nalgebra-lapack/tests/linalg/lu.rs new file mode 100644 index 00000000..ff36735f --- /dev/null +++ b/nalgebra-lapack/tests/linalg/lu.rs @@ -0,0 +1,107 @@ +use std::cmp; + +use nl::LU; +use na::{DMatrix, DVector, Matrix4, Matrix4x3, Matrix3x4, Vector4}; + +quickcheck!{ + fn lup(m: DMatrix) -> bool { + if m.len() != 0 { + let lup = LU::new(m.clone()); + let l = lup.l(); + let u = lup.u(); + let mut computed1 = &l * &u; + lup.permute(&mut computed1); + + let computed2 = lup.p() * l * u; + + relative_eq!(computed1, m, epsilon = 1.0e-7) && + relative_eq!(computed2, m, epsilon = 1.0e-7) + } + else { + true + } + } + + fn lu_static(m: Matrix3x4) -> bool { + let lup = LU::new(m); + let l = lup.l(); + let u = lup.u(); + let mut computed1 = l * u; + lup.permute(&mut computed1); + + let computed2 = lup.p() * l * u; + + relative_eq!(computed1, m, epsilon = 1.0e-7) && + relative_eq!(computed2, m, epsilon = 1.0e-7) + } + + fn lu_solve(n: usize, nb: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 25); // To avoid slowing down the test too much. + let nb = cmp::min(nb, 25); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let lup = LU::new(m.clone()); + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = lup.solve(&b1).unwrap(); + let sol2 = lup.solve(&b2).unwrap(); + + let tr_sol1 = lup.solve_transpose(&b1).unwrap(); + let tr_sol2 = lup.solve_transpose(&b2).unwrap(); + + relative_eq!(&m * sol1, b1, epsilon = 1.0e-7) && + relative_eq!(&m * sol2, b2, epsilon = 1.0e-7) && + relative_eq!(m.transpose() * tr_sol1, b1, epsilon = 1.0e-7) && + relative_eq!(m.transpose() * tr_sol2, b2, epsilon = 1.0e-7) + } + else { + true + } + } + + fn lu_solve_static(m: Matrix4) -> bool { + let lup = LU::new(m); + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + let sol1 = lup.solve(&b1).unwrap(); + let sol2 = lup.solve(&b2).unwrap(); + let tr_sol1 = lup.solve_transpose(&b1).unwrap(); + let tr_sol2 = lup.solve_transpose(&b2).unwrap(); + + relative_eq!(m * sol1, b1, epsilon = 1.0e-7) && + relative_eq!(m * sol2, b2, epsilon = 1.0e-7) && + relative_eq!(m.transpose() * tr_sol1, b1, epsilon = 1.0e-7) && + relative_eq!(m.transpose() * tr_sol2, b2, epsilon = 1.0e-7) + } + + fn lu_inverse(n: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 25); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + if let Some(m1) = LU::new(m.clone()).inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + return id1.is_identity(1.0e-7) && id2.is_identity(1.0e-7); + } + } + + return true; + } + + fn lu_inverse_static(m: Matrix4) -> bool { + match LU::new(m.clone()).inverse() { + Some(m1) => { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + }, + None => true + } + } +} diff --git a/nalgebra-lapack/tests/linalg/mod.rs b/nalgebra-lapack/tests/linalg/mod.rs new file mode 100644 index 00000000..f692fa88 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/mod.rs @@ -0,0 +1,7 @@ +mod real_eigensystem; +mod symmetric_eigen; +mod cholesky; +mod lu; +mod qr; +mod svd; +mod real_schur; diff --git a/nalgebra-lapack/tests/linalg/qr.rs b/nalgebra-lapack/tests/linalg/qr.rs new file mode 100644 index 00000000..baac445b --- /dev/null +++ b/nalgebra-lapack/tests/linalg/qr.rs @@ -0,0 +1,20 @@ +use nl::QR; +use na::{DMatrix, Matrix4x3}; + +quickcheck!{ + fn qr(m: DMatrix) -> bool { + let qr = QR::new(m.clone()); + let q = qr.q(); + let r = qr.r(); + + relative_eq!(m, q * r, epsilon = 1.0e-7) + } + + fn qr_static(m: Matrix4x3) -> bool { + let qr = QR::new(m); + let q = qr.q(); + let r = qr.r(); + + relative_eq!(m, q * r, epsilon = 1.0e-7) + } +} diff --git a/nalgebra-lapack/tests/linalg/real_eigensystem.rs b/nalgebra-lapack/tests/linalg/real_eigensystem.rs new file mode 100644 index 00000000..70a3d787 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/real_eigensystem.rs @@ -0,0 +1,48 @@ +use std::cmp; + +use nl::Eigen; +use na::{DMatrix, Matrix4}; + +quickcheck!{ + fn eigensystem(n: usize) -> bool { + if n != 0 { + let n = cmp::min(n, 25); + let m = DMatrix::::new_random(n, n); + + match Eigen::new(m.clone(), true, true) { + Some(eig) => { + let eigvals = DMatrix::from_diagonal(&eig.eigenvalues); + let transformed_eigvectors = &m * eig.eigenvectors.as_ref().unwrap(); + let scaled_eigvectors = eig.eigenvectors.as_ref().unwrap() * &eigvals; + + let transformed_left_eigvectors = m.transpose() * eig.left_eigenvectors.as_ref().unwrap(); + let scaled_left_eigvectors = eig.left_eigenvectors.as_ref().unwrap() * &eigvals; + + relative_eq!(transformed_eigvectors, scaled_eigvectors, epsilon = 1.0e-7) && + relative_eq!(transformed_left_eigvectors, scaled_left_eigvectors, epsilon = 1.0e-7) + }, + None => true + } + } + else { + true + } + } + + fn eigensystem_static(m: Matrix4) -> bool { + match Eigen::new(m, true, true) { + Some(eig) => { + let eigvals = Matrix4::from_diagonal(&eig.eigenvalues); + let transformed_eigvectors = m * eig.eigenvectors.unwrap(); + let scaled_eigvectors = eig.eigenvectors.unwrap() * eigvals; + + let transformed_left_eigvectors = m.transpose() * eig.left_eigenvectors.unwrap(); + let scaled_left_eigvectors = eig.left_eigenvectors.unwrap() * eigvals; + + relative_eq!(transformed_eigvectors, scaled_eigvectors, epsilon = 1.0e-7) && + relative_eq!(transformed_left_eigvectors, scaled_left_eigvectors, epsilon = 1.0e-7) + }, + None => true + } + } +} diff --git a/nalgebra-lapack/tests/linalg/real_schur.rs b/nalgebra-lapack/tests/linalg/real_schur.rs new file mode 100644 index 00000000..4511d925 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/real_schur.rs @@ -0,0 +1,21 @@ +use std::cmp; +use nl::RealSchur; +use na::{DMatrix, Matrix4}; + +quickcheck! { + fn schur(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let m = DMatrix::::new_random(n, n); + + let (vecs, vals) = RealSchur::new(m.clone()).unpack(); + + relative_eq!(&vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7) + } + + fn schur_static(m: Matrix4) -> bool { + let (vecs, vals) = RealSchur::new(m.clone()).unpack(); + + relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7) + } +} + diff --git a/nalgebra-lapack/tests/linalg/svd.rs b/nalgebra-lapack/tests/linalg/svd.rs new file mode 100644 index 00000000..9ab7a99e --- /dev/null +++ b/nalgebra-lapack/tests/linalg/svd.rs @@ -0,0 +1,57 @@ +use nl::SVD; +use na::{DMatrix, Matrix3x4}; + +quickcheck!{ + fn svd(m: DMatrix) -> bool { + if m.nrows() != 0 && m.ncols() != 0 { + let svd = SVD::new(m.clone()).unwrap(); + let sm = DMatrix::from_partial_diagonal(m.nrows(), m.ncols(), svd.singular_values.as_slice()); + + let reconstructed_m = &svd.u * sm * &svd.vt; + let reconstructed_m2 = svd.recompose(); + + relative_eq!(reconstructed_m, m, epsilon = 1.0e-7) && + relative_eq!(reconstructed_m2, reconstructed_m, epsilon = 1.0e-7) + } + else { + true + } + } + + fn svd_static(m: Matrix3x4) -> bool { + let svd = SVD::new(m).unwrap(); + let sm = Matrix3x4::from_partial_diagonal(svd.singular_values.as_slice()); + + let reconstructed_m = &svd.u * &sm * &svd.vt; + let reconstructed_m2 = svd.recompose(); + + relative_eq!(reconstructed_m, m, epsilon = 1.0e-7) && + relative_eq!(reconstructed_m2, m, epsilon = 1.0e-7) + } + + fn pseudo_inverse(m: DMatrix) -> bool { + if m.nrows() == 0 || m.ncols() == 0 { + return true; + } + + let svd = SVD::new(m.clone()).unwrap(); + let im = svd.pseudo_inverse(1.0e-7); + + if m.nrows() <= m.ncols() { + return (&m * &im).is_identity(1.0e-7) + } + + if m.nrows() >= m.ncols() { + return (im * m).is_identity(1.0e-7) + } + + return true; + } + + fn pseudo_inverse_static(m: Matrix3x4) -> bool { + let svd = SVD::new(m).unwrap(); + let im = svd.pseudo_inverse(1.0e-7); + + (m * im).is_identity(1.0e-7) + } +} diff --git a/nalgebra-lapack/tests/linalg/symmetric_eigen.rs b/nalgebra-lapack/tests/linalg/symmetric_eigen.rs new file mode 100644 index 00000000..a1ebdfa2 --- /dev/null +++ b/nalgebra-lapack/tests/linalg/symmetric_eigen.rs @@ -0,0 +1,20 @@ +use std::cmp; + +use nl::SymmetricEigen; +use na::{DMatrix, Matrix4}; + +quickcheck!{ + fn symmetric_eigen(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let m = DMatrix::::new_random(n, n); + let eig = SymmetricEigen::new(m.clone()); + let recomp = eig.recompose(); + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } + + fn symmetric_eigen_static(m: Matrix4) -> bool { + let eig = SymmetricEigen::new(m); + let recomp = eig.recompose(); + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } +} diff --git a/src/core/alias.rs b/src/core/alias.rs index df296257..8fa29e5e 100644 --- a/src/core/alias.rs +++ b/src/core/alias.rs @@ -1,7 +1,7 @@ use core::Matrix; use core::dimension::{Dynamic, U1, U2, U3, U4, U5, U6}; -use core::matrix_array::MatrixArray; use core::matrix_vec::MatrixVec; +use core::storage::Owned; /* * @@ -10,14 +10,18 @@ use core::matrix_vec::MatrixVec; * * */ -/// A dynamically sized column-major matrix. -pub type DMatrix = Matrix>; +/// A staticaly sized column-major matrix with `R` rows and `C` columns. +#[deprecated(note = "This matrix name contains a typo. Use MatrixMN instead.")] +pub type MatrixNM = Matrix>; /// A staticaly sized column-major matrix with `R` rows and `C` columns. -pub type MatrixNM = Matrix>; +pub type MatrixMN = Matrix>; /// A staticaly sized column-major square matrix with `D` rows and columns. -pub type MatrixN = MatrixNM; +pub type MatrixN = MatrixMN; + +/// A dynamically sized column-major matrix. +pub type DMatrix = MatrixN; /// A stack-allocated, column-major, 1x1 square matrix. pub type Matrix1 = MatrixN; @@ -33,75 +37,75 @@ pub type Matrix5 = MatrixN; pub type Matrix6 = MatrixN; /// A stack-allocated, column-major, 1x2 square matrix. -pub type Matrix1x2 = MatrixNM; +pub type Matrix1x2 = MatrixMN; /// A stack-allocated, column-major, 1x3 square matrix. -pub type Matrix1x3 = MatrixNM; +pub type Matrix1x3 = MatrixMN; /// A stack-allocated, column-major, 1x4 square matrix. -pub type Matrix1x4 = MatrixNM; +pub type Matrix1x4 = MatrixMN; /// A stack-allocated, column-major, 1x5 square matrix. -pub type Matrix1x5 = MatrixNM; +pub type Matrix1x5 = MatrixMN; /// A stack-allocated, column-major, 1x6 square matrix. -pub type Matrix1x6 = MatrixNM; +pub type Matrix1x6 = MatrixMN; /// A stack-allocated, column-major, 2x3 square matrix. -pub type Matrix2x3 = MatrixNM; +pub type Matrix2x3 = MatrixMN; /// A stack-allocated, column-major, 2x4 square matrix. -pub type Matrix2x4 = MatrixNM; +pub type Matrix2x4 = MatrixMN; /// A stack-allocated, column-major, 2x5 square matrix. -pub type Matrix2x5 = MatrixNM; +pub type Matrix2x5 = MatrixMN; /// A stack-allocated, column-major, 2x6 square matrix. -pub type Matrix2x6 = MatrixNM; +pub type Matrix2x6 = MatrixMN; /// A stack-allocated, column-major, 3x4 square matrix. -pub type Matrix3x4 = MatrixNM; +pub type Matrix3x4 = MatrixMN; /// A stack-allocated, column-major, 3x5 square matrix. -pub type Matrix3x5 = MatrixNM; +pub type Matrix3x5 = MatrixMN; /// A stack-allocated, column-major, 3x6 square matrix. -pub type Matrix3x6 = MatrixNM; +pub type Matrix3x6 = MatrixMN; /// A stack-allocated, column-major, 4x5 square matrix. -pub type Matrix4x5 = MatrixNM; +pub type Matrix4x5 = MatrixMN; /// A stack-allocated, column-major, 4x6 square matrix. -pub type Matrix4x6 = MatrixNM; +pub type Matrix4x6 = MatrixMN; /// A stack-allocated, column-major, 5x6 square matrix. -pub type Matrix5x6 = MatrixNM; +pub type Matrix5x6 = MatrixMN; /// A stack-allocated, column-major, 2x1 square matrix. -pub type Matrix2x1 = MatrixNM; +pub type Matrix2x1 = MatrixMN; /// A stack-allocated, column-major, 3x1 square matrix. -pub type Matrix3x1 = MatrixNM; +pub type Matrix3x1 = MatrixMN; /// A stack-allocated, column-major, 4x1 square matrix. -pub type Matrix4x1 = MatrixNM; +pub type Matrix4x1 = MatrixMN; /// A stack-allocated, column-major, 5x1 square matrix. -pub type Matrix5x1 = MatrixNM; +pub type Matrix5x1 = MatrixMN; /// A stack-allocated, column-major, 6x1 square matrix. -pub type Matrix6x1 = MatrixNM; +pub type Matrix6x1 = MatrixMN; /// A stack-allocated, column-major, 3x2 square matrix. -pub type Matrix3x2 = MatrixNM; +pub type Matrix3x2 = MatrixMN; /// A stack-allocated, column-major, 4x2 square matrix. -pub type Matrix4x2 = MatrixNM; +pub type Matrix4x2 = MatrixMN; /// A stack-allocated, column-major, 5x2 square matrix. -pub type Matrix5x2 = MatrixNM; +pub type Matrix5x2 = MatrixMN; /// A stack-allocated, column-major, 6x2 square matrix. -pub type Matrix6x2 = MatrixNM; +pub type Matrix6x2 = MatrixMN; /// A stack-allocated, column-major, 4x3 square matrix. -pub type Matrix4x3 = MatrixNM; +pub type Matrix4x3 = MatrixMN; /// A stack-allocated, column-major, 5x3 square matrix. -pub type Matrix5x3 = MatrixNM; +pub type Matrix5x3 = MatrixMN; /// A stack-allocated, column-major, 6x3 square matrix. -pub type Matrix6x3 = MatrixNM; +pub type Matrix6x3 = MatrixMN; /// A stack-allocated, column-major, 5x4 square matrix. -pub type Matrix5x4 = MatrixNM; +pub type Matrix5x4 = MatrixMN; /// A stack-allocated, column-major, 6x4 square matrix. -pub type Matrix6x4 = MatrixNM; +pub type Matrix6x4 = MatrixMN; /// A stack-allocated, column-major, 6x5 square matrix. -pub type Matrix6x5 = MatrixNM; +pub type Matrix6x5 = MatrixMN; /* @@ -115,7 +119,7 @@ pub type Matrix6x5 = MatrixNM; pub type DVector = Matrix>; /// A statically sized D-dimensional column vector. -pub type VectorN = MatrixNM; +pub type VectorN = MatrixMN; /// A stack-allocated, 1-dimensional column vector. pub type Vector1 = VectorN; @@ -142,7 +146,7 @@ pub type Vector6 = VectorN; pub type RowDVector = Matrix>; /// A statically sized D-dimensional row vector. -pub type RowVectorN = MatrixNM; +pub type RowVectorN = MatrixMN; /// A stack-allocated, 1-dimensional row vector. pub type RowVector1 = RowVectorN; diff --git a/src/core/allocator.rs b/src/core/allocator.rs index d0c0a416..13d2de3c 100644 --- a/src/core/allocator.rs +++ b/src/core/allocator.rs @@ -2,10 +2,10 @@ use std::any::Any; -use core::Scalar; +use core::{DefaultAllocator, Scalar}; use core::constraint::{SameNumberOfRows, SameNumberOfColumns, ShapeConstraint}; use core::dimension::{Dim, U1}; -use core::storage::{Storage, OwnedStorage}; +use core::storage::ContiguousStorageMut; /// A matrix allocator of a memory buffer that may contain `R::to_usize() * C::to_usize()` /// elements of type `N`. @@ -16,9 +16,9 @@ use core::storage::{Storage, OwnedStorage}; /// /// Every allocator must be both static and dynamic. Though not all implementations may share the /// same `Buffer` type. -pub trait Allocator: Any + Sized { +pub trait Allocator: Any + Sized { /// The type of buffer this allocator can instanciate. - type Buffer: OwnedStorage; + type Buffer: ContiguousStorageMut + Clone; /// Allocates a buffer with the given number of rows and columns without initializing its content. unsafe fn allocate_uninitialized(nrows: R, ncols: C) -> Self::Buffer; @@ -27,15 +27,20 @@ pub trait Allocator: Any + Sized { fn allocate_from_iterator>(nrows: R, ncols: C, iter: I) -> Self::Buffer; } -/// A matrix data allocator dedicated to the given owned matrix storage. -pub trait OwnedAllocator>: - Allocator { -} - -impl OwnedAllocator for T - where N: Scalar, R: Dim, C: Dim, - T: Allocator, - S: OwnedStorage { +/// A matrix reallocator. Changes the size of the memory buffer that initially contains (RFrom × +/// CFrom) elements to a smaller or larger size (RTo, CTo). +pub trait Reallocator: + Allocator + Allocator { + /// Reallocates a buffer of shape `(RTo, CTo)`, possibly reusing a previously allocated buffer + /// `buf`. Data stored by `buf` are linearly copied to the output: + /// + /// * The copy is performed as if both were just arrays (without a matrix structure). + /// * If `buf` is larger than the output size, then extra elements of `buf` are truncated. + /// * If `buf` is smaller than the output size, then extra elements of the output are left + /// uninitialized. + unsafe fn reallocate_copy(nrows: RTo, ncols: CTo, + buf: >::Buffer) + -> >::Buffer; } /// The number of rows of the result of a componentwise operation on two matrices. @@ -45,45 +50,36 @@ pub type SameShapeR = >::Rep pub type SameShapeC = >::Representative; // FIXME: Bad name. -/// Restricts the given number of rows and columns to be respectively the same. Can only be used -/// when `Self = SA::Alloc`. -pub trait SameShapeAllocator: +/// Restricts the given number of rows and columns to be respectively the same. +pub trait SameShapeAllocator: Allocator + Allocator, SameShapeC> where R1: Dim, R2: Dim, C1: Dim, C2: Dim, N: Scalar, - SA: Storage, ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { } -impl SameShapeAllocator for SA::Alloc +impl SameShapeAllocator for DefaultAllocator where R1: Dim, R2: Dim, C1: Dim, C2: Dim, N: Scalar, - SA: Storage, - SA::Alloc: - Allocator + - Allocator, SameShapeC>, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + DefaultAllocator: Allocator + Allocator, SameShapeC>, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { } // XXX: Bad name. -/// Restricts the given number of rows to be equal. Can only be used when `Self = SA::Alloc`. -pub trait SameShapeColumnVectorAllocator: - Allocator + - Allocator, U1> + - SameShapeAllocator +/// Restricts the given number of rows to be equal. +pub trait SameShapeVectorAllocator: + Allocator + + Allocator> + + SameShapeAllocator where R1: Dim, R2: Dim, N: Scalar, - SA: Storage, ShapeConstraint: SameNumberOfRows { } -impl SameShapeColumnVectorAllocator for SA::Alloc +impl SameShapeVectorAllocator for DefaultAllocator where R1: Dim, R2: Dim, N: Scalar, - SA: Storage, - SA::Alloc: - Allocator + - Allocator, U1>, - ShapeConstraint: SameNumberOfRows { + DefaultAllocator: Allocator + Allocator>, + ShapeConstraint: SameNumberOfRows { } diff --git a/src/core/blas.rs b/src/core/blas.rs new file mode 100644 index 00000000..19eeda7a --- /dev/null +++ b/src/core/blas.rs @@ -0,0 +1,458 @@ +use std::mem; +use num::{Zero, One, Signed}; +use matrixmultiply; +use alga::general::{ClosedMul, ClosedAdd}; + +use core::{Scalar, Matrix, Vector}; +use core::dimension::{Dim, U1, U2, U3, U4, Dynamic}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable, DimEq}; +use core::storage::{Storage, StorageMut}; + + + +impl> Vector { + /// Computes the index of the vector component with the largest absolute value. + #[inline] + pub fn iamax(&self) -> usize { + assert!(!self.is_empty(), "The input vector must not be empty."); + + let mut the_max = unsafe { self.vget_unchecked(0).abs() }; + let mut the_i = 0; + + for i in 1 .. self.nrows() { + let val = unsafe { self.vget_unchecked(i).abs() }; + + if val > the_max { + the_max = val; + the_i = i; + } + } + + the_i + } +} + +impl> Matrix { + /// Computes the index of the matrix component with the largest absolute value. + #[inline] + pub fn iamax_full(&self) -> (usize, usize) { + assert!(!self.is_empty(), "The input matrix must not be empty."); + + let mut the_max = unsafe { self.get_unchecked(0, 0).abs() }; + let mut the_ij = (0, 0); + + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + let val = unsafe { self.get_unchecked(i, j).abs() }; + + if val > the_max { + the_max = val; + the_ij = (i, j); + } + } + } + + the_ij + } +} + +impl> Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul { + /// The dot product between two matrices (seen as vectors). + /// + /// Note that this is **not** the matrix multiplication as in, e.g., numpy. For matrix + /// multiplication, use one of: `.gemm`, `mul_to`, `.mul`, `*`. + #[inline] + pub fn dot(&self, rhs: &Matrix) -> N + where SB: Storage, + ShapeConstraint: DimEq + DimEq { + assert!(self.nrows() == rhs.nrows(), "Dot product dimensions mismatch."); + + + // So we do some special cases for common fixed-size vectors of dimension lower than 8 + // because the `for` loop bellow won't be very efficient on those. + if (R::is::() || R2::is::()) && + (C::is::() || C2::is::()) { + unsafe { + let a = *self.get_unchecked(0, 0) * *rhs.get_unchecked(0, 0); + let b = *self.get_unchecked(1, 0) * *rhs.get_unchecked(1, 0); + + return a + b; + } + } + if (R::is::() || R2::is::()) && + (C::is::() || C2::is::()) { + unsafe { + let a = *self.get_unchecked(0, 0) * *rhs.get_unchecked(0, 0); + let b = *self.get_unchecked(1, 0) * *rhs.get_unchecked(1, 0); + let c = *self.get_unchecked(2, 0) * *rhs.get_unchecked(2, 0); + + return a + b + c; + } + } + if (R::is::() || R2::is::()) && + (C::is::() || C2::is::()) { + unsafe { + let mut a = *self.get_unchecked(0, 0) * *rhs.get_unchecked(0, 0); + let mut b = *self.get_unchecked(1, 0) * *rhs.get_unchecked(1, 0); + let c = *self.get_unchecked(2, 0) * *rhs.get_unchecked(2, 0); + let d = *self.get_unchecked(3, 0) * *rhs.get_unchecked(3, 0); + + a += c; + b += d; + + return a + b; + } + } + + + // All this is inspired from the "unrolled version" discussed in: + // http://blog.theincredibleholk.org/blog/2012/12/10/optimizing-dot-product/ + // + // And this comment from bluss: + // https://users.rust-lang.org/t/how-to-zip-two-slices-efficiently/2048/12 + let mut res = N::zero(); + + // We have to define them outside of the loop (and not inside at first assignment) + // otherwize vectorization won't kick in for some reason. + let mut acc0; + let mut acc1; + let mut acc2; + let mut acc3; + let mut acc4; + let mut acc5; + let mut acc6; + let mut acc7; + + for j in 0 .. self.ncols() { + let mut i = 0; + + acc0 = N::zero(); + acc1 = N::zero(); + acc2 = N::zero(); + acc3 = N::zero(); + acc4 = N::zero(); + acc5 = N::zero(); + acc6 = N::zero(); + acc7 = N::zero(); + + while self.nrows() - i >= 8 { + acc0 += unsafe { *self.get_unchecked(i + 0, j) * *rhs.get_unchecked(i + 0, j) }; + acc1 += unsafe { *self.get_unchecked(i + 1, j) * *rhs.get_unchecked(i + 1, j) }; + acc2 += unsafe { *self.get_unchecked(i + 2, j) * *rhs.get_unchecked(i + 2, j) }; + acc3 += unsafe { *self.get_unchecked(i + 3, j) * *rhs.get_unchecked(i + 3, j) }; + acc4 += unsafe { *self.get_unchecked(i + 4, j) * *rhs.get_unchecked(i + 4, j) }; + acc5 += unsafe { *self.get_unchecked(i + 5, j) * *rhs.get_unchecked(i + 5, j) }; + acc6 += unsafe { *self.get_unchecked(i + 6, j) * *rhs.get_unchecked(i + 6, j) }; + acc7 += unsafe { *self.get_unchecked(i + 7, j) * *rhs.get_unchecked(i + 7, j) }; + i += 8; + } + + res += acc0 + acc4; + res += acc1 + acc5; + res += acc2 + acc6; + res += acc3 + acc7; + + for k in i .. self.nrows() { + res += unsafe { *self.get_unchecked(k, j) * *rhs.get_unchecked(k, j) } + } + } + + res + } + + /// The dot product between the transpose of `self` and `rhs`. + #[inline] + pub fn tr_dot(&self, rhs: &Matrix) -> N + where SB: Storage, + ShapeConstraint: DimEq + DimEq { + let (nrows, ncols) = self.shape(); + assert!((ncols, nrows) == rhs.shape(), "Transposed dot product dimension mismatch."); + + let mut res = N::zero(); + + for j in 0 .. self.nrows() { + for i in 0 .. self.ncols() { + res += unsafe { *self.get_unchecked(j, i) * *rhs.get_unchecked(i, j) } + } + } + + res + } +} + +fn array_axpy(y: &mut [N], a: N, x: &[N], beta: N, stride1: usize, stride2: usize, len: usize) + where N: Scalar + Zero + ClosedAdd + ClosedMul { + for i in 0 .. len { + unsafe { + let y = y.get_unchecked_mut(i * stride1); + *y = a * *x.get_unchecked(i * stride2) + beta * *y; + } + } +} + +fn array_ax(y: &mut [N], a: N, x: &[N], stride1: usize, stride2: usize, len: usize) + where N: Scalar + Zero + ClosedAdd + ClosedMul { + for i in 0 .. len { + unsafe { + *y.get_unchecked_mut(i * stride1) = a * *x.get_unchecked(i * stride2); + } + } +} + +impl Vector + where N: Scalar + Zero + ClosedAdd + ClosedMul, + S: StorageMut { + /// Computes `self = a * x + b * self`. + /// + /// If be is zero, `self` is never read from. + #[inline] + pub fn axpy(&mut self, a: N, x: &Vector, b: N) + where SB: Storage, + ShapeConstraint: DimEq { + + assert_eq!(self.nrows(), x.nrows(), "Axpy: mismatched vector shapes."); + + let rstride1 = self.strides().0; + let rstride2 = x.strides().0; + + let y = self.data.as_mut_slice(); + let x = x.data.as_slice(); + + if !b.is_zero() { + array_axpy(y, a, x, b, rstride1, rstride2, x.len()); + } + else { + array_ax(y, a, x, rstride1, rstride2, x.len()); + } + } + + /// Computes `self = alpha * a * x + beta * self`, where `a` is a matrix, `x` a vector, and + /// `alpha, beta` two scalars. + /// + /// If `beta` is zero, `self` is never read. + #[inline] + pub fn gemv(&mut self, + alpha: N, + a: &Matrix, + x: &Vector, + beta: N) + where N: One, + SB: Storage, + SC: Storage, + ShapeConstraint: DimEq + + AreMultipliable { + let dim1 = self.nrows(); + let (nrows2, ncols2) = a.shape(); + let dim3 = x.nrows(); + + assert!(ncols2 == dim3 && dim1 == nrows2, "Gemv: dimensions mismatch."); + + if ncols2 == 0 { + return; + } + + // FIXME: avoid bound checks. + let col2 = a.column(0); + let val = unsafe { *x.vget_unchecked(0) }; + self.axpy(alpha * val, &col2, beta); + + for j in 1 .. ncols2 { + let col2 = a.column(j); + let val = unsafe { *x.vget_unchecked(j) }; + + self.axpy(alpha * val, &col2, N::one()); + } + } + + /// Computes `self = alpha * a * x + beta * self`, where `a` is a **symmetric** matrix, `x` a + /// vector, and `alpha, beta` two scalars. + /// + /// If `beta` is zero, `self` is never read. If `self` is read, only its lower-triangular part + /// (including the diagonal) is actually read. + #[inline] + pub fn gemv_symm(&mut self, + alpha: N, + a: &Matrix, + x: &Vector, + beta: N) + where N: One, + SB: Storage, + SC: Storage, + ShapeConstraint: DimEq + + AreMultipliable { + let dim1 = self.nrows(); + let dim2 = a.nrows(); + let dim3 = x.nrows(); + + assert!(a.is_square(), "Syetric gemv: the input matrix must be square."); + assert!(dim2 == dim3 && dim1 == dim2, "Symmetric gemv: dimensions mismatch."); + + if dim2 == 0 { + return; + } + + // FIXME: avoid bound checks. + let col2 = a.column(0); + let val = unsafe { *x.vget_unchecked(0) }; + self.axpy(alpha * val, &col2, beta); + self[0] += alpha * x.rows_range(1 ..).dot(&a.slice_range(1 .., 0)); + + for j in 1 .. dim2 { + let col2 = a.column(j); + let dot = x.rows_range(j ..).dot(&col2.rows_range(j ..)); + + let val; + unsafe { + val = *x.vget_unchecked(j); + *self.vget_unchecked_mut(j) += alpha * dot; + } + self.rows_range_mut(j + 1 ..).axpy(alpha * val, &col2.rows_range(j + 1 ..), N::one()); + } + } +} + +impl> Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul { + + /// Computes `self = alpha * x * y.transpose() + beta * self`. + /// + /// If `beta` is zero, `self` is never read. + #[inline] + pub fn ger(&mut self, alpha: N, x: &Vector, y: &Vector, beta: N) + where N: One, + SB: Storage, + SC: Storage, + ShapeConstraint: DimEq + DimEq { + let (nrows1, ncols1) = self.shape(); + let dim2 = x.nrows(); + let dim3 = y.nrows(); + + assert!(nrows1 == dim2 && ncols1 == dim3, "ger: dimensions mismatch."); + + for j in 0 .. ncols1 { + // FIXME: avoid bound checks. + let val = unsafe { *y.vget_unchecked(j) }; + self.column_mut(j).axpy(alpha * val, x, beta); + } + } + + /// Computes `self = alpha * a * b + beta * self`, where `a, b, self` are matrices. + /// `alpha` and `beta` are scalar. + /// + /// If `beta` is zero, `self` is never read. + #[inline] + pub fn gemm(&mut self, + alpha: N, + a: &Matrix, + b: &Matrix, + beta: N) + where N: One, + SB: Storage, + SC: Storage, + ShapeConstraint: SameNumberOfRows + + SameNumberOfColumns + + AreMultipliable { + let (nrows1, ncols1) = self.shape(); + let (nrows2, ncols2) = a.shape(); + let (nrows3, ncols3) = b.shape(); + + assert_eq!(ncols2, nrows3, "gemm: dimensions mismatch for multiplication."); + assert_eq!((nrows1, ncols1), (nrows2, ncols3), "gemm: dimensions mismatch for addition."); + + // We assume large matrices will be Dynamic but small matrices static. + // We could use matrixmultiply for large statically-sized matrices but the performance + // threshold to activate it would be different from SMALL_DIM because our code optimizes + // better for statically-sized matrices. + let is_dynamic = R1::is::() || C1::is::() || + R2::is::() || C2::is::() || + R3::is::() || C3::is::(); + // Thershold determined ampirically. + const SMALL_DIM: usize = 5; + + if is_dynamic && + nrows1 > SMALL_DIM && ncols1 > SMALL_DIM && + nrows2 > SMALL_DIM && ncols2 > SMALL_DIM { + if N::is::() { + let (rsa, csa) = a.strides(); + let (rsb, csb) = b.strides(); + let (rsc, csc) = self.strides(); + + unsafe { + matrixmultiply::sgemm( + nrows2, + ncols2, + ncols3, + mem::transmute_copy(&alpha), + a.data.ptr() as *const f32, + rsa as isize, csa as isize, + b.data.ptr() as *const f32, + rsb as isize, csb as isize, + mem::transmute_copy(&beta), + self.data.ptr_mut() as *mut f32, + rsc as isize, csc as isize); + } + } + else if N::is::() { + let (rsa, csa) = a.strides(); + let (rsb, csb) = b.strides(); + let (rsc, csc) = self.strides(); + + unsafe { + matrixmultiply::dgemm( + nrows2, + ncols2, + ncols3, + mem::transmute_copy(&alpha), + a.data.ptr() as *const f64, + rsa as isize, csa as isize, + b.data.ptr() as *const f64, + rsb as isize, csb as isize, + mem::transmute_copy(&beta), + self.data.ptr_mut() as *mut f64, + rsc as isize, csc as isize); + } + } + } + else { + for j1 in 0 .. ncols1 { + // FIXME: avoid bound checks. + self.column_mut(j1).gemv(alpha, a, &b.column(j1), beta); + } + } + } +} + + +impl> Matrix + where N: Scalar + Zero + ClosedAdd + ClosedMul { + /// Computes `self = alpha * x * y.transpose() + beta * self`, where `self` is a **symmetric** + /// matrix. + /// + /// If `beta` is zero, `self` is never read. The result is symmetric. Only the lower-triangular + /// (including the diagonal) part of `self` is read/written. + #[inline] + pub fn ger_symm(&mut self, + alpha: N, + x: &Vector, + y: &Vector, + beta: N) + where N: One, + SB: Storage, + SC: Storage, + ShapeConstraint: DimEq + DimEq { + let dim1 = self.nrows(); + let dim2 = x.nrows(); + let dim3 = y.nrows(); + + assert!(self.is_square(), "Symmetric ger: the input matrix must be square."); + assert!(dim1 == dim2 && dim1 == dim3, "ger: dimensions mismatch."); + + for j in 0 .. dim1 { + // FIXME: avoid bound checks. + let val = unsafe { *y.vget_unchecked(j) }; + let subdim = Dynamic::new(dim1 - j); + self.generic_slice_mut((j, j), (subdim, U1)).axpy(alpha * val, &x.rows_range(j ..), beta); + } + } +} diff --git a/src/core/cg.rs b/src/core/cg.rs index e409d006..0ef8507b 100644 --- a/src/core/cg.rs +++ b/src/core/cg.rs @@ -7,20 +7,20 @@ use num::One; -use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, Unit}; -use core::dimension::{DimName, DimNameSub, DimNameDiff, U1, U2, U3, U4}; -use core::storage::{Storage, StorageMut, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; -use geometry::{PointBase, OrthographicBase, PerspectiveBase, IsometryBase, OwnedRotation, OwnedPoint}; +use core::{DefaultAllocator, Scalar, SquareMatrix, Vector, Unit, + VectorN, MatrixN, Vector3, Matrix3, Matrix4}; +use core::dimension::{DimName, DimNameSub, DimNameDiff, U1}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; +use geometry::{Point, Isometry, Point3, Rotation2, Rotation3, Orthographic3, Perspective3, IsometryMatrix3}; use alga::general::{Real, Field}; use alga::linear::Transformation; -impl SquareMatrix +impl MatrixN where N: Scalar + Field, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { /// Creates a new homogeneous matrix that applies the same scaling factor on each dimension. #[inline] pub fn new_scaling(scaling: N) -> Self { @@ -32,9 +32,9 @@ impl SquareMatrix /// Creates a new homogeneous matrix that applies a distinct scaling factor for each dimension. #[inline] - pub fn new_nonuniform_scaling(scaling: &ColumnVector, SB>) -> Self + pub fn new_nonuniform_scaling(scaling: &Vector, SB>) -> Self where D: DimNameSub, - SB: Storage, U1> { + SB: Storage> { let mut res = Self::one(); for i in 0 .. scaling.len() { res[(i, i)] = scaling[i]; @@ -45,10 +45,9 @@ impl SquareMatrix /// Creates a new homogeneous matrix that applies a pure translation. #[inline] - pub fn new_translation(translation: &ColumnVector, SB>) -> Self + pub fn new_translation(translation: &Vector, SB>) -> Self where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator, U1> { + SB: Storage> { let mut res = Self::one(); res.fixed_slice_mut::, U1>(0, D::dim() - 1).copy_from(translation); @@ -56,44 +55,30 @@ impl SquareMatrix } } -impl SquareMatrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Matrix3 { /// Builds a 2 dimensional homogeneous rotation matrix from an angle in radian. #[inline] - pub fn new_rotation(angle: N) -> Self - where S::Alloc: Allocator { - OwnedRotation::::new(angle).to_homogeneous() + pub fn new_rotation(angle: N) -> Self { + Rotation2::new(angle).to_homogeneous() } } -impl SquareMatrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { - +impl Matrix4 { /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). /// /// Returns the identity matrix if the given argument is zero. #[inline] - pub fn new_rotation(axisangle: ColumnVector) -> Self - where SB: Storage, - S::Alloc: Allocator { - OwnedRotation::::new(axisangle).to_homogeneous() + pub fn new_rotation(axisangle: Vector3) -> Self { + Rotation3::new(axisangle).to_homogeneous() } /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). /// /// Returns the identity matrix if the given argument is zero. #[inline] - pub fn new_rotation_wrt_point(axisangle: ColumnVector, pt: OwnedPoint) -> Self - where SB: Storage, - S::Alloc: Allocator + - Allocator + - Allocator { - let rot = OwnedRotation::::from_scaled_axis(axisangle); - IsometryBase::rotation_wrt_point(rot, pt).to_homogeneous() + pub fn new_rotation_wrt_point(axisangle: Vector3, pt: Point3) -> Self { + let rot = Rotation3::from_scaled_axis(axisangle); + Isometry::rotation_wrt_point(rot, pt).to_homogeneous() } /// Builds a 3D homogeneous rotation matrix from an axis and an angle (multiplied together). @@ -101,37 +86,32 @@ impl SquareMatrix /// Returns the identity matrix if the given argument is zero. /// This is identical to `Self::new_rotation`. #[inline] - pub fn from_scaled_axis(axisangle: ColumnVector) -> Self - where SB: Storage, - S::Alloc: Allocator { - OwnedRotation::::from_scaled_axis(axisangle).to_homogeneous() + pub fn from_scaled_axis(axisangle: Vector3) -> Self { + Rotation3::from_scaled_axis(axisangle).to_homogeneous() } /// Creates a new rotation from Euler angles. /// /// The primitive rotations are applied in order: 1 roll − 2 pitch − 3 yaw. - pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Self - where S::Alloc: Allocator { - OwnedRotation::::from_euler_angles(roll, pitch, yaw).to_homogeneous() + pub fn from_euler_angles(roll: N, pitch: N, yaw: N) -> Self { + Rotation3::from_euler_angles(roll, pitch, yaw).to_homogeneous() } /// Builds a 3D homogeneous rotation matrix from an axis and a rotation angle. - pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self - where SB: Storage, - S::Alloc: Allocator { - OwnedRotation::::from_axis_angle(axis, angle).to_homogeneous() + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self { + Rotation3::from_axis_angle(axis, angle).to_homogeneous() } /// Creates a new homogeneous matrix for an orthographic projection. #[inline] pub fn new_orthographic(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Self { - OrthographicBase::new(left, right, bottom, top, znear, zfar).unwrap() + Orthographic3::new(left, right, bottom, top, znear, zfar).unwrap() } /// Creates a new homogeneous matrix for a perspective projection. #[inline] pub fn new_perspective(aspect: N, fovy: N, znear: N, zfar: N) -> Self { - PerspectiveBase::new(aspect, fovy, znear, zfar).unwrap() + Perspective3::new(aspect, fovy, znear, zfar).unwrap() } /// Creates an isometry that corresponds to the local frame of an observer standing at the @@ -140,57 +120,30 @@ impl SquareMatrix /// It maps the view direction `target - eye` to the positive `z` axis and the origin to the /// `eye`. #[inline] - pub fn new_observer_frame(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) - -> Self - where SB: OwnedStorage, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { - IsometryBase::> - ::new_observer_frame(eye, target, up).to_homogeneous() + pub fn new_observer_frame(eye: &Point3, target: &Point3, up: &Vector3) -> Self { + IsometryMatrix3::new_observer_frame(eye, target, up).to_homogeneous() } /// Builds a right-handed look-at view matrix. #[inline] - pub fn look_at_rh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) - -> Self - where SB: OwnedStorage, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { - IsometryBase::> - ::look_at_rh(eye, target, up).to_homogeneous() + pub fn look_at_rh(eye: &Point3, target: &Point3, up: &Vector3) -> Self { + IsometryMatrix3::look_at_rh(eye, target, up).to_homogeneous() } /// Builds a left-handed look-at view matrix. #[inline] - pub fn look_at_lh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) - -> Self - where SB: OwnedStorage, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { - IsometryBase::> - ::look_at_lh(eye, target, up).to_homogeneous() + pub fn look_at_lh(eye: &Point3, target: &Point3, up: &Vector3) -> Self { + IsometryMatrix3::look_at_lh(eye, target, up).to_homogeneous() } } -impl SquareMatrix - where N: Scalar + Field, - S: Storage { - +impl> SquareMatrix { /// Computes the transformation equal to `self` followed by an uniform scaling factor. #[inline] - pub fn append_scaling(&self, scaling: N) -> OwnedSquareMatrix + pub fn append_scaling(&self, scaling: N) -> MatrixN where D: DimNameSub, - S::Alloc: Allocator, D> { + DefaultAllocator: Allocator { let mut res = self.clone_owned(); res.append_scaling_mut(scaling); res @@ -198,9 +151,9 @@ impl SquareMatrix /// Computes the transformation equal to an uniform scaling factor followed by `self`. #[inline] - pub fn prepend_scaling(&self, scaling: N) -> OwnedSquareMatrix + pub fn prepend_scaling(&self, scaling: N) -> MatrixN where D: DimNameSub, - S::Alloc: Allocator> { + DefaultAllocator: Allocator { let mut res = self.clone_owned(); res.prepend_scaling_mut(scaling); res @@ -208,11 +161,10 @@ impl SquareMatrix /// Computes the transformation equal to `self` followed by a non-uniform scaling factor. #[inline] - pub fn append_nonuniform_scaling(&self, scaling: &ColumnVector, SB>) - -> OwnedSquareMatrix - where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator { + pub fn append_nonuniform_scaling(&self, scaling: &Vector, SB>) -> MatrixN + where D: DimNameSub, + SB: Storage>, + DefaultAllocator: Allocator { let mut res = self.clone_owned(); res.append_nonuniform_scaling_mut(scaling); res @@ -220,11 +172,10 @@ impl SquareMatrix /// Computes the transformation equal to a non-uniform scaling factor followed by `self`. #[inline] - pub fn prepend_nonuniform_scaling(&self, scaling: &ColumnVector, SB>) - -> OwnedSquareMatrix - where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator { + pub fn prepend_nonuniform_scaling(&self, scaling: &Vector, SB>) -> MatrixN + where D: DimNameSub, + SB: Storage>, + DefaultAllocator: Allocator { let mut res = self.clone_owned(); res.prepend_nonuniform_scaling_mut(scaling); res @@ -232,11 +183,10 @@ impl SquareMatrix /// Computes the transformation equal to `self` followed by a translation. #[inline] - pub fn append_translation(&self, shift: &ColumnVector, SB>) - -> OwnedSquareMatrix + pub fn append_translation(&self, shift: &Vector, SB>) -> MatrixN where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator, U1> { + SB: Storage>, + DefaultAllocator: Allocator { let mut res = self.clone_owned(); res.append_translation_mut(shift); res @@ -244,28 +194,23 @@ impl SquareMatrix /// Computes the transformation equal to a translation followed by `self`. #[inline] - pub fn prepend_translation(&self, shift: &ColumnVector, SB>) - -> OwnedSquareMatrix + pub fn prepend_translation(&self, shift: &Vector, SB>) -> MatrixN where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator, U1> + - Allocator, DimNameDiff> + - Allocator> { + SB: Storage>, + DefaultAllocator: Allocator + + Allocator> { let mut res = self.clone_owned(); res.prepend_translation_mut(shift); res } } -impl SquareMatrix - where N: Scalar + Field, - S: StorageMut { +impl> SquareMatrix { /// Computes in-place the transformation equal to `self` followed by an uniform scaling factor. #[inline] pub fn append_scaling_mut(&mut self, scaling: N) - where D: DimNameSub, - S::Alloc: Allocator, D> { + where D: DimNameSub { let mut to_scale = self.fixed_rows_mut::>(0); to_scale *= scaling; } @@ -273,18 +218,16 @@ impl SquareMatrix /// Computes in-place the transformation equal to an uniform scaling factor followed by `self`. #[inline] pub fn prepend_scaling_mut(&mut self, scaling: N) - where D: DimNameSub, - S::Alloc: Allocator> { + where D: DimNameSub { let mut to_scale = self.fixed_columns_mut::>(0); to_scale *= scaling; } /// Computes in-place the transformation equal to `self` followed by a non-uniform scaling factor. #[inline] - pub fn append_nonuniform_scaling_mut(&mut self, scaling: &ColumnVector, SB>) + pub fn append_nonuniform_scaling_mut(&mut self, scaling: &Vector, SB>) where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator { + SB: Storage> { for i in 0 .. scaling.len() { let mut to_scale = self.fixed_rows_mut::(i); to_scale *= scaling[i]; @@ -293,10 +236,9 @@ impl SquareMatrix /// Computes in-place the transformation equal to a non-uniform scaling factor followed by `self`. #[inline] - pub fn prepend_nonuniform_scaling_mut(&mut self, scaling: &ColumnVector, SB>) + pub fn prepend_nonuniform_scaling_mut(&mut self, scaling: &Vector, SB>) where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator { + SB: Storage> { for i in 0 .. scaling.len() { let mut to_scale = self.fixed_columns_mut::(i); to_scale *= scaling[i]; @@ -305,10 +247,9 @@ impl SquareMatrix /// Computes the transformation equal to `self` followed by a translation. #[inline] - pub fn append_translation_mut(&mut self, shift: &ColumnVector, SB>) + pub fn append_translation_mut(&mut self, shift: &Vector, SB>) where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator, U1> { + SB: Storage> { for i in 0 .. D::dim() { for j in 0 .. D::dim() - 1 { self[(j, i)] += shift[j] * self[(D::dim() - 1, i)]; @@ -318,12 +259,10 @@ impl SquareMatrix /// Computes the transformation equal to a translation followed by `self`. #[inline] - pub fn prepend_translation_mut(&mut self, shift: &ColumnVector, SB>) + pub fn prepend_translation_mut(&mut self, shift: &Vector, SB>) where D: DimNameSub, - SB: Storage, U1>, - S::Alloc: Allocator, U1> + - Allocator, DimNameDiff> + - Allocator> { + SB: Storage>, + DefaultAllocator: Allocator> { let scale = self.fixed_slice::>(D::dim() - 1, 0).tr_dot(&shift); let post_translation = self.fixed_slice::, DimNameDiff>(0, 0) * shift; @@ -335,19 +274,12 @@ impl SquareMatrix } -impl Transformation, SB>> for SquareMatrix - where N: Real, - D: DimNameSub, - SA: OwnedStorage, - SB: OwnedStorage, U1, Alloc = SA::Alloc>, - SA::Alloc: OwnedAllocator + - Allocator, DimNameDiff> + - Allocator, U1> + - Allocator>, - SB::Alloc: OwnedAllocator, U1, SB> { +impl> Transformation>> for MatrixN + where DefaultAllocator: Allocator + + Allocator> + + Allocator, DimNameDiff> { #[inline] - fn transform_vector(&self, v: &ColumnVector, SB>) - -> ColumnVector, SB> { + fn transform_vector(&self, v: &VectorN>) -> VectorN> { let transform = self.fixed_slice::, DimNameDiff>(0, 0); let normalizer = self.fixed_slice::>(D::dim() - 1, 0); let n = normalizer.tr_dot(&v); @@ -360,8 +292,7 @@ impl Transformation, SB>> for Squa } #[inline] - fn transform_point(&self, pt: &PointBase, SB>) - -> PointBase, SB> { + fn transform_point(&self, pt: &Point>) -> Point> { let transform = self.fixed_slice::, DimNameDiff>(0, 0); let translation = self.fixed_slice::, U1>(0, D::dim() - 1); let normalizer = self.fixed_slice::>(D::dim() - 1, 0); diff --git a/src/core/componentwise.rs b/src/core/componentwise.rs index e8fa857b..65a4d1a9 100644 --- a/src/core/componentwise.rs +++ b/src/core/componentwise.rs @@ -4,21 +4,22 @@ use num::Signed; use alga::general::{ClosedMul, ClosedDiv}; -use core::{Scalar, Matrix, OwnedMatrix, MatrixSum}; +use core::{DefaultAllocator, Scalar, Matrix, MatrixMN, MatrixSum}; use core::dimension::Dim; use core::storage::{Storage, StorageMut}; -use core::allocator::SameShapeAllocator; +use core::allocator::{Allocator, SameShapeAllocator}; use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; /// The type of the result of a matrix componentwise operation. -pub type MatrixComponentOp = MatrixSum; +pub type MatrixComponentOp = MatrixSum; impl> Matrix { /// Computes the componentwise absolute value. #[inline] - pub fn abs(&self) -> OwnedMatrix - where N: Signed { + pub fn abs(&self) -> MatrixMN + where N: Signed, + DefaultAllocator: Allocator { let mut res = self.clone_owned(); for e in res.iter_mut() { @@ -32,48 +33,72 @@ impl> Matrix { } macro_rules! component_binop_impl( - ($($binop: ident, $binop_mut: ident, $Trait: ident . $binop_assign: ident, $desc:expr, $desc_mut:expr);* $(;)*) => {$( - impl> Matrix { + ($($binop: ident, $binop_mut: ident, $binop_assign: ident, $Trait: ident . $op_assign: ident, $desc:expr, $desc_mut:expr);* $(;)*) => {$( + impl> Matrix { #[doc = $desc] #[inline] - pub fn $binop(&self, rhs: &Matrix) -> MatrixComponentOp + pub fn $binop(&self, rhs: &Matrix) -> MatrixComponentOp where N: $Trait, R2: Dim, C2: Dim, SB: Storage, - S::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + assert_eq!(self.shape(), rhs.shape(), "Componentwise mul/div: mismatched matrix dimensions."); let mut res = self.clone_owned_sum(); - - for (res, rhs) in res.iter_mut().zip(rhs.iter()) { - res.$binop_assign(*rhs); + + for j in 0 .. res.ncols() { + for i in 0 .. res.nrows() { + unsafe { + res.get_unchecked_mut(i, j).$op_assign(*rhs.get_unchecked(i, j)); + } + } } - + res } } - - impl> Matrix { + + impl> Matrix { #[doc = $desc_mut] #[inline] + pub fn $binop_assign(&mut self, rhs: &Matrix) + where N: $Trait, + R2: Dim, + C2: Dim, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + assert_eq!(self.shape(), rhs.shape(), "Componentwise mul/div: mismatched matrix dimensions."); + + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + unsafe { + self.get_unchecked_mut(i, j).$op_assign(*rhs.get_unchecked(i, j)); + } + } + } + } + + #[doc = $desc_mut] + #[inline] + #[deprecated(note = "This is renamed using the `_assign` sufix instead of the `_mut` suffix.")] pub fn $binop_mut(&mut self, rhs: &Matrix) where N: $Trait, R2: Dim, C2: Dim, SB: Storage, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - for (me, rhs) in self.iter_mut().zip(rhs.iter()) { - me.$binop_assign(*rhs); - } + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + self.$binop_assign(rhs) } } )*} ); component_binop_impl!( - component_mul, component_mul_mut, ClosedMul.mul_assign, + component_mul, component_mul_mut, component_mul_assign, ClosedMul.mul_assign, "Componentwise matrix multiplication.", "Mutable, componentwise matrix multiplication."; - component_div, component_div_mut, ClosedDiv.div_assign, + component_div, component_div_mut, component_div_assign, ClosedDiv.div_assign, "Componentwise matrix division.", "Mutable, componentwise matrix division."; // FIXME: add other operators like bitshift, etc. ? ); diff --git a/src/core/constraint.rs b/src/core/constraint.rs index 0f017e5b..36867b18 100644 --- a/src/core/constraint.rs +++ b/src/core/constraint.rs @@ -6,8 +6,7 @@ use core::dimension::{Dim, DimName, Dynamic}; pub struct ShapeConstraint; /// Constraints `C1` and `R2` to be equivalent. -pub trait AreMultipliable { +pub trait AreMultipliable: DimEq { } @@ -15,11 +14,30 @@ impl AreMultipliable for Sha where ShapeConstraint: DimEq { } +/// Constraints `D1` and `D2` to be equivalent. +pub trait DimEq { + /// This is either equal to `D1` or `D2`, always choosing the one (if any) which is a type-level + /// constant. + type Representative: Dim; +} + +impl DimEq for ShapeConstraint { + type Representative = D; +} + +impl DimEq for ShapeConstraint { + type Representative = D; +} + +impl DimEq for ShapeConstraint { + type Representative = D; +} + macro_rules! equality_trait_decl( ($($doc: expr, $Trait: ident),* $(,)*) => {$( // XXX: we can't do something like `DimEq for D2` because we would require a blancket impl… #[doc = $doc] - pub trait $Trait { + pub trait $Trait: DimEq + DimEq { /// This is either equal to `D1` or `D2`, always choosing the one (if any) which is a type-level /// constant. type Representative: Dim; @@ -40,9 +58,6 @@ macro_rules! equality_trait_decl( ); equality_trait_decl!( - "Constraints `D1` and `D2` to be equivalent.", - DimEq, - "Constraints `D1` and `D2` to be equivalent. \ They are both assumed to be the number of \ rows of a matrix.", diff --git a/src/core/construction.rs b/src/core/construction.rs index 606f0a2f..ae536be4 100644 --- a/src/core/construction.rs +++ b/src/core/construction.rs @@ -1,5 +1,7 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; use std::iter; use num::{Zero, One, Bounded}; @@ -8,38 +10,44 @@ use typenum::{self, Cmp, Greater}; use alga::general::{ClosedAdd, ClosedMul}; -use core::{Scalar, Matrix, SquareMatrix, ColumnVector, Unit}; +use core::{DefaultAllocator, Scalar, Matrix, Vector, Unit, MatrixMN, MatrixN, VectorN}; use core::dimension::{Dim, DimName, Dynamic, U1, U2, U3, U4, U5, U6}; -use core::allocator::{Allocator, OwnedAllocator}; -use core::storage::{Storage, OwnedStorage}; +use core::allocator::Allocator; +use core::storage::Storage; /* * * Generic constructors. * */ -impl> Matrix - // XXX: needed because of a compiler bug. See the rust compiler issue #26026. - where S::Alloc: OwnedAllocator { +impl MatrixMN + where DefaultAllocator: Allocator { /// Creates a new uninitialized matrix. If the matrix has a compile-time dimension, this panics /// if `nrows != R::to_usize()` or `ncols != C::to_usize()`. #[inline] - pub unsafe fn new_uninitialized_generic(nrows: R, ncols: C) -> Matrix { - Matrix::from_data(S::Alloc::allocate_uninitialized(nrows, ncols)) + pub unsafe fn new_uninitialized_generic(nrows: R, ncols: C) -> Self { + Self::from_data(DefaultAllocator::allocate_uninitialized(nrows, ncols)) } /// Creates a matrix with all its elements set to `elem`. #[inline] - pub fn from_element_generic(nrows: R, ncols: C, elem: N) -> Matrix { + pub fn from_element_generic(nrows: R, ncols: C, elem: N) -> Self { let len = nrows.value() * ncols.value(); - Matrix::from_iterator_generic(nrows, ncols, iter::repeat(elem).take(len)) + Self::from_iterator_generic(nrows, ncols, iter::repeat(elem).take(len)) + } + + /// Creates a matrix with all its elements set to 0. + #[inline] + pub fn zeros_generic(nrows: R, ncols: C) -> Self + where N: Zero { + Self::from_element_generic(nrows, ncols, N::zero()) } /// Creates a matrix with all its elements filled by an iterator. #[inline] - pub fn from_iterator_generic(nrows: R, ncols: C, iter: I) -> Matrix + pub fn from_iterator_generic(nrows: R, ncols: C, iter: I) -> Self where I: IntoIterator { - Matrix::from_data(S::Alloc::allocate_from_iterator(nrows, ncols, iter)) + Self::from_data(DefaultAllocator::allocate_from_iterator(nrows, ncols, iter)) } /// Creates a matrix with its elements filled with the components provided by a slice in @@ -48,7 +56,7 @@ impl> Matrix /// The order of elements in the slice must follow the usual mathematic writing, i.e., /// row-by-row. #[inline] - pub fn from_row_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Matrix { + pub fn from_row_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Self { assert!(slice.len() == nrows.value() * ncols.value(), "Matrix init. error: the slice did not contain the right number of elements."); @@ -69,14 +77,14 @@ impl> Matrix /// Creates a matrix with its elements filled with the components provided by a slice. The /// components must have the same layout as the matrix data storage (i.e. row-major or column-major). #[inline] - pub fn from_column_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Matrix { - Matrix::from_iterator_generic(nrows, ncols, slice.iter().cloned()) + pub fn from_column_slice_generic(nrows: R, ncols: C, slice: &[N]) -> Self { + Self::from_iterator_generic(nrows, ncols, slice.iter().cloned()) } /// Creates a matrix filled with the results of a function applied to each of its component /// coordinates. #[inline] - pub fn from_fn_generic(nrows: R, ncols: C, mut f: F) -> Matrix + pub fn from_fn_generic(nrows: R, ncols: C, mut f: F) -> Self where F: FnMut(usize, usize) -> N { let mut res = unsafe { Self::new_uninitialized_generic(nrows, ncols) }; @@ -94,7 +102,7 @@ impl> Matrix /// If the matrix is not square, the largest square submatrix starting at index `(0, 0)` is set /// to the identity matrix. All other entries are set to zero. #[inline] - pub fn identity_generic(nrows: R, ncols: C) -> Matrix + pub fn identity_generic(nrows: R, ncols: C) -> Self where N: Zero + One { Self::from_diagonal_element_generic(nrows, ncols, N::one()) } @@ -104,10 +112,9 @@ impl> Matrix /// If the matrix is not square, the largest square submatrix starting at index `(0, 0)` is set /// to the identity matrix. All other entries are set to zero. #[inline] - pub fn from_diagonal_element_generic(nrows: R, ncols: C, elt: N) -> Matrix + pub fn from_diagonal_element_generic(nrows: R, ncols: C, elt: N) -> Self where N: Zero + One { - let mut res = unsafe { Self::new_uninitialized_generic(nrows, ncols) }; - res.fill(N::zero()); + let mut res = Self::zeros_generic(nrows, ncols); for i in 0 .. ::min(nrows.value(), ncols.value()) { unsafe { *res.get_unchecked_mut(i, i) = elt } @@ -116,12 +123,29 @@ impl> Matrix res } + /// Creates a new matrix that may be rectangular. The first `elts.len()` diagonal elements are + /// filled with the content of `elts`. Others are set to 0. + /// + /// Panics if `elts.len()` is larger than the minimum among `nrows` and `ncols`. + #[inline] + pub fn from_partial_diagonal_generic(nrows: R, ncols: C, elts: &[N]) -> Self + where N: Zero { + let mut res = Self::zeros_generic(nrows, ncols); + assert!(elts.len() <= ::min(nrows.value(), ncols.value()), "Too many diagonal elements provided."); + + for (i, elt) in elts.iter().enumerate() { + unsafe { *res.get_unchecked_mut(i, i) = *elt } + } + + res + } + /// Builds a new matrix from its rows. /// /// Panics if not enough rows are provided (for statically-sized matrices), or if all rows do /// not have the same dimensions. #[inline] - pub fn from_rows(rows: &[Matrix]) -> Matrix + pub fn from_rows(rows: &[Matrix]) -> Self where SB: Storage { assert!(rows.len() > 0, "At least one row must be given."); @@ -144,8 +168,8 @@ impl> Matrix /// Panics if not enough columns are provided (for statically-sized matrices), or if all /// columns do not have the same dimensions. #[inline] - pub fn from_columns(columns: &[ColumnVector]) -> Matrix - where SB: Storage { + pub fn from_columns(columns: &[Vector]) -> Self + where SB: Storage { assert!(columns.len() > 0, "At least one column must be given."); let ncols = C::try_to_usize().unwrap_or(columns.len()); @@ -160,31 +184,27 @@ impl> Matrix // FIXME: optimize that. Self::from_fn_generic(R::from_usize(nrows), C::from_usize(ncols), |i, j| columns[j][i]) } -} -impl Matrix - where N: Scalar + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { /// Creates a matrix filled with random values. #[inline] - pub fn new_random_generic(nrows: R, ncols: C) -> Matrix { - Matrix::from_fn_generic(nrows, ncols, |_, _| rand::random()) + pub fn new_random_generic(nrows: R, ncols: C) -> Self + where N: Rand { + Self::from_fn_generic(nrows, ncols, |_, _| rand::random()) } } -impl SquareMatrix - where N: Scalar + Zero, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl MatrixN + where N: Scalar, + DefaultAllocator: Allocator { /// Creates a square matrix with its diagonal set to `diag` and all other entries set to 0. #[inline] - pub fn from_diagonal>(diag: &ColumnVector) -> Self { + pub fn from_diagonal>(diag: &Vector) -> Self + where N: Zero { let (dim, _) = diag.data.shape(); - let mut res = Self::from_element_generic(dim, dim, N::zero()); + let mut res = Self::zeros_generic(dim, dim); for i in 0 .. diag.len() { - unsafe { *res.get_unchecked_mut(i, i) = *diag.get_unchecked(i, 0); } + unsafe { *res.get_unchecked_mut(i, i) = *diag.vget_unchecked(i); } } res @@ -199,25 +219,31 @@ impl SquareMatrix */ macro_rules! impl_constructors( ($($Dims: ty),*; $(=> $DimIdent: ident: $DimBound: ident),*; $($gargs: expr),*; $($args: ident),*) => { - impl Matrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl MatrixMN + where DefaultAllocator: Allocator { /// Creates a new uninitialized matrix. #[inline] - pub unsafe fn new_uninitialized($($args: usize),*) -> Matrix { + pub unsafe fn new_uninitialized($($args: usize),*) -> Self { Self::new_uninitialized_generic($($gargs),*) } /// Creates a matrix with all its elements set to `elem`. #[inline] - pub fn from_element($($args: usize,)* elem: N) -> Matrix { + pub fn from_element($($args: usize,)* elem: N) -> Self { Self::from_element_generic($($gargs, )* elem) } + /// Creates a matrix with all its elements set to `0`. + #[inline] + pub fn zeros($($args: usize),*) -> Self + where N: Zero { + Self::zeros_generic($($gargs),*) + } + /// Creates a matrix with all its elements filled by an iterator. #[inline] - pub fn from_iterator($($args: usize,)* iter: I) -> Matrix + pub fn from_iterator($($args: usize,)* iter: I) -> Self where I: IntoIterator { Self::from_iterator_generic($($gargs, )* iter) } @@ -228,14 +254,14 @@ macro_rules! impl_constructors( /// The order of elements in the slice must follow the usual mathematic writing, i.e., /// row-by-row. #[inline] - pub fn from_row_slice($($args: usize,)* slice: &[N]) -> Matrix { + pub fn from_row_slice($($args: usize,)* slice: &[N]) -> Self { Self::from_row_slice_generic($($gargs, )* slice) } /// Creates a matrix with its elements filled with the components provided by a slice /// in column-major order. #[inline] - pub fn from_column_slice($($args: usize,)* slice: &[N]) -> Matrix { + pub fn from_column_slice($($args: usize,)* slice: &[N]) -> Self { Self::from_column_slice_generic($($gargs, )* slice) } @@ -243,7 +269,7 @@ macro_rules! impl_constructors( /// component coordinates. // FIXME: don't take a dimension of the matrix is statically sized. #[inline] - pub fn from_fn($($args: usize,)* f: F) -> Matrix + pub fn from_fn($($args: usize,)* f: F) -> Self where F: FnMut(usize, usize) -> N { Self::from_fn_generic($($gargs, )* f) } @@ -252,7 +278,7 @@ macro_rules! impl_constructors( /// submatrix (starting at the first row and column) is set to the identity while all /// other entries are set to zero. #[inline] - pub fn identity($($args: usize,)*) -> Matrix + pub fn identity($($args: usize,)*) -> Self where N: Zero + One { Self::identity_generic($($gargs),* ) } @@ -260,19 +286,28 @@ macro_rules! impl_constructors( /// Creates a matrix filled with its diagonal filled with `elt` and all other /// components set to zero. #[inline] - pub fn from_diagonal_element($($args: usize,)* elt: N) -> Matrix + pub fn from_diagonal_element($($args: usize,)* elt: N) -> Self where N: Zero + One { Self::from_diagonal_element_generic($($gargs, )* elt) } + + /// Creates a new matrix that may be rectangular. The first `elts.len()` diagonal + /// elements are filled with the content of `elts`. Others are set to 0. + /// + /// Panics if `elts.len()` is larger than the minimum among `nrows` and `ncols`. + #[inline] + pub fn from_partial_diagonal($($args: usize,)* elts: &[N]) -> Self + where N: Zero { + Self::from_partial_diagonal_generic($($gargs, )* elts) + } } - impl Matrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl MatrixMN + where DefaultAllocator: Allocator { /// Creates a matrix filled with random values. #[inline] - pub fn new_random($($args: usize),*) -> Matrix { + pub fn new_random($($args: usize),*) -> Self { Self::new_random_generic($($gargs),*) } } @@ -305,10 +340,9 @@ impl_constructors!(Dynamic, Dynamic; * Zero, One, Rand traits. * */ -impl Zero for Matrix +impl Zero for MatrixMN where N: Scalar + Zero + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn zero() -> Self { Self::from_element(N::zero()) @@ -320,20 +354,18 @@ impl Zero for Matrix } } -impl One for Matrix +impl One for MatrixN where N: Scalar + Zero + One + ClosedMul + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn one() -> Self { Self::identity() } } -impl Bounded for Matrix +impl Bounded for MatrixMN where N: Scalar + Bounded, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn max_value() -> Self { Self::from_element(N::max_value()) @@ -345,9 +377,8 @@ impl Bounded for Matrix } } -impl Rand for Matrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for MatrixMN + where DefaultAllocator: Allocator { #[inline] fn rand(rng: &mut G) -> Self { let nrows = R::try_to_usize().unwrap_or(rng.gen_range(0, 10)); @@ -359,11 +390,11 @@ impl Rand for Matrix #[cfg(feature = "arbitrary")] -impl Arbitrary for Matrix +impl Arbitrary for MatrixMN where R: Dim, C: Dim, N: Scalar + Arbitrary + Send, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator, + Owned: Clone + Send { #[inline] fn arbitrary(g: &mut G) -> Self { let nrows = R::try_to_usize().unwrap_or(g.gen_range(0, 10)); @@ -381,13 +412,12 @@ impl Arbitrary for Matrix */ macro_rules! componentwise_constructors_impl( ($($R: ty, $C: ty, $($args: ident:($irow: expr,$icol: expr)),*);* $(;)*) => {$( - impl Matrix + impl MatrixMN where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { /// Initializes this matrix from its components. #[inline] - pub fn new($($args: N),*) -> Matrix { + pub fn new($($args: N),*) -> Self { unsafe { let mut res = Self::new_uninitialized(); $( *res.get_unchecked_mut($irow, $icol) = $args; )* @@ -549,16 +579,15 @@ componentwise_constructors_impl!( * Axis constructors. * */ -impl ColumnVector +impl VectorN where N: Scalar + Zero + One, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { /// The column vector with a 1 as its first component, and zero elsewhere. #[inline] pub fn x() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(0, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(0) = N::one(); } res } @@ -567,8 +596,8 @@ where N: Scalar + Zero + One, #[inline] pub fn y() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(1, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(1) = N::one(); } res } @@ -577,8 +606,8 @@ where N: Scalar + Zero + One, #[inline] pub fn z() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(2, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(2) = N::one(); } res } @@ -587,8 +616,8 @@ where N: Scalar + Zero + One, #[inline] pub fn w() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(3, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(3) = N::one(); } res } @@ -597,8 +626,8 @@ where N: Scalar + Zero + One, #[inline] pub fn a() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(4, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(4) = N::one(); } res } @@ -607,8 +636,8 @@ where N: Scalar + Zero + One, #[inline] pub fn b() -> Self where R::Value: Cmp { - let mut res = Self::from_element(N::zero()); - unsafe { *res.get_unchecked_mut(5, 0) = N::one(); } + let mut res = Self::zeros(); + unsafe { *res.vget_unchecked_mut(5) = N::one(); } res } diff --git a/src/core/conversion.rs b/src/core/conversion.rs index 7e8f40cb..738857d5 100644 --- a/src/core/conversion.rs +++ b/src/core/conversion.rs @@ -3,37 +3,35 @@ use std::mem; use std::convert::{From, Into, AsRef, AsMut}; use alga::general::{SubsetOf, SupersetOf}; -use core::{Scalar, Matrix}; +use core::{DefaultAllocator, Scalar, Matrix, MatrixMN}; use core::dimension::{Dim, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13, U14, U15, U16 }; -use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; -use core::storage::{Storage, StorageMut, OwnedStorage}; use core::iter::{MatrixIter, MatrixIterMut}; -use core::allocator::{OwnedAllocator, SameShapeAllocator}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; +use core::storage::{ContiguousStorage, ContiguousStorageMut, Storage, StorageMut}; +use core::allocator::{Allocator, SameShapeAllocator}; // FIXME: too bad this won't work allo slice conversions. -impl SubsetOf> for Matrix +impl SubsetOf> for MatrixMN where R1: Dim, C1: Dim, R2: Dim, C2: Dim, N1: Scalar, N2: Scalar + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SB::Alloc: OwnedAllocator, - SA::Alloc: OwnedAllocator + - SameShapeAllocator, + DefaultAllocator: Allocator + + Allocator + + SameShapeAllocator, ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { #[inline] - fn to_superset(&self) -> Matrix { + fn to_superset(&self) -> MatrixMN { let (nrows, ncols) = self.shape(); let nrows2 = R2::from_usize(nrows); let ncols2 = C2::from_usize(ncols); - let mut res = unsafe { Matrix::::new_uninitialized_generic(nrows2, ncols2) }; + let mut res = unsafe { MatrixMN::::new_uninitialized_generic(nrows2, ncols2) }; for i in 0 .. nrows { for j in 0 .. ncols { unsafe { @@ -46,12 +44,12 @@ impl SubsetOf> for Matrix } #[inline] - fn is_in_subset(m: &Matrix) -> bool { + fn is_in_subset(m: &MatrixMN) -> bool { m.iter().all(|e| e.is_in_subset()) } #[inline] - unsafe fn from_superset_unchecked(m: &Matrix) -> Self { + unsafe fn from_superset_unchecked(m: &MatrixMN) -> Self { let (nrows2, ncols2) = m.shape(); let nrows = R1::from_usize(nrows2); let ncols = C1::from_usize(ncols2); @@ -90,10 +88,9 @@ impl<'a, N: Scalar, R: Dim, C: Dim, S: StorageMut> IntoIterator for &'a macro_rules! impl_from_into_asref_1D( ($(($NRows: ident, $NCols: ident) => $SZ: expr);* $(;)*) => {$( - impl From<[N; $SZ]> for Matrix + impl From<[N; $SZ]> for MatrixMN where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn from(arr: [N; $SZ]) -> Self { unsafe { @@ -107,8 +104,7 @@ macro_rules! impl_from_into_asref_1D( impl Into<[N; $SZ]> for Matrix where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + S: ContiguousStorage { #[inline] fn into(self) -> [N; $SZ] { unsafe { @@ -122,8 +118,7 @@ macro_rules! impl_from_into_asref_1D( impl AsRef<[N; $SZ]> for Matrix where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + S: ContiguousStorage { #[inline] fn as_ref(&self) -> &[N; $SZ] { unsafe { @@ -134,8 +129,7 @@ macro_rules! impl_from_into_asref_1D( impl AsMut<[N; $SZ]> for Matrix where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + S: ContiguousStorageMut { #[inline] fn as_mut(&mut self) -> &mut [N; $SZ] { unsafe { @@ -165,10 +159,8 @@ impl_from_into_asref_1D!( macro_rules! impl_from_into_asref_2D( ($(($NRows: ty, $NCols: ty) => ($SZRows: expr, $SZCols: expr));* $(;)*) => {$( - impl From<[[N; $SZRows]; $SZCols]> for Matrix - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl From<[[N; $SZRows]; $SZCols]> for MatrixMN + where DefaultAllocator: Allocator { #[inline] fn from(arr: [[N; $SZRows]; $SZCols]) -> Self { unsafe { @@ -180,10 +172,8 @@ macro_rules! impl_from_into_asref_2D( } } - impl Into<[[N; $SZRows]; $SZCols]> for Matrix - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl Into<[[N; $SZRows]; $SZCols]> for Matrix + where S: ContiguousStorage { #[inline] fn into(self) -> [[N; $SZRows]; $SZCols] { unsafe { @@ -195,10 +185,8 @@ macro_rules! impl_from_into_asref_2D( } } - impl AsRef<[[N; $SZRows]; $SZCols]> for Matrix - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl AsRef<[[N; $SZRows]; $SZCols]> for Matrix + where S: ContiguousStorage { #[inline] fn as_ref(&self) -> &[[N; $SZRows]; $SZCols] { unsafe { @@ -207,10 +195,8 @@ macro_rules! impl_from_into_asref_2D( } } - impl AsMut<[[N; $SZRows]; $SZCols]> for Matrix - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl AsMut<[[N; $SZRows]; $SZCols]> for Matrix + where S: ContiguousStorageMut { #[inline] fn as_mut(&mut self) -> &mut [[N; $SZRows]; $SZCols] { unsafe { @@ -222,7 +208,7 @@ macro_rules! impl_from_into_asref_2D( ); -// Implement for matrices with shape 2x2 .. 4x4. +// Implement for matrices with shape 2x2 .. 6x6. impl_from_into_asref_2D!( (U2, U2) => (2, 2); (U2, U3) => (2, 3); (U2, U4) => (2, 4); (U2, U5) => (2, 5); (U2, U6) => (2, 6); (U3, U2) => (3, 2); (U3, U3) => (3, 3); (U3, U4) => (3, 4); (U3, U5) => (3, 5); (U3, U6) => (3, 6); diff --git a/src/core/coordinates.rs b/src/core/coordinates.rs index 63d81166..95fd3011 100644 --- a/src/core/coordinates.rs +++ b/src/core/coordinates.rs @@ -9,8 +9,7 @@ use std::ops::{Deref, DerefMut}; use core::{Scalar, Matrix}; use core::dimension::{U1, U2, U3, U4, U5, U6}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::storage::{ContiguousStorage, ContiguousStorageMut}; /* * @@ -35,22 +34,20 @@ macro_rules! coords_impl( macro_rules! deref_impl( ($R: ty, $C: ty; $Target: ident) => { impl Deref for Matrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + where S: ContiguousStorage { type Target = $Target; #[inline] fn deref(&self) -> &Self::Target { - unsafe { mem::transmute(self) } + unsafe { mem::transmute(self.data.ptr()) } } } impl DerefMut for Matrix - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + where S: ContiguousStorageMut { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { mem::transmute(self) } + unsafe { mem::transmute(self.data.ptr_mut()) } } } } diff --git a/src/core/decompositions.rs b/src/core/decompositions.rs deleted file mode 100644 index 41183fcb..00000000 --- a/src/core/decompositions.rs +++ /dev/null @@ -1,373 +0,0 @@ -use std::cmp; - -use alga::general::Real; -use core::{SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector}; -use dimension::{Dim, Dynamic, U1}; -use storage::{Storage, OwnedStorage}; -use allocator::{Allocator, OwnedAllocator}; - - - -impl SquareMatrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { - /// Get the householder matrix corresponding to a reflexion to the hyperplane - /// defined by `vector`. It can be a reflexion contained in a subspace. - /// - /// # Arguments - /// * `dimension` - the dimension of the space the resulting matrix operates in - /// * `start` - the starting dimension of the subspace of the reflexion - /// * `vector` - the vector defining the reflection. - pub fn new_householder_generic(dimension: D, start: usize, vector: &ColumnVector) - -> OwnedSquareMatrix - where D2: Dim, - SB: Storage { - let mut qk = Self::identity_generic(dimension, dimension); - let subdim = vector.shape().0; - - let stop = subdim + start; - - assert!(dimension.value() >= stop, "Householder matrix creation: subspace dimension index out of bounds."); - - for j in start .. stop { - for i in start .. stop { - unsafe { - let vv = *vector.get_unchecked(i - start, 0) * *vector.get_unchecked(j - start, 0); - let qkij = *qk.get_unchecked(i, j); - *qk.get_unchecked_mut(i, j) = qkij - vv - vv; - } - } - } - - qk - } -} - - -impl> SquareMatrix { - /// QR decomposition using Householder reflections. - pub fn qr(self) -> (OwnedSquareMatrix, OwnedSquareMatrix) - where S::Alloc: Allocator + - Allocator { - - let (nrows, ncols) = self.data.shape(); - - // XXX: too restrictive. - assert!(nrows.value() >= ncols.value(), ""); - - let mut q = OwnedSquareMatrix::::identity_generic(nrows, ncols); - let mut r = self.into_owned(); - - // Temporary buffer that contains a column. - let mut col = unsafe { - OwnedColumnVector::::new_uninitialized_generic(nrows, U1) - }; - - for ite in 0 .. cmp::min(nrows.value() - 1, ncols.value()) { - let subdim = Dynamic::new(nrows.value() - ite); - let mut v = col.rows_mut(0, subdim.value()); - v.copy_from(&r.generic_slice((ite, ite), (subdim, U1))); - - let alpha = - if unsafe { *v.get_unchecked(ite, 0) } >= ::zero() { - -v.norm() - } - else { - v.norm() - }; - - unsafe { - let x = *v.get_unchecked(0, 0); - *v.get_unchecked_mut(0, 0) = x - alpha; - } - - if !v.normalize_mut().is_zero() { - let mut qk = OwnedSquareMatrix::::new_householder_generic(nrows, ite, &v); - r = &qk * r; - - // FIXME: add a method `q.mul_tr(qk) := q * qk.transpose` ? - qk.transpose_mut(); - q = q * qk; - } - } - - (q, r) - } - - /// Eigendecomposition of a square symmetric matrix. - pub fn eig(&self, eps: N, niter: usize) - -> (OwnedSquareMatrix, OwnedColumnVector) - where S::Alloc: Allocator + - Allocator { - - assert!(self.is_square(), - "Unable to compute the eigenvectors and eigenvalues of a non-square matrix."); - - let dim = self.data.shape().0; - - let (mut eigenvectors, mut eigenvalues) = self.hessenberg(); - - if dim.value() == 1 { - return (eigenvectors, eigenvalues.diagonal()); - } - - // Allocate arrays for Givens rotation components - let mut c = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; - let mut s = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; - - let mut iter = 0; - let mut curdim = dim.value() - 1; - - for _ in 0 .. dim.value() { - - let mut stop = false; - - while !stop && iter < niter { - - let lambda; - - unsafe { - let a = *eigenvalues.get_unchecked(curdim - 1, curdim - 1); - let b = *eigenvalues.get_unchecked(curdim - 1, curdim); - let c = *eigenvalues.get_unchecked(curdim, curdim - 1); - let d = *eigenvalues.get_unchecked(curdim, curdim); - - let trace = a + d; - let determinant = a * d - b * c; - - let constquarter: N = ::convert(0.25f64); - let consthalf: N = ::convert(0.5f64); - - let e = (constquarter * trace * trace - determinant).sqrt(); - - let lambda1 = consthalf * trace + e; - let lambda2 = consthalf * trace - e; - - if (lambda1 - d).abs() < (lambda2 - d).abs() { - lambda = lambda1; - } - else { - lambda = lambda2; - } - - } - - // Shift matrix - for k in 0 .. curdim + 1 { - unsafe { - let a = *eigenvalues.get_unchecked(k, k); - *eigenvalues.get_unchecked_mut(k, k) = a - lambda; - } - } - - - // Givens rotation from left - for k in 0 .. curdim { - let x_i = unsafe { *eigenvalues.get_unchecked(k, k) }; - let x_j = unsafe { *eigenvalues.get_unchecked(k + 1, k) }; - - let ctmp; - let stmp; - - if x_j.abs() < eps { - ctmp = N::one(); - stmp = N::zero(); - } - else if x_i.abs() < eps { - ctmp = N::zero(); - stmp = -N::one(); - } - else { - let r = x_i.hypot(x_j); - ctmp = x_i / r; - stmp = -x_j / r; - } - - c[k] = ctmp; - s[k] = stmp; - - for j in k .. (curdim + 1) { - unsafe { - let a = *eigenvalues.get_unchecked(k, j); - let b = *eigenvalues.get_unchecked(k + 1, j); - - *eigenvalues.get_unchecked_mut(k, j) = ctmp * a - stmp * b; - *eigenvalues.get_unchecked_mut(k + 1, j) = stmp * a + ctmp * b; - } - - } - } - - // Givens rotation from right applied to eigenvalues - for k in 0 .. curdim { - for i in 0 .. (k + 2) { - unsafe { - let a = *eigenvalues.get_unchecked(i, k); - let b = *eigenvalues.get_unchecked(i, k + 1); - - *eigenvalues.get_unchecked_mut(i, k) = c[k] * a - s[k] * b; - *eigenvalues.get_unchecked_mut(i, k + 1) = s[k] * a + c[k] * b; - } - } - } - - - // Shift back - for k in 0 .. curdim + 1 { - unsafe { - let a = *eigenvalues.get_unchecked(k, k); - *eigenvalues.get_unchecked_mut(k, k) = a + lambda; - } - } - - - // Givens rotation from right applied to eigenvectors - for k in 0 .. curdim { - for i in 0 .. dim.value() { - - unsafe { - let a = *eigenvectors.get_unchecked(i, k); - let b = *eigenvectors.get_unchecked(i, k + 1); - - *eigenvectors.get_unchecked_mut(i, k) = c[k] * a - s[k] * b; - *eigenvectors.get_unchecked_mut(i, k + 1) = s[k] * a + c[k] * b; - } - } - } - - iter = iter + 1; - stop = true; - - for j in 0 .. curdim { - // Check last row. - if unsafe { *eigenvalues.get_unchecked(curdim, j) }.abs() >= eps { - stop = false; - break; - } - - // Check last column. - if unsafe { *eigenvalues.get_unchecked(j, curdim) }.abs() >= eps { - stop = false; - break; - } - } - } - - - if stop { - if curdim > 1 { - curdim = curdim - 1; - } - else { - break; - } - } - } - - (eigenvectors, eigenvalues.diagonal()) - } - - /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T - /// - /// Matrix symmetricness is not checked. Returns `None` if `self` is not definite positive. - #[inline] - pub fn cholesky(&self) -> Result, &'static str> { - let out = self.transpose(); - - if !out.relative_eq(self, N::default_epsilon(), N::default_max_relative()) { - return Err("Cholesky: Input matrix is not symmetric"); - } - - self.do_cholesky(out) - } - - /// Cholesky decomposition G of a square symmetric positive definite matrix A, such that A = G * G^T - #[inline] - pub fn cholesky_unchecked(&self) -> Result, &'static str> { - let out = self.transpose(); - self.do_cholesky(out) - } - - #[inline(always)] - fn do_cholesky(&self, mut out: OwnedSquareMatrix) - -> Result, &'static str> { - assert!(self.is_square(), "The input matrix must be square."); - - for i in 0 .. out.nrows() { - for j in 0 .. (i + 1) { - - let mut sum = out[(i, j)]; - - for k in 0 .. j { - sum = sum - out[(i, k)] * out[(j, k)]; - } - - if i > j { - out[(i, j)] = sum / out[(j, j)]; - } - else if sum > N::zero() { - out[(i, i)] = sum.sqrt(); - } - else { - return Err("Cholesky: Input matrix is not positive definite to machine precision."); - } - } - } - - for i in 0 .. out.nrows() { - for j in i + 1 .. out.ncols() { - out[(i, j)] = N::zero(); - } - } - - Ok(out) - } - - /// Hessenberg - /// Returns the matrix `self` in Hessenberg form and the corresponding similarity transformation - /// - /// # Returns - /// The tuple (`q`, `h`) that `q * h * q^T = self` - pub fn hessenberg(&self) -> (OwnedSquareMatrix, OwnedSquareMatrix) - where S::Alloc: Allocator + Allocator { - - let (nrows, ncols) = self.data.shape(); - let mut h = self.clone_owned(); - - let mut q = OwnedSquareMatrix::::identity_generic(nrows, ncols); - - if ncols.value() <= 2 { - return (q, h); - } - - // Temporary buffer that contains a column. - let mut col = unsafe { - OwnedColumnVector::::new_uninitialized_generic(nrows, U1) - }; - - for ite in 0 .. (ncols.value() - 2) { - let subdim = Dynamic::new(nrows.value() - (ite + 1)); - let mut v = col.rows_mut(0, subdim.value()); - v.copy_from(&h.generic_slice((ite + 1, ite), (subdim, U1))); - - let alpha = v.norm(); - - unsafe { - let x = *v.get_unchecked(0, 0); - *v.get_unchecked_mut(0, 0) = x - alpha; - } - - if !v.normalize_mut().is_zero() { - // XXX: we output the householder matrix to a pre-allocated matrix instead of - // return a value to `p`. This would avoid allocation at each iteration. - let p = OwnedSquareMatrix::::new_householder_generic(nrows, ite + 1, &v); - - q = q * &p; - h = &p * h * p; - } - } - - (q, h) - } -} diff --git a/src/core/default_allocator.rs b/src/core/default_allocator.rs index acfbe97c..fd801d27 100644 --- a/src/core/default_allocator.rs +++ b/src/core/default_allocator.rs @@ -4,6 +4,8 @@ //! heap-allocated buffers for matrices with at least one dimension unknown at compile-time. use std::mem; +use std::ptr; +use std::cmp; use std::ops::Mul; use typenum::Prod; @@ -11,7 +13,8 @@ use generic_array::ArrayLength; use core::Scalar; use core::dimension::{Dim, DimName, Dynamic}; -use core::allocator::Allocator; +use core::allocator::{Allocator, Reallocator}; +use core::storage::{Storage, StorageMut}; use core::matrix_array::MatrixArray; use core::matrix_vec::MatrixVec; @@ -107,3 +110,110 @@ impl Allocator for DefaultAllocator { MatrixVec::new(nrows, ncols, res) } } + +/* + * + * Reallocator. + * + */ +// Anything -> Static × Static +impl Reallocator for DefaultAllocator + where RFrom: Dim, + CFrom: Dim, + RTo: DimName, + CTo: DimName, + Self: Allocator, + RTo::Value: Mul, + Prod: ArrayLength { + + #[inline] + unsafe fn reallocate_copy(rto: RTo, cto: CTo, buf: >::Buffer) -> MatrixArray { + let mut res = >::allocate_uninitialized(rto, cto); + + let (rfrom, cfrom) = buf.shape(); + + let len_from = rfrom.value() * cfrom.value(); + let len_to = rto.value() * cto.value(); + ptr::copy_nonoverlapping(buf.ptr(), res.ptr_mut(), cmp::min(len_from, len_to)); + + res + } +} + + +// Static × Static -> Dynamic × Any +impl Reallocator for DefaultAllocator + where RFrom: DimName, + CFrom: DimName, + CTo: Dim, + RFrom::Value: Mul, + Prod: ArrayLength { + + #[inline] + unsafe fn reallocate_copy(rto: Dynamic, cto: CTo, buf: MatrixArray) -> MatrixVec { + let mut res = >::allocate_uninitialized(rto, cto); + + let (rfrom, cfrom) = buf.shape(); + + let len_from = rfrom.value() * cfrom.value(); + let len_to = rto.value() * cto.value(); + ptr::copy_nonoverlapping(buf.ptr(), res.ptr_mut(), cmp::min(len_from, len_to)); + + res + } +} + +// Static × Static -> Static × Dynamic +impl Reallocator for DefaultAllocator + where RFrom: DimName, + CFrom: DimName, + RTo: DimName, + RFrom::Value: Mul, + Prod: ArrayLength { + + #[inline] + unsafe fn reallocate_copy(rto: RTo, cto: Dynamic, buf: MatrixArray) -> MatrixVec { + let mut res = >::allocate_uninitialized(rto, cto); + + let (rfrom, cfrom) = buf.shape(); + + let len_from = rfrom.value() * cfrom.value(); + let len_to = rto.value() * cto.value(); + ptr::copy_nonoverlapping(buf.ptr(), res.ptr_mut(), cmp::min(len_from, len_to)); + + res + } +} + +// All conversion from a dynamic buffer to a dynamic buffer. +impl Reallocator for DefaultAllocator { + #[inline] + unsafe fn reallocate_copy(rto: Dynamic, cto: CTo, buf: MatrixVec) -> MatrixVec { + let new_buf = buf.resize(rto.value() * cto.value()); + MatrixVec::new(rto, cto, new_buf) + } +} + +impl Reallocator for DefaultAllocator { + #[inline] + unsafe fn reallocate_copy(rto: RTo, cto: Dynamic, buf: MatrixVec) -> MatrixVec { + let new_buf = buf.resize(rto.value() * cto.value()); + MatrixVec::new(rto, cto, new_buf) + } +} + +impl Reallocator for DefaultAllocator { + #[inline] + unsafe fn reallocate_copy(rto: Dynamic, cto: CTo, buf: MatrixVec) -> MatrixVec { + let new_buf = buf.resize(rto.value() * cto.value()); + MatrixVec::new(rto, cto, new_buf) + } +} + +impl Reallocator for DefaultAllocator { + #[inline] + unsafe fn reallocate_copy(rto: RTo, cto: Dynamic, buf: MatrixVec) -> MatrixVec { + let new_buf = buf.resize(rto.value() * cto.value()); + MatrixVec::new(rto, cto, new_buf) + } +} diff --git a/src/core/dimension.rs b/src/core/dimension.rs index ec8f3c57..f87eb2cf 100644 --- a/src/core/dimension.rs +++ b/src/core/dimension.rs @@ -3,9 +3,11 @@ //! Traits and tags for identifying the dimension of all algebraic entities. use std::fmt::Debug; -use std::any::Any; +use std::any::{TypeId, Any}; +use std::cmp; use std::ops::{Add, Sub, Mul, Div}; -use typenum::{self, Unsigned, UInt, B1, Bit, UTerm, Sum, Prod, Diff, Quot}; +use typenum::{self, Unsigned, UInt, B1, Bit, UTerm, Sum, Prod, Diff, Quot, + Min, Minimum, Max, Maximum}; #[cfg(feature = "serde-serialize")] use serde::{Serialize, Serializer, Deserialize, Deserializer}; @@ -55,6 +57,11 @@ impl IsNotStaticOne for Dynamic { } /// Trait implemented by any type that can be used as a dimension. This includes type-level /// integers and `Dynamic` (for dimensions not known at compile-time). pub trait Dim: Any + Debug + Copy + PartialEq + Send { + #[inline(always)] + fn is() -> bool { + TypeId::of::() == TypeId::of::() + } + /// Gets the compile-time value of `Self`. Returns `None` if it is not known, i.e., if `Self = /// Dynamic`. fn try_to_usize() -> Option; @@ -85,6 +92,24 @@ impl Dim for Dynamic { } } +impl Add for Dynamic { + type Output = Dynamic; + + #[inline] + fn add(self, rhs: usize) -> Dynamic { + Dynamic::new(self.value + rhs) + } +} + +impl Sub for Dynamic { + type Output = Dynamic; + + #[inline] + fn sub(self, rhs: usize) -> Dynamic { + Dynamic::new(self.value - rhs) + } +} + /* * * Operations. @@ -93,7 +118,7 @@ impl Dim for Dynamic { macro_rules! dim_ops( ($($DimOp: ident, $DimNameOp: ident, - $Op: ident, $op: ident, + $Op: ident, $op: ident, $op_path: path, $DimResOp: ident, $DimNameResOp: ident, $ResOp: ident);* $(;)*) => {$( pub type $DimResOp = >::Output; @@ -120,7 +145,7 @@ macro_rules! dim_ops( #[inline] fn $op(self, other: D) -> Dynamic { - Dynamic::new(self.value.$op(other.value())) + Dynamic::new($op_path(self.value, other.value())) } } @@ -129,7 +154,7 @@ macro_rules! dim_ops( #[inline] fn $op(self, other: Dynamic) -> Dynamic { - Dynamic::new(self.value().$op(other.value)) + Dynamic::new($op_path(self.value(), other.value)) } } @@ -155,10 +180,12 @@ macro_rules! dim_ops( ); dim_ops!( - DimAdd, DimNameAdd, Add, add, DimSum, DimNameSum, Sum; - DimMul, DimNameMul, Mul, mul, DimProd, DimNameProd, Prod; - DimSub, DimNameSub, Sub, sub, DimDiff, DimNameDiff, Diff; - DimDiv, DimNameDiv, Div, div, DimQuot, DimNameQuot, Quot; + DimAdd, DimNameAdd, Add, add, Add::add, DimSum, DimNameSum, Sum; + DimMul, DimNameMul, Mul, mul, Mul::mul, DimProd, DimNameProd, Prod; + DimSub, DimNameSub, Sub, sub, Sub::sub, DimDiff, DimNameDiff, Diff; + DimDiv, DimNameDiv, Div, div, Div::div, DimQuot, DimNameQuot, Quot; + DimMin, DimNameMin, Min, min, cmp::min, DimMinimum, DimNameNimimum, Minimum; + DimMax, DimNameMax, Max, max, cmp::max, DimMaximum, DimNameMaximum, Maximum; ); diff --git a/src/core/edition.rs b/src/core/edition.rs new file mode 100644 index 00000000..43389a24 --- /dev/null +++ b/src/core/edition.rs @@ -0,0 +1,565 @@ +use num::{Zero, One}; +use std::cmp; +use std::ptr; + +use core::{DefaultAllocator, Scalar, Matrix, DMatrix, MatrixMN, Vector, RowVector}; +use core::dimension::{Dim, DimName, DimSub, DimDiff, DimAdd, DimSum, DimMin, DimMinimum, U1, Dynamic}; +use core::constraint::{ShapeConstraint, DimEq, SameNumberOfColumns, SameNumberOfRows}; +use core::allocator::{Allocator, Reallocator}; +use core::storage::{Storage, StorageMut}; + +impl> Matrix { + /// Extracts the upper triangular part of this matrix (including the diagonal). + #[inline] + pub fn upper_triangle(&self) -> MatrixMN + where DefaultAllocator: Allocator { + let mut res = self.clone_owned(); + res.fill_lower_triangle(N::zero(), 1); + + res + } + + /// Extracts the upper triangular part of this matrix (including the diagonal). + #[inline] + pub fn lower_triangle(&self) -> MatrixMN + where DefaultAllocator: Allocator { + let mut res = self.clone_owned(); + res.fill_upper_triangle(N::zero(), 1); + + res + } +} + +impl> Matrix { + /// Sets all the elements of this matrix to `val`. + #[inline] + pub fn fill(&mut self, val: N) { + for e in self.iter_mut() { + *e = val + } + } + + /// Fills `self` with the identity matrix. + #[inline] + pub fn fill_with_identity(&mut self) + where N: Zero + One { + self.fill(N::zero()); + self.fill_diagonal(N::one()); + } + + /// Sets all the diagonal elements of this matrix to `val`. + #[inline] + pub fn fill_diagonal(&mut self, val: N) { + let (nrows, ncols) = self.shape(); + let n = cmp::min(nrows, ncols); + + for i in 0 .. n { + unsafe { *self.get_unchecked_mut(i, i) = val } + } + } + + /// Sets all the elements of the selected row to `val`. + #[inline] + pub fn fill_row(&mut self, i: usize, val: N) { + assert!(i < self.nrows(), "Row index out of bounds."); + for j in 0 .. self.ncols() { + unsafe { *self.get_unchecked_mut(i, j) = val } + } + } + + /// Sets all the elements of the selected column to `val`. + #[inline] + pub fn fill_column(&mut self, j: usize, val: N) { + assert!(j < self.ncols(), "Row index out of bounds."); + for i in 0 .. self.nrows() { + unsafe { *self.get_unchecked_mut(i, j) = val } + } + } + + /// Fills the diagonal of this matrix with the content of the given vector. + #[inline] + pub fn set_diagonal(&mut self, diag: &Vector) + where R: DimMin, + S2: Storage, + ShapeConstraint: DimEq, R2> { + let (nrows, ncols) = self.shape(); + let min_nrows_ncols = cmp::min(nrows, ncols); + assert_eq!(diag.len(), min_nrows_ncols, "Mismatched dimensions."); + + for i in 0 .. min_nrows_ncols { + unsafe { *self.get_unchecked_mut(i, i) = *diag.vget_unchecked(i) } + } + } + + /// Fills the selected row of this matrix with the content of the given vector. + #[inline] + pub fn set_row(&mut self, i: usize, row: &RowVector) + where S2: Storage, + ShapeConstraint: SameNumberOfColumns { + self.row_mut(i).copy_from(row); + } + + /// Fills the selected column of this matrix with the content of the given vector. + #[inline] + pub fn set_column(&mut self, i: usize, column: &Vector) + where S2: Storage, + ShapeConstraint: SameNumberOfRows { + self.column_mut(i).copy_from(column); + } + + /// Sets all the elements of the lower-triangular part of this matrix to `val`. + /// + /// The parameter `shift` allows some subdiagonals to be left untouched: + /// * If `shift = 0` then the diagonal is overwritten as well. + /// * If `shift = 1` then the diagonal is left untouched. + /// * If `shift > 1`, then the diagonal and the first `shift - 1` subdiagonals are left + /// untouched. + #[inline] + pub fn fill_lower_triangle(&mut self, val: N, shift: usize) { + for j in 0 .. self.ncols() { + for i in (j + shift) .. self.nrows() { + unsafe { *self.get_unchecked_mut(i, j) = val } + } + } + } + + /// Sets all the elements of the lower-triangular part of this matrix to `val`. + /// + /// The parameter `shift` allows some superdiagonals to be left untouched: + /// * If `shift = 0` then the diagonal is overwritten as well. + /// * If `shift = 1` then the diagonal is left untouched. + /// * If `shift > 1`, then the diagonal and the first `shift - 1` superdiagonals are left + /// untouched. + #[inline] + pub fn fill_upper_triangle(&mut self, val: N, shift: usize) { + for j in shift .. self.ncols() { + // FIXME: is there a more efficient way to avoid the min ? + // (necessary for rectangular matrices) + for i in 0 .. cmp::min(j + 1 - shift, self.nrows()) { + unsafe { *self.get_unchecked_mut(i, j) = val } + } + } + } + + /// Swaps two rows in-place. + #[inline] + pub fn swap_rows(&mut self, irow1: usize, irow2: usize) { + assert!(irow1 < self.nrows() && irow2 < self.nrows()); + + if irow1 != irow2 { + // FIXME: optimize that. + for i in 0 .. self.ncols() { + unsafe { self.swap_unchecked((irow1, i), (irow2, i)) } + } + } + // Otherwise do nothing. + } + + /// Swaps two columns in-place. + #[inline] + pub fn swap_columns(&mut self, icol1: usize, icol2: usize) { + assert!(icol1 < self.ncols() && icol2 < self.ncols()); + + if icol1 != icol2 { + // FIXME: optimize that. + for i in 0 .. self.nrows() { + unsafe { self.swap_unchecked((i, icol1), (i, icol2)) } + } + } + // Otherwise do nothing. + } +} + +impl> Matrix { + /// Copies the upper-triangle of this matrix to its lower-triangular part. + /// + /// This makes the matrix symmetric. Panics if the matrix is not square. + pub fn fill_lower_triangle_with_upper_triangle(&mut self) { + assert!(self.is_square(), "The input matrix should be square."); + + let dim = self.nrows(); + for j in 0 .. dim { + for i in j + 1 .. dim { + unsafe { + *self.get_unchecked_mut(i, j) = *self.get_unchecked(j, i); + } + } + } + } + + /// Copies the upper-triangle of this matrix to its upper-triangular part. + /// + /// This makes the matrix symmetric. Panics if the matrix is not square. + pub fn fill_upper_triangle_with_lower_triangle(&mut self) { + assert!(self.is_square(), "The input matrix should be square."); + + for j in 1 .. self.ncols() { + for i in 0 .. j { + unsafe { + *self.get_unchecked_mut(i, j) = *self.get_unchecked(j, i); + } + } + } + } +} + +/* + * + * FIXME: specialize all the following for slices. + * + */ +impl> Matrix { + /* + * + * Column removal. + * + */ + /// Removes the `i`-th column from this matrix. + #[inline] + pub fn remove_column(self, i: usize) -> MatrixMN> + where C: DimSub, + DefaultAllocator: Reallocator> { + self.remove_fixed_columns::(i) + } + + /// Removes `D::dim()` consecutive columns from this matrix, starting with the `i`-th + /// (included). + #[inline] + pub fn remove_fixed_columns(self, i: usize) -> MatrixMN> + where D: DimName, + C: DimSub, + DefaultAllocator: Reallocator> { + + self.remove_columns_generic(i, D::name()) + } + + /// Removes `n` consecutive columns from this matrix, starting with the `i`-th (included). + #[inline] + pub fn remove_columns(self, i: usize, n: usize) -> MatrixMN + where C: DimSub, + DefaultAllocator: Reallocator { + + self.remove_columns_generic(i, Dynamic::new(n)) + } + + /// Removes `nremove.value()` columns from this matrix, starting with the `i`-th (included). + /// + /// This is the generic implementation of `.remove_columns(...)` and + /// `.remove_fixed_columns(...)` which have nicer API interfaces. + #[inline] + pub fn remove_columns_generic(self, i: usize, nremove: D) -> MatrixMN> + where D: Dim, + C: DimSub, + DefaultAllocator: Reallocator> { + + let mut m = self.into_owned(); + let (nrows, ncols) = m.data.shape(); + assert!(i + nremove.value() <= ncols.value(), "Column index out of range."); + + if nremove.value() != 0 && i + nremove.value() < ncols.value() { + // The first `deleted_i * nrows` are left untouched. + let copied_value_start = i + nremove.value(); + + unsafe { + let ptr_in = m.data.ptr().offset((copied_value_start * nrows.value()) as isize); + let ptr_out = m.data.ptr_mut().offset((i * nrows.value()) as isize); + + ptr::copy(ptr_in, ptr_out, (ncols.value() - copied_value_start) * nrows.value()); + } + } + + unsafe { + Matrix::from_data(DefaultAllocator::reallocate_copy(nrows, ncols.sub(nremove), m.data)) + } + } + + + /* + * + * Row removal. + * + */ + /// Removes the `i`-th row from this matrix. + #[inline] + pub fn remove_row(self, i: usize) -> MatrixMN, C> + where R: DimSub, + DefaultAllocator: Reallocator, C> { + self.remove_fixed_rows::(i) + } + + /// Removes `D::dim()` consecutive rows from this matrix, starting with the `i`-th (included). + #[inline] + pub fn remove_fixed_rows(self, i: usize) -> MatrixMN, C> + where D: DimName, + R: DimSub, + DefaultAllocator: Reallocator, C> { + + self.remove_rows_generic(i, D::name()) + } + + /// Removes `n` consecutive rows from this matrix, starting with the `i`-th (included). + #[inline] + pub fn remove_rows(self, i: usize, n: usize) -> MatrixMN + where R: DimSub, + DefaultAllocator: Reallocator { + + self.remove_rows_generic(i, Dynamic::new(n)) + } + + /// Removes `nremove.value()` rows from this matrix, starting with the `i`-th (included). + /// + /// This is the generic implementation of `.remove_rows(...)` and `.remove_fixed_rows(...)` + /// which have nicer API interfaces. + #[inline] + pub fn remove_rows_generic(self, i: usize, nremove: D) -> MatrixMN, C> + where D: Dim, + R: DimSub, + DefaultAllocator: Reallocator, C> { + let mut m = self.into_owned(); + let (nrows, ncols) = m.data.shape(); + assert!(i + nremove.value() <= nrows.value(), "Row index out of range."); + + if nremove.value() != 0 { + unsafe { + compress_rows(&mut m.data.as_mut_slice(), nrows.value(), ncols.value(), i, nremove.value()); + } + } + + unsafe { + Matrix::from_data(DefaultAllocator::reallocate_copy(nrows.sub(nremove), ncols, m.data)) + } + } + + /* + * + * Columns insertion. + * + */ + /// Inserts a column filled with `val` at the `i-th` position. + #[inline] + pub fn insert_column(self, i: usize, val: N) -> MatrixMN> + where C: DimAdd, + DefaultAllocator: Reallocator> { + self.insert_fixed_columns::(i, val) + } + + /// Inserts `D::dim()` columns filled with `val` starting at the `i-th` position. + #[inline] + pub fn insert_fixed_columns(self, i: usize, val: N) -> MatrixMN> + where D: DimName, + C: DimAdd, + DefaultAllocator: Reallocator> { + let mut res = unsafe { self.insert_columns_generic_uninitialized(i, D::name()) }; + res.fixed_columns_mut::(i).fill(val); + res + } + + /// Inserts `n` columns filled with `val` starting at the `i-th` position. + #[inline] + pub fn insert_columns(self, i: usize, n: usize, val: N) -> MatrixMN + where C: DimAdd, + DefaultAllocator: Reallocator { + let mut res = unsafe { self.insert_columns_generic_uninitialized(i, Dynamic::new(n)) }; + res.columns_mut(i, n).fill(val); + res + } + + /// Inserts `ninsert.value()` columns starting at the `i-th` place of this matrix. + /// + /// The added column values are not initialized. + #[inline] + pub unsafe fn insert_columns_generic_uninitialized(self, i: usize, ninsert: D) + -> MatrixMN> + where D: Dim, + C: DimAdd, + DefaultAllocator: Reallocator> { + + let m = self.into_owned(); + let (nrows, ncols) = m.data.shape(); + let mut res = Matrix::from_data(DefaultAllocator::reallocate_copy(nrows, ncols.add(ninsert), m.data)); + + assert!(i <= ncols.value(), "Column insertion index out of range."); + + if ninsert.value() != 0 && i != ncols.value() { + let ptr_in = res.data.ptr().offset((i * nrows.value()) as isize); + let ptr_out = res.data.ptr_mut().offset(((i + ninsert.value()) * nrows.value()) as isize); + + ptr::copy(ptr_in, ptr_out, (ncols.value() - i) * nrows.value()) + } + + res + } + + /* + * + * Rows insertion. + * + */ + /// Inserts a row filled with `val` at the `i-th` position. + #[inline] + pub fn insert_row(self, i: usize, val: N) -> MatrixMN, C> + where R: DimAdd, + DefaultAllocator: Reallocator, C> { + self.insert_fixed_rows::(i, val) + } + + /// Inserts `D::dim()` rows filled with `val` starting at the `i-th` position. + #[inline] + pub fn insert_fixed_rows(self, i: usize, val: N) -> MatrixMN, C> + where D: DimName, + R: DimAdd, + DefaultAllocator: Reallocator, C> { + let mut res = unsafe { self.insert_rows_generic_uninitialized(i, D::name()) }; + res.fixed_rows_mut::(i).fill(val); + res + } + + /// Inserts `n` rows filled with `val` starting at the `i-th` position. + #[inline] + pub fn insert_rows(self, i: usize, n: usize, val: N) -> MatrixMN + where R: DimAdd, + DefaultAllocator: Reallocator { + let mut res = unsafe { self.insert_rows_generic_uninitialized(i, Dynamic::new(n)) }; + res.rows_mut(i, n).fill(val); + res + } + + /// Inserts `ninsert.value()` rows at the `i-th` place of this matrix. + /// + /// The added rows values are not initialized. + /// This is the generic implementation of `.insert_rows(...)` and + /// `.insert_fixed_rows(...)` which have nicer API interfaces. + #[inline] + pub unsafe fn insert_rows_generic_uninitialized(self, i: usize, ninsert: D) + -> MatrixMN, C> + where D: Dim, + R: DimAdd, + DefaultAllocator: Reallocator, C> { + + let m = self.into_owned(); + let (nrows, ncols) = m.data.shape(); + let mut res = Matrix::from_data(DefaultAllocator::reallocate_copy(nrows.add(ninsert), ncols, m.data)); + + assert!(i <= nrows.value(), "Row insertion index out of range."); + + if ninsert.value() != 0 { + extend_rows(&mut res.data.as_mut_slice(), nrows.value(), ncols.value(), i, ninsert.value()); + } + + res + } + + /* + * + * Resizing. + * + */ + + /// Resizes this matrix so that it contains `new_nrows` rows and `new_ncols` columns. + /// + /// 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`. + pub fn resize(self, new_nrows: usize, new_ncols: usize, val: N) -> DMatrix + where DefaultAllocator: Reallocator { + + self.resize_generic(Dynamic::new(new_nrows), Dynamic::new(new_ncols), val) + } + + /// Resizes this matrix so that it contains `R2::value()` rows and `C2::value()` columns. + /// + /// 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`. + pub fn fixed_resize(self, val: N) -> MatrixMN + where DefaultAllocator: Reallocator { + + self.resize_generic(R2::name(), C2::name(), val) + } + + /// Resizes `self` such that it has dimensions `new_nrows × now_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`. + #[inline] + pub fn resize_generic(self, new_nrows: R2, new_ncols: C2, val: N) -> MatrixMN + where DefaultAllocator: Reallocator { + + let (nrows, ncols) = self.shape(); + let mut data = self.data.into_owned(); + + if new_nrows.value() == nrows { + let res = unsafe { DefaultAllocator::reallocate_copy(new_nrows, new_ncols, data) }; + + Matrix::from_data(res) + } + else { + let mut res; + + unsafe { + if new_nrows.value() < nrows { + compress_rows(&mut data.as_mut_slice(), nrows, ncols, new_nrows.value(), nrows - new_nrows.value()); + res = Matrix::from_data(DefaultAllocator::reallocate_copy(new_nrows, new_ncols, data)); + } + else { + res = Matrix::from_data(DefaultAllocator::reallocate_copy(new_nrows, new_ncols, data)); + extend_rows(&mut res.data.as_mut_slice(), nrows, ncols, nrows, new_nrows.value() - nrows); + } + } + + if new_ncols.value() > ncols { + res.columns_range_mut(ncols ..).fill(val); + } + + if new_nrows.value() > nrows { + res.slice_range_mut(nrows .., .. cmp::min(ncols, new_ncols.value())).fill(val); + } + + res + } + } +} + + +unsafe fn compress_rows(data: &mut [N], nrows: usize, ncols: usize, i: usize, nremove: usize) { + let new_nrows = nrows - nremove; + let ptr_in = data.as_ptr(); + let ptr_out = data.as_mut_ptr(); + + let mut curr_i = i; + + for k in 0 .. ncols - 1 { + ptr::copy(ptr_in.offset((curr_i + (k + 1) * nremove) as isize), + ptr_out.offset(curr_i as isize), + new_nrows); + + curr_i += new_nrows; + } + + // Deal with the last column from which less values have to be copied. + let remaining_len = nrows - i - nremove; + ptr::copy(ptr_in.offset((nrows * ncols - remaining_len) as isize), + ptr_out.offset(curr_i as isize), + remaining_len); +} + + +unsafe fn extend_rows(data: &mut [N], nrows: usize, ncols: usize, i: usize, ninsert: usize) { + let new_nrows = nrows + ninsert; + let ptr_in = data.as_ptr(); + let ptr_out = data.as_mut_ptr(); + + let remaining_len = nrows - i; + let mut curr_i = new_nrows * ncols - remaining_len; + + // Deal with the last column from which less values have to be copied. + ptr::copy(ptr_in.offset((nrows * ncols - remaining_len) as isize), + ptr_out.offset(curr_i as isize), + remaining_len); + + for k in (0 .. ncols - 1).rev() { + curr_i -= new_nrows; + + ptr::copy(ptr_in.offset((k * nrows + i) as isize), + ptr_out.offset(curr_i as isize), + nrows); + } +} diff --git a/src/core/inverse.rs b/src/core/inverse.rs deleted file mode 100644 index 488c5b5b..00000000 --- a/src/core/inverse.rs +++ /dev/null @@ -1,203 +0,0 @@ -use approx::ApproxEq; - -use alga::general::Field; - -use core::{Scalar, Matrix, SquareMatrix, OwnedSquareMatrix}; -use core::dimension::Dim; -use core::storage::{Storage, StorageMut}; - - -impl SquareMatrix - where N: Scalar + Field + ApproxEq, - S: Storage { - /// Attempts to invert this matrix. - #[inline] - pub fn try_inverse(self) -> Option> { - let mut res = self.into_owned(); - - if res.shape().0 <= 3 { - if res.try_inverse_mut() { - Some(res) - } - else { - None - } - } - else { - gauss_jordan_inverse(res) - } - } -} - - -impl SquareMatrix - where N: Scalar + Field + ApproxEq, - S: StorageMut { - /// Attempts to invert this matrix in-place. Returns `false` and leaves `self` untouched if - /// inversion fails. - #[inline] - pub fn try_inverse_mut(&mut self) -> bool { - assert!(self.is_square(), "Unable to invert a non-square matrix."); - - let dim = self.shape().0; - - unsafe { - match dim { - 0 => true, - 1 => { - let determinant = self.get_unchecked(0, 0).clone(); - if determinant == N::zero() { - false - } - else { - *self.get_unchecked_mut(0, 0) = N::one() / determinant; - true - } - }, - 2 => { - let determinant = self.determinant(); - - if determinant == N::zero() { - false - } - else { - let m11 = *self.get_unchecked(0, 0); let m12 = *self.get_unchecked(0, 1); - let m21 = *self.get_unchecked(1, 0); let m22 = *self.get_unchecked(1, 1); - - *self.get_unchecked_mut(0, 0) = m22 / determinant; - *self.get_unchecked_mut(0, 1) = -m12 / determinant; - - *self.get_unchecked_mut(1, 0) = -m21 / determinant; - *self.get_unchecked_mut(1, 1) = m11 / determinant; - - true - } - }, - 3 => { - let m11 = *self.get_unchecked(0, 0); - let m12 = *self.get_unchecked(0, 1); - let m13 = *self.get_unchecked(0, 2); - - let m21 = *self.get_unchecked(1, 0); - let m22 = *self.get_unchecked(1, 1); - let m23 = *self.get_unchecked(1, 2); - - let m31 = *self.get_unchecked(2, 0); - let m32 = *self.get_unchecked(2, 1); - let m33 = *self.get_unchecked(2, 2); - - - let minor_m12_m23 = m22 * m33 - m32 * m23; - let minor_m11_m23 = m21 * m33 - m31 * m23; - let minor_m11_m22 = m21 * m32 - m31 * m22; - - let determinant = m11 * minor_m12_m23 - - m12 * minor_m11_m23 + - m13 * minor_m11_m22; - - if determinant == N::zero() { - false - } - else { - *self.get_unchecked_mut(0, 0) = minor_m12_m23 / determinant; - *self.get_unchecked_mut(0, 1) = (m13 * m32 - m33 * m12) / determinant; - *self.get_unchecked_mut(0, 2) = (m12 * m23 - m22 * m13) / determinant; - - *self.get_unchecked_mut(1, 0) = -minor_m11_m23 / determinant; - *self.get_unchecked_mut(1, 1) = (m11 * m33 - m31 * m13) / determinant; - *self.get_unchecked_mut(1, 2) = (m13 * m21 - m23 * m11) / determinant; - - *self.get_unchecked_mut(2, 0) = minor_m11_m22 / determinant; - *self.get_unchecked_mut(2, 1) = (m12 * m31 - m32 * m11) / determinant; - *self.get_unchecked_mut(2, 2) = (m11 * m22 - m21 * m12) / determinant; - - true - } - }, - _ => { - let oself = self.clone_owned(); - if let Some(res) = gauss_jordan_inverse(oself) { - self.copy_from(&res); - true - } - else { - false - } - } - } - } - } -} - - -/// Inverts the given matrix using Gauss-Jordan Ellimitation. -fn gauss_jordan_inverse(mut matrix: SquareMatrix) -> Option> - where D: Dim, - N: Scalar + Field + ApproxEq, - S: StorageMut { - - assert!(matrix.is_square(), "Unable to invert a non-square matrix."); - let dim = matrix.data.shape().0; - let mut res: OwnedSquareMatrix = Matrix::identity_generic(dim, dim); - let dim = dim.value(); - - unsafe { - for k in 0 .. dim { - // Search a non-zero value on the k-th column. - // FIXME: would it be worth it to spend some more time searching for the - // max instead? - - let mut n0 = k; // index of a non-zero entry. - - while n0 != dim { - if !matrix.get_unchecked(n0, k).is_zero() { - break; - } - - n0 += 1; - } - - if n0 == dim { - return None - } - - // Swap pivot line. - if n0 != k { - for j in 0 .. dim { - matrix.swap_unchecked((n0, j), (k, j)); - res.swap_unchecked((n0, j), (k, j)); - } - } - - let pivot = *matrix.get_unchecked(k, k); - - for j in k .. dim { - let selfval = *matrix.get_unchecked(k, j) / pivot; - *matrix.get_unchecked_mut(k, j) = selfval; - } - - for j in 0 .. dim { - let resval = *res.get_unchecked(k, j) / pivot; - *res.get_unchecked_mut(k, j) = resval; - } - - for l in 0 .. dim { - if l != k { - let normalizer = *matrix.get_unchecked(l, k); - - for j in k .. dim { - let selfval = *matrix.get_unchecked(l, j) - *matrix.get_unchecked(k, j) * normalizer; - *matrix.get_unchecked_mut(l, j) = selfval; - } - - for j in 0 .. dim { - let resval = *res.get_unchecked(l, j) - *res.get_unchecked(k, j) * normalizer; - *res.get_unchecked_mut(l, j) = resval; - } - } - } - } - - Some(res) - } -} diff --git a/src/core/iter.rs b/src/core/iter.rs index 3c99a4ae..c4cb515b 100644 --- a/src/core/iter.rs +++ b/src/core/iter.rs @@ -81,6 +81,13 @@ macro_rules! iterator { self.size_hint().0 } } + + impl<'a, N: Scalar, R: Dim, C: Dim, S: 'a + $Storage> ExactSizeIterator for $Name<'a, N, R, C, S> { + #[inline] + fn len(&self) -> usize { + self.size + } + } } } diff --git a/src/core/matrix.rs b/src/core/matrix.rs index b56ce61a..4b1f7deb 100644 --- a/src/core/matrix.rs +++ b/src/core/matrix.rs @@ -1,4 +1,5 @@ use num::Zero; +use num_complex::Complex; use std::cmp::Ordering; use std::marker::PhantomData; @@ -15,54 +16,33 @@ use abomonation::Abomonation; use alga::general::{Ring, Real}; -use core::{Scalar, Unit}; +use core::{Scalar, DefaultAllocator, Unit, VectorN, MatrixMN}; use core::dimension::{Dim, DimAdd, DimSum, U1, U2}; -use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, DimEq}; use core::iter::{MatrixIter, MatrixIterMut}; -use core::allocator::{Allocator, OwnedAllocator, SameShapeAllocator, SameShapeR, SameShapeC}; -use core::storage::{Storage, StorageMut, Owned, OwnedStorage, MulStorage, TrMulStorage, SumStorage}; - -/// The type of the result of a matrix allocation by the allocator `A`. -pub type OwnedMatrix = Matrix>::Buffer>; +use core::allocator::{Allocator, SameShapeAllocator, SameShapeR, SameShapeC}; +use core::storage::{Storage, StorageMut, Owned, ContiguousStorage, ContiguousStorageMut, SameShapeStorage}; /// A square matrix. pub type SquareMatrix = Matrix; -/// The type of the result of a square matrix allocation by the allocator `A`. -pub type OwnedSquareMatrix = OwnedMatrix; - /// A matrix with one column and `D` rows. -pub type ColumnVector = Matrix; +pub type Vector = Matrix; -/// An owned matrix with one column and `D` rows. -pub type OwnedColumnVector = OwnedMatrix; - -/// A matrix with one row and `D` columns. +/// A matrix with one row and `D` columns . pub type RowVector = Matrix; -/// An owned matrix with one row and `D` columns. -pub type OwnedRowVector = OwnedMatrix; +/// The type of the result of a matrix sum. +pub type MatrixSum = + Matrix, SameShapeC, SameShapeStorage>; /// The type of the result of a matrix sum. -pub type MatrixSum = - Matrix, SameShapeC, SumStorage>; - -/// The type of the result of a matrix sum. -pub type ColumnVectorSum = - Matrix, U1, SumStorage>; +pub type VectorSum = + Matrix, U1, SameShapeStorage>; /// The type of the result of a matrix cross product. -pub type MatrixCross = MatrixSum; - -/// The type of the result of a matrix multiplication. -pub type MatrixMul = Matrix>; - -/// The type of the result of a matrix transpose-multiplication. -pub type MatrixTrMul = Matrix>; - -/// The matrix with storage `S` and scalar type changed from `NOld` to `NNew`. -pub type MatrixWithScalar = -Matrix>::Alloc as Allocator>::Buffer>; +pub type MatrixCross = + Matrix, SameShapeC, SameShapeStorage>; /// The most generic column-major matrix (and vector) type. /// @@ -92,7 +72,7 @@ Matrix>::Alloc as Allocator>: pub struct Matrix { /// The data storage that contains all the matrix components and informations about its number /// of rows and column (if needed). - pub data: S, + pub data: S, _phantoms: PhantomData<(N, R, C)> } @@ -102,11 +82,9 @@ impl Serialize for Matrix where N: Scalar, R: Dim, C: Dim, - S: Serialize, -{ + S: Serialize, { fn serialize(&self, serializer: T) -> Result - where T: Serializer - { + where T: Serializer { self.data.serialize(serializer) } } @@ -116,8 +94,7 @@ impl<'de, N, R, C, S> Deserialize<'de> for Matrix where N: Scalar, R: Dim, C: Dim, - S: Deserialize<'de>, -{ + S: Deserialize<'de> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { @@ -161,65 +138,6 @@ impl> Matrix { } } - /// Moves this matrix into one that owns its data. - #[inline] - pub fn into_owned(self) -> OwnedMatrix { - Matrix::from_data(self.data.into_owned()) - } - - // FIXME: this could probably benefit from specialization. - // XXX: bad name. - /// Moves this matrix into one that owns its data. The actual type of the result depends on - /// matrix storage combination rules for addition. - #[inline] - pub fn into_owned_sum(self) -> MatrixSum - where R2: Dim, C2: Dim, - S::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - if TypeId::of::>() == TypeId::of::>() { - // We can just return `self.into_owned()`. - - unsafe { - // FIXME: check that those copies are optimized away by the compiler. - let owned = self.into_owned(); - let res = mem::transmute_copy(&owned); - mem::forget(owned); - res - } - } - else { - self.clone_owned_sum() - } - } - - /// Clones this matrix into one that owns its data. - #[inline] - pub fn clone_owned(&self) -> OwnedMatrix { - Matrix::from_data(self.data.clone_owned()) - } - - /// Clones this matrix into one that owns its data. The actual type of the result depends on - /// matrix storage combination rules for addition. - #[inline] - pub fn clone_owned_sum(&self) -> MatrixSum - where R2: Dim, C2: Dim, - S::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - let (nrows, ncols) = self.shape(); - let nrows: SameShapeR = Dim::from_usize(nrows); - let ncols: SameShapeC = Dim::from_usize(ncols); - - let mut res: MatrixSum = unsafe { - Matrix::new_uninitialized_generic(nrows, ncols) - }; - - for (r, s) in res.iter_mut().zip(self.iter()) { - *r = *s - } - - res - } - /// The total number of elements of this matrix. #[inline] pub fn len(&self) -> usize { @@ -282,6 +200,7 @@ impl> Matrix { /// bound-checking. #[inline] pub unsafe fn get_unchecked(&self, irow: usize, icol: usize) -> &N { + debug_assert!(irow < self.nrows() && icol < self.ncols(), "Matrix index out of bounds."); self.data.get_unchecked(irow, icol) } @@ -301,8 +220,168 @@ impl> Matrix { assert!(self.shape() == other.shape()); self.iter().zip(other.iter()).all(|(a, b)| a.relative_eq(b, eps, max_relative)) } + + /// Tests whether `self` and `rhs` are exactly equal. + #[inline] + pub fn eq(&self, other: &Matrix) -> bool + where N: PartialEq, + R2: Dim, C2: Dim, + SB: Storage, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + assert!(self.shape() == other.shape()); + self.iter().zip(other.iter()).all(|(a, b)| *a == *b) + } + + /// Moves this matrix into one that owns its data. + #[inline] + pub fn into_owned(self) -> MatrixMN + where DefaultAllocator: Allocator { + Matrix::from_data(self.data.into_owned()) + } + + // FIXME: this could probably benefit from specialization. + // XXX: bad name. + /// Moves this matrix into one that owns its data. The actual type of the result depends on + /// matrix storage combination rules for addition. + #[inline] + pub fn into_owned_sum(self) -> MatrixSum + where R2: Dim, C2: Dim, + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + if TypeId::of::>() == TypeId::of::>() { + // We can just return `self.into_owned()`. + + unsafe { + // FIXME: check that those copies are optimized away by the compiler. + let owned = self.into_owned(); + let res = mem::transmute_copy(&owned); + mem::forget(owned); + res + } + } + else { + self.clone_owned_sum() + } + } + + /// Clones this matrix to one that owns its data. + #[inline] + pub fn clone_owned(&self) -> MatrixMN + where DefaultAllocator: Allocator { + Matrix::from_data(self.data.clone_owned()) + } + + /// Clones this matrix into one that owns its data. The actual type of the result depends on + /// matrix storage combination rules for addition. + #[inline] + pub fn clone_owned_sum(&self) -> MatrixSum + where R2: Dim, C2: Dim, + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + let (nrows, ncols) = self.shape(); + let nrows: SameShapeR = Dim::from_usize(nrows); + let ncols: SameShapeC = Dim::from_usize(ncols); + + let mut res: MatrixSum = unsafe { + Matrix::new_uninitialized_generic(nrows, ncols) + }; + + // FIXME: use copy_from + for j in 0 .. res.ncols() { + for i in 0 .. res.nrows() { + unsafe { *res.get_unchecked_mut(i, j) = *self.get_unchecked(i, j); } + } + } + + res + } + + /// Returns a matrix containing the result of `f` applied to each of its entries. + #[inline] + pub fn map N2>(&self, mut f: F) -> MatrixMN + where DefaultAllocator: Allocator { + let (nrows, ncols) = self.data.shape(); + + let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) }; + + for j in 0 .. ncols.value() { + for i in 0 .. nrows.value() { + unsafe { + let a = *self.data.get_unchecked(i, j); + *res.data.get_unchecked_mut(i, j) = f(a) + } + } + } + + res + } + + /// Returns a matrix containing the result of `f` applied to each entries of `self` and + /// `rhs`. + #[inline] + pub fn zip_map(&self, rhs: &Matrix, mut f: F) -> MatrixMN + where N2: Scalar, + N3: Scalar, + S2: Storage, + F: FnMut(N, N2) -> N3, + DefaultAllocator: Allocator { + let (nrows, ncols) = self.data.shape(); + + let mut res = unsafe { MatrixMN::new_uninitialized_generic(nrows, ncols) }; + + assert!((nrows.value(), ncols.value()) == rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch."); + + for j in 0 .. ncols.value() { + for i in 0 .. nrows.value() { + unsafe { + let a = *self.data.get_unchecked(i, j); + let b = *rhs.data.get_unchecked(i, j); + *res.data.get_unchecked_mut(i, j) = f(a, b) + } + } + } + + res + } + + /// Transposes `self` and store the result into `out`. + #[inline] + pub fn transpose_to(&self, out: &mut Matrix) + where R2: Dim, C2: Dim, + SB: StorageMut, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + let (nrows, ncols) = self.shape(); + assert!((ncols, nrows) == out.shape(), "Incompatible shape for transpose-copy."); + + // FIXME: optimize that. + for i in 0 .. nrows { + for j in 0 .. ncols { + unsafe { + *out.get_unchecked_mut(j, i) = *self.get_unchecked(i, j); + } + } + } + } + + + /// Transposes `self`. + #[inline] + pub fn transpose(&self) -> MatrixMN + where DefaultAllocator: Allocator { + let (nrows, ncols) = self.data.shape(); + + unsafe { + let mut res = Matrix::new_uninitialized_generic(ncols, nrows); + self.transpose_to(&mut res); + + res + } + } } + impl> Matrix { /// Mutably iterates through this matrix coordinates. #[inline] @@ -313,12 +392,15 @@ impl> Matrix { /// Gets a mutable reference to the i-th element of this matrix. #[inline] pub unsafe fn get_unchecked_mut(&mut self, irow: usize, icol: usize) -> &mut N { + debug_assert!(irow < self.nrows() && icol < self.ncols(), "Matrix index out of bounds."); self.data.get_unchecked_mut(irow, icol) } /// Swaps two entries without bound-checking. #[inline] pub unsafe fn swap_unchecked(&mut self, row_cols1: (usize, usize), row_cols2: (usize, usize)) { + debug_assert!(row_cols1.0 < self.nrows() && row_cols1.1 < self.ncols()); + debug_assert!(row_cols2.0 < self.nrows() && row_cols2.1 < self.ncols()); self.data.swap_unchecked(row_cols1, row_cols2) } @@ -339,114 +421,81 @@ impl> Matrix { ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { assert!(self.shape() == other.shape(), "Unable to copy from a matrix with a different shape."); - for (out, other) in self.iter_mut().zip(other.iter()) { - *out = *other + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + unsafe { *self.get_unchecked_mut(i, j) = *other.get_unchecked(i, j); } + } } } - /// Sets all the entries of this matrix to `value`. + /// Fills this matrix with the content of the transpose another one. #[inline] - pub fn fill(&mut self, value: N) { - for e in self.iter_mut() { - *e = value + pub fn tr_copy_from(&mut self, other: &Matrix) + where R2: Dim, C2: Dim, + SB: Storage, + ShapeConstraint: DimEq + SameNumberOfColumns { + let (nrows, ncols) = self.shape(); + assert!((ncols, nrows) == other.shape(), "Unable to copy from a matrix with incompatible shape."); + + for j in 0 .. ncols { + for i in 0 .. nrows { + unsafe { *self.get_unchecked_mut(i, j) = *other.get_unchecked(j, i); } + } } } - /// Fills the selected row of this matrix with the content of the given vector. + /// Replaces each component of `self` by the result of a closure `f` applied on it. #[inline] - pub fn set_row(&mut self, i: usize, row: &RowVector) - where S2: Storage, - S::Alloc: Allocator, - ShapeConstraint: SameNumberOfColumns { - self.row_mut(i).copy_from(row); - } + pub fn apply N>(&mut self, mut f: F) + where DefaultAllocator: Allocator { + let (nrows, ncols) = self.shape(); - /// Fills the selected column of this matrix with the content of the given vector. - #[inline] - pub fn set_column(&mut self, i: usize, column: &ColumnVector) - where S2: Storage, - S::Alloc: Allocator, - ShapeConstraint: SameNumberOfRows { - self.column_mut(i).copy_from(column); + for j in 0 .. ncols { + for i in 0 .. nrows { + unsafe { + let e = self.data.get_unchecked_mut(i, j); + *e = f(*e) + } + } + } } } -impl> Matrix - // XXX: see the rust issue #26026 - where S::Alloc: OwnedAllocator { +impl> Vector { + /// Gets a reference to the i-th element of this column vector without bound checking. + #[inline] + pub unsafe fn vget_unchecked(&self, i: usize) -> &N { + debug_assert!(i < self.nrows(), "Vector index out of bounds."); + let i = i * self.strides().0; + self.data.get_unchecked_linear(i) + } +} - /// Extracts a slice containing the entire matrix entries orderd column-by-columns. +impl> Vector { + /// Gets a mutable reference to the i-th element of this column vector without bound checking. + #[inline] + pub unsafe fn vget_unchecked_mut(&mut self, i: usize) -> &mut N { + debug_assert!(i < self.nrows(), "Vector index out of bounds."); + let i = i * self.strides().0; + self.data.get_unchecked_linear_mut(i) + } +} + + +impl> Matrix { + /// Extracts a slice containing the entire matrix entries ordered column-by-columns. #[inline] pub fn as_slice(&self) -> &[N] { self.data.as_slice() } +} - /// Extracts a mutable slice containing the entire matrix entries orderd column-by-columns. +impl> Matrix { + /// Extracts a mutable slice containing the entire matrix entries ordered column-by-columns. #[inline] pub fn as_mut_slice(&mut self) -> &mut [N] { self.data.as_mut_slice() } - - /// Returns a matrix containing the result of `f` applied to each of its entries. - #[inline] - pub fn map N>(&self, mut f: F) -> Matrix { - let shape = self.data.shape(); - - let mut res: Matrix; - res = unsafe { Self::new_uninitialized_generic(shape.0, shape.1) }; - - for i in 0 .. shape.0.value() * shape.1.value() { - unsafe { - let a = *self.data.get_unchecked_linear(i); - *res.data.get_unchecked_linear_mut(i) = f(a) - } - } - - res - } - - /// Returns a matrix containing the result of `f` applied to each entries of `self` and - /// `rhs`. - #[inline] - pub fn zip_map N>(&self, rhs: &Matrix, mut f: F) -> Matrix { - let shape_generic = self.data.shape(); - let shape = self.shape(); - - let mut res: Matrix; - res = unsafe { Self::new_uninitialized_generic(shape_generic.0, shape_generic.1) }; - - assert!(shape == rhs.shape(), "Matrix simultaneous traversal error: dimension mismatch."); - - for i in 0 .. shape.0 * shape.1 { - unsafe { - let a = *self.data.get_unchecked_linear(i); - let b = *rhs.data.get_unchecked_linear(i); - *res.data.get_unchecked_linear_mut(i) = f(a, b) - } - } - - res - } -} - -impl> Matrix - where S::Alloc: Allocator { - /// Transposes `self`. - #[inline] - pub fn transpose(&self) -> OwnedMatrix { - let (nrows, ncols) = self.data.shape(); - - unsafe { - let mut res: OwnedMatrix = Matrix::new_uninitialized_generic(ncols, nrows); - for i in 0 .. nrows.value() { - for j in 0 .. ncols.value() { - *res.get_unchecked_mut(j, i) = *self.get_unchecked(i, j); - } - } - - res - } - } } impl> Matrix { @@ -464,20 +513,76 @@ impl> Matrix { } } -impl SquareMatrix - where N: Scalar, - S: Storage, - S::Alloc: Allocator { +impl, R, C>> Matrix, R, C, S> { + /// Takes the conjugate and transposes `self` and store the result into `out`. + #[inline] + pub fn conjugate_transpose_to(&self, out: &mut Matrix, R2, C2, SB>) + where R2: Dim, C2: Dim, + SB: StorageMut, R2, C2>, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + + let (nrows, ncols) = self.shape(); + assert!((ncols, nrows) == out.shape(), "Incompatible shape for transpose-copy."); + + // FIXME: optimize that. + for i in 0 .. nrows { + for j in 0 .. ncols { + unsafe { + *out.get_unchecked_mut(j, i) = self.get_unchecked(i, j).conj(); + } + } + } + } + + /// The conjugate transposition of `self`. + #[inline] + pub fn conjugate_transpose(&self) -> MatrixMN, C, R> + where DefaultAllocator: Allocator, C, R> { + let (nrows, ncols) = self.data.shape(); + + unsafe { + let mut res: MatrixMN<_, C, R> = Matrix::new_uninitialized_generic(ncols, nrows); + self.conjugate_transpose_to(&mut res); + + res + } + } +} + +impl, D, D>> Matrix, D, D, S> { + /// Sets `self` to its conjugate transpose. + pub fn conjugate_transpose_mut(&mut self) { + assert!(self.is_square(), "Unable to transpose a non-square matrix in-place."); + + let dim = self.shape().0; + + for i in 1 .. dim { + for j in 0 .. i { + unsafe { + let ref_ij = self.get_unchecked_mut(i, j) as *mut Complex; + let ref_ji = self.get_unchecked_mut(j, i) as *mut Complex; + let conj_ij = (*ref_ij).conj(); + let conj_ji = (*ref_ji).conj(); + *ref_ij = conj_ji; + *ref_ji = conj_ij; + } + } + } + } +} + +impl> SquareMatrix { /// Creates a square matrix with its diagonal set to `diag` and all other entries set to 0. #[inline] - pub fn diagonal(&self) -> OwnedColumnVector { - assert!(self.is_square(), "Unable to get the diagonal of a non-square."); + pub fn diagonal(&self) -> VectorN + where DefaultAllocator: Allocator { + assert!(self.is_square(), "Unable to get the diagonal of a non-square matrix."); let dim = self.data.shape().0; - let mut res = unsafe { OwnedColumnVector::::new_uninitialized_generic(dim, U1) }; + let mut res = unsafe { VectorN::new_uninitialized_generic(dim, U1) }; for i in 0 .. dim.value() { - unsafe { *res.get_unchecked_mut(i, 0) = *self.get_unchecked(i, i); } + unsafe { *res.vget_unchecked_mut(i) = *self.get_unchecked(i, i); } } res @@ -501,18 +606,16 @@ impl SquareMatrix } } -impl ColumnVector - where N: Scalar + Zero, - D: DimAdd, - S: Storage, - S::Alloc: Allocator, U1> { +impl, S: Storage> Vector { /// Computes the coordinates in projective space of this vector, i.e., appends a `0` to its /// coordinates. #[inline] - pub fn to_homogeneous(&self) -> OwnedColumnVector, S::Alloc> { + pub fn to_homogeneous(&self) -> VectorN> + where DefaultAllocator: Allocator> { + let len = self.len(); let hnrows = DimSum::::from_usize(len + 1); - let mut res = unsafe { OwnedColumnVector::::new_uninitialized_generic(hnrows, U1) }; + let mut res = unsafe { VectorN::::new_uninitialized_generic(hnrows, U1) }; res.generic_slice_mut((0, 0), self.data.shape()).copy_from(self); res[(len, 0)] = N::zero(); @@ -522,8 +625,9 @@ impl ColumnVector /// Constructs a vector from coordinates in projective space, i.e., removes a `0` at the end of /// `self`. Returns `None` if this last component is not zero. #[inline] - pub fn from_homogeneous(v: ColumnVector, SB>) -> Option> - where SB: Storage, U1, Alloc = S::Alloc> { + pub fn from_homogeneous(v: Vector, SB>) -> Option> + where SB: Storage>, + DefaultAllocator: Allocator { if v[v.len() - 1].is_zero() { let nrows = D::from_usize(v.len() - 1); Some(v.generic_slice((0, 0), (nrows, U1)).into_owned()) @@ -534,232 +638,6 @@ impl ColumnVector } } - -// // /* -// // * -// // * Conversions (AsRef, AsMut, From) -// // * -// // */ -// // impl FromIterator for Matrix -// // where N: Scalar + Rand, -// // R: Dim, -// // C: Dim, -// // A: Allocator { -// // #[inline] -// // fn from_iter>(iter: I) -> Matrix { -// // let mut iter = iter.into_iter(); -// // } -// // } -// // -// // impl AsRef<[[N; $dimension]; $dimension]> for $t { -// // #[inline] -// // fn as_ref(&self) -> &[[N; $dimension]; $dimension] { -// // unsafe { -// // mem::transmute(self) -// // } -// // } -// // } -// -// // impl AsMut<[[N; $dimension]; $dimension]> for $t { -// // #[inline] -// // fn as_mut(&mut self) -> &mut [[N; $dimension]; $dimension] { -// // unsafe { -// // mem::transmute(self) -// // } -// // } -// // } -// -// // impl<'a, N> From<&'a [[N; $dimension]; $dimension]> for &'a $t { -// // #[inline] -// // fn from(arr: &'a [[N; $dimension]; $dimension]) -> &'a $t { -// // unsafe { -// // mem::transmute(arr) -// // } -// // } -// // } -// -// // impl<'a, N> From<&'a mut [[N; $dimension]; $dimension]> for &'a mut $t { -// // #[inline] -// // fn from(arr: &'a mut [[N; $dimension]; $dimension]) -> &'a mut $t { -// // unsafe { -// // mem::transmute(arr) -// // } -// // } -// // } -// -// // impl<'a, N: Clone> From<&'a [[N; $dimension]; $dimension]> for $t { -// // #[inline] -// // fn from(arr: &'a [[N; $dimension]; $dimension]) -> $t { -// // let tref: &$t = From::from(arr); -// // tref.clone() -// // } -// // } -// -// // impl MatrixEdit for $t { -// // type RowSlice = $dvector; -// // type ColumnSlice = $dvector; -// // type MinorMatrix = $tsmaller; -// // -// // #[inline] -// // fn column_slice(&self, cid: usize, rstart: usize, rend: usize) -> Self::ColumnSlice { -// // let column = self.column(cid); -// // -// // $dvector::from_slice(rend - rstart, &column.as_ref()[rstart .. rend]) -// // } -// -// // #[inline] -// // fn row_slice(&self, rid: usize, cstart: usize, cend: usize) -> Self::RowSlice { -// // let row = self.row(rid); -// // -// // $dvector::from_slice(cend - cstart, &row.as_ref()[cstart .. cend]) -// // } -// -// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). -// // #[inline] -// // fn delete_row_column(&self, row_id: usize, column_id: usize) -> Self::MinorMatrix { -// // assert!(row_id < $dimension && column_id < $dimension); -// // -// // unsafe { -// // let mut res = $tsmaller::new_uninitialized_generic($dimension - 1, $dimension - 1); -// // -// // for irow in 0 .. row_id { -// // for icol in 0 .. column_id { -// // res.unsafe_set((irow, icol), self.unsafe_at((irow, icol))) -// // } -// // -// // for icol in column_id + 1 .. $dimension { -// // res.unsafe_set((irow, icol - 1), self.unsafe_at((irow, icol))) -// // } -// // } -// // -// // for irow in row_id + 1 .. $dimension { -// // for icol in 0 .. column_id { -// // res.unsafe_set((irow - 1, icol), self.unsafe_at((irow, icol))) -// // } -// // -// // for icol in column_id + 1 .. $dimension { -// // res.unsafe_set((irow - 1, icol - 1), self.unsafe_at((irow, icol))) -// // } -// // } -// // -// // res -// // } -// // } -// -// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). -// // #[inline] -// // fn swap_rows(&mut self, row_id1: usize, row_id2: usize) { -// // if row_id1 != row_id2 { -// // assert!(row_id1 < $dimension && row_id2 < $dimension); -// // -// // for icol in 0 .. $dimension { -// // self.swap((row_id1, icol), (row_id2, icol)) -// // } -// // } -// // } -// // -// // // FIXME: optimize that (+ this is a Copy/paste from dmatrix). -// // #[inline] -// // fn swap_columns(&mut self, column_id1: usize, column_id2: usize) { -// // if column_id1 != column_id2 { -// // assert!(column_id1 < $dimension && column_id2 < $dimension); -// // -// // for irow in 0 .. $dimension { -// // self.swap((irow, column_id1), (irow, column_id2)) -// // } -// // } -// // } -// // } -// // -// // /* -// // * -// // * Mean -// // * -// // */ -// // impl> Mean<$vector> for $t { -// // fn mean(&self) -> $vector { -// // let mut res: $vector = ::zero(); -// // let normalizer: N = ::convert(1.0f64 / $dimension as f64); -// // -// // for i in 0 .. $dimension { -// // for j in 0 .. $dimension { -// // unsafe { -// // let acc = res.unsafe_at(j) + self.unsafe_at((i, j)) * normalizer; -// // res.unsafe_set(j, acc); -// // } -// // } -// // } -// // -// // res -// // } -// // } -// // -// // /* -// // * -// // * Componentwise unary operations. -// // * -// // */ -// // componentwise_absolute!($t, $($compN),+); -// // ) -// // ); -// -// -// // FIXME: specialize for row-major/column major -// // -// -// // macro_rules! to_homogeneous_impl( -// // ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( -// // impl ToHomogeneous<$t2> for $t { -// // #[inline] -// // fn to_homogeneous(&self) -> $t2 { -// // let mut res: $t2 = ::one(); -// // -// // for i in 0 .. $dimension { -// // for j in 0 .. $dimension { -// // res[(i, j)] = self[(i, j)] -// // } -// // } -// // -// // res -// // } -// // } -// // ) -// // ); -// -// // macro_rules! from_homogeneous_impl( -// // ($t: ident, $t2: ident, $dimension: expr, $dim2: expr) => ( -// // impl FromHomogeneous<$t2> for $t { -// // #[inline] -// // fn from(m: &$t2) -> $t { -// // let mut res: $t = ::one(); -// // -// // for i in 0 .. $dimension { -// // for j in 0 .. $dimension { -// // res[(i, j)] = m[(i, j)] -// // } -// // } -// // -// // // FIXME: do we have to deal the lost components -// // // (like if the 1 is not a 1… do we have to divide?) -// // -// // res -// // } -// // } -// // ) -// // ); -// -// -// // macro_rules! eigen_qr_impl( -// // ($t: ident, $v: ident) => ( -// // impl EigenQR for $t { -// // fn eigen_qr(&self, eps: N, niter: usize) -> ($t, $v) { -// // linalg::eigen_qr(self, eps, niter) -// // } -// // } -// // ) -// // ); - - impl ApproxEq for Matrix where N: Scalar + ApproxEq, S: Storage, @@ -795,7 +673,7 @@ impl ApproxEq for Matrix impl PartialOrd for Matrix where N: Scalar + PartialOrd, - S: Storage { + S: Storage { #[inline] fn partial_cmp(&self, other: &Self) -> Option { assert!(self.shape() == other.shape(), "Matrix comparison error: dimensions mismatch."); @@ -873,107 +751,62 @@ impl PartialEq for Matrix } -// FIXME: the bounds are much too restrictive here! This won't even work for, e.g., -// integer-valued matrices... impl fmt::Display for Matrix - where N: Real + fmt::Display, + where N: Scalar + fmt::Display, S: Storage, - S::Alloc: Allocator { - // XXX: will not always work correctly due to rounding errors. + DefaultAllocator: Allocator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fn integral_length(val: &N) -> usize { - let mut res = 1; - let mut curr: N = ::convert(10.0f64); - - while curr <= *val { - curr = curr * ::convert(10.0f64); - res += 1; - } - - if val.is_sign_negative() { - res + 1 - } - else { - res + fn val_width(val: N, f: &mut fmt::Formatter) -> usize { + match f.precision() { + Some(precision) => format!("{:.1$}", val, precision).chars().count(), + None => format!("{}", val).chars().count() } } let (nrows, ncols) = self.data.shape(); - let mut max_decimal_length = 0; - let mut decimal_lengths: MatrixWithScalar = - Matrix::from_element_generic(nrows, ncols, 0); + + if nrows.value() == 0 || ncols.value() == 0 { + return write!(f, "[ ]"); + } + + let mut max_length = 0; + let mut lengths: MatrixMN = Matrix::zeros_generic(nrows, ncols); let (nrows, ncols) = self.shape(); for i in 0 .. nrows { for j in 0 .. ncols { - decimal_lengths[(i, j)] = integral_length(&self[(i, j)]); - max_decimal_length = ::max(max_decimal_length, decimal_lengths[(i, j)]); + lengths[(i, j)] = val_width(self[(i, j)], f); + max_length = ::max(max_length, lengths[(i, j)]); } } - let precision = f.precision().unwrap_or(3); - let max_number_length = max_decimal_length + precision + 1; + let max_length_with_space = max_length + 1; - try!(writeln!(f, " ┌ {:>width$} ┐", "", width = max_number_length * ncols + ncols - 1)); + try!(writeln!(f, "")); + try!(writeln!(f, " ┌ {:>width$} ┐", "", width = max_length_with_space * ncols - 1)); for i in 0 .. nrows { try!(write!(f, " │")); for j in 0 .. ncols { - let number_length = decimal_lengths[(i, j)] + precision + 1; - let pad = max_number_length - number_length; + let number_length = lengths[(i, j)] + 1; + let pad = max_length_with_space - number_length; try!(write!(f, " {:>thepad$}", "", thepad = pad)); - try!(write!(f, "{:.*}", precision, (*self)[(i, j)])); + match f.precision() { + Some(precision) => try!(write!(f, "{:.1$}", (*self)[(i, j)], precision)), + None => try!(write!(f, "{}", (*self)[(i, j)])) + } } try!(writeln!(f, " │")); } - writeln!(f, " └ {:>width$} ┘", "", width = max_number_length * ncols + ncols - 1) + try!(writeln!(f, " └ {:>width$} ┘", "", width = max_length_with_space * ncols - 1)); + writeln!(f, "") } } -impl Matrix - where N: Scalar + Ring, - S: Storage { - /// The dot product between two matrices (seen as vectors). - #[inline] - pub fn dot(&self, other: &Matrix) -> N - where SB: Storage, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - assert!(self.shape() == other.shape(), "Dot product dimension mismatch."); - self.iter().zip(other.iter()).fold(N::zero(), |acc, (a, b)| acc + *a * *b) - } - - // FIXME: we could specialize this for when we only have vectors in which case we can just use - // `iter().zip(iter())` as for the regular `.dot` method. - /// The dot product between the transpose of `self` and `other`. - #[inline] - pub fn tr_dot(&self, other: &Matrix) -> N - where SB: Storage, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - let (nrows, ncols) = self.shape(); - assert!((ncols, nrows) == other.shape(), "Dot product dimension mismatch."); - - let mut res = N::zero(); - - for i in 0 .. nrows { - for j in 0 .. ncols { - unsafe { - res += *self.get_unchecked(i, j) * *other.get_unchecked(j, i); - } - } - } - - res - } - - - /// The squared L2 norm of this matrix. - #[inline] - pub fn norm_squared(&self) -> N { - self.dot(self) - } - +impl> Matrix { /// The perpendicular product between two 2D column vectors, i.e. `a.x * b.y - a.y * b.x`. #[inline] pub fn perp(&self, b: &Matrix) -> N @@ -997,10 +830,10 @@ impl Matrix /// Panics if the shape is not 3D vector. In the future, this will be implemented only for /// dynamically-sized matrices and statically-sized 3D matrices. #[inline] - pub fn cross(&self, b: &Matrix) -> MatrixCross + pub fn cross(&self, b: &Matrix) -> MatrixCross where R2: Dim, C2: Dim, SB: Storage, - S::Alloc: SameShapeAllocator, + DefaultAllocator: SameShapeAllocator, ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { let shape = self.shape(); assert!(shape == b.shape(), "Vector cross product dimension mismatch."); @@ -1031,7 +864,7 @@ impl Matrix } else { unsafe { - // FIXME: soooo ugly! + // FIXME: ugly! let nrows = SameShapeR::::from_usize(1); let ncols = SameShapeC::::from_usize(3); let mut res = Matrix::new_uninitialized_generic(nrows, ncols); @@ -1054,14 +887,12 @@ impl Matrix } } -impl Matrix - where N: Real, - S: Storage { - /// The smallest angle between two matrices seen as vectors. +impl> Matrix { + /// The smallest angle between two vectors. #[inline] pub fn angle(&self, other: &Matrix) -> N where SB: Storage, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + ShapeConstraint: DimEq + DimEq { let prod = self.dot(other); let n1 = self.norm(); let n2 = other.norm(); @@ -1083,6 +914,21 @@ impl Matrix } } } +} + +impl> Matrix { + /// The squared L2 norm of this vector. + #[inline] + pub fn norm_squared(&self) -> N { + let mut res = N::zero(); + + for i in 0 .. self.ncols() { + let col = self.column(i); + res += col.dot(&col) + } + + res + } /// The L2 norm of this matrix. #[inline] @@ -1092,13 +938,15 @@ impl Matrix /// Returns a normalized version of this matrix. #[inline] - pub fn normalize(&self) -> OwnedMatrix { + pub fn normalize(&self) -> MatrixMN + where DefaultAllocator: Allocator { self / self.norm() } /// Returns a normalized version of this matrix unless its norm as smaller or equal to `eps`. #[inline] - pub fn try_normalize(&self, min_norm: N) -> Option> { + pub fn try_normalize(&self, min_norm: N) -> Option> + where DefaultAllocator: Allocator { let n = self.norm(); if n <= min_norm { @@ -1110,9 +958,7 @@ impl Matrix } } -impl Matrix - where N: Real, - S: StorageMut { +impl> Matrix { /// Normalizes this matrix in-place and returns its norm. #[inline] pub fn normalize_mut(&mut self) -> N { diff --git a/src/core/matrix_alga.rs b/src/core/matrix_alga.rs index ed0bad14..37b0b27f 100644 --- a/src/core/matrix_alga.rs +++ b/src/core/matrix_alga.rs @@ -7,42 +7,39 @@ use alga::general::{AbstractMagma, AbstractGroupAbelian, AbstractGroup, Abstract ClosedAdd, ClosedNeg, ClosedMul}; use alga::linear::{VectorSpace, NormedSpace, InnerSpace, FiniteDimVectorSpace, FiniteDimInnerSpace}; -use core::{Scalar, Matrix, SquareMatrix}; +use core::{DefaultAllocator, Scalar, MatrixMN, MatrixN}; use core::dimension::{Dim, DimName}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; /* * * Additive structures. * */ -impl Identity for Matrix +impl Identity for MatrixMN where N: Scalar + Zero, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::from_element(N::zero()) } } -impl AbstractMagma for Matrix +impl AbstractMagma for MatrixMN where N: Scalar + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn operate(&self, other: &Self) -> Self { self + other } } -impl Inverse for Matrix +impl Inverse for MatrixMN where N: Scalar + ClosedNeg, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] - fn inverse(&self) -> Matrix { + fn inverse(&self) -> MatrixMN { -self } @@ -54,10 +51,9 @@ impl Inverse for Matrix macro_rules! inherit_additive_structure( ($($marker: ident<$operator: ident> $(+ $bounds: ident)*),* $(,)*) => {$( - impl $marker<$operator> for Matrix + impl $marker<$operator> for MatrixMN where N: Scalar + $marker<$operator> $(+ $bounds)*, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + DefaultAllocator: Allocator { } )*} ); @@ -70,10 +66,9 @@ inherit_additive_structure!( AbstractGroupAbelian + Zero + ClosedAdd + ClosedNeg ); -impl AbstractModule for Matrix +impl AbstractModule for MatrixMN where N: Scalar + RingCommutative, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { type AbstractRing = N; #[inline] @@ -82,24 +77,21 @@ impl AbstractModule for Matrix } } -impl Module for Matrix +impl Module for MatrixMN where N: Scalar + RingCommutative, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { type Ring = N; } -impl VectorSpace for Matrix +impl VectorSpace for MatrixMN where N: Scalar + Field, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { type Field = N; } -impl FiniteDimVectorSpace for Matrix +impl FiniteDimVectorSpace for MatrixMN where N: Scalar + Field, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn dimension() -> usize { R::dim() * C::dim() @@ -131,10 +123,8 @@ impl FiniteDimVectorSpace for Matrix } } -impl NormedSpace for Matrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl NormedSpace for MatrixMN + where DefaultAllocator: Allocator { #[inline] fn norm_squared(&self) -> N { self.norm_squared() @@ -166,10 +156,8 @@ impl NormedSpace for Matrix } } -impl InnerSpace for Matrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl InnerSpace for MatrixMN + where DefaultAllocator: Allocator { type Real = N; #[inline] @@ -187,12 +175,10 @@ impl InnerSpace for Matrix // In particular: // − use `x()` instead of `::canonical_basis_element` // − use `::new(x, y, z)` instead of `::from_slice` -impl FiniteDimInnerSpace for Matrix - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl FiniteDimInnerSpace for MatrixMN + where DefaultAllocator: Allocator { #[inline] - fn orthonormalize(vs: &mut [Matrix]) -> usize { + fn orthonormalize(vs: &mut [MatrixMN]) -> usize { let mut nbasis_elements = 0; for i in 0 .. vs.len() { @@ -229,7 +215,7 @@ impl FiniteDimInnerSpace for Matrix match Self::dimension() { 1 => { if vs.len() == 0 { - f(&Self::canonical_basis_element(0)); + let _ = f(&Self::canonical_basis_element(0)); } }, 2 => { @@ -241,7 +227,7 @@ impl FiniteDimInnerSpace for Matrix let v = &vs[0]; let res = Self::from_column_slice(&[-v[1], v[0]]); - f(&res.normalize()); + let _ = f(&res.normalize()); } // Otherwise, nothing. @@ -266,11 +252,11 @@ impl FiniteDimInnerSpace for Matrix let _ = a.normalize_mut(); if f(&a.cross(v)) { - f(&a); + let _ = f(&a); } } else if vs.len() == 2 { - f(&vs[0].cross(&vs[1]).normalize()); + let _ = f(&vs[0].cross(&vs[1]).normalize()); } }, _ => { @@ -307,20 +293,18 @@ impl FiniteDimInnerSpace for Matrix * * */ -impl Identity for SquareMatrix +impl Identity for MatrixN where N: Scalar + Zero + One, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::identity() } } -impl AbstractMagma for SquareMatrix - where N: Scalar + Zero + ClosedAdd + ClosedMul, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for MatrixN + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, + DefaultAllocator: Allocator { #[inline] fn operate(&self, other: &Self) -> Self { self * other @@ -329,10 +313,9 @@ impl AbstractMagma for SquareMatrix macro_rules! impl_multiplicative_structure( ($($marker: ident<$operator: ident> $(+ $bounds: ident)*),* $(,)*) => {$( - impl $marker<$operator> for SquareMatrix - where N: Scalar + Zero + ClosedAdd + ClosedMul + $marker<$operator> $(+ $bounds)*, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for MatrixN + where N: Scalar + Zero + One + ClosedAdd + ClosedMul + $marker<$operator> $(+ $bounds)*, + DefaultAllocator: Allocator { } )*} ); @@ -341,421 +324,24 @@ impl_multiplicative_structure!( AbstractMonoid + One ); -// // FIXME: Field too strong? -// impl Matrix for Matrix -// where N: Scalar + Field, -// S: Storage { -// type Field = N; -// type Row = OwnedMatrix, S::C, S::Alloc>; -// type Column = OwnedMatrix, S::Alloc>; -// type Transpose = OwnedMatrix; - -// #[inline] -// fn nrows(&self) -> usize { -// self.shape().0 -// } - -// #[inline] -// fn ncolumns(&self) -> usize { -// self.shape().1 -// } - -// #[inline] -// fn row(&self, row: usize) -> Self::Row { -// let mut res: Self::Row = ::zero(); - -// for (column, e) in res.iter_mut().enumerate() { -// *e = self[(row, column)]; -// } - -// res -// } - -// #[inline] -// fn column(&self, column: usize) -> Self::Column { -// let mut res: Self::Column = ::zero(); - -// for (row, e) in res.iter_mut().enumerate() { -// *e = self[(row, column)]; -// } - -// res -// } - -// #[inline] -// unsafe fn get_unchecked(&self, i: usize, j: usize) -> Self::Field { -// self.get_unchecked(i, j) -// } - -// #[inline] -// fn transpose(&self) -> Self::Transpose { -// self.transpose() -// } -// } - -// impl MatrixMut for Matrix -// where N: Scalar + Field, -// S: StorageMut { -// #[inline] -// fn set_row_mut(&mut self, irow: usize, row: &Self::Row) { -// assert!(irow < self.shape().0, "Row index out of bounds."); - -// for (icol, e) in row.iter().enumerate() { -// unsafe { self.set_unchecked(irow, icol, *e) } -// } -// } - -// #[inline] -// fn set_column_mut(&mut self, icol: usize, col: &Self::Column) { -// assert!(icol < self.shape().1, "Column index out of bounds."); -// for (irow, e) in col.iter().enumerate() { -// unsafe { self.set_unchecked(irow, icol, *e) } -// } -// } - -// #[inline] -// unsafe fn set_unchecked(&mut self, i: usize, j: usize, val: Self::Field) { -// *self.get_unchecked_mut(i, j) = val -// } -// } - -// // FIXME: Real is needed here only for invertibility... -// impl SquareMatrixMut for $t { -// #[inline] -// fn from_diagonal(diag: &Self::Coordinates) -> Self { -// let mut res: $t = ::zero(); -// res.set_diagonal_mut(diag); -// res -// } - -// #[inline] -// fn set_diagonal_mut(&mut self, diag: &Self::Coordinates) { -// for (i, e) in diag.iter().enumerate() { -// unsafe { self.set_unchecked(i, i, *e) } -// } -// } -// } - - - -// Specializations depending on the dimension. -// matrix_group_approx_impl!(common: $t, 1, $vector, $($compN),+); - -// // FIXME: Real is needed here only for invertibility... -// impl SquareMatrix for $t { -// type Vector = $vector; - -// #[inline] -// fn diagonal(&self) -> Self::Coordinates { -// $vector::new(self.m11) -// } - -// #[inline] -// fn determinant(&self) -> Self::Field { -// self.m11 -// } - -// #[inline] -// fn try_inverse(&self) -> Option { -// let mut res = *self; -// if res.try_inverse_mut() { -// Some(res) -// } -// else { -// None -// } -// } - -// #[inline] -// fn try_inverse_mut(&mut self) -> bool { -// if relative_eq!(&self.m11, &::zero()) { -// false -// } -// else { -// self.m11 = ::one::() / ::determinant(self); - -// true -// } -// } - -// #[inline] -// fn transpose_mut(&mut self) { -// // no-op -// } -// } - -// ident, 2, $vector: ident, $($compN: ident),+) => { -// matrix_group_approx_impl!(common: $t, 2, $vector, $($compN),+); - -// // FIXME: Real is needed only for inversion here. -// impl SquareMatrix for $t { -// type Vector = $vector; - -// #[inline] -// fn diagonal(&self) -> Self::Coordinates { -// $vector::new(self.m11, self.m22) -// } - -// #[inline] -// fn determinant(&self) -> Self::Field { -// self.m11 * self.m22 - self.m21 * self.m12 -// } - -// #[inline] -// fn try_inverse(&self) -> Option { -// let mut res = *self; -// if res.try_inverse_mut() { -// Some(res) -// } -// else { -// None -// } -// } - -// #[inline] -// fn try_inverse_mut(&mut self) -> bool { -// let determinant = ::determinant(self); - -// if relative_eq!(&determinant, &::zero()) { -// false -// } -// else { -// *self = Matrix2::new( -// self.m22 / determinant , -self.m12 / determinant, -// -self.m21 / determinant, self.m11 / determinant); - -// true -// } -// } - -// #[inline] -// fn transpose_mut(&mut self) { -// mem::swap(&mut self.m12, &mut self.m21) -// } -// } - -// ident, 3, $vector: ident, $($compN: ident),+) => { -// matrix_group_approx_impl!(common: $t, 3, $vector, $($compN),+); - -// // FIXME: Real is needed only for inversion here. -// impl SquareMatrix for $t { -// type Vector = $vector; - -// #[inline] -// fn diagonal(&self) -> Self::Coordinates { -// $vector::new(self.m11, self.m22, self.m33) -// } - -// #[inline] -// fn determinant(&self) -> Self::Field { -// let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; -// let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; -// let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; - -// self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22 -// } - -// #[inline] -// fn try_inverse(&self) -> Option { -// let mut res = *self; -// if res.try_inverse_mut() { -// Some(res) -// } -// else { -// None -// } -// } - -// #[inline] -// fn try_inverse_mut(&mut self) -> bool { -// let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23; -// let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23; -// let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22; - -// let determinant = self.m11 * minor_m12_m23 - -// self.m12 * minor_m11_m23 + -// self.m13 * minor_m11_m22; - -// if relative_eq!(&determinant, &::zero()) { -// false -// } -// else { -// *self = Matrix3::new( -// (minor_m12_m23 / determinant), -// ((self.m13 * self.m32 - self.m33 * self.m12) / determinant), -// ((self.m12 * self.m23 - self.m22 * self.m13) / determinant), - -// (-minor_m11_m23 / determinant), -// ((self.m11 * self.m33 - self.m31 * self.m13) / determinant), -// ((self.m13 * self.m21 - self.m23 * self.m11) / determinant), - -// (minor_m11_m22 / determinant), -// ((self.m12 * self.m31 - self.m32 * self.m11) / determinant), -// ((self.m11 * self.m22 - self.m21 * self.m12) / determinant) -// ); - -// true -// } -// } - -// #[inline] -// fn transpose_mut(&mut self) { -// mem::swap(&mut self.m12, &mut self.m21); -// mem::swap(&mut self.m13, &mut self.m31); -// mem::swap(&mut self.m23, &mut self.m32); -// } -// } - -// ident, $dimension: expr, $vector: ident, $($compN: ident),+) => { -// matrix_group_approx_impl!(common: $t, $dimension, $vector, $($compN),+); - -// // FIXME: Real is needed only for inversion here. -// impl SquareMatrix for $t { -// type Vector = $vector; - -// #[inline] -// fn diagonal(&self) -> Self::Coordinates { -// let mut diagonal: $vector = ::zero(); - -// for i in 0 .. $dimension { -// unsafe { diagonal.unsafe_set(i, self.get_unchecked(i, i)) } -// } - -// diagonal -// } - -// #[inline] -// fn determinant(&self) -> Self::Field { -// // FIXME: extremely naive implementation. -// let mut det = ::zero(); - -// for icol in 0 .. $dimension { -// let e = unsafe { self.unsafe_at((0, icol)) }; - -// if e != ::zero() { -// let minor_mat = self.delete_row_column(0, icol); -// let minor = minor_mat.determinant(); - -// if icol % 2 == 0 { -// det += minor; -// } -// else { -// det -= minor; -// } -// } -// } - -// det -// } - -// #[inline] -// fn try_inverse(&self) -> Option { -// let mut res = *self; -// if res.try_inverse_mut() { -// Some(res) -// } -// else { -// None -// } -// } - -// #[inline] -// fn try_inverse_mut(&mut self) -> bool { -// let mut res: $t = ::one(); - -// // Inversion using Gauss-Jordan elimination -// for k in 0 .. $dimension { -// // search a non-zero value on the k-th column -// // FIXME: would it be worth it to spend some more time searching for the -// // max instead? - -// let mut n0 = k; // index of a non-zero entry - -// while n0 != $dimension { -// if self[(n0, k)] != ::zero() { -// break; -// } - -// n0 = n0 + 1; -// } - -// if n0 == $dimension { -// return false -// } - -// // swap pivot line -// if n0 != k { -// for j in 0 .. $dimension { -// self.swap((n0, j), (k, j)); -// res.swap((n0, j), (k, j)); -// } -// } - -// let pivot = self[(k, k)]; - -// for j in k .. $dimension { -// let selfval = self[(k, j)] / pivot; -// self[(k, j)] = selfval; -// } - -// for j in 0 .. $dimension { -// let resval = res[(k, j)] / pivot; -// res[(k, j)] = resval; -// } - -// for l in 0 .. $dimension { -// if l != k { -// let normalizer = self[(l, k)]; - -// for j in k .. $dimension { -// let selfval = self[(l, j)] - self[(k, j)] * normalizer; -// self[(l, j)] = selfval; -// } - -// for j in 0 .. $dimension { -// let resval = res[(l, j)] - res[(k, j)] * normalizer; -// res[(l, j)] = resval; -// } -// } -// } -// } - -// *self = res; - -// true -// } - -// #[inline] -// fn transpose_mut(&mut self) { -// for i in 1 .. $dimension { -// for j in 0 .. i { -// self.swap((i, j), (j, i)) -// } -// } -// } - - - /* * * Ordering * */ -impl MeetSemilattice for Matrix +impl MeetSemilattice for MatrixMN where N: Scalar + MeetSemilattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn meet(&self, other: &Self) -> Self { self.zip_map(other, |a, b| a.meet(&b)) } } -impl JoinSemilattice for Matrix +impl JoinSemilattice for MatrixMN where N: Scalar + JoinSemilattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn join(&self, other: &Self) -> Self { self.zip_map(other, |a, b| a.join(&b)) @@ -763,10 +349,9 @@ impl JoinSemilattice for Matrix } -impl Lattice for Matrix +impl Lattice for MatrixMN where N: Scalar + Lattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn meet_join(&self, other: &Self) -> (Self, Self) { let shape = self.data.shape(); diff --git a/src/core/matrix_array.rs b/src/core/matrix_array.rs index 86544ec0..5ebec52a 100644 --- a/src/core/matrix_array.rs +++ b/src/core/matrix_array.rs @@ -21,7 +21,7 @@ use generic_array::{ArrayLength, GenericArray}; use core::Scalar; use core::dimension::{DimName, U1}; -use core::storage::{Storage, StorageMut, Owned, OwnedStorage}; +use core::storage::{Storage, StorageMut, Owned, ContiguousStorage, ContiguousStorageMut}; use core::allocator::Allocator; use core::default_allocator::DefaultAllocator; @@ -139,22 +139,10 @@ unsafe impl Storage for MatrixArray R: DimName, C: DimName, R::Value: Mul, - Prod: ArrayLength { + Prod: ArrayLength, + DefaultAllocator: Allocator { type RStride = U1; type CStride = R; - type Alloc = DefaultAllocator; - - #[inline] - fn into_owned(self) -> Owned { - self - } - - #[inline] - fn clone_owned(&self) -> Owned { - let it = self.iter().cloned(); - - Self::Alloc::allocate_from_iterator(self.shape().0, self.shape().1, it) - } #[inline] fn ptr(&self) -> *const N { @@ -170,30 +158,44 @@ unsafe impl Storage for MatrixArray fn strides(&self) -> (Self::RStride, Self::CStride) { (Self::RStride::name(), Self::CStride::name()) } + + #[inline] + fn is_contiguous(&self) -> bool { + true + } + + #[inline] + fn into_owned(self) -> Owned + where DefaultAllocator: Allocator { + self + } + + #[inline] + fn clone_owned(&self) -> Owned + where DefaultAllocator: Allocator { + let it = self.iter().cloned(); + + DefaultAllocator::allocate_from_iterator(self.shape().0, self.shape().1, it) + } + + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } } + unsafe impl StorageMut for MatrixArray where N: Scalar, R: DimName, C: DimName, R::Value: Mul, - Prod: ArrayLength { + Prod: ArrayLength, + DefaultAllocator: Allocator { #[inline] fn ptr_mut(&mut self) -> *mut N { self[..].as_mut_ptr() } -} - -unsafe impl OwnedStorage for MatrixArray - where N: Scalar, - R: DimName, - C: DimName, - R::Value: Mul, - Prod: ArrayLength { - #[inline] - fn as_slice(&self) -> &[N] { - &self[..] - } #[inline] fn as_mut_slice(&mut self) -> &mut [N] { @@ -201,6 +203,24 @@ unsafe impl OwnedStorage for MatrixArray } } +unsafe impl ContiguousStorage for MatrixArray + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength, + DefaultAllocator: Allocator { +} + +unsafe impl ContiguousStorageMut for MatrixArray + where N: Scalar, + R: DimName, + C: DimName, + R::Value: Mul, + Prod: ArrayLength, + DefaultAllocator: Allocator { +} + /* * diff --git a/src/core/matrix_slice.rs b/src/core/matrix_slice.rs index 8a09fb5f..71b2ea99 100644 --- a/src/core/matrix_slice.rs +++ b/src/core/matrix_slice.rs @@ -1,28 +1,31 @@ use std::marker::PhantomData; +use std::ops::{Range, RangeFrom, RangeTo, RangeFull}; +use std::slice; use core::{Scalar, Matrix}; -use core::dimension::{Dim, DimName, Dynamic, DimMul, DimProd, U1}; +use core::dimension::{Dim, DimName, Dynamic, U1}; use core::iter::MatrixIter; use core::storage::{Storage, StorageMut, Owned}; use core::allocator::Allocator; +use core::default_allocator::DefaultAllocator; macro_rules! slice_storage_impl( ($doc: expr; $Storage: ident as $SRef: ty; $T: ident.$get_addr: ident ($Ptr: ty as $Ref: ty)) => { #[doc = $doc] - pub struct $T<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> { + #[derive(Debug)] + pub struct $T<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim> { ptr: $Ptr, shape: (R, C), strides: (RStride, CStride), - _phantoms: PhantomData<($Ref, Alloc)>, + _phantoms: PhantomData<$Ref>, } - // Dynamic and () are arbitrary. It's just to be able to call the constructors with - // `Slice::` - impl<'a, N: Scalar, R: Dim, C: Dim> $T<'a, N, R, C, Dynamic, Dynamic, ()> { + // Dynamic is arbitrary. It's just to be able to call the constructors with `Slice::` + impl<'a, N: Scalar, R: Dim, C: Dim> $T<'a, N, R, C, Dynamic, Dynamic> { /// Create a new matrix slice without bound checking. #[inline] pub unsafe fn new_unchecked(storage: $SRef, start: (usize, usize), shape: (R, C)) - -> $T<'a, N, R, C, S::RStride, S::CStride, S::Alloc> + -> $T<'a, N, R, C, S::RStride, S::CStride> where RStor: Dim, CStor: Dim, S: $Storage { @@ -37,17 +40,29 @@ macro_rules! slice_storage_impl( start: (usize, usize), shape: (R, C), strides: (RStride, CStride)) - -> $T<'a, N, R, C, RStride, CStride, S::Alloc> + -> $T<'a, N, R, C, RStride, CStride> where RStor: Dim, CStor: Dim, S: $Storage, RStride: Dim, CStride: Dim { + $T::from_raw_parts(storage.$get_addr(start.0, start.1), shape, strides) + } + + /// Create a new matrix slice without bound checking and from a raw pointer. + #[inline] + pub unsafe fn from_raw_parts(ptr: $Ptr, + shape: (R, C), + strides: (RStride, CStride)) + -> $T<'a, N, R, C, RStride, CStride> + where RStride: Dim, + CStride: Dim { + $T { - ptr: storage.$get_addr(start.0, start.1), + ptr: ptr, shape: shape, - strides: (strides.0, strides.1), + strides: strides, _phantoms: PhantomData } } @@ -65,11 +80,11 @@ slice_storage_impl!("A mutable matrix data storage for mutable matrix slice. Onl ); -impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Copy -for SliceStorage<'a, N, R, C, RStride, CStride, Alloc> { } +impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim> Copy +for SliceStorage<'a, N, R, C, RStride, CStride> { } -impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Clone -for SliceStorage<'a, N, R, C, RStride, CStride, Alloc> { +impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim> Clone +for SliceStorage<'a, N, R, C, RStride, CStride> { #[inline] fn clone(&self) -> Self { SliceStorage { @@ -83,26 +98,11 @@ for SliceStorage<'a, N, R, C, RStride, CStride, Alloc> { macro_rules! storage_impl( ($($T: ident),* $(,)*) => {$( - unsafe impl<'a, N, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> Storage - for $T<'a, N, R, C, RStride, CStride, Alloc> - where N: Scalar, - Alloc: Allocator { + unsafe impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim> Storage + for $T<'a, N, R, C, RStride, CStride> { type RStride = RStride; type CStride = CStride; - type Alloc = Alloc; - - #[inline] - fn into_owned(self) -> Owned { - self.clone_owned() - } - - #[inline] - fn clone_owned(&self) -> Owned { - let (nrows, ncols) = self.shape(); - let it = MatrixIter::new(self).cloned(); - Alloc::allocate_from_iterator(nrows, ncols, it) - } #[inline] fn ptr(&self) -> *const N { @@ -118,20 +118,74 @@ macro_rules! storage_impl( fn strides(&self) -> (Self::RStride, Self::CStride) { self.strides } + + #[inline] + fn is_contiguous(&self) -> bool { + // Common cases that can be deduced at compile-time even if one of the dimensions + // is Dynamic. + if (RStride::is::() && C::is::()) || // Column vector. + (CStride::is::() && R::is::()) { // Row vector. + true + } + else { + let (nrows, _) = self.shape(); + let (srows, scols) = self.strides(); + + srows.value() == 1 && scols.value() == nrows.value() + } + } + + + + #[inline] + fn into_owned(self) -> Owned + where DefaultAllocator: Allocator { + self.clone_owned() + } + + #[inline] + fn clone_owned(&self) -> Owned + where DefaultAllocator: Allocator { + let (nrows, ncols) = self.shape(); + let it = MatrixIter::new(self).cloned(); + DefaultAllocator::allocate_from_iterator(nrows, ncols, it) + } + + #[inline] + fn as_slice(&self) -> &[N] { + let (nrows, ncols) = self.shape(); + if nrows.value() != 0 && ncols.value() != 0 { + let sz = self.linear_index(nrows.value() - 1, ncols.value() - 1); + unsafe { slice::from_raw_parts(self.ptr, sz + 1) } + } + else { + unsafe { slice::from_raw_parts(self.ptr, 0) } + } + } } )*} ); storage_impl!(SliceStorage, SliceStorageMut); -unsafe impl<'a, N, R: Dim, C: Dim, RStride: Dim, CStride: Dim, Alloc> StorageMut - for SliceStorageMut<'a, N, R, C, RStride, CStride, Alloc> - where N: Scalar, - Alloc: Allocator { +unsafe impl<'a, N: Scalar, R: Dim, C: Dim, RStride: Dim, CStride: Dim> StorageMut + for SliceStorageMut<'a, N, R, C, RStride, CStride> { #[inline] fn ptr_mut(&mut self) -> *mut N { self.ptr } + + #[inline] + fn as_mut_slice(&mut self) -> &mut [N] { + let (nrows, ncols) = self.shape(); + if nrows.value() != 0 && ncols.value() != 0 { + let sz = self.linear_index(nrows.value() - 1, ncols.value() - 1); + unsafe { slice::from_raw_parts_mut(self.ptr, sz + 1) } + } + else { + unsafe { slice::from_raw_parts_mut(self.ptr, 0) } + } + } } @@ -139,35 +193,45 @@ impl> Matrix { #[inline] fn assert_slice_index(&self, start: (usize, usize), shape: (usize, usize), steps: (usize, usize)) { let my_shape = self.shape(); - assert!(start.0 + (shape.0 - 1) * steps.0 <= my_shape.0, "Matrix slicing out of bounds."); - assert!(start.1 + (shape.1 - 1) * steps.1 <= my_shape.1, "Matrix slicing out of bounds."); + // NOTE: we don't do any subtraction to avoid underflow for zero-sized matrices. + // + // Terms that would have been negative are moved to the other side of the inequality + // instead. + assert!(start.0 + (steps.0 + 1) * shape.0 <= my_shape.0 + steps.0, "Matrix slicing out of bounds."); + assert!(start.1 + (steps.1 + 1) * shape.1 <= my_shape.1 + steps.1, "Matrix slicing out of bounds."); } } macro_rules! matrix_slice_impl( - ($me: ident: $Me: ty, $MatrixSlice: ident, $SliceStorage: ident, $Storage: ident, $data: expr; + ($me: ident: $Me: ty, $MatrixSlice: ident, $SliceStorage: ident, $Storage: ident.$get_addr: ident (), $data: expr; $row: ident, + $row_part: ident, $rows: ident, $rows_with_step: ident, $fixed_rows: ident, $fixed_rows_with_step: ident, $rows_generic: ident, + $rows_generic_with_step: ident, $column: ident, + $column_part: ident, $columns: ident, $columns_with_step: ident, $fixed_columns: ident, $fixed_columns_with_step: ident, $columns_generic: ident, + $columns_generic_with_step: ident, $slice: ident, $slice_with_steps: ident, $fixed_slice: ident, $fixed_slice_with_steps: ident, $generic_slice: ident, - $generic_slice_with_steps: ident) => { + $generic_slice_with_steps: ident, + $rows_range_pair: ident, + $columns_range_pair: ident) => { /// A matrix slice. - pub type $MatrixSlice<'a, N, R, C, RStride, CStride, Alloc> - = Matrix>; + pub type $MatrixSlice<'a, N, R, C, RStride, CStride> + = Matrix>; impl> Matrix { /* @@ -175,73 +239,80 @@ macro_rules! matrix_slice_impl( * Row slicing. * */ - /// Returns a slice containing the i-th column of this matrix. + /// Returns a slice containing the i-th row of this matrix. #[inline] - pub fn $row($me: $Me, i: usize) -> $MatrixSlice { + pub fn $row($me: $Me, i: usize) -> $MatrixSlice { $me.$fixed_rows::(i) } + /// Returns a slice containing the `n` first elements of the i-th row of this matrix. + #[inline] + pub fn $row_part($me: $Me, i: usize, n: usize) -> $MatrixSlice { + $me.$generic_slice((i, 0), (U1, Dynamic::new(n))) + } + /// Extracts from this matrix a set of consecutive rows. #[inline] pub fn $rows($me: $Me, first_row: usize, nrows: usize) - -> $MatrixSlice { + -> $MatrixSlice { - let my_shape = $me.data.shape(); - $me.assert_slice_index((first_row, 0), (nrows, my_shape.1.value()), (1, 1)); - let shape = (Dynamic::new(nrows), my_shape.1); - - unsafe { - let data = $SliceStorage::new_unchecked($data, (first_row, 0), shape); - Matrix::from_data_statically_unchecked(data) - } + $me.$rows_generic(first_row, Dynamic::new(nrows)) } - /// Extracts from this matrix a set of consecutive rows regularly spaced by `step` rows. + /// Extracts from this matrix a set of consecutive rows regularly skipping `step` rows. #[inline] pub fn $rows_with_step($me: $Me, first_row: usize, nrows: usize, step: usize) - -> $MatrixSlice { + -> $MatrixSlice { - $me.$rows_generic(first_row, Dynamic::new(nrows), Dynamic::new(step)) + $me.$rows_generic_with_step(first_row, Dynamic::new(nrows), step) } /// Extracts a compile-time number of consecutive rows from this matrix. #[inline] - pub fn $fixed_rows($me: $Me, first_row: usize) - -> $MatrixSlice - where RSlice: DimName { + pub fn $fixed_rows($me: $Me, first_row: usize) + -> $MatrixSlice { - let my_shape = $me.data.shape(); - $me.assert_slice_index((first_row, 0), (RSlice::dim(), my_shape.1.value()), (1, 1)); - let shape = (RSlice::name(), my_shape.1); + $me.$rows_generic(first_row, RSlice::name()) + } + + /// Extracts from this matrix a compile-time number of rows regularly skipping `step` + /// rows. + #[inline] + pub fn $fixed_rows_with_step($me: $Me, first_row: usize, step: usize) + -> $MatrixSlice { + + $me.$rows_generic_with_step(first_row, RSlice::name(), step) + } + + /// Extracts from this matrix `nrows` rows regularly skipping `step` rows. Both + /// argument may or may not be values known at compile-time. + #[inline] + pub fn $rows_generic($me: $Me, row_start: usize, nrows: RSlice) + -> $MatrixSlice { + + let my_shape = $me.data.shape(); + $me.assert_slice_index((row_start, 0), (nrows.value(), my_shape.1.value()), (0, 0)); + + let shape = (nrows, my_shape.1); unsafe { - let data = $SliceStorage::new_unchecked($data, (first_row, 0), shape); + let data = $SliceStorage::new_unchecked($data, (row_start, 0), shape); Matrix::from_data_statically_unchecked(data) } } - /// Extracts from this matrix a compile-time number of rows regularly spaced by `step` rows. + /// Extracts from this matrix `nrows` rows regularly skipping `step` rows. Both + /// argument may or may not be values known at compile-time. #[inline] - pub fn $fixed_rows_with_step($me: $Me, first_row: usize, step: usize) - -> $MatrixSlice - where RSlice: DimName { - - $me.$rows_generic(first_row, RSlice::name(), Dynamic::new(step)) - } - - /// Extracts from this matrix `nrows` rows regularly spaced by `step` rows. Both argument may - /// or may not be values known at compile-time. - #[inline] - pub fn $rows_generic($me: $Me, row_start: usize, nrows: RSlice, step: RStep) - -> $MatrixSlice, S::CStride, S::Alloc> - where RSlice: Dim, - RStep: DimMul { + pub fn $rows_generic_with_step($me: $Me, row_start: usize, nrows: RSlice, step: usize) + -> $MatrixSlice + where RSlice: Dim { let my_shape = $me.data.shape(); let my_strides = $me.data.strides(); - $me.assert_slice_index((row_start, 0), (nrows.value(), my_shape.1.value()), (step.value(), 1)); + $me.assert_slice_index((row_start, 0), (nrows.value(), my_shape.1.value()), (step, 0)); - let strides = (step.mul(my_strides.0), my_strides.1); + let strides = (Dynamic::new((step + 1) * my_strides.0.value()), my_strides.1); let shape = (nrows, my_shape.1); unsafe { @@ -257,42 +328,59 @@ macro_rules! matrix_slice_impl( */ /// Returns a slice containing the i-th column of this matrix. #[inline] - pub fn $column($me: $Me, i: usize) -> $MatrixSlice { + pub fn $column($me: $Me, i: usize) -> $MatrixSlice { $me.$fixed_columns::(i) } + /// Returns a slice containing the `n` first elements of the i-th column of this matrix. + #[inline] + pub fn $column_part($me: $Me, i: usize, n: usize) -> $MatrixSlice { + $me.$generic_slice((0, i), (Dynamic::new(n), U1)) + } + /// Extracts from this matrix a set of consecutive columns. #[inline] pub fn $columns($me: $Me, first_col: usize, ncols: usize) - -> $MatrixSlice { + -> $MatrixSlice { - let my_shape = $me.data.shape(); - $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols), (1, 1)); - let shape = (my_shape.0, Dynamic::new(ncols)); - - unsafe { - let data = $SliceStorage::new_unchecked($data, (0, first_col), shape); - Matrix::from_data_statically_unchecked(data) - } + $me.$columns_generic(first_col, Dynamic::new(ncols)) } - /// Extracts from this matrix a set of consecutive columns regularly spaced by `step` columns. + /// Extracts from this matrix a set of consecutive columns regularly skipping `step` + /// columns. #[inline] pub fn $columns_with_step($me: $Me, first_col: usize, ncols: usize, step: usize) - -> $MatrixSlice { + -> $MatrixSlice { - $me.$columns_generic(first_col, Dynamic::new(ncols), Dynamic::new(step)) + $me.$columns_generic_with_step(first_col, Dynamic::new(ncols), step) } /// Extracts a compile-time number of consecutive columns from this matrix. #[inline] - pub fn $fixed_columns($me: $Me, first_col: usize) - -> $MatrixSlice - where CSlice: DimName { + pub fn $fixed_columns($me: $Me, first_col: usize) + -> $MatrixSlice { + + $me.$columns_generic(first_col, CSlice::name()) + } + + /// Extracts from this matrix a compile-time number of columns regularly skipping + /// `step` columns. + #[inline] + pub fn $fixed_columns_with_step($me: $Me, first_col: usize, step: usize) + -> $MatrixSlice { + + $me.$columns_generic_with_step(first_col, CSlice::name(), step) + } + + /// Extracts from this matrix `ncols` columns. The number of columns may or may not be + /// known at compile-time. + #[inline] + pub fn $columns_generic($me: $Me, first_col: usize, ncols: CSlice) + -> $MatrixSlice { let my_shape = $me.data.shape(); - $me.assert_slice_index((0, first_col), (my_shape.0.value(), CSlice::dim()), (1, 1)); - let shape = (my_shape.0, CSlice::name()); + $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols.value()), (0, 0)); + let shape = (my_shape.0, ncols); unsafe { let data = $SliceStorage::new_unchecked($data, (0, first_col), shape); @@ -300,30 +388,19 @@ macro_rules! matrix_slice_impl( } } - /// Extracts from this matrix a compile-time number of columns regularly spaced by `step` - /// columns. - #[inline] - pub fn $fixed_columns_with_step($me: $Me, first_col: usize, step: usize) - -> $MatrixSlice - where CSlice: DimName { - $me.$columns_generic(first_col, CSlice::name(), Dynamic::new(step)) - } - - /// Extracts from this matrix `ncols` columns regularly spaced by `step` columns. Both argument may + /// Extracts from this matrix `ncols` columns skipping `step` columns. Both argument may /// or may not be values known at compile-time. #[inline] - pub fn $columns_generic($me: $Me, first_col: usize, ncols: CSlice, step: CStep) - -> $MatrixSlice, S::Alloc> - where CSlice: Dim, - CStep: DimMul { + pub fn $columns_generic_with_step($me: $Me, first_col: usize, ncols: CSlice, step: usize) + -> $MatrixSlice { let my_shape = $me.data.shape(); let my_strides = $me.data.strides(); - $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols.value()), (1, step.value())); + $me.assert_slice_index((0, first_col), (my_shape.0.value(), ncols.value()), (0, step)); - let strides = (my_strides.0, step.mul(my_strides.1)); + let strides = (my_strides.0, Dynamic::new((step + 1) * my_strides.1.value())); let shape = (my_shape.0, ncols); unsafe { @@ -341,9 +418,9 @@ macro_rules! matrix_slice_impl( /// consecutive elements. #[inline] pub fn $slice($me: $Me, start: (usize, usize), shape: (usize, usize)) - -> $MatrixSlice { + -> $MatrixSlice { - $me.assert_slice_index(start, shape, (1, 1)); + $me.assert_slice_index(start, shape, (0, 0)); let shape = (Dynamic::new(shape.0), Dynamic::new(shape.1)); unsafe { @@ -359,9 +436,8 @@ macro_rules! matrix_slice_impl( /// original matrix. #[inline] pub fn $slice_with_steps($me: $Me, start: (usize, usize), shape: (usize, usize), steps: (usize, usize)) - -> $MatrixSlice { + -> $MatrixSlice { let shape = (Dynamic::new(shape.0), Dynamic::new(shape.1)); - let steps = (Dynamic::new(steps.0), Dynamic::new(steps.1)); $me.$generic_slice_with_steps(start, shape, steps) } @@ -370,11 +446,11 @@ macro_rules! matrix_slice_impl( /// CSlice::dim())` consecutive components. #[inline] pub fn $fixed_slice($me: $Me, irow: usize, icol: usize) - -> $MatrixSlice + -> $MatrixSlice where RSlice: DimName, CSlice: DimName { - $me.assert_slice_index((irow, icol), (RSlice::dim(), CSlice::dim()), (1, 1)); + $me.assert_slice_index((irow, icol), (RSlice::dim(), CSlice::dim()), (0, 0)); let shape = (RSlice::name(), CSlice::name()); unsafe { @@ -389,22 +465,21 @@ macro_rules! matrix_slice_impl( /// the original matrix. #[inline] pub fn $fixed_slice_with_steps($me: $Me, start: (usize, usize), steps: (usize, usize)) - -> $MatrixSlice + -> $MatrixSlice where RSlice: DimName, CSlice: DimName { let shape = (RSlice::name(), CSlice::name()); - let steps = (Dynamic::new(steps.0), Dynamic::new(steps.1)); $me.$generic_slice_with_steps(start, shape, steps) } /// Creates a slice that may or may not have a fixed size and stride. #[inline] pub fn $generic_slice($me: $Me, start: (usize, usize), shape: (RSlice, CSlice)) - -> $MatrixSlice + -> $MatrixSlice where RSlice: Dim, CSlice: Dim { - $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), (1, 1)); + $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), (0, 0)); unsafe { let data = $SliceStorage::new_unchecked($data, start, shape); @@ -414,69 +489,335 @@ macro_rules! matrix_slice_impl( /// Creates a slice that may or may not have a fixed size and stride. #[inline] - pub fn $generic_slice_with_steps($me: $Me, - start: (usize, usize), - shape: (RSlice, CSlice), - steps: (RStep, CStep)) - -> $MatrixSlice, DimProd, S::Alloc> + pub fn $generic_slice_with_steps($me: $Me, + start: (usize, usize), + shape: (RSlice, CSlice), + steps: (usize, usize)) + -> $MatrixSlice where RSlice: Dim, - CSlice: Dim, - RStep: DimMul, - CStep: DimMul { + CSlice: Dim { - $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), (steps.0.value(), steps.1.value())); + $me.assert_slice_index(start, (shape.0.value(), shape.1.value()), steps); let my_strides = $me.data.strides(); - let strides = (steps.0.mul(my_strides.0), steps.1.mul(my_strides.1)); + let strides = (Dynamic::new((steps.0 + 1) * my_strides.0.value()), + Dynamic::new((steps.1 + 1) * my_strides.1.value())); unsafe { let data = $SliceStorage::new_with_strides_unchecked($data, start, shape, strides); Matrix::from_data_statically_unchecked(data) } } + + /* + * + * Splitting. + * + */ + /// Splits this NxM matrix into two parts delimited by two ranges. + /// + /// Panics if the ranges overlap or if the first range is empty. + #[inline] + pub fn $rows_range_pair, Range2: SliceRange>($me: $Me, r1: Range1, r2: Range2) + -> ($MatrixSlice, + $MatrixSlice) { + + let (nrows, ncols) = $me.data.shape(); + let strides = $me.data.strides(); + + let start1 = r1.begin(nrows); + let start2 = r2.begin(nrows); + + let end1 = r1.end(nrows); + let end2 = r2.end(nrows); + + let nrows1 = r1.size(nrows); + let nrows2 = r2.size(nrows); + + assert!(start2 >= end1 || start1 >= end2, "Rows range pair: the slice ranges must not overlap."); + assert!(end2 <= nrows.value(), "Rows range pair: index out of range."); + + unsafe { + let ptr1 = $data.$get_addr(start1, 0); + let ptr2 = $data.$get_addr(start2, 0); + + let data1 = $SliceStorage::from_raw_parts(ptr1, (nrows1, ncols), strides); + let data2 = $SliceStorage::from_raw_parts(ptr2, (nrows2, ncols), strides); + let slice1 = Matrix::from_data_statically_unchecked(data1); + let slice2 = Matrix::from_data_statically_unchecked(data2); + + (slice1, slice2) + } + } + + /// Splits this NxM matrix into two parts delimited by two ranges. + /// + /// Panics if the ranges overlap or if the first range is empty. + #[inline] + pub fn $columns_range_pair, Range2: SliceRange>($me: $Me, r1: Range1, r2: Range2) + -> ($MatrixSlice, + $MatrixSlice) { + + let (nrows, ncols) = $me.data.shape(); + let strides = $me.data.strides(); + + let start1 = r1.begin(ncols); + let start2 = r2.begin(ncols); + + let end1 = r1.end(ncols); + let end2 = r2.end(ncols); + + let ncols1 = r1.size(ncols); + let ncols2 = r2.size(ncols); + + assert!(start2 >= end1 || start1 >= end2, "Columns range pair: the slice ranges must not overlap."); + assert!(end2 <= ncols.value(), "Columns range pair: index out of range."); + + unsafe { + let ptr1 = $data.$get_addr(0, start1); + let ptr2 = $data.$get_addr(0, start2); + + let data1 = $SliceStorage::from_raw_parts(ptr1, (nrows, ncols1), strides); + let data2 = $SliceStorage::from_raw_parts(ptr2, (nrows, ncols2), strides); + let slice1 = Matrix::from_data_statically_unchecked(data1); + let slice2 = Matrix::from_data_statically_unchecked(data2); + + (slice1, slice2) + } + } } } ); matrix_slice_impl!( - self: &Self, MatrixSlice, SliceStorage, Storage, &self.data; + self: &Self, MatrixSlice, SliceStorage, Storage.get_address_unchecked(), &self.data; row, + row_part, rows, rows_with_step, fixed_rows, fixed_rows_with_step, rows_generic, + rows_generic_with_step, column, + column_part, columns, columns_with_step, fixed_columns, fixed_columns_with_step, columns_generic, + columns_generic_with_step, slice, slice_with_steps, fixed_slice, fixed_slice_with_steps, generic_slice, - generic_slice_with_steps); + generic_slice_with_steps, + rows_range_pair, + columns_range_pair); matrix_slice_impl!( - self: &mut Self, MatrixSliceMut, SliceStorageMut, StorageMut, &mut self.data; + self: &mut Self, MatrixSliceMut, SliceStorageMut, StorageMut.get_address_unchecked_mut(), &mut self.data; row_mut, + row_part_mut, rows_mut, rows_with_step_mut, fixed_rows_mut, fixed_rows_with_step_mut, rows_generic_mut, + rows_generic_with_step_mut, column_mut, + column_part_mut, columns_mut, columns_with_step_mut, fixed_columns_mut, fixed_columns_with_step_mut, columns_generic_mut, + columns_generic_with_step_mut, slice_mut, slice_with_steps_mut, fixed_slice_mut, fixed_slice_with_steps_mut, generic_slice_mut, - generic_slice_with_steps_mut); + generic_slice_with_steps_mut, + rows_range_pair_mut, + columns_range_pair_mut); + + +/// A range with a size that may be known at compile-time. +/// +/// This may be: +/// * A single `usize` index, e.g., `4` +/// * A left-open range `std::ops::RangeTo`, e.g., `.. 4` +/// * A right-open range `std::ops::RangeFrom`, e.g., `4 ..` +/// * A full range `std::ops::RangeFull`, e.g., `..` +pub trait SliceRange { + /// Type of the range size. May be a type-level integer. + type Size: Dim; + + /// The start index of the range. + fn begin(&self, shape: D) -> usize; + // NOTE: this is the index immediatly after the last index. + /// The index immediatly after the last index inside of the range. + fn end(&self, shape: D) -> usize; + /// The number of elements of the range, i.e., `self.end - self.begin`. + fn size(&self, shape: D) -> Self::Size; +} + +impl SliceRange for usize { + type Size = U1; + + #[inline(always)] + fn begin(&self, _: D) -> usize { + *self + } + + #[inline(always)] + fn end(&self, _: D) -> usize { + *self + 1 + } + + #[inline(always)] + fn size(&self, _: D) -> Self::Size { + U1 + } +} + +impl SliceRange for Range { + type Size = Dynamic; + + #[inline(always)] + fn begin(&self, _: D) -> usize { + self.start + } + + #[inline(always)] + fn end(&self, _: D) -> usize { + self.end + } + + #[inline(always)] + fn size(&self, _: D) -> Self::Size { + Dynamic::new(self.end - self.start) + } +} + +impl SliceRange for RangeFrom { + type Size = Dynamic; + + #[inline(always)] + fn begin(&self, _: D) -> usize { + self.start + } + + #[inline(always)] + fn end(&self, dim: D) -> usize { + dim.value() + } + + #[inline(always)] + fn size(&self, dim: D) -> Self::Size { + Dynamic::new(dim.value() - self.start) + } +} + +impl SliceRange for RangeTo { + type Size = Dynamic; + + #[inline(always)] + fn begin(&self, _: D) -> usize { + 0 + } + + #[inline(always)] + fn end(&self, _: D) -> usize { + self.end + } + + #[inline(always)] + fn size(&self, _: D) -> Self::Size { + Dynamic::new(self.end) + } +} + +impl SliceRange for RangeFull { + type Size = D; + + #[inline(always)] + fn begin(&self, _: D) -> usize { + 0 + } + + #[inline(always)] + fn end(&self, dim: D) -> usize { + dim.value() + } + + #[inline(always)] + fn size(&self, dim: D) -> Self::Size { + dim + } +} + + +impl> Matrix { + /// Slices a sub-matrix containing the rows indexed by the range `rows` and the columns indexed + /// by the range `cols`. + #[inline] + pub fn slice_range(&self, rows: RowRange, cols: ColRange) + -> MatrixSlice + where RowRange: SliceRange, + ColRange: SliceRange { + + let (nrows, ncols) = self.data.shape(); + self.generic_slice((rows.begin(nrows), cols.begin(ncols)), + (rows.size(nrows), cols.size(ncols))) + } + + /// Slice containing all the rows indexed by the range `rows`. + #[inline] + pub fn rows_range>(&self, rows: RowRange) + -> MatrixSlice { + + self.slice_range(rows, ..) + } + + /// Slice containing all the columns indexed by the range `rows`. + #[inline] + pub fn columns_range>(&self, cols: ColRange) + -> MatrixSlice { + + self.slice_range(.., cols) + } +} + +impl> Matrix { + /// Slices a mutable sub-matrix containing the rows indexed by the range `rows` and the columns + /// indexed by the range `cols`. + pub fn slice_range_mut(&mut self, rows: RowRange, cols: ColRange) + -> MatrixSliceMut + where RowRange: SliceRange, + ColRange: SliceRange { + + let (nrows, ncols) = self.data.shape(); + self.generic_slice_mut((rows.begin(nrows), cols.begin(ncols)), + (rows.size(nrows), cols.size(ncols))) + } + + /// Slice containing all the rows indexed by the range `rows`. + #[inline] + pub fn rows_range_mut>(&mut self, rows: RowRange) + -> MatrixSliceMut { + + self.slice_range_mut(rows, ..) + } + + /// Slice containing all the columns indexed by the range `cols`. + #[inline] + pub fn columns_range_mut>(&mut self, cols: ColRange) + -> MatrixSliceMut { + + self.slice_range_mut(.., cols) + } +} diff --git a/src/core/matrix_vec.rs b/src/core/matrix_vec.rs index 2b5bb372..260f79ce 100644 --- a/src/core/matrix_vec.rs +++ b/src/core/matrix_vec.rs @@ -2,7 +2,8 @@ use std::ops::Deref; use core::Scalar; use core::dimension::{Dim, DimName, Dynamic, U1}; -use core::storage::{Storage, StorageMut, Owned, OwnedStorage}; +use core::storage::{Storage, StorageMut, Owned, ContiguousStorage, ContiguousStorageMut}; +use core::allocator::Allocator; use core::default_allocator::DefaultAllocator; #[cfg(feature = "abomonation-serialize")] @@ -48,6 +49,26 @@ impl MatrixVec { pub unsafe fn data_mut(&mut self) -> &mut Vec { &mut self.data } + + /// Resizes the undelying mutable data storage and unrwaps it. + /// + /// If `sz` is larger than the current size, additional elements are uninitialized. + /// If `sz` is smaller than the current size, additional elements are trucated. + #[inline] + pub unsafe fn resize(mut self, sz: usize) -> Vec{ + let len = self.len(); + + if sz < len { + self.data.set_len(sz); + self.data.shrink_to_fit(); + } + else { + self.data.reserve_exact(sz - len); + self.data.set_len(sz); + } + + self.data + } } impl Deref for MatrixVec { @@ -65,24 +86,14 @@ impl Deref for MatrixVec { * Dynamic − Dynamic * */ -unsafe impl Storage for MatrixVec { +unsafe impl Storage for MatrixVec + where DefaultAllocator: Allocator { type RStride = U1; type CStride = Dynamic; - type Alloc = DefaultAllocator; - - #[inline] - fn into_owned(self) -> Owned { - self - } - - #[inline] - fn clone_owned(&self) -> Owned { - self.clone() - } #[inline] fn ptr(&self) -> *const N { - self[..].as_ptr() + self.data.as_ptr() } #[inline] @@ -94,27 +105,39 @@ unsafe impl Storage for MatrixVec (Self::RStride, Self::CStride) { (Self::RStride::name(), self.nrows) } -} - - -unsafe impl Storage for MatrixVec { - type RStride = U1; - type CStride = R; - type Alloc = DefaultAllocator; #[inline] - fn into_owned(self) -> Owned { + fn is_contiguous(&self) -> bool { + true + } + + #[inline] + fn into_owned(self) -> Owned + where DefaultAllocator: Allocator { self } #[inline] - fn clone_owned(&self) -> Owned { + fn clone_owned(&self) -> Owned + where DefaultAllocator: Allocator { self.clone() } + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } +} + + +unsafe impl Storage for MatrixVec + where DefaultAllocator: Allocator { + type RStride = U1; + type CStride = R; + #[inline] fn ptr(&self) -> *const N { - self[..].as_ptr() + self.data.as_ptr() } #[inline] @@ -126,6 +149,28 @@ unsafe impl Storage for MatrixVec (Self::RStride, Self::CStride) { (Self::RStride::name(), self.nrows) } + + #[inline] + fn is_contiguous(&self) -> bool { + true + } + + #[inline] + fn into_owned(self) -> Owned + where DefaultAllocator: Allocator { + self + } + + #[inline] + fn clone_owned(&self) -> Owned + where DefaultAllocator: Allocator { + self.clone() + } + + #[inline] + fn as_slice(&self) -> &[N] { + &self[..] + } } @@ -133,20 +178,14 @@ unsafe impl Storage for MatrixVec StorageMut for MatrixVec { +unsafe impl StorageMut for MatrixVec + where DefaultAllocator: Allocator { #[inline] fn ptr_mut(&mut self) -> *mut N { - self.as_mut_slice().as_mut_ptr() - } -} - -unsafe impl OwnedStorage for MatrixVec { - #[inline] - fn as_slice(&self) -> &[N] { - &self[..] + self.data.as_mut_ptr() } #[inline] @@ -155,18 +194,20 @@ unsafe impl OwnedStorage for MatrixVec StorageMut for MatrixVec { - #[inline] - fn ptr_mut(&mut self) -> *mut N { - self.as_mut_slice().as_mut_ptr() - } +unsafe impl ContiguousStorage for MatrixVec + where DefaultAllocator: Allocator { } -unsafe impl OwnedStorage for MatrixVec { +unsafe impl ContiguousStorageMut for MatrixVec + where DefaultAllocator: Allocator { +} + + +unsafe impl StorageMut for MatrixVec + where DefaultAllocator: Allocator { #[inline] - fn as_slice(&self) -> &[N] { - &self[..] + fn ptr_mut(&mut self) -> *mut N { + self.data.as_mut_ptr() } #[inline] @@ -189,3 +230,11 @@ impl Abomonation for MatrixVec { self.data.exhume(bytes) } } + +unsafe impl ContiguousStorage for MatrixVec + where DefaultAllocator: Allocator { +} + +unsafe impl ContiguousStorageMut for MatrixVec + where DefaultAllocator: Allocator { +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 356dd29d..e3532703 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,6 +6,7 @@ pub mod allocator; pub mod storage; pub mod coordinates; mod ops; +mod blas; pub mod iter; pub mod default_allocator; @@ -15,8 +16,6 @@ mod construction; mod properties; mod alias; mod matrix_alga; -mod determinant; -mod inverse; mod conversion; mod matrix_slice; mod matrix_array; @@ -24,8 +23,7 @@ mod matrix_vec; mod cg; mod unit; mod componentwise; - -mod decompositions; +mod edition; #[doc(hidden)] pub mod helper; diff --git a/src/core/ops.rs b/src/core/ops.rs index 98c0186f..f303e4d9 100644 --- a/src/core/ops.rs +++ b/src/core/ops.rs @@ -1,15 +1,16 @@ use std::iter; use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg, Index, IndexMut}; -use num::{Zero, One}; +use std::cmp::PartialOrd; +use num::{Zero, One, Signed}; use alga::general::{ClosedMul, ClosedDiv, ClosedAdd, ClosedSub, ClosedNeg}; -use core::{Scalar, Matrix, OwnedMatrix, SquareMatrix, MatrixSum, MatrixMul, MatrixTrMul}; -use core::dimension::{Dim, DimMul, DimName, DimProd}; -use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable}; -use core::storage::{Storage, StorageMut, OwnedStorage}; -use core::allocator::{SameShapeAllocator, Allocator, OwnedAllocator}; +use core::{DefaultAllocator, Scalar, Matrix, MatrixN, MatrixMN, MatrixSum}; +use core::dimension::{Dim, DimName, DimProd, DimMul}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable, DimEq}; +use core::storage::{Storage, StorageMut, ContiguousStorageMut}; +use core::allocator::{SameShapeAllocator, Allocator, SameShapeR, SameShapeC}; /* * @@ -70,8 +71,9 @@ impl IndexMut<(usize, usize)> for Matrix */ impl Neg for Matrix where N: Scalar + ClosedNeg, - S: Storage { - type Output = OwnedMatrix; + S: Storage, + DefaultAllocator: Allocator { + type Output = MatrixMN; #[inline] fn neg(self) -> Self::Output { @@ -83,8 +85,9 @@ impl Neg for Matrix impl<'a, N, R: Dim, C: Dim, S> Neg for &'a Matrix where N: Scalar + ClosedNeg, - S: Storage { - type Output = OwnedMatrix; + S: Storage, + DefaultAllocator: Allocator { + type Output = MatrixMN; #[inline] fn neg(self) -> Self::Output { @@ -109,33 +112,156 @@ impl Matrix * Addition & Substraction * */ + macro_rules! componentwise_binop_impl( ($Trait: ident, $method: ident, $bound: ident; - $TraitAssign: ident, $method_assign: ident) => { + $TraitAssign: ident, $method_assign: ident, $method_assign_statically_unchecked: ident, + $method_assign_statically_unchecked_rhs: ident; + $method_to: ident, $method_to_statically_unchecked: ident) => { + + impl> Matrix + where N: Scalar + $bound { + + /* + * + * Methods without dimension checking at compile-time. + * This is useful for code reuse because the sum representative system does not plays + * easily with static checks. + * + */ + #[inline] + fn $method_to_statically_unchecked(&self, + rhs: &Matrix, + 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."); + + // This is the most common case and should be deduced at compile-time. + // FIXME: use specialization instead? + if self.data.is_contiguous() && rhs.data.is_contiguous() && out.data.is_contiguous() { + let arr1 = self.data.as_slice(); + let arr2 = rhs.data.as_slice(); + let out = out.data.as_mut_slice(); + for i in 0 .. arr1.len() { + unsafe { + *out.get_unchecked_mut(i) = arr1.get_unchecked(i).$method(*arr2.get_unchecked(i)); + } + } + } + else { + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + unsafe { + let val = self.get_unchecked(i, j).$method(*rhs.get_unchecked(i, j)); + *out.get_unchecked_mut(i, j) = val; + } + } + } + } + } + + + #[inline] + fn $method_assign_statically_unchecked(&mut self, rhs: &Matrix) + where R2: Dim, + C2: Dim, + SA: StorageMut, + SB: Storage { + assert!(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? + if self.data.is_contiguous() && rhs.data.is_contiguous() { + let arr1 = self.data.as_mut_slice(); + let arr2 = rhs.data.as_slice(); + for i in 0 .. arr2.len() { + unsafe { + arr1.get_unchecked_mut(i).$method_assign(*arr2.get_unchecked(i)); + } + } + } + else { + for j in 0 .. rhs.ncols() { + for i in 0 .. rhs.nrows() { + unsafe { + self.get_unchecked_mut(i, j).$method_assign(*rhs.get_unchecked(i, j)) + } + } + } + } + } + + + #[inline] + fn $method_assign_statically_unchecked_rhs(&self, rhs: &mut Matrix) + where R2: Dim, + C2: Dim, + SB: StorageMut { + assert!(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? + if self.data.is_contiguous() && rhs.data.is_contiguous() { + let arr1 = self.data.as_slice(); + let arr2 = rhs.data.as_mut_slice(); + for i in 0 .. arr1.len() { + unsafe { + let res = arr1.get_unchecked(i).$method(*arr2.get_unchecked(i)); + *arr2.get_unchecked_mut(i) = res; + } + } + } + else { + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + unsafe { + let r = rhs.get_unchecked_mut(i, j); + *r = self.get_unchecked(i, j).$method(*r) + } + } + } + } + } + + + /* + * + * Methods without dimension checking at compile-time. + * This is useful for code reuse because the sum representative system does not plays + * easily with static checks. + * + */ + /// Equivalent to `self + rhs` but stores the result into `out` to avoid allocations. + #[inline] + pub fn $method_to(&self, + rhs: &Matrix, + out: &mut Matrix) + where SB: Storage, + SC: StorageMut, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns + + SameNumberOfRows + SameNumberOfColumns { + self.$method_to_statically_unchecked(rhs, out) + } + } + impl<'b, N, R1, C1, R2, C2, SA, SB> $Trait<&'b Matrix> for Matrix where R1: Dim, C1: Dim, R2: Dim, C2: Dim, N: Scalar + $bound, SA: Storage, SB: Storage, - SA::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - type Output = MatrixSum; + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; #[inline] - fn $method(self, right: &'b Matrix) -> Self::Output { - assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); + fn $method(self, rhs: &'b Matrix) -> Self::Output { + assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); let mut res = self.into_owned_sum::(); - - // XXX: optimize our iterator! - // - // Using our own iterator prevents loop unrolling, wich breaks some optimization - // (like SIMD). On the other hand, using the slice iterator is 4x faster. - - // for (left, right) in res.iter_mut().zip(right.iter()) { - for (left, right) in res.as_mut_slice().iter_mut().zip(right.iter()) { - *left = left.$method(*right) - } - + res.$method_assign_statically_unchecked(rhs); res } } @@ -145,26 +271,16 @@ macro_rules! componentwise_binop_impl( N: Scalar + $bound, SA: Storage, SB: Storage, - SB::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - type Output = MatrixSum; + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; #[inline] - fn $method(self, right: Matrix) -> Self::Output { - assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); - let mut res = right.into_owned_sum::(); - - // XXX: optimize our iterator! - // - // Using our own iterator prevents loop unrolling, wich breaks some optimization - // (like SIMD). On the other hand, using the slice iterator is 4x faster. - - // for (left, right) in self.iter().zip(res.iter_mut()) { - for (left, right) in self.iter().zip(res.as_mut_slice().iter_mut()) { - *right = left.$method(*right) - } - - res + fn $method(self, rhs: Matrix) -> Self::Output { + let mut rhs = rhs.into_owned_sum::(); + assert!(self.shape() == rhs.shape(), "Matrix addition/subtraction dimensions mismatch."); + self.$method_assign_statically_unchecked_rhs(&mut rhs); + rhs } } @@ -173,13 +289,13 @@ macro_rules! componentwise_binop_impl( N: Scalar + $bound, SA: Storage, SB: Storage, - SA::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - type Output = MatrixSum; + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; #[inline] - fn $method(self, right: Matrix) -> Self::Output { - self.$method(&right) + fn $method(self, rhs: Matrix) -> Self::Output { + self.$method(&rhs) } } @@ -188,13 +304,21 @@ macro_rules! componentwise_binop_impl( N: Scalar + $bound, SA: Storage, SB: Storage, - SA::Alloc: SameShapeAllocator, - ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { - type Output = MatrixSum; + DefaultAllocator: SameShapeAllocator, + ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { + type Output = MatrixSum; #[inline] - fn $method(self, right: &'b Matrix) -> Self::Output { - self.clone_owned().$method(right) + fn $method(self, rhs: &'b Matrix) -> Self::Output { + let mut res = unsafe { + let (nrows, ncols) = self.shape(); + let nrows: SameShapeR = Dim::from_usize(nrows); + let ncols: SameShapeC = Dim::from_usize(ncols); + Matrix::new_uninitialized_generic(nrows, ncols) + }; + + self.$method_to_statically_unchecked(rhs, &mut res); + res } } @@ -206,11 +330,8 @@ macro_rules! componentwise_binop_impl( ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { #[inline] - fn $method_assign(&mut self, right: &'b Matrix) { - assert!(self.shape() == right.shape(), "Matrix addition/subtraction dimensions mismatch."); - for (left, right) in self.iter_mut().zip(right.iter()) { - left.$method_assign(*right) - } + fn $method_assign(&mut self, rhs: &'b Matrix) { + self.$method_assign_statically_unchecked(rhs) } } @@ -222,32 +343,34 @@ macro_rules! componentwise_binop_impl( ShapeConstraint: SameNumberOfRows + SameNumberOfColumns { #[inline] - fn $method_assign(&mut self, right: Matrix) { - self.$method_assign(&right) + fn $method_assign(&mut self, rhs: Matrix) { + self.$method_assign(&rhs) } } } ); -componentwise_binop_impl!(Add, add, ClosedAdd; AddAssign, add_assign); -componentwise_binop_impl!(Sub, sub, ClosedSub; SubAssign, sub_assign); +componentwise_binop_impl!(Add, add, ClosedAdd; + AddAssign, add_assign, add_assign_statically_unchecked, add_assign_statically_unchecked_mut; + add_to, add_to_statically_unchecked); +componentwise_binop_impl!(Sub, sub, ClosedSub; + SubAssign, sub_assign, sub_assign_statically_unchecked, sub_assign_statically_unchecked_mut; + sub_to, sub_to_statically_unchecked); -impl iter::Sum for Matrix +impl iter::Sum for MatrixMN where N: Scalar + ClosedAdd + Zero, - S: OwnedStorage, - S::Alloc: OwnedAllocator + DefaultAllocator: Allocator { - fn sum>>(iter: I) -> Matrix { + fn sum>>(iter: I) -> MatrixMN { iter.fold(Matrix::zero(), |acc, x| acc + x) } } -impl<'a, N, R: DimName, C: DimName, S> iter::Sum<&'a Matrix> for Matrix +impl<'a, N, R: DimName, C: DimName> iter::Sum<&'a MatrixMN> for MatrixMN where N: Scalar + ClosedAdd + Zero, - S: OwnedStorage, - S::Alloc: OwnedAllocator + DefaultAllocator: Allocator { - fn sum>>(iter: I) -> Matrix { + fn sum>>(iter: I) -> MatrixMN { iter.fold(Matrix::zero(), |acc, x| acc + x) } } @@ -266,8 +389,9 @@ macro_rules! componentwise_scalarop_impl( $TraitAssign: ident, $method_assign: ident) => { impl $Trait for Matrix where N: Scalar + $bound, - S: Storage { - type Output = OwnedMatrix; + S: Storage, + DefaultAllocator: Allocator { + type Output = MatrixMN; #[inline] fn $method(self, rhs: N) -> Self::Output { @@ -289,8 +413,9 @@ macro_rules! componentwise_scalarop_impl( impl<'a, N, R: Dim, C: Dim, S> $Trait for &'a Matrix where N: Scalar + $bound, - S: Storage { - type Output = OwnedMatrix; + S: Storage, + DefaultAllocator: Allocator { + type Output = MatrixMN; #[inline] fn $method(self, rhs: N) -> Self::Output { @@ -302,9 +427,11 @@ macro_rules! componentwise_scalarop_impl( where N: Scalar + $bound, S: StorageMut { #[inline] - fn $method_assign(&mut self, right: N) { - for left in self.iter_mut() { - left.$method_assign(right) + fn $method_assign(&mut self, rhs: N) { + for j in 0 .. self.ncols() { + for i in 0 .. self.nrows() { + unsafe { self.get_unchecked_mut(i, j).$method_assign(rhs) }; + } } } } @@ -316,35 +443,35 @@ componentwise_scalarop_impl!(Div, div, ClosedDiv; DivAssign, div_assign); macro_rules! left_scalar_mul_impl( ($($T: ty),* $(,)*) => {$( - impl Mul> for $T - where S: Storage<$T, R, C> { - type Output = OwnedMatrix<$T, R, C, S::Alloc>; + impl> Mul> for $T + where DefaultAllocator: Allocator<$T, R, C> { + type Output = MatrixMN<$T, R, C>; #[inline] - fn mul(self, right: Matrix<$T, R, C, S>) -> Self::Output { - let mut res = right.into_owned(); + fn mul(self, rhs: Matrix<$T, R, C, S>) -> Self::Output { + let mut res = rhs.into_owned(); // XXX: optimize our iterator! // // Using our own iterator prevents loop unrolling, wich breaks some optimization // (like SIMD). On the other hand, using the slice iterator is 4x faster. - // for right in res.iter_mut() { - for right in res.as_mut_slice().iter_mut() { - *right = self * *right + // for rhs in res.iter_mut() { + for rhs in res.as_mut_slice().iter_mut() { + *rhs = self * *rhs } res } } - impl<'b, R: Dim, C: Dim, S> Mul<&'b Matrix<$T, R, C, S>> for $T - where S: Storage<$T, R, C> { - type Output = OwnedMatrix<$T, R, C, S::Alloc>; + impl<'b, R: Dim, C: Dim, S: Storage<$T, R, C>> Mul<&'b Matrix<$T, R, C, S>> for $T + where DefaultAllocator: Allocator<$T, R, C> { + type Output = MatrixMN<$T, R, C>; #[inline] - fn mul(self, right: &'b Matrix<$T, R, C, S>) -> Self::Output { - self * right.clone_owned() + fn mul(self, rhs: &'b Matrix<$T, R, C, S>) -> Self::Output { + self * rhs.clone_owned() } } )*} @@ -361,84 +488,66 @@ left_scalar_mul_impl!( // Matrix × Matrix impl<'a, 'b, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul<&'b Matrix> for &'a Matrix - where N: Scalar + Zero + ClosedAdd + ClosedMul, - SB: Storage, + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, SA: Storage, - SA::Alloc: Allocator, - ShapeConstraint: AreMultipliable { - type Output = MatrixMul; + SB: Storage, + DefaultAllocator: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMN; #[inline] - fn mul(self, right: &'b Matrix) -> Self::Output { - let (nrows1, ncols1) = self.shape(); - let (nrows2, ncols2) = right.shape(); - - assert!(ncols1 == nrows2, "Matrix multiplication dimensions mismatch."); - - let mut res: MatrixMul = unsafe { - Matrix::new_uninitialized_generic(self.data.shape().0, right.data.shape().1) + fn mul(self, rhs: &'b Matrix) -> Self::Output { + let mut res = unsafe { + Matrix::new_uninitialized_generic(self.data.shape().0, rhs.data.shape().1) }; - for i in 0 .. nrows1 { - for j in 0 .. ncols2 { - let mut acc = N::zero(); - - unsafe { - for k in 0 .. ncols1 { - acc = acc + *self.get_unchecked(i, k) * *right.get_unchecked(k, j); - } - - *res.get_unchecked_mut(i, j) = acc; - } - } - } - + self.mul_to(rhs, &mut res); res } } impl<'a, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul> for &'a Matrix - where N: Scalar + Zero + ClosedAdd + ClosedMul, + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, SB: Storage, SA: Storage, - SA::Alloc: Allocator, - ShapeConstraint: AreMultipliable { - type Output = MatrixMul; + DefaultAllocator: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMN; #[inline] - fn mul(self, right: Matrix) -> Self::Output { - self * &right + fn mul(self, rhs: Matrix) -> Self::Output { + self * &rhs } } impl<'b, N, R1: Dim, C1: Dim, R2: Dim, C2: Dim, SA, SB> Mul<&'b Matrix> for Matrix - where N: Scalar + Zero + ClosedAdd + ClosedMul, + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, SB: Storage, SA: Storage, - SA::Alloc: Allocator, - ShapeConstraint: AreMultipliable { - type Output = MatrixMul; + DefaultAllocator: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMN; #[inline] - fn mul(self, right: &'b Matrix) -> Self::Output { - &self * right + fn mul(self, rhs: &'b Matrix) -> Self::Output { + &self * rhs } } impl Mul> for Matrix - where N: Scalar + Zero + ClosedAdd + ClosedMul, + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, SB: Storage, SA: Storage, - SA::Alloc: Allocator, - ShapeConstraint: AreMultipliable { - type Output = MatrixMul; + DefaultAllocator: Allocator, + ShapeConstraint: AreMultipliable { + type Output = MatrixMN; #[inline] - fn mul(self, right: Matrix) -> Self::Output { - &self * &right + fn mul(self, rhs: Matrix) -> Self::Output { + &self * &rhs } } @@ -447,84 +556,106 @@ for Matrix // − we can't use `a *= b` when C2 is not equal to C1. impl MulAssign> for Matrix where R1: Dim, C1: Dim, R2: Dim, - N: Scalar + Zero + ClosedAdd + ClosedMul, + N: Scalar + Zero + One + ClosedAdd + ClosedMul, SB: Storage, - SA: OwnedStorage, - ShapeConstraint: AreMultipliable, - SA::Alloc: OwnedAllocator { + SA: ContiguousStorageMut + Clone, + ShapeConstraint: AreMultipliable, + DefaultAllocator: Allocator { #[inline] - fn mul_assign(&mut self, right: Matrix) { - *self = &*self * right + fn mul_assign(&mut self, rhs: Matrix) { + *self = &*self * rhs } } impl<'b, N, R1, C1, R2, SA, SB> MulAssign<&'b Matrix> for Matrix where R1: Dim, C1: Dim, R2: Dim, - N: Scalar + Zero + ClosedAdd + ClosedMul, + N: Scalar + Zero + One + ClosedAdd + ClosedMul, SB: Storage, - SA: OwnedStorage, + SA: ContiguousStorageMut + Clone, ShapeConstraint: AreMultipliable, // FIXME: this is too restrictive. See comments for the non-ref version. - SA::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] - fn mul_assign(&mut self, right: &'b Matrix) { - *self = &*self * right + fn mul_assign(&mut self, rhs: &'b Matrix) { + *self = &*self * rhs } } // Transpose-multiplication. impl Matrix - where N: Scalar, + where N: Scalar + Zero + One + ClosedAdd + ClosedMul, SA: Storage { - /// Equivalent to `self.transpose() * right`. + /// Equivalent to `self.transpose() * rhs`. #[inline] - pub fn tr_mul(&self, right: &Matrix) -> MatrixTrMul - where N: Zero + ClosedAdd + ClosedMul, - SB: Storage, - SA::Alloc: Allocator, - ShapeConstraint: AreMultipliable { + pub fn tr_mul(&self, rhs: &Matrix) -> MatrixMN + where SB: Storage, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + + let mut res = unsafe { + Matrix::new_uninitialized_generic(self.data.shape().1, rhs.data.shape().1) + }; + + self.tr_mul_to(rhs, &mut res); + res + } + + /// Equivalent to `self.transpose() * rhs` but stores the result into `out` to avoid + /// allocations. + #[inline] + pub fn tr_mul_to(&self, + rhs: &Matrix, + out: &mut Matrix) + where SB: Storage, + SC: StorageMut, + ShapeConstraint: SameNumberOfRows + + DimEq + + DimEq { let (nrows1, ncols1) = self.shape(); - let (nrows2, ncols2) = right.shape(); + let (nrows2, ncols2) = rhs.shape(); + let (nrows3, ncols3) = out.shape(); assert!(nrows1 == nrows2, "Matrix multiplication dimensions mismatch."); - - let mut res: MatrixTrMul = unsafe { - Matrix::new_uninitialized_generic(self.data.shape().1, right.data.shape().1) - }; + assert!(nrows3 == ncols1 && ncols3 == ncols2, "Matrix multiplication output dimensions mismatch."); for i in 0 .. ncols1 { for j in 0 .. ncols2 { - let mut acc = N::zero(); - - unsafe { - for k in 0 .. nrows1 { - acc += *self.get_unchecked(k, i) * *right.get_unchecked(k, j); - } - - *res.get_unchecked_mut(i, j) = acc; - } + let dot = self.column(i).dot(&rhs.column(j)); + unsafe { *out.get_unchecked_mut(i, j) = dot }; } } + } - res + /// Equivalent to `self * rhs` but stores the result into `out` to avoid allocations. + #[inline] + pub fn mul_to(&self, + rhs: &Matrix, + out: &mut Matrix) + where SB: Storage, + SC: StorageMut, + ShapeConstraint: SameNumberOfRows + + SameNumberOfColumns + + AreMultipliable { + out.gemm(N::one(), self, rhs, N::zero()); } /// The kronecker product of two matrices (aka. tensor product of the corresponding linear /// maps). pub fn kronecker(&self, rhs: &Matrix) - -> OwnedMatrix, DimProd, SA::Alloc> + -> MatrixMN, DimProd> where N: ClosedMul, R1: DimMul, C1: DimMul, SB: Storage, - SA::Alloc: Allocator, DimProd> { + DefaultAllocator: Allocator, DimProd> { let (nrows1, ncols1) = self.data.shape(); let (nrows2, ncols2) = rhs.data.shape(); - let mut res: OwnedMatrix<_, _, _, SA::Alloc> = - unsafe { Matrix::new_uninitialized_generic(nrows1.mul(nrows2), ncols1.mul(ncols2)) }; + let mut res = unsafe { Matrix::new_uninitialized_generic(nrows1.mul(nrows2), ncols1.mul(ncols2)) }; { let mut data_res = res.data.ptr_mut(); @@ -549,22 +680,76 @@ impl Matrix } } -impl iter::Product for SquareMatrix +impl> Matrix { + /// Adds a scalar to `self`. + #[inline] + pub fn add_scalar(&self, rhs: N) -> MatrixMN + where DefaultAllocator: Allocator { + let mut res = self.clone_owned(); + res.add_scalar_mut(rhs); + res + } + + /// Adds a scalar to `self` in-place. + #[inline] + pub fn add_scalar_mut(&mut self, rhs: N) + where S: StorageMut { + for e in self.iter_mut() { + *e += rhs + } + } +} + + +impl iter::Product for MatrixN where N: Scalar + Zero + One + ClosedMul + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator + DefaultAllocator: Allocator { - fn product>>(iter: I) -> SquareMatrix { + fn product>>(iter: I) -> MatrixN { iter.fold(Matrix::one(), |acc, x| acc * x) } } -impl<'a, N, D: DimName, S> iter::Product<&'a SquareMatrix> for SquareMatrix +impl<'a, N, D: DimName> iter::Product<&'a MatrixN> for MatrixN where N: Scalar + Zero + One + ClosedMul + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator + DefaultAllocator: Allocator { - fn product>>(iter: I) -> SquareMatrix { + fn product>>(iter: I) -> MatrixN { iter.fold(Matrix::one(), |acc, x| acc * x) } } + +impl> Matrix { + /// Returns the absolute value of the coefficient with the largest absolute value. + #[inline] + pub fn amax(&self) -> N { + let mut max = N::zero(); + + for e in self.iter() { + let ae = e.abs(); + + if ae > max { + max = ae; + } + } + + max + } + + /// Returns the absolute value of the coefficient with the smallest absolute value. + #[inline] + pub fn amin(&self) -> N { + let mut it = self.iter(); + let mut min = it.next().expect("amin: empty matrices not supported.").abs(); + + for e in it { + let ae = e.abs(); + + if ae < min { + min = ae; + } + } + + min + } +} diff --git a/src/core/properties.rs b/src/core/properties.rs index 77a28c73..be1fbc6f 100644 --- a/src/core/properties.rs +++ b/src/core/properties.rs @@ -2,33 +2,38 @@ use num::{Zero, One}; use approx::ApproxEq; -use alga::general::{ClosedAdd, ClosedMul, ClosedSub, Field}; +use alga::general::{ClosedAdd, ClosedMul, Real}; -use core::{Scalar, Matrix, SquareMatrix}; -use core::dimension::Dim; +use core::{DefaultAllocator, Scalar, Matrix, SquareMatrix}; +use core::dimension::{Dim, DimMin}; use core::storage::Storage; +use core::allocator::Allocator; impl> Matrix { /// Indicates if this is a square matrix. #[inline] - pub fn is_square(&self) -> bool { - let shape = self.shape(); - shape.0 == shape.1 + pub fn is_empty(&self) -> bool { + let (nrows, ncols) = self.shape(); + nrows == 0 || ncols == 0 + } + + /// Indicates if this is a square matrix. + #[inline] + pub fn is_square(&self) -> bool { + let (nrows, ncols) = self.shape(); + nrows == ncols } -} -impl> Matrix // FIXME: ApproxEq prevents us from using those methods on integer matrices… - where N: ApproxEq, - N::Epsilon: Copy { /// Indicated if this is the identity matrix within a relative error of `eps`. /// /// If the matrix is diagonal, this checks that diagonal elements (i.e. at coordinates `(i, i)` /// for i from `0` to `min(R, C)`) are equal one; and that all other elements are zero. #[inline] pub fn is_identity(&self, eps: N::Epsilon) -> bool - where N: Zero + One { + where N: Zero + One + ApproxEq, + N::Epsilon: Copy { let (nrows, ncols) = self.shape(); let d; @@ -75,32 +80,35 @@ impl> Matrix true } -} - -impl> SquareMatrix - where N: Zero + One + ClosedAdd + ClosedMul, - N::Epsilon: Copy { - /// Checks that this matrix is orthogonal, i.e., that it is square and `M × Mᵀ = Id`. + /// Checks that `Mᵀ × M = Id`. /// /// In this definition `Id` is approximately equal to the identity matrix with a relative error /// equal to `eps`. #[inline] - pub fn is_orthogonal(&self, eps: N::Epsilon) -> bool { - self.is_square() && (self.tr_mul(self)).is_identity(eps) + pub fn is_orthogonal(&self, eps: N::Epsilon) -> bool + where N: Zero + One + ClosedAdd + ClosedMul + ApproxEq, + S: Storage, + N::Epsilon: Copy, + DefaultAllocator: Allocator { + (self.tr_mul(self)).is_identity(eps) } +} + +impl> SquareMatrix + where DefaultAllocator: Allocator { /// Checks that this matrix is orthogonal and has a determinant equal to 1. #[inline] - pub fn is_special_orthogonal(&self, eps: N::Epsilon) -> bool - where N: ClosedSub + PartialOrd { - self.is_orthogonal(eps) && self.determinant() > N::zero() + pub fn is_special_orthogonal(&self, eps: N) -> bool + where D: DimMin, + DefaultAllocator: Allocator<(usize, usize), D> { + self.is_square() && self.is_orthogonal(eps) && self.determinant() > N::zero() } /// Returns `true` if this matrix is invertible. #[inline] - pub fn is_invertible(&self) -> bool - where N: Field { + pub fn is_invertible(&self) -> bool { // FIXME: improve this? self.clone_owned().try_inverse().is_some() } diff --git a/src/core/scalar.rs b/src/core/scalar.rs index 9d2cb8c7..6735523b 100644 --- a/src/core/scalar.rs +++ b/src/core/scalar.rs @@ -1,3 +1,4 @@ +use std::any::TypeId; use std::fmt::Debug; use std::any::Any; @@ -5,5 +6,12 @@ use std::any::Any; /// /// This does not make any assumption on the algebraic properties of `Self`. pub trait Scalar: Copy + PartialEq + Debug + Any { + #[inline] + /// Tests if `Self` the the same as the type `T` + /// + /// Typically used to test of `Self` is a f32 or a f64 with `N::is::()`. + fn is() -> bool { + TypeId::of::() == TypeId::of::() + } } impl Scalar for T { } diff --git a/src/core/storage.rs b/src/core/storage.rs index d70e416c..a0dd48a2 100644 --- a/src/core/storage.rs +++ b/src/core/storage.rs @@ -1,39 +1,28 @@ //! Abstract definition of a matrix data storage. +use std::fmt::Debug; use std::mem; -use std::any::Any; use core::Scalar; -use dimension::Dim; -use allocator::{Allocator, SameShapeR, SameShapeC}; +use core::default_allocator::DefaultAllocator; +use core::dimension::{Dim, U1}; +use core::allocator::{Allocator, SameShapeR, SameShapeC}; /* - * Aliases for sum storage. + * Aliases for allocation results. */ /// The data storage for the sum of two matrices with dimensions `(R1, C1)` and `(R2, C2)`. -pub type SumStorage = - <>::Alloc as Allocator, SameShapeC>>::Buffer; +pub type SameShapeStorage = , SameShapeC>>::Buffer; -/* - * Aliases for multiplication storage. - */ -/// The data storage for the multiplication of two matrices with dimensions `(R1, C1)` on the left -/// hand side, and with `C2` columns on the right hand side. -pub type MulStorage = - <>::Alloc as Allocator>::Buffer; - -/// The data storage for the multiplication of two matrices with dimensions `(R1, C1)` on the left -/// hand side, and with `C2` columns on the right hand side. The first matrix is implicitly -/// transposed. -pub type TrMulStorage = - <>::Alloc as Allocator>::Buffer; - -/* - * Alias for allocation result. - */ +// FIXME: better name than Owned ? /// The owned data storage that can be allocated from `S`. -pub type Owned = - >::Buffer; +pub type Owned = >::Buffer; + +/// The row-stride of the owned data storage for a buffer of dimension `(R, C)`. +pub type RStride = <>::Buffer as Storage>::RStride; + +/// The column-stride of the owned data storage for a buffer of dimension `(R, C)`. +pub type CStride = <>::Buffer as Storage>::CStride; /// The trait shared by all matrix data storage. @@ -45,22 +34,13 @@ pub type Owned = /// should **not** allow the user to modify the size of the underlying buffer with safe methods /// (for example the `MatrixVec::data_mut` method is unsafe because the user could change the /// vector's size so that it no longer contains enough elements: this will lead to UB. -pub unsafe trait Storage: Sized { +pub unsafe trait Storage: Debug + Sized { /// The static stride of this storage's rows. type RStride: Dim; /// The static stride of this storage's columns. type CStride: Dim; - /// The allocator for this family of storage. - type Alloc: Allocator; - - /// Builds a matrix data storage that does not contain any reference. - fn into_owned(self) -> Owned; - - /// Clones this data storage into one that does not contain any reference. - fn clone_owned(&self) -> Owned; - /// The matrix data pointer. fn ptr(&self) -> *const N; @@ -110,6 +90,24 @@ pub unsafe trait Storage: Sized { unsafe fn get_unchecked(&self, irow: usize, icol: usize) -> &N { self.get_unchecked_linear(self.linear_index(irow, icol)) } + + /// Indicates whether this data buffer stores its elements contiguously. + #[inline] + fn is_contiguous(&self) -> bool; + + /// Retrieves the data buffer as a contiguous slice. + /// + /// The matrix components may not be stored in a contiguous way, depending on the strides. + #[inline] + fn as_slice(&self) -> &[N]; + + /// Builds a matrix data storage that does not contain any reference. + fn into_owned(self) -> Owned + where DefaultAllocator: Allocator; + + /// Clones this data storage to one that does not contain any reference. + fn clone_owned(&self) -> Owned + where DefaultAllocator: Allocator; } @@ -118,7 +116,7 @@ pub unsafe trait Storage: Sized { /// Note that a mutable access does not mean that the matrix owns its data. For example, a mutable /// matrix slice can provide mutable access to its elements even if it does not own its data (it /// contains only an internal reference to them). -pub unsafe trait StorageMut: Storage { +pub unsafe trait StorageMut: Storage { /// The matrix mutable data pointer. fn ptr_mut(&mut self) -> *mut N; @@ -163,22 +161,24 @@ pub unsafe trait StorageMut: Storage { self.swap_unchecked_linear(lid1, lid2) } -} -/// A matrix storage that does not contain any reference and that is stored contiguously in memory. -/// -/// The storage requirement means that for any value of `i` in `[0, nrows * ncols[`, the value -/// `.get_unchecked_linear` succeeds. This trait is unsafe because failing to comply to this may -/// cause Undefined Behaviors. -pub unsafe trait OwnedStorage: StorageMut + Clone + Any - where Self::Alloc: Allocator { - // NOTE: We could auto-impl those two methods but we don't to make sure the user is aware that - // data must be contiguous. - /// Converts this data storage to a slice. - #[inline] - fn as_slice(&self) -> &[N]; - - /// Converts this data storage to a mutable slice. + /// Retrieves the mutable data buffer as a contiguous slice. + /// + /// Matrix components may not be contiguous, depending on its strides. #[inline] fn as_mut_slice(&mut self) -> &mut [N]; } + +/// 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 +/// `.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: Storage { } + +/// 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 +/// `.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 { } diff --git a/src/debug/mod.rs b/src/debug/mod.rs new file mode 100644 index 00000000..57a16dfd --- /dev/null +++ b/src/debug/mod.rs @@ -0,0 +1,8 @@ +//! Various tools useful for testing/debugging/benchmarking. + + +mod random_orthogonal; +mod random_sdp; + +pub use self::random_orthogonal::*; +pub use self::random_sdp::*; diff --git a/src/debug/random_orthogonal.rs b/src/debug/random_orthogonal.rs new file mode 100644 index 00000000..819bb21b --- /dev/null +++ b/src/debug/random_orthogonal.rs @@ -0,0 +1,51 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; + +use num_complex::Complex; +use alga::general::Real; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{Dim, Dynamic, U2}; +use core::allocator::Allocator; +use geometry::UnitComplex; + +/// A random orthogonal matrix. +#[derive(Clone, Debug)] +pub struct RandomOrthogonal + where DefaultAllocator: Allocator { + m: MatrixN +} + + +impl RandomOrthogonal + where DefaultAllocator: Allocator { + /// Retrieve the generated matrix. + pub fn unwrap(self) -> MatrixN { + self.m + } + + /// Creates a new random orthogonal matrix from its dimension and a random reals generators. + pub fn new N>(dim: D, mut rand: Rand) -> Self { + let mut res = MatrixN::identity_generic(dim, dim); + + // Create an orthogonal matrix by compositing planar 2D rotations. + for i in 0 .. dim.value() - 1 { + let c = Complex::new(rand(), rand()); + let rot: UnitComplex = UnitComplex::from_complex(c); + rot.rotate(&mut res.fixed_rows_mut::(i)); + } + + RandomOrthogonal { m: res } + } +} + +#[cfg(feature = "arbitrary")] +impl Arbitrary for RandomOrthogonal + where DefaultAllocator: Allocator, + Owned: Clone + Send { + fn arbitrary(g: &mut G) -> Self { + let dim = D::try_to_usize().unwrap_or(g.gen_range(1, 50)); + Self::new(D::from_usize(dim), || N::arbitrary(g)) + } +} diff --git a/src/debug/random_sdp.rs b/src/debug/random_sdp.rs new file mode 100644 index 00000000..d193b3c8 --- /dev/null +++ b/src/debug/random_sdp.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "arbitrary")] +use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; + +use alga::general::Real; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{Dim, Dynamic}; +use core::allocator::Allocator; + +use debug::RandomOrthogonal; + + +/// A random, well-conditioned, symmetric definite-positive matrix. +#[derive(Clone, Debug)] +pub struct RandomSDP + where DefaultAllocator: Allocator { + m: MatrixN +} + + +impl RandomSDP + where DefaultAllocator: Allocator { + + /// Retrieve the generated matrix. + pub fn unwrap(self) -> MatrixN { + self.m + } + + /// Creates a new well conditioned symmetric definite-positive matrix from its dimension and a + /// random reals generators. + pub fn new N>(dim: D, mut rand: Rand) -> Self { + let mut m = RandomOrthogonal::new(dim, || rand()).unwrap(); + let mt = m.transpose(); + + for i in 0 .. dim.value() { + let mut col = m.column_mut(i); + let eigenval = N::one() + rand().abs(); + col *= eigenval; + } + + RandomSDP { m: m * mt } + } +} + +#[cfg(feature = "arbitrary")] +impl Arbitrary for RandomSDP + where DefaultAllocator: Allocator, + Owned: Clone + Send { + fn arbitrary(g: &mut G) -> Self { + let dim = D::try_to_usize().unwrap_or(g.gen_range(1, 50)); + Self::new(D::from_usize(dim), || N::arbitrary(g)) + } +} diff --git a/src/geometry/isometry.rs b/src/geometry/isometry.rs index 9ae55ed9..fbde8c79 100644 --- a/src/geometry/isometry.rs +++ b/src/geometry/isometry.rs @@ -1,33 +1,43 @@ use std::fmt; +use std::hash; use std::marker::PhantomData; use approx::ApproxEq; -use alga::general::{Real, SubsetOf}; -use alga::linear::Rotation; - -use core::{Scalar, OwnedSquareMatrix}; -use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; -use geometry::{TranslationBase, PointBase}; +#[cfg(feature = "serde-serialize")] +use serde; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; +use alga::general::{Real, SubsetOf}; +use alga::linear::Rotation; -/// An isometry that uses a data storage deduced from the allocator `A`. -pub type OwnedIsometryBase = - IsometryBase>::Buffer, R>; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::Owned; +use core::allocator::Allocator; +use geometry::{Translation, Point}; /// A direct isometry, i.e., a rotation followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] +#[derive(Debug)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] -pub struct IsometryBase { +#[cfg_attr(feature = "serde-serialize", + serde(bound( + serialize = "R: serde::Serialize, + DefaultAllocator: Allocator, + Owned: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound( + deserialize = "R: serde::Deserialize<'de>, + DefaultAllocator: Allocator, + Owned: serde::Deserialize<'de>")))] +pub struct Isometry + where DefaultAllocator: Allocator { /// The pure rotational part of this isometry. pub rotation: R, /// The pure translational part of this isometry. - pub translation: TranslationBase, + pub translation: Translation, // One dummy private field just to prevent explicit construction. @@ -36,11 +46,12 @@ pub struct IsometryBase { } #[cfg(feature = "abomonation-serialize")] -impl Abomonation for IsometryBase +impl Abomonation for IsometryBase where N: Scalar, D: DimName, R: Abomonation, - TranslationBase: Abomonation + TranslationBase: Abomonation, + DefaultAllocator: Allocator { unsafe fn entomb(&self, writer: &mut Vec) { self.rotation.entomb(writer); @@ -58,15 +69,35 @@ impl Abomonation for IsometryBase } } -impl IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl hash::Hash for Isometry + where DefaultAllocator: Allocator, + Owned: hash::Hash { + fn hash(&self, state: &mut H) { + self.translation.hash(state); + self.rotation.hash(state); + } +} + +impl> + Copy> Copy for Isometry + where DefaultAllocator: Allocator, + Owned: Copy { +} + +impl> + Clone> Clone for Isometry + where DefaultAllocator: Allocator { + #[inline] + fn clone(&self) -> Self { + Isometry::from_parts(self.translation.clone(), self.rotation.clone()) + } +} + +impl>> Isometry + where DefaultAllocator: Allocator { + /// Creates a new isometry from its rotational and translational parts. #[inline] - pub fn from_parts(translation: TranslationBase, rotation: R) -> IsometryBase { - IsometryBase { + pub fn from_parts(translation: Translation, rotation: R) -> Isometry { + Isometry { rotation: rotation, translation: translation, _noconstruct: PhantomData @@ -75,7 +106,7 @@ impl IsometryBase /// Inverts `self`. #[inline] - pub fn inverse(&self) -> IsometryBase { + pub fn inverse(&self) -> Isometry { let mut res = self.clone(); res.inverse_mut(); res @@ -91,7 +122,7 @@ impl IsometryBase /// Appends to `self` the given translation in-place. #[inline] - pub fn append_translation_mut(&mut self, t: &TranslationBase) { + pub fn append_translation_mut(&mut self, t: &Translation) { self.translation.vector += &t.vector } @@ -105,7 +136,7 @@ impl IsometryBase /// Appends in-place to `self` a rotation centered at the point `p`, i.e., the rotation that /// lets `p` invariant. #[inline] - pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &PointBase) { + pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &Point) { self.translation.vector -= &p.coords; self.append_rotation_mut(r); self.translation.vector += &p.coords; @@ -115,7 +146,7 @@ impl IsometryBase /// `self.translation`. #[inline] pub fn append_rotation_wrt_center_mut(&mut self, r: &R) { - let center = PointBase::from_coordinates(self.translation.vector.clone()); + let center = Point::from_coordinates(self.translation.vector.clone()); self.append_rotation_wrt_point_mut(r, ¢er) } } @@ -124,16 +155,15 @@ impl IsometryBase // 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 // explicit struct construction is prevented by the dummy ZST field). -impl IsometryBase - where N: Scalar, - S: Storage { +impl Isometry + where DefaultAllocator: Allocator { /// Converts this isometry into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + pub fn to_homogeneous(&self) -> MatrixN> where D: DimNameAdd, - R: SubsetOf, S::Alloc>>, - S::Alloc: Allocator, DimNameSum> { - let mut res: OwnedSquareMatrix = ::convert_ref(&self.rotation); + R: SubsetOf>>, + DefaultAllocator: Allocator, DimNameSum> { + let mut res: MatrixN = ::convert_ref(&self.rotation); res.fixed_slice_mut::(0, D::dim()).copy_from(&self.translation.vector); res @@ -141,30 +171,24 @@ impl IsometryBase } -impl Eq for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation> + Eq, - S::Alloc: OwnedAllocator { +impl Eq for Isometry + where R: Rotation> + Eq, + DefaultAllocator: Allocator { } -impl PartialEq for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation> + PartialEq, - S::Alloc: OwnedAllocator { +impl PartialEq for Isometry + where R: Rotation> + PartialEq, + DefaultAllocator: Allocator { #[inline] - fn eq(&self, right: &IsometryBase) -> bool { + fn eq(&self, right: &Isometry) -> bool { self.translation == right.translation && self.rotation == right.rotation } } -impl ApproxEq for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation> + ApproxEq, - S::Alloc: OwnedAllocator, +impl ApproxEq for Isometry + where R: Rotation> + ApproxEq, + DefaultAllocator: Allocator, N::Epsilon: Copy { type Epsilon = N::Epsilon; @@ -201,32 +225,16 @@ impl ApproxEq for IsometryBase * Display * */ -impl fmt::Display for IsometryBase - where N: Real + fmt::Display, - S: OwnedStorage, - R: fmt::Display, - S::Alloc: OwnedAllocator + Allocator { +impl fmt::Display for Isometry + where R: fmt::Display, + DefaultAllocator: Allocator + + Allocator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let precision = f.precision().unwrap_or(3); - try!(writeln!(f, "IsometryBase {{")); + try!(writeln!(f, "Isometry {{")); try!(write!(f, "{:.*}", precision, self.translation)); try!(write!(f, "{:.*}", precision, self.rotation)); writeln!(f, "}}") } } - - -// /* -// * -// * Absolute -// * -// */ -// impl Absolute for $t { -// type AbsoluteValue = $submatrix; -// -// #[inline] -// fn abs(m: &$t) -> $submatrix { -// Absolute::abs(&m.submatrix) -// } -// } diff --git a/src/geometry/isometry_alga.rs b/src/geometry/isometry_alga.rs index 8728d9ac..8e3a6453 100644 --- a/src/geometry/isometry_alga.rs +++ b/src/geometry/isometry_alga.rs @@ -1,14 +1,14 @@ use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; -use alga::linear::{Transformation, Similarity, AffineTransformation, DirectIsometry, Isometry, +use alga::linear::{Transformation, Similarity, AffineTransformation, DirectIsometry, Rotation, ProjectiveTransformation}; +use alga::linear::Isometry as AlgaIsometry; -use core::ColumnVector; -use core::dimension::{DimName, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::{DefaultAllocator, VectorN}; +use core::dimension::DimName; +use core::allocator::Allocator; -use geometry::{IsometryBase, TranslationBase, PointBase}; +use geometry::{Isometry, Translation, Point}; /* @@ -16,22 +16,18 @@ use geometry::{IsometryBase, TranslationBase, PointBase}; * Algebraic structures. * */ -impl Identity for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Identity for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::identity() } } -impl Inverse for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Inverse for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn inverse(&self) -> Self { self.inverse() @@ -43,11 +39,9 @@ impl Inverse for IsometryBase } } -impl AbstractMagma for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs @@ -56,11 +50,9 @@ impl AbstractMagma for IsometryBase),* $(,)*) => {$( - impl $marker<$operator> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { } )*} ); @@ -77,49 +69,43 @@ impl_multiplicative_structures!( * Transformation groups. * */ -impl Transformation> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Transformation> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point) -> Point { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &VectorN) -> VectorN { self * v } } -impl ProjectiveTransformation> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point) -> Point { self.rotation.inverse_transform_point(&(pt - &self.translation.vector)) } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.rotation.inverse_transform_vector(v) } } -impl AffineTransformation> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl AffineTransformation> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { type Rotation = R; type NonUniformScaling = Id; - type Translation = TranslationBase; + type Translation = Translation; #[inline] - fn decompose(&self) -> (TranslationBase, R, Id, R) { + fn decompose(&self) -> (Translation, R, Id, R) { (self.translation.clone(), self.rotation.clone(), Id::new(), R::identity()) } @@ -136,7 +122,7 @@ impl AffineTransformation> for IsometryB #[inline] fn append_rotation(&self, r: &Self::Rotation) -> Self { let shift = r.transform_vector(&self.translation.vector); - IsometryBase::from_parts(TranslationBase::from_vector(shift), r.clone() * self.rotation.clone()) + Isometry::from_parts(Translation::from_vector(shift), r.clone() * self.rotation.clone()) } #[inline] @@ -155,22 +141,20 @@ impl AffineTransformation> for IsometryB } #[inline] - fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &PointBase) -> Option { + fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &Point) -> Option { let mut res = self.clone(); res.append_rotation_wrt_point_mut(r, p); Some(res) } } -impl Similarity> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Similarity> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { type Scaling = Id; #[inline] - fn translation(&self) -> TranslationBase { + fn translation(&self) -> Translation { self.translation.clone() } @@ -187,12 +171,10 @@ impl Similarity> for IsometryBase {$( - impl $Trait> for IsometryBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { } + impl $Trait> for Isometry + where R: Rotation>, + DefaultAllocator: Allocator { } )*} ); -marker_impl!(Isometry, DirectIsometry); +marker_impl!(AlgaIsometry, DirectIsometry); diff --git a/src/geometry/isometry_alias.rs b/src/geometry/isometry_alias.rs index 0bf44b4a..b9db1c63 100644 --- a/src/geometry/isometry_alias.rs +++ b/src/geometry/isometry_alias.rs @@ -1,19 +1,16 @@ -use core::MatrixArray; -use core::dimension::{U1, U2, U3}; +use core::dimension::{U2, U3}; -use geometry::{Rotation, IsometryBase, UnitQuaternion, UnitComplex}; +use geometry::{Isometry, Rotation2, Rotation3, UnitQuaternion, UnitComplex}; -/// A D-dimensional isometry. -pub type Isometry = IsometryBase, Rotation>; /// A 2-dimensional isometry using a unit complex number for its rotational part. -pub type Isometry2 = IsometryBase, UnitComplex>; +pub type Isometry2 = Isometry>; /// A 3-dimensional isometry using a unit quaternion for its rotational part. -pub type Isometry3 = IsometryBase, UnitQuaternion>; +pub type Isometry3 = Isometry>; -/// A 2-dimensional isometry using a rotation matrix for its rotation part. -pub type IsometryMatrix2 = Isometry; +/// A 2-dimensional isometry using a rotation matrix for its rotational part. +pub type IsometryMatrix2 = Isometry>; -/// A 3-dimensional isometry using a rotation matrix for its rotation part. -pub type IsometryMatrix3 = Isometry; +/// A 3-dimensional isometry using a rotation matrix for its rotational part. +pub type IsometryMatrix3 = Isometry>; diff --git a/src/geometry/isometry_construction.rs b/src/geometry/isometry_construction.rs index 9eb3a42f..27ac5169 100644 --- a/src/geometry/isometry_construction.rs +++ b/src/geometry/isometry_construction.rs @@ -1,5 +1,7 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; use num::One; use rand::{Rng, Rand}; @@ -7,31 +9,33 @@ use rand::{Rng, Rand}; use alga::general::Real; use alga::linear::Rotation as AlgaRotation; -use core::ColumnVector; -use core::dimension::{DimName, U1, U2, U3, U4}; -use core::allocator::{OwnedAllocator, Allocator}; -use core::storage::OwnedStorage; +use core::{DefaultAllocator, Vector2, Vector3}; +use core::dimension::{DimName, U2, U3}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, RotationBase, IsometryBase, UnitQuaternionBase, UnitComplex}; +use geometry::{Point, Translation, Rotation, Isometry, UnitQuaternion, UnitComplex, + Point3, Rotation2, Rotation3}; -impl IsometryBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { +impl>> Isometry + where DefaultAllocator: Allocator { /// Creates a new identity isometry. #[inline] pub fn identity() -> Self { - Self::from_parts(TranslationBase::identity(), R::identity()) + Self::from_parts(Translation::identity(), R::identity()) + } + + /// The isometry that applies the rotation `r` with its axis passing through the point `p`. + /// This effectively lets `p` invariant. + #[inline] + pub fn rotation_wrt_point(r: R, p: Point) -> Self { + let shift = r.transform_vector(&-&p.coords); + Self::from_parts(Translation::from_vector(shift + p.coords), r) } } -impl One for IsometryBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { +impl>> One for Isometry + where DefaultAllocator: Allocator { /// Creates a new identity isometry. #[inline] fn one() -> Self { @@ -39,37 +43,21 @@ impl One for IsometryBase } } -impl Rand for IsometryBase - where N: Real + Rand, - S: OwnedStorage, - R: AlgaRotation> + Rand, - S::Alloc: OwnedAllocator { +impl Rand for Isometry + where R: AlgaRotation> + Rand, + DefaultAllocator: Allocator { #[inline] fn rand(rng: &mut G) -> Self { Self::from_parts(rng.gen(), rng.gen()) } } -impl IsometryBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { - /// The isometry that applies the rotation `r` with its axis passing through the point `p`. - /// This effectively lets `p` invariant. - #[inline] - pub fn rotation_wrt_point(r: R, p: PointBase) -> Self { - let shift = r.transform_vector(&-&p.coords); - Self::from_parts(TranslationBase::from_vector(shift + p.coords), r) - } -} - #[cfg(feature = "arbitrary")] -impl Arbitrary for IsometryBase +impl Arbitrary for Isometry where N: Real + Arbitrary + Send, - S: OwnedStorage + Send, - R: AlgaRotation> + Arbitrary + Send, - S::Alloc: OwnedAllocator { + R: AlgaRotation> + Arbitrary + Send, + Owned: Send, + DefaultAllocator: Allocator { #[inline] fn arbitrary(rng: &mut G) -> Self { Self::from_parts(Arbitrary::arbitrary(rng), Arbitrary::arbitrary(rng)) @@ -83,45 +71,31 @@ impl Arbitrary for IsometryBase */ // 2D rotation. -impl IsometryBase> - where N: Real, - S: OwnedStorage, - SR: OwnedStorage, - S::Alloc: OwnedAllocator, - SR::Alloc: OwnedAllocator { +impl Isometry> { /// Creates a new isometry from a translation and a rotation angle. #[inline] - pub fn new(translation: ColumnVector, angle: N) -> Self { - Self::from_parts(TranslationBase::from_vector(translation), RotationBase::::new(angle)) + pub fn new(translation: Vector2, angle: N) -> Self { + Self::from_parts(Translation::from_vector(translation), Rotation::::new(angle)) } } -impl IsometryBase> - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Isometry> { /// Creates a new isometry from a translation and a rotation angle. #[inline] - pub fn new(translation: ColumnVector, angle: N) -> Self { - Self::from_parts(TranslationBase::from_vector(translation), UnitComplex::from_angle(angle)) + pub fn new(translation: Vector2, angle: N) -> Self { + Self::from_parts(Translation::from_vector(translation), UnitComplex::from_angle(angle)) } } // 3D rotation. macro_rules! isometry_construction_impl( ($RotId: ident < $($RotParams: ident),*>, $RRDim: ty, $RCDim: ty) => { - impl IsometryBase> - where N: Real, - S: OwnedStorage, - SR: OwnedStorage, - S::Alloc: OwnedAllocator, - SR::Alloc: OwnedAllocator + - Allocator { + impl Isometry> { /// Creates a new isometry from a translation and a rotation axis-angle. #[inline] - pub fn new(translation: ColumnVector, axisangle: ColumnVector) -> Self { + pub fn new(translation: Vector3, axisangle: Vector3) -> Self { Self::from_parts( - TranslationBase::from_vector(translation), + Translation::from_vector(translation), $RotId::<$($RotParams),*>::from_scaled_axis(axisangle)) } @@ -137,12 +111,12 @@ macro_rules! isometry_construction_impl( /// * up - Vertical direction. The only requirement of this parameter is to not be collinear /// to `eye - at`. Non-collinearity is not checked. #[inline] - pub fn new_observer_frame(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) + pub fn new_observer_frame(eye: &Point3, + target: &Point3, + up: &Vector3) -> Self { Self::from_parts( - TranslationBase::from_vector(eye.coords.clone()), + Translation::from_vector(eye.coords.clone()), $RotId::new_observer_frame(&(target - eye), up)) } @@ -157,14 +131,14 @@ macro_rules! isometry_construction_impl( /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_rh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) + pub fn look_at_rh(eye: &Point3, + target: &Point3, + up: &Vector3) -> Self { let rotation = $RotId::look_at_rh(&(target - eye), up); let trans = &rotation * (-eye); - Self::from_parts(TranslationBase::from_vector(trans.coords), rotation) + Self::from_parts(Translation::from_vector(trans.coords), rotation) } /// Builds a left-handed look-at view matrix. @@ -178,18 +152,18 @@ macro_rules! isometry_construction_impl( /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_lh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector) + pub fn look_at_lh(eye: &Point3, + target: &Point3, + up: &Vector3) -> Self { let rotation = $RotId::look_at_lh(&(target - eye), up); let trans = &rotation * (-eye); - Self::from_parts(TranslationBase::from_vector(trans.coords), rotation) + Self::from_parts(Translation::from_vector(trans.coords), rotation) } } } ); -isometry_construction_impl!(RotationBase, U3, U3); -isometry_construction_impl!(UnitQuaternionBase, U4, U1); +isometry_construction_impl!(Rotation3, U3, U3); +isometry_construction_impl!(UnitQuaternion, U4, U1); diff --git a/src/geometry/isometry_conversion.rs b/src/geometry/isometry_conversion.rs index c71b7289..01aa55db 100644 --- a/src/geometry/isometry_conversion.rs +++ b/src/geometry/isometry_conversion.rs @@ -1,50 +1,47 @@ use alga::general::{Real, SubsetOf, SupersetOf}; use alga::linear::Rotation; -use core::{SquareMatrix, OwnedSquareMatrix}; -use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, DimMin, U1}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; +use geometry::{Point, Translation, Isometry, Similarity, Transform, SuperTCategoryOf, TAffine}; /* * This file provides the following conversions: * ============================================= * - * IsometryBase -> IsometryBase - * IsometryBase -> SimilarityBase - * IsometryBase -> TransformBase - * IsometryBase -> Matrix (homogeneous) + * Isometry -> Isometry + * Isometry -> Similarity + * Isometry -> Transform + * Isometry -> Matrix (homogeneous) */ -impl SubsetOf> for IsometryBase +impl SubsetOf> for Isometry where N1: Real, N2: Real + SupersetOf, - R1: Rotation> + SubsetOf, - R2: Rotation>, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R1: Rotation> + SubsetOf, + R2: Rotation>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> IsometryBase { - IsometryBase::from_parts( + fn to_superset(&self) -> Isometry { + Isometry::from_parts( self.translation.to_superset(), self.rotation.to_superset() ) } #[inline] - fn is_in_subset(iso: &IsometryBase) -> bool { - ::is_convertible::<_, TranslationBase>(&iso.translation) && + fn is_in_subset(iso: &Isometry) -> bool { + ::is_convertible::<_, Translation>(&iso.translation) && ::is_convertible::<_, R1>(&iso.rotation) } #[inline] - unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { - IsometryBase::from_parts( + unsafe fn from_superset_unchecked(iso: &Isometry) -> Self { + Isometry::from_parts( iso.translation.to_subset_unchecked(), iso.rotation.to_subset_unchecked() ) @@ -52,95 +49,91 @@ impl SubsetOf> f } -impl SubsetOf> for IsometryBase +impl SubsetOf> for Isometry where N1: Real, N2: Real + SupersetOf, - R1: Rotation> + SubsetOf, - R2: Rotation>, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R1: Rotation> + SubsetOf, + R2: Rotation>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_isometry( + fn to_superset(&self) -> Similarity { + Similarity::from_isometry( self.to_superset(), N2::one() ) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { - ::is_convertible::<_, IsometryBase>(&sim.isometry) && + fn is_in_subset(sim: &Similarity) -> bool { + ::is_convertible::<_, Isometry>(&sim.isometry) && sim.scaling() == N2::one() } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { ::convert_ref_unchecked(&sim.isometry) } } -impl SubsetOf> for IsometryBase +impl SubsetOf> for Isometry where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, C: SuperTCategoryOf, - R: Rotation> + - SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() - SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator + // needed by R - Allocator, DimNameSum> + // needed by: .to_homogeneous() - Allocator, DimNameSum>, // needed by R - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + // needed by: mm.fixed_slice_mut - Allocator + // needed by: m.fixed_slice - Allocator { // needed by: m.fixed_slice + R: Rotation> + + SubsetOf>> + + SubsetOf>>, + D: DimNameAdd + + DimMin, // needed by .is_special_orthogonal() + DefaultAllocator: Allocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum> + // needed by R + Allocator, DimNameSum> + + Allocator<(usize, usize), D> + // needed by .is_special_orthogonal() + Allocator + + Allocator { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf, SB>> for IsometryBase +impl SubsetOf>> for Isometry where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, - R: Rotation> + - SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() - SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator + // needed by R - Allocator, DimNameSum> + // needed by: .to_homogeneous() - Allocator, DimNameSum>, // needed by R - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + // needed by: mm.fixed_slice_mut - Allocator + // needed by: m.fixed_slice - Allocator { // needed by: m.fixed_slice + R: Rotation> + + SubsetOf>> + + SubsetOf>>, + D: DimNameAdd + + DimMin, // needed by .is_special_orthogonal() + DefaultAllocator: Allocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum> + // needed by R + Allocator, DimNameSum> + + Allocator<(usize, usize), D> + // needed by .is_special_orthogonal() + Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SquareMatrix, SB> { + fn to_superset(&self) -> MatrixN> { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + fn is_in_subset(m: &MatrixN>) -> bool { let rot = m.fixed_slice::(0, 0); let bottom = m.fixed_slice::(D::dim(), 0); @@ -154,9 +147,9 @@ impl SubsetOf, SB>> for } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + unsafe fn from_superset_unchecked(m: &MatrixN>) -> Self { let t = m.fixed_slice::(0, D::dim()).into_owned(); - let t = TranslationBase::from_vector(::convert_unchecked(t)); + let t = Translation::from_vector(::convert_unchecked(t)); Self::from_parts(t, ::convert_unchecked(m.clone_owned())) } diff --git a/src/geometry/isometry_ops.rs b/src/geometry/isometry_ops.rs index 0ca1bca8..02b89590 100644 --- a/src/geometry/isometry_ops.rs +++ b/src/geometry/isometry_ops.rs @@ -1,14 +1,13 @@ use std::ops::{Mul, MulAssign, Div, DivAssign}; use alga::general::Real; -use alga::linear::Rotation; +use alga::linear::Rotation as AlgaRotation; -use core::ColumnVector; +use core::{DefaultAllocator, VectorN}; use core::dimension::{DimName, U1, U3, U4}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::{PointBase, RotationBase, IsometryBase, TranslationBase, UnitQuaternionBase}; +use geometry::{Point, Rotation, Isometry, Translation, UnitQuaternion}; // FIXME: there are several cloning of rotations that we could probably get rid of (but we didn't // yet because that would require to add a bound like `where for<'a, 'b> &'a R: Mul<&'b R, Output = R>` @@ -22,41 +21,41 @@ use geometry::{PointBase, RotationBase, IsometryBase, TranslationBase, UnitQuate * * (Operators) * - * IsometryBase × IsometryBase - * IsometryBase × R + * Isometry × Isometry + * Isometry × R * * - * IsometryBase ÷ IsometryBase - * IsometryBase ÷ R + * Isometry ÷ Isometry + * Isometry ÷ R * - * IsometryBase × PointBase - * IsometryBase × ColumnVector + * Isometry × Point + * Isometry × Vector * * - * IsometryBase × TranslationBase - * TranslationBase × IsometryBase - * TranslationBase × R -> IsometryBase + * Isometry × Translation + * Translation × Isometry + * Translation × R -> Isometry * - * NOTE: The following are provided explicitly because we can't have R × IsometryBase. - * RotationBase × IsometryBase - * UnitQuaternion × IsometryBase + * NOTE: The following are provided explicitly because we can't have R × Isometry. + * Rotation × Isometry + * UnitQuaternion × Isometry * - * RotationBase ÷ IsometryBase - * UnitQuaternion ÷ IsometryBase + * Rotation ÷ Isometry + * UnitQuaternion ÷ Isometry * - * RotationBase × TranslationBase -> IsometryBase - * UnitQuaternion × TranslationBase -> IsometryBase + * Rotation × Translation -> Isometry + * UnitQuaternion × Translation -> Isometry * * * (Assignment Operators) * - * IsometryBase ×= TranslationBase + * Isometry ×= Translation * - * IsometryBase ×= IsometryBase - * IsometryBase ×= R + * Isometry ×= Isometry + * Isometry ×= R * - * IsometryBase ÷= IsometryBase - * IsometryBase ÷= R + * Isometry ÷= Isometry + * Isometry ÷= R * */ @@ -65,11 +64,9 @@ macro_rules! isometry_binop_impl( ($Op: ident, $op: ident; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N, D: DimName, S, R> $Op<$Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl<$($lives ,)* N: Real, D: DimName, R> $Op<$Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { type Output = $Output; #[inline] @@ -114,22 +111,18 @@ macro_rules! isometry_binop_assign_impl_all( $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; [val] => $action_val: expr; [ref] => $action_ref: expr;) => { - impl $OpAssign<$Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl $OpAssign<$Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { #[inline] fn $op_assign(&mut $lhs, $rhs: $Rhs) { $action_val } } - impl<'b, N, D: DimName, S, R> $OpAssign<&'b $Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl<'b, N: Real, D: DimName, R> $OpAssign<&'b $Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { #[inline] fn $op_assign(&mut $lhs, $rhs: &'b $Rhs) { $action_ref @@ -138,18 +131,18 @@ macro_rules! isometry_binop_assign_impl_all( } ); -// IsometryBase × IsometryBase -// IsometryBase ÷ IsometryBase +// Isometry × Isometry +// Isometry ÷ Isometry isometry_binop_impl_all!( Mul, mul; - self: IsometryBase, rhs: IsometryBase, Output = IsometryBase; + self: Isometry, rhs: Isometry, Output = Isometry; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => { let shift = self.rotation.transform_vector(&rhs.translation.vector); - IsometryBase::from_parts(TranslationBase::from_vector(&self.translation.vector + shift), + Isometry::from_parts(Translation::from_vector(&self.translation.vector + shift), self.rotation.clone() * rhs.rotation.clone()) // FIXME: too bad we have to clone. }; ); @@ -157,7 +150,7 @@ isometry_binop_impl_all!( isometry_binop_impl_all!( Div, div; - self: IsometryBase, rhs: IsometryBase, Output = IsometryBase; + self: Isometry, rhs: Isometry, Output = Isometry; [val val] => self * rhs.inverse(); [ref val] => self * rhs.inverse(); [val ref] => self * rhs.inverse(); @@ -165,10 +158,10 @@ isometry_binop_impl_all!( ); -// IsometryBase ×= TranslationBase +// Isometry ×= Translation isometry_binop_assign_impl_all!( MulAssign, mul_assign; - self: IsometryBase, rhs: TranslationBase; + self: Isometry, rhs: Translation; [val] => *self *= &rhs; [ref] => { let shift = self.rotation.transform_vector(&rhs.vector); @@ -176,11 +169,11 @@ isometry_binop_assign_impl_all!( }; ); -// IsometryBase ×= IsometryBase -// IsometryBase ÷= IsometryBase +// Isometry ×= Isometry +// Isometry ÷= Isometry isometry_binop_assign_impl_all!( MulAssign, mul_assign; - self: IsometryBase, rhs: IsometryBase; + self: Isometry, rhs: Isometry; [val] => *self *= &rhs; [ref] => { let shift = self.rotation.transform_vector(&rhs.translation.vector); @@ -191,66 +184,68 @@ isometry_binop_assign_impl_all!( isometry_binop_assign_impl_all!( DivAssign, div_assign; - self: IsometryBase, rhs: IsometryBase; + self: Isometry, rhs: Isometry; [val] => *self /= &rhs; [ref] => *self *= rhs.inverse(); ); -// IsometryBase ×= R -// IsometryBase ÷= R +// Isometry ×= R +// Isometry ÷= R isometry_binop_assign_impl_all!( MulAssign, mul_assign; - self: IsometryBase, rhs: R; + self: Isometry, rhs: R; [val] => self.rotation *= rhs; [ref] => self.rotation *= rhs.clone(); ); isometry_binop_assign_impl_all!( DivAssign, div_assign; - self: IsometryBase, rhs: R; + self: Isometry, rhs: R; // FIXME: don't invert explicitly? [val] => *self *= rhs.inverse(); [ref] => *self *= rhs.inverse(); ); -// IsometryBase × R -// IsometryBase ÷ R +// Isometry × R +// Isometry ÷ R isometry_binop_impl_all!( Mul, mul; - self: IsometryBase, rhs: R, Output = IsometryBase; - [val val] => IsometryBase::from_parts(self.translation, self.rotation * rhs); - [ref val] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() * rhs); // FIXME: do not clone. - [val ref] => IsometryBase::from_parts(self.translation, self.rotation * rhs.clone()); - [ref ref] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() * rhs.clone()); + self: Isometry, rhs: R, Output = Isometry; + [val val] => Isometry::from_parts(self.translation, self.rotation * rhs); + [ref val] => Isometry::from_parts(self.translation.clone(), self.rotation.clone() * rhs); // FIXME: do not clone. + [val ref] => Isometry::from_parts(self.translation, self.rotation * rhs.clone()); + [ref ref] => Isometry::from_parts(self.translation.clone(), self.rotation.clone() * rhs.clone()); ); isometry_binop_impl_all!( Div, div; - self: IsometryBase, rhs: R, Output = IsometryBase; - [val val] => IsometryBase::from_parts(self.translation, self.rotation / rhs); - [ref val] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() / rhs); - [val ref] => IsometryBase::from_parts(self.translation, self.rotation / rhs.clone()); - [ref ref] => IsometryBase::from_parts(self.translation.clone(), self.rotation.clone() / rhs.clone()); + self: Isometry, rhs: R, Output = Isometry; + [val val] => Isometry::from_parts(self.translation, self.rotation / rhs); + [ref val] => Isometry::from_parts(self.translation.clone(), self.rotation.clone() / rhs); + [val ref] => Isometry::from_parts(self.translation, self.rotation / rhs.clone()); + [ref ref] => Isometry::from_parts(self.translation.clone(), self.rotation.clone() / rhs.clone()); ); -// IsometryBase × PointBase +// Isometry × Point isometry_binop_impl_all!( Mul, mul; - self: IsometryBase, right: PointBase, Output = PointBase; - [val val] => self.translation * self.rotation.transform_point(&right); + self: Isometry, right: Point, Output = Point; + [val val] => self.translation * self.rotation.transform_point(&right); [ref val] => &self.translation * self.rotation.transform_point(&right); - [val ref] => self.translation * self.rotation.transform_point(right); + [val ref] => self.translation * self.rotation.transform_point(right); [ref ref] => &self.translation * self.rotation.transform_point(right); ); -// IsometryBase × Vector +// Isometry × Vector isometry_binop_impl_all!( Mul, mul; - self: IsometryBase, right: ColumnVector, Output = ColumnVector; + // FIXME: because of `transform_vector`, we cant use a generic storage type for the rhs vector, + // i.e., right: Vector where S: Storage. + self: Isometry, right: VectorN, Output = VectorN; [val val] => self.rotation.transform_vector(&right); [ref val] => self.rotation.transform_vector(&right); [val ref] => self.rotation.transform_vector(right); @@ -258,38 +253,38 @@ isometry_binop_impl_all!( ); -// IsometryBase × TranslationBase +// Isometry × Translation isometry_binop_impl_all!( Mul, mul; - self: IsometryBase, right: TranslationBase, Output = IsometryBase; + self: Isometry, right: Translation, Output = Isometry; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; [ref ref] => { let new_tr = &self.translation.vector + self.rotation.transform_vector(&right.vector); - IsometryBase::from_parts(TranslationBase::from_vector(new_tr), self.rotation.clone()) + Isometry::from_parts(Translation::from_vector(new_tr), self.rotation.clone()) }; ); -// TranslationBase × IsometryBase +// Translation × Isometry isometry_binop_impl_all!( Mul, mul; - self: TranslationBase, right: IsometryBase, Output = IsometryBase; - [val val] => IsometryBase::from_parts(self * right.translation, right.rotation); - [ref val] => IsometryBase::from_parts(self * &right.translation, right.rotation); - [val ref] => IsometryBase::from_parts(self * &right.translation, right.rotation.clone()); - [ref ref] => IsometryBase::from_parts(self * &right.translation, right.rotation.clone()); + self: Translation, right: Isometry, Output = Isometry; + [val val] => Isometry::from_parts(self * right.translation, right.rotation); + [ref val] => Isometry::from_parts(self * &right.translation, right.rotation); + [val ref] => Isometry::from_parts(self * &right.translation, right.rotation.clone()); + [ref ref] => Isometry::from_parts(self * &right.translation, right.rotation.clone()); ); -// TranslationBase × R +// Translation × R isometry_binop_impl_all!( Mul, mul; - self: TranslationBase, right: R, Output = IsometryBase; - [val val] => IsometryBase::from_parts(self, right); - [ref val] => IsometryBase::from_parts(self.clone(), right); - [val ref] => IsometryBase::from_parts(self, right.clone()); - [ref ref] => IsometryBase::from_parts(self.clone(), right.clone()); + self: Translation, right: R, Output = Isometry; + [val val] => Isometry::from_parts(self, right); + [ref val] => Isometry::from_parts(self.clone(), right); + [val ref] => Isometry::from_parts(self, right.clone()); + [ref ref] => Isometry::from_parts(self.clone(), right.clone()); ); @@ -300,12 +295,9 @@ macro_rules! isometry_from_composition_impl( ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + impl<$($lives ,)* N: Real $(, $Dims: $DimsBound)*> $Op<$Rhs> for $Lhs + where DefaultAllocator: Allocator + + Allocator { type Output = $Output; #[inline] @@ -352,51 +344,51 @@ macro_rules! isometry_from_composition_impl_all( ); -// RotationBase × TranslationBase +// Rotation × Translation isometry_from_composition_impl_all!( Mul, mul; (D, D), (D, U1) for D: DimName; - self: RotationBase, right: TranslationBase, Output = IsometryBase>; - [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); - [ref val] => IsometryBase::from_parts(TranslationBase::from_vector(self * right.vector), self.clone()); - [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); - [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector(self * &right.vector), self.clone()); + self: Rotation, right: Translation, Output = Isometry>; + [val val] => Isometry::from_parts(Translation::from_vector(&self * right.vector), self); + [ref val] => Isometry::from_parts(Translation::from_vector(self * right.vector), self.clone()); + [val ref] => Isometry::from_parts(Translation::from_vector(&self * &right.vector), self); + [ref ref] => Isometry::from_parts(Translation::from_vector(self * &right.vector), self.clone()); ); -// UnitQuaternionBase × TranslationBase +// UnitQuaternion × Translation isometry_from_composition_impl_all!( Mul, mul; (U4, U1), (U3, U1); - self: UnitQuaternionBase, right: TranslationBase, - Output = IsometryBase>; - [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * right.vector), self); - [ref val] => IsometryBase::from_parts(TranslationBase::from_vector( self * right.vector), self.clone()); - [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &right.vector), self); - [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector( self * &right.vector), self.clone()); + self: UnitQuaternion, right: Translation, + Output = Isometry>; + [val val] => Isometry::from_parts(Translation::from_vector(&self * right.vector), self); + [ref val] => Isometry::from_parts(Translation::from_vector( self * right.vector), self.clone()); + [val ref] => Isometry::from_parts(Translation::from_vector(&self * &right.vector), self); + [ref ref] => Isometry::from_parts(Translation::from_vector( self * &right.vector), self.clone()); ); -// RotationBase × IsometryBase +// Rotation × Isometry isometry_from_composition_impl_all!( Mul, mul; (D, D), (D, U1) for D: DimName; - self: RotationBase, right: IsometryBase>, - Output = IsometryBase>; + self: Rotation, right: Isometry>, + Output = Isometry>; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; [ref ref] => { let shift = self * &right.translation.vector; - IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &right.rotation) + Isometry::from_parts(Translation::from_vector(shift), self * &right.rotation) }; ); -// RotationBase ÷ IsometryBase +// Rotation ÷ Isometry isometry_from_composition_impl_all!( Div, div; (D, D), (D, U1) for D: DimName; - self: RotationBase, right: IsometryBase>, - Output = IsometryBase>; + self: Rotation, right: Isometry>, + Output = Isometry>; // FIXME: don't call iverse explicitly? [val val] => self * right.inverse(); [ref val] => self * right.inverse(); @@ -405,28 +397,28 @@ isometry_from_composition_impl_all!( ); -// UnitQuaternion × IsometryBase +// UnitQuaternion × Isometry isometry_from_composition_impl_all!( Mul, mul; (U4, U1), (U3, U1); - self: UnitQuaternionBase, right: IsometryBase>, - Output = IsometryBase>; + self: UnitQuaternion, right: Isometry>, + Output = Isometry>; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; [ref ref] => { let shift = self * &right.translation.vector; - IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &right.rotation) + Isometry::from_parts(Translation::from_vector(shift), self * &right.rotation) }; ); -// UnitQuaternion ÷ IsometryBase +// UnitQuaternion ÷ Isometry isometry_from_composition_impl_all!( Div, div; (U4, U1), (U3, U1); - self: UnitQuaternionBase, right: IsometryBase>, - Output = IsometryBase>; + self: UnitQuaternion, right: Isometry>, + Output = Isometry>; // FIXME: don't call inverse explicitly? [val val] => self * right.inverse(); [ref val] => self * right.inverse(); diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index d99e82cb..f125f9ed 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -14,7 +14,7 @@ mod point_coordinates; mod rotation; mod rotation_construction; mod rotation_ops; -mod rotation_alga; // FIXME: implement RotationBase methods. +mod rotation_alga; // FIXME: implement Rotation methods. mod rotation_conversion; mod rotation_alias; mod rotation_specialization; @@ -23,9 +23,8 @@ mod quaternion; mod quaternion_construction; mod quaternion_ops; mod quaternion_alga; -mod quaternion_alias; -mod quaternion_coordinates; mod quaternion_conversion; +mod quaternion_coordinates; mod unit_complex; mod unit_complex_construction; @@ -61,6 +60,8 @@ mod transform_alga; mod transform_conversion; mod transform_alias; +mod reflection; + mod orthographic; mod perspective; @@ -71,7 +72,6 @@ pub use self::rotation::*; pub use self::rotation_alias::*; pub use self::quaternion::*; -pub use self::quaternion_alias::*; pub use self::unit_complex::*; @@ -87,5 +87,7 @@ pub use self::similarity_alias::*; pub use self::transform::*; pub use self::transform_alias::*; -pub use self::orthographic::{OrthographicBase, Orthographic3}; -pub use self::perspective::{PerspectiveBase, Perspective3}; +pub use self::reflection::*; + +pub use self::orthographic::Orthographic3; +pub use self::perspective::Perspective3; diff --git a/src/geometry/op_macros.rs b/src/geometry/op_macros.rs index c81fd452..112b2649 100644 --- a/src/geometry/op_macros.rs +++ b/src/geometry/op_macros.rs @@ -8,7 +8,7 @@ macro_rules! md_impl( // Operator, operator method, and calar bounds. $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; // Storage dimensions, and dimension bounds. - ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$($BoundParam: ty),*>)*),+ // [Optional] Extra allocator bounds. $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; // Argument identifiers and types + output. @@ -17,10 +17,11 @@ macro_rules! md_impl( $action: expr; // Lifetime. $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$BoundParam>)*)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Scalar + Zero + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, - SA: Storage, - SB: Storage, + impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$($BoundParam),*>)*)*> $Op<$Rhs> for $Lhs + where N: Scalar + Zero + One + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, + DefaultAllocator: Allocator + + Allocator + + Allocator, $( $ConstraintType: $ConstraintBound<$( $ConstraintBoundParams $( = $EqBound )*),*> ),* { type Output = $Result; @@ -41,7 +42,7 @@ macro_rules! md_impl_all( // Operator, operator method, and calar bounds. $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; // Storage dimensions, and dimension bounds. - ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$($BoundParam: ty),*>)*),+ // [Optional] Extra allocator bounds. $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; // Argument identifiers and types + output. @@ -54,28 +55,28 @@ macro_rules! md_impl_all( md_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; $lhs: $Lhs, $rhs: $Rhs, Output = $Result; $action_val_val; ); md_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Result; $action_ref_val; 'a); md_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Result; $action_val_ref; 'b); md_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Result; $action_ref_ref; 'a, 'b); @@ -89,19 +90,18 @@ macro_rules! md_assign_impl( // Operator, operator method, and scalar bounds. $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; // Storage dimensions, and dimension bounds. - ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$($BoundParam: ty),*>)*),+ // [Optional] Extra allocator bounds. - $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + $(where $ConstraintType: ty: $ConstraintBound: ident $(<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*>)* )*; // Argument identifiers and types. $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; // Actual implementation and lifetimes. $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$BoundParam>)*)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Scalar + Zero + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, - SA: OwnedStorage, // FIXME: this is too restrictive. - SB: Storage, - SA::Alloc: OwnedAllocator, - $( $ConstraintType: $ConstraintBound<$( $ConstraintBoundParams $( = $EqBound )*),*> ),* + impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$($BoundParam),*>)*)*> $Op<$Rhs> for $Lhs + where N: Scalar + Zero + One + ClosedAdd + ClosedMul $($(+ $ScalarBounds)*)*, + DefaultAllocator: Allocator + + Allocator, + $( $ConstraintType: $ConstraintBound $(<$( $ConstraintBoundParams $( = $EqBound )*),*>)* ),* { #[inline] fn $op(&mut $lhs, $rhs: $Rhs) { @@ -118,9 +118,9 @@ macro_rules! md_assign_impl_all( // Operator, operator method, and scalar bounds. $Op: ident, $op: ident $(where N: $($ScalarBounds: ident),*)*; // Storage dimensions, and dimension bounds. - ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$BoundParam: ty>)*),+ + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident $(<$($BoundParam: ty),*>)*),+ // [Optional] Extra allocator bounds. - $(where $ConstraintType: ty: $ConstraintBound: ident<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*> )*; + $(where $ConstraintType: ty: $ConstraintBound: ident$(<$($ConstraintBoundParams: ty $( = $EqBound: ty )*),*>)* )*; // Argument identifiers and types. $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; // Actual implementation and lifetimes. @@ -128,15 +128,15 @@ macro_rules! md_assign_impl_all( [ref] => $action_ref: expr;) => { md_assign_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ - $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ + $(where $ConstraintType: $ConstraintBound $(<$($ConstraintBoundParams $( = $EqBound )*),*>)*)*; $lhs: $Lhs, $rhs: $Rhs; $action_val; ); md_assign_impl!( $Op, $op $(where N: $($ScalarBounds),*)*; - ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$BoundParam>)*),+ - $(where $ConstraintType: $ConstraintBound<$($ConstraintBoundParams $( = $EqBound )*),*>)*; + ($R1, $C1),($R2, $C2) for $($Dims: $DimsBound $(<$($BoundParam),*>)*),+ + $(where $ConstraintType: $ConstraintBound $(<$($ConstraintBoundParams $( = $EqBound )*),*>)*)*; $lhs: $Lhs, $rhs: &'b $Rhs; $action_ref; 'b); } @@ -146,14 +146,14 @@ macro_rules! md_assign_impl_all( /// Macro for the implementation of addition and subtraction. macro_rules! add_sub_impl( ($Op: ident, $op: ident, $bound: ident; - ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(-> ($RRes: ty))* for $($Dims: ident: $DimsBound: ident),+; + ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(-> ($RRes: ty))* for $($Dims: ident: $DimsBound: ident $(<$($BoundParam: ty),*>)*),+; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Scalar + $bound, - SA: Storage, - SB: Storage, - SA::Alloc: SameShapeAllocator, + impl<$($lives ,)* N $(, $Dims: $DimsBound $(<$($BoundParam),*>)*)*> $Op<$Rhs> for $Lhs + where N: Scalar + $bound, + DefaultAllocator: Allocator + + Allocator + + SameShapeAllocator, ShapeConstraint: SameNumberOfRows<$R1, $R2 $(, Representative = $RRes)*> + SameNumberOfColumns<$C1, $C2> { type Output = $Result; @@ -173,11 +173,10 @@ macro_rules! add_sub_assign_impl( ($R1: ty, $C1: ty),($R2: ty, $C2: ty) for $($Dims: ident: $DimsBound: ident),+; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Scalar + $bound, - SA: OwnedStorage, // FIXME: this is too restrictive. - SB: Storage, - SA::Alloc: OwnedAllocator, + impl<$($lives ,)* N $(, $Dims: $DimsBound)*> $Op<$Rhs> for $Lhs + where N: Scalar + $bound, + DefaultAllocator: Allocator + + Allocator, ShapeConstraint: SameNumberOfRows<$R1, $R2> + SameNumberOfColumns<$C1, $C2> { #[inline] fn $op(&mut $lhs, $rhs: $Rhs) { diff --git a/src/geometry/orthographic.rs b/src/geometry/orthographic.rs index c907de6a..b74ef66e 100644 --- a/src/geometry/orthographic.rs +++ b/src/geometry/orthographic.rs @@ -1,70 +1,65 @@ #[cfg(feature="arbitrary")] use quickcheck::{Arbitrary, Gen}; use rand::{Rand, Rng}; - #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; +use std::fmt; use alga::general::Real; -use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector, MatrixArray}; -use core::dimension::{U1, U3, U4}; -use core::storage::{OwnedStorage, Storage, StorageMut}; -use core::allocator::OwnedAllocator; +use core::{Matrix4, Vector, Vector3}; +use core::dimension::U3; +use core::storage::Storage; use core::helper; -use geometry::{PointBase, OwnedPoint}; +use geometry::Point3; /// A 3D orthographic projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy)] // FIXME: Hash -pub struct OrthographicBase> { - matrix: SquareMatrix +pub struct Orthographic3 { + matrix: Matrix4 } -#[cfg(feature = "serde-serialize")] -impl Serialize for OrthographicBase - where N: Scalar, - S: Storage, - SquareMatrix: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.matrix.serialize(serializer) +impl Copy for Orthographic3 { } + +impl Clone for Orthographic3 { + #[inline] + fn clone(&self) -> Self { + Orthographic3::from_matrix_unchecked(self.matrix.clone()) } } -#[cfg(feature = "serde-serialize")] -impl<'de, N, S> Deserialize<'de> for OrthographicBase - where N: Scalar, - S: Storage, - SquareMatrix: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - SquareMatrix::deserialize(deserializer).map(|x| OrthographicBase { matrix: x }) +impl fmt::Debug for Orthographic3 { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.matrix.fmt(f) } } -/// A 3D orthographic projection stored as a static homogeneous 4x4 matrix. -pub type Orthographic3 = OrthographicBase>; - -impl Eq for OrthographicBase - where N: Scalar + Eq, - S: Storage { } - -impl> PartialEq for OrthographicBase { +impl PartialEq for Orthographic3 { #[inline] fn eq(&self, right: &Self) -> bool { self.matrix == right.matrix } } -impl OrthographicBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for Orthographic3 { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.matrix.serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: Real + serde::Deserialize<'a>> serde::Deserialize<'a> for Orthographic3 { + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let matrix = Matrix4::::deserialize(deserializer)?; + + Ok(Orthographic3::from_matrix_unchecked(matrix)) + } +} + +impl Orthographic3 { /// Creates a new orthographic projection matrix. #[inline] pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Self { @@ -72,7 +67,7 @@ impl OrthographicBase assert!(bottom < top, "The top corner must be higher than the bottom corner."); assert!(znear < zfar, "The far plane must be farther than the near plane."); - let matrix = SquareMatrix::::identity(); + let matrix = Matrix4::::identity(); let mut res = Self::from_matrix_unchecked(matrix); res.set_left_and_right(left, right); @@ -87,8 +82,8 @@ impl OrthographicBase /// It is not checked whether or not the given matrix actually represents an orthographic /// projection. #[inline] - pub fn from_matrix_unchecked(matrix: SquareMatrix) -> Self { - OrthographicBase { + pub fn from_matrix_unchecked(matrix: Matrix4) -> Self { + Orthographic3 { matrix: matrix } } @@ -105,24 +100,10 @@ impl OrthographicBase Self::new(-width * half, width * half, -height * half, height * half, znear, zfar) } -} - -impl> OrthographicBase { - /// A reference to the underlying homogeneous transformation matrix. - #[inline] - pub fn as_matrix(&self) -> &SquareMatrix { - &self.matrix - } - - /// Retrieves the underlying homogeneous matrix. - #[inline] - pub fn unwrap(self) -> SquareMatrix { - self.matrix - } /// Retrieves the inverse of the underlying homogeneous matrix. #[inline] - pub fn inverse(&self) -> OwnedSquareMatrix { + pub fn inverse(&self) -> Matrix4 { let mut res = self.to_homogeneous(); let inv_m11 = N::one() / self.matrix[(0, 0)]; @@ -142,10 +123,22 @@ impl> OrthographicBase { /// Computes the corresponding homogeneous matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix { + pub fn to_homogeneous(&self) -> Matrix4 { self.matrix.clone_owned() } + /// A reference to the underlying homogeneous transformation matrix. + #[inline] + pub fn as_matrix(&self) -> &Matrix4 { + &self.matrix + } + + /// Retrieves the underlying homogeneous matrix. + #[inline] + pub fn unwrap(self) -> Matrix4 { + self.matrix + } + /// The smallest x-coordinate of the view cuboid. #[inline] pub fn left(&self) -> N { @@ -185,10 +178,8 @@ impl> OrthographicBase { // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a point. Faster than matrix multiplication. #[inline] - pub fn project_point(&self, p: &PointBase) -> OwnedPoint - where SB: Storage { - - OwnedPoint::::new( + pub fn project_point(&self, p: &Point3) -> Point3 { + Point3::new( self.matrix[(0, 0)] * p[0] + self.matrix[(0, 3)], self.matrix[(1, 1)] * p[1] + self.matrix[(1, 3)], self.matrix[(2, 2)] * p[2] + self.matrix[(2, 3)] @@ -197,10 +188,9 @@ impl> OrthographicBase { /// Un-projects a point. Faster than multiplication by the underlying matrix inverse. #[inline] - pub fn unproject_point(&self, p: &PointBase) -> OwnedPoint - where SB: Storage { + pub fn unproject_point(&self, p: &Point3) -> Point3 { - OwnedPoint::::new( + Point3::new( (p[0] - self.matrix[(0, 3)]) / self.matrix[(0, 0)], (p[1] - self.matrix[(1, 3)]) / self.matrix[(1, 1)], (p[2] - self.matrix[(2, 3)]) / self.matrix[(2, 2)] @@ -210,18 +200,16 @@ impl> OrthographicBase { // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a vector. Faster than matrix multiplication. #[inline] - pub fn project_vector(&self, p: &ColumnVector) -> OwnedColumnVector - where SB: Storage { + pub fn project_vector(&self, p: &Vector) -> Vector3 + where SB: Storage { - OwnedColumnVector::::new( + Vector3::new( self.matrix[(0, 0)] * p[0], self.matrix[(1, 1)] * p[1], self.matrix[(2, 2)] * p[2] ) } -} -impl> OrthographicBase { /// Sets the smallest x-coordinate of the view cuboid. #[inline] pub fn set_left(&mut self, left: N) { @@ -289,10 +277,7 @@ impl> OrthographicBase { } } -impl Rand for OrthographicBase - where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Orthographic3 { fn rand(r: &mut R) -> Self { let left = Rand::rand(r); let right = helper::reject_rand(r, |x: &N| *x > left); @@ -306,10 +291,8 @@ impl Rand for OrthographicBase } #[cfg(feature="arbitrary")] -impl Arbitrary for OrthographicBase - where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Orthographic3 + where Matrix4: Send { fn arbitrary(g: &mut G) -> Self { let left = Arbitrary::arbitrary(g); let right = helper::reject(g, |x: &N| *x > left); diff --git a/src/geometry/perspective.rs b/src/geometry/perspective.rs index ba1eced4..3193549f 100644 --- a/src/geometry/perspective.rs +++ b/src/geometry/perspective.rs @@ -3,77 +3,71 @@ use quickcheck::{Arbitrary, Gen}; use rand::{Rand, Rng}; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; +use std::fmt; use alga::general::Real; -use core::{Scalar, SquareMatrix, OwnedSquareMatrix, ColumnVector, OwnedColumnVector, MatrixArray}; -use core::dimension::{U1, U3, U4}; -use core::storage::{OwnedStorage, Storage, StorageMut}; -use core::allocator::OwnedAllocator; +use core::{Scalar, Matrix4, Vector, Vector3}; +use core::dimension::U3; +use core::storage::Storage; use core::helper; -use geometry::{PointBase, OwnedPoint}; +use geometry::Point3; /// A 3D perspective projection stored as an homogeneous 4x4 matrix. -#[derive(Debug, Clone, Copy)] // FIXME: Hash -pub struct PerspectiveBase> { - matrix: SquareMatrix +pub struct Perspective3 { + matrix: Matrix4 } -#[cfg(feature = "serde-serialize")] -impl Serialize for PerspectiveBase - where N: Scalar, - S: Storage, - SquareMatrix: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.matrix.serialize(serializer) +impl Copy for Perspective3 { } + +impl Clone for Perspective3 { + #[inline] + fn clone(&self) -> Self { + Perspective3::from_matrix_unchecked(self.matrix.clone()) } } -#[cfg(feature = "serde-serialize")] -impl<'de, N, S> Deserialize<'de> for PerspectiveBase - where N: Scalar, - S: Storage, - SquareMatrix: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - SquareMatrix::deserialize(deserializer).map(|x| PerspectiveBase { matrix: x }) +impl fmt::Debug for Perspective3 { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.matrix.fmt(f) } } -/// A 3D perspective projection stored as a static homogeneous 4x4 matrix. -pub type Perspective3 = PerspectiveBase>; - -impl Eq for PerspectiveBase - where N: Scalar + Eq, - S: Storage { } - -impl PartialEq for PerspectiveBase - where N: Scalar, - S: Storage { +impl PartialEq for Perspective3 { #[inline] fn eq(&self, right: &Self) -> bool { self.matrix == right.matrix } } -impl PerspectiveBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for Perspective3 { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.matrix.serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: Real + serde::Deserialize<'a>> serde::Deserialize<'a> for Perspective3 { + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let matrix = Matrix4::::deserialize(deserializer)?; + + Ok(Perspective3::from_matrix_unchecked(matrix)) + } +} + +impl Perspective3 { /// Creates a new perspective matrix from the aspect ratio, y field of view, and near/far planes. pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> Self { assert!(!relative_eq!(zfar - znear, N::zero()), "The near-plane and far-plane must not be superimposed."); assert!(!relative_eq!(aspect, N::zero()), "The apsect ratio must not be zero."); - let matrix = SquareMatrix::::identity(); - let mut res = PerspectiveBase::from_matrix_unchecked(matrix); + let matrix = Matrix4::identity(); + let mut res = Perspective3::from_matrix_unchecked(matrix); res.set_fovy(fovy); res.set_aspect(aspect); @@ -91,32 +85,15 @@ impl PerspectiveBase /// It is not checked whether or not the given matrix actually represents an orthographic /// projection. #[inline] - pub fn from_matrix_unchecked(matrix: SquareMatrix) -> Self { - PerspectiveBase { + pub fn from_matrix_unchecked(matrix: Matrix4) -> Self { + Perspective3 { matrix: matrix } } -} - -impl PerspectiveBase - where N: Real, - S: Storage { - - /// A reference to the underlying homogeneous transformation matrix. - #[inline] - pub fn as_matrix(&self) -> &SquareMatrix { - &self.matrix - } - - /// Retrieves the underlying homogeneous matrix. - #[inline] - pub fn unwrap(self) -> SquareMatrix { - self.matrix - } /// Retrieves the inverse of the underlying homogeneous matrix. #[inline] - pub fn inverse(&self) -> OwnedSquareMatrix { + pub fn inverse(&self) -> Matrix4 { let mut res = self.to_homogeneous(); res[(0, 0)] = N::one() / self.matrix[(0, 0)]; @@ -135,10 +112,22 @@ impl PerspectiveBase /// Computes the corresponding homogeneous matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix { + pub fn to_homogeneous(&self) -> Matrix4 { self.matrix.clone_owned() } + /// A reference to the underlying homogeneous transformation matrix. + #[inline] + pub fn as_matrix(&self) -> &Matrix4 { + &self.matrix + } + + /// Retrieves the underlying homogeneous matrix. + #[inline] + pub fn unwrap(self) -> Matrix4 { + self.matrix + } + /// Gets the `width / height` aspect ratio of the view frustrum. #[inline] pub fn aspect(&self) -> N { @@ -174,11 +163,9 @@ impl PerspectiveBase // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a point. Faster than matrix multiplication. #[inline] - pub fn project_point(&self, p: &PointBase) -> OwnedPoint - where SB: Storage { - + pub fn project_point(&self, p: &Point3) -> Point3 { let inverse_denom = -N::one() / p[2]; - OwnedPoint::::new( + Point3::new( self.matrix[(0, 0)] * p[0] * inverse_denom, self.matrix[(1, 1)] * p[1] * inverse_denom, (self.matrix[(2, 2)] * p[2] + self.matrix[(2, 3)]) * inverse_denom @@ -187,12 +174,10 @@ impl PerspectiveBase /// Un-projects a point. Faster than multiplication by the matrix inverse. #[inline] - pub fn unproject_point(&self, p: &PointBase) -> OwnedPoint - where SB: Storage { - + pub fn unproject_point(&self, p: &Point3) -> Point3 { let inverse_denom = self.matrix[(2, 3)] / (p[2] + self.matrix[(2, 2)]); - OwnedPoint::::new( + Point3::new( p[0] * inverse_denom / self.matrix[(0, 0)], p[1] * inverse_denom / self.matrix[(1, 1)], -inverse_denom @@ -202,22 +187,17 @@ impl PerspectiveBase // FIXME: when we get specialization, specialize the Mul impl instead. /// Projects a vector. Faster than matrix multiplication. #[inline] - pub fn project_vector(&self, p: &ColumnVector) -> OwnedColumnVector - where SB: Storage { + pub fn project_vector(&self, p: &Vector) -> Vector3 + where SB: Storage { let inverse_denom = -N::one() / p[2]; - OwnedColumnVector::::new( + Vector3::new( self.matrix[(0, 0)] * p[0] * inverse_denom, self.matrix[(1, 1)] * p[1] * inverse_denom, self.matrix[(2, 2)] ) } -} - -impl PerspectiveBase - where N: Real, - S: StorageMut { /// Updates this perspective matrix with a new `width / height` aspect ratio of the view /// frustrum. #[inline] @@ -256,10 +236,7 @@ impl PerspectiveBase } } -impl Rand for PerspectiveBase - where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Perspective3 { fn rand(r: &mut R) -> Self { let znear = Rand::rand(r); let zfar = helper::reject_rand(r, |&x: &N| !(x - znear).is_zero()); @@ -270,10 +247,7 @@ impl Rand for PerspectiveBase } #[cfg(feature="arbitrary")] -impl Arbitrary for PerspectiveBase - where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Perspective3 { fn arbitrary(g: &mut G) -> Self { let znear = Arbitrary::arbitrary(g); let zfar = helper::reject(g, |&x: &N| !(x - znear).is_zero()); diff --git a/src/geometry/point.rs b/src/geometry/point.rs index 8587e528..ec18976c 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -1,89 +1,81 @@ use num::One; +use std::hash; use std::fmt; use std::cmp::Ordering; use approx::ApproxEq; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; -use core::{Scalar, ColumnVector, OwnedColumnVector}; +use core::{DefaultAllocator, Scalar, VectorN}; use core::iter::{MatrixIter, MatrixIterMut}; use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::{Storage, StorageMut, MulStorage}; -use core::allocator::{Allocator, SameShapeR}; - -// XXX Bad name: we can't even add points… -/// The type of the result of the sum of a point with a vector. -pub type PointSum = - PointBase, - <>::Alloc as Allocator, U1>>::Buffer>; - -/// The type of the result of the multiplication of a point by a matrix. -pub type PointMul = PointBase>; - -/// A point with an owned storage. -pub type OwnedPoint = PointBase>::Buffer>; +use core::allocator::Allocator; /// A point in a n-dimensional euclidean space. #[repr(C)] -#[derive(Hash, Debug)] -pub struct PointBase> { +#[derive(Debug)] +pub struct Point + where DefaultAllocator: Allocator { /// The coordinates of this point, i.e., the shift from the origin. - pub coords: ColumnVector + pub coords: VectorN } -impl Copy for PointBase - where N: Scalar, - D: DimName, - S: Storage + Copy { } +impl hash::Hash for Point + where DefaultAllocator: Allocator, + >::Buffer: hash::Hash { + fn hash(&self, state: &mut H) { + self.coords.hash(state) + } +} -impl Clone for PointBase - where N: Scalar, - D: DimName, - S: Storage + Clone { +impl Copy for Point + where DefaultAllocator: Allocator, + >::Buffer: Copy { } + +impl Clone for Point + where DefaultAllocator: Allocator, + >::Buffer: Clone { #[inline] fn clone(&self) -> Self { - PointBase::from_coordinates(self.coords.clone()) + Point::from_coordinates(self.coords.clone()) } } #[cfg(feature = "serde-serialize")] -impl Serialize for PointBase - where N: Scalar, - D: DimName, - S: Storage, - ColumnVector: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.coords.serialize(serializer) - } +impl serde::Serialize for Point +where DefaultAllocator: Allocator, + >::Buffer: serde::Serialize { + + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.coords.serialize(serializer) + } } #[cfg(feature = "serde-serialize")] -impl<'de, N, D, S> Deserialize<'de> for PointBase - where N: Scalar, - D: DimName, - S: Storage, - ColumnVector: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - ColumnVector::deserialize(deserializer).map(|x| PointBase { coords: x }) - } +impl<'a, N: Scalar, D: DimName> serde::Deserialize<'a> for Point +where DefaultAllocator: Allocator, + >::Buffer: serde::Deserialize<'a> { + + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let coords = VectorN::::deserialize(deserializer)?; + + Ok(Point::from_coordinates(coords)) + } } + #[cfg(feature = "abomonation-serialize")] -impl Abomonation for PointBase +impl Abomonation for PointBase where N: Scalar, D: DimName, - S: Storage, - ColumnVector: Abomonation + ColumnVector: Abomonation, + DefaultAllocator: Allocator { unsafe fn entomb(&self, writer: &mut Vec) { self.coords.entomb(writer) @@ -98,27 +90,38 @@ impl Abomonation for PointBase } } -impl> PointBase { - /// Creates a new point with the given coordinates. - #[inline] - pub fn from_coordinates(coords: ColumnVector) -> PointBase { - PointBase { - coords: coords - } - } -} - -impl> PointBase { - /// Moves this point into one that owns its data. - #[inline] - pub fn into_owned(self) -> OwnedPoint { - PointBase::from_coordinates(self.coords.into_owned()) - } +impl Point + where DefaultAllocator: Allocator { /// Clones this point into one that owns its data. #[inline] - pub fn clone_owned(&self) -> OwnedPoint { - PointBase::from_coordinates(self.coords.clone_owned()) + pub fn clone(&self) -> Point { + Point::from_coordinates(self.coords.clone_owned()) + } + + /// Converts this point into a vector in homogeneous coordinates, i.e., appends a `1` at the + /// end of it. + #[inline] + pub fn to_homogeneous(&self) -> VectorN> + where N: One, + D: DimNameAdd, + DefaultAllocator: Allocator> { + + let mut res = unsafe { + VectorN::<_, DimNameSum>::new_uninitialized() + }; + res.fixed_slice_mut::(0, 0).copy_from(&self.coords); + res[(D::dim(), 0)] = N::one(); + + res + } + + /// Creates a new point with the given coordinates. + #[inline] + pub fn from_coordinates(coords: VectorN) -> Point { + Point { + coords: coords + } } /// The dimension of this point. @@ -136,44 +139,26 @@ impl> PointBase { /// Iterates through this point coordinates. #[inline] - pub fn iter(&self) -> MatrixIter { + pub fn iter(&self) -> MatrixIter>::Buffer> { self.coords.iter() } /// Gets a reference to i-th element of this point without bound-checking. #[inline] pub unsafe fn get_unchecked(&self, i: usize) -> &N { - self.coords.get_unchecked(i, 0) + self.coords.vget_unchecked(i) } - - /// Converts this point into a vector in homogeneous coordinates, i.e., appends a `1` at the - /// end of it. - #[inline] - pub fn to_homogeneous(&self) -> OwnedColumnVector, S::Alloc> - where N: One, - D: DimNameAdd, - S::Alloc: Allocator, U1> { - - let mut res = unsafe { OwnedColumnVector::::new_uninitialized() }; - res.fixed_slice_mut::(0, 0).copy_from(&self.coords); - res[(D::dim(), 0)] = N::one(); - - res - } -} - -impl> PointBase { /// Mutably iterates through this point coordinates. #[inline] - pub fn iter_mut(&mut self) -> MatrixIterMut { + pub fn iter_mut(&mut self) -> MatrixIterMut>::Buffer> { self.coords.iter_mut() } /// Gets a mutable reference to i-th element of this point without bound-checking. #[inline] pub unsafe fn get_unchecked_mut(&mut self, i: usize) -> &mut N { - self.coords.get_unchecked_mut(i, 0) + self.coords.vget_unchecked_mut(i) } /// Swaps two entries without bound-checking. @@ -183,9 +168,8 @@ impl> PointBase { } } -impl ApproxEq for PointBase - where N: Scalar + ApproxEq, - S: Storage, +impl ApproxEq for Point + where DefaultAllocator: Allocator, N::Epsilon: Copy { type Epsilon = N::Epsilon; @@ -215,22 +199,19 @@ impl ApproxEq for PointBase } } -impl Eq for PointBase - where N: Scalar + Eq, - S: Storage { } +impl Eq for Point + where DefaultAllocator: Allocator { } -impl PartialEq for PointBase - where N: Scalar, - S: Storage { +impl PartialEq for Point + where DefaultAllocator: Allocator { #[inline] fn eq(&self, right: &Self) -> bool { self.coords == right.coords } } -impl PartialOrd for PointBase - where N: Scalar + PartialOrd, - S: Storage { +impl PartialOrd for Point + where DefaultAllocator: Allocator { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.coords.partial_cmp(&other.coords) @@ -262,9 +243,8 @@ impl PartialOrd for PointBase * Display * */ -impl fmt::Display for PointBase - where N: Scalar + fmt::Display, - S: Storage { +impl fmt::Display for Point + where DefaultAllocator: Allocator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "{{")); diff --git a/src/geometry/point_alga.rs b/src/geometry/point_alga.rs index d237af64..7d5962ab 100644 --- a/src/geometry/point_alga.rs +++ b/src/geometry/point_alga.rs @@ -1,26 +1,22 @@ use alga::general::{Field, Real, MeetSemilattice, JoinSemilattice, Lattice}; use alga::linear::{AffineSpace, EuclideanSpace}; -use core::{ColumnVector, Scalar}; -use core::dimension::{DimName, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::{DefaultAllocator, Scalar, VectorN}; +use core::dimension::DimName; +use core::allocator::Allocator; -use geometry::PointBase; +use geometry::Point; -impl AffineSpace for PointBase +impl AffineSpace for Point where N: Scalar + Field, - S: OwnedStorage, - S::Alloc: OwnedAllocator { - type Translation = ColumnVector; + DefaultAllocator: Allocator { + type Translation = VectorN; } -impl EuclideanSpace for PointBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { - type Coordinates = ColumnVector; +impl EuclideanSpace for Point + where DefaultAllocator: Allocator { + type Coordinates = VectorN; type Real = N; #[inline] @@ -49,35 +45,32 @@ impl EuclideanSpace for PointBase * Ordering * */ -impl MeetSemilattice for PointBase +impl MeetSemilattice for Point where N: Scalar + MeetSemilattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn meet(&self, other: &Self) -> Self { - PointBase::from_coordinates(self.coords.meet(&other.coords)) + Point::from_coordinates(self.coords.meet(&other.coords)) } } -impl JoinSemilattice for PointBase +impl JoinSemilattice for Point where N: Scalar + JoinSemilattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn join(&self, other: &Self) -> Self { - PointBase::from_coordinates(self.coords.join(&other.coords)) + Point::from_coordinates(self.coords.join(&other.coords)) } } -impl Lattice for PointBase +impl Lattice for Point where N: Scalar + Lattice, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn meet_join(&self, other: &Self) -> (Self, Self) { let (meet, join) = self.coords.meet_join(&other.coords); - (PointBase::from_coordinates(meet), PointBase::from_coordinates(join)) + (Point::from_coordinates(meet), Point::from_coordinates(join)) } } diff --git a/src/geometry/point_alias.rs b/src/geometry/point_alias.rs index 20cce1bc..c779e57d 100644 --- a/src/geometry/point_alias.rs +++ b/src/geometry/point_alias.rs @@ -1,10 +1,6 @@ -use core::MatrixArray; use core::dimension::{U1, U2, U3, U4, U5, U6}; -use geometry::PointBase; - -/// A statically sized D-dimensional column point. -pub type Point = PointBase>; +use geometry::Point; /// A statically sized 1-dimensional column point. pub type Point1 = Point; diff --git a/src/geometry/point_construction.rs b/src/geometry/point_construction.rs index 5a3d6e93..1f1fffa7 100644 --- a/src/geometry/point_construction.rs +++ b/src/geometry/point_construction.rs @@ -5,28 +5,25 @@ use rand::{Rand, Rng}; use num::{Zero, One, Bounded}; use alga::general::ClosedDiv; -use core::{Scalar, ColumnVector}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; +use core::{DefaultAllocator, Scalar, VectorN}; +use core::allocator::Allocator; use core::dimension::{DimName, DimNameAdd, DimNameSum, U1, U2, U3, U4, U5, U6}; -use geometry::PointBase; +use geometry::Point; -impl PointBase - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Point + where DefaultAllocator: Allocator { /// Creates a new point with uninitialized coordinates. #[inline] pub unsafe fn new_uninitialized() -> Self { - Self::from_coordinates(ColumnVector::<_, D, _>::new_uninitialized()) + Self::from_coordinates(VectorN::new_uninitialized()) } /// Creates a new point with all coordinates equal to zero. #[inline] pub fn origin() -> Self where N: Zero { - Self::from_coordinates(ColumnVector::<_, D, _>::from_element(N::zero())) + Self::from_coordinates(VectorN::from_element(N::zero())) } /// Creates a new point from its homogeneous vector representation. @@ -34,11 +31,10 @@ impl PointBase /// In practice, this builds a D-dimensional points with the same first D component as `v` /// divided by the last component of `v`. Returns `None` if this divisor is zero. #[inline] - pub fn from_homogeneous(v: ColumnVector, SB>) -> Option - where N: Scalar + Zero + One + ClosedDiv, - D: DimNameAdd, - SB: Storage, U1, Alloc = S::Alloc>, - S::Alloc: Allocator, U1> { + pub fn from_homogeneous(v: VectorN>) -> Option + where N: Scalar + Zero + One + ClosedDiv, + D: DimNameAdd, + DefaultAllocator: Allocator> { if !v[D::dim()].is_zero() { let coords = v.fixed_slice::(0, 0) / v[D::dim()]; @@ -56,39 +52,34 @@ impl PointBase * Traits that buid points. * */ -impl Bounded for PointBase - where N: Scalar + Bounded, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Bounded for Point + where DefaultAllocator: Allocator { #[inline] fn max_value() -> Self { - Self::from_coordinates(ColumnVector::max_value()) + Self::from_coordinates(VectorN::max_value()) } #[inline] fn min_value() -> Self { - Self::from_coordinates(ColumnVector::min_value()) + Self::from_coordinates(VectorN::min_value()) } } -impl Rand for PointBase - where N: Scalar + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Point + where DefaultAllocator: Allocator { #[inline] fn rand(rng: &mut G) -> Self { - PointBase::from_coordinates(rng.gen()) + Point::from_coordinates(rng.gen()) } } #[cfg(feature="arbitrary")] -impl Arbitrary for PointBase - where N: Scalar + Arbitrary + Send, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Point + where DefaultAllocator: Allocator, + >::Buffer: Send { #[inline] fn arbitrary(g: &mut G) -> Self { - PointBase::from_coordinates(ColumnVector::arbitrary(g)) + Point::from_coordinates(VectorN::arbitrary(g)) } } @@ -99,13 +90,11 @@ impl Arbitrary for PointBase */ macro_rules! componentwise_constructors_impl( ($($D: ty, $($args: ident:$irow: expr),*);* $(;)*) => {$( - impl PointBase - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl Point + where DefaultAllocator: Allocator { /// Initializes this matrix from its components. #[inline] - pub fn new($($args: N),*) -> PointBase { + pub fn new($($args: N),*) -> Point { unsafe { let mut res = Self::new_uninitialized(); $( *res.get_unchecked_mut($irow) = $args; )* diff --git a/src/geometry/point_conversion.rs b/src/geometry/point_conversion.rs index 2b463c72..f22a9689 100644 --- a/src/geometry/point_conversion.rs +++ b/src/geometry/point_conversion.rs @@ -1,72 +1,67 @@ use num::{One, Zero}; use alga::general::{SubsetOf, SupersetOf, ClosedDiv}; -use core::{Scalar, Matrix, ColumnVector, OwnedColumnVector}; +use core::{DefaultAllocator, Scalar, Matrix, VectorN}; use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; +use core::allocator::Allocator; -use geometry::{PointBase, OwnedPoint}; +use geometry::Point; /* * This file provides the following conversions: * ============================================= * - * PointBase -> PointBase - * PointBase -> ColumnVector (homogeneous) + * Point -> Point + * Point -> Vector (homogeneous) */ -impl SubsetOf> for PointBase +impl SubsetOf> for Point where D: DimName, N1: Scalar, N2: Scalar + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SB::Alloc: OwnedAllocator, - SA::Alloc: OwnedAllocator { + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> PointBase { - PointBase::from_coordinates(self.coords.to_superset()) + fn to_superset(&self) -> Point { + Point::from_coordinates(self.coords.to_superset()) } #[inline] - fn is_in_subset(m: &PointBase) -> bool { + fn is_in_subset(m: &Point) -> bool { // FIXME: is there a way to reuse the `.is_in_subset` from the matrix implementation of // SubsetOf? m.iter().all(|e| e.is_in_subset()) } #[inline] - unsafe fn from_superset_unchecked(m: &PointBase) -> Self { - PointBase::from_coordinates(Matrix::from_superset_unchecked(&m.coords)) + unsafe fn from_superset_unchecked(m: &Point) -> Self { + Point::from_coordinates(Matrix::from_superset_unchecked(&m.coords)) } } -impl SubsetOf, SB>> for PointBase +impl SubsetOf>> for Point where D: DimNameAdd, N1: Scalar, N2: Scalar + Zero + One + ClosedDiv + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, U1>, - SA::Alloc: OwnedAllocator + - Allocator, U1>, - SB::Alloc: OwnedAllocator, U1, SB> + - Allocator { + DefaultAllocator: Allocator + + Allocator> + + Allocator> + + Allocator { #[inline] - fn to_superset(&self) -> ColumnVector, SB> { - let p: OwnedPoint = self.to_superset(); + fn to_superset(&self) -> VectorN> { + let p: Point = self.to_superset(); p.to_homogeneous() } #[inline] - fn is_in_subset(v: &ColumnVector, SB>) -> bool { - ::is_convertible::<_, OwnedColumnVector, SA::Alloc>>(v) && + fn is_in_subset(v: &VectorN>) -> bool { + ::is_convertible::<_, VectorN>>(v) && !v[D::dim()].is_zero() } #[inline] - unsafe fn from_superset_unchecked(v: &ColumnVector, SB>) -> Self { + unsafe fn from_superset_unchecked(v: &VectorN>) -> Self { let coords = v.fixed_slice::(0, 0) / v[D::dim()]; Self::from_coordinates(::convert_unchecked(coords)) } diff --git a/src/geometry/point_coordinates.rs b/src/geometry/point_coordinates.rs index 5eaf15cc..3e22a578 100644 --- a/src/geometry/point_coordinates.rs +++ b/src/geometry/point_coordinates.rs @@ -1,25 +1,23 @@ use std::mem; use std::ops::{Deref, DerefMut}; -use core::Scalar; +use core::{DefaultAllocator, Scalar}; use core::dimension::{U1, U2, U3, U4, U5, U6}; use core::coordinates::{X, XY, XYZ, XYZW, XYZWA, XYZWAB}; -use core::allocator::OwnedAllocator; -use core::storage::OwnedStorage; +use core::allocator::Allocator; -use geometry::PointBase; +use geometry::Point; /* * - * Give coordinates to PointBase{1 .. 6} + * Give coordinates to Point{1 .. 6} * */ macro_rules! deref_impl( ($D: ty, $Target: ident $(, $comps: ident)*) => { - impl Deref for PointBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl Deref for Point + where DefaultAllocator: Allocator { type Target = $Target; #[inline] @@ -28,9 +26,8 @@ macro_rules! deref_impl( } } - impl DerefMut for PointBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl DerefMut for Point + where DefaultAllocator: Allocator { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { unsafe { mem::transmute(self) } diff --git a/src/geometry/point_ops.rs b/src/geometry/point_ops.rs index 4a9deb7f..fdabe34e 100644 --- a/src/geometry/point_ops.rs +++ b/src/geometry/point_ops.rs @@ -1,15 +1,15 @@ use std::ops::{Neg, Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Index, IndexMut}; -use num::Zero; +use num::{Zero, One}; use alga::general::{ClosedNeg, ClosedAdd, ClosedSub, ClosedMul, ClosedDiv}; -use core::{Scalar, ColumnVector, Matrix, ColumnVectorSum}; +use core::{DefaultAllocator, Scalar, Vector, Matrix, VectorSum}; use core::dimension::{Dim, DimName, U1}; use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns, AreMultipliable}; -use core::storage::{Storage, StorageMut}; +use core::storage::Storage; use core::allocator::{SameShapeAllocator, Allocator}; -use geometry::{PointBase, OwnedPoint, PointMul}; +use geometry::Point; /* @@ -17,9 +17,8 @@ use geometry::{PointBase, OwnedPoint, PointMul}; * Indexing. * */ -impl Index for PointBase - where N: Scalar, - S: Storage { +impl Index for Point + where DefaultAllocator: Allocator { type Output = N; #[inline] @@ -28,9 +27,8 @@ impl Index for PointBase } } -impl IndexMut for PointBase - where N: Scalar, - S: StorageMut { +impl IndexMut for Point + where DefaultAllocator: Allocator { #[inline] fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.coords[i] @@ -38,28 +36,27 @@ impl IndexMut for PointBase } /* + * * Neg. * */ -impl Neg for PointBase - where N: Scalar + ClosedNeg, - S: Storage { - type Output = OwnedPoint; +impl Neg for Point + where DefaultAllocator: Allocator { + type Output = Point; #[inline] fn neg(self) -> Self::Output { - PointBase::from_coordinates(-self.coords) + Point::from_coordinates(-self.coords) } } -impl<'a, N, D: DimName, S> Neg for &'a PointBase - where N: Scalar + ClosedNeg, - S: Storage { - type Output = OwnedPoint; +impl<'a, N: Scalar + ClosedNeg, D: DimName> Neg for &'a Point + where DefaultAllocator: Allocator { + type Output = Point; #[inline] fn neg(self) -> Self::Output { - PointBase::from_coordinates(-&self.coords) + Point::from_coordinates(-&self.coords) } } @@ -69,94 +66,94 @@ impl<'a, N, D: DimName, S> Neg for &'a PointBase * */ -// PointBase - PointBase +// Point - Point add_sub_impl!(Sub, sub, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: &'a PointBase, right: &'b PointBase, Output = ColumnVectorSum; + self: &'a Point, right: &'b Point, Output = VectorSum; &self.coords - &right.coords; 'a, 'b); add_sub_impl!(Sub, sub, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: &'a PointBase, right: PointBase, Output = ColumnVectorSum; + self: &'a Point, right: Point, Output = VectorSum; &self.coords - right.coords; 'a); add_sub_impl!(Sub, sub, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: PointBase, right: &'b PointBase, Output = ColumnVectorSum; + self: Point, right: &'b Point, Output = VectorSum; self.coords - &right.coords; 'b); add_sub_impl!(Sub, sub, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: PointBase, right: PointBase, Output = ColumnVectorSum; + self: Point, right: Point, Output = VectorSum; self.coords - right.coords; ); -// PointBase - Vector +// Point - Vector add_sub_impl!(Sub, sub, ClosedSub; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: &'a PointBase, right: &'b ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: &'a Point, right: &'b Vector, Output = Point; Self::Output::from_coordinates(&self.coords - right); 'a, 'b); add_sub_impl!(Sub, sub, ClosedSub; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: &'a PointBase, right: ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: &'a Point, right: Vector, Output = Point; Self::Output::from_coordinates(&self.coords - &right); 'a); // FIXME: should not be a ref to `right`. add_sub_impl!(Sub, sub, ClosedSub; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: PointBase, right: &'b ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: Point, right: &'b Vector, Output = Point; Self::Output::from_coordinates(self.coords - right); 'b); add_sub_impl!(Sub, sub, ClosedSub; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: PointBase, right: ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: Point, right: Vector, Output = Point; Self::Output::from_coordinates(self.coords - right); ); -// PointBase + Vector +// Point + Vector add_sub_impl!(Add, add, ClosedAdd; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: &'a PointBase, right: &'b ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: &'a Point, right: &'b Vector, Output = Point; Self::Output::from_coordinates(&self.coords + right); 'a, 'b); add_sub_impl!(Add, add, ClosedAdd; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: &'a PointBase, right: ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: &'a Point, right: Vector, Output = Point; Self::Output::from_coordinates(&self.coords + &right); 'a); // FIXME: should not be a ref to `right`. add_sub_impl!(Add, add, ClosedAdd; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: PointBase, right: &'b ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: Point, right: &'b Vector, Output = Point; Self::Output::from_coordinates(self.coords + right); 'b); add_sub_impl!(Add, add, ClosedAdd; - (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim; - self: PointBase, right: ColumnVector, Output = OwnedPoint; + (D1, U1), (D2, U1) -> (D1) for D1: DimName, D2: Dim, SB: Storage; + self: Point, right: Vector, Output = Point; Self::Output::from_coordinates(self.coords + right); ); // XXX: replace by the shared macro: add_sub_assign_impl macro_rules! op_assign_impl( ($($TraitAssign: ident, $method_assign: ident, $bound: ident);* $(;)*) => {$( - impl<'b, N, D1: DimName, D2: Dim, SA, SB> $TraitAssign<&'b ColumnVector> for PointBase - where N: Scalar + $bound, - SA: StorageMut, - SB: Storage, + impl<'b, N, D1: DimName, D2: Dim, SB> $TraitAssign<&'b Vector> for Point + where N: Scalar + $bound, + SB: Storage, + DefaultAllocator: Allocator, ShapeConstraint: SameNumberOfRows { #[inline] - fn $method_assign(&mut self, right: &'b ColumnVector) { + fn $method_assign(&mut self, right: &'b Vector) { self.coords.$method_assign(right) } } - impl $TraitAssign> for PointBase - where N: Scalar + $bound, - SA: StorageMut, - SB: Storage, + impl $TraitAssign> for Point + where N: Scalar + $bound, + SB: Storage, + DefaultAllocator: Allocator, ShapeConstraint: SameNumberOfRows { #[inline] - fn $method_assign(&mut self, right: ColumnVector) { + fn $method_assign(&mut self, right: Vector) { self.coords.$method_assign(right) } } @@ -171,56 +168,52 @@ op_assign_impl!( /* * - * Matrix × PointBase + * Matrix × Point * */ md_impl_all!( Mul, mul; - (R1, C1), (D2, U1) for R1: DimName, C1: Dim, D2: DimName - where SA::Alloc: Allocator + (R1, C1), (D2, U1) for R1: DimName, C1: Dim, D2: DimName, SA: Storage where ShapeConstraint: AreMultipliable; - self: Matrix, right: PointBase, Output = PointMul; - [val val] => PointBase::from_coordinates(self * right.coords); - [ref val] => PointBase::from_coordinates(self * right.coords); - [val ref] => PointBase::from_coordinates(self * &right.coords); - [ref ref] => PointBase::from_coordinates(self * &right.coords); + self: Matrix, right: Point, Output = Point; + [val val] => Point::from_coordinates(self * right.coords); + [ref val] => Point::from_coordinates(self * right.coords); + [val ref] => Point::from_coordinates(self * &right.coords); + [ref ref] => Point::from_coordinates(self * &right.coords); ); /* * - * PointBase ×/÷ Scalar + * Point ×/÷ Scalar * */ macro_rules! componentwise_scalarop_impl( ($Trait: ident, $method: ident, $bound: ident; $TraitAssign: ident, $method_assign: ident) => { - impl $Trait for PointBase - where N: Scalar + $bound, - S: Storage { - type Output = OwnedPoint; + impl $Trait for Point + where DefaultAllocator: Allocator { + type Output = Point; #[inline] fn $method(self, right: N) -> Self::Output { - PointBase::from_coordinates(self.coords.$method(right)) + Point::from_coordinates(self.coords.$method(right)) } } - impl<'a, N, D: DimName, S> $Trait for &'a PointBase - where N: Scalar + $bound, - S: Storage { - type Output = OwnedPoint; + impl<'a, N: Scalar + $bound, D: DimName> $Trait for &'a Point + where DefaultAllocator: Allocator { + type Output = Point; #[inline] fn $method(self, right: N) -> Self::Output { - PointBase::from_coordinates((&self.coords).$method(right)) + Point::from_coordinates((&self.coords).$method(right)) } } - impl $TraitAssign for PointBase - where N: Scalar + $bound, - S: StorageMut { + impl $TraitAssign for Point + where DefaultAllocator: Allocator { #[inline] fn $method_assign(&mut self, right: N) { self.coords.$method_assign(right) @@ -234,23 +227,23 @@ componentwise_scalarop_impl!(Div, div, ClosedDiv; DivAssign, div_assign); macro_rules! left_scalar_mul_impl( ($($T: ty),* $(,)*) => {$( - impl Mul> for $T - where S: Storage<$T, D, U1> { - type Output = OwnedPoint<$T, D, S::Alloc>; + impl Mul> for $T + where DefaultAllocator: Allocator<$T, D> { + type Output = Point<$T, D>; #[inline] - fn mul(self, right: PointBase<$T, D, S>) -> Self::Output { - PointBase::from_coordinates(self * right.coords) + fn mul(self, right: Point<$T, D>) -> Self::Output { + Point::from_coordinates(self * right.coords) } } - impl<'b, D: DimName, S> Mul<&'b PointBase<$T, D, S>> for $T - where S: Storage<$T, D, U1> { - type Output = OwnedPoint<$T, D, S::Alloc>; + impl<'b, D: DimName> Mul<&'b Point<$T, D>> for $T + where DefaultAllocator: Allocator<$T, D> { + type Output = Point<$T, D>; #[inline] - fn mul(self, right: &'b PointBase<$T, D, S>) -> Self::Output { - PointBase::from_coordinates(self * &right.coords) + fn mul(self, right: &'b Point<$T, D>) -> Self::Output { + Point::from_coordinates(self * &right.coords) } } )*} diff --git a/src/geometry/quaternion.rs b/src/geometry/quaternion.rs index 97482ab6..8dc3a99b 100644 --- a/src/geometry/quaternion.rs +++ b/src/geometry/quaternion.rs @@ -1,69 +1,36 @@ use std::fmt; +use std::hash; use num::Zero; use approx::ApproxEq; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; +#[cfg(feature = "serde-serialize")] +use core::storage::Owned; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; use alga::general::Real; -use core::{Unit, ColumnVector, OwnedColumnVector, MatrixSlice, MatrixSliceMut, SquareMatrix, - OwnedSquareMatrix}; -use core::storage::{Storage, StorageMut}; -use core::allocator::Allocator; +use core::{Unit, Vector3, Vector4, MatrixSlice, MatrixSliceMut, SquareMatrix, MatrixN}; use core::dimension::{U1, U3, U4}; +use core::storage::{RStride, CStride}; -use geometry::{RotationBase, OwnedRotation}; +use geometry::Rotation; -/// A quaternion with an owned storage allocated by `A`. -pub type OwnedQuaternionBase = QuaternionBase>::Buffer>; - -/// A unit quaternion with an owned storage allocated by `A`. -pub type OwnedUnitQuaternionBase = UnitQuaternionBase>::Buffer>; - -/// A quaternion. See the type alias `UnitQuaternionBase = Unit` for a quaternion +/// A quaternion. See the type alias `UnitQuaternion = Unit` for a quaternion /// that may be used as a rotation. #[repr(C)] -#[derive(Hash, Debug, Copy, Clone)] -pub struct QuaternionBase> { +#[derive(Debug)] +pub struct Quaternion { /// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order. - pub coords: ColumnVector -} - -#[cfg(feature = "serde-serialize")] -impl Serialize for QuaternionBase - where N: Real, - S: Storage, - ColumnVector: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.coords.serialize(serializer) - } -} - -#[cfg(feature = "serde-serialize")] -impl<'de, N, S> Deserialize<'de> for QuaternionBase - where N: Real, - S: Storage, - ColumnVector: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - ColumnVector::deserialize(deserializer).map(|x| QuaternionBase { coords: x }) - } + pub coords: Vector4 } #[cfg(feature = "abomonation-serialize")] -impl Abomonation for QuaternionBase - where N: Real, - S: Storage, - ColumnVector: Abomonation +impl Abomonation for QuaternionBase + where Vector4: Abomonation { unsafe fn entomb(&self, writer: &mut Vec) { self.coords.entomb(writer) @@ -78,14 +45,9 @@ impl Abomonation for QuaternionBase } } -impl Eq for QuaternionBase - where N: Real + Eq, - S: Storage { -} +impl Eq for Quaternion { } -impl PartialEq for QuaternionBase - where N: Real, - S: Storage { +impl PartialEq for Quaternion { fn eq(&self, rhs: &Self) -> bool { self.coords == rhs.coords || // Account for the double-covering of S², i.e. q = -q @@ -93,24 +55,93 @@ impl PartialEq for QuaternionBase } } -impl QuaternionBase - where N: Real, - S: Storage { - /// Moves this quaternion into one that owns its data. +impl hash::Hash for Quaternion { + fn hash(&self, state: &mut H) { + self.coords.hash(state) + } +} + +impl Copy for Quaternion { } + +impl Clone for Quaternion { #[inline] - pub fn into_owned(self) -> OwnedQuaternionBase { - QuaternionBase::from_vector(self.coords.into_owned()) + fn clone(&self) -> Self { + Quaternion::from_vector(self.coords.clone()) + } +} + +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for Quaternion +where Owned: serde::Serialize { + + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.coords.serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: Real> serde::Deserialize<'a> for Quaternion +where Owned: serde::Deserialize<'a> { + + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let coords = Vector4::::deserialize(deserializer)?; + + Ok(Quaternion::from_vector(coords)) + } +} + +impl Quaternion { + /// Moves this unit quaternion into one that owns its data. + #[inline] + #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + pub fn into_owned(self) -> Quaternion { + self } - /// Clones this quaternion into one that owns its data. + /// Clones this unit quaternion into one that owns its data. #[inline] - pub fn clone_owned(&self) -> OwnedQuaternionBase { - QuaternionBase::from_vector(self.coords.clone_owned()) + #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + pub fn clone_owned(&self) -> Quaternion { + Quaternion::from_vector(self.coords.clone_owned()) + } + + /// Normalizes this quaternion. + #[inline] + pub fn normalize(&self) -> Quaternion { + Quaternion::from_vector(self.coords.normalize()) + } + + /// Compute the conjugate of this quaternion. + #[inline] + pub fn conjugate(&self) -> Quaternion { + let v = Vector4::new(-self.coords[0], -self.coords[1], -self.coords[2], self.coords[3]); + Quaternion::from_vector(v) + } + + /// Inverts this quaternion if it is not zero. + #[inline] + pub fn try_inverse(&self) -> Option> { + let mut res = Quaternion::from_vector(self.coords.clone_owned()); + + if res.try_inverse_mut() { + Some(res) + } + else { + None + } + } + + /// Linear interpolation between two quaternion. + #[inline] + pub fn lerp(&self, other: &Quaternion, t: N) -> Quaternion { + self * (N::one() - t) + other * t } /// The vector part `(i, j, k)` of this quaternion. #[inline] - pub fn vector(&self) -> MatrixSlice { + pub fn vector(&self) -> MatrixSlice, CStride> { self.coords.fixed_rows::(0) } @@ -122,7 +153,7 @@ impl QuaternionBase /// Reinterprets this quaternion as a 4D vector. #[inline] - pub fn as_vector(&self) -> &ColumnVector { + pub fn as_vector(&self) -> &Vector4 { &self.coords } @@ -138,54 +169,12 @@ impl QuaternionBase self.coords.norm_squared() } - /// Normalizes this quaternion. - #[inline] - pub fn normalize(&self) -> OwnedQuaternionBase { - QuaternionBase::from_vector(self.coords.normalize()) - } - - /// Compute the conjugate of this quaternion. - #[inline] - pub fn conjugate(&self) -> OwnedQuaternionBase { - let v = OwnedColumnVector::::new(-self.coords[0], - -self.coords[1], - -self.coords[2], - self.coords[3]); - QuaternionBase::from_vector(v) - } - - /// Inverts this quaternion if it is not zero. - #[inline] - pub fn try_inverse(&self) -> Option> { - let mut res = QuaternionBase::from_vector(self.coords.clone_owned()); - - if res.try_inverse_mut() { - Some(res) - } - else { - None - } - } - - /// Linear interpolation between two quaternion. - #[inline] - pub fn lerp(&self, other: &QuaternionBase, t: N) -> OwnedQuaternionBase - where S2: Storage { - self * (N::one() - t) + other * t - } -} - - -impl QuaternionBase - where N: Real, - S: Storage, - S::Alloc: Allocator { /// The polar decomposition of this quaternion. /// /// Returns, from left to right: the quaternion norm, the half rotation angle, the rotation /// axis. If the rotation angle is zero, the rotation axis is set to `None`. - pub fn polar_decomposition(&self) -> (N, N, Option>>) { - if let Some((q, n)) = Unit::try_new_and_get(self.clone_owned(), N::zero()) { + pub fn polar_decomposition(&self) -> (N, N, Option>>) { + if let Some((q, n)) = Unit::try_new_and_get(*self, N::zero()) { if let Some(axis) = Unit::try_new(self.vector().clone_owned(), N::zero()) { let angle = q.angle() / ::convert(2.0f64); @@ -202,51 +191,47 @@ impl QuaternionBase /// Compute the exponential of a quaternion. #[inline] - pub fn exp(&self) -> OwnedQuaternionBase { + pub fn exp(&self) -> Quaternion { let v = self.vector(); let nn = v.norm_squared(); if relative_eq!(nn, N::zero()) { - QuaternionBase::identity() + Quaternion::identity() } else { let w_exp = self.scalar().exp(); let n = nn.sqrt(); let nv = v * (w_exp * n.sin() / n); - QuaternionBase::from_parts(n.cos(), nv) + Quaternion::from_parts(n.cos(), nv) } } /// Compute the natural logarithm of a quaternion. #[inline] - pub fn ln(&self) -> OwnedQuaternionBase { + pub fn ln(&self) -> Quaternion { let n = self.norm(); let v = self.vector(); let s = self.scalar(); - QuaternionBase::from_parts(n.ln(), v.normalize() * (s / n).acos()) + Quaternion::from_parts(n.ln(), v.normalize() * (s / n).acos()) } /// Raise the quaternion to a given floating power. #[inline] - pub fn powf(&self, n: N) -> OwnedQuaternionBase { + pub fn powf(&self, n: N) -> Quaternion { (self.ln() * n).exp() } -} -impl QuaternionBase - where N: Real, - S: StorageMut { /// Transforms this quaternion into its 4D vector form (Vector part, Scalar part). #[inline] - pub fn as_vector_mut(&mut self) -> &mut ColumnVector { + pub fn as_vector_mut(&mut self) -> &mut Vector4 { &mut self.coords } /// The mutable vector part `(i, j, k)` of this quaternion. #[inline] - pub fn vector_mut(&mut self) -> MatrixSliceMut { + pub fn vector_mut(&mut self) -> MatrixSliceMut, CStride> { self.coords.fixed_rows_mut::(0) } @@ -281,9 +266,7 @@ impl QuaternionBase } } -impl ApproxEq for QuaternionBase - where N: Real + ApproxEq, - S: Storage { +impl> ApproxEq for Quaternion { type Epsilon = N; #[inline] @@ -317,147 +300,29 @@ impl ApproxEq for QuaternionBase } -impl fmt::Display for QuaternionBase - where N: Real + fmt::Display, - S: Storage { +impl fmt::Display for Quaternion { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Quaternion {} − ({}, {}, {})", self[3], self[0], self[1], self[2]) } } /// A unit quaternions. May be used to represent a rotation. -/// -/// -///
-/// -/// Due to a [bug](https://github.com/rust-lang/rust/issues/32077) in rustdoc, the documentation -/// below has been written manually lists only method signatures.
-/// Trait implementations are not listed either. -///
-///
-/// -/// Please refer directly to the documentation written above each function definition on the source -/// code for more details. -/// -///

Methods

-/// -/// -///
-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-///

-pub type UnitQuaternionBase = Unit>; +pub type UnitQuaternion = Unit>; - -impl UnitQuaternionBase - where N: Real, - S: Storage { +impl UnitQuaternion { /// Moves this unit quaternion into one that owns its data. #[inline] - pub fn into_owned(self) -> OwnedUnitQuaternionBase { - UnitQuaternionBase::new_unchecked(self.unwrap().into_owned()) + #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + pub fn into_owned(self) -> UnitQuaternion { + self } /// Clones this unit quaternion into one that owns its data. #[inline] - pub fn clone_owned(&self) -> OwnedUnitQuaternionBase { - UnitQuaternionBase::new_unchecked(self.as_ref().clone_owned()) + #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + pub fn clone_owned(&self) -> UnitQuaternion { + *self } /// The rotation angle in [0; pi] of this unit quaternion. @@ -478,26 +343,25 @@ impl UnitQuaternionBase /// /// Same as `self.as_ref()`. #[inline] - pub fn quaternion(&self) -> &QuaternionBase { + pub fn quaternion(&self) -> &Quaternion { self.as_ref() } /// Compute the conjugate of this unit quaternion. #[inline] - pub fn conjugate(&self) -> OwnedUnitQuaternionBase { - UnitQuaternionBase::new_unchecked(self.as_ref().conjugate()) + pub fn conjugate(&self) -> UnitQuaternion { + UnitQuaternion::new_unchecked(self.as_ref().conjugate()) } /// Inverts this quaternion if it is not zero. #[inline] - pub fn inverse(&self) -> OwnedUnitQuaternionBase { + pub fn inverse(&self) -> UnitQuaternion { self.conjugate() } /// The rotation angle needed to make `self` and `other` coincide. #[inline] - pub fn angle_to(&self, other: &UnitQuaternionBase) -> N - where S2: Storage { + pub fn angle_to(&self, other: &UnitQuaternion) -> N { let delta = self.rotation_to(other); delta.angle() } @@ -506,8 +370,7 @@ impl UnitQuaternionBase /// /// The result is such that: `self.rotation_to(other) * self == other`. #[inline] - pub fn rotation_to(&self, other: &UnitQuaternionBase) -> OwnedUnitQuaternionBase - where S2: Storage { + pub fn rotation_to(&self, other: &UnitQuaternion) -> UnitQuaternion { other / self } @@ -515,19 +378,17 @@ impl UnitQuaternionBase /// /// The result is not normalized. #[inline] - pub fn lerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedQuaternionBase - where S2: Storage { + pub fn lerp(&self, other: &UnitQuaternion, t: N) -> Quaternion { self.as_ref().lerp(other.as_ref(), t) } /// Normalized linear interpolation between two unit quaternions. #[inline] - pub fn nlerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedUnitQuaternionBase - where S2: Storage { + pub fn nlerp(&self, other: &UnitQuaternion, t: N) -> UnitQuaternion { let mut res = self.lerp(other, t); let _ = res.normalize_mut(); - UnitQuaternionBase::new_unchecked(res) + UnitQuaternion::new_unchecked(res) } /// Spherical linear interpolation between two unit quaternions. @@ -535,8 +396,7 @@ impl UnitQuaternionBase /// Panics if the angle between both quaternion is 180 degrees (in which case the interpolation /// is not well-defined). #[inline] - pub fn slerp(&self, other: &UnitQuaternionBase, t: N) -> OwnedUnitQuaternionBase - where S2: Storage { + pub fn slerp(&self, other: &UnitQuaternion, t: N) -> UnitQuaternion { self.try_slerp(other, t, N::zero()).expect( "Unable to perform a spherical quaternion interpolation when they \ are 180 degree apart (the result is not unique).") @@ -553,15 +413,13 @@ impl UnitQuaternionBase /// * `epsilon`: the value bellow which the sinus of the angle separating both quaternion /// must be to return `None`. #[inline] - pub fn try_slerp(&self, other: &UnitQuaternionBase, t: N, epsilon: N) - -> Option> - where S2: Storage { + pub fn try_slerp(&self, other: &UnitQuaternion, t: N, epsilon: N) -> Option> { let c_hang = self.coords.dot(&other.coords); // self == other if c_hang.abs() >= N::one() { - return Some(self.clone_owned()) + return Some(*self) } let hang = c_hang.acos(); @@ -576,14 +434,10 @@ impl UnitQuaternionBase let tb = (t * hang).sin() / s_hang; let res = self.as_ref() * ta + other.as_ref() * tb; - Some(UnitQuaternionBase::new_unchecked(res)) + Some(UnitQuaternion::new_unchecked(res)) } } -} -impl UnitQuaternionBase - where N: Real, - S: StorageMut { /// Compute the conjugate of this unit quaternion in-place. #[inline] pub fn conjugate_mut(&mut self) { @@ -595,15 +449,10 @@ impl UnitQuaternionBase pub fn inverse_mut(&mut self) { self.as_mut_unchecked().conjugate_mut() } -} -impl UnitQuaternionBase - where N: Real, - S: Storage, - S::Alloc: Allocator { /// The rotation axis of this unit quaternion or `None` if the rotation is zero. #[inline] - pub fn axis(&self) -> Option>> { + pub fn axis(&self) -> Option>> { let v = if self.quaternion().scalar() >= N::zero() { self.as_ref().vector().clone_owned() @@ -618,35 +467,35 @@ impl UnitQuaternionBase /// The rotation axis of this unit quaternion multiplied by the rotation agle. #[inline] - pub fn scaled_axis(&self) -> OwnedColumnVector { + pub fn scaled_axis(&self) -> Vector3 { if let Some(axis) = self.axis() { axis.unwrap() * self.angle() } else { - ColumnVector::zero() + Vector3::zero() } } /// Compute the exponential of a quaternion. /// - /// Note that this function yields a `QuaternionBase` because it looses the unit property. + /// Note that this function yields a `Quaternion` because it looses the unit property. #[inline] - pub fn exp(&self) -> OwnedQuaternionBase { + pub fn exp(&self) -> Quaternion { self.as_ref().exp() } /// Compute the natural logarithm of a quaternion. /// - /// Note that this function yields a `QuaternionBase` because it looses the unit property. + /// Note that this function yields a `Quaternion` because it looses the unit property. /// The vector part of the return value corresponds to the axis-angle representation (divided /// by 2.0) of this unit quaternion. #[inline] - pub fn ln(&self) -> OwnedQuaternionBase { + pub fn ln(&self) -> Quaternion { if let Some(v) = self.axis() { - QuaternionBase::from_parts(N::zero(), v.unwrap() * self.angle()) + Quaternion::from_parts(N::zero(), v.unwrap() * self.angle()) } else { - QuaternionBase::zero() + Quaternion::zero() } } @@ -655,23 +504,18 @@ impl UnitQuaternionBase /// This returns the unit quaternion that identifies a rotation with axis `self.axis()` and /// angle `self.angle() × n`. #[inline] - pub fn powf(&self, n: N) -> OwnedUnitQuaternionBase { + pub fn powf(&self, n: N) -> UnitQuaternion { if let Some(v) = self.axis() { - UnitQuaternionBase::from_axis_angle(&v, self.angle() * n) + UnitQuaternion::from_axis_angle(&v, self.angle() * n) } else { - UnitQuaternionBase::identity() + UnitQuaternion::identity() } } -} -impl UnitQuaternionBase - where N: Real, - S: Storage, - S::Alloc: Allocator { /// Builds a rotation matrix from this unit quaternion. #[inline] - pub fn to_rotation_matrix(&self) -> OwnedRotation { + pub fn to_rotation_matrix(&self) -> Rotation { let i = self.as_ref()[0]; let j = self.as_ref()[1]; let k = self.as_ref()[2]; @@ -688,7 +532,7 @@ impl UnitQuaternionBase let jk = j * k * ::convert(2.0f64); let wi = w * i * ::convert(2.0f64); - RotationBase::from_matrix_unchecked( + Rotation::from_matrix_unchecked( SquareMatrix::<_, U3, _>::new( ww + ii - jj - kk, ij - wk, wj + ik, wk + ij, ww - ii + jj - kk, jk - wi, @@ -699,17 +543,13 @@ impl UnitQuaternionBase /// Converts this unit quaternion into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix - where S::Alloc: Allocator { + pub fn to_homogeneous(&self) -> MatrixN { self.to_rotation_matrix().to_homogeneous() } } -impl fmt::Display for UnitQuaternionBase - where N: Real + fmt::Display, - S: Storage, - S::Alloc: Allocator { +impl fmt::Display for UnitQuaternion { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(axis) = self.axis() { let axis = axis.unwrap(); @@ -721,9 +561,7 @@ impl fmt::Display for UnitQuaternionBase } } -impl ApproxEq for UnitQuaternionBase - where N: Real + ApproxEq, - S: Storage { +impl> ApproxEq for UnitQuaternion { type Epsilon = N; #[inline] diff --git a/src/geometry/quaternion_alga.rs b/src/geometry/quaternion_alga.rs index 16cd03f3..5bb52dee 100644 --- a/src/geometry/quaternion_alga.rs +++ b/src/geometry/quaternion_alga.rs @@ -7,57 +7,39 @@ use alga::linear::{Transformation, AffineTransformation, Similarity, Isometry, D OrthogonalTransformation, VectorSpace, FiniteDimVectorSpace, NormedSpace, Rotation, ProjectiveTransformation}; -use core::ColumnVector; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; -use core::dimension::{U1, U3, U4}; -use geometry::{PointBase, QuaternionBase, UnitQuaternionBase}; +use core::{Vector3, Vector4}; +use geometry::{Point3, Quaternion, UnitQuaternion}; -impl Identity for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Identity for Quaternion { #[inline] fn identity() -> Self { Self::identity() } } -impl Identity for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Identity for Quaternion { #[inline] fn identity() -> Self { Self::zero() } } -impl AbstractMagma for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Quaternion { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs } } -impl AbstractMagma for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Quaternion { #[inline] fn operate(&self, rhs: &Self) -> Self { self + rhs } } -impl Inverse for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Inverse for Quaternion { #[inline] fn inverse(&self) -> Self { -self @@ -66,15 +48,12 @@ impl Inverse for QuaternionBase macro_rules! impl_structures( ($Quaternion: ident; $($marker: ident<$operator: ident>),* $(,)*) => {$( - impl $marker<$operator> for $Quaternion - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for $Quaternion { } )*} ); impl_structures!( - QuaternionBase; + Quaternion; AbstractSemigroup, AbstractMonoid, @@ -92,10 +71,7 @@ impl_structures!( * Vector space. * */ -impl AbstractModule for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractModule for Quaternion { type AbstractRing = N; #[inline] @@ -104,24 +80,15 @@ impl AbstractModule for QuaternionBase } } -impl Module for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Module for Quaternion { type Ring = N; } -impl VectorSpace for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl VectorSpace for Quaternion { type Field = N; } -impl FiniteDimVectorSpace for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl FiniteDimVectorSpace for Quaternion { #[inline] fn dimension() -> usize { 4 @@ -129,7 +96,7 @@ impl FiniteDimVectorSpace for QuaternionBase #[inline] fn canonical_basis_element(i: usize) -> Self { - Self::from_vector(ColumnVector::canonical_basis_element(i)) + Self::from_vector(Vector4::canonical_basis_element(i)) } #[inline] @@ -148,10 +115,7 @@ impl FiniteDimVectorSpace for QuaternionBase } } -impl NormedSpace for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl NormedSpace for Quaternion { #[inline] fn norm_squared(&self) -> N { self.coords.norm_squared() @@ -191,33 +155,24 @@ impl NormedSpace for QuaternionBase /* * - * Implementations for UnitQuaternionBase. + * Implementations for UnitQuaternion. * */ -impl Identity for UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Identity for UnitQuaternion { #[inline] fn identity() -> Self { Self::identity() } } -impl AbstractMagma for UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for UnitQuaternion { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs } } -impl Inverse for UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Inverse for UnitQuaternion { #[inline] fn inverse(&self) -> Self { self.inverse() @@ -230,7 +185,7 @@ impl Inverse for UnitQuaternionBase } impl_structures!( - UnitQuaternionBase; + UnitQuaternion; AbstractSemigroup, AbstractQuasigroup, AbstractMonoid, @@ -238,48 +193,33 @@ impl_structures!( AbstractGroup ); -impl Transformation> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl Transformation> for UnitQuaternion { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point3) -> Point3 { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &Vector3) -> Vector3 { self * v } } -impl ProjectiveTransformation> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for UnitQuaternion { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point3) -> Point3 { // FIXME: would it be useful performancewise not to call inverse explicitly (i-e. implement // the inverse transformation explicitly here) ? self.inverse() * pt } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &Vector3) -> Vector3 { self.inverse() * v } } -impl AffineTransformation> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl AffineTransformation> for UnitQuaternion { type Rotation = Self; type NonUniformScaling = Id; type Translation = Id; @@ -320,12 +260,7 @@ impl AffineTransformation> for UnitQuaternionBas } } -impl Similarity> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl Similarity> for UnitQuaternion { type Scaling = Id; #[inline] @@ -346,12 +281,7 @@ impl Similarity> for UnitQuaternionBase macro_rules! marker_impl( ($($Trait: ident),*) => {$( - impl $Trait> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { } + impl $Trait> for UnitQuaternion { } )*} ); @@ -359,24 +289,19 @@ marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); -impl Rotation> for UnitQuaternionBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator + Allocator, - SB::Alloc: OwnedAllocator { +impl Rotation> for UnitQuaternion { #[inline] fn powf(&self, n: N) -> Option { Some(self.powf(n)) } #[inline] - fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { + fn rotation_between(a: &Vector3, b: &Vector3) -> Option { Self::rotation_between(a, b) } #[inline] - fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option { + fn scaled_rotation_between(a: &Vector3, b: &Vector3, s: N) -> Option { Self::scaled_rotation_between(a, b, s) } } diff --git a/src/geometry/quaternion_alias.rs b/src/geometry/quaternion_alias.rs deleted file mode 100644 index 36427e92..00000000 --- a/src/geometry/quaternion_alias.rs +++ /dev/null @@ -1,10 +0,0 @@ -use core::MatrixArray; -use core::dimension::{U1, U4}; - -use geometry::{QuaternionBase, UnitQuaternionBase}; - -/// A statically-allocated quaternion. -pub type Quaternion = QuaternionBase>; - -/// A statically-allocated unit quaternion. -pub type UnitQuaternion = UnitQuaternionBase>; diff --git a/src/geometry/quaternion_construction.rs b/src/geometry/quaternion_construction.rs index 2a627d93..2951e8b1 100644 --- a/src/geometry/quaternion_construction.rs +++ b/src/geometry/quaternion_construction.rs @@ -1,42 +1,38 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; +#[cfg(feature = "arbitrary")] +use core::dimension::U4; use rand::{Rand, Rng}; use num::{Zero, One}; use alga::general::Real; -use core::{Unit, ColumnVector, Vector3}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; -use core::dimension::{U1, U3, U4}; +use core::{Unit, Vector, Vector4, Vector3}; +use core::storage::Storage; +use core::dimension::U3; -use geometry::{QuaternionBase, UnitQuaternionBase, RotationBase, OwnedRotation}; +use geometry::{Quaternion, UnitQuaternion, Rotation}; -impl QuaternionBase - where N: Real, - S: Storage { +impl Quaternion { /// Creates a quaternion from a 4D vector. The quaternion scalar part corresponds to the `w` /// vector component. #[inline] - pub fn from_vector(vector: ColumnVector) -> Self { - QuaternionBase { + pub fn from_vector(vector: Vector4) -> Self { + Quaternion { coords: vector } } -} -impl QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { /// Creates a new quaternion from its individual components. Note that the arguments order does /// **not** follow the storage order. /// /// The storage order is `[ x, y, z, w ]`. #[inline] pub fn new(w: N, x: N, y: N, z: N) -> Self { - let v = ColumnVector::::new(x, y, z, w); + let v = Vector4::::new(x, y, z, w); Self::from_vector(v) } @@ -46,8 +42,8 @@ impl QuaternionBase /// The storage order is [ vector, scalar ]. #[inline] // FIXME: take a reference to `vector`? - pub fn from_parts(scalar: N, vector: ColumnVector) -> Self - where SB: Storage { + pub fn from_parts(scalar: N, vector: Vector) -> Self + where SB: Storage { Self::new(scalar, vector[0], vector[1], vector[2]) } @@ -56,9 +52,9 @@ impl QuaternionBase /// /// Note that `axis` is assumed to be a unit vector. // FIXME: take a reference to `axis`? - pub fn from_polar_decomposition(scale: N, theta: N, axis: Unit>) -> Self - where SB: Storage { - let rot = UnitQuaternionBase::::from_axis_angle(&axis, theta * ::convert(2.0f64)); + pub fn from_polar_decomposition(scale: N, theta: N, axis: Unit>) -> Self + where SB: Storage { + let rot = UnitQuaternion::::from_axis_angle(&axis, theta * ::convert(2.0f64)); rot.unwrap() * scale } @@ -70,20 +66,14 @@ impl QuaternionBase } } -impl One for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl One for Quaternion { #[inline] fn one() -> Self { Self::identity() } } -impl Zero for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Zero for Quaternion { #[inline] fn zero() -> Self { Self::new(N::zero(), N::zero(), N::zero(), N::zero()) @@ -95,46 +85,38 @@ impl Zero for QuaternionBase } } -impl Rand for QuaternionBase - where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Quaternion { #[inline] fn rand(rng: &mut R) -> Self { - QuaternionBase::new(rng.gen(), rng.gen(), rng.gen(), rng.gen()) + Quaternion::new(rng.gen(), rng.gen(), rng.gen(), rng.gen()) } } #[cfg(feature="arbitrary")] -impl Arbitrary for QuaternionBase - where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Quaternion + where Owned: Send { #[inline] fn arbitrary(g: &mut G) -> Self { - QuaternionBase::new(N::arbitrary(g), N::arbitrary(g), + Quaternion::new(N::arbitrary(g), N::arbitrary(g), N::arbitrary(g), N::arbitrary(g)) } } -impl UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl UnitQuaternion { /// The quaternion multiplicative identity. #[inline] pub fn identity() -> Self { - Self::new_unchecked(QuaternionBase::identity()) + Self::new_unchecked(Quaternion::identity()) } /// Creates a new quaternion from a unit vector (the rotation axis) and an angle /// (the rotation angle). #[inline] - pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self - where SB: Storage { + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self + where SB: Storage { let (sang, cang) = (angle / ::convert(2.0f64)).sin_cos(); - let q = QuaternionBase::from_parts(cang, axis.as_ref() * sang); + let q = Quaternion::from_parts(cang, axis.as_ref() * sang); Self::new_unchecked(q) } @@ -142,7 +124,7 @@ impl UnitQuaternionBase /// /// The input quaternion will be normalized. #[inline] - pub fn from_quaternion(q: QuaternionBase) -> Self { + pub fn from_quaternion(q: Quaternion) -> Self { Self::new_normalize(q) } @@ -155,7 +137,7 @@ impl UnitQuaternionBase let (sp, cp) = (pitch * ::convert(0.5f64)).sin_cos(); let (sy, cy) = (yaw * ::convert(0.5f64)).sin_cos(); - let q = QuaternionBase::new( + let q = Quaternion::new( cr * cp * cy + sr * sp * sy, sr * cp * cy - cr * sp * sy, cr * sp * cy + sr * cp * sy, @@ -166,10 +148,7 @@ impl UnitQuaternionBase /// Builds an unit quaternion from a rotation matrix. #[inline] - pub fn from_rotation_matrix(rotmat: &RotationBase) -> Self - where SB: Storage, - SB::Alloc: Allocator { - + pub fn from_rotation_matrix(rotmat: &Rotation) -> Self { // Robust matrix to quaternion transformation. // See http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion let tr = rotmat[(0, 0)] + rotmat[(1, 1)] + rotmat[(2, 2)]; @@ -179,28 +158,28 @@ impl UnitQuaternionBase if tr > N::zero() { let denom = (tr + N::one()).sqrt() * ::convert(2.0); - res = QuaternionBase::new(_0_25 * denom, + res = Quaternion::new(_0_25 * denom, (rotmat[(2, 1)] - rotmat[(1, 2)]) / denom, (rotmat[(0, 2)] - rotmat[(2, 0)]) / denom, (rotmat[(1, 0)] - rotmat[(0, 1)]) / denom); } else if rotmat[(0, 0)] > rotmat[(1, 1)] && rotmat[(0, 0)] > rotmat[(2, 2)] { let denom = (N::one() + rotmat[(0, 0)] - rotmat[(1, 1)] - rotmat[(2, 2)]).sqrt() * ::convert(2.0); - res = QuaternionBase::new((rotmat[(2, 1)] - rotmat[(1, 2)]) / denom, + res = Quaternion::new((rotmat[(2, 1)] - rotmat[(1, 2)]) / denom, _0_25 * denom, (rotmat[(0, 1)] + rotmat[(1, 0)]) / denom, (rotmat[(0, 2)] + rotmat[(2, 0)]) / denom); } else if rotmat[(1, 1)] > rotmat[(2, 2)] { let denom = (N::one() + rotmat[(1, 1)] - rotmat[(0, 0)] - rotmat[(2, 2)]).sqrt() * ::convert(2.0); - res = QuaternionBase::new((rotmat[(0, 2)] - rotmat[(2, 0)]) / denom, + res = Quaternion::new((rotmat[(0, 2)] - rotmat[(2, 0)]) / denom, (rotmat[(0, 1)] + rotmat[(1, 0)]) / denom, _0_25 * denom, (rotmat[(1, 2)] + rotmat[(2, 1)]) / denom); } else { let denom = (N::one() + rotmat[(2, 2)] - rotmat[(0, 0)] - rotmat[(1, 1)]).sqrt() * ::convert(2.0); - res = QuaternionBase::new((rotmat[(1, 0)] - rotmat[(0, 1)]) / denom, + res = Quaternion::new((rotmat[(1, 0)] - rotmat[(0, 1)]) / denom, (rotmat[(0, 2)] + rotmat[(2, 0)]) / denom, (rotmat[(1, 2)] + rotmat[(2, 1)]) / denom, _0_25 * denom); @@ -212,19 +191,22 @@ impl UnitQuaternionBase /// The unit quaternion needed to make `a` and `b` be collinear and point toward the same /// direction. #[inline] - pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option - where SB: Storage, - SC: Storage { + pub fn rotation_between(a: &Vector, b: &Vector) -> Option + where SB: Storage, + SC: Storage { Self::scaled_rotation_between(a, b, N::one()) } /// The smallest rotation needed to make `a` and `b` collinear and point toward the same /// direction, raised to the power `s`. #[inline] - pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option - where SB: Storage, - SC: Storage { - // FIXME: code duplication with RotationBase. + pub fn scaled_rotation_between(a: &Vector, + b: &Vector, + s: N) + -> Option + where SB: Storage, + SC: Storage { + // FIXME: code duplication with Rotation. if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { let c = na.cross(&nb); @@ -257,12 +239,10 @@ impl UnitQuaternionBase /// collinear /// to `dir`. Non-collinearity is not checked. #[inline] - pub fn new_observer_frame(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage, - S::Alloc: Allocator + - Allocator { - Self::from_rotation_matrix(&OwnedRotation::::new_observer_frame(dir, up)) + pub fn new_observer_frame(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { + Self::from_rotation_matrix(&Rotation::::new_observer_frame(dir, up)) } @@ -277,11 +257,9 @@ impl UnitQuaternionBase /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_rh(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage, - S::Alloc: Allocator + - Allocator { + pub fn look_at_rh(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { Self::new_observer_frame(&-dir, up).inverse() } @@ -296,28 +274,20 @@ impl UnitQuaternionBase /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_lh(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage, - S::Alloc: Allocator + - Allocator { + pub fn look_at_lh(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { Self::new_observer_frame(dir, up).inverse() } -} -impl UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator { /// Creates a new unit quaternion rotation from a rotation axis scaled by the rotation angle. /// /// If `axisangle` is zero, this returns the indentity rotation. #[inline] - pub fn new(axisangle: ColumnVector) -> Self - where SB: Storage { + pub fn new(axisangle: Vector) -> Self + where SB: Storage { let two: N = ::convert(2.0f64); - let q = QuaternionBase::::from_parts(N::zero(), axisangle / two).exp(); + let q = Quaternion::::from_parts(N::zero(), axisangle / two).exp(); Self::new_unchecked(q) } @@ -326,44 +296,35 @@ impl UnitQuaternionBase /// If `axisangle` is zero, this returns the indentity rotation. /// Same as `Self::new(axisangle)`. #[inline] - pub fn from_scaled_axis(axisangle: ColumnVector) -> Self - where SB: Storage { + pub fn from_scaled_axis(axisangle: Vector) -> Self + where SB: Storage { Self::new(axisangle) } } -impl One for UnitQuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl One for UnitQuaternion { #[inline] fn one() -> Self { Self::identity() } } -impl Rand for UnitQuaternionBase - where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator { +impl Rand for UnitQuaternion { #[inline] fn rand(rng: &mut R) -> Self { let axisangle = Vector3::rand(rng); - UnitQuaternionBase::from_scaled_axis(axisangle) + UnitQuaternion::from_scaled_axis(axisangle) } } #[cfg(feature="arbitrary")] -impl Arbitrary for UnitQuaternionBase - where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator + - Allocator { +impl Arbitrary for UnitQuaternion + where Owned: Send, + Owned: Send { #[inline] fn arbitrary(g: &mut G) -> Self { let axisangle = Vector3::arbitrary(g); - UnitQuaternionBase::from_scaled_axis(axisangle) + UnitQuaternion::from_scaled_axis(axisangle) } } diff --git a/src/geometry/quaternion_conversion.rs b/src/geometry/quaternion_conversion.rs index da70cda0..7ac90eed 100644 --- a/src/geometry/quaternion_conversion.rs +++ b/src/geometry/quaternion_conversion.rs @@ -3,12 +3,11 @@ use num::Zero; use alga::general::{SubsetOf, SupersetOf, Real}; use alga::linear::Rotation as AlgaRotation; -use core::{ColumnVector, SquareMatrix}; -use core::dimension::{U1, U3, U4}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; -use geometry::{PointBase, QuaternionBase, UnitQuaternionBase, OwnedUnitQuaternionBase, RotationBase, - OwnedRotation, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine, TranslationBase}; +use core::{Vector4, Matrix4}; +use core::dimension::U3; +use geometry::{Quaternion, UnitQuaternion, Rotation, Isometry, Similarity, + Transform, SuperTCategoryOf, TAffine, Translation, + Rotation3, Point3}; /* * This file provides the following conversions: @@ -16,197 +15,154 @@ use geometry::{PointBase, QuaternionBase, UnitQuaternionBase, OwnedUnitQuaternio * * Quaternion -> Quaternion * UnitQuaternion -> UnitQuaternion - * UnitQuaternion -> RotationBase - * UnitQuaternion -> IsometryBase - * UnitQuaternion -> SimilarityBase - * UnitQuaternion -> TransformBase + * UnitQuaternion -> Rotation + * UnitQuaternion -> Isometry + * UnitQuaternion -> Similarity + * UnitQuaternion -> Transform * UnitQuaternion -> Matrix (homogeneous) * * NOTE: * UnitQuaternion -> Quaternion is already provided by: Unit -> T */ -impl SubsetOf> for QuaternionBase +impl SubsetOf> for Quaternion where N1: Real, - N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + N2: Real + SupersetOf { #[inline] - fn to_superset(&self) -> QuaternionBase { - QuaternionBase::from_vector(self.coords.to_superset()) + fn to_superset(&self) -> Quaternion { + Quaternion::from_vector(self.coords.to_superset()) } #[inline] - fn is_in_subset(q: &QuaternionBase) -> bool { - ::is_convertible::<_, ColumnVector>(&q.coords) + fn is_in_subset(q: &Quaternion) -> bool { + ::is_convertible::<_, Vector4>(&q.coords) } #[inline] - unsafe fn from_superset_unchecked(q: &QuaternionBase) -> Self { + unsafe fn from_superset_unchecked(q: &Quaternion) -> Self { Self::from_vector(q.coords.to_subset_unchecked()) } } -impl SubsetOf> for UnitQuaternionBase +impl SubsetOf> for UnitQuaternion where N1: Real, - N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + N2: Real + SupersetOf { #[inline] - fn to_superset(&self) -> UnitQuaternionBase { - UnitQuaternionBase::new_unchecked(self.as_ref().to_superset()) + fn to_superset(&self) -> UnitQuaternion { + UnitQuaternion::new_unchecked(self.as_ref().to_superset()) } #[inline] - fn is_in_subset(uq: &UnitQuaternionBase) -> bool { - ::is_convertible::<_, QuaternionBase>(uq.as_ref()) + fn is_in_subset(uq: &UnitQuaternion) -> bool { + ::is_convertible::<_, Quaternion>(uq.as_ref()) } #[inline] - unsafe fn from_superset_unchecked(uq: &UnitQuaternionBase) -> Self { + unsafe fn from_superset_unchecked(uq: &UnitQuaternion) -> Self { Self::new_unchecked(::convert_ref_unchecked(uq.as_ref())) } } -impl SubsetOf> for UnitQuaternionBase +impl SubsetOf> for UnitQuaternion where N1: Real, - N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator + - Allocator, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { + N2: Real + SupersetOf { #[inline] - fn to_superset(&self) -> RotationBase { - let q: OwnedUnitQuaternionBase = self.to_superset(); + fn to_superset(&self) -> Rotation3 { + let q: UnitQuaternion = self.to_superset(); q.to_rotation_matrix() } #[inline] - fn is_in_subset(rot: &RotationBase) -> bool { - ::is_convertible::<_, OwnedRotation>(rot) + fn is_in_subset(rot: &Rotation3) -> bool { + ::is_convertible::<_, Rotation3>(rot) } #[inline] - unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { - let q = OwnedUnitQuaternionBase::::from_rotation_matrix(rot); + unsafe fn from_superset_unchecked(rot: &Rotation3) -> Self { + let q = UnitQuaternion::::from_rotation_matrix(rot); ::convert_unchecked(q) } } -impl SubsetOf> for UnitQuaternionBase +impl SubsetOf> for UnitQuaternion where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf> { #[inline] - fn to_superset(&self) -> IsometryBase { - IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + fn to_superset(&self) -> Isometry { + Isometry::from_parts(Translation::identity(), ::convert_ref(self)) } #[inline] - fn is_in_subset(iso: &IsometryBase) -> bool { + fn is_in_subset(iso: &Isometry) -> bool { iso.translation.vector.is_zero() } #[inline] - unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + unsafe fn from_superset_unchecked(iso: &Isometry) -> Self { ::convert_ref_unchecked(&iso.rotation) } } -impl SubsetOf> for UnitQuaternionBase +impl SubsetOf> for UnitQuaternion where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf> { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_isometry(::convert_ref(self), N2::one()) + fn to_superset(&self) -> Similarity { + Similarity::from_isometry(::convert_ref(self), N2::one()) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { + fn is_in_subset(sim: &Similarity) -> bool { sim.isometry.translation.vector.is_zero() && sim.scaling() == N2::one() } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { ::convert_ref_unchecked(&sim.isometry) } } -impl SubsetOf> for UnitQuaternionBase +impl SubsetOf> for UnitQuaternion where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - C: SuperTCategoryOf, - SA::Alloc: OwnedAllocator + - Allocator + - Allocator + - Allocator, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { + C: SuperTCategoryOf { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf> for UnitQuaternionBase - where N1: Real, - N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator + - Allocator + - Allocator + - Allocator, - SB::Alloc: OwnedAllocator + - Allocator + - Allocator { +impl> SubsetOf> for UnitQuaternion { #[inline] - fn to_superset(&self) -> SquareMatrix { + fn to_superset(&self) -> Matrix4 { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix) -> bool { - ::is_convertible::<_, OwnedRotation>(m) + fn is_in_subset(m: &Matrix4) -> bool { + ::is_convertible::<_, Rotation3>(m) } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix) -> Self { - let rot: OwnedRotation = ::convert_ref_unchecked(m); + unsafe fn from_superset_unchecked(m: &Matrix4) -> Self { + let rot: Rotation3 = ::convert_ref_unchecked(m); Self::from_rotation_matrix(&rot) } } diff --git a/src/geometry/quaternion_coordinates.rs b/src/geometry/quaternion_coordinates.rs index fdf4169c..cdfb39cd 100644 --- a/src/geometry/quaternion_coordinates.rs +++ b/src/geometry/quaternion_coordinates.rs @@ -4,17 +4,11 @@ use std::ops::{Deref, DerefMut}; use alga::general::Real; use core::coordinates::IJKW; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; -use core::dimension::{U1, U4}; -use geometry::QuaternionBase; +use geometry::Quaternion; -impl Deref for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Deref for Quaternion { type Target = IJKW; #[inline] @@ -23,10 +17,7 @@ impl Deref for QuaternionBase } } -impl DerefMut for QuaternionBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl DerefMut for Quaternion { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { unsafe { mem::transmute(self) } diff --git a/src/geometry/quaternion_ops.rs b/src/geometry/quaternion_ops.rs index ce577176..af1650a2 100644 --- a/src/geometry/quaternion_ops.rs +++ b/src/geometry/quaternion_ops.rs @@ -17,17 +17,17 @@ * * (Unit Quaternion) * UnitQuaternion × UnitQuaternion - * UnitQuaternion × RotationBase -> UnitQuaternion - * RotationBase × UnitQuaternion -> UnitQuaternion + * UnitQuaternion × Rotation -> UnitQuaternion + * Rotation × UnitQuaternion -> UnitQuaternion * * UnitQuaternion ÷ UnitQuaternion - * UnitQuaternion ÷ RotationBase -> UnitQuaternion - * RotationBase ÷ UnitQuaternion -> UnitQuaternion + * UnitQuaternion ÷ Rotation -> UnitQuaternion + * Rotation ÷ UnitQuaternion -> UnitQuaternion * * - * UnitQuaternion × PointBase - * UnitQuaternion × ColumnVector - * UnitQuaternion × Unit + * UnitQuaternion × Point + * UnitQuaternion × Vector + * UnitQuaternion × Unit * * NOTE: -UnitQuaternion is already provided by `Unit`. * @@ -40,31 +40,28 @@ * Quaternion -= Quaternion * * UnitQuaternion ×= UnitQuaternion - * UnitQuaternion ×= RotationBase + * UnitQuaternion ×= Rotation * * UnitQuaternion ÷= UnitQuaternion - * UnitQuaternion ÷= RotationBase + * UnitQuaternion ÷= Rotation * - * FIXME: RotationBase ×= UnitQuaternion - * FIXME: RotationBase ÷= UnitQuaternion + * FIXME: Rotation ×= UnitQuaternion + * FIXME: Rotation ÷= UnitQuaternion * */ -use std::ops::{Index, IndexMut, Neg, Add, AddAssign, Mul, MulAssign, Div, DivAssign, Sub, SubAssign}; +use std::ops::{Index, IndexMut, Neg, Add, AddAssign, Mul, MulAssign, Sub, SubAssign, Div, DivAssign}; use alga::general::Real; -use core::{ColumnVector, OwnedColumnVector, Unit}; -use core::storage::{Storage, StorageMut}; +use core::{DefaultAllocator, Vector, Vector3, Unit}; +use core::storage::Storage; use core::allocator::Allocator; use core::dimension::{U1, U3, U4}; -use geometry::{QuaternionBase, OwnedQuaternionBase, UnitQuaternionBase, OwnedUnitQuaternionBase, - PointBase, OwnedPoint, RotationBase}; +use geometry::{Quaternion, UnitQuaternion, Point3, Rotation}; -impl Index for QuaternionBase - where N: Real, - S: Storage { +impl Index for Quaternion { type Output = N; #[inline] @@ -73,10 +70,7 @@ impl Index for QuaternionBase } } -impl IndexMut for QuaternionBase - where N: Real, - S: StorageMut { - +impl IndexMut for Quaternion { #[inline] fn index_mut(&mut self, i: usize) -> &mut N { &mut self.coords[i] @@ -85,15 +79,13 @@ impl IndexMut for QuaternionBase macro_rules! quaternion_op_impl( ($Op: ident, $op: ident; - ($LhsRDim: ident, $LhsCDim: ident), ($RhsRDim: ident, $RhsCDim: ident); + ($LhsRDim: ident, $LhsCDim: ident), ($RhsRDim: ident, $RhsCDim: ident) + $(for $Storage: ident: $StoragesBound: ident $(<$($BoundParam: ty),*>)*),*; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty $(=> $VDimA: ty, $VDimB: ty)*; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N, SA, SB> $Op<$Rhs> for $Lhs - where N: Real, - SA: Storage, - SB: Storage, - $(SA::Alloc: Allocator, - SB::Alloc: Allocator)* { + impl<$($lives ,)* N: Real $(, $Storage: $StoragesBound $(<$($BoundParam),*>)*)*> $Op<$Rhs> for $Lhs + where DefaultAllocator: Allocator + + Allocator { type Output = $Result; #[inline] @@ -109,29 +101,29 @@ macro_rules! quaternion_op_impl( quaternion_op_impl!( Add, add; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(&self.coords + &rhs.coords); + self: &'a Quaternion, rhs: &'b Quaternion, Output = Quaternion; + Quaternion::from_vector(&self.coords + &rhs.coords); 'a, 'b); quaternion_op_impl!( Add, add; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(&self.coords + rhs.coords); + self: &'a Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from_vector(&self.coords + rhs.coords); 'a); quaternion_op_impl!( Add, add; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(self.coords + &rhs.coords); + self: Quaternion, rhs: &'b Quaternion, Output = Quaternion; + Quaternion::from_vector(self.coords + &rhs.coords); 'b); quaternion_op_impl!( Add, add; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(self.coords + rhs.coords); + self: Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from_vector(self.coords + rhs.coords); ); @@ -139,29 +131,29 @@ quaternion_op_impl!( quaternion_op_impl!( Sub, sub; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(&self.coords - &rhs.coords); + self: &'a Quaternion, rhs: &'b Quaternion, Output = Quaternion; + Quaternion::from_vector(&self.coords - &rhs.coords); 'a, 'b); quaternion_op_impl!( Sub, sub; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(&self.coords - rhs.coords); + self: &'a Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from_vector(&self.coords - rhs.coords); 'a); quaternion_op_impl!( Sub, sub; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(self.coords - &rhs.coords); + self: Quaternion, rhs: &'b Quaternion, Output = Quaternion; + Quaternion::from_vector(self.coords - &rhs.coords); 'b); quaternion_op_impl!( Sub, sub; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::from_vector(self.coords - rhs.coords); + self: Quaternion, rhs: Quaternion, Output = Quaternion; + Quaternion::from_vector(self.coords - rhs.coords); ); @@ -169,8 +161,8 @@ quaternion_op_impl!( quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; - QuaternionBase::new( + self: &'a Quaternion, rhs: &'b Quaternion, Output = Quaternion; + Quaternion::new( self[3] * rhs[3] - self[0] * rhs[0] - self[1] * rhs[1] - self[2] * rhs[2], self[3] * rhs[0] + self[0] * rhs[3] + self[1] * rhs[2] - self[2] * rhs[1], self[3] * rhs[1] - self[0] * rhs[2] + self[1] * rhs[3] + self[2] * rhs[0], @@ -180,21 +172,21 @@ quaternion_op_impl!( quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: &'a QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + self: &'a Quaternion, rhs: Quaternion, Output = Quaternion; self * &rhs; 'a); quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase, Output = OwnedQuaternionBase; + self: Quaternion, rhs: &'b Quaternion, Output = Quaternion; &self * rhs; 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase, Output = OwnedQuaternionBase; + self: Quaternion, rhs: Quaternion, Output = Quaternion; &self * &rhs; ); @@ -202,28 +194,28 @@ quaternion_op_impl!( quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: &'a UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; - UnitQuaternionBase::new_unchecked(self.quaternion() * rhs.quaternion()); + self: &'a UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion; + UnitQuaternion::new_unchecked(self.quaternion() * rhs.quaternion()); 'a, 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: &'a UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: &'a UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; self * &rhs; 'a); quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion; &self * rhs; 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; &self * &rhs; ); @@ -231,173 +223,173 @@ quaternion_op_impl!( quaternion_op_impl!( Div, div; (U4, U1), (U4, U1); - self: &'a UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: &'a UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion; self * rhs.inverse(); 'a, 'b); quaternion_op_impl!( Div, div; (U4, U1), (U4, U1); - self: &'a UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: &'a UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; self / &rhs; 'a); quaternion_op_impl!( Div, div; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: UnitQuaternion, rhs: &'b UnitQuaternion, Output = UnitQuaternion; &self / rhs; 'b); quaternion_op_impl!( Div, div; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: UnitQuaternionBase, Output = OwnedUnitQuaternionBase; + self: UnitQuaternion, rhs: UnitQuaternion, Output = UnitQuaternion; &self / &rhs; ); -// UnitQuaternion × RotationBase +// UnitQuaternion × Rotation quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U3); - self: &'a UnitQuaternionBase, rhs: &'b RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; + self: &'a UnitQuaternion, rhs: &'b Rotation, + Output = UnitQuaternion => U3, U3; // FIXME: can we avoid the conversion from a rotation matrix? - self * OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + self * UnitQuaternion::::from_rotation_matrix(rhs); 'a, 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U3); - self: &'a UnitQuaternionBase, rhs: RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self * OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + self: &'a UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self * UnitQuaternion::::from_rotation_matrix(&rhs); 'a); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: &'b RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self * OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + self: UnitQuaternion, rhs: &'b Rotation, + Output = UnitQuaternion => U3, U3; + self * UnitQuaternion::::from_rotation_matrix(rhs); 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self * OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + self: UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self * UnitQuaternion::::from_rotation_matrix(&rhs); ); -// UnitQuaternion ÷ RotationBase +// UnitQuaternion ÷ Rotation quaternion_op_impl!( Div, div; (U4, U1), (U3, U3); - self: &'a UnitQuaternionBase, rhs: &'b RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; + self: &'a UnitQuaternion, rhs: &'b Rotation, + Output = UnitQuaternion => U3, U3; // FIXME: can we avoid the conversion to a rotation matrix? - self / OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + self / UnitQuaternion::::from_rotation_matrix(rhs); 'a, 'b); quaternion_op_impl!( Div, div; (U4, U1), (U3, U3); - self: &'a UnitQuaternionBase, rhs: RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self / OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + self: &'a UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self / UnitQuaternion::::from_rotation_matrix(&rhs); 'a); quaternion_op_impl!( Div, div; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: &'b RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self / OwnedUnitQuaternionBase::::from_rotation_matrix(rhs); + self: UnitQuaternion, rhs: &'b Rotation, + Output = UnitQuaternion => U3, U3; + self / UnitQuaternion::::from_rotation_matrix(rhs); 'b); quaternion_op_impl!( Div, div; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: RotationBase, - Output = OwnedUnitQuaternionBase => U3, U3; - self / OwnedUnitQuaternionBase::::from_rotation_matrix(&rhs); + self: UnitQuaternion, rhs: Rotation, + Output = UnitQuaternion => U3, U3; + self / UnitQuaternion::::from_rotation_matrix(&rhs); ); -// RotationBase × UnitQuaternion +// Rotation × UnitQuaternion quaternion_op_impl!( Mul, mul; (U3, U3), (U4, U1); - self: &'a RotationBase, rhs: &'b UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; + self: &'a Rotation, rhs: &'b UnitQuaternion, + Output = UnitQuaternion => U3, U3; // FIXME: can we avoid the conversion from a rotation matrix? - OwnedUnitQuaternionBase::::from_rotation_matrix(self) * rhs; + UnitQuaternion::::from_rotation_matrix(self) * rhs; 'a, 'b); quaternion_op_impl!( Mul, mul; (U3, U3), (U4, U1); - self: &'a RotationBase, rhs: UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(self) * rhs; + self: &'a Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(self) * rhs; 'a); quaternion_op_impl!( Mul, mul; (U3, U3), (U4, U1); - self: RotationBase, rhs: &'b UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(&self) * rhs; + self: Rotation, rhs: &'b UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) * rhs; 'b); quaternion_op_impl!( Mul, mul; (U3, U3), (U4, U1); - self: RotationBase, rhs: UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(&self) * rhs; + self: Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) * rhs; ); -// RotationBase ÷ UnitQuaternion +// Rotation ÷ UnitQuaternion quaternion_op_impl!( Div, div; (U3, U3), (U4, U1); - self: &'a RotationBase, rhs: &'b UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; + self: &'a Rotation, rhs: &'b UnitQuaternion, + Output = UnitQuaternion => U3, U3; // FIXME: can we avoid the conversion from a rotation matrix? - OwnedUnitQuaternionBase::::from_rotation_matrix(self) / rhs; + UnitQuaternion::::from_rotation_matrix(self) / rhs; 'a, 'b); quaternion_op_impl!( Div, div; (U3, U3), (U4, U1); - self: &'a RotationBase, rhs: UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(self) / rhs; + self: &'a Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(self) / rhs; 'a); quaternion_op_impl!( Div, div; (U3, U3), (U4, U1); - self: RotationBase, rhs: &'b UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(&self) / rhs; + self: Rotation, rhs: &'b UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) / rhs; 'b); quaternion_op_impl!( Div, div; (U3, U3), (U4, U1); - self: RotationBase, rhs: UnitQuaternionBase, - Output = OwnedUnitQuaternionBase => U3, U3; - OwnedUnitQuaternionBase::::from_rotation_matrix(&self) / rhs; + self: Rotation, rhs: UnitQuaternion, + Output = UnitQuaternion => U3, U3; + UnitQuaternion::::from_rotation_matrix(&self) / rhs; ); // UnitQuaternion × Vector quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: &'b ColumnVector, - Output = OwnedColumnVector => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: &'a UnitQuaternion, rhs: &'b Vector, + Output = Vector3 => U3, U4; { let two: N = ::convert(2.0f64); let t = self.as_ref().vector().cross(rhs) * two; @@ -409,91 +401,91 @@ quaternion_op_impl!( quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: ColumnVector, - Output = OwnedColumnVector => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: &'a UnitQuaternion, rhs: Vector, + Output = Vector3 => U3, U4; self * &rhs; 'a); quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: &'b ColumnVector, - Output = OwnedColumnVector => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: &'b Vector, + Output = Vector3 => U3, U4; &self * rhs; 'b); quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: ColumnVector, - Output = OwnedColumnVector => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: Vector, + Output = Vector3 => U3, U4; &self * &rhs; ); -// UnitQuaternion × PointBase +// UnitQuaternion × Point quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: &'b PointBase, - Output = OwnedPoint => U3, U4; - PointBase::from_coordinates(self * &rhs.coords); + self: &'a UnitQuaternion, rhs: &'b Point3, + Output = Point3 => U3, U4; + Point3::from_coordinates(self * &rhs.coords); 'a, 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: PointBase, - Output = OwnedPoint => U3, U4; - PointBase::from_coordinates(self * rhs.coords); + self: &'a UnitQuaternion, rhs: Point3, + Output = Point3 => U3, U4; + Point3::from_coordinates(self * rhs.coords); 'a); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: &'b PointBase, - Output = OwnedPoint => U3, U4; - PointBase::from_coordinates(self * &rhs.coords); + self: UnitQuaternion, rhs: &'b Point3, + Output = Point3 => U3, U4; + Point3::from_coordinates(self * &rhs.coords); 'b); quaternion_op_impl!( Mul, mul; (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: PointBase, - Output = OwnedPoint => U3, U4; - PointBase::from_coordinates(self * rhs.coords); + self: UnitQuaternion, rhs: Point3, + Output = Point3 => U3, U4; + Point3::from_coordinates(self * rhs.coords); ); // UnitQuaternion × Unit quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: &'b Unit>, - Output = Unit> => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: &'a UnitQuaternion, rhs: &'b Unit>, + Output = Unit> => U3, U4; Unit::new_unchecked(self * rhs.as_ref()); 'a, 'b); quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: &'a UnitQuaternionBase, rhs: Unit>, - Output = Unit> => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: &'a UnitQuaternion, rhs: Unit>, + Output = Unit> => U3, U4; Unit::new_unchecked(self * rhs.unwrap()); 'a); quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: &'b Unit>, - Output = Unit> => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: &'b Unit>, + Output = Unit> => U3, U4; Unit::new_unchecked(self * rhs.as_ref()); 'b); quaternion_op_impl!( Mul, mul; - (U4, U1), (U3, U1); - self: UnitQuaternionBase, rhs: Unit>, - Output = Unit> => U3, U4; + (U4, U1), (U3, U1) for SB: Storage ; + self: UnitQuaternion, rhs: Unit>, + Output = Unit> => U3, U4; Unit::new_unchecked(self * rhs.unwrap()); ); @@ -501,31 +493,25 @@ quaternion_op_impl!( macro_rules! scalar_op_impl( ($($Op: ident, $op: ident, $OpAssign: ident, $op_assign: ident);* $(;)*) => {$( - impl $Op for QuaternionBase - where N: Real, - S: Storage { - type Output = OwnedQuaternionBase; + impl $Op for Quaternion { + type Output = Quaternion; #[inline] fn $op(self, n: N) -> Self::Output { - QuaternionBase::from_vector(self.coords.$op(n)) + Quaternion::from_vector(self.coords.$op(n)) } } - impl<'a, N, S> $Op for &'a QuaternionBase - where N: Real, - S: Storage { - type Output = OwnedQuaternionBase; + impl<'a, N: Real> $Op for &'a Quaternion { + type Output = Quaternion; #[inline] fn $op(self, n: N) -> Self::Output { - QuaternionBase::from_vector((&self.coords).$op(n)) + Quaternion::from_vector((&self.coords).$op(n)) } } - impl $OpAssign for QuaternionBase - where N: Real, - S: StorageMut { + impl $OpAssign for Quaternion { #[inline] fn $op_assign(&mut self, n: N) { @@ -542,23 +528,21 @@ scalar_op_impl!( macro_rules! left_scalar_mul_impl( ($($T: ty),* $(,)*) => {$( - impl Mul> for $T - where S: Storage<$T, U4, U1> { - type Output = OwnedQuaternionBase<$T, S::Alloc>; + impl Mul> for $T { + type Output = Quaternion<$T>; #[inline] - fn mul(self, right: QuaternionBase<$T, S>) -> Self::Output { - QuaternionBase::from_vector(self * right.coords) + fn mul(self, right: Quaternion<$T>) -> Self::Output { + Quaternion::from_vector(self * right.coords) } } - impl<'b, S> Mul<&'b QuaternionBase<$T, S>> for $T - where S: Storage<$T, U4, U1> { - type Output = OwnedQuaternionBase<$T, S::Alloc>; + impl<'b> Mul<&'b Quaternion<$T>> for $T { + type Output = Quaternion<$T>; #[inline] - fn mul(self, right: &'b QuaternionBase<$T, S>) -> Self::Output { - QuaternionBase::from_vector(self * &right.coords) + fn mul(self, right: &'b Quaternion<$T>) -> Self::Output { + Quaternion::from_vector(self * &right.coords) } } )*} @@ -566,25 +550,21 @@ macro_rules! left_scalar_mul_impl( left_scalar_mul_impl!(f32, f64); -impl Neg for QuaternionBase - where N: Real, - S: Storage { - type Output = OwnedQuaternionBase; +impl Neg for Quaternion { + type Output = Quaternion; #[inline] fn neg(self) -> Self::Output { - QuaternionBase::from_vector(-self.coords) + Quaternion::from_vector(-self.coords) } } -impl<'a, N, S> Neg for &'a QuaternionBase - where N: Real, - S: Storage { - type Output = OwnedQuaternionBase; +impl<'a, N: Real> Neg for &'a Quaternion { + type Output = Quaternion; #[inline] fn neg(self) -> Self::Output { - QuaternionBase::from_vector(-&self.coords) + Quaternion::from_vector(-&self.coords) } } @@ -593,17 +573,9 @@ macro_rules! quaternion_op_impl( ($LhsRDim: ident, $LhsCDim: ident), ($RhsRDim: ident, $RhsCDim: ident); $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty $(=> $VDimA: ty, $VDimB: ty)*; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N, SA, SB> $OpAssign<$Rhs> for $Lhs - where N: Real, - SA: StorageMut, - SB: Storage, - $(SA::Alloc: Allocator + Allocator, - // ^^^^^^^^^^^^^^^^^^^^ - // XXX: For some reasons, the compiler needs - // this bound to compile UnitQuat *= RotationBase. - // Though in theory this bound is already - // inherited from `SA: StorageMut`… - SB::Alloc: Allocator)* { + impl<$($lives ,)* N: Real> $OpAssign<$Rhs> for $Lhs + where DefaultAllocator: Allocator + + Allocator { #[inline] fn $op_assign(&mut $lhs, $rhs: $Rhs) { @@ -617,14 +589,14 @@ macro_rules! quaternion_op_impl( quaternion_op_impl!( AddAssign, add_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase; + self: Quaternion, rhs: &'b Quaternion; self.coords += &rhs.coords; 'b); quaternion_op_impl!( AddAssign, add_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase; + self: Quaternion, rhs: Quaternion; self.coords += rhs.coords; ); @@ -632,21 +604,21 @@ quaternion_op_impl!( quaternion_op_impl!( SubAssign, sub_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase; + self: Quaternion, rhs: &'b Quaternion; self.coords -= &rhs.coords; 'b); quaternion_op_impl!( SubAssign, sub_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase; + self: Quaternion, rhs: Quaternion; self.coords -= rhs.coords; ); // Quaternion ×= Quaternion quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: &'b QuaternionBase; + self: Quaternion, rhs: &'b Quaternion; { let res = &*self * rhs; // FIXME: will this be optimized away? @@ -657,14 +629,14 @@ quaternion_op_impl!( quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U4, U1); - self: QuaternionBase, rhs: QuaternionBase; + self: Quaternion, rhs: Quaternion; *self *= &rhs; ); // UnitQuaternion ×= UnitQuaternion quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase; + self: UnitQuaternion, rhs: &'b UnitQuaternion; { let res = &*self * rhs; self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); @@ -674,14 +646,14 @@ quaternion_op_impl!( quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: UnitQuaternionBase; + self: UnitQuaternion, rhs: UnitQuaternion; *self *= &rhs; ); // UnitQuaternion ÷= UnitQuaternion quaternion_op_impl!( DivAssign, div_assign; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: &'b UnitQuaternionBase; + self: UnitQuaternion, rhs: &'b UnitQuaternion; { let res = &*self / rhs; self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); @@ -691,14 +663,14 @@ quaternion_op_impl!( quaternion_op_impl!( DivAssign, div_assign; (U4, U1), (U4, U1); - self: UnitQuaternionBase, rhs: UnitQuaternionBase; + self: UnitQuaternion, rhs: UnitQuaternion; *self /= &rhs; ); -// UnitQuaternion ×= RotationBase +// UnitQuaternion ×= Rotation quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: &'b RotationBase => U3, U3; + self: UnitQuaternion, rhs: &'b Rotation => U3, U3; { let res = &*self * rhs; self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); @@ -708,14 +680,14 @@ quaternion_op_impl!( quaternion_op_impl!( MulAssign, mul_assign; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: RotationBase => U3, U3; + self: UnitQuaternion, rhs: Rotation => U3, U3; *self *= &rhs; ); -// UnitQuaternion ÷= RotationBase +// UnitQuaternion ÷= Rotation quaternion_op_impl!( DivAssign, div_assign; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: &'b RotationBase => U3, U3; + self: UnitQuaternion, rhs: &'b Rotation => U3, U3; { let res = &*self / rhs; self.as_mut_unchecked().coords.copy_from(&res.as_ref().coords); @@ -725,5 +697,5 @@ quaternion_op_impl!( quaternion_op_impl!( DivAssign, div_assign; (U4, U1), (U3, U3); - self: UnitQuaternionBase, rhs: RotationBase => U3, U3; + self: UnitQuaternion, rhs: Rotation => U3, U3; *self /= &rhs; ); diff --git a/src/geometry/reflection.rs b/src/geometry/reflection.rs new file mode 100644 index 00000000..72d87005 --- /dev/null +++ b/src/geometry/reflection.rs @@ -0,0 +1,72 @@ +use alga::general::Real; +use core::{DefaultAllocator, Scalar, Unit, Matrix, Vector}; +use core::constraint::{ShapeConstraint, SameNumberOfRows, DimEq, AreMultipliable}; +use core::allocator::Allocator; +use dimension::{Dim, DimName, U1}; +use storage::{Storage, StorageMut}; + +use geometry::Point; + +/// A reflection wrt. a plane. +pub struct Reflection> { + axis: Vector, + bias: N +} + +impl> Reflection { + /// Creates a new reflection wrt the plane orthogonal to the given axis and bias. + /// + /// The bias is the position of the plane on the axis. In particular, a bias equal to zero + /// represents a plane that passes through the origin. + pub fn new(axis: Unit>, bias: N) -> Reflection { + Reflection { axis: axis.unwrap(), bias: bias } + } + + /// Creates a new reflection wrt. the plane orthogonal to the given axis and that contains the + /// point `pt`. + pub fn new_containing_point(axis: Unit>, pt: &Point) -> Reflection + where D: DimName, + DefaultAllocator: Allocator { + let bias = pt.coords.dot(axis.as_ref()); + Self::new(axis, bias) + } + + /// The reflexion axis. + pub fn axis(&self) -> &Vector { + &self.axis + } + + // FIXME: naming convension: reflect_to, reflect_assign ? + /// Applies the reflection to the columns of `rhs`. + pub fn reflect(&self, rhs: &mut Matrix) + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + + for i in 0 .. rhs.ncols() { + // NOTE: we borrow the column twice here. First it is borrowed immutably for the + // dot product, and then mutably. Somehow, this allows significantly + // better optimizations of the dot product from the compiler. + let m_two: N = ::convert(-2.0f64); + let factor = (rhs.column(i).dot(&self.axis) - self.bias) * m_two; + rhs.column_mut(i).axpy(factor, &self.axis, N::one()); + } + } + + /// Applies the reflection to the rows of `rhs`. + pub fn reflect_rows(&self, + rhs: &mut Matrix, + work: &mut Vector) + where S2: StorageMut, + S3: StorageMut, + ShapeConstraint: DimEq + AreMultipliable { + + rhs.mul_to(&self.axis, work); + + if !self.bias.is_zero() { + work.add_scalar_mut(-self.bias); + } + + let m_two: N = ::convert(-2.0f64); + rhs.ger(m_two, &work, &self.axis, N::one()); + } +} diff --git a/src/geometry/rotation.rs b/src/geometry/rotation.rs index e9f06b69..b97597cd 100644 --- a/src/geometry/rotation.rs +++ b/src/geometry/rotation.rs @@ -1,62 +1,59 @@ use num::{Zero, One}; +use std::hash; use std::fmt; use approx::ApproxEq; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; + +#[cfg(feature = "serde-serialize")] +use core::storage::Owned; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; use alga::general::Real; -use core::{SquareMatrix, Scalar, OwnedSquareMatrix}; +use core::{DefaultAllocator, Scalar, MatrixN}; use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::{Storage, StorageMut}; use core::allocator::Allocator; -/// A rotation matrix with an owned storage. -pub type OwnedRotation = RotationBase>::Buffer>; - /// A rotation matrix. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] -pub struct RotationBase { - matrix: SquareMatrix +#[derive(Debug)] +pub struct Rotation + where DefaultAllocator: Allocator { + matrix: MatrixN } -#[cfg(feature = "serde-serialize")] -impl Serialize for RotationBase - where N: Scalar, - D: DimName, - SquareMatrix: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.matrix.serialize(serializer) +impl hash::Hash for Rotation + where DefaultAllocator: Allocator, + >::Buffer: hash::Hash { + fn hash(&self, state: &mut H) { + self.matrix.hash(state) } } -#[cfg(feature = "serde-serialize")] -impl<'de, N, D, S> Deserialize<'de> for RotationBase - where N: Scalar, - D: DimName, - SquareMatrix: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - SquareMatrix::deserialize(deserializer).map(|x| RotationBase { matrix: x }) +impl Copy for Rotation + where DefaultAllocator: Allocator, + >::Buffer: Copy { } + +impl Clone for Rotation + where DefaultAllocator: Allocator, + >::Buffer: Clone { + #[inline] + fn clone(&self) -> Self { + Rotation::from_matrix_unchecked(self.matrix.clone()) } } #[cfg(feature = "abomonation-serialize")] -impl Abomonation for RotationBase +impl Abomonation for RotationBase where N: Scalar, D: DimName, - SquareMatrix: Abomonation + MatrixN: Abomonation, + DefaultAllocator: Allocator { unsafe fn entomb(&self, writer: &mut Vec) { self.matrix.entomb(writer) @@ -71,12 +68,35 @@ impl Abomonation for RotationBase } } -impl> RotationBase - where N: Scalar, - S: Storage { +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for Rotation +where DefaultAllocator: Allocator, + Owned: serde::Serialize { + + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.matrix.serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: Scalar, D: DimName> serde::Deserialize<'a> for Rotation +where DefaultAllocator: Allocator, + Owned: serde::Deserialize<'a> { + + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let matrix = MatrixN::::deserialize(deserializer)?; + + Ok(Rotation::from_matrix_unchecked(matrix)) + } +} + +impl Rotation + where DefaultAllocator: Allocator { /// A reference to the underlying matrix representation of this rotation. #[inline] - pub fn matrix(&self) -> &SquareMatrix { + pub fn matrix(&self) -> &MatrixN { &self.matrix } @@ -86,57 +106,52 @@ impl> RotationBase /// non-square, non-inversible, or non-orthonormal. If one of those properties is broken, /// subsequent method calls may be UB. #[inline] - pub unsafe fn matrix_mut(&mut self) -> &mut SquareMatrix { + pub unsafe fn matrix_mut(&mut self) -> &mut MatrixN { &mut self.matrix } /// Unwraps the underlying matrix. #[inline] - pub fn unwrap(self) -> SquareMatrix { + pub fn unwrap(self) -> MatrixN { self.matrix } /// Converts this rotation into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + pub fn to_homogeneous(&self) -> MatrixN> where N: Zero + One, D: DimNameAdd, - S::Alloc: Allocator, DimNameSum> { - let mut res = OwnedSquareMatrix::::identity(); + DefaultAllocator: Allocator, DimNameSum> { + let mut res = MatrixN::>::identity(); res.fixed_slice_mut::(0, 0).copy_from(&self.matrix); res } -} -impl> RotationBase { /// Creates a new rotation from the given square matrix. /// /// The matrix squareness is checked but not its orthonormality. #[inline] - pub fn from_matrix_unchecked(matrix: SquareMatrix) -> RotationBase { + pub fn from_matrix_unchecked(matrix: MatrixN) -> Rotation { assert!(matrix.is_square(), "Unable to create a rotation from a non-square matrix."); - RotationBase { + Rotation { matrix: matrix } } /// Transposes `self`. #[inline] - pub fn transpose(&self) -> OwnedRotation { - RotationBase::from_matrix_unchecked(self.matrix.transpose()) + pub fn transpose(&self) -> Rotation { + Rotation::from_matrix_unchecked(self.matrix.transpose()) } /// Inverts `self`. #[inline] - pub fn inverse(&self) -> OwnedRotation { + pub fn inverse(&self) -> Rotation { self.transpose() } -} - -impl> RotationBase { /// Transposes `self` in-place. #[inline] pub fn transpose_mut(&mut self) { @@ -150,18 +165,20 @@ impl> RotationBase { } } -impl> Eq for RotationBase { } +impl Eq for Rotation + where DefaultAllocator: Allocator { } -impl> PartialEq for RotationBase { +impl PartialEq for Rotation + where DefaultAllocator: Allocator { #[inline] - fn eq(&self, right: &RotationBase) -> bool { + fn eq(&self, right: &Rotation) -> bool { self.matrix == right.matrix } } -impl ApproxEq for RotationBase +impl ApproxEq for Rotation where N: Scalar + ApproxEq, - S: Storage, + DefaultAllocator: Allocator, N::Epsilon: Copy { type Epsilon = N::Epsilon; @@ -196,14 +213,14 @@ impl ApproxEq for RotationBase * Display * */ -impl fmt::Display for RotationBase +impl fmt::Display for Rotation where N: Real + fmt::Display, - S: Storage, - S::Alloc: Allocator { + DefaultAllocator: Allocator + + Allocator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let precision = f.precision().unwrap_or(3); - try!(writeln!(f, "RotationBase matrix {{")); + try!(writeln!(f, "Rotation matrix {{")); try!(write!(f, "{:.*}", precision, self.matrix)); writeln!(f, "}}") } diff --git a/src/geometry/rotation_alga.rs b/src/geometry/rotation_alga.rs index de226095..b982aef2 100644 --- a/src/geometry/rotation_alga.rs +++ b/src/geometry/rotation_alga.rs @@ -1,14 +1,13 @@ use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; -use alga::linear::{Transformation, Similarity, AffineTransformation, Isometry, DirectIsometry, - OrthogonalTransformation, ProjectiveTransformation, Rotation}; +use alga::linear::{self, Transformation, Similarity, AffineTransformation, Isometry, + DirectIsometry, OrthogonalTransformation, ProjectiveTransformation}; -use core::ColumnVector; -use core::dimension::{DimName, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::{DefaultAllocator, VectorN}; +use core::dimension::DimName; +use core::allocator::Allocator; -use geometry::{RotationBase, PointBase}; +use geometry::{Rotation, Point}; @@ -17,20 +16,16 @@ use geometry::{RotationBase, PointBase}; * Algebraic structures. * */ -impl Identity for RotationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Identity for Rotation + where DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::identity() } } -impl Inverse for RotationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Inverse for Rotation + where DefaultAllocator: Allocator { #[inline] fn inverse(&self) -> Self { self.transpose() @@ -42,10 +37,8 @@ impl Inverse for RotationBase } } -impl AbstractMagma for RotationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Rotation + where DefaultAllocator: Allocator { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs @@ -54,10 +47,8 @@ impl AbstractMagma for RotationBase macro_rules! impl_multiplicative_structures( ($($marker: ident<$operator: ident>),* $(,)*) => {$( - impl $marker<$operator> for RotationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for Rotation + where DefaultAllocator: Allocator { } )*} ); @@ -74,46 +65,37 @@ impl_multiplicative_structures!( * Transformation groups. * */ -impl Transformation> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl Transformation> for Rotation + where DefaultAllocator: Allocator + + Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point) -> Point { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &VectorN) -> VectorN { self * v } } -impl ProjectiveTransformation> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for Rotation + where DefaultAllocator: Allocator + + Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { - PointBase::from_coordinates(self.inverse_transform_vector(&pt.coords)) + fn inverse_transform_point(&self, pt: &Point) -> Point { + Point::from_coordinates(self.inverse_transform_vector(&pt.coords)) } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.matrix().tr_mul(v) } } -impl AffineTransformation> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl AffineTransformation> for Rotation + where DefaultAllocator: Allocator + + Allocator { type Rotation = Self; type NonUniformScaling = Id; type Translation = Id; @@ -155,12 +137,9 @@ impl AffineTransformation> for Rotati } -impl Similarity> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl Similarity> for Rotation + where DefaultAllocator: Allocator + + Allocator { type Scaling = Id; #[inline] @@ -181,12 +160,9 @@ impl Similarity> for RotationBase {$( - impl $Trait> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { } + impl $Trait> for Rotation + where DefaultAllocator: Allocator + + Allocator { } )*} ); @@ -194,12 +170,9 @@ marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); /// Subgroups of the n-dimensional rotation group `SO(n)`. -impl Rotation> for RotationBase - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { +impl linear::Rotation> for Rotation + where DefaultAllocator: Allocator + + Allocator { #[inline] fn powf(&self, _: N) -> Option { // XXX: Add the general case. @@ -208,14 +181,14 @@ impl Rotation> for RotationBase, _: &ColumnVector) -> Option { + fn rotation_between(_: &VectorN, _: &VectorN) -> Option { // XXX: Add the general case. // XXX: Use specialization for 2D and 3D. unimplemented!() } #[inline] - fn scaled_rotation_between(_: &ColumnVector, _: &ColumnVector, _: N) -> Option { + fn scaled_rotation_between(_: &VectorN, _: &VectorN, _: N) -> Option { // XXX: Add the general case. // XXX: Use specialization for 2D and 3D. unimplemented!() @@ -223,7 +196,7 @@ impl Rotation> for RotationBase Matrix for RotationBase { +impl Matrix for Rotation { type Field = N; type Row = Matrix; type Column = Matrix; @@ -261,11 +234,11 @@ impl Matrix for RotationBase { #[inline] fn transpose(&self) -> Self::Transpose { - RotationBase::from_matrix_unchecked(self.submatrix.transpose()) + Rotation::from_matrix_unchecked(self.submatrix.transpose()) } } -impl SquareMatrix for RotationBase { +impl SquareMatrix for Rotation { type Vector = Matrix; #[inline] @@ -295,7 +268,7 @@ impl SquareMatrix for RotationBase { } } -impl InversibleSquareMatrix for RotationBase { } +impl InversibleSquareMatrix for Rotation { } */ diff --git a/src/geometry/rotation_alias.rs b/src/geometry/rotation_alias.rs index 50523aa7..7e8edbaa 100644 --- a/src/geometry/rotation_alias.rs +++ b/src/geometry/rotation_alias.rs @@ -1,10 +1,6 @@ -use core::MatrixArray; use core::dimension::{U2, U3}; -use geometry::RotationBase; - -/// A D-dimensional rotation matrix. -pub type Rotation = RotationBase>; +use geometry::Rotation; /// A 2-dimensional rotation matrix. pub type Rotation2 = Rotation; diff --git a/src/geometry/rotation_construction.rs b/src/geometry/rotation_construction.rs index 50473d33..4eb526e5 100644 --- a/src/geometry/rotation_construction.rs +++ b/src/geometry/rotation_construction.rs @@ -2,28 +2,25 @@ use num::{Zero, One}; use alga::general::{ClosedAdd, ClosedMul}; -use core::{SquareMatrix, Scalar}; +use core::{DefaultAllocator, MatrixN, Scalar}; use core::dimension::DimName; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::RotationBase; +use geometry::Rotation; -impl RotationBase +impl Rotation where N: Scalar + Zero + One, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { /// Creates a new square identity rotation of the given `dimension`. #[inline] - pub fn identity() -> RotationBase { - Self::from_matrix_unchecked(SquareMatrix::::identity()) + pub fn identity() -> Rotation { + Self::from_matrix_unchecked(MatrixN::::identity()) } } -impl One for RotationBase +impl One for Rotation where N: Scalar + Zero + One + ClosedAdd + ClosedMul, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + DefaultAllocator: Allocator { #[inline] fn one() -> Self { Self::identity() diff --git a/src/geometry/rotation_conversion.rs b/src/geometry/rotation_conversion.rs index fc1c8be8..df839062 100644 --- a/src/geometry/rotation_conversion.rs +++ b/src/geometry/rotation_conversion.rs @@ -3,178 +3,186 @@ use num::Zero; use alga::general::{Real, SubsetOf, SupersetOf}; use alga::linear::Rotation as AlgaRotation; -use core::{Matrix, SquareMatrix}; -use core::dimension::{DimName, DimNameSum, DimNameAdd, U1, U3, U4}; -use core::storage::OwnedStorage; -use core::allocator::{OwnedAllocator, Allocator}; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, DimMin, U1}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, RotationBase, UnitQuaternionBase, OwnedUnitQuaternionBase, IsometryBase, - SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; +use geometry::{Point, Translation, Rotation, UnitQuaternion, UnitComplex, Isometry, + Similarity, Transform, SuperTCategoryOf, TAffine, + Rotation2, Rotation3}; /* * This file provides the following conversions: * ============================================= * - * RotationBase -> RotationBase + * Rotation -> Rotation * Rotation3 -> UnitQuaternion - * RotationBase -> IsometryBase - * RotationBase -> SimilarityBase - * RotationBase -> TransformBase - * RotationBase -> Matrix (homogeneous) + * Rotation2 -> UnitComplex + * Rotation -> Isometry + * Rotation -> Similarity + * Rotation -> Transform + * Rotation -> Matrix (homogeneous) */ -impl SubsetOf> for RotationBase +impl SubsetOf> for Rotation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> RotationBase { - RotationBase::from_matrix_unchecked(self.matrix().to_superset()) + fn to_superset(&self) -> Rotation { + Rotation::from_matrix_unchecked(self.matrix().to_superset()) } #[inline] - fn is_in_subset(rot: &RotationBase) -> bool { - ::is_convertible::<_, Matrix>(rot.matrix()) + fn is_in_subset(rot: &Rotation) -> bool { + ::is_convertible::<_, MatrixN>(rot.matrix()) } #[inline] - unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { - RotationBase::from_matrix_unchecked(rot.matrix().to_subset_unchecked()) + unsafe fn from_superset_unchecked(rot: &Rotation) -> Self { + Rotation::from_matrix_unchecked(rot.matrix().to_subset_unchecked()) } } -impl SubsetOf> for RotationBase +impl SubsetOf> for Rotation3 where N1: Real, - N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator + - Allocator + - Allocator, - SB::Alloc: OwnedAllocator + - Allocator { + N2: Real + SupersetOf { #[inline] - fn to_superset(&self) -> UnitQuaternionBase { - let q = OwnedUnitQuaternionBase::::from_rotation_matrix(self); + fn to_superset(&self) -> UnitQuaternion { + let q = UnitQuaternion::::from_rotation_matrix(self); q.to_superset() } #[inline] - fn is_in_subset(q: &UnitQuaternionBase) -> bool { - ::is_convertible::<_, OwnedUnitQuaternionBase>(q) + fn is_in_subset(q: &UnitQuaternion) -> bool { + ::is_convertible::<_, UnitQuaternion>(q) } #[inline] - unsafe fn from_superset_unchecked(q: &UnitQuaternionBase) -> Self { - let q: OwnedUnitQuaternionBase = ::convert_ref_unchecked(q); + unsafe fn from_superset_unchecked(q: &UnitQuaternion) -> Self { + let q: UnitQuaternion = ::convert_ref_unchecked(q); + q.to_rotation_matrix() + } +} + +impl SubsetOf> for Rotation2 + where N1: Real, + N2: Real + SupersetOf { + #[inline] + fn to_superset(&self) -> UnitComplex { + let q = UnitComplex::::from_rotation_matrix(self); + q.to_superset() + } + + #[inline] + fn is_in_subset(q: &UnitComplex) -> bool { + ::is_convertible::<_, UnitComplex>(q) + } + + #[inline] + unsafe fn from_superset_unchecked(q: &UnitComplex) -> Self { + let q: UnitComplex = ::convert_ref_unchecked(q); q.to_rotation_matrix() } } -impl SubsetOf> for RotationBase + +impl SubsetOf> for Rotation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> IsometryBase { - IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + fn to_superset(&self) -> Isometry { + Isometry::from_parts(Translation::identity(), ::convert_ref(self)) } #[inline] - fn is_in_subset(iso: &IsometryBase) -> bool { + fn is_in_subset(iso: &Isometry) -> bool { iso.translation.vector.is_zero() } #[inline] - unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + unsafe fn from_superset_unchecked(iso: &Isometry) -> Self { ::convert_ref_unchecked(&iso.rotation) } } -impl SubsetOf> for RotationBase +impl SubsetOf> for Rotation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_parts(TranslationBase::identity(), ::convert_ref(self), N2::one()) + fn to_superset(&self) -> Similarity { + Similarity::from_parts(Translation::identity(), ::convert_ref(self), N2::one()) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { + fn is_in_subset(sim: &Similarity) -> bool { sim.isometry.translation.vector.is_zero() && sim.scaling() == N2::one() } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { ::convert_ref_unchecked(&sim.isometry.rotation) } } -impl SubsetOf> for RotationBase +impl SubsetOf> for Rotation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, C: SuperTCategoryOf, - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator, DimNameSum>, - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + - Allocator { + D: DimNameAdd + + DimMin, // needed by .is_special_orthogonal() + DefaultAllocator: Allocator + + Allocator + + Allocator, DimNameSum> + + Allocator, DimNameSum> + + Allocator<(usize, usize), D> { // needed by .is_special_orthogonal() #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf, SB>> for RotationBase +impl SubsetOf>> for Rotation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator, DimNameSum>, - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + - Allocator { + D: DimNameAdd + + DimMin, // needed by .is_special_orthogonal() + DefaultAllocator: Allocator + + Allocator + + Allocator, DimNameSum> + + Allocator, DimNameSum> + + Allocator<(usize, usize), D> { // needed by .is_special_orthogonal() #[inline] - fn to_superset(&self) -> SquareMatrix, SB> { + fn to_superset(&self) -> MatrixN> { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + fn is_in_subset(m: &MatrixN>) -> bool { let rot = m.fixed_slice::(0, 0); let bottom = m.fixed_slice::(D::dim(), 0); @@ -188,7 +196,7 @@ impl SubsetOf, SB>> for Ro } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + unsafe fn from_superset_unchecked(m: &MatrixN>) -> Self { let r = m.fixed_slice::(0, 0); Self::from_matrix_unchecked(::convert_unchecked(r.into_owned())) } diff --git a/src/geometry/rotation_ops.rs b/src/geometry/rotation_ops.rs index ca9b907b..53236b64 100644 --- a/src/geometry/rotation_ops.rs +++ b/src/geometry/rotation_ops.rs @@ -4,33 +4,34 @@ * * Index<(usize, usize)> * - * RotationBase × RotationBase - * RotationBase ÷ RotationBase - * RotationBase × Matrix - * Matrix × RotationBase - * Matrix ÷ RotationBase - * RotationBase × PointBase + * Rotation × Rotation + * Rotation ÷ Rotation + * Rotation × Matrix + * Matrix × Rotation + * Matrix ÷ Rotation + * Rotation × Point * * - * RotationBase ×= RotationBase - * Matrix ×= RotationBase + * Rotation ×= Rotation + * Matrix ×= Rotation */ use std::ops::{Mul, MulAssign, Div, DivAssign, Index}; -use num::Zero; +use num::{Zero, One}; use alga::general::{ClosedMul, ClosedAdd}; -use core::{Scalar, Matrix, MatrixMul}; +use core::{DefaultAllocator, Scalar, Matrix, MatrixMN}; use core::dimension::{Dim, DimName, U1}; use core::constraint::{ShapeConstraint, AreMultipliable}; -use core::storage::{OwnedStorage, Storage}; -use core::allocator::{OwnedAllocator, Allocator}; +use core::storage::Storage; +use core::allocator::Allocator; -use geometry::{PointBase, PointMul, RotationBase, OwnedRotation}; +use geometry::{Point, Rotation}; -impl> Index<(usize, usize)> for RotationBase { +impl Index<(usize, usize)> for Rotation + where DefaultAllocator: Allocator { type Output = N; #[inline] @@ -39,62 +40,62 @@ impl> Index<(usize, usize)> for Rotat } } -// RotationBase × RotationBase +// Rotation × Rotation md_impl_all!( Mul, mul; (D, D), (D, D) for D: DimName; - self: RotationBase, right: RotationBase, Output = OwnedRotation; - [val val] => RotationBase::from_matrix_unchecked(self.unwrap() * right.unwrap()); - [ref val] => RotationBase::from_matrix_unchecked(self.matrix() * right.unwrap()); - [val ref] => RotationBase::from_matrix_unchecked(self.unwrap() * right.matrix()); - [ref ref] => RotationBase::from_matrix_unchecked(self.matrix() * right.matrix()); + self: Rotation, right: Rotation, Output = Rotation; + [val val] => Rotation::from_matrix_unchecked(self.unwrap() * right.unwrap()); + [ref val] => Rotation::from_matrix_unchecked(self.matrix() * right.unwrap()); + [val ref] => Rotation::from_matrix_unchecked(self.unwrap() * right.matrix()); + [ref ref] => Rotation::from_matrix_unchecked(self.matrix() * right.matrix()); ); -// RotationBase ÷ RotationBase +// Rotation ÷ Rotation // FIXME: instead of calling inverse explicitely, could we just add a `mul_tr` or `mul_inv` method? md_impl_all!( Div, div; (D, D), (D, D) for D: DimName; - self: RotationBase, right: RotationBase, Output = OwnedRotation; + 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(); ); -// RotationBase × Matrix +// Rotation × Matrix md_impl_all!( Mul, mul; - (D1, D1), (R2, C2) for D1: DimName, R2: Dim, C2: Dim - where SA::Alloc: Allocator + (D1, D1), (R2, C2) for D1: DimName, R2: Dim, C2: Dim, SB: Storage + where DefaultAllocator: Allocator where ShapeConstraint: AreMultipliable; - self: RotationBase, right: Matrix, Output = MatrixMul; + self: Rotation, right: Matrix, Output = MatrixMN; [val val] => self.unwrap() * right; [ref val] => self.matrix() * right; [val ref] => self.unwrap() * right; [ref ref] => self.matrix() * right; ); -// Matrix × RotationBase +// Matrix × Rotation md_impl_all!( Mul, mul; - (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName - where SA::Alloc: Allocator - where ShapeConstraint: AreMultipliable; - self: Matrix, right: RotationBase, Output = MatrixMul; + (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName, SA: Storage + where DefaultAllocator: Allocator + where ShapeConstraint: AreMultipliable; + self: Matrix, right: Rotation, Output = MatrixMN; [val val] => self * right.unwrap(); [ref val] => self * right.unwrap(); [val ref] => self * right.matrix(); [ref ref] => self * right.matrix(); ); -// Matrix ÷ RotationBase +// Matrix ÷ Rotation md_impl_all!( Div, div; - (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName - where SA::Alloc: Allocator + (R1, C1), (D2, D2) for R1: Dim, C1: Dim, D2: DimName, SA: Storage + where DefaultAllocator: Allocator where ShapeConstraint: AreMultipliable; - self: Matrix, right: RotationBase, Output = MatrixMul; + self: Matrix, right: Rotation, Output = MatrixMN; [val val] => self * right.inverse(); [ref val] => self * right.inverse(); [val ref] => self * right.inverse(); @@ -102,15 +103,15 @@ md_impl_all!( ); -// RotationBase × PointBase +// Rotation × Point // FIXME: we don't handle properly non-zero origins here. Do we want this to be the intended // behavior? md_impl_all!( Mul, mul; (D, D), (D, U1) for D: DimName - where SA::Alloc: Allocator - where ShapeConstraint: AreMultipliable; - self: RotationBase, right: PointBase, Output = PointMul; + where DefaultAllocator: Allocator + where ShapeConstraint: AreMultipliable; + self: Rotation, right: Point, Output = Point; [val val] => self.unwrap() * right; [ref val] => self.matrix() * right; [val ref] => self.unwrap() * right; @@ -118,13 +119,13 @@ md_impl_all!( ); -// RotationBase ×= RotationBase +// Rotation ×= Rotation // FIXME: try not to call `inverse()` explicitly. md_assign_impl_all!( MulAssign, mul_assign; (D, D), (D, D) for D: DimName; - self: RotationBase, right: RotationBase; + self: Rotation, right: Rotation; [val] => unsafe { self.matrix_mut().mul_assign(right.unwrap()) }; [ref] => unsafe { self.matrix_mut().mul_assign(right.matrix()) }; ); @@ -133,12 +134,12 @@ md_assign_impl_all!( md_assign_impl_all!( DivAssign, div_assign; (D, D), (D, D) for D: DimName; - self: RotationBase, right: RotationBase; + self: Rotation, right: Rotation; [val] => unsafe { self.matrix_mut().mul_assign(right.inverse().unwrap()) }; [ref] => unsafe { self.matrix_mut().mul_assign(right.inverse().matrix()) }; ); -// Matrix *= RotationBase +// Matrix *= Rotation // FIXME: try not to call `inverse()` explicitly. // FIXME: this shares the same limitations as for the current impl. of MulAssign for matrices. // (In particular the number of matrix column must be equal to the number of rotation columns, @@ -147,7 +148,7 @@ md_assign_impl_all!( md_assign_impl_all!( MulAssign, mul_assign; (R1, C1), (C1, C1) for R1: DimName, C1: DimName; - self: Matrix, right: RotationBase; + self: MatrixMN, right: Rotation; [val] => self.mul_assign(right.unwrap()); [ref] => self.mul_assign(right.matrix()); ); @@ -156,7 +157,7 @@ md_assign_impl_all!( md_assign_impl_all!( DivAssign, div_assign; (R1, C1), (C1, C1) for R1: DimName, C1: DimName; - self: Matrix, right: RotationBase; + self: MatrixMN, right: Rotation; [val] => self.mul_assign(right.inverse().unwrap()); [ref] => self.mul_assign(right.inverse().matrix()); ); diff --git a/src/geometry/rotation_specialization.rs b/src/geometry/rotation_specialization.rs index 9c0f13bb..abe3066f 100644 --- a/src/geometry/rotation_specialization.rs +++ b/src/geometry/rotation_specialization.rs @@ -1,39 +1,37 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; use std::ops::Neg; use num::Zero; use rand::{Rand, Rng}; use alga::general::Real; -use core::{Unit, ColumnVector, SquareMatrix, OwnedSquareMatrix, OwnedColumnVector, Vector3}; +use core::{Unit, Vector, MatrixN, VectorN, Vector3}; use core::dimension::{U1, U2, U3}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; +use core::storage::Storage; -use geometry::{RotationBase, OwnedRotation, UnitComplex}; +use geometry::{UnitComplex, Rotation2, Rotation3}; /* * - * 2D RotationBase matrix. + * 2D Rotation matrix. * */ -impl RotationBase -where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rotation2 { /// Builds a 2 dimensional rotation matrix from an angle in radian. pub fn new(angle: N) -> Self { let (sia, coa) = angle.sin_cos(); - Self::from_matrix_unchecked(SquareMatrix::::new(coa, -sia, sia, coa)) + Self::from_matrix_unchecked(MatrixN::::new(coa, -sia, sia, coa)) } /// Builds a 2 dimensional rotation matrix from an angle in radian wrapped in a 1-dimensional vector. /// /// Equivalent to `Self::new(axisangle[0])`. #[inline] - pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + pub fn from_scaled_axis>(axisangle: Vector) -> Self { Self::new(axisangle[0]) } @@ -41,25 +39,23 @@ where N: Real, /// /// This is the rotation `R` such that `(R * a).angle(b) == 0 && (R * a).dot(b).is_positive()`. #[inline] - pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Self - where SB: Storage, - SC: Storage { + pub fn rotation_between(a: &Vector, b: &Vector) -> Self + where SB: Storage, + SC: Storage { ::convert(UnitComplex::rotation_between(a, b).to_rotation_matrix()) } /// The smallest rotation needed to make `a` and `b` collinear and point toward the same /// direction, raised to the power `s`. #[inline] - pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Self - where SB: Storage, - SC: Storage { + pub fn scaled_rotation_between(a: &Vector, b: &Vector, s: N) -> Self + where SB: Storage, + SC: Storage { ::convert(UnitComplex::scaled_rotation_between(a, b, s).to_rotation_matrix()) } } -impl RotationBase -where N: Real, - S: Storage { +impl Rotation2 { /// The rotation angle. #[inline] pub fn angle(&self) -> N { @@ -68,7 +64,7 @@ where N: Real, /// The rotation angle needed to make `self` and `other` coincide. #[inline] - pub fn angle_to>(&self, other: &RotationBase) -> N { + pub fn angle_to(&self, other: &Rotation2) -> N { self.rotation_to(other).angle() } @@ -76,30 +72,25 @@ where N: Real, /// /// The result is such that: `self.rotation_to(other) * self == other`. #[inline] - pub fn rotation_to(&self, other: &RotationBase) -> OwnedRotation - where SB: Storage { + pub fn rotation_to(&self, other: &Rotation2) -> Rotation2 { other * self.inverse() } /// Raise the quaternion to a given floating power, i.e., returns the rotation with the angle /// of `self` multiplied by `n`. #[inline] - pub fn powf(&self, n: N) -> OwnedRotation { - OwnedRotation::<_, _, S::Alloc>::new(self.angle() * n) + pub fn powf(&self, n: N) -> Rotation2 { + Self::new(self.angle() * n) } /// The rotation angle returned as a 1-dimensional vector. #[inline] - pub fn scaled_axis(&self) -> OwnedColumnVector - where S::Alloc: Allocator { - ColumnVector::<_, U1, _>::new(self.angle()) + pub fn scaled_axis(&self) -> VectorN { + Vector::<_, U1, _>::new(self.angle()) } } -impl Rand for RotationBase -where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Rotation2 { #[inline] fn rand(rng: &mut R) -> Self { Self::new(rng.gen()) @@ -107,10 +98,8 @@ where N: Real + Rand, } #[cfg(feature="arbitrary")] -impl Arbitrary for RotationBase -where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Rotation2 +where Owned: Send { #[inline] fn arbitrary(g: &mut G) -> Self { Self::new(N::arbitrary(g)) @@ -120,32 +109,29 @@ where N: Real + Arbitrary, /* * - * 3D RotationBase matrix. + * 3D Rotation matrix. * */ -impl RotationBase -where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { - +impl Rotation3 { /// Builds a 3 dimensional rotation matrix from an axis and an angle. /// /// # Arguments /// * `axisangle` - A vector representing the rotation. Its magnitude is the amount of rotation /// in radian. Its direction is the axis of rotation. - pub fn new>(axisangle: ColumnVector) -> Self { - let (axis, angle) = Unit::new_and_get(axisangle.into_owned()); + pub fn new>(axisangle: Vector) -> Self { + let axisangle = axisangle.into_owned(); + let (axis, angle) = Unit::new_and_get(axisangle); Self::from_axis_angle(&axis, angle) } /// Builds a 3D rotation matrix from an axis scaled by the rotation angle. - pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + pub fn from_scaled_axis>(axisangle: Vector) -> Self { Self::new(axisangle) } /// Builds a 3D rotation matrix from an axis and a rotation angle. - pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self - where SB: Storage { + pub fn from_axis_angle(axis: &Unit>, angle: N) -> Self + where SB: Storage { if angle.is_zero() { Self::identity() } @@ -160,7 +146,7 @@ where N: Real, let one_m_cos = N::one() - cos; Self::from_matrix_unchecked( - SquareMatrix::::new( + MatrixN::::new( (sqx + (N::one() - sqx) * cos), (ux * uy * one_m_cos - uz * sin), (ux * uz * one_m_cos + uy * sin), @@ -184,7 +170,7 @@ where N: Real, let (sy, cy) = yaw.sin_cos(); Self::from_matrix_unchecked( - SquareMatrix::::new( + MatrixN::::new( cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, -sp, cp * sr, cp * cr) @@ -202,14 +188,14 @@ where N: Real, /// collinear /// to `dir`. Non-collinearity is not checked. #[inline] - pub fn new_observer_frame(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage { + pub fn new_observer_frame(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { let zaxis = dir.normalize(); let xaxis = up.cross(&zaxis).normalize(); let yaxis = zaxis.cross(&xaxis).normalize(); - Self::from_matrix_unchecked(SquareMatrix::::new( + Self::from_matrix_unchecked(MatrixN::::new( xaxis.x, yaxis.x, zaxis.x, xaxis.y, yaxis.y, zaxis.y, xaxis.z, yaxis.z, zaxis.z)) @@ -227,9 +213,9 @@ where N: Real, /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_rh(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage { + pub fn look_at_rh(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { Self::new_observer_frame(&dir.neg(), up).inverse() } @@ -244,9 +230,9 @@ where N: Real, /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_lh(dir: &ColumnVector, up: &ColumnVector) -> Self - where SB: Storage, - SC: Storage { + pub fn look_at_lh(dir: &Vector, up: &Vector) -> Self + where SB: Storage, + SC: Storage { Self::new_observer_frame(dir, up).inverse() } @@ -254,20 +240,20 @@ where N: Real, /// /// This is the rotation `R` such that `(R * a).angle(b) == 0 && (R * a).dot(b).is_positive()`. #[inline] - pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option - where SB: Storage, - SC: Storage { + pub fn rotation_between(a: &Vector, b: &Vector) -> Option + where SB: Storage, + SC: Storage { Self::scaled_rotation_between(a, b, N::one()) } /// The smallest rotation needed to make `a` and `b` collinear and point toward the same /// direction, raised to the power `s`. #[inline] - pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, n: N) + pub fn scaled_rotation_between(a: &Vector, b: &Vector, n: N) -> Option - where SB: Storage, - SC: Storage { - // FIXME: code duplication with RotationBase. + where SB: Storage, + SC: Storage { + // FIXME: code duplication with Rotation. if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { let c = na.cross(&nb); @@ -287,26 +273,17 @@ where N: Real, Some(Self::identity()) } -} -impl RotationBase -where N: Real, - S: Storage { /// The rotation angle. #[inline] pub fn angle(&self) -> N { ((self.matrix()[(0, 0)] + self.matrix()[(1, 1)] + self.matrix()[(2, 2)] - N::one()) / ::convert(2.0)).acos() } -} -impl RotationBase -where N: Real, - S: Storage, - S::Alloc: Allocator { /// The rotation axis. Returns `None` if the rotation angle is zero or PI. #[inline] - pub fn axis(&self) -> Option>> { - let axis = OwnedColumnVector::::new( + pub fn axis(&self) -> Option>> { + let axis = VectorN::::new( self.matrix()[(2, 1)] - self.matrix()[(1, 2)], self.matrix()[(0, 2)] - self.matrix()[(2, 0)], self.matrix()[(1, 0)] - self.matrix()[(0, 1)]); @@ -316,18 +293,18 @@ where N: Real, /// The rotation axis multiplied by the rotation angle. #[inline] - pub fn scaled_axis(&self) -> OwnedColumnVector { + pub fn scaled_axis(&self) -> Vector3 { if let Some(axis) = self.axis() { axis.unwrap() * self.angle() } else { - ColumnVector::zero() + Vector::zero() } } /// The rotation angle needed to make `self` and `other` coincide. #[inline] - pub fn angle_to>(&self, other: &RotationBase) -> N { + pub fn angle_to(&self, other: &Rotation3) -> N { self.rotation_to(other).angle() } @@ -335,47 +312,40 @@ where N: Real, /// /// The result is such that: `self.rotation_to(other) * self == other`. #[inline] - pub fn rotation_to(&self, other: &RotationBase) -> OwnedRotation - where SB: Storage { + pub fn rotation_to(&self, other: &Rotation3) -> Rotation3 { other * self.inverse() } /// Raise the quaternion to a given floating power, i.e., returns the rotation with the same /// axis as `self` and an angle equal to `self.angle()` multiplied by `n`. #[inline] - pub fn powf(&self, n: N) -> OwnedRotation { + pub fn powf(&self, n: N) -> Rotation3 { if let Some(axis) = self.axis() { - OwnedRotation::<_, _, S::Alloc>::from_axis_angle(&axis, self.angle() * n) + Self::from_axis_angle(&axis, self.angle() * n) } else if self.matrix()[(0, 0)] < N::zero() { - let minus_id = OwnedSquareMatrix::::from_diagonal_element(-N::one()); - OwnedRotation::<_, _, S::Alloc>::from_matrix_unchecked(minus_id) + let minus_id = MatrixN::::from_diagonal_element(-N::one()); + Self::from_matrix_unchecked(minus_id) } else { - OwnedRotation::<_, _, S::Alloc>::identity() + Self::identity() } } } -impl Rand for RotationBase -where N: Real + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator { +impl Rand for Rotation3 { #[inline] fn rand(rng: &mut R) -> Self { - Self::new(Vector3::rand(rng)) + Self::new(VectorN::rand(rng)) } } #[cfg(feature="arbitrary")] -impl Arbitrary for RotationBase -where N: Real + Arbitrary, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator + - Allocator { +impl Arbitrary for Rotation3 +where Owned: Send, + Owned: Send { #[inline] fn arbitrary(g: &mut G) -> Self { - Self::new(Vector3::arbitrary(g)) + Self::new(VectorN::arbitrary(g)) } } diff --git a/src/geometry/similarity.rs b/src/geometry/similarity.rs index bb7fd4f4..75ab6bbc 100644 --- a/src/geometry/similarity.rs +++ b/src/geometry/similarity.rs @@ -1,37 +1,51 @@ use std::fmt; +use std::hash; use approx::ApproxEq; -use alga::general::{ClosedMul, Real, SubsetOf}; -use alga::linear::Rotation; - -use core::{Scalar, OwnedSquareMatrix}; -use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; -use geometry::{PointBase, TranslationBase, IsometryBase}; +#[cfg(feature = "serde-serialize")] +use serde; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; +use alga::general::{Real, SubsetOf}; +use alga::linear::Rotation; + +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; +use core::storage::Owned; +use core::allocator::Allocator; +use geometry::{Point, Translation, Isometry}; + -/// A similarity that uses a data storage deduced from the allocator `A`. -pub type OwnedSimilarityBase = - SimilarityBase>::Buffer, R>; /// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] +#[derive(Debug)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] -pub struct SimilarityBase { +#[cfg_attr(feature = "serde-serialize", + serde(bound( + serialize = "N: serde::Serialize, + R: serde::Serialize, + DefaultAllocator: Allocator, + Owned: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound( + deserialize = "N: serde::Deserialize<'de>, + R: serde::Deserialize<'de>, + DefaultAllocator: Allocator, + Owned: serde::Deserialize<'de>")))] +pub struct Similarity + where DefaultAllocator: Allocator { /// The part of this similarity that does not include the scaling factor. - pub isometry: IsometryBase, + pub isometry: Isometry, scaling: N } - #[cfg(feature = "abomonation-serialize")] -impl Abomonation for SimilarityBase - where IsometryBase: Abomonation +impl Abomonation for SimilarityBase + where IsometryBase: Abomonation, + DefaultAllocator: Allocator { unsafe fn entomb(&self, writer: &mut Vec) { self.isometry.entomb(writer) @@ -46,23 +60,43 @@ impl Abomonation for SimilarityBase } } -impl SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl hash::Hash for Similarity + where DefaultAllocator: Allocator, + Owned: hash::Hash { + fn hash(&self, state: &mut H) { + self.isometry.hash(state); + self.scaling.hash(state); + } +} + +impl> + Copy> Copy for Similarity + where DefaultAllocator: Allocator, + Owned: Copy { +} + +impl> + Clone> Clone for Similarity + where DefaultAllocator: Allocator { + #[inline] + fn clone(&self) -> Self { + Similarity::from_isometry(self.isometry.clone(), self.scaling) + } +} + +impl Similarity + where R: Rotation>, + DefaultAllocator: Allocator { /// Creates a new similarity from its rotational and translational parts. #[inline] - pub fn from_parts(translation: TranslationBase, rotation: R, scaling: N) -> SimilarityBase { - SimilarityBase::from_isometry(IsometryBase::from_parts(translation, rotation), scaling) + pub fn from_parts(translation: Translation, rotation: R, scaling: N) -> Similarity { + Similarity::from_isometry(Isometry::from_parts(translation, rotation), scaling) } /// Creates a new similarity from its rotational and translational parts. #[inline] - pub fn from_isometry(isometry: IsometryBase, scaling: N) -> SimilarityBase { + pub fn from_isometry(isometry: Isometry, scaling: N) -> Similarity { assert!(!relative_eq!(scaling, N::zero()), "The scaling factor must not be zero."); - SimilarityBase { + Similarity { isometry: isometry, scaling: scaling } @@ -70,13 +104,13 @@ impl SimilarityBase /// Creates a new similarity that applies only a scaling factor. #[inline] - pub fn from_scaling(scaling: N) -> SimilarityBase { - Self::from_isometry(IsometryBase::identity(), scaling) + pub fn from_scaling(scaling: N) -> Similarity { + Self::from_isometry(Isometry::identity(), scaling) } /// Inverts `self`. #[inline] - pub fn inverse(&self) -> SimilarityBase { + pub fn inverse(&self) -> Similarity { let mut res = self.clone(); res.inverse_mut(); res @@ -98,6 +132,12 @@ impl SimilarityBase self.scaling = scaling; } + /// The scaling factor of this similarity transformation. + #[inline] + pub fn scaling(&self) -> N { + self.scaling + } + /// The similarity transformation that applies a scaling factor `scaling` before `self`. #[inline] pub fn prepend_scaling(&self, scaling: N) -> Self { @@ -112,7 +152,7 @@ impl SimilarityBase assert!(!relative_eq!(scaling, N::zero()), "The similarity scaling factor must not be zero."); Self::from_parts( - TranslationBase::from_vector(&self.isometry.translation.vector * scaling), + Translation::from_vector(&self.isometry.translation.vector * scaling), self.isometry.rotation.clone(), self.scaling * scaling) } @@ -136,7 +176,7 @@ impl SimilarityBase /// Appends to `self` the given translation in-place. #[inline] - pub fn append_translation_mut(&mut self, t: &TranslationBase) { + pub fn append_translation_mut(&mut self, t: &Translation) { self.isometry.append_translation_mut(t) } @@ -149,7 +189,7 @@ impl SimilarityBase /// Appends in-place to `self` a rotation centered at the point `p`, i.e., the rotation that /// lets `p` invariant. #[inline] - pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &PointBase) { + pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &Point) { self.isometry.append_rotation_wrt_point_mut(r, p) } @@ -166,16 +206,14 @@ impl SimilarityBase // and makes it harde to use it, e.g., for Transform × Isometry implementation. // This is OK since all constructors of the isometry enforce the Rotation bound already (and // explicit struct construction is prevented by the private scaling factor). -impl SimilarityBase - where N: Scalar + ClosedMul, - S: Storage { +impl Similarity + where DefaultAllocator: Allocator { /// Converts this similarity into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + pub fn to_homogeneous(&self) -> MatrixN> where D: DimNameAdd, - R: SubsetOf, S::Alloc>>, - S::Alloc: Allocator + - Allocator, DimNameSum> { + R: SubsetOf>>, + DefaultAllocator: Allocator, DimNameSum> { let mut res = self.isometry.to_homogeneous(); for e in res.fixed_slice_mut::(0, 0).iter_mut() { @@ -184,38 +222,26 @@ impl SimilarityBase res } - - /// The scaling factor of this similarity transformation. - #[inline] - pub fn scaling(&self) -> N { - self.scaling - } } -impl Eq for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation> + Eq, - S::Alloc: OwnedAllocator { +impl Eq for Similarity + where R: Rotation> + Eq, + DefaultAllocator: Allocator { } -impl PartialEq for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation> + PartialEq, - S::Alloc: OwnedAllocator { +impl PartialEq for Similarity + where R: Rotation> + PartialEq, + DefaultAllocator: Allocator { #[inline] - fn eq(&self, right: &SimilarityBase) -> bool { + fn eq(&self, right: &Similarity) -> bool { self.isometry == right.isometry && self.scaling == right.scaling } } -impl ApproxEq for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation> + ApproxEq, - S::Alloc: OwnedAllocator, +impl ApproxEq for Similarity + where R: Rotation> + ApproxEq, + DefaultAllocator: Allocator, N::Epsilon: Copy { type Epsilon = N::Epsilon; @@ -252,46 +278,16 @@ impl ApproxEq for SimilarityBase * Display * */ -impl fmt::Display for SimilarityBase +impl fmt::Display for Similarity where N: Real + fmt::Display, - S: OwnedStorage, - R: Rotation> + fmt::Display, - S::Alloc: OwnedAllocator + Allocator { + R: Rotation> + fmt::Display, + DefaultAllocator: Allocator + Allocator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let precision = f.precision().unwrap_or(3); - try!(writeln!(f, "SimilarityBase {{")); + try!(writeln!(f, "Similarity {{")); try!(write!(f, "{:.*}", precision, self.isometry)); try!(write!(f, "Scaling: {:.*}", precision, self.scaling)); writeln!(f, "}}") } } - -/* -// /* -// * -// * ToHomogeneous -// * -// */ -// impl ToHomogeneous<$homogeneous> for $t { -// #[inline] -// fn to_homogeneous(&self) -> $homogeneous { -// self.vector.to_homogeneous() -// } -// } - - -// /* -// * -// * Absolute -// * -// */ -// impl Absolute for $t { -// type AbsoluteValue = $submatrix; -// -// #[inline] -// fn abs(m: &$t) -> $submatrix { -// Absolute::abs(&m.submatrix) -// } -// } -*/ diff --git a/src/geometry/similarity_alga.rs b/src/geometry/similarity_alga.rs index 5959d6f1..c60eb621 100644 --- a/src/geometry/similarity_alga.rs +++ b/src/geometry/similarity_alga.rs @@ -1,13 +1,13 @@ use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, Real, Inverse, Multiplicative, Identity}; -use alga::linear::{Transformation, AffineTransformation, Rotation, Similarity, ProjectiveTransformation}; +use alga::linear::{Transformation, AffineTransformation, Rotation, ProjectiveTransformation}; +use alga::linear::Similarity as AlgaSimilarity; -use core::ColumnVector; -use core::dimension::{DimName, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::{DefaultAllocator, VectorN}; +use core::dimension::DimName; +use core::allocator::Allocator; -use geometry::{SimilarityBase, TranslationBase, PointBase}; +use geometry::{Similarity, Translation, Point}; /* @@ -15,22 +15,18 @@ use geometry::{SimilarityBase, TranslationBase, PointBase}; * Algebraic structures. * */ -impl Identity for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Identity for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::identity() } } -impl Inverse for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Inverse for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn inverse(&self) -> Self { self.inverse() @@ -42,11 +38,9 @@ impl Inverse for SimilarityBase } } -impl AbstractMagma for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs @@ -55,11 +49,9 @@ impl AbstractMagma for SimilarityBase),* $(,)*) => {$( - impl $marker<$operator> for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { } )*} ); @@ -76,49 +68,43 @@ impl_multiplicative_structures!( * Transformation groups. * */ -impl Transformation> for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl Transformation> for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point) -> Point { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &VectorN) -> VectorN { self * v } } -impl ProjectiveTransformation> for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point) -> Point { self.isometry.inverse_transform_point(pt) / self.scaling() } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.isometry.inverse_transform_vector(v) / self.scaling() } } -impl AffineTransformation> for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl AffineTransformation> for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { type NonUniformScaling = N; type Rotation = R; - type Translation = TranslationBase; + type Translation = Translation; #[inline] - fn decompose(&self) -> (TranslationBase, R, N, R) { + fn decompose(&self) -> (Translation, R, N, R) { (self.isometry.translation.clone(), self.isometry.rotation.clone(), self.scaling(), R::identity()) } @@ -134,7 +120,7 @@ impl AffineTransformation> for Similarit #[inline] fn append_rotation(&self, r: &Self::Rotation) -> Self { - SimilarityBase::from_isometry(self.isometry.append_rotation(r), self.scaling()) + Similarity::from_isometry(self.isometry.append_rotation(r), self.scaling()) } #[inline] @@ -153,22 +139,20 @@ impl AffineTransformation> for Similarit } #[inline] - fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &PointBase) -> Option { + fn append_rotation_wrt_point(&self, r: &Self::Rotation, p: &Point) -> Option { let mut res = self.clone(); res.append_rotation_wrt_point_mut(r, p); Some(res) } } -impl Similarity> for SimilarityBase - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { +impl AlgaSimilarity> for Similarity + where R: Rotation>, + DefaultAllocator: Allocator { type Scaling = N; #[inline] - fn translation(&self) -> TranslationBase { + fn translation(&self) -> Translation { self.isometry.translation() } diff --git a/src/geometry/similarity_alias.rs b/src/geometry/similarity_alias.rs index 3bf96e75..557b5db1 100644 --- a/src/geometry/similarity_alias.rs +++ b/src/geometry/similarity_alias.rs @@ -1,19 +1,15 @@ -use core::MatrixArray; -use core::dimension::{U1, U2, U3}; +use core::dimension::{U2, U3}; -use geometry::{Rotation, SimilarityBase, UnitQuaternion, UnitComplex}; - -/// A D-dimensional similarity. -pub type Similarity = SimilarityBase, Rotation>; +use geometry::{Similarity, UnitQuaternion, UnitComplex, Rotation2, Rotation3}; /// A 2-dimensional similarity. -pub type Similarity2 = SimilarityBase, UnitComplex>; +pub type Similarity2 = Similarity>; /// A 3-dimensional similarity. -pub type Similarity3 = SimilarityBase, UnitQuaternion>; +pub type Similarity3 = Similarity>; /// A 2-dimensional similarity using a rotation matrix for its rotation part. -pub type SimilarityMatrix2 = Similarity; +pub type SimilarityMatrix2 = Similarity>; /// A 3-dimensional similarity using a rotation matrix for its rotation part. -pub type SimilarityMatrix3 = Similarity; +pub type SimilarityMatrix3 = Similarity>; diff --git a/src/geometry/similarity_construction.rs b/src/geometry/similarity_construction.rs index d8f039f1..180f0cf3 100644 --- a/src/geometry/similarity_construction.rs +++ b/src/geometry/similarity_construction.rs @@ -1,5 +1,7 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; use num::One; use rand::{Rng, Rand}; @@ -7,32 +9,27 @@ use rand::{Rng, Rand}; use alga::general::Real; use alga::linear::Rotation as AlgaRotation; -use core::ColumnVector; -use core::dimension::{DimName, U1, U2, U3, U4}; -use core::allocator::{OwnedAllocator, Allocator}; -use core::storage::OwnedStorage; +use core::{DefaultAllocator, Vector2, Vector3}; +use core::dimension::{DimName, U2, U3}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, RotationBase, SimilarityBase, - UnitComplex, UnitQuaternionBase, IsometryBase}; +use geometry::{Point, Translation, Similarity, UnitComplex, UnitQuaternion, Isometry, + Point3, Rotation2, Rotation3}; -impl SimilarityBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { +impl Similarity + where R: AlgaRotation>, + DefaultAllocator: Allocator { /// Creates a new identity similarity. #[inline] pub fn identity() -> Self { - Self::from_isometry(IsometryBase::identity(), N::one()) + Self::from_isometry(Isometry::identity(), N::one()) } } -impl One for SimilarityBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { +impl One for Similarity + where R: AlgaRotation>, + DefaultAllocator: Allocator { /// Creates a new identity similarity. #[inline] fn one() -> Self { @@ -40,11 +37,9 @@ impl One for SimilarityBase } } -impl Rand for SimilarityBase - where N: Real + Rand, - S: OwnedStorage, - R: AlgaRotation> + Rand, - S::Alloc: OwnedAllocator { +impl Rand for Similarity + where R: AlgaRotation> + Rand, + DefaultAllocator: Allocator { #[inline] fn rand(rng: &mut G) -> Self { let mut s = rng.gen(); @@ -56,26 +51,24 @@ impl Rand for SimilarityBase } } -impl SimilarityBase - where N: Real, - S: OwnedStorage, - R: AlgaRotation>, - S::Alloc: OwnedAllocator { +impl Similarity + where R: AlgaRotation>, + DefaultAllocator: Allocator { /// The similarity that applies tha scaling factor `scaling`, followed by the rotation `r` with /// its axis passing through the point `p`. #[inline] - pub fn rotation_wrt_point(r: R, p: PointBase, scaling: N) -> Self { + pub fn rotation_wrt_point(r: R, p: Point, scaling: N) -> Self { let shift = r.transform_vector(&-&p.coords); - Self::from_parts(TranslationBase::from_vector(shift + p.coords), r, scaling) + Self::from_parts(Translation::from_vector(shift + p.coords), r, scaling) } } #[cfg(feature = "arbitrary")] -impl Arbitrary for SimilarityBase +impl Arbitrary for Similarity where N: Real + Arbitrary + Send, - S: OwnedStorage + Send, - R: AlgaRotation> + Arbitrary + Send, - S::Alloc: OwnedAllocator { + R: AlgaRotation> + Arbitrary + Send, + DefaultAllocator: Allocator, + Owned: Send { #[inline] fn arbitrary(rng: &mut G) -> Self { let mut s = Arbitrary::arbitrary(rng); @@ -94,45 +87,31 @@ impl Arbitrary for SimilarityBase */ // 2D rotation. -impl SimilarityBase> - where N: Real, - S: OwnedStorage, - SR: OwnedStorage, - S::Alloc: OwnedAllocator, - SR::Alloc: OwnedAllocator { +impl Similarity> { /// Creates a new similarity from a translation and a rotation angle. #[inline] - pub fn new(translation: ColumnVector, angle: N, scaling: N) -> Self { - Self::from_parts(TranslationBase::from_vector(translation), RotationBase::::new(angle), scaling) + pub fn new(translation: Vector2, angle: N, scaling: N) -> Self { + Self::from_parts(Translation::from_vector(translation), Rotation2::new(angle), scaling) } } -impl SimilarityBase> - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Similarity> { /// Creates a new similarity from a translation and a rotation angle. #[inline] - pub fn new(translation: ColumnVector, angle: N, scaling: N) -> Self { - Self::from_parts(TranslationBase::from_vector(translation), UnitComplex::new(angle), scaling) + pub fn new(translation: Vector2, angle: N, scaling: N) -> Self { + Self::from_parts(Translation::from_vector(translation), UnitComplex::new(angle), scaling) } } // 3D rotation. macro_rules! similarity_construction_impl( - ($Rot: ty, $RotId: ident, $RRDim: ty, $RCDim: ty) => { - impl SimilarityBase - where N: Real, - S: OwnedStorage, - SR: OwnedStorage, - S::Alloc: OwnedAllocator, - SR::Alloc: OwnedAllocator + - Allocator { + ($Rot: ty) => { + impl Similarity { /// Creates a new similarity from a translation, rotation axis-angle, and scaling /// factor. #[inline] - pub fn new(translation: ColumnVector, axisangle: ColumnVector, scaling: N) -> Self { - Self::from_isometry(IsometryBase::<_, _, _, $Rot>::new(translation, axisangle), scaling) + pub fn new(translation: Vector3, axisangle: Vector3, scaling: N) -> Self { + Self::from_isometry(Isometry::<_, U3, $Rot>::new(translation, axisangle), scaling) } /// Creates an similarity that corresponds to the a scaling factor and a local frame of @@ -147,12 +126,12 @@ macro_rules! similarity_construction_impl( /// * up - Vertical direction. The only requirement of this parameter is to not be collinear /// to `eye - at`. Non-collinearity is not checked. #[inline] - pub fn new_observer_frame(eye: &PointBase, - target: &PointBase, - up: &ColumnVector, + pub fn new_observer_frame(eye: &Point3, + target: &Point3, + up: &Vector3, scaling: N) -> Self { - Self::from_isometry(IsometryBase::<_, _, _, $Rot>::new_observer_frame(eye, target, up), scaling) + Self::from_isometry(Isometry::<_, U3, $Rot>::new_observer_frame(eye, target, up), scaling) } /// Builds a right-handed look-at view matrix including scaling factor. @@ -166,12 +145,12 @@ macro_rules! similarity_construction_impl( /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_rh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector, + pub fn look_at_rh(eye: &Point3, + target: &Point3, + up: &Vector3, scaling: N) -> Self { - Self::from_isometry(IsometryBase::<_, _, _, $Rot>::look_at_rh(eye, target, up), scaling) + Self::from_isometry(Isometry::<_, U3, $Rot>::look_at_rh(eye, target, up), scaling) } /// Builds a left-handed look-at view matrix including a scaling factor. @@ -185,16 +164,16 @@ macro_rules! similarity_construction_impl( /// * up - A vector approximately aligned with required the vertical axis. The only /// requirement of this parameter is to not be collinear to `target - eye`. #[inline] - pub fn look_at_lh(eye: &PointBase, - target: &PointBase, - up: &ColumnVector, + pub fn look_at_lh(eye: &Point3, + target: &Point3, + up: &Vector3, scaling: N) -> Self { - Self::from_isometry(IsometryBase::<_, _, _, $Rot>::look_at_lh(eye, target, up), scaling) + Self::from_isometry(Isometry::<_, _, $Rot>::look_at_lh(eye, target, up), scaling) } } } ); -similarity_construction_impl!(RotationBase, RotationBase, U3, U3); -similarity_construction_impl!(UnitQuaternionBase, UnitQuaternionBase, U4, U1); +similarity_construction_impl!(Rotation3); +similarity_construction_impl!(UnitQuaternion); diff --git a/src/geometry/similarity_conversion.rs b/src/geometry/similarity_conversion.rs index e629db77..8f5a675d 100644 --- a/src/geometry/similarity_conversion.rs +++ b/src/geometry/similarity_conversion.rs @@ -1,49 +1,46 @@ use alga::general::{Real, SubsetOf, SupersetOf}; use alga::linear::Rotation; -use core::{SquareMatrix, OwnedSquareMatrix}; -use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; +use core::{DefaultAllocator, MatrixN}; +use core::dimension::{DimName, DimNameAdd, DimNameSum, DimMin, U1}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; +use geometry::{Point, Translation, Isometry, Similarity, Transform, SuperTCategoryOf, TAffine}; /* * This file provides the following conversions: * ============================================= * - * SimilarityBase -> SimilarityBase - * SimilarityBase -> TransformBase - * SimilarityBase -> Matrix (homogeneous) + * Similarity -> Similarity + * Similarity -> Transform + * Similarity -> Matrix (homogeneous) */ -impl SubsetOf> for SimilarityBase +impl SubsetOf> for Similarity where N1: Real + SubsetOf, N2: Real + SupersetOf, - R1: Rotation> + SubsetOf, - R2: Rotation>, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R1: Rotation> + SubsetOf, + R2: Rotation>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_isometry( + fn to_superset(&self) -> Similarity { + Similarity::from_isometry( self.isometry.to_superset(), self.scaling().to_superset() ) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { - ::is_convertible::<_, IsometryBase>(&sim.isometry) && + fn is_in_subset(sim: &Similarity) -> bool { + ::is_convertible::<_, Isometry>(&sim.isometry) && ::is_convertible::<_, N1>(&sim.scaling()) } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { - SimilarityBase::from_isometry( + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { + Similarity::from_isometry( sim.isometry.to_subset_unchecked(), sim.scaling().to_subset_unchecked() ) @@ -51,65 +48,63 @@ impl SubsetOf> } -impl SubsetOf> for SimilarityBase +impl SubsetOf> for Similarity where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, C: SuperTCategoryOf, - R: Rotation> + - SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() - SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator + // needed by R - Allocator, DimNameSum> + // needed by: .to_homogeneous() - Allocator, DimNameSum>, // needed by R - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + // needed by: mm.fixed_slice_mut - Allocator + // needed by: m.fixed_slice - Allocator { // needed by: m.fixed_slice + R: Rotation> + + SubsetOf>> + + SubsetOf>>, + D: DimNameAdd + + DimMin, // needed by .determinant() + DefaultAllocator: Allocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by: .to_homogeneous() + Allocator, DimNameSum> + // needed by R + Allocator<(usize, usize), D> + // needed by .determinant() + Allocator, DimNameSum> + + Allocator + + Allocator { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf, SB>> for SimilarityBase +impl SubsetOf>> for Similarity where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, - R: Rotation> + - SubsetOf, SA::Alloc>> + // needed by: .to_homogeneous() - SubsetOf, SB>>, // needed by: ::convert_unchecked(mm) - D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator + // needed by R - Allocator, DimNameSum> + // needed by: .to_homogeneous() - Allocator, DimNameSum>, // needed by R - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + // needed by: mm.fixed_slice_mut - Allocator + // needed by: m.fixed_slice - Allocator { // needed by: m.fixed_slice + R: Rotation> + + SubsetOf>> + + SubsetOf>>, + D: DimNameAdd + + DimMin, // needed by .determinant() + DefaultAllocator: Allocator + + Allocator + // needed by R + Allocator, DimNameSum> + // needed by .to_homogeneous() + Allocator, DimNameSum> + // needed by R + Allocator<(usize, usize), D> + // needed by .determinant() + Allocator, DimNameSum> + + Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SquareMatrix, SB> { + fn to_superset(&self) -> MatrixN> { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + fn is_in_subset(m: &MatrixN>) -> bool { let mut rot = m.fixed_slice::(0, 0).clone_owned(); if rot.fixed_columns_mut::(0).try_normalize_mut(N2::zero()).is_some() && rot.fixed_columns_mut::(1).try_normalize_mut(N2::zero()).is_some() && @@ -138,7 +133,7 @@ impl SubsetOf, SB>> for } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + unsafe fn from_superset_unchecked(m: &MatrixN>) -> Self { let mut mm = m.clone_owned(); let na = mm.fixed_slice_mut::(0, 0).normalize_mut(); let nb = mm.fixed_slice_mut::(0, 1).normalize_mut(); @@ -156,7 +151,7 @@ impl SubsetOf, SB>> for } let t = m.fixed_slice::(0, D::dim()).into_owned(); - let t = TranslationBase::from_vector(::convert_unchecked(t)); + let t = Translation::from_vector(::convert_unchecked(t)); Self::from_parts(t, ::convert_unchecked(mm), ::convert_unchecked(scale)) } diff --git a/src/geometry/similarity_ops.rs b/src/geometry/similarity_ops.rs index 59274db1..768acb9e 100644 --- a/src/geometry/similarity_ops.rs +++ b/src/geometry/similarity_ops.rs @@ -1,14 +1,13 @@ use std::ops::{Mul, MulAssign, Div, DivAssign}; use alga::general::Real; -use alga::linear::Rotation; +use alga::linear::Rotation as AlgaRotation; -use core::ColumnVector; +use core::{DefaultAllocator, VectorN}; use core::dimension::{DimName, U1, U3, U4}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::{PointBase, RotationBase, SimilarityBase, TranslationBase, UnitQuaternionBase, IsometryBase}; +use geometry::{Point, Rotation, Similarity, Translation, UnitQuaternion, Isometry}; // FIXME: there are several cloning of rotations that we could probably get rid of (but we didn't // yet because that would require to add a bound like `where for<'a, 'b> &'a R: Mul<&'b R, Output = R>` @@ -22,43 +21,43 @@ use geometry::{PointBase, RotationBase, SimilarityBase, TranslationBase, UnitQua * * (Operators) * - * SimilarityBase × SimilarityBase - * SimilarityBase × R - * SimilarityBase × IsometryBase + * Similarity × Similarity + * Similarity × R + * Similarity × Isometry * - * IsometryBase × SimilarityBase - * IsometryBase ÷ SimilarityBase + * Isometry × Similarity + * Isometry ÷ Similarity * * - * SimilarityBase ÷ SimilarityBase - * SimilarityBase ÷ R - * SimilarityBase ÷ IsometryBase + * Similarity ÷ Similarity + * Similarity ÷ R + * Similarity ÷ Isometry * - * SimilarityBase × PointBase - * SimilarityBase × ColumnVector + * Similarity × Point + * Similarity × Vector * * - * SimilarityBase × TranslationBase - * TranslationBase × SimilarityBase + * Similarity × Translation + * Translation × Similarity * - * NOTE: The following are provided explicitly because we can't have R × SimilarityBase. - * RotationBase × SimilarityBase - * UnitQuaternion × SimilarityBase + * NOTE: The following are provided explicitly because we can't have R × Similarity. + * Rotation × Similarity + * UnitQuaternion × Similarity * - * RotationBase ÷ SimilarityBase - * UnitQuaternion ÷ SimilarityBase + * Rotation ÷ Similarity + * UnitQuaternion ÷ Similarity * * (Assignment Operators) * - * SimilarityBase ×= TranslationBase + * Similarity ×= Translation * - * SimilarityBase ×= SimilarityBase - * SimilarityBase ×= IsometryBase - * SimilarityBase ×= R + * Similarity ×= Similarity + * Similarity ×= Isometry + * Similarity ×= R * - * SimilarityBase ÷= SimilarityBase - * SimilarityBase ÷= IsometryBase - * SimilarityBase ÷= R + * Similarity ÷= Similarity + * Similarity ÷= Isometry + * Similarity ÷= R * */ @@ -68,11 +67,9 @@ macro_rules! similarity_binop_impl( ($Op: ident, $op: ident; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N, D: DimName, S, R> $Op<$Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl<$($lives ,)* N: Real, D: DimName, R> $Op<$Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { type Output = $Output; #[inline] @@ -117,22 +114,18 @@ macro_rules! similarity_binop_assign_impl_all( $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty; [val] => $action_val: expr; [ref] => $action_ref: expr;) => { - impl $OpAssign<$Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl $OpAssign<$Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { #[inline] fn $op_assign(&mut $lhs, $rhs: $Rhs) { $action_val } } - impl<'b, N, D: DimName, S, R> $OpAssign<&'b $Rhs> for $Lhs - where N: Real, - S: OwnedStorage, - R: Rotation>, - S::Alloc: OwnedAllocator { + impl<'b, N: Real, D: DimName, R> $OpAssign<&'b $Rhs> for $Lhs + where R: AlgaRotation>, + DefaultAllocator: Allocator { #[inline] fn $op_assign(&mut $lhs, $rhs: &'b $Rhs) { $action_ref @@ -141,11 +134,11 @@ macro_rules! similarity_binop_assign_impl_all( } ); -// SimilarityBase × SimilarityBase -// SimilarityBase ÷ SimilarityBase +// Similarity × Similarity +// Similarity ÷ Similarity similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, rhs: SimilarityBase, Output = SimilarityBase; + self: Similarity, rhs: Similarity, Output = Similarity; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; @@ -159,7 +152,7 @@ similarity_binop_impl_all!( similarity_binop_impl_all!( Div, div; - self: SimilarityBase, rhs: SimilarityBase, Output = SimilarityBase; + self: Similarity, rhs: Similarity, Output = Similarity; [val val] => self * rhs.inverse(); [ref val] => self * rhs.inverse(); [val ref] => self * rhs.inverse(); @@ -167,10 +160,10 @@ similarity_binop_impl_all!( ); -// SimilarityBase ×= TranslationBase +// Similarity ×= Translation similarity_binop_assign_impl_all!( MulAssign, mul_assign; - self: SimilarityBase, rhs: TranslationBase; + self: Similarity, rhs: Translation; [val] => *self *= &rhs; [ref] => { let shift = self.isometry.rotation.transform_vector(&rhs.vector) * self.scaling(); @@ -179,11 +172,11 @@ similarity_binop_assign_impl_all!( ); -// SimilarityBase ×= SimilarityBase -// SimilarityBase ÷= SimilarityBase +// Similarity ×= Similarity +// Similarity ÷= Similarity similarity_binop_assign_impl_all!( MulAssign, mul_assign; - self: SimilarityBase, rhs: SimilarityBase; + self: Similarity, rhs: Similarity; [val] => *self *= &rhs; [ref] => { *self *= &rhs.isometry; @@ -194,18 +187,18 @@ similarity_binop_assign_impl_all!( similarity_binop_assign_impl_all!( DivAssign, div_assign; - self: SimilarityBase, rhs: SimilarityBase; + self: Similarity, rhs: Similarity; [val] => *self /= &rhs; // FIXME: don't invert explicitly. [ref] => *self *= rhs.inverse(); ); -// SimilarityBase ×= IsometryBase -// SimilarityBase ÷= IsometryBase +// Similarity ×= Isometry +// Similarity ÷= Isometry similarity_binop_assign_impl_all!( MulAssign, mul_assign; - self: SimilarityBase, rhs: IsometryBase; + self: Similarity, rhs: Isometry; [val] => *self *= &rhs; [ref] => { let shift = self.isometry.rotation.transform_vector(&rhs.translation.vector) * self.scaling(); @@ -217,18 +210,18 @@ similarity_binop_assign_impl_all!( similarity_binop_assign_impl_all!( DivAssign, div_assign; - self: SimilarityBase, rhs: IsometryBase; + self: Similarity, rhs: Isometry; [val] => *self /= &rhs; // FIXME: don't invert explicitly. [ref] => *self *= rhs.inverse(); ); -// SimilarityBase ×= R -// SimilarityBase ÷= R +// Similarity ×= R +// Similarity ÷= R similarity_binop_assign_impl_all!( MulAssign, mul_assign; - self: SimilarityBase, rhs: R; + self: Similarity, rhs: R; [val] => self.isometry.rotation *= rhs; [ref] => self.isometry.rotation *= rhs.clone(); ); @@ -236,59 +229,59 @@ similarity_binop_assign_impl_all!( similarity_binop_assign_impl_all!( DivAssign, div_assign; - self: SimilarityBase, rhs: R; + self: Similarity, rhs: R; // FIXME: don't invert explicitly? [val] => *self *= rhs.inverse(); [ref] => *self *= rhs.inverse(); ); -// SimilarityBase × R -// SimilarityBase ÷ R +// Similarity × R +// Similarity ÷ R similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, rhs: R, Output = SimilarityBase; + self: Similarity, rhs: R, Output = Similarity; [val val] => { let scaling = self.scaling(); - SimilarityBase::from_isometry(self.isometry * rhs, scaling) + Similarity::from_isometry(self.isometry * rhs, scaling) }; - [ref val] => SimilarityBase::from_isometry(&self.isometry * rhs, self.scaling()); + [ref val] => Similarity::from_isometry(&self.isometry * rhs, self.scaling()); [val ref] => { let scaling = self.scaling(); - SimilarityBase::from_isometry(self.isometry * rhs, scaling) + Similarity::from_isometry(self.isometry * rhs, scaling) }; - [ref ref] => SimilarityBase::from_isometry(&self.isometry * rhs, self.scaling()); + [ref ref] => Similarity::from_isometry(&self.isometry * rhs, self.scaling()); ); similarity_binop_impl_all!( Div, div; - self: SimilarityBase, rhs: R, Output = SimilarityBase; + self: Similarity, rhs: R, Output = Similarity; [val val] => { let scaling = self.scaling(); - SimilarityBase::from_isometry(self.isometry / rhs, scaling) + Similarity::from_isometry(self.isometry / rhs, scaling) }; - [ref val] => SimilarityBase::from_isometry(&self.isometry / rhs, self.scaling()); + [ref val] => Similarity::from_isometry(&self.isometry / rhs, self.scaling()); [val ref] => { let scaling = self.scaling(); - SimilarityBase::from_isometry(self.isometry / rhs, scaling) + Similarity::from_isometry(self.isometry / rhs, scaling) }; - [ref ref] => SimilarityBase::from_isometry(&self.isometry / rhs, self.scaling()); + [ref ref] => Similarity::from_isometry(&self.isometry / rhs, self.scaling()); ); -// SimilarityBase × IsometryBase -// SimilarityBase ÷ IsometryBase +// Similarity × Isometry +// Similarity ÷ Isometry similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, rhs: IsometryBase, Output = SimilarityBase; + self: Similarity, rhs: Isometry, Output = Similarity; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => { let shift = self.isometry.rotation.transform_vector(&rhs.translation.vector) * self.scaling(); - SimilarityBase::from_parts( - TranslationBase::from_vector(&self.isometry.translation.vector + shift), + Similarity::from_parts( + Translation::from_vector(&self.isometry.translation.vector + shift), self.isometry.rotation.clone() * rhs.rotation.clone(), self.scaling()) }; @@ -298,40 +291,40 @@ similarity_binop_impl_all!( similarity_binop_impl_all!( Div, div; - self: SimilarityBase, rhs: IsometryBase, Output = SimilarityBase; + 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(); ); -// IsometryBase × SimilarityBase -// IsometryBase ÷ SimilarityBase +// Isometry × Similarity +// Isometry ÷ Similarity similarity_binop_impl_all!( Mul, mul; - self: IsometryBase, rhs: SimilarityBase, Output = SimilarityBase; + self: Isometry, rhs: Similarity, Output = Similarity; [val val] => { let scaling = rhs.scaling(); - SimilarityBase::from_isometry(self * rhs.isometry, scaling) + Similarity::from_isometry(self * rhs.isometry, scaling) }; [ref val] => { let scaling = rhs.scaling(); - SimilarityBase::from_isometry(self * rhs.isometry, scaling) + Similarity::from_isometry(self * rhs.isometry, scaling) }; [val ref] => { let scaling = rhs.scaling(); - SimilarityBase::from_isometry(self * &rhs.isometry, scaling) + Similarity::from_isometry(self * &rhs.isometry, scaling) }; [ref ref] => { let scaling = rhs.scaling(); - SimilarityBase::from_isometry(self * &rhs.isometry, scaling) + Similarity::from_isometry(self * &rhs.isometry, scaling) }; ); similarity_binop_impl_all!( Div, div; - self: IsometryBase, rhs: SimilarityBase, Output = SimilarityBase; + self: Isometry, rhs: Similarity, Output = Similarity; [val val] => self * rhs.inverse(); [ref val] => self * rhs.inverse(); [val ref] => self * rhs.inverse(); @@ -339,10 +332,10 @@ similarity_binop_impl_all!( ); -// SimilarityBase × PointBase +// Similarity × Point similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, right: PointBase, Output = PointBase; + self: Similarity, right: Point, Output = Point; [val val] => { let scaling = self.scaling(); self.isometry.translation * (self.isometry.rotation.transform_point(&right) * scaling) @@ -356,10 +349,10 @@ similarity_binop_impl_all!( ); -// SimilarityBase × Vector +// Similarity × Vector similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, right: ColumnVector, Output = ColumnVector; + self: Similarity, right: VectorN, Output = VectorN; [val val] => self.isometry.rotation.transform_vector(&right) * self.scaling(); [ref val] => self.isometry.rotation.transform_vector(&right) * self.scaling(); [val ref] => self.isometry.rotation.transform_vector(right) * self.scaling(); @@ -367,37 +360,37 @@ similarity_binop_impl_all!( ); -// SimilarityBase × TranslationBase +// Similarity × Translation similarity_binop_impl_all!( Mul, mul; - self: SimilarityBase, right: TranslationBase, Output = SimilarityBase; + self: Similarity, right: Translation, Output = Similarity; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; [ref ref] => { let shift = self.isometry.rotation.transform_vector(&right.vector) * self.scaling(); - SimilarityBase::from_parts( - TranslationBase::from_vector(&self.isometry.translation.vector + shift), + Similarity::from_parts( + Translation::from_vector(&self.isometry.translation.vector + shift), self.isometry.rotation.clone(), self.scaling()) }; ); -// TranslationBase × SimilarityBase +// Translation × Similarity similarity_binop_impl_all!( Mul, mul; - self: TranslationBase, right: SimilarityBase, Output = SimilarityBase; + self: Translation, right: Similarity, Output = Similarity; [val val] => { let scaling = right.scaling(); - SimilarityBase::from_isometry(self * right.isometry, scaling) + Similarity::from_isometry(self * right.isometry, scaling) }; [ref val] => { let scaling = right.scaling(); - SimilarityBase::from_isometry(self * right.isometry, scaling) + Similarity::from_isometry(self * right.isometry, scaling) }; - [val ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); - [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); + [val ref] => Similarity::from_isometry(self * &right.isometry, right.scaling()); + [ref ref] => Similarity::from_isometry(self * &right.isometry, right.scaling()); ); @@ -406,12 +399,9 @@ macro_rules! similarity_from_composition_impl( ($R1: ty, $C1: ty),($R2: ty, $C2: ty) $(for $Dims: ident: $DimsBound: ident),*; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Output: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N $(, $Dims: $DimsBound)*, SA, SB> $Op<$Rhs> for $Lhs - where N: Real, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + impl<$($lives ,)* N: Real $(, $Dims: $DimsBound)*> $Op<$Rhs> for $Lhs + where DefaultAllocator: Allocator + + Allocator { type Output = $Output; #[inline] @@ -458,25 +448,25 @@ macro_rules! similarity_from_composition_impl_all( ); -// RotationBase × SimilarityBase +// Rotation × Similarity similarity_from_composition_impl_all!( Mul, mul; (D, D), (D, U1) for D: DimName; - self: RotationBase, right: SimilarityBase>, - Output = SimilarityBase>; + self: Rotation, right: Similarity>, + Output = Similarity>; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; - [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); + [ref ref] => Similarity::from_isometry(self * &right.isometry, right.scaling()); ); -// RotationBase ÷ SimilarityBase +// Rotation ÷ Similarity similarity_from_composition_impl_all!( Div, div; (D, D), (D, U1) for D: DimName; - self: RotationBase, right: SimilarityBase>, - Output = SimilarityBase>; + self: Rotation, right: Similarity>, + Output = Similarity>; // FIXME: don't call iverse explicitly? [val val] => self * right.inverse(); [ref val] => self * right.inverse(); @@ -485,25 +475,25 @@ similarity_from_composition_impl_all!( ); -// UnitQuaternion × SimilarityBase +// UnitQuaternion × Similarity similarity_from_composition_impl_all!( Mul, mul; (U4, U1), (U3, U1); - self: UnitQuaternionBase, right: SimilarityBase>, - Output = SimilarityBase>; + self: UnitQuaternion, right: Similarity>, + Output = Similarity>; [val val] => &self * &right; [ref val] => self * &right; [val ref] => &self * right; - [ref ref] => SimilarityBase::from_isometry(self * &right.isometry, right.scaling()); + [ref ref] => Similarity::from_isometry(self * &right.isometry, right.scaling()); ); -// UnitQuaternion ÷ SimilarityBase +// UnitQuaternion ÷ Similarity similarity_from_composition_impl_all!( Div, div; (U4, U1), (U3, U1); - self: UnitQuaternionBase, right: SimilarityBase>, - Output = SimilarityBase>; + self: UnitQuaternion, right: Similarity>, + Output = Similarity>; // FIXME: don't call inverse explicitly? [val val] => self * right.inverse(); [ref val] => self * right.inverse(); diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index 5ff2d85e..698c3b89 100644 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -1,16 +1,15 @@ use std::any::Any; use std::fmt::Debug; use std::marker::PhantomData; -use approx::ApproxEq; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; -use alga::general::Field; +use alga::general::Real; -use core::{Scalar, SquareMatrix, OwnedSquareMatrix}; +use core::{DefaultAllocator, MatrixN}; use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use core::storage::{Storage, StorageMut}; +use core::storage::Owned; use core::allocator::Allocator; /// Trait implemented by phantom types identifying the projective transformation type. @@ -26,11 +25,9 @@ pub trait TCategory: Any + Debug + Copy + PartialEq + Send { /// Checks that the given matrix is a valid homogeneous representation of an element of the /// category `Self`. - fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool - where N: Scalar + Field + ApproxEq, - D: DimName, - S: Storage, - N::Epsilon: Copy; + fn check_homogeneous_invariants(mat: &MatrixN) -> bool + where N::Epsilon: Copy, + DefaultAllocator: Allocator; } /// Traits that gives the `Transform` category that is compatible with the result of the @@ -68,22 +65,18 @@ pub enum TAffine { } impl TCategory for TGeneral { #[inline] - fn check_homogeneous_invariants(_: &SquareMatrix) -> bool - where N: Scalar + Field + ApproxEq, - D: DimName, - S: Storage, - N::Epsilon: Copy { + fn check_homogeneous_invariants(_: &MatrixN) -> bool + where N::Epsilon: Copy, + DefaultAllocator: Allocator { true } } impl TCategory for TProjective { #[inline] - fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool - where N: Scalar + Field + ApproxEq, - D: DimName, - S: Storage, - N::Epsilon: Copy { + fn check_homogeneous_invariants(mat: &MatrixN) -> bool + where N::Epsilon: Copy, + DefaultAllocator: Allocator { mat.is_invertible() } } @@ -95,11 +88,9 @@ impl TCategory for TAffine { } #[inline] - fn check_homogeneous_invariants(mat: &SquareMatrix) -> bool - where N: Scalar + Field + ApproxEq, - D: DimName, - S: Storage, - N::Epsilon: Copy { + fn check_homogeneous_invariants(mat: &MatrixN) -> bool + where N::Epsilon: Copy, + DefaultAllocator: Allocator { let last = D::dim() - 1; mat.is_invertible() && mat[(last, last)] == N::one() && @@ -121,17 +112,17 @@ impl TCategoryMul for T { } category_mul_impl!( - // TGeneral * TGeneral => TGeneral; - TGeneral * TProjective => TGeneral; - TGeneral * TAffine => TGeneral; +// TGeneral * TGeneral => TGeneral; + TGeneral * TProjective => TGeneral; + TGeneral * TAffine => TGeneral; - TProjective * TGeneral => TGeneral; - // TProjective * TProjective => TProjective; - TProjective * TAffine => TProjective; + TProjective * TGeneral => TGeneral; +// TProjective * TProjective => TProjective; + TProjective * TAffine => TProjective; - TAffine * TGeneral => TGeneral; - TAffine * TProjective => TProjective; - // TAffine * TAffine => TAffine; + TAffine * TGeneral => TGeneral; + TAffine * TProjective => TProjective; +// TAffine * TAffine => TAffine; ); macro_rules! super_tcategory_impl( @@ -143,109 +134,104 @@ macro_rules! super_tcategory_impl( impl SuperTCategoryOf for T { } super_tcategory_impl!( - TGeneral >= TProjective; - TGeneral >= TAffine; + TGeneral >= TProjective; + TGeneral >= TAffine; TProjective >= TAffine; ); -/// A transformation matrix that owns its data. -pub type OwnedTransform - = TransformBase, DimNameSum>>::Buffer, C>; - - /// A transformation matrix in homogeneous coordinates. /// /// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a /// 3D transformation. #[repr(C)] -#[derive(Debug, Clone, Copy)] // FIXME: Hash -pub struct TransformBase, S, C: TCategory> { - matrix: SquareMatrix, S>, +#[derive(Debug)] +pub struct Transform, C: TCategory> + where DefaultAllocator: Allocator, DimNameSum> { + matrix: MatrixN>, _phantom: PhantomData } -#[cfg(feature = "serde-serialize")] -impl Serialize for TransformBase - where N: Scalar, - D: DimNameAdd, - C: TCategory, - SquareMatrix, S>: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.matrix.serialize(serializer) +// FIXME +// impl + hash::Hash, C: TCategory> hash::Hash for Transform +// where DefaultAllocator: Allocator, DimNameSum>, +// Owned, DimNameSum>: hash::Hash { +// fn hash(&self, state: &mut H) { +// self.matrix.hash(state); +// } +// } + +impl + Copy, C: TCategory> Copy for Transform + where DefaultAllocator: Allocator, DimNameSum>, + Owned, DimNameSum>: Copy { +} + +impl, C: TCategory> Clone for Transform + where DefaultAllocator: Allocator, DimNameSum> { + #[inline] + fn clone(&self) -> Self { + Transform::from_matrix_unchecked(self.matrix.clone()) } } #[cfg(feature = "serde-serialize")] -impl<'de, N, D, S, C> Deserialize<'de> for TransformBase - where N: Scalar, - D: DimNameAdd, - C: TCategory, - SquareMatrix, S>: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - SquareMatrix::deserialize(deserializer).map(|x| TransformBase { matrix: x, _phantom: PhantomData }) - } +impl, C: TCategory> serde::Serialize for Transform +where DefaultAllocator: Allocator, DimNameSum>, + Owned, DimNameSum>: serde::Serialize { + + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.matrix.serialize(serializer) + } } -// XXX: for some reasons, implementing Clone and Copy manually causes an ICE… +#[cfg(feature = "serde-serialize")] +impl<'a, N: Real, D: DimNameAdd, C: TCategory> serde::Deserialize<'a> for Transform +where DefaultAllocator: Allocator, DimNameSum>, + Owned, DimNameSum>: serde::Deserialize<'a> { -impl Eq for TransformBase - where N: Scalar + Eq, - D: DimNameAdd, - S: Storage, DimNameSum> { } + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let matrix = MatrixN::>::deserialize(deserializer)?; -impl PartialEq for TransformBase - where N: Scalar, - D: DimNameAdd, - S: Storage, DimNameSum> { + Ok(Transform::from_matrix_unchecked(matrix)) + } +} + + +impl, C: TCategory> Eq for Transform + where DefaultAllocator: Allocator, DimNameSum> { } + +impl, C: TCategory> PartialEq for Transform + where DefaultAllocator: Allocator, DimNameSum> { #[inline] fn eq(&self, right: &Self) -> bool { self.matrix == right.matrix } } -impl TransformBase - where N: Scalar, - D: DimNameAdd, - S: Storage, DimNameSum> { +impl, C: TCategory> Transform + where DefaultAllocator: Allocator, DimNameSum> { /// Creates a new transformation from the given homogeneous matrix. The transformation category /// of `Self` is not checked to be verified by the given matrix. #[inline] - pub fn from_matrix_unchecked(matrix: SquareMatrix, S>) -> Self { - TransformBase { + pub fn from_matrix_unchecked(matrix: MatrixN>) -> Self { + Transform { matrix: matrix, _phantom: PhantomData } } - /// Moves this transform into one that owns its data. - #[inline] - pub fn into_owned(self) -> OwnedTransform { - TransformBase::from_matrix_unchecked(self.matrix.into_owned()) - } - - /// Clones this transform into one that owns its data. - #[inline] - pub fn clone_owned(&self) -> OwnedTransform { - TransformBase::from_matrix_unchecked(self.matrix.clone_owned()) - } - /// The underlying matrix. #[inline] - pub fn unwrap(self) -> SquareMatrix, S> { + pub fn unwrap(self) -> MatrixN> { self.matrix } /// A reference to the underlynig matrix. #[inline] - pub fn matrix(&self) -> &SquareMatrix, S> { + pub fn matrix(&self) -> &MatrixN> { &self.matrix } @@ -254,7 +240,7 @@ impl TransformBase /// It is `_unchecked` because direct modifications of this matrix may break invariants /// identified by this transformation category. #[inline] - pub fn matrix_mut_unchecked(&mut self) -> &mut SquareMatrix, S> { + pub fn matrix_mut_unchecked(&mut self) -> &mut MatrixN> { &mut self.matrix } @@ -265,28 +251,29 @@ impl TransformBase /// `TAffine` because not all projective transformations are affine (the other way-round is /// valid though). #[inline] - pub fn set_category>(self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.matrix) + pub fn set_category>(self) -> Transform { + Transform::from_matrix_unchecked(self.matrix) + } + + /// Clones this transform into one that owns its data. + #[inline] + #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + pub fn clone_owned(&self) -> Transform { + Transform::from_matrix_unchecked(self.matrix.clone_owned()) } /// Converts this transform into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> { + pub fn to_homogeneous(&self) -> MatrixN> { self.matrix().clone_owned() } -} -impl TransformBase - where N: Scalar + Field + ApproxEq, - D: DimNameAdd, - C: TCategory, - S: Storage, DimNameSum> { /// Attempts to invert this transformation. You may use `.inverse` instead of this /// transformation has a subcategory of `TProjective`. #[inline] - pub fn try_inverse(self) -> Option> { + pub fn try_inverse(self) -> Option> { if let Some(m) = self.matrix.try_inverse() { - Some(TransformBase::from_matrix_unchecked(m)) + Some(Transform::from_matrix_unchecked(m)) } else { None @@ -296,18 +283,12 @@ impl TransformBase /// Inverts this transformation. Use `.try_inverse` if this transform has the `TGeneral` /// category (it may not be invertible). #[inline] - pub fn inverse(self) -> OwnedTransform + pub fn inverse(self) -> Transform where C: SubTCategoryOf { // FIXME: specialize for TAffine? - TransformBase::from_matrix_unchecked(self.matrix.try_inverse().unwrap()) + Transform::from_matrix_unchecked(self.matrix.try_inverse().unwrap()) } -} -impl TransformBase - where N: Scalar + Field + ApproxEq, - D: DimNameAdd, - C: TCategory, - S: StorageMut, DimNameSum> { /// Attempts to invert this transformation in-place. You may use `.inverse_mut` instead of this /// transformation has a subcategory of `TProjective`. #[inline] @@ -324,14 +305,12 @@ impl TransformBase } } -impl TransformBase - where N: Scalar, - D: DimNameAdd, - S: Storage, DimNameSum> { +impl> Transform + where DefaultAllocator: Allocator, DimNameSum> { /// A mutable reference to underlying matrix. Use `.matrix_mut_unchecked` instead if this /// transformation category is not `TGeneral`. #[inline] - pub fn matrix_mut(&mut self) -> &mut SquareMatrix, S> { + pub fn matrix_mut(&mut self) -> &mut MatrixN> { self.matrix_mut_unchecked() } } @@ -345,5 +324,4 @@ mod tests { fn checks_homogeneous_invariants_of_square_identity_matrix() { assert!(TAffine::check_homogeneous_invariants(&Matrix4::::identity())); } - } diff --git a/src/geometry/transform_alga.rs b/src/geometry/transform_alga.rs index 5c7e1abc..e91d49ed 100644 --- a/src/geometry/transform_alga.rs +++ b/src/geometry/transform_alga.rs @@ -1,15 +1,12 @@ -use approx::ApproxEq; - use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, - AbstractSemigroup, Field, Real, Inverse, Multiplicative, Identity}; + AbstractSemigroup, Real, Inverse, Multiplicative, Identity}; use alga::linear::{Transformation, ProjectiveTransformation}; -use core::{Scalar, ColumnVector}; +use core::{DefaultAllocator, VectorN}; use core::dimension::{DimNameSum, DimNameAdd, U1}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; +use core::allocator::Allocator; -use geometry::{PointBase, TransformBase, TCategory, SubTCategoryOf, TProjective}; +use geometry::{Point, Transform, TCategory, SubTCategoryOf, TProjective}; /* @@ -17,22 +14,18 @@ use geometry::{PointBase, TransformBase, TCategory, SubTCategoryOf, TProjective} * Algebraic structures. * */ -impl, S, C> Identity for TransformBase - where N: Scalar + Field, - S: OwnedStorage, DimNameSum>, - C: TCategory, - S::Alloc: OwnedAllocator, DimNameSum, S> { +impl, C> Identity for Transform + where C: TCategory, + DefaultAllocator: Allocator, DimNameSum> { #[inline] fn identity() -> Self { Self::identity() } } -impl, S, C> Inverse for TransformBase - where N: Scalar + Field + ApproxEq, - S: OwnedStorage, DimNameSum>, - C: SubTCategoryOf, - S::Alloc: OwnedAllocator, DimNameSum, S> { +impl, C> Inverse for Transform + where C: SubTCategoryOf, + DefaultAllocator: Allocator, DimNameSum> { #[inline] fn inverse(&self) -> Self { self.clone().inverse() @@ -44,11 +37,9 @@ impl, S, C> Inverse for TransformBase, S, C> AbstractMagma for TransformBase - where N: Scalar + Field, - S: OwnedStorage, DimNameSum>, - C: TCategory, - S::Alloc: OwnedAllocator, DimNameSum, S> { +impl, C> AbstractMagma for Transform + where C: TCategory, + DefaultAllocator: Allocator, DimNameSum> { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs @@ -57,21 +48,17 @@ impl, S, C> AbstractMagma for TransformBase macro_rules! impl_multiplicative_structures( ($($marker: ident<$operator: ident>),* $(,)*) => {$( - impl, S, C> $marker<$operator> for TransformBase - where N: Scalar + Field, - S: OwnedStorage, DimNameSum>, - C: TCategory, - S::Alloc: OwnedAllocator, DimNameSum, S> { } + impl, C> $marker<$operator> for Transform + where C: TCategory, + DefaultAllocator: Allocator, DimNameSum> { } )*} ); macro_rules! impl_inversible_multiplicative_structures( ($($marker: ident<$operator: ident>),* $(,)*) => {$( - impl, S, C> $marker<$operator> for TransformBase - where N: Scalar + Field + ApproxEq, - S: OwnedStorage, DimNameSum>, - C: SubTCategoryOf, - S::Alloc: OwnedAllocator, DimNameSum, S> { } + impl, C> $marker<$operator> for Transform + where C: SubTCategoryOf, + DefaultAllocator: Allocator, DimNameSum> { } )*} ); @@ -91,64 +78,54 @@ impl_inversible_multiplicative_structures!( * Transformation groups. * */ -impl, SA, SB, C> Transformation> for TransformBase +impl, C> Transformation> for Transform where N: Real, - SA: OwnedStorage, DimNameSum>, - SB: OwnedStorage, C: TCategory, - SA::Alloc: OwnedAllocator, DimNameSum, SA> + - Allocator + - Allocator + - Allocator, - SB::Alloc: OwnedAllocator { + DefaultAllocator: Allocator, DimNameSum> + + Allocator> + + Allocator + + Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point) -> Point { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &VectorN) -> VectorN { self * v } } -impl, SA, SB, C> ProjectiveTransformation> for TransformBase +impl, C> ProjectiveTransformation> for Transform where N: Real, - SA: OwnedStorage, DimNameSum>, - SB: OwnedStorage, C: SubTCategoryOf, - SA::Alloc: OwnedAllocator, DimNameSum, SA> + - Allocator + - Allocator + - Allocator, - SB::Alloc: OwnedAllocator { + DefaultAllocator: Allocator, DimNameSum> + + Allocator> + + Allocator + + Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point) -> Point { self.inverse() * pt } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { self.inverse() * v } } // FIXME: we need to implement an SVD for this. // -// impl, SA, SB, C> AffineTransformation> for TransformBase +// impl, C> AffineTransformation> for Transform // where N: Real, -// SA: OwnedStorage, DimNameSum>, -// SB: OwnedStorage, // C: SubTCategoryOf, -// SA::Alloc: OwnedAllocator, DimNameSum, SA> + -// Allocator + -// Allocator + -// Allocator, -// SB::Alloc: OwnedAllocator { -// type PreRotation = OwnedRotation; -// type NonUniformScaling = OwnedColumnVector; -// type PostRotation = OwnedRotation; -// type Translation = OwnedTranslation; +// DefaultAllocator: Allocator, DimNameSum> + +// Allocator + +// Allocator { +// type PreRotation = Rotation; +// type NonUniformScaling = VectorN; +// type PostRotation = Rotation; +// type Translation = Translation; // // #[inline] // fn decompose(&self) -> (Self::Translation, Self::PostRotation, Self::NonUniformScaling, Self::PreRotation) { diff --git a/src/geometry/transform_alias.rs b/src/geometry/transform_alias.rs index c387fb4f..e0235b55 100644 --- a/src/geometry/transform_alias.rs +++ b/src/geometry/transform_alias.rs @@ -1,29 +1,17 @@ -use core::MatrixArray; -use core::dimension::{U1, U2, U3, DimNameSum}; +use core::dimension::{U2, U3}; -use geometry::{TransformBase, TGeneral, TProjective, TAffine}; - -/// A `D`-dimensional general transformation that may not be inversible. Stored as an homogeneous -/// `(D + 1) × (D + 1)` matrix. -pub type Transform = TransformBase, DimNameSum>, TGeneral>; - -/// An inversible `D`-dimensional general transformation. Stored as an homogeneous -/// `(D + 1) × (D + 1)` matrix. -pub type Projective = TransformBase, DimNameSum>, TProjective>; - -/// A `D`-dimensional affine transformation. Stored as an homogeneous `(D + 1) × (D + 1)` matrix. -pub type Affine = TransformBase, DimNameSum>, TAffine>; +use geometry::{Transform, TGeneral, TProjective, TAffine}; /// A 2D general transformation that may not be inversible. Stored as an homogeneous 3x3 matrix. -pub type Transform2 = Transform; +pub type Transform2 = Transform; /// An inversible 2D general transformation. Stored as an homogeneous 3x3 matrix. -pub type Projective2 = Projective; +pub type Projective2 = Transform; /// A 2D affine transformation. Stored as an homogeneous 3x3 matrix. -pub type Affine2 = Affine; +pub type Affine2 = Transform; /// A 3D general transformation that may not be inversible. Stored as an homogeneous 4x4 matrix. -pub type Transform3 = Transform; +pub type Transform3 = Transform; /// An inversible 3D general transformation. Stored as an homogeneous 4x4 matrix. -pub type Projective3 = Projective; +pub type Projective3 = Transform; /// A 3D affine transformation. Stored as an homogeneous 4x4 matrix. -pub type Affine3 = Affine; +pub type Affine3 = Transform; diff --git a/src/geometry/transform_construction.rs b/src/geometry/transform_construction.rs index 9a2dbe98..38daf246 100644 --- a/src/geometry/transform_construction.rs +++ b/src/geometry/transform_construction.rs @@ -1,32 +1,25 @@ -use num::{Zero, One}; +use num::One; -use alga::general::Field; +use alga::general::Real; -use core::{Scalar, OwnedSquareMatrix}; +use core::{DefaultAllocator, MatrixN}; use core::dimension::{DimNameAdd, DimNameSum, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::{TransformBase, TCategory}; +use geometry::{Transform, TCategory}; -impl TransformBase - where N: Scalar + Zero + One, - D: DimNameAdd, - S: OwnedStorage, DimNameSum>, - S::Alloc: OwnedAllocator, DimNameSum, S> { +impl, C: TCategory> Transform + where DefaultAllocator: Allocator, DimNameSum> { /// Creates a new identity transform. #[inline] pub fn identity() -> Self { - Self::from_matrix_unchecked(OwnedSquareMatrix::::identity()) + Self::from_matrix_unchecked(MatrixN::<_, DimNameSum>::identity()) } } -impl One for TransformBase - where N: Scalar + Field, - D: DimNameAdd, - S: OwnedStorage, DimNameSum>, - S::Alloc: OwnedAllocator, DimNameSum, S> { +impl, C: TCategory> One for Transform + where DefaultAllocator: Allocator, DimNameSum> { /// Creates a new identity transform. #[inline] fn one() -> Self { diff --git a/src/geometry/transform_conversion.rs b/src/geometry/transform_conversion.rs index 44267a00..56506474 100644 --- a/src/geometry/transform_conversion.rs +++ b/src/geometry/transform_conversion.rs @@ -1,67 +1,60 @@ -use approx::ApproxEq; +use alga::general::{SubsetOf, Real}; -use alga::general::{SubsetOf, Field}; - -use core::{Scalar, SquareMatrix}; +use core::{DefaultAllocator, MatrixN}; use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::{TransformBase, TCategory, SuperTCategoryOf}; +use geometry::{Transform, TCategory, SuperTCategoryOf}; -impl SubsetOf> for TransformBase - where N1: Scalar + Field + ApproxEq + SubsetOf, - N2: Scalar + Field + ApproxEq, +impl SubsetOf> for Transform + where N1: Real + SubsetOf, + N2: Real, C1: TCategory, C2: SuperTCategoryOf, D: DimNameAdd, - SA: OwnedStorage, DimNameSum>, - SB: OwnedStorage, DimNameSum>, - SA::Alloc: OwnedAllocator, DimNameSum, SA>, - SB::Alloc: OwnedAllocator, DimNameSum, SB>, + DefaultAllocator: Allocator, DimNameSum> + + Allocator, DimNameSum>, N1::Epsilon: Copy, N2::Epsilon: Copy { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf, SB>> for TransformBase - where N1: Scalar + Field + ApproxEq + SubsetOf, - N2: Scalar + Field + ApproxEq, +impl SubsetOf>> for Transform + where N1: Real + SubsetOf, + N2: Real, C: TCategory, D: DimNameAdd, - SA: OwnedStorage, DimNameSum>, - SB: OwnedStorage, DimNameSum>, - SA::Alloc: OwnedAllocator, DimNameSum, SA>, - SB::Alloc: OwnedAllocator, DimNameSum, SB>, + DefaultAllocator: Allocator, DimNameSum> + + Allocator, DimNameSum>, N1::Epsilon: Copy, N2::Epsilon: Copy { #[inline] - fn to_superset(&self) -> SquareMatrix, SB> { + fn to_superset(&self) -> MatrixN> { self.matrix().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + fn is_in_subset(m: &MatrixN>) -> bool { C::check_homogeneous_invariants(m) } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { - TransformBase::from_matrix_unchecked(::convert_ref_unchecked(m)) + unsafe fn from_superset_unchecked(m: &MatrixN>) -> Self { + Transform::from_matrix_unchecked(::convert_ref_unchecked(m)) } } diff --git a/src/geometry/transform_ops.rs b/src/geometry/transform_ops.rs index bacc883e..56a3d048 100644 --- a/src/geometry/transform_ops.rs +++ b/src/geometry/transform_ops.rs @@ -1,17 +1,15 @@ use num::{Zero, One}; use std::ops::{Index, IndexMut, Mul, MulAssign, Div, DivAssign}; -use approx::ApproxEq; -use alga::general::{Field, Real, ClosedAdd, ClosedMul, ClosedNeg, SubsetOf}; +use alga::general::{Real, ClosedAdd, ClosedMul, SubsetOf}; -use core::{Scalar, ColumnVector, OwnedColumnVector, OwnedSquareMatrix}; -use core::storage::{Storage, StorageMut, OwnedStorage}; -use core::allocator::{Allocator, OwnedAllocator}; +use core::{DefaultAllocator, Scalar, VectorN, MatrixN}; +use core::allocator::Allocator; use core::dimension::{DimName, DimNameAdd, DimNameSum, U1, U3, U4}; -use geometry::{PointBase, OwnedPoint, TransformBase, OwnedTransform, TCategory, TCategoryMul, - SubTCategoryOf, SuperTCategoryOf, TGeneral, TProjective, TAffine, RotationBase, - UnitQuaternionBase, IsometryBase, SimilarityBase, TranslationBase}; +use geometry::{Point, Transform, TCategory, TCategoryMul, + SubTCategoryOf, SuperTCategoryOf, TGeneral, TProjective, TAffine, Rotation, + UnitQuaternion, Isometry, Similarity, Translation}; /* * @@ -23,55 +21,55 @@ use geometry::{PointBase, OwnedPoint, TransformBase, OwnedTransform, TCategory, * * (Operators) * - * TransformBase × IsometryBase - * TransformBase × RotationBase - * TransformBase × SimilarityBase - * TransformBase × TransformBase - * TransformBase × UnitQuaternion - * FIXME: TransformBase × UnitComplex - * TransformBase × TranslationBase - * TransformBase × ColumnVector - * TransformBase × PointBase + * Transform × Isometry + * Transform × Rotation + * Transform × Similarity + * Transform × Transform + * Transform × UnitQuaternion + * FIXME: Transform × UnitComplex + * Transform × Translation + * Transform × Vector + * Transform × Point * - * IsometryBase × TransformBase - * RotationBase × TransformBase - * SimilarityBase × TransformBase - * TranslationBase × TransformBase - * UnitQuaternionBase × TransformBase - * FIXME: UnitComplex × TransformBase + * Isometry × Transform + * Rotation × Transform + * Similarity × Transform + * Translation × Transform + * UnitQuaternion × Transform + * FIXME: UnitComplex × Transform * - * FIXME: TransformBase ÷ IsometryBase - * TransformBase ÷ RotationBase - * FIXME: TransformBase ÷ SimilarityBase - * TransformBase ÷ TransformBase - * TransformBase ÷ UnitQuaternion - * TransformBase ÷ TranslationBase + * FIXME: Transform ÷ Isometry + * Transform ÷ Rotation + * FIXME: Transform ÷ Similarity + * Transform ÷ Transform + * Transform ÷ UnitQuaternion + * Transform ÷ Translation * - * FIXME: IsometryBase ÷ TransformBase - * RotationBase ÷ TransformBase - * FIXME: SimilarityBase ÷ TransformBase - * TranslationBase ÷ TransformBase - * UnitQuaternionBase ÷ TransformBase - * FIXME: UnitComplex ÷ TransformBase + * FIXME: Isometry ÷ Transform + * Rotation ÷ Transform + * FIXME: Similarity ÷ Transform + * Translation ÷ Transform + * UnitQuaternion ÷ Transform + * FIXME: UnitComplex ÷ Transform * * * (Assignment Operators) * * - * TransformBase ×= TransformBase - * TransformBase ×= SimilarityBase - * TransformBase ×= IsometryBase - * TransformBase ×= RotationBase - * TransformBase ×= UnitQuaternionBase - * FIXME: TransformBase ×= UnitComplex - * TransformBase ×= TranslationBase + * Transform ×= Transform + * Transform ×= Similarity + * Transform ×= Isometry + * Transform ×= Rotation + * Transform ×= UnitQuaternion + * FIXME: Transform ×= UnitComplex + * Transform ×= Translation * - * TransformBase ÷= TransformBase - * FIXME: TransformBase ÷= SimilarityBase - * FIXME: TransformBase ÷= IsometryBase - * TransformBase ÷= RotationBase - * TransformBase ÷= UnitQuaternionBase - * FIXME: TransformBase ÷= UnitComplex + * Transform ÷= Transform + * FIXME: Transform ÷= Similarity + * FIXME: Transform ÷= Isometry + * Transform ÷= Rotation + * Transform ÷= UnitQuaternion + * FIXME: Transform ÷= UnitComplex * */ @@ -80,10 +78,9 @@ use geometry::{PointBase, OwnedPoint, TransformBase, OwnedTransform, TCategory, * Indexing. * */ -impl Index<(usize, usize)> for TransformBase - where N: Scalar, - D: DimName + DimNameAdd, - S: Storage, DimNameSum> { +impl Index<(usize, usize)> for Transform + where D: DimName + DimNameAdd, + DefaultAllocator: Allocator, DimNameSum> { type Output = N; #[inline] @@ -93,10 +90,9 @@ impl Index<(usize, usize)> for TransformBase } // Only general transformations are mutably indexable. -impl IndexMut<(usize, usize)> for TransformBase - where N: Scalar, - D: DimName + DimNameAdd, - S: StorageMut, DimNameSum> { +impl IndexMut<(usize, usize)> for Transform + where D: DimName + DimNameAdd, + DefaultAllocator: Allocator, DimNameSum> { #[inline] fn index_mut(&mut self, ij: (usize, usize)) -> &mut N { self.matrix_mut().index_mut(ij) @@ -104,14 +100,11 @@ impl IndexMut<(usize, usize)> for TransformBase } -// TransformBase × ColumnVector +// Transform × Vector md_impl_all!( - Mul, mul where N: Field; - (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory - where SA::Alloc: Allocator - where SA::Alloc: Allocator - where SA::Alloc: Allocator; - self: TransformBase, rhs: ColumnVector, Output = OwnedColumnVector; + Mul, mul where N: Real; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory; + self: Transform, rhs: VectorN, Output = VectorN; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; @@ -132,14 +125,12 @@ md_impl_all!( ); -// TransformBase × PointBase +// Transform × Point md_impl_all!( - Mul, mul where N: Field; + Mul, mul where N: Real; (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory - where SA::Alloc: Allocator - where SA::Alloc: Allocator - where SA::Alloc: Allocator; - self: TransformBase, rhs: PointBase, Output = OwnedPoint; + where DefaultAllocator: Allocator; + self: Transform, rhs: Point, Output = Point; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; @@ -161,11 +152,11 @@ md_impl_all!( ); -// TransformBase × TransformBase +// Transform × Transform md_impl_all!( - Mul, mul; + Mul, mul where N: Real; (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategoryMul, CB: TCategory; - self: TransformBase, rhs: TransformBase, Output = OwnedTransform; + self: Transform, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.matrix()); @@ -173,12 +164,11 @@ md_impl_all!( ); -// TransformBase × RotationBase +// Transform × Rotation md_impl_all!( - Mul, mul where N: One; - (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: RotationBase, Output = OwnedTransform; + Mul, mul where N: Real; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul; + self: Transform, rhs: Rotation, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); @@ -186,12 +176,11 @@ md_impl_all!( ); -// RotationBase × TransformBase +// Rotation × Transform md_impl_all!( - Mul, mul where N: One; - (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul - where SA::Alloc: Allocator, DimNameSum >; - self: RotationBase, rhs: TransformBase, Output = OwnedTransform; + Mul, mul where N: Real; + (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul; + self: Rotation, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -199,13 +188,11 @@ md_impl_all!( ); -// TransformBase × UnitQuaternionBase +// Transform × UnitQuaternion md_impl_all!( Mul, mul where N: Real; - (U4, U4), (U4, U1) for C: TCategoryMul - where SB::Alloc: Allocator - where SB::Alloc: Allocator; - self: TransformBase, rhs: UnitQuaternionBase, Output = OwnedTransform; + (U4, U4), (U4, U1) for C: TCategoryMul; + self: Transform, rhs: UnitQuaternion, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); @@ -213,13 +200,11 @@ md_impl_all!( ); -// UnitQuaternionBase × TransformBase +// UnitQuaternion × Transform md_impl_all!( Mul, mul where N: Real; - (U4, U1), (U4, U4) for C: TCategoryMul - where SA::Alloc: Allocator - where SA::Alloc: Allocator; - self: UnitQuaternionBase, rhs: TransformBase, Output = OwnedTransform; + (U4, U1), (U4, U4) for C: TCategoryMul; + self: UnitQuaternion, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -228,26 +213,24 @@ md_impl_all!( -// TransformBase × IsometryBase +// Transform × Isometry md_impl_all!( Mul, mul where N: Real; (DimNameSum, DimNameSum), (D, U1) - for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: IsometryBase, Output = OwnedTransform; + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> >; + self: Transform, rhs: Isometry, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); ); -// IsometryBase × TransformBase +// Isometry × Transform md_impl_all!( Mul, mul where N: Real; (D, U1), (DimNameSum, DimNameSum) - for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > - where SA::Alloc: Allocator, DimNameSum >; - self: IsometryBase, rhs: TransformBase, Output = OwnedTransform; + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> >; + self: Isometry, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -255,28 +238,24 @@ md_impl_all!( ); -// TransformBase × SimilarityBase +// Transform × Similarity md_impl_all!( Mul, mul where N: Real; (DimNameSum, DimNameSum), (D, U1) - for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > - where SB::Alloc: Allocator - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: SimilarityBase, Output = OwnedTransform; + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> >; + self: Transform, rhs: Similarity, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); ); -// SimilarityBase × TransformBase +// Similarity × Transform md_impl_all!( Mul, mul where N: Real; (D, U1), (DimNameSum, DimNameSum) - for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > - where SA::Alloc: Allocator - where SA::Alloc: Allocator, DimNameSum >; - self: SimilarityBase, rhs: TransformBase, Output = OwnedTransform; + for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> >; + self: Similarity, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -293,25 +272,23 @@ md_impl_all!( * `DimNameAdd` requirement). * */ -// TransformBase × TranslationBase +// Transform × Translation md_impl_all!( Mul, mul where N: Real; - (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: TranslationBase, Output = OwnedTransform; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul; + self: Transform, rhs: Translation, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); ); -// TranslationBase × TransformBase +// Translation × Transform md_impl_all!( Mul, mul where N: Real; (D, U1), (DimNameSum, DimNameSum) - for D: DimNameAdd, C: TCategoryMul - where SA::Alloc: Allocator, DimNameSum >; - self: TranslationBase, rhs: TransformBase, Output = OwnedTransform; + for D: DimNameAdd, C: TCategoryMul; + self: Translation, rhs: Transform, Output = Transform; [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -320,23 +297,22 @@ md_impl_all!( -// TransformBase ÷ TransformBase +// Transform ÷ Transform md_impl_all!( - Div, div where N: ApproxEq, Field; + Div, div where N: Real; (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategoryMul, CB: SubTCategoryOf; - self: TransformBase, rhs: TransformBase, Output = OwnedTransform; + self: Transform, rhs: Transform, Output = Transform; [val val] => self * rhs.inverse(); [ref val] => self * rhs.inverse(); - [val ref] => self * rhs.clone_owned().inverse(); - [ref ref] => self * rhs.clone_owned().inverse(); + [val ref] => self * rhs.clone().inverse(); + [ref ref] => self * rhs.clone().inverse(); ); -// TransformBase ÷ RotationBase +// Transform ÷ Rotation md_impl_all!( - Div, div where N: One; - (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategoryMul - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: RotationBase, Output = OwnedTransform; + Div, div where N: Real; + (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(); @@ -344,12 +320,11 @@ md_impl_all!( ); -// RotationBase ÷ TransformBase +// Rotation ÷ Transform md_impl_all!( - Div, div where N: One; - (D, D), (DimNameSum, DimNameSum) for D: DimNameAdd, C: TCategoryMul - where SA::Alloc: Allocator, DimNameSum >; - self: RotationBase, rhs: TransformBase, Output = OwnedTransform; + Div, div where N: Real; + (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; @@ -357,13 +332,11 @@ md_impl_all!( ); -// TransformBase ÷ UnitQuaternionBase +// Transform ÷ UnitQuaternion md_impl_all!( Div, div where N: Real; - (U4, U4), (U4, U1) for C: TCategoryMul - where SB::Alloc: Allocator - where SB::Alloc: Allocator; - self: TransformBase, rhs: UnitQuaternionBase, Output = OwnedTransform; + (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(); @@ -371,13 +344,11 @@ md_impl_all!( ); -// UnitQuaternionBase ÷ TransformBase +// UnitQuaternion ÷ Transform md_impl_all!( Div, div where N: Real; - (U4, U1), (U4, U4) for C: TCategoryMul - where SA::Alloc: Allocator - where SA::Alloc: Allocator; - self: UnitQuaternionBase, rhs: TransformBase, Output = OwnedTransform; + (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; @@ -386,26 +357,26 @@ md_impl_all!( -// // TransformBase ÷ IsometryBase +// // Transform ÷ Isometry // md_impl_all!( // Div, div where N: Real; // (DimNameSum, DimNameSum), (D, U1) -// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> > // where SB::Alloc: Allocator, DimNameSum >; -// self: TransformBase, rhs: IsometryBase, Output = OwnedTransform; +// self: Transform, rhs: Isometry, Output = Transform; // [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.inverse().to_homogeneous()); // [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.inverse().to_homogeneous()); // [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.inverse().to_homogeneous()); // [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.inverse().to_homogeneous()); // ); -// // IsometryBase ÷ TransformBase +// // Isometry ÷ Transform // md_impl_all!( // Div, div where N: Real; // (D, U1), (DimNameSum, DimNameSum) -// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> > // where SA::Alloc: Allocator, DimNameSum >; -// self: IsometryBase, rhs: TransformBase, Output = OwnedTransform; +// self: Isometry, rhs: Transform, Output = Transform; // [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); // [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); // [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -413,28 +384,28 @@ md_impl_all!( // ); -// // TransformBase ÷ SimilarityBase +// // Transform ÷ Similarity // md_impl_all!( // Div, div where N: Real; // (DimNameSum, DimNameSum), (D, U1) -// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SB::Alloc> > +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> > // where SB::Alloc: Allocator // where SB::Alloc: Allocator, DimNameSum >; -// self: TransformBase, rhs: SimilarityBase, Output = OwnedTransform; +// self: Transform, rhs: Similarity, Output = Transform; // [val val] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); // [ref val] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); // [val ref] => Self::Output::from_matrix_unchecked(self.unwrap() * rhs.to_homogeneous()); // [ref ref] => Self::Output::from_matrix_unchecked(self.matrix() * rhs.to_homogeneous()); // ); -// // SimilarityBase ÷ TransformBase +// // Similarity ÷ Transform // md_impl_all!( // Div, div where N: Real; // (D, U1), (DimNameSum, DimNameSum) -// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf, SA::Alloc> > +// for D: DimNameAdd, C: TCategoryMul, R: SubsetOf> > // where SA::Alloc: Allocator // where SA::Alloc: Allocator, DimNameSum >; -// self: SimilarityBase, rhs: TransformBase, Output = OwnedTransform; +// self: Similarity, rhs: Transform, Output = Transform; // [val val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); // [ref val] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.unwrap()); // [val ref] => Self::Output::from_matrix_unchecked(self.to_homogeneous() * rhs.matrix()); @@ -443,25 +414,23 @@ md_impl_all!( -// TransformBase ÷ TranslationBase +// Transform ÷ Translation md_impl_all!( Div, div where N: Real; - (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategoryMul - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: TranslationBase, Output = OwnedTransform; + (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(); ); -// TranslationBase ÷ TransformBase +// Translation ÷ Transform md_impl_all!( Div, div where N: Real; (D, U1), (DimNameSum, DimNameSum) - for D: DimNameAdd, C: TCategoryMul - where SA::Alloc: Allocator, DimNameSum >; - self: TranslationBase, rhs: TransformBase, Output = OwnedTransform; + 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; @@ -469,36 +438,33 @@ md_impl_all!( ); -// TransformBase ×= TransformBase +// Transform ×= Transform md_assign_impl_all!( - MulAssign, mul_assign; + MulAssign, mul_assign where N: Real; (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) for D: DimNameAdd, CA: TCategory, CB: SubTCategoryOf; - self: TransformBase, rhs: TransformBase; + self: Transform, rhs: Transform; [val] => *self.matrix_mut_unchecked() *= rhs.unwrap(); [ref] => *self.matrix_mut_unchecked() *= rhs.matrix(); ); -// TransformBase ×= SimilarityBase +// Transform ×= Similarity md_assign_impl_all!( - MulAssign, mul_assign; + MulAssign, mul_assign where N: Real; (DimNameSum, DimNameSum), (D, U1) - for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > - where SB::Alloc: Allocator, DimNameSum > - where SB::Alloc: Allocator; - self: TransformBase, rhs: SimilarityBase; + for D: DimNameAdd, C: TCategory, R: SubsetOf> >; + self: Transform, rhs: Similarity; [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); ); -// TransformBase ×= IsometryBase +// Transform ×= Isometry md_assign_impl_all!( - MulAssign, mul_assign; + MulAssign, mul_assign where N: Real; (DimNameSum, DimNameSum), (D, U1) - for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: IsometryBase; + for D: DimNameAdd, C: TCategory, R: SubsetOf> >; + self: Transform, rhs: Isometry; [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); ); @@ -511,105 +477,94 @@ md_assign_impl_all!( * `DimNameAdd` requirement). * */ -// TransformBase ×= TranslationBase -md_assign_impl_all!( - MulAssign, mul_assign where N: One; - (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: TranslationBase; - [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); - [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); -); - - -// TransformBase ×= RotationBase -md_assign_impl_all!( - MulAssign, mul_assign where N: One; - (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: RotationBase; - [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); - [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); -); - - -// TransformBase ×= UnitQuaternionBase +// Transform ×= Translation md_assign_impl_all!( MulAssign, mul_assign where N: Real; - (U4, U4), (U4, U1) for C: TCategory - where SB::Alloc: Allocator - where SB::Alloc: Allocator; - self: TransformBase, rhs: UnitQuaternionBase; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory; + self: Transform, rhs: Translation; [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); ); -// TransformBase ÷= TransformBase +// Transform ×= Rotation md_assign_impl_all!( - DivAssign, div_assign where N: Field, ApproxEq; - (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) - for D: DimNameAdd, CA: SuperTCategoryOf, CB: SubTCategoryOf; - self: TransformBase, rhs: TransformBase; - [val] => *self *= rhs.clone_owned().inverse(); - [ref] => *self *= rhs.clone_owned().inverse(); + MulAssign, mul_assign where N: Real; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory; + self: Transform, rhs: Rotation; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); ); -// // TransformBase ÷= SimilarityBase -// md_assign_impl_all!( -// DivAssign, div_assign; -// (DimNameSum, DimNameSum), (D, U1) -// for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > -// where SB::Alloc: Allocator, DimNameSum > -// where SB::Alloc: Allocator; -// self: TransformBase, rhs: SimilarityBase; -// [val] => *self *= rhs.inverse(); -// [ref] => *self *= rhs.inverse(); -// ); -// -// -// // TransformBase ÷= IsometryBase -// md_assign_impl_all!( -// DivAssign, div_assign; -// (DimNameSum, DimNameSum), (D, U1) -// for D: DimNameAdd, C: TCategory, R: SubsetOf, SB::Alloc> > -// where SB::Alloc: Allocator, DimNameSum >; -// self: TransformBase, rhs: IsometryBase; -// [val] => *self *= rhs.inverse(); -// [ref] => *self *= rhs.inverse(); -// ); - - -// TransformBase ÷= TranslationBase +// Transform ×= UnitQuaternion md_assign_impl_all!( - DivAssign, div_assign where N: One, ClosedNeg; - (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: TranslationBase; - [val] => *self *= rhs.inverse(); - [ref] => *self *= rhs.inverse(); + MulAssign, mul_assign where N: Real; + (U4, U4), (U4, U1) for C: TCategory; + self: Transform, rhs: UnitQuaternion; + [val] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); + [ref] => *self.matrix_mut_unchecked() *= rhs.to_homogeneous(); ); -// TransformBase ÷= RotationBase -md_assign_impl_all!( - DivAssign, div_assign where N: One; - (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory - where SB::Alloc: Allocator, DimNameSum >; - self: TransformBase, rhs: RotationBase; - [val] => *self *= rhs.inverse(); - [ref] => *self *= rhs.inverse(); -); - - -// TransformBase ÷= UnitQuaternionBase +// Transform ÷= Transform md_assign_impl_all!( DivAssign, div_assign where N: Real; - (U4, U4), (U4, U1) for C: TCategory - where SB::Alloc: Allocator - where SB::Alloc: Allocator; - self: TransformBase, rhs: UnitQuaternionBase; + (DimNameSum, DimNameSum), (DimNameSum, DimNameSum) + for D: DimNameAdd, CA: SuperTCategoryOf, CB: SubTCategoryOf; + self: Transform, rhs: Transform; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.clone().inverse(); +); + + +// // Transform ÷= Similarity +// md_assign_impl_all!( +// DivAssign, div_assign; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategory, R: SubsetOf> >; +// self: Transform, rhs: Similarity; +// [val] => *self *= rhs.inverse(); +// [ref] => *self *= rhs.inverse(); +// ); +// +// +// // Transform ÷= Isometry +// md_assign_impl_all!( +// DivAssign, div_assign; +// (DimNameSum, DimNameSum), (D, U1) +// for D: DimNameAdd, C: TCategory, R: SubsetOf> >; +// self: Transform, rhs: Isometry; +// [val] => *self *= rhs.inverse(); +// [ref] => *self *= rhs.inverse(); +// ); + + +// Transform ÷= Translation +md_assign_impl_all!( + DivAssign, div_assign where N: Real; + (DimNameSum, DimNameSum), (D, U1) for D: DimNameAdd, C: TCategory; + self: Transform, rhs: Translation; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// Transform ÷= Rotation +md_assign_impl_all!( + DivAssign, div_assign where N: Real; + (DimNameSum, DimNameSum), (D, D) for D: DimNameAdd, C: TCategory; + self: Transform, rhs: Rotation; + [val] => *self *= rhs.inverse(); + [ref] => *self *= rhs.inverse(); +); + + +// Transform ÷= UnitQuaternion +md_assign_impl_all!( + DivAssign, div_assign where N: Real; + (U4, U4), (U4, U1) for C: TCategory; + self: Transform, rhs: UnitQuaternion; [val] => *self *= rhs.inverse(); [ref] => *self *= rhs.inverse(); ); diff --git a/src/geometry/translation.rs b/src/geometry/translation.rs index 9c4d26e1..68d89f42 100644 --- a/src/geometry/translation.rs +++ b/src/geometry/translation.rs @@ -1,63 +1,58 @@ use num::{Zero, One}; +use std::hash; use std::fmt; use approx::ApproxEq; #[cfg(feature = "serde-serialize")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde; #[cfg(feature = "abomonation-serialize")] use abomonation::Abomonation; use alga::general::{Real, ClosedNeg}; -use core::{Scalar, ColumnVector, OwnedSquareMatrix}; +use core::{DefaultAllocator, Scalar, MatrixN, VectorN}; use core::dimension::{DimName, DimNameSum, DimNameAdd, U1}; -use core::storage::{Storage, StorageMut, Owned}; +use core::storage::Owned; use core::allocator::Allocator; -/// A translation with an owned vector storage. -pub type OwnedTranslation = TranslationBase>::Alloc>>; - /// A translation. #[repr(C)] -#[derive(Hash, Debug, Clone, Copy)] -pub struct TranslationBase*/> { +#[derive(Debug)] +pub struct Translation + where DefaultAllocator: Allocator { /// The translation coordinates, i.e., how much is added to a point's coordinates when it is /// translated. - pub vector: ColumnVector + pub vector: VectorN } -#[cfg(feature = "serde-serialize")] -impl Serialize for TranslationBase - where N: Scalar, - D: DimName, - ColumnVector: Serialize, -{ - fn serialize(&self, serializer: T) -> Result - where T: Serializer - { - self.vector.serialize(serializer) +impl hash::Hash for Translation + where DefaultAllocator: Allocator, + Owned: hash::Hash { + fn hash(&self, state: &mut H) { + self.vector.hash(state) } } -#[cfg(feature = "serde-serialize")] -impl<'de, N, D, S> Deserialize<'de> for TranslationBase - where N: Scalar, - D: DimName, - ColumnVector: Deserialize<'de>, -{ - fn deserialize(deserializer: T) -> Result - where T: Deserializer<'de> - { - ColumnVector::deserialize(deserializer).map(|x| TranslationBase { vector: x }) +impl Copy for Translation + where DefaultAllocator: Allocator, + Owned: Copy { } + +impl Clone for Translation + where DefaultAllocator: Allocator, + Owned: Clone { + #[inline] + fn clone(&self) -> Self { + Translation::from_vector(self.vector.clone()) } } #[cfg(feature = "abomonation-serialize")] -impl Abomonation for TranslationBase +impl Abomonation for TranslationBase where N: Scalar, D: DimName, - ColumnVector: Abomonation + VectorN: Abomonation, + DefaultAllocator: Allocator { unsafe fn entomb(&self, writer: &mut Vec) { self.vector.entomb(writer) @@ -72,65 +67,81 @@ impl Abomonation for TranslationBase } } -impl TranslationBase - where N: Scalar, - S: Storage { +#[cfg(feature = "serde-serialize")] +impl serde::Serialize for Translation +where DefaultAllocator: Allocator, + Owned: serde::Serialize { + + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + self.vector.serialize(serializer) + } +} + +#[cfg(feature = "serde-serialize")] +impl<'a, N: Scalar, D: DimName> serde::Deserialize<'a> for Translation +where DefaultAllocator: Allocator, + Owned: serde::Deserialize<'a> { + + fn deserialize(deserializer: Des) -> Result + where Des: serde::Deserializer<'a> { + let matrix = VectorN::::deserialize(deserializer)?; + + Ok(Translation::from_vector(matrix)) + } +} + +impl Translation + where DefaultAllocator: Allocator { /// Creates a new translation from the given vector. #[inline] - pub fn from_vector(vector: ColumnVector) -> TranslationBase { - TranslationBase { + pub fn from_vector(vector: VectorN) -> Translation { + Translation { vector: vector } } /// Inverts `self`. #[inline] - pub fn inverse(&self) -> OwnedTranslation + pub fn inverse(&self) -> Translation where N: ClosedNeg { - TranslationBase::from_vector(-&self.vector) + Translation::from_vector(-&self.vector) } /// Converts this translation into its equivalent homogeneous transformation matrix. #[inline] - pub fn to_homogeneous(&self) -> OwnedSquareMatrix, S::Alloc> + pub fn to_homogeneous(&self) -> MatrixN> where N: Zero + One, D: DimNameAdd, - S::Alloc: Allocator, DimNameSum> { - let mut res = OwnedSquareMatrix::::identity(); + DefaultAllocator: Allocator, DimNameSum> { + let mut res = MatrixN::>::identity(); res.fixed_slice_mut::(0, D::dim()).copy_from(&self.vector); res } -} - -impl TranslationBase - where N: Scalar + ClosedNeg, - S: StorageMut { /// Inverts `self` in-place. #[inline] - pub fn inverse_mut(&mut self) { + pub fn inverse_mut(&mut self) + where N: ClosedNeg { self.vector.neg_mut() } } -impl Eq for TranslationBase - where N: Scalar + Eq, - S: Storage { +impl Eq for Translation + where DefaultAllocator: Allocator { } -impl PartialEq for TranslationBase - where N: Scalar + PartialEq, - S: Storage { +impl PartialEq for Translation + where DefaultAllocator: Allocator { #[inline] - fn eq(&self, right: &TranslationBase) -> bool { + fn eq(&self, right: &Translation) -> bool { self.vector == right.vector } } -impl ApproxEq for TranslationBase - where N: Scalar + ApproxEq, - S: Storage, +impl ApproxEq for Translation + where DefaultAllocator: Allocator, N::Epsilon: Copy { type Epsilon = N::Epsilon; @@ -165,31 +176,14 @@ impl ApproxEq for TranslationBase * Display * */ -impl fmt::Display for TranslationBase - where N: Real + fmt::Display, - S: Storage, - S::Alloc: Allocator { +impl fmt::Display for Translation + where DefaultAllocator: Allocator + + Allocator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let precision = f.precision().unwrap_or(3); - try!(writeln!(f, "TranslationBase {{")); + try!(writeln!(f, "Translation {{")); try!(write!(f, "{:.*}", precision, self.vector)); writeln!(f, "}}") } } - - -// // /* -// // * -// // * Absolute -// // * -// // */ -// // impl Absolute for $t { -// // type AbsoluteValue = $submatrix; -// // -// // #[inline] -// // fn abs(m: &$t) -> $submatrix { -// // Absolute::abs(&m.submatrix) -// // } -// // } -// */ diff --git a/src/geometry/translation_alga.rs b/src/geometry/translation_alga.rs index fcac3860..b0fbdbcc 100644 --- a/src/geometry/translation_alga.rs +++ b/src/geometry/translation_alga.rs @@ -1,14 +1,14 @@ use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, AbstractQuasigroup, AbstractSemigroup, Real, Inverse, Multiplicative, Identity, Id}; use alga::linear::{Transformation, ProjectiveTransformation, Similarity, AffineTransformation, - Isometry, DirectIsometry, Translation}; + Isometry, DirectIsometry}; +use alga::linear::Translation as AlgaTranslation; -use core::ColumnVector; -use core::dimension::{DimName, U1}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::{DefaultAllocator, VectorN}; +use core::dimension::DimName; +use core::allocator::Allocator; -use geometry::{TranslationBase, PointBase}; +use geometry::{Translation, Point}; /* @@ -16,20 +16,16 @@ use geometry::{TranslationBase, PointBase}; * Algebraic structures. * */ -impl Identity for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Identity for Translation + where DefaultAllocator: Allocator { #[inline] fn identity() -> Self { Self::identity() } } -impl Inverse for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Inverse for Translation + where DefaultAllocator: Allocator { #[inline] fn inverse(&self) -> Self { self.inverse() @@ -41,10 +37,8 @@ impl Inverse for TranslationBase } } -impl AbstractMagma for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AbstractMagma for Translation + where DefaultAllocator: Allocator { #[inline] fn operate(&self, rhs: &Self) -> Self { self * rhs @@ -53,10 +47,8 @@ impl AbstractMagma for TranslationBase),* $(,)*) => {$( - impl $marker<$operator> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $marker<$operator> for Translation + where DefaultAllocator: Allocator { } )*} ); @@ -73,40 +65,34 @@ impl_multiplicative_structures!( * Transformation groups. * */ -impl Transformation> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Transformation> for Translation + where DefaultAllocator: Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point) -> Point { pt + &self.vector } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &VectorN) -> VectorN { v.clone() } } -impl ProjectiveTransformation> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for Translation + where DefaultAllocator: Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point) -> Point { pt - &self.vector } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &VectorN) -> VectorN { v.clone() } } -impl AffineTransformation> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AffineTransformation> for Translation + where DefaultAllocator: Allocator { type Rotation = Id; type NonUniformScaling = Id; type Translation = Self; @@ -148,10 +134,9 @@ impl AffineTransformation> for TranslationB } -impl Similarity> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Similarity> for Translation + where DefaultAllocator: Allocator { + type Scaling = Id; #[inline] @@ -172,10 +157,8 @@ impl Similarity> for TranslationBase {$( - impl $Trait> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $Trait> for Translation + where DefaultAllocator: Allocator { } )*} ); @@ -183,17 +166,15 @@ marker_impl!(Isometry, DirectIsometry); /// Subgroups of the n-dimensional translation group `T(n)`. -impl Translation> for TranslationBase - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AlgaTranslation> for Translation + where DefaultAllocator: Allocator { #[inline] - fn to_vector(&self) -> ColumnVector { + fn to_vector(&self) -> VectorN { self.vector.clone() } #[inline] - fn from_vector(v: ColumnVector) -> Option { + fn from_vector(v: VectorN) -> Option { Some(Self::from_vector(v)) } @@ -203,7 +184,7 @@ impl Translation> for TranslationBase, b: &PointBase) -> Option { + fn translation_between(a: &Point, b: &Point) -> Option { Some(Self::from_vector(b - a)) } } diff --git a/src/geometry/translation_alias.rs b/src/geometry/translation_alias.rs index b607d6af..c6dfcf4b 100644 --- a/src/geometry/translation_alias.rs +++ b/src/geometry/translation_alias.rs @@ -1,10 +1,6 @@ -use core::MatrixArray; -use core::dimension::{U1, U2, U3}; +use core::dimension::{U2, U3}; -use geometry::TranslationBase; - -/// A D-dimensional translation. -pub type Translation = TranslationBase>; +use geometry::Translation; /// A 2-dimensional translation. pub type Translation2 = Translation; diff --git a/src/geometry/translation_construction.rs b/src/geometry/translation_construction.rs index 92bbe342..1030f34a 100644 --- a/src/geometry/translation_construction.rs +++ b/src/geometry/translation_construction.rs @@ -1,43 +1,39 @@ #[cfg(feature = "arbitrary")] use quickcheck::{Arbitrary, Gen}; +#[cfg(feature = "arbitrary")] +use core::storage::Owned; use num::{Zero, One}; use rand::{Rng, Rand}; use alga::general::ClosedAdd; -use core::{ColumnVector, Scalar}; +use core::{DefaultAllocator, Scalar, VectorN}; use core::dimension::{DimName, U1, U2, U3, U4, U5, U6}; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; +use core::allocator::Allocator; -use geometry::TranslationBase; +use geometry::Translation; + +impl Translation + where DefaultAllocator: Allocator { -impl TranslationBase - where N: Scalar + Zero, - S: OwnedStorage, - S::Alloc: OwnedAllocator { /// Creates a new square identity rotation of the given `dimension`. #[inline] - pub fn identity() -> TranslationBase { - Self::from_vector(ColumnVector::::from_element(N::zero())) + pub fn identity() -> Translation { + Self::from_vector(VectorN::::from_element(N::zero())) } } -impl One for TranslationBase - where N: Scalar + Zero + ClosedAdd, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl One for Translation + where DefaultAllocator: Allocator { #[inline] fn one() -> Self { Self::identity() } } -impl Rand for TranslationBase - where N: Scalar + Rand, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rand for Translation + where DefaultAllocator: Allocator { #[inline] fn rand(rng: &mut G) -> Self { Self::from_vector(rng.gen()) @@ -46,10 +42,9 @@ impl Rand for TranslationBase #[cfg(feature = "arbitrary")] -impl Arbitrary for TranslationBase - where N: Scalar + Arbitrary + Send, - S: OwnedStorage + Send, - S::Alloc: OwnedAllocator { +impl Arbitrary for Translation + where DefaultAllocator: Allocator, + Owned: Send { #[inline] fn arbitrary(rng: &mut G) -> Self { Self::from_vector(Arbitrary::arbitrary(rng)) @@ -63,14 +58,12 @@ impl Arbitrary for TranslationBase */ macro_rules! componentwise_constructors_impl( ($($D: ty, $($args: ident:$irow: expr),*);* $(;)*) => {$( - impl TranslationBase - where N: Scalar, - S: OwnedStorage, - S::Alloc: OwnedAllocator { + impl Translation + where DefaultAllocator: Allocator { /// Initializes this matrix from its components. #[inline] pub fn new($($args: N),*) -> Self { - Self::from_vector(ColumnVector::::new($($args),*)) + Self::from_vector(VectorN::::new($($args),*)) } } )*} diff --git a/src/geometry/translation_conversion.rs b/src/geometry/translation_conversion.rs index c449a54f..e6c8a4b5 100644 --- a/src/geometry/translation_conversion.rs +++ b/src/geometry/translation_conversion.rs @@ -1,146 +1,133 @@ use alga::general::{SubsetOf, SupersetOf, Real}; use alga::linear::Rotation; -use core::{Scalar, ColumnVector, SquareMatrix}; +use core::{DefaultAllocator, Scalar, VectorN, MatrixN}; use core::dimension::{DimName, DimNameAdd, DimNameSum, U1}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; +use core::allocator::Allocator; -use geometry::{PointBase, TranslationBase, IsometryBase, SimilarityBase, TransformBase, SuperTCategoryOf, TAffine}; +use geometry::{Point, Translation, Isometry, Similarity, Transform, SuperTCategoryOf, TAffine}; /* * This file provides the following conversions: * ============================================= * - * TranslationBase -> TranslationBase - * TranslationBase -> IsometryBase - * TranslationBase -> SimilarityBase - * TranslationBase -> TransformBase - * TranslationBase -> Matrix (homogeneous) + * Translation -> Translation + * Translation -> Isometry + * Translation -> Similarity + * Translation -> Transform + * Translation -> Matrix (homogeneous) */ -impl SubsetOf> for TranslationBase +impl SubsetOf> for Translation where N1: Scalar, N2: Scalar + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> TranslationBase { - TranslationBase::from_vector(self.vector.to_superset()) + fn to_superset(&self) -> Translation { + Translation::from_vector(self.vector.to_superset()) } #[inline] - fn is_in_subset(rot: &TranslationBase) -> bool { - ::is_convertible::<_, ColumnVector>(&rot.vector) + fn is_in_subset(rot: &Translation) -> bool { + ::is_convertible::<_, VectorN>(&rot.vector) } #[inline] - unsafe fn from_superset_unchecked(rot: &TranslationBase) -> Self { - TranslationBase::from_vector(rot.vector.to_subset_unchecked()) + unsafe fn from_superset_unchecked(rot: &Translation) -> Self { + Translation::from_vector(rot.vector.to_subset_unchecked()) } } -impl SubsetOf> for TranslationBase +impl SubsetOf> for Translation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: Rotation>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: Rotation>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> IsometryBase { - IsometryBase::from_parts(self.to_superset(), R::identity()) + fn to_superset(&self) -> Isometry { + Isometry::from_parts(self.to_superset(), R::identity()) } #[inline] - fn is_in_subset(iso: &IsometryBase) -> bool { + fn is_in_subset(iso: &Isometry) -> bool { iso.rotation == R::identity() } #[inline] - unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + unsafe fn from_superset_unchecked(iso: &Isometry) -> Self { Self::from_superset_unchecked(&iso.translation) } } -impl SubsetOf> for TranslationBase +impl SubsetOf> for Translation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, - R: Rotation>, - SA::Alloc: OwnedAllocator, - SB::Alloc: OwnedAllocator { + R: Rotation>, + DefaultAllocator: Allocator + + Allocator { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_parts(self.to_superset(), R::identity(), N2::one()) + fn to_superset(&self) -> Similarity { + Similarity::from_parts(self.to_superset(), R::identity(), N2::one()) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { + fn is_in_subset(sim: &Similarity) -> bool { sim.isometry.rotation == R::identity() && sim.scaling() == N2::one() } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { Self::from_superset_unchecked(&sim.isometry.translation) } } -impl SubsetOf> for TranslationBase +impl SubsetOf> for Translation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, C: SuperTCategoryOf, D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator, DimNameSum>, - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + - Allocator, D> { + DefaultAllocator: Allocator + + Allocator + + Allocator, DimNameSum> + + Allocator, DimNameSum> { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf, SB>> for TranslationBase +impl SubsetOf>> for Translation where N1: Real, N2: Real + SupersetOf, - SA: OwnedStorage, - SB: OwnedStorage, DimNameSum>, D: DimNameAdd, - SA::Alloc: OwnedAllocator + - Allocator, DimNameSum>, - SB::Alloc: OwnedAllocator, DimNameSum, SB> + - Allocator + - Allocator, D> { + DefaultAllocator: Allocator + + Allocator + + Allocator, DimNameSum> + + Allocator, DimNameSum> { #[inline] - fn to_superset(&self) -> SquareMatrix, SB> { + fn to_superset(&self) -> MatrixN> { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix, SB>) -> bool { + fn is_in_subset(m: &MatrixN>) -> bool { let id = m.fixed_slice::, D>(0, 0); // Scalar types agree. @@ -152,7 +139,7 @@ impl SubsetOf, SB>> for Tr } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix, SB>) -> Self { + unsafe fn from_superset_unchecked(m: &MatrixN>) -> Self { let t = m.fixed_slice::(0, D::dim()); Self::from_vector(::convert_unchecked(t.into_owned())) } diff --git a/src/geometry/translation_ops.rs b/src/geometry/translation_ops.rs index 08db5ac6..08284524 100644 --- a/src/geometry/translation_ops.rs +++ b/src/geometry/translation_ops.rs @@ -2,167 +2,166 @@ use std::ops::{Mul, MulAssign, Div, DivAssign}; use alga::general::{ClosedAdd, ClosedSub}; -use core::Scalar; +use core::{DefaultAllocator, Scalar}; use core::dimension::{DimName, U1}; use core::constraint::{ShapeConstraint, SameNumberOfRows, SameNumberOfColumns}; -use core::storage::{OwnedStorage, Storage}; -use core::allocator::{OwnedAllocator, SameShapeAllocator}; +use core::allocator::{Allocator, SameShapeAllocator}; -use geometry::{PointBase, OwnedPoint, TranslationBase, OwnedTranslation}; +use geometry::{Point, Translation}; -// TranslationBase × TranslationBase +// Translation × Translation add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: &'a TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(&self.vector + &right.vector); 'a, 'b); + self: &'a Translation, right: &'b Translation, Output = Translation; + Translation::from_vector(&self.vector + &right.vector); 'a, 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: &'a TranslationBase, right: TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(&self.vector + right.vector); 'a); + self: &'a Translation, right: Translation, Output = Translation; + Translation::from_vector(&self.vector + right.vector); 'a); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(self.vector + &right.vector); 'b); + self: Translation, right: &'b Translation, Output = Translation; + Translation::from_vector(self.vector + &right.vector); 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(self.vector + right.vector); ); + self: Translation, right: Translation, Output = Translation; + Translation::from_vector(self.vector + right.vector); ); -// TranslationBase ÷ TranslationBase +// Translation ÷ Translation // FIXME: instead of calling inverse explicitely, 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 TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(&self.vector - &right.vector); 'a, 'b); + self: &'a Translation, right: &'b Translation, Output = Translation; + Translation::from_vector(&self.vector - &right.vector); 'a, 'b); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; - self: &'a TranslationBase, right: TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(&self.vector - right.vector); 'a); + self: &'a Translation, right: Translation, Output = Translation; + Translation::from_vector(&self.vector - right.vector); 'a); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: &'b TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(self.vector - &right.vector); 'b); + self: Translation, right: &'b Translation, Output = Translation; + Translation::from_vector(self.vector - &right.vector); 'b); add_sub_impl!(Div, div, ClosedSub; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: TranslationBase, Output = OwnedTranslation; - TranslationBase::from_vector(self.vector - right.vector); ); + self: Translation, right: Translation, Output = Translation; + Translation::from_vector(self.vector - right.vector); ); -// TranslationBase × PointBase +// Translation × Point // FIXME: we don't handle properly non-zero origins here. Do we want this to be the intended // behavior? add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: &'a TranslationBase, right: &'b PointBase, Output = OwnedPoint; + self: &'a Translation, right: &'b Point, Output = Point; right + &self.vector; 'a, 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: &'a TranslationBase, right: PointBase, Output = OwnedPoint; + self: &'a Translation, right: Point, Output = Point; right + &self.vector; 'a); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: &'b PointBase, Output = OwnedPoint; + self: Translation, right: &'b Point, Output = Point; right + self.vector; 'b); add_sub_impl!(Mul, mul, ClosedAdd; (D, U1), (D, U1) -> (D) for D: DimName; - self: TranslationBase, right: PointBase, Output = OwnedPoint; + self: Translation, right: Point, Output = Point; right + self.vector; ); -// TranslationBase *= TranslationBase +// Translation *= Translation add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; (D, U1), (D, U1) for D: DimName; - self: TranslationBase, right: &'b TranslationBase; + self: Translation, right: &'b Translation; self.vector += &right.vector; 'b); add_sub_assign_impl!(MulAssign, mul_assign, ClosedAdd; (D, U1), (D, U1) for D: DimName; - self: TranslationBase, right: TranslationBase; + self: Translation, right: Translation; self.vector += right.vector; ); add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: TranslationBase, right: &'b TranslationBase; + self: Translation, right: &'b Translation; self.vector -= &right.vector; 'b); add_sub_assign_impl!(DivAssign, div_assign, ClosedSub; (D, U1), (D, U1) for D: DimName; - self: TranslationBase, right: TranslationBase; + self: Translation, right: Translation; self.vector -= right.vector; ); /* -// TranslationBase × Matrix +// Translation × Matrix add_sub_impl!(Mul, mul; (D1, D1), (R2, C2) for D1, R2, C2; - self: &'a TranslationBase, right: &'b Matrix, Output = MatrixMul; + 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 TranslationBase, right: Matrix, Output = MatrixMul; + 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: TranslationBase, right: &'b Matrix, Output = MatrixMul; + 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: TranslationBase, right: Matrix, Output = MatrixMul; + self: Translation, right: Matrix, Output = MatrixMN; self.unwrap() * right; ); -// Matrix × TranslationBase +// Matrix × Translation add_sub_impl!(Mul, mul; (R1, C1), (D2, D2) for R1, C1, D2; - self: &'a Matrix, right: &'b TranslationBase, Output = MatrixMul; + 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: TranslationBase, Output = MatrixMul; + 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 TranslationBase, Output = MatrixMul; + 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: TranslationBase, Output = MatrixMul; + self: Matrix, right: Translation, Output = MatrixMN; self * right.unwrap(); ); -// Matrix *= TranslationBase +// Matrix *= Translation md_assign_impl!(MulAssign, mul_assign; (R1, C1), (C1, C1) for R1, C1; - self: Matrix, right: &'b TranslationBase; + 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: TranslationBase; + 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 TranslationBase; + 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: TranslationBase; + self: Matrix, right: Translation; self.mul_assign(right.inverse().unwrap()); ); */ diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 8d371234..61400e21 100644 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -3,105 +3,10 @@ use approx::ApproxEq; use num_complex::Complex; use alga::general::Real; -use core::{Unit, SquareMatrix, Vector1, Matrix3}; -use core::dimension::U2; +use core::{Unit, Vector1, Matrix2, Matrix3}; use geometry::Rotation2; /// A complex number with a norm equal to 1. -/// -///
-/// -/// Due to a [bug](https://github.com/rust-lang/rust/issues/32077) in rustdoc, the documentation -/// below has been written manually lists only method signatures.
-/// Trait implementations are not listed either. -///
-///
-/// -/// Please refer directly to the documentation written above each function definition on the source -/// code for more details. -/// -/// -///

Methods

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

-/// -///

pub type UnitComplex = Unit>; impl UnitComplex { @@ -111,6 +16,18 @@ impl UnitComplex { self.im.atan2(self.re) } + /// The sine of the rotation angle. + #[inline] + pub fn sin_angle(&self) -> N { + self.im + } + + /// The cosine of the rotation angle. + #[inline] + pub fn cos_angle(&self) -> N { + self.re + } + /// The rotation angle returned as a 1-dimensional vector. #[inline] pub fn scaled_axis(&self) -> Vector1 { @@ -180,12 +97,8 @@ impl UnitComplex { let r = self.re; let i = self.im; - Rotation2::from_matrix_unchecked( - SquareMatrix::<_, U2, _>::new( - r, -i, - i, r - ) - ) + Rotation2::from_matrix_unchecked(Matrix2::new(r, -i, + i, r)) } /// Converts this unit complex number into its equivalent homogeneous transformation matrix. diff --git a/src/geometry/unit_complex_alga.rs b/src/geometry/unit_complex_alga.rs index 9c03b354..f86db26a 100644 --- a/src/geometry/unit_complex_alga.rs +++ b/src/geometry/unit_complex_alga.rs @@ -3,11 +3,10 @@ use alga::general::{AbstractMagma, AbstractGroup, AbstractLoop, AbstractMonoid, use alga::linear::{Transformation, AffineTransformation, Similarity, Isometry, DirectIsometry, OrthogonalTransformation, Rotation, ProjectiveTransformation}; -use core::ColumnVector; -use core::storage::OwnedStorage; -use core::allocator::OwnedAllocator; -use core::dimension::{U1, U2}; -use geometry::{PointBase, UnitComplex}; +use core::{DefaultAllocator, Vector2}; +use core::allocator::Allocator; +use core::dimension::U2; +use geometry::{Point2, UnitComplex}; /* * @@ -56,42 +55,36 @@ impl_structures!( AbstractGroup ); -impl Transformation> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Transformation> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn transform_point(&self, pt: &PointBase) -> PointBase { + fn transform_point(&self, pt: &Point2) -> Point2 { self * pt } #[inline] - fn transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn transform_vector(&self, v: &Vector2) -> Vector2 { self * v } } -impl ProjectiveTransformation> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl ProjectiveTransformation> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn inverse_transform_point(&self, pt: &PointBase) -> PointBase { + fn inverse_transform_point(&self, pt: &Point2) -> Point2 { // FIXME: would it be useful performancewise not to call inverse explicitly (i-e. implement // the inverse transformation explicitly here) ? self.inverse() * pt } #[inline] - fn inverse_transform_vector(&self, v: &ColumnVector) -> ColumnVector { + fn inverse_transform_vector(&self, v: &Vector2) -> Vector2 { self.inverse() * v } } -impl AffineTransformation> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl AffineTransformation> for UnitComplex + where DefaultAllocator: Allocator { type Rotation = Self; type NonUniformScaling = Id; type Translation = Id; @@ -132,10 +125,8 @@ impl AffineTransformation> for UnitComplex } } -impl Similarity> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Similarity> for UnitComplex + where DefaultAllocator: Allocator { type Scaling = Id; #[inline] @@ -156,10 +147,8 @@ impl Similarity> for UnitComplex macro_rules! marker_impl( ($($Trait: ident),*) => {$( - impl $Trait> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { } + impl $Trait> for UnitComplex + where DefaultAllocator: Allocator { } )*} ); @@ -167,22 +156,20 @@ marker_impl!(Isometry, DirectIsometry, OrthogonalTransformation); -impl Rotation> for UnitComplex - where N: Real, - S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl Rotation> for UnitComplex + where DefaultAllocator: Allocator { #[inline] fn powf(&self, n: N) -> Option { Some(self.powf(n)) } #[inline] - fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Option { + fn rotation_between(a: &Vector2, b: &Vector2) -> Option { Some(Self::rotation_between(a, b)) } #[inline] - fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Option { + fn scaled_rotation_between(a: &Vector2, b: &Vector2, s: N) -> Option { Some(Self::scaled_rotation_between(a, b, s)) } } diff --git a/src/geometry/unit_complex_construction.rs b/src/geometry/unit_complex_construction.rs index 8be17a63..d7a478ef 100644 --- a/src/geometry/unit_complex_construction.rs +++ b/src/geometry/unit_complex_construction.rs @@ -6,10 +6,11 @@ use num_complex::Complex; use rand::{Rand, Rng}; use alga::general::Real; -use core::ColumnVector; +use core::{DefaultAllocator, Vector}; use core::dimension::{U1, U2}; use core::storage::Storage; -use geometry::{UnitComplex, RotationBase}; +use core::allocator::Allocator; +use geometry::{UnitComplex, Rotation}; impl UnitComplex { @@ -22,8 +23,8 @@ impl UnitComplex { /// Builds the unit complex number corresponding to the rotation with the angle. #[inline] pub fn new(angle: N) -> Self { - let (s, c) = angle.sin_cos(); - UnitComplex::new_unchecked(Complex::new(c, s)) + let (sin, cos) = angle.sin_cos(); + Self::from_cos_sin_unchecked(cos, sin) } /// Builds the unit complex number corresponding to the rotation with the angle. @@ -34,11 +35,19 @@ impl UnitComplex { Self::new(angle) } + /// Builds the unit complex number frow the sinus and cosinus of the rotation angle. + /// + /// The input values are not checked. + #[inline] + pub fn from_cos_sin_unchecked(cos: N, sin: N) -> Self { + UnitComplex::new_unchecked(Complex::new(cos, sin)) + } + /// Builds a unit complex rotation from an angle in radian wrapped in a 1-dimensional vector. /// /// Equivalent to `Self::new(axisangle[0])`. #[inline] - pub fn from_scaled_axis>(axisangle: ColumnVector) -> Self { + pub fn from_scaled_axis>(axisangle: Vector) -> Self { Self::from_angle(axisangle[0]) } @@ -47,19 +56,29 @@ impl UnitComplex { /// The input complex number will be normalized. #[inline] pub fn from_complex(q: Complex) -> Self { - Self::new_unchecked(q / (q.im * q.im + q.re * q.re).sqrt()) + Self::from_complex_and_get(q).0 + } + + /// Creates a new unit complex number from a complex number. + /// + /// The input complex number will be normalized. Returns the complex number norm as well. + #[inline] + pub fn from_complex_and_get(q: Complex) -> (Self, N) { + let norm = (q.im * q.im + q.re * q.re).sqrt(); + (Self::new_unchecked(q / norm), norm) } /// Builds the unit complex number from the corresponding 2D rotation matrix. #[inline] - pub fn from_rotation_matrix>(rotmat: &RotationBase) -> Self { + pub fn from_rotation_matrix(rotmat: &Rotation) -> Self + where DefaultAllocator: Allocator { Self::new_unchecked(Complex::new(rotmat[(0, 0)], rotmat[(1, 0)])) } /// The unit complex needed to make `a` and `b` be collinear and point toward the same /// direction. #[inline] - pub fn rotation_between(a: &ColumnVector, b: &ColumnVector) -> Self + pub fn rotation_between(a: &Vector, b: &Vector) -> Self where SB: Storage, SC: Storage { Self::scaled_rotation_between(a, b, N::one()) @@ -68,7 +87,7 @@ impl UnitComplex { /// The smallest rotation needed to make `a` and `b` collinear and point toward the same /// direction, raised to the power `s`. #[inline] - pub fn scaled_rotation_between(a: &ColumnVector, b: &ColumnVector, s: N) -> Self + pub fn scaled_rotation_between(a: &Vector, b: &Vector, s: N) -> Self where SB: Storage, SC: Storage { if let (Some(na), Some(nb)) = (a.try_normalize(N::zero()), b.try_normalize(N::zero())) { diff --git a/src/geometry/unit_complex_conversion.rs b/src/geometry/unit_complex_conversion.rs index b5d3bdf2..ef2bdaca 100644 --- a/src/geometry/unit_complex_conversion.rs +++ b/src/geometry/unit_complex_conversion.rs @@ -4,22 +4,20 @@ use num_complex::Complex; use alga::general::{SubsetOf, SupersetOf, Real}; use alga::linear::Rotation as AlgaRotation; -use core::SquareMatrix; -use core::dimension::{U1, U2, U3}; -use core::storage::OwnedStorage; -use core::allocator::{Allocator, OwnedAllocator}; -use geometry::{PointBase, UnitComplex, RotationBase, OwnedRotation, IsometryBase, - SimilarityBase, TransformBase, SuperTCategoryOf, TAffine, TranslationBase}; +use core::Matrix3; +use core::dimension::U2; +use geometry::{UnitComplex, Isometry, Similarity, Transform, SuperTCategoryOf, TAffine, Translation, + Point2, Rotation2}; /* * This file provides the following conversions: * ============================================= * * UnitComplex -> UnitComplex - * UnitComplex -> RotationBase - * UnitComplex -> IsometryBase - * UnitComplex -> SimilarityBase - * UnitComplex -> TransformBase + * UnitComplex -> Rotation + * UnitComplex -> Isometry + * UnitComplex -> Similarity + * UnitComplex -> Transform * UnitComplex -> Matrix (homogeneous) * * NOTE: @@ -45,129 +43,106 @@ impl SubsetOf> for UnitComplex } } -impl SubsetOf> for UnitComplex +impl SubsetOf> for UnitComplex where N1: Real, - N2: Real + SupersetOf, - S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator + - Allocator + - Allocator { + N2: Real + SupersetOf { #[inline] - fn to_superset(&self) -> RotationBase { + fn to_superset(&self) -> Rotation2 { let q: UnitComplex = self.to_superset(); q.to_rotation_matrix().to_superset() } #[inline] - fn is_in_subset(rot: &RotationBase) -> bool { - ::is_convertible::<_, OwnedRotation>(rot) + fn is_in_subset(rot: &Rotation2) -> bool { + ::is_convertible::<_, Rotation2>(rot) } #[inline] - unsafe fn from_superset_unchecked(rot: &RotationBase) -> Self { + unsafe fn from_superset_unchecked(rot: &Rotation2) -> Self { let q = UnitComplex::::from_rotation_matrix(rot); ::convert_unchecked(q) } } -impl SubsetOf> for UnitComplex +impl SubsetOf> for UnitComplex where N1: Real, N2: Real + SupersetOf, - S: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - S::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf> { #[inline] - fn to_superset(&self) -> IsometryBase { - IsometryBase::from_parts(TranslationBase::identity(), ::convert_ref(self)) + fn to_superset(&self) -> Isometry { + Isometry::from_parts(Translation::identity(), ::convert_ref(self)) } #[inline] - fn is_in_subset(iso: &IsometryBase) -> bool { + fn is_in_subset(iso: &Isometry) -> bool { iso.translation.vector.is_zero() } #[inline] - unsafe fn from_superset_unchecked(iso: &IsometryBase) -> Self { + unsafe fn from_superset_unchecked(iso: &Isometry) -> Self { ::convert_ref_unchecked(&iso.rotation) } } -impl SubsetOf> for UnitComplex +impl SubsetOf> for UnitComplex where N1: Real, N2: Real + SupersetOf, - S: OwnedStorage, - R: AlgaRotation> + SupersetOf>, - S::Alloc: OwnedAllocator { + R: AlgaRotation> + SupersetOf> { #[inline] - fn to_superset(&self) -> SimilarityBase { - SimilarityBase::from_isometry(::convert_ref(self), N2::one()) + fn to_superset(&self) -> Similarity { + Similarity::from_isometry(::convert_ref(self), N2::one()) } #[inline] - fn is_in_subset(sim: &SimilarityBase) -> bool { + fn is_in_subset(sim: &Similarity) -> bool { sim.isometry.translation.vector.is_zero() && sim.scaling() == N2::one() } #[inline] - unsafe fn from_superset_unchecked(sim: &SimilarityBase) -> Self { + unsafe fn from_superset_unchecked(sim: &Similarity) -> Self { ::convert_ref_unchecked(&sim.isometry) } } -impl SubsetOf> for UnitComplex +impl SubsetOf> for UnitComplex where N1: Real, N2: Real + SupersetOf, - S: OwnedStorage, - C: SuperTCategoryOf, - S::Alloc: OwnedAllocator + - Allocator + - Allocator + - Allocator + - Allocator { + C: SuperTCategoryOf { #[inline] - fn to_superset(&self) -> TransformBase { - TransformBase::from_matrix_unchecked(self.to_homogeneous().to_superset()) + fn to_superset(&self) -> Transform { + Transform::from_matrix_unchecked(self.to_homogeneous().to_superset()) } #[inline] - fn is_in_subset(t: &TransformBase) -> bool { + fn is_in_subset(t: &Transform) -> bool { >::is_in_subset(t.matrix()) } #[inline] - unsafe fn from_superset_unchecked(t: &TransformBase) -> Self { + unsafe fn from_superset_unchecked(t: &Transform) -> Self { Self::from_superset_unchecked(t.matrix()) } } -impl SubsetOf> for UnitComplex - where N1: Real, - N2: Real + SupersetOf, - S: OwnedStorage, - S::Alloc: OwnedAllocator + - Allocator + - Allocator + - Allocator + - Allocator { +impl> SubsetOf> for UnitComplex { #[inline] - fn to_superset(&self) -> SquareMatrix { + fn to_superset(&self) -> Matrix3 { self.to_homogeneous().to_superset() } #[inline] - fn is_in_subset(m: &SquareMatrix) -> bool { - ::is_convertible::<_, OwnedRotation>(m) + fn is_in_subset(m: &Matrix3) -> bool { + ::is_convertible::<_, Rotation2>(m) } #[inline] - unsafe fn from_superset_unchecked(m: &SquareMatrix) -> Self { - let rot: OwnedRotation = ::convert_ref_unchecked(m); + unsafe fn from_superset_unchecked(m: &Matrix3) -> Self { + let rot: Rotation2 = ::convert_ref_unchecked(m); Self::from_rotation_matrix(&rot) } } diff --git a/src/geometry/unit_complex_ops.rs b/src/geometry/unit_complex_ops.rs index f7331903..e45ba465 100644 --- a/src/geometry/unit_complex_ops.rs +++ b/src/geometry/unit_complex_ops.rs @@ -1,49 +1,46 @@ use std::ops::{Mul, MulAssign, Div, DivAssign}; use alga::general::Real; -use core::{Unit, ColumnVector, OwnedColumnVector}; -use core::dimension::{U1, U2}; -use core::storage::{Storage, OwnedStorage}; -use core::allocator::OwnedAllocator; -use geometry::{UnitComplex, RotationBase, - PointBase, OwnedPoint, - IsometryBase, OwnedIsometryBase, - SimilarityBase, OwnedSimilarityBase, - TranslationBase}; +use core::{DefaultAllocator, Unit, Vector, Vector2, Matrix}; +use core::dimension::{Dim, U1, U2}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; +use core::constraint::{ShapeConstraint, DimEq}; +use geometry::{UnitComplex, Rotation, Isometry, Similarity, Translation, Point2}; /* * This file provides: * =================== * * UnitComplex × UnitComplex - * UnitComplex × RotationBase -> UnitComplex - * RotationBase × UnitComplex -> UnitComplex + * UnitComplex × Rotation -> UnitComplex + * Rotation × UnitComplex -> UnitComplex * * UnitComplex ÷ UnitComplex - * UnitComplex ÷ RotationBase -> UnitComplex - * RotationBase ÷ UnitComplex -> UnitComplex + * UnitComplex ÷ Rotation -> UnitComplex + * Rotation ÷ UnitComplex -> UnitComplex * * - * UnitComplex × PointBase - * UnitComplex × ColumnVector + * UnitComplex × Point + * UnitComplex × Vector * UnitComplex × Unit * - * UnitComplex × IsometryBase - * UnitComplex × SimilarityBase - * UnitComplex × TranslationBase -> IsometryBase + * UnitComplex × Isometry + * UnitComplex × Similarity + * UnitComplex × Translation -> Isometry * * NOTE: -UnitComplex is already provided by `Unit`. * * (Assignment Operators) * * UnitComplex ×= UnitComplex - * UnitComplex ×= RotationBase + * UnitComplex ×= Rotation * * UnitComplex ÷= UnitComplex - * UnitComplex ÷= RotationBase + * UnitComplex ÷= Rotation * - * RotationBase ×= UnitComplex - * RotationBase ÷= UnitComplex + * Rotation ×= UnitComplex + * Rotation ÷= UnitComplex * */ @@ -123,12 +120,11 @@ impl<'a, 'b, N: Real> Div<&'b UnitComplex> for &'a UnitComplex { macro_rules! complex_op_impl( ($Op: ident, $op: ident; - ($RDim: ident, $CDim: ident); + ($RDim: ident, $CDim: ident) $(for $Storage: ident: $StoragesBound: ident $(<$($BoundParam: ty),*>)*),*; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; $action: expr; $($lives: tt),*) => { - impl<$($lives ,)* N, S> $Op<$Rhs> for $Lhs - where N: Real, - S: Storage { + impl<$($lives ,)* N: Real $(, $Storage: $StoragesBound $(<$($BoundParam),*>)*)*> $Op<$Rhs> for $Lhs + where DefaultAllocator: Allocator { type Output = $Result; #[inline] @@ -141,7 +137,7 @@ macro_rules! complex_op_impl( macro_rules! complex_op_impl_all( ($Op: ident, $op: ident; - ($RDim: ident, $CDim: ident); + ($RDim: ident, $CDim: ident) $(for $Storage: ident: $StoragesBound: ident $(<$($BoundParam: ty),*>)*),*; $lhs: ident: $Lhs: ty, $rhs: ident: $Rhs: ty, Output = $Result: ty; [val val] => $action_val_val: expr; [ref val] => $action_ref_val: expr; @@ -149,22 +145,22 @@ macro_rules! complex_op_impl_all( [ref ref] => $action_ref_ref: expr;) => { complex_op_impl!($Op, $op; - ($RDim, $CDim); + ($RDim, $CDim) $(for $Storage: $StoragesBound $(<$($BoundParam),*>)*),*; $lhs: $Lhs, $rhs: $Rhs, Output = $Result; $action_val_val; ); complex_op_impl!($Op, $op; - ($RDim, $CDim); + ($RDim, $CDim) $(for $Storage: $StoragesBound $(<$($BoundParam),*>)*),*; $lhs: &'a $Lhs, $rhs: $Rhs, Output = $Result; $action_ref_val; 'a); complex_op_impl!($Op, $op; - ($RDim, $CDim); + ($RDim, $CDim) $(for $Storage: $StoragesBound $(<$($BoundParam),*>)*),*; $lhs: $Lhs, $rhs: &'b $Rhs, Output = $Result; $action_val_ref; 'b); complex_op_impl!($Op, $op; - ($RDim, $CDim); + ($RDim, $CDim) $(for $Storage: $StoragesBound $(<$($BoundParam),*>)*),*; $lhs: &'a $Lhs, $rhs: &'b $Rhs, Output = $Result; $action_ref_ref; 'a, 'b); @@ -173,22 +169,22 @@ macro_rules! complex_op_impl_all( ); -// UnitComplex × RotationBase +// UnitComplex × Rotation complex_op_impl_all!( Mul, mul; (U2, U2); - self: UnitComplex, rhs: RotationBase, Output = UnitComplex; + self: UnitComplex, rhs: Rotation, Output = UnitComplex; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => self * UnitComplex::from_rotation_matrix(rhs); ); -// UnitComplex ÷ RotationBase +// UnitComplex ÷ Rotation complex_op_impl_all!( Div, div; (U2, U2); - self: UnitComplex, rhs: RotationBase, Output = UnitComplex; + self: UnitComplex, rhs: Rotation, Output = UnitComplex; [val val] => &self / &rhs; [ref val] => self / &rhs; [val ref] => &self / rhs; @@ -196,102 +192,102 @@ complex_op_impl_all!( ); -// RotationBase × UnitComplex +// Rotation × UnitComplex complex_op_impl_all!( Mul, mul; (U2, U2); - self: RotationBase, rhs: UnitComplex, Output = UnitComplex; + self: Rotation, rhs: UnitComplex, Output = UnitComplex; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => UnitComplex::from_rotation_matrix(self) * rhs; ); -// RotationBase ÷ UnitComplex +// Rotation ÷ UnitComplex complex_op_impl_all!( Div, div; (U2, U2); - self: RotationBase, rhs: UnitComplex, Output = UnitComplex; + self: Rotation, rhs: UnitComplex, Output = UnitComplex; [val val] => &self / &rhs; [ref val] => self / &rhs; [val ref] => &self / rhs; [ref ref] => UnitComplex::from_rotation_matrix(self) * rhs.inverse(); ); -// UnitComplex × PointBase +// UnitComplex × Point complex_op_impl_all!( Mul, mul; (U2, U1); - self: UnitComplex, rhs: PointBase, Output = OwnedPoint; + self: UnitComplex, rhs: Point2, Output = Point2; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; - [ref ref] => PointBase::from_coordinates(self * &rhs.coords); + [ref ref] => Point2::from_coordinates(self * &rhs.coords); ); -// UnitComplex × ColumnVector +// UnitComplex × Vector complex_op_impl_all!( Mul, mul; - (U2, U1); - self: UnitComplex, rhs: ColumnVector, Output = OwnedColumnVector; + (U2, U1) for S: Storage; + self: UnitComplex, rhs: Vector, Output = Vector2; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => { let i = self.as_ref().im; let r = self.as_ref().re; - OwnedColumnVector::<_, U2, S::Alloc>::new(r * rhs[0] - i * rhs[1], i * rhs[0] + r * rhs[1]) + Vector2::new(r * rhs[0] - i * rhs[1], i * rhs[0] + r * rhs[1]) }; ); // UnitComplex × Unit complex_op_impl_all!( Mul, mul; - (U2, U1); - self: UnitComplex, rhs: Unit>, Output = Unit>; + (U2, U1) for S: Storage; + self: UnitComplex, rhs: Unit>, Output = Unit>; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => Unit::new_unchecked(self * rhs.as_ref()); ); -// UnitComplex × IsometryBase +// UnitComplex × Isometry complex_op_impl_all!( Mul, mul; (U2, U1); - self: UnitComplex, rhs: IsometryBase>, - Output = OwnedIsometryBase>; + self: UnitComplex, rhs: Isometry>, + Output = Isometry>; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; [ref ref] => { let shift = self * &rhs.translation.vector; - IsometryBase::from_parts(TranslationBase::from_vector(shift), self * &rhs.rotation) + Isometry::from_parts(Translation::from_vector(shift), self * &rhs.rotation) }; ); -// UnitComplex × SimilarityBase +// UnitComplex × Similarity complex_op_impl_all!( Mul, mul; (U2, U1); - self: UnitComplex, rhs: SimilarityBase>, - Output = OwnedSimilarityBase>; + self: UnitComplex, rhs: Similarity>, + Output = Similarity>; [val val] => &self * &rhs; [ref val] => self * &rhs; [val ref] => &self * rhs; - [ref ref] => SimilarityBase::from_isometry(self * &rhs.isometry, rhs.scaling()); + [ref ref] => Similarity::from_isometry(self * &rhs.isometry, rhs.scaling()); ); -// UnitComplex × TranslationBase +// UnitComplex × Translation complex_op_impl_all!( Mul, mul; (U2, U1); - self: UnitComplex, rhs: TranslationBase, - Output = OwnedIsometryBase>; - [val val] => IsometryBase::from_parts(TranslationBase::from_vector(&self * rhs.vector), self); - [ref val] => IsometryBase::from_parts(TranslationBase::from_vector( self * rhs.vector), self.clone()); - [val ref] => IsometryBase::from_parts(TranslationBase::from_vector(&self * &rhs.vector), self); - [ref ref] => IsometryBase::from_parts(TranslationBase::from_vector( self * &rhs.vector), self.clone()); + self: UnitComplex, rhs: Translation, + Output = Isometry>; + [val val] => Isometry::from_parts(Translation::from_vector(&self * rhs.vector), self); + [ref val] => Isometry::from_parts(Translation::from_vector( self * rhs.vector), self.clone()); + [val ref] => Isometry::from_parts(Translation::from_vector(&self * &rhs.vector), self); + [ref ref] => Isometry::from_parts(Translation::from_vector( self * &rhs.vector), self.clone()); ); // UnitComplex ×= UnitComplex @@ -325,71 +321,113 @@ impl<'b, N: Real> DivAssign<&'b UnitComplex> for UnitComplex { } -// UnitComplex ×= RotationBase -impl> MulAssign> for UnitComplex { +// UnitComplex ×= Rotation +impl MulAssign> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn mul_assign(&mut self, rhs: RotationBase) { + fn mul_assign(&mut self, rhs: Rotation) { *self = &*self * rhs } } -impl<'b, N: Real, S: Storage> MulAssign<&'b RotationBase> for UnitComplex { +impl<'b, N: Real> MulAssign<&'b Rotation> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn mul_assign(&mut self, rhs: &'b RotationBase) { + fn mul_assign(&mut self, rhs: &'b Rotation) { *self = &*self * rhs } } -// UnitComplex ÷= RotationBase -impl> DivAssign> for UnitComplex { +// UnitComplex ÷= Rotation +impl DivAssign> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn div_assign(&mut self, rhs: RotationBase) { + fn div_assign(&mut self, rhs: Rotation) { *self = &*self / rhs } } -impl<'b, N: Real, S: Storage> DivAssign<&'b RotationBase> for UnitComplex { +impl<'b, N: Real> DivAssign<&'b Rotation> for UnitComplex + where DefaultAllocator: Allocator { #[inline] - fn div_assign(&mut self, rhs: &'b RotationBase) { + fn div_assign(&mut self, rhs: &'b Rotation) { *self = &*self / rhs } } -// RotationBase ×= UnitComplex -impl MulAssign> for RotationBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { +// Rotation ×= UnitComplex +impl MulAssign> for Rotation + where DefaultAllocator: Allocator { #[inline] fn mul_assign(&mut self, rhs: UnitComplex) { self.mul_assign(rhs.to_rotation_matrix()) } } -impl<'b, N: Real, S: Storage> MulAssign<&'b UnitComplex> for RotationBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl<'b, N: Real> MulAssign<&'b UnitComplex> for Rotation + where DefaultAllocator: Allocator { #[inline] fn mul_assign(&mut self, rhs: &'b UnitComplex) { self.mul_assign(rhs.to_rotation_matrix()) } } -// RotationBase ÷= UnitComplex -impl DivAssign> for RotationBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { +// Rotation ÷= UnitComplex +impl DivAssign> for Rotation + where DefaultAllocator: Allocator { #[inline] fn div_assign(&mut self, rhs: UnitComplex) { self.div_assign(rhs.to_rotation_matrix()) } } -impl<'b, N: Real, S: Storage> DivAssign<&'b UnitComplex> for RotationBase - where S: OwnedStorage, - S::Alloc: OwnedAllocator { +impl<'b, N: Real> DivAssign<&'b UnitComplex> for Rotation + where DefaultAllocator: Allocator { #[inline] fn div_assign(&mut self, rhs: &'b UnitComplex) { self.div_assign(rhs.to_rotation_matrix()) } } + +// Matrix = UnitComplex * Matrix +impl UnitComplex { + /// Performs the multiplication `rhs = self * rhs` in-place. + pub fn rotate>(&self, rhs: &mut Matrix) + where ShapeConstraint: DimEq { + + assert_eq!(rhs.nrows(), 2, "Unit complex rotation: the input matrix must have exactly two rows."); + let i = self.as_ref().im; + let r = self.as_ref().re; + + for j in 0 .. rhs.ncols() { + unsafe { + let a = *rhs.get_unchecked(0, j); + let b = *rhs.get_unchecked(1, j); + + *rhs.get_unchecked_mut(0, j) = r * a - i * b; + *rhs.get_unchecked_mut(1, j) = i * a + r * b; + } + } + } + + /// Performs the multiplication `lhs = lhs * self` in-place. + pub fn rotate_rows>(&self, lhs: &mut Matrix) + where ShapeConstraint: DimEq { + + assert_eq!(lhs.ncols(), 2, "Unit complex rotation: the input matrix must have exactly two columns."); + let i = self.as_ref().im; + let r = self.as_ref().re; + + // FIXME: can we optimize that to iterate on one column at a time ? + for j in 0 .. lhs.nrows() { + unsafe { + let a = *lhs.get_unchecked(j, 0); + let b = *lhs.get_unchecked(j, 1); + + *lhs.get_unchecked_mut(j, 0) = r * a + i * b; + *lhs.get_unchecked_mut(j, 1) = -i * a + r * b; + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 22421a02..b5e5e3b2 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.12" +nalgebra = "0.13" ``` @@ -50,7 +50,7 @@ an optimized set of tools for computer graphics and physics. Those features incl * Matrices and vectors with compile-time sizes are statically allocated while dynamic ones are allocated on the heap. * Convenient aliases for low-dimensional matrices and vectors: `Vector1` to `Vector6` and - `Matrix1x1` to `Matrix6x6` (including rectangular matrices like `Matrix2x5`. + `Matrix1x1` to `Matrix6x6`, including rectangular matrices like `Matrix2x5`. * Points sizes known at compile time, and convenience aliases: `Point1` to `Point6`. * Translation (seen as a transformation that composes by multiplication): `Translation2`, `Translation3`. @@ -66,7 +66,8 @@ an optimized set of tools for computer graphics and physics. Those features incl * General transformations that does not have to be invertible, stored as an homogeneous matrix: `Transform2`, `Transform3`. * 3D projections for computer graphics: `Perspective3`, `Orthographic3`. -* Linear algebra and data analysis operators: QR decomposition, eigen-decomposition. +* Matrix factorizations: `Cholesky`, `QR`, `LU`, `FullPivLU`, `SVD`, `RealSchur`, `Hessenberg`, `SymmetricEigen`. +* Insertion and removal of rows of columns of a matrix. * Implements traits from the [alga](https://crates.io/crates/alga) crate for generic programming. */ @@ -84,7 +85,7 @@ an optimized set of tools for computer graphics and physics. Those features incl #![deny(non_upper_case_globals)] #![deny(unused_qualifications)] #![deny(unused_results)] -#![warn(missing_docs)] +#![deny(missing_docs)] #![doc(html_root_url = "http://nalgebra.org/rustdoc")] #[cfg(feature = "arbitrary")] @@ -106,17 +107,20 @@ extern crate rand; extern crate approx; extern crate typenum; extern crate generic_array; +extern crate matrixmultiply; extern crate alga; pub mod core; +pub mod linalg; pub mod geometry; -mod traits; +#[cfg(feature = "debug")] +pub mod debug; pub use core::*; +pub use linalg::*; pub use geometry::*; -pub use traits::*; use std::cmp::{self, PartialOrd, Ordering}; @@ -127,7 +131,7 @@ use alga::general::{Identity, SupersetOf, MeetSemilattice, JoinSemilattice, Latt use alga::linear::SquareMatrix as AlgaSquareMatrix; use alga::linear::{InnerSpace, NormedSpace, FiniteDimVectorSpace, EuclideanSpace}; -pub use alga::general::Id; +pub use alga::general::{Real, Id}; /* diff --git a/src/linalg/balancing.rs b/src/linalg/balancing.rs new file mode 100644 index 00000000..fd391648 --- /dev/null +++ b/src/linalg/balancing.rs @@ -0,0 +1,82 @@ +//! Functions for balancing a matrix. + +use std::ops::{DivAssign, MulAssign}; +use alga::general::Real; + +use core::{DefaultAllocator, MatrixN, VectorN}; +use core::dimension::{Dim, U1}; +use core::storage::Storage; +use allocator::Allocator; + +/// Applies in-place a modified Parlett and Reinsch matrix balancing with 2-norm to the matrix `m` and returns +/// the corresponding diagonal transformation. +/// +/// See https://arxiv.org/pdf/1401.5766.pdf +pub fn balance_parlett_reinsch(m: &mut MatrixN) -> VectorN + where DefaultAllocator: Allocator + + Allocator { + assert!(m.is_square(), "Unable to balance a non-square matrix."); + + let dim = m.data.shape().0; + let radix: N = ::convert(2.0f64); + let mut d = VectorN::from_element_generic(dim, U1, N::one()); + + let mut converged = false; + + while !converged { + converged = true; + + for i in 0 .. dim.value() { + let mut c = m.column(i).norm_squared(); + let mut r = m.row(i).norm_squared(); + let mut f = N::one(); + + let s = c + r; + c = c.sqrt(); + r = r.sqrt(); + + if c.is_zero() || r.is_zero() { + continue; + } + + while c < r / radix { + c *= radix; + r /= radix; + f *= radix; + } + + while c >= r * radix { + c /= radix; + r *= radix; + f /= radix; + } + + let eps: N = ::convert(0.95); + if c * c + r * r < eps * s { + converged = false; + d[i] *= f; + m.column_mut(i).mul_assign(f); + m.row_mut(i).div_assign(f); + } + } + } + + d +} + +/// Computes in-place `D * m * D.inverse()`, where `D` is the matrix with diagonal `d`. +pub fn unbalance(m: &mut MatrixN, d: &VectorN) + where DefaultAllocator: Allocator + + Allocator { + assert!(m.is_square(), "Unable to unbalance a non-square matrix."); + assert_eq!(m.nrows(), d.len(), "Unbalancing: mismatched dimensions."); + + for j in 0 .. d.len() { + let mut col = m.column_mut(j); + let denom = N::one() / d[j]; + + for i in 0 .. d.len() { + col[i] *= d[i] * denom; + } + } +} diff --git a/src/linalg/bidiagonal.rs b/src/linalg/bidiagonal.rs new file mode 100644 index 00000000..2ccbb24a --- /dev/null +++ b/src/linalg/bidiagonal.rs @@ -0,0 +1,304 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; +use core::{Unit, Matrix, MatrixN, MatrixMN, VectorN, DefaultAllocator}; +use dimension::{Dim, DimMin, DimMinimum, DimSub, DimDiff, Dynamic, U1}; +use storage::Storage; +use allocator::Allocator; +use constraint::{ShapeConstraint, DimEq}; + +use linalg::householder; +use geometry::Reflection; + + +/// The bidiagonalization of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator> + + Allocator, U1>>, + MatrixMN: serde::Serialize, + VectorN>: serde::Serialize, + VectorN, U1>>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator> + + Allocator, U1>>, + MatrixMN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>, + VectorN, U1>>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Bidiagonal, C: Dim> + where DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator> + + Allocator, U1>> { + // FIXME: perhaps we should pack the axises into different vectors so that axises for `v_t` are + // contiguous. This prevents some useless copies. + uv: MatrixMN, + /// The diagonal elements of the decomposed matrix. + pub diagonal: VectorN>, + /// The off-diagonal elements of the decomposed matrix. + pub off_diagonal: VectorN, U1>>, + upper_diagonal: bool +} + +impl, C: Dim> Copy for Bidiagonal + where DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator> + + Allocator, U1>>, + MatrixMN: Copy, + VectorN>: Copy, + VectorN, U1>>: Copy { } + + +impl, C: Dim> Bidiagonal + where DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator> + + Allocator, U1>> { + + /// Computes the Bidiagonal decomposition using householder reflections. + pub fn new(mut matrix: MatrixMN) -> Self { + let (nrows, ncols) = matrix.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + let dim = min_nrows_ncols.value(); + assert!(dim != 0, "Cannot compute the bidiagonalization of an empty matrix."); + + let mut diagonal = unsafe { MatrixMN::new_uninitialized_generic(min_nrows_ncols, U1) }; + let mut off_diagonal = unsafe { MatrixMN::new_uninitialized_generic(min_nrows_ncols.sub(U1), U1) }; + let mut axis_packed = unsafe { MatrixMN::new_uninitialized_generic(ncols, U1) }; + let mut work = unsafe { MatrixMN::new_uninitialized_generic(nrows, U1) }; + + let upper_diagonal = nrows.value() >= ncols.value(); + if upper_diagonal { + for ite in 0 .. dim - 1 { + householder::clear_column_unchecked(&mut matrix, &mut diagonal[ite], ite, 0, None); + householder::clear_row_unchecked(&mut matrix, &mut off_diagonal[ite], &mut axis_packed, &mut work, ite, 1); + } + + householder::clear_column_unchecked(&mut matrix, &mut diagonal[dim - 1], dim - 1, 0, None); + } + else { + for ite in 0 .. dim - 1 { + householder::clear_row_unchecked(&mut matrix, &mut diagonal[ite], &mut axis_packed, &mut work, ite, 0); + householder::clear_column_unchecked(&mut matrix, &mut off_diagonal[ite], ite, 1, None); + } + + householder::clear_row_unchecked(&mut matrix, &mut diagonal[dim - 1], &mut axis_packed, &mut work, dim - 1, 0); + } + + Bidiagonal { uv: matrix, diagonal: diagonal, off_diagonal: off_diagonal, upper_diagonal: upper_diagonal } + } + + /// Indicates whether this decomposition contains an upper-diagonal matrix. + #[inline] + pub fn is_upper_diagonal(&self) -> bool { + self.upper_diagonal + } + + + #[inline] + fn axis_shift(&self) -> (usize, usize) { + if self.upper_diagonal { + (0, 1) + } + else { + (1, 0) + } + } + + /// Unpacks this decomposition into its three matrix factors `(U, D, V^t)`. + /// + /// The decomposed matrix `M` is equal to `U * D * V^t`. + #[inline] + pub fn unpack(self) -> (MatrixMN>, + MatrixN>, + MatrixMN, C>) + where DefaultAllocator: Allocator, DimMinimum> + + Allocator> + + Allocator, C>, + // FIXME: the following bounds are ugly. + DimMinimum: DimMin, Output = DimMinimum>, + ShapeConstraint: DimEq, U1>> { + // FIXME: optimize by calling a reallocator. + (self.u(), self.d(), self.v_t()) + } + + /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. + #[inline] + pub fn d(&self) -> MatrixN> + where DefaultAllocator: Allocator, DimMinimum>, + // FIXME: the following bounds are ugly. + DimMinimum: DimMin, Output = DimMinimum>, + ShapeConstraint: DimEq, U1>> { + let (nrows, ncols) = self.uv.data.shape(); + + let d = nrows.min(ncols); + let mut res = MatrixN::identity_generic(d, d); + res.set_diagonal(&self.diagonal); + + let start = self.axis_shift(); + res.slice_mut(start, (d.value() - 1, d.value() - 1)).set_diagonal(&self.off_diagonal); + res + } + + /// Computes the orthogonal matrix `U` of this `U * D * V` decomposition. + // FIXME: code duplication with householder::assemble_q. + // Except that we are returning a rectangular matrix here. + pub fn u(&self) -> MatrixMN> + where DefaultAllocator: Allocator> { + let (nrows, ncols) = self.uv.data.shape(); + + let mut res = Matrix::identity_generic(nrows, nrows.min(ncols)); + let dim = self.diagonal.len(); + let shift = self.axis_shift().0; + + for i in (0 .. dim - shift).rev() { + let axis = self.uv.slice_range(i + shift .., i); + // FIXME: sometimes, the axis might have a zero magnitude. + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + let mut res_rows = res.slice_range_mut(i + shift .., i ..); + refl.reflect(&mut res_rows); + } + + res + } + + /// Computes the orthogonal matrix `V` of this `U * D * V` decomposition. + pub fn v_t(&self) -> MatrixMN, C> + where DefaultAllocator: Allocator, C> { + let (nrows, ncols) = self.uv.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + + let mut res = Matrix::identity_generic(min_nrows_ncols, ncols); + let mut work = unsafe { MatrixMN::new_uninitialized_generic(min_nrows_ncols, U1) }; + let mut axis_packed = unsafe { MatrixMN::new_uninitialized_generic(ncols, U1) }; + + let shift = self.axis_shift().1; + + for i in (0 .. min_nrows_ncols.value() - shift).rev() { + let axis = self.uv.slice_range(i, i + shift ..); + let mut axis_packed = axis_packed.rows_range_mut(i + shift ..); + axis_packed.tr_copy_from(&axis); + // FIXME: sometimes, the axis might have a zero magnitude. + let refl = Reflection::new(Unit::new_unchecked(axis_packed), N::zero()); + + let mut res_rows = res.slice_range_mut(i .., i + shift ..); + refl.reflect_rows(&mut res_rows, &mut work.rows_range_mut(i ..)); + } + + res + } + + /// The diagonal part of this decomposed matrix. + pub fn diagonal(&self) -> &VectorN> { + &self.diagonal + } + + /// The off-diagonal part of this decomposed matrix. + pub fn off_diagonal(&self) -> &VectorN, U1>> { + &self.off_diagonal + } + + #[doc(hidden)] + pub fn uv_internal(&self) -> &MatrixMN { + &self.uv + } +} + +// impl + DimSub> Bidiagonal +// where DefaultAllocator: Allocator + +// Allocator { +// /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. +// pub fn solve(&self, b: &Matrix) -> MatrixMN +// where S2: StorageMut, +// ShapeConstraint: SameNumberOfRows, +// DefaultAllocator: Allocator { +// let mut res = b.clone_owned(); +// self.solve_mut(&mut res); +// res +// } +// +// /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. +// pub fn solve_mut(&self, b: &mut Matrix) +// where S2: StorageMut, +// ShapeConstraint: SameNumberOfRows { +// +// assert_eq!(self.uv.nrows(), b.nrows(), "Bidiagonal solve matrix dimension mismatch."); +// assert!(self.uv.is_square(), "Bidiagonal solve: unable to solve a non-square system."); +// +// self.q_tr_mul(b); +// self.solve_upper_triangular_mut(b); +// } +// +// // FIXME: duplicate code from the `solve` module. +// fn solve_upper_triangular_mut(&self, b: &mut Matrix) +// where S2: StorageMut, +// ShapeConstraint: SameNumberOfRows { +// +// let dim = self.uv.nrows(); +// +// for k in 0 .. b.ncols() { +// let mut b = b.column_mut(k); +// for i in (0 .. dim).rev() { +// let coeff; +// +// unsafe { +// let diag = *self.diag.vget_unchecked(i); +// coeff = *b.vget_unchecked(i) / diag; +// *b.vget_unchecked_mut(i) = coeff; +// } +// +// b.rows_range_mut(.. i).axpy(-coeff, &self.uv.slice_range(.. i, i), N::one()); +// } +// } +// } +// +// /// Computes the inverse of the decomposed matrix. +// pub fn inverse(&self) -> MatrixN { +// assert!(self.uv.is_square(), "Bidiagonal inverse: unable to compute the inverse of a non-square matrix."); +// +// // FIXME: is there a less naive method ? +// let (nrows, ncols) = self.uv.data.shape(); +// let mut res = MatrixN::identity_generic(nrows, ncols); +// self.solve_mut(&mut res); +// res +// } +// +// // /// Computes the determinant of the decomposed matrix. +// // pub fn determinant(&self) -> N { +// // let dim = self.uv.nrows(); +// // assert!(self.uv.is_square(), "Bidiagonal determinant: unable to compute the determinant of a non-square matrix."); +// +// // let mut res = N::one(); +// // for i in 0 .. dim { +// // res *= unsafe { *self.diag.vget_unchecked(i) }; +// // } +// +// // res self.q_determinant() +// // } +// } + +impl, C: Dim, S: Storage> Matrix + where DimMinimum: DimSub, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator> + + Allocator, U1>> { + + /// Computes the bidiagonalization using householder reflections. + pub fn bidiagonalize(self) -> Bidiagonal { + Bidiagonal::new(self.into_owned()) + } +} diff --git a/src/linalg/cholesky.rs b/src/linalg/cholesky.rs new file mode 100644 index 00000000..a7512854 --- /dev/null +++ b/src/linalg/cholesky.rs @@ -0,0 +1,142 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; + +use core::{DefaultAllocator, MatrixN, MatrixMN, Matrix, SquareMatrix}; +use constraint::{ShapeConstraint, SameNumberOfRows}; +use storage::{Storage, StorageMut}; +use allocator::Allocator; +use dimension::{Dim, Dynamic, DimSub}; + +/// The Cholesky decomposion of a symmetric-definite-positive matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Cholesky + where DefaultAllocator: Allocator { + chol: MatrixN +} + +impl Copy for Cholesky + where DefaultAllocator: Allocator, + MatrixN: Copy { } + +impl> Cholesky + where DefaultAllocator: Allocator { + /// Attempts to compute the Cholesky decomposition of `matrix`. + /// + /// Returns `None` if the input matrix is not definite-positive. The intput matrix is assumed + /// to be symmetric and only the lower-triangular part is read. + pub fn new(mut matrix: MatrixN) -> Option { + assert!(matrix.is_square(), "The input matrix must be square."); + + let n = matrix.nrows(); + + for j in 0 .. n { + for k in 0 .. j { + let factor = unsafe { -*matrix.get_unchecked(j, k) }; + + let (mut col_j, col_k) = matrix.columns_range_pair_mut(j, k); + let mut col_j = col_j.rows_range_mut(j ..); + let col_k = col_k.rows_range(j ..); + + col_j.axpy(factor, &col_k, N::one()); + } + + let diag = unsafe { *matrix.get_unchecked(j, j) }; + if diag > N::zero() { + let denom = diag.sqrt(); + unsafe { *matrix.get_unchecked_mut(j, j) = denom; } + + let mut col = matrix.slice_range_mut(j + 1 .., j); + col /= denom; + } + else { + return None; + } + } + + Some(Cholesky { chol: matrix }) + } + + /// Retrieves the lower-triangular factor of the Cholesky decomposition with its strictly + /// upper-triangular part filled with zeros. + pub fn unpack(mut self) -> MatrixN { + self.chol.fill_upper_triangle(N::zero(), 1); + self.chol + } + + /// Retrieves the lower-triangular factor of the Cholesky decomposition, without zeroing-out + /// its strict upper-triangular part. + /// + /// The values of the strict upper-triangular part are garbage and should be ignored by further + /// computations. + pub fn unpack_dirty(self) -> MatrixN { + self.chol + } + + /// Retrieves the lower-triangular factor of the Cholesky decomposition with its strictly + /// uppen-triangular part filled with zeros. + pub fn l(&self) -> MatrixN { + self.chol.lower_triangle() + } + + /// Retrieves the lower-triangular factor of the Cholesky decomposition, without zeroing-out + /// its strict upper-triangular part. + /// + /// This is an allocation-less version of `self.l()`. The values of the strict upper-triangular + /// part are garbage and should be ignored by further computations. + pub fn l_dirty(&self) -> &MatrixN { + &self.chol + } + + /// Solves the system `self * x = b` where `self` is the decomposed matrix and `x` the unknown. + /// + /// The result is stored on `b`. + pub fn solve_mut(&self, b: &mut Matrix) + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let _ = self.chol.solve_lower_triangular_mut(b); + let _ = self.chol.tr_solve_lower_triangular_mut(b); + } + + /// Returns the solution of the system `self * x = b` where `self` is the decomposed matrix and + /// `x` the unknown. + pub fn solve(&self, b: &Matrix) -> MatrixMN + where S2: StorageMut, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + let mut res = b.clone_owned(); + self.solve_mut(&mut res); + res + } + + /// Computes the inverse of the decomposed matrix. + pub fn inverse(&self) -> MatrixN { + let shape = self.chol.data.shape(); + let mut res = MatrixN::identity_generic(shape.0, shape.1); + + self.solve_mut(&mut res); + res + } +} + +impl, S: Storage> SquareMatrix + where DefaultAllocator: Allocator { + + /// Attempts to compute the Cholesky decomposition of this matrix. + /// + /// Returns `None` if the input matrix is not definite-positive. The intput matrix is assumed + /// to be symmetric and only the lower-triangular part is read. + pub fn cholesky(self) -> Option> { + Cholesky::new(self.into_owned()) + } +} diff --git a/src/core/determinant.rs b/src/linalg/determinant.rs similarity index 67% rename from src/core/determinant.rs rename to src/linalg/determinant.rs index 8d5bf0cf..5ce77d16 100644 --- a/src/core/determinant.rs +++ b/src/linalg/determinant.rs @@ -1,19 +1,23 @@ -use num::One; +use alga::general::Real; -use alga::general::{ClosedMul, ClosedSub, ClosedAdd}; - -use core::{Scalar, SquareMatrix}; -use core::dimension::Dim; +use core::{DefaultAllocator, SquareMatrix}; +use core::dimension::DimMin; use core::storage::Storage; +use core::allocator::Allocator; + +use linalg::LU; -impl SquareMatrix - where N: Scalar + One + ClosedMul + ClosedAdd + ClosedSub, - S: Storage { - /// This matrix determinant. +impl, S: Storage> SquareMatrix { + /// Computes the matrix determinant. + /// + /// If the matrix has a dimension larger than 3, an LU decomposition is used. #[inline] - pub fn determinant(&self) -> N { - assert!(self.is_square(), "Unable to invert a non-square matrix."); + pub fn determinant(&self) -> N + where DefaultAllocator: Allocator + + Allocator<(usize, usize), D> { + + assert!(self.is_square(), "Unable to compute the determinant of a non-square matrix."); let dim = self.shape().0; unsafe { @@ -48,7 +52,7 @@ impl SquareMatrix m11 * minor_m12_m23 - m12 * minor_m11_m23 + m13 * minor_m11_m22 }, _ => { - unimplemented!() + LU::new(self.clone_owned()).determinant() } } } diff --git a/src/linalg/eigen.rs b/src/linalg/eigen.rs new file mode 100644 index 00000000..26c2b312 --- /dev/null +++ b/src/linalg/eigen.rs @@ -0,0 +1,105 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use std::fmt::Display; +use std::cmp; +use num_complex::Complex; +use alga::general::Real; +use std::ops::MulAssign; + +use core::{DefaultAllocator, SquareMatrix, VectorN, MatrixN, Hessenberg, Unit, Vector2, Vector3}; +use core::dimension::{Dim, DimSub, DimDiff, Dynamic, U1, U2, U3}; +use core::storage::Storage; +use constraint::{ShapeConstraint, DimEq}; +use allocator::Allocator; + +use linalg::householder; +use linalg::RealSchur; +use geometry::{Reflection, UnitComplex}; + + +/// Eigendecomposition of a matrix with real eigenvalues. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct RealEigen + where DefaultAllocator: Allocator + + Allocator { + pub eigenvectors: MatrixN, + pub eigenvalues: VectorN +} + + +impl Copy for RealEigen + where DefaultAllocator: Allocator + + Allocator, + MatrixN: Copy, + VectorN: Copy { } + +impl RealEigen + where D: DimSub, // For Hessenberg. + ShapeConstraint: DimEq>, // For Hessenberg. + DefaultAllocator: Allocator> + // For Hessenberg. + Allocator> + // For Hessenberg. + Allocator + + Allocator, + // XXX: for debug + DefaultAllocator: Allocator, + MatrixN: Display { + + /// Computes the eigendecomposition of a diagonalizable matrix with real eigenvalues. + pub fn new(m: MatrixN) -> Option> { + assert!(m.is_square(), "Unable to compute the eigendecomposition of a non-square matrix."); + + let dim = m.nrows(); + let (mut eigenvectors, mut eigenvalues) = RealSchur::new(m, 0).unwrap().unpack(); + + println!("Schur eigenvalues: {}", eigenvalues); + + // Check that the eigenvalues are all real. + for i in 0 .. dim - 1 { + if !eigenvalues[(i + 1, i)].is_zero() { + return None; + } + } + + for j in 1 .. dim { + for i in 0 .. j { + let diff = eigenvalues[(i, i)] - eigenvalues[(j, j)]; + + if diff.is_zero() && !eigenvalues[(i, j)].is_zero() { + return None; + } + + let z = -eigenvalues[(i, j)] / diff; + + for k in j + 1 .. dim { + eigenvalues[(i, k)] -= z * eigenvalues[(j, k)]; + } + + for k in 0 .. dim { + eigenvectors[(k, j)] += z * eigenvectors[(k, i)]; + } + } + } + + // Normalize the eigenvector basis. + for i in 0 .. dim { + let _ = eigenvectors.column_mut(i).normalize_mut(); + } + + Some(RealEigen { + eigenvectors: eigenvectors, + eigenvalues: eigenvalues.diagonal() + }) + } +} diff --git a/src/linalg/full_piv_lu.rs b/src/linalg/full_piv_lu.rs new file mode 100644 index 00000000..d5f3da9a --- /dev/null +++ b/src/linalg/full_piv_lu.rs @@ -0,0 +1,242 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; +use core::{Matrix, MatrixN, MatrixMN, DefaultAllocator}; +use dimension::{Dim, DimMin, DimMinimum}; +use storage::{Storage, StorageMut}; +use allocator::Allocator; +use constraint::{ShapeConstraint, SameNumberOfRows}; + +use linalg::lu; +use linalg::PermutationSequence; + + + +/// LU decomposition with full row and column pivoting. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: serde::Serialize, + PermutationSequence>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: serde::Deserialize<'de>, + PermutationSequence>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct FullPivLU, C: Dim> + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + lu: MatrixMN, + p: PermutationSequence>, + q: PermutationSequence> +} + + +impl, C: Dim> Copy for FullPivLU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: Copy, + PermutationSequence>: Copy { } + + +impl, C: Dim> FullPivLU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + /// Computes the LU decomposition with full pivoting of `matrix`. + /// + /// This effectively computes `P, L, U, Q` such that `P * matrix * Q = LU`. + pub fn new(mut matrix: MatrixMN) -> Self { + let (nrows, ncols) = matrix.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + + let mut p = PermutationSequence::identity_generic(min_nrows_ncols); + let mut q = PermutationSequence::identity_generic(min_nrows_ncols); + + if min_nrows_ncols.value() == 0 { + return FullPivLU { lu: matrix, p: p, q: q }; + } + + for i in 0 .. min_nrows_ncols.value() { + let piv = matrix.slice_range(i .., i ..).iamax_full(); + let row_piv = piv.0 + i; + let col_piv = piv.1 + i; + let diag = matrix[(row_piv, col_piv)]; + + if diag.is_zero() { + // The remaining of the matrix is zero. + break; + } + + matrix.swap_columns(i, col_piv); + q.append_permutation(i, col_piv); + + if row_piv != i { + p.append_permutation(i, row_piv); + matrix.columns_range_mut(.. i).swap_rows(i, row_piv); + lu::gauss_step_swap(&mut matrix, diag, i, row_piv); + } + else { + lu::gauss_step(&mut matrix, diag, i); + } + } + + FullPivLU { lu: matrix, p: p, q: q } + } + + #[doc(hidden)] + pub fn lu_internal(&self) -> &MatrixMN { + &self.lu + } + + /// The lower triangular matrix of this decomposition. + #[inline] + pub fn l(&self) -> MatrixMN> + where DefaultAllocator: Allocator> { + + let (nrows, ncols) = self.lu.data.shape(); + let mut m = self.lu.columns_generic(0, nrows.min(ncols)).into_owned(); + m.fill_upper_triangle(N::zero(), 1); + m.fill_diagonal(N::one()); + m + } + + /// The upper triangular matrix of this decomposition. + #[inline] + pub fn u(&self) -> MatrixMN, C> + where DefaultAllocator: Allocator, C> { + let (nrows, ncols) = self.lu.data.shape(); + self.lu.rows_generic(0, nrows.min(ncols)).upper_triangle() + } + + /// The row permutations of this decomposition. + #[inline] + pub fn p(&self) -> &PermutationSequence> { + &self.p + } + + /// The column permutations of this decomposition. + #[inline] + pub fn q(&self) -> &PermutationSequence> { + &self.q + } + + /// The two matrices of this decomposition and the row and column permutations: `(P, L, U, Q)`. + #[inline] + pub fn unpack(self) -> (PermutationSequence>, + MatrixMN>, + MatrixMN, C>, + PermutationSequence>) + where DefaultAllocator: Allocator> + + Allocator, C> { + // Use reallocation for either l or u. + let l = self.l(); + let u = self.u(); + let p = self.p; + let q = self.q; + + (p, l, u, q) + } +} + +impl> FullPivLU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), D> { + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// Retuns `None` if the decomposed matrix is not invertible. + pub fn solve(&self, b: &Matrix) -> Option> + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows, + DefaultAllocator: Allocator { + let mut res = b.clone_owned(); + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// If the decomposed matrix is not invertible, this returns `false` and its input `b` may + /// be overwritten with garbage. + pub fn solve_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + + assert_eq!(self.lu.nrows(), b.nrows(), "FullPivLU solve matrix dimension mismatch."); + assert!(self.lu.is_square(), "FullPivLU solve: unable to solve a non-square system."); + + if self.is_invertible() { + self.p.permute_rows(b); + let _ = self.lu.solve_lower_triangular_with_diag_mut(b, N::one()); + let _ = self.lu.solve_upper_triangular_mut(b); + self.q.inv_permute_rows(b); + + true + } + else { + false + } + } + + /// Computes the inverse of the decomposed matrix. + /// + /// Returns `None` if the decomposed matrix is not invertible. + pub fn try_inverse(&self) -> Option> { + assert!(self.lu.is_square(), "FullPivLU inverse: unable to compute the inverse of a non-square matrix."); + + let (nrows, ncols) = self.lu.data.shape(); + + let mut res = MatrixN::identity_generic(nrows, ncols); + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Indicates if the decomposed matrix is invertible. + pub fn is_invertible(&self) -> bool { + assert!(self.lu.is_square(), "FullPivLU: unable to test the invertibility of a non-square matrix."); + + let dim = self.lu.nrows(); + !self.lu[(dim - 1, dim - 1)].is_zero() + } + + /// Computes the determinant of the decomposed matrix. + pub fn determinant(&self) -> N { + assert!(self.lu.is_square(), "FullPivLU determinant: unable to compute the determinant of a non-square matrix."); + + let dim = self.lu.nrows(); + let mut res = self.lu[(dim - 1, dim - 1)]; + if !res.is_zero() { + for i in 0 .. dim - 1 { + res *= unsafe { *self.lu.get_unchecked(i, i) }; + } + + res * self.p.determinant() * self.q.determinant() + } + else { + N::zero() + } + } +} + +impl, C: Dim, S: Storage> Matrix + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + /// Computes the LU decomposition with full pivoting of `matrix`. + /// + /// This effectively computes `P, L, U, Q` such that `P * matrix * Q = LU`. + pub fn full_piv_lu(self) -> FullPivLU { + FullPivLU::new(self.into_owned()) + } +} diff --git a/src/linalg/givens.rs b/src/linalg/givens.rs new file mode 100644 index 00000000..d0efeba1 --- /dev/null +++ b/src/linalg/givens.rs @@ -0,0 +1,40 @@ +//! Construction of givens rotations. + + +use alga::general::Real; +use num_complex::Complex; + +use core::Vector; +use core::storage::Storage; +use core::dimension::U2; + +use geometry::UnitComplex; + + +/// Computes the rotation `R` required such that the `y` component of `R * v` is zero. +/// +/// Returns `None` if no rotation is needed (i.e. if `v.y == 0`). Otherwise, this returns the norm +/// of `v` and the rotation `r` such that `R * v = [ |v|, 0.0 ]^t` where `|v|` is the norm of `v`. +pub fn cancel_y>(v: &Vector) -> Option<(UnitComplex, N)> { + if !v[1].is_zero() { + let c = Complex::new(v[0], -v[1]); + Some(UnitComplex::from_complex_and_get(c)) + } + else { + None + } +} + +/// Computes the rotation `R` required such that the `x` component of `R * v` is zero. +/// +/// Returns `None` if no rotation is needed (i.e. if `v.x == 0`). Otherwise, this returns the norm +/// of `v` and the rotation `r` such that `R * v = [ 0.0, |v| ]^t` where `|v|` is the norm of `v`. +pub fn cancel_x>(v: &Vector) -> Option<(UnitComplex, N)> { + if !v[0].is_zero() { + let c = Complex::new(v[1], v[0]); + Some(UnitComplex::from_complex_and_get(c)) + } + else { + None + } +} diff --git a/src/linalg/hessenberg.rs b/src/linalg/hessenberg.rs new file mode 100644 index 00000000..f3f1dc3f --- /dev/null +++ b/src/linalg/hessenberg.rs @@ -0,0 +1,132 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; +use core::{SquareMatrix, MatrixN, MatrixMN, VectorN, DefaultAllocator}; +use dimension::{DimSub, DimDiff, Dynamic, U1}; +use storage::Storage; +use allocator::Allocator; +use constraint::{ShapeConstraint, DimEq}; + +use linalg::householder; + +/// Hessenberg decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct Hessenberg> + where DefaultAllocator: Allocator + + Allocator> { + + hess: MatrixN, + subdiag: VectorN> +} + +impl> Copy for Hessenberg + where DefaultAllocator: Allocator + + Allocator>, + MatrixN: Copy, + VectorN>: Copy { } + +impl> Hessenberg + where DefaultAllocator: Allocator + + Allocator + + Allocator> { + + /// Computes the Hessenberg decomposition using householder reflections. + pub fn new(hess: MatrixN) -> Self { + let mut work = unsafe { MatrixMN::new_uninitialized_generic(hess.data.shape().0, U1) }; + Self::new_with_workspace(hess, &mut work) + } + + /// Computes the Hessenberg decomposition using householder reflections. + /// + /// The workspace containing `D` elements must be provided but its content does not have to be + /// initialized. + pub fn new_with_workspace(mut hess: MatrixN, work: &mut VectorN) -> Self { + assert!(hess.is_square(), "Cannot compute the hessenberg decomposition of a non-square matrix."); + + let dim = hess.data.shape().0; + + assert!(dim.value() != 0, "Cannot compute the hessenberg decomposition of an empty matrix."); + assert_eq!(dim.value(), work.len(), "Hessenberg: invalid workspace size."); + + let mut subdiag = unsafe { MatrixMN::new_uninitialized_generic(dim.sub(U1), U1) }; + + if dim.value() == 0 { + return Hessenberg { hess, subdiag }; + } + + for ite in 0 .. dim.value() - 1 { + householder::clear_column_unchecked(&mut hess, &mut subdiag[ite], ite, 1, Some(work)); + } + + Hessenberg { hess, subdiag } + } + + /// Retrieves `(q, h)` with `q` the orthogonal matrix of this decomposition and `h` the + /// hessenberg matrix. + #[inline] + pub fn unpack(self) -> (MatrixN, MatrixN) + where ShapeConstraint: DimEq> { + let q = self.q(); + + (q, self.unpack_h()) + } + + /// Retrieves the upper trapezoidal submatrix `H` of this decomposition. + #[inline] + pub fn unpack_h(mut self) -> MatrixN + where ShapeConstraint: DimEq> { + let dim = self.hess.nrows(); + self.hess.fill_lower_triangle(N::zero(), 2); + self.hess.slice_mut((1, 0), (dim - 1, dim - 1)).set_diagonal(&self.subdiag); + self.hess + } + + // FIXME: add a h that moves out of self. + /// Retrieves the upper trapezoidal submatrix `H` of this decomposition. + /// + /// This is less efficient than `.unpack_h()` as it allocates a new matrix. + #[inline] + pub fn h(&self) -> MatrixN + where ShapeConstraint: DimEq> { + let dim = self.hess.nrows(); + let mut res = self.hess.clone(); + res.fill_lower_triangle(N::zero(), 2); + res.slice_mut((1, 0), (dim - 1, dim - 1)).set_diagonal(&self.subdiag); + res + } + + /// Computes the orthogonal matrix `Q` of this decomposition. + pub fn q(&self) -> MatrixN { + householder::assemble_q(&self.hess) + } + + #[doc(hidden)] + pub fn hess_internal(&self) -> &MatrixN { + &self.hess + } +} + + +impl, S: Storage> SquareMatrix + where DefaultAllocator: Allocator + + Allocator + + Allocator> { + /// Computes the Hessenberg decomposition of this matrix using householder reflections. + pub fn hessenberg(self) -> Hessenberg { + Hessenberg::new(self.into_owned()) + } +} diff --git a/src/linalg/householder.rs b/src/linalg/householder.rs new file mode 100644 index 00000000..959cff23 --- /dev/null +++ b/src/linalg/householder.rs @@ -0,0 +1,120 @@ +//! Construction of householder elementary reflections. + +use alga::general::Real; +use core::{Unit, MatrixN, MatrixMN, Vector, VectorN, DefaultAllocator}; +use dimension::Dim; +use storage::{Storage, StorageMut}; +use allocator::Allocator; + +use geometry::Reflection; + +/// Replaces `column` by the axis of the householder reflection that transforms `column` into +/// `(+/-|column|, 0, ..., 0)`. +/// +/// The unit-length axis is output to `column`. Returns what would be the first component of +/// `column` after reflection and `false` if no reflection was necessary. +#[doc(hidden)] +#[inline(always)] +pub fn reflection_axis_mut>(column: &mut Vector) -> (N, bool) { + let reflection_sq_norm = column.norm_squared(); + let mut reflection_norm = reflection_sq_norm.sqrt(); + + let factor; + unsafe { + if *column.vget_unchecked(0) > N::zero() { + reflection_norm = -reflection_norm; + } + + factor = (reflection_sq_norm - *column.vget_unchecked(0) * reflection_norm) * ::convert(2.0); + *column.vget_unchecked_mut(0) -= reflection_norm; + } + + + if !factor.is_zero() { + *column /= factor.sqrt(); + (reflection_norm, true) + } + else { + (reflection_norm, false) + } +} + +/// Uses an householder reflection to zero out the `icol`-th column, starting with the `shift + 1`-th +/// subdiagonal element. +#[doc(hidden)] +pub fn clear_column_unchecked(matrix: &mut MatrixMN, + diag_elt: &mut N, + icol: usize, + shift: usize, + bilateral: Option<&mut VectorN>) + where DefaultAllocator: Allocator + + Allocator { + + let (mut left, mut right) = matrix.columns_range_pair_mut(icol, icol + 1 ..); + let mut axis = left.rows_range_mut(icol + shift ..); + + let (reflection_norm, not_zero) = reflection_axis_mut(&mut axis); + *diag_elt = reflection_norm; + + if not_zero { + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + if let Some(mut work) = bilateral { + refl.reflect_rows(&mut right, &mut work); + } + refl.reflect(&mut right.rows_range_mut(icol + shift ..)); + } +} + +/// Uses an hoseholder reflection to zero out the `irow`-th row, ending before the `shift + 1`-th +/// superdiagonal element. +#[doc(hidden)] +pub fn clear_row_unchecked(matrix: &mut MatrixMN, + diag_elt: &mut N, + axis_packed: &mut VectorN, + work: &mut VectorN, + irow: usize, + shift: usize) + where DefaultAllocator: Allocator + + Allocator + + Allocator { + + let (mut top, mut bottom) = matrix.rows_range_pair_mut(irow, irow + 1 ..); + let mut axis = axis_packed.rows_range_mut(irow + shift ..); + axis.tr_copy_from(&top.columns_range(irow + shift ..)); + + let (reflection_norm, not_zero) = reflection_axis_mut(&mut axis); + *diag_elt = reflection_norm; + + if not_zero { + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + refl.reflect_rows(&mut bottom.columns_range_mut(irow + shift ..), &mut work.rows_range_mut(irow + 1 ..)); + top.columns_range_mut(irow + shift ..).tr_copy_from(refl.axis()); + } + else { + top.columns_range_mut(irow + shift ..).tr_copy_from(&axis); + } +} + +/// Computes the orthogonal transformation described by the elementary reflector axices stored on +/// the lower-diagonal element of the given matrix. +/// matrices. +#[doc(hidden)] +pub fn assemble_q(m: &MatrixN) -> MatrixN + where DefaultAllocator: Allocator { + assert!(m.is_square()); + let dim = m.data.shape().0; + + // NOTE: we could build the identity matrix and call p_mult on it. + // Instead we don't so that we take in accout the matrix sparcity. + let mut res = MatrixN::identity_generic(dim, dim); + + for i in (0 .. dim.value() - 1).rev() { + let axis = m.slice_range(i + 1 .., i); + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + let mut res_rows = res.slice_range_mut(i + 1 .., i ..); + refl.reflect(&mut res_rows); + } + + res +} diff --git a/src/linalg/inverse.rs b/src/linalg/inverse.rs new file mode 100644 index 00000000..d5786e30 --- /dev/null +++ b/src/linalg/inverse.rs @@ -0,0 +1,258 @@ +use alga::general::Real; + +use core::{DefaultAllocator, SquareMatrix, MatrixN}; +use core::dimension::Dim; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; + +use linalg::lu; + +impl> SquareMatrix { + /// Attempts to invert this matrix. + #[inline] + pub fn try_inverse(self) -> Option> + where DefaultAllocator: Allocator { + + let mut me = self.into_owned(); + if me.try_inverse_mut() { + Some(me) + } + else { + None + } + } +} + + +impl> SquareMatrix { + /// Attempts to invert this matrix in-place. Returns `false` and leaves `self` untouched if + /// inversion fails. + #[inline] + pub fn try_inverse_mut(&mut self) -> bool + where DefaultAllocator: Allocator { + assert!(self.is_square(), "Unable to invert a non-square matrix."); + + let dim = self.shape().0; + + unsafe { + match dim { + 0 => true, + 1 => { + let determinant = self.get_unchecked(0, 0).clone(); + if determinant == N::zero() { + false + } + else { + *self.get_unchecked_mut(0, 0) = N::one() / determinant; + true + } + }, + 2 => { + let m11 = *self.get_unchecked(0, 0); let m12 = *self.get_unchecked(0, 1); + let m21 = *self.get_unchecked(1, 0); let m22 = *self.get_unchecked(1, 1); + + let determinant = m11 * m22 - m21 * m12; + + if determinant == N::zero() { + false + } + else { + + *self.get_unchecked_mut(0, 0) = m22 / determinant; + *self.get_unchecked_mut(0, 1) = -m12 / determinant; + + *self.get_unchecked_mut(1, 0) = -m21 / determinant; + *self.get_unchecked_mut(1, 1) = m11 / determinant; + + true + } + }, + 3 => { + let m11 = *self.get_unchecked(0, 0); + let m12 = *self.get_unchecked(0, 1); + let m13 = *self.get_unchecked(0, 2); + + let m21 = *self.get_unchecked(1, 0); + let m22 = *self.get_unchecked(1, 1); + let m23 = *self.get_unchecked(1, 2); + + let m31 = *self.get_unchecked(2, 0); + let m32 = *self.get_unchecked(2, 1); + let m33 = *self.get_unchecked(2, 2); + + + let minor_m12_m23 = m22 * m33 - m32 * m23; + let minor_m11_m23 = m21 * m33 - m31 * m23; + let minor_m11_m22 = m21 * m32 - m31 * m22; + + let determinant = m11 * minor_m12_m23 - + m12 * minor_m11_m23 + + m13 * minor_m11_m22; + + if determinant == N::zero() { + false + } + else { + *self.get_unchecked_mut(0, 0) = minor_m12_m23 / determinant; + *self.get_unchecked_mut(0, 1) = (m13 * m32 - m33 * m12) / determinant; + *self.get_unchecked_mut(0, 2) = (m12 * m23 - m22 * m13) / determinant; + + *self.get_unchecked_mut(1, 0) = -minor_m11_m23 / determinant; + *self.get_unchecked_mut(1, 1) = (m11 * m33 - m31 * m13) / determinant; + *self.get_unchecked_mut(1, 2) = (m13 * m21 - m23 * m11) / determinant; + + *self.get_unchecked_mut(2, 0) = minor_m11_m22 / determinant; + *self.get_unchecked_mut(2, 1) = (m12 * m31 - m32 * m11) / determinant; + *self.get_unchecked_mut(2, 2) = (m11 * m22 - m21 * m12) / determinant; + + true + } + }, + 4=> { + let oself = self.clone_owned(); + do_inverse4(&oself, self) + } + _ => { + let oself = self.clone_owned(); + lu::try_invert_to(oself, self) + } + } + } + } +} + + + +// NOTE: this is an extremely efficient, loop-unrolled matrix inverse from MESA (MIT licenced). +fn do_inverse4>(m: &MatrixN, out: &mut SquareMatrix) -> bool + where DefaultAllocator: Allocator { + let m = m.data.as_slice(); + + out[(0, 0)] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + out[(1, 0)] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + out[(2, 0)] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + out[(3, 0)] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + out[(0, 1)] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + out[(1, 1)] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + out[(2, 1)] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + out[(3, 1)] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + out[(0, 2)] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + out[(1, 2)] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + out[(2, 2)] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + out[(0, 3)] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + out[(3, 2)] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + out[(1, 3)] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + out[(2, 3)] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + out[(3, 3)] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + let det = m[0] * out[(0, 0)] + m[1] * out[(0, 1)] + m[2] * out[(0, 2)] + m[3] * out[(0, 3)]; + + if !det.is_zero() { + let inv_det = N::one() / det; + + for j in 0 .. 4 { + for i in 0 .. 4 { + out[(i, j)] *= inv_det; + } + } + true + } + else { + false + } +} diff --git a/src/linalg/lu.rs b/src/linalg/lu.rs new file mode 100644 index 00000000..49ae2f60 --- /dev/null +++ b/src/linalg/lu.rs @@ -0,0 +1,335 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use std::mem; +use alga::general::{Field, Real}; +use core::{Scalar, Matrix, MatrixN, MatrixMN, DefaultAllocator}; +use dimension::{Dim, DimMin, DimMinimum}; +use storage::{Storage, StorageMut}; +use allocator::{Allocator, Reallocator}; +use constraint::{ShapeConstraint, SameNumberOfRows}; + +use linalg::PermutationSequence; + + + +/// LU decomposition with partial (row) pivoting. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: serde::Serialize, + PermutationSequence>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: serde::Deserialize<'de>, + PermutationSequence>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct LU, C: Dim> + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + lu: MatrixMN, + p: PermutationSequence> +} + +impl, C: Dim> Copy for LU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum>, + MatrixMN: Copy, + PermutationSequence>: Copy { } + +/// Performs a LU decomposition to overwrite `out` with the inverse of `matrix`. +/// +/// If `matrix` is not invertible, `false` is returned and `out` may contain invalid data. +pub fn try_invert_to(mut matrix: MatrixN, + out: &mut Matrix) + -> bool + where S: StorageMut, + DefaultAllocator: Allocator { + + assert!(matrix.is_square(), "LU inversion: unable to invert a rectangular matrix."); + let dim = matrix.nrows(); + + out.fill_with_identity(); + + for i in 0 .. dim { + let piv = matrix.slice_range(i .., i).iamax() + i; + let diag = matrix[(piv, i)]; + + if diag.is_zero() { + return false; + } + + if piv != i { + out.swap_rows(i, piv); + matrix.columns_range_mut(.. i).swap_rows(i, piv); + gauss_step_swap(&mut matrix, diag, i, piv); + } + else { + gauss_step(&mut matrix, diag, i); + } + } + + let _ = matrix.solve_lower_triangular_with_diag_mut(out, N::one()); + matrix.solve_upper_triangular_mut(out) +} + +impl, C: Dim> LU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + /// Computes the LU decomposition with partial (row) pivoting of `matrix`. + pub fn new(mut matrix: MatrixMN) -> Self { + let (nrows, ncols) = matrix.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + + let mut p = PermutationSequence::identity_generic(min_nrows_ncols); + + if min_nrows_ncols.value() == 0 { + return LU { lu: matrix, p: p }; + } + + for i in 0 .. min_nrows_ncols.value() { + let piv = matrix.slice_range(i .., i).iamax() + i; + let diag = matrix[(piv, i)]; + + if diag.is_zero() { + // No non-zero entries on this column. + continue; + } + + if piv != i { + p.append_permutation(i, piv); + matrix.columns_range_mut(.. i).swap_rows(i, piv); + gauss_step_swap(&mut matrix, diag, i, piv); + } + else { + gauss_step(&mut matrix, diag, i); + } + } + + LU { lu: matrix, p: p } + } + + #[doc(hidden)] + pub fn lu_internal(&self) -> &MatrixMN { + &self.lu + } + + /// The lower triangular matrix of this decomposition. + #[inline] + pub fn l(&self) -> MatrixMN> + where DefaultAllocator: Allocator> { + + let (nrows, ncols) = self.lu.data.shape(); + let mut m = self.lu.columns_generic(0, nrows.min(ncols)).into_owned(); + m.fill_upper_triangle(N::zero(), 1); + m.fill_diagonal(N::one()); + m + } + + /// The lower triangular matrix of this decomposition. + fn l_unpack_with_p(self) -> (MatrixMN>, + PermutationSequence>) + where DefaultAllocator: Reallocator> { + + let (nrows, ncols) = self.lu.data.shape(); + let mut m = self.lu.resize_generic(nrows, nrows.min(ncols), N::zero()); + m.fill_upper_triangle(N::zero(), 1); + m.fill_diagonal(N::one()); + (m, self.p) + } + + /// The lower triangular matrix of this decomposition. + #[inline] + pub fn l_unpack(self) -> MatrixMN> + where DefaultAllocator: Reallocator> { + + let (nrows, ncols) = self.lu.data.shape(); + let mut m = self.lu.resize_generic(nrows, nrows.min(ncols), N::zero()); + m.fill_upper_triangle(N::zero(), 1); + m.fill_diagonal(N::one()); + m + } + + + /// The upper triangular matrix of this decomposition. + #[inline] + pub fn u(&self) -> MatrixMN, C> + where DefaultAllocator: Allocator, C> { + let (nrows, ncols) = self.lu.data.shape(); + self.lu.rows_generic(0, nrows.min(ncols)).upper_triangle() + } + + /// The row permutations of this decomposition. + #[inline] + pub fn p(&self) -> &PermutationSequence> { + &self.p + } + + /// The row permutations and two triangular matrices of this decomposition: `(P, L, U)`. + #[inline] + pub fn unpack(self) -> (PermutationSequence>, + MatrixMN>, + MatrixMN, C>) + where DefaultAllocator: Allocator> + + Allocator, C> + + Reallocator> { + // Use reallocation for either l or u. + let u = self.u(); + let (l, p) = self.l_unpack_with_p(); + + (p, l, u) + } +} + +impl> LU + where DefaultAllocator: Allocator + + Allocator<(usize, usize), D> { + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// Returns `None` if `self` is not invertible. + pub fn solve(&self, b: &Matrix) -> Option> + where S2: Storage, + ShapeConstraint: SameNumberOfRows, + DefaultAllocator: Allocator { + let mut res = b.clone_owned(); + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// If the decomposed matrix is not invertible, this returns `false` and its input `b` may + /// be overwritten with garbage. + pub fn solve_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + + assert_eq!(self.lu.nrows(), b.nrows(), "LU solve matrix dimension mismatch."); + assert!(self.lu.is_square(), "LU solve: unable to solve a non-square system."); + + self.p.permute_rows(b); + let _ = self.lu.solve_lower_triangular_with_diag_mut(b, N::one()); + self.lu.solve_upper_triangular_mut(b) + } + + /// Computes the inverse of the decomposed matrix. + /// + /// Returns `None` if the matrix is not invertible. + pub fn try_inverse(&self) -> Option> { + assert!(self.lu.is_square(), "LU inverse: unable to compute the inverse of a non-square matrix."); + + let (nrows, ncols) = self.lu.data.shape(); + let mut res = MatrixN::identity_generic(nrows, ncols); + if self.try_inverse_to(&mut res) { + Some(res) + } + else { + None + } + } + + /// Computes the inverse of the decomposed matrix and outputs the result to `out`. + /// + /// If the decomposed matrix is not invertible, this returns `false` and `out` may be + /// overwritten with garbage. + pub fn try_inverse_to>(&self, out: &mut Matrix) -> bool { + assert!(self.lu.is_square(), "LU inverse: unable to compute the inverse of a non-square matrix."); + assert!(self.lu.shape() == out.shape(), "LU inverse: mismatched output shape."); + + out.fill_with_identity(); + self.solve_mut(out) + } + + /// Computes the determinant of the decomposed matrix. + pub fn determinant(&self) -> N { + let dim = self.lu.nrows(); + assert!(self.lu.is_square(), "LU determinant: unable to compute the determinant of a non-square matrix."); + + let mut res = N::one(); + for i in 0 .. dim { + res *= unsafe { *self.lu.get_unchecked(i, i) }; + } + + res * self.p.determinant() + } + + /// Indicates if the decomposed matrix is invertible. + pub fn is_invertible(&self) -> bool { + assert!(self.lu.is_square(), "QR: unable to test the invertibility of a non-square matrix."); + + for i in 0 .. self.lu.nrows() { + if self.lu[(i, i)].is_zero() { + return false; + } + } + + true + } +} + +#[doc(hidden)] +/// Executes one step of gaussian elimination on the i-th row and column of `matrix`. The diagonal +/// element `matrix[(i, i)]` is provided as argument. +pub fn gauss_step(matrix: &mut Matrix, diag: N, i: usize) + where N: Scalar + Field, + S: StorageMut { + + let mut submat = matrix.slice_range_mut(i .., i ..); + + let inv_diag = N::one() / diag; + + let (mut coeffs, mut submat) = submat.columns_range_pair_mut(0, 1 ..); + + let mut coeffs = coeffs.rows_range_mut(1 ..); + coeffs *= inv_diag; + + let (pivot_row, mut down) = submat.rows_range_pair_mut(0, 1 ..); + + for k in 0 .. pivot_row.ncols() { + down.column_mut(k).axpy(-pivot_row[k], &coeffs, N::one()); + } +} + +#[doc(hidden)] +/// Swaps the rows `i` with the row `piv` and executes one step of gaussian elimination on the i-th +/// row and column of `matrix`. The diagonal element `matrix[(i, i)]` is provided as argument. +pub fn gauss_step_swap(matrix: &mut Matrix, diag: N, i: usize, piv: usize) + where N: Scalar + Field, + S: StorageMut { + + let piv = piv - i; + let mut submat = matrix.slice_range_mut(i .., i ..); + + let inv_diag = N::one() / diag; + + let (mut coeffs, mut submat) = submat.columns_range_pair_mut(0, 1 ..); + + coeffs.swap((0, 0), (piv, 0)); + let mut coeffs = coeffs.rows_range_mut(1 ..); + coeffs *= inv_diag; + + let (mut pivot_row, mut down) = submat.rows_range_pair_mut(0, 1 ..); + + for k in 0 .. pivot_row.ncols() { + mem::swap(&mut pivot_row[k], &mut down[(piv - 1, k)]); + down.column_mut(k).axpy(-pivot_row[k], &coeffs, N::one()); + } +} + +impl, C: Dim, S: Storage> Matrix + where DefaultAllocator: Allocator + + Allocator<(usize, usize), DimMinimum> { + + /// Computes the LU decomposition with partial (row) pivoting of `matrix`. + pub fn lu(self) -> LU { + LU::new(self.into_owned()) + } +} diff --git a/src/linalg/mod.rs b/src/linalg/mod.rs new file mode 100644 index 00000000..04f7e3f8 --- /dev/null +++ b/src/linalg/mod.rs @@ -0,0 +1,35 @@ +//! [Reexported at the root of this crate.] Factorization of real matrices. + +mod solve; +mod determinant; +mod inverse; +pub mod householder; +pub mod givens; +pub mod balancing; +mod permutation_sequence; +mod qr; +mod hessenberg; +mod bidiagonal; +mod symmetric_tridiagonal; +mod cholesky; +mod lu; +mod full_piv_lu; +mod schur; +mod svd; +mod symmetric_eigen; + +//// FIXME: Not complete enough for publishing. +//// This handles only cases where each eigenvalue has multiplicity one. +// mod eigen; + +pub use self::permutation_sequence::*; +pub use self::qr::*; +pub use self::hessenberg::*; +pub use self::bidiagonal::*; +pub use self::cholesky::*; +pub use self::lu::*; +pub use self::full_piv_lu::*; +pub use self::schur::*; +pub use self::svd::*; +pub use self::symmetric_tridiagonal::*; +pub use self::symmetric_eigen::*; diff --git a/src/linalg/permutation_sequence.rs b/src/linalg/permutation_sequence.rs new file mode 100644 index 00000000..84b23354 --- /dev/null +++ b/src/linalg/permutation_sequence.rs @@ -0,0 +1,135 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num::One; +use alga::general::ClosedNeg; + +use core::{Scalar, Matrix, VectorN, DefaultAllocator}; +use dimension::{Dim, DimName, Dynamic, U1}; +use storage::StorageMut; +use allocator::Allocator; + + +/// A sequence of row or column permutations. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator<(usize, usize), D>, + VectorN<(usize, usize), D>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator<(usize, usize), D>, + VectorN<(usize, usize), D>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct PermutationSequence + where DefaultAllocator: Allocator<(usize, usize), D> { + len: usize, + ipiv: VectorN<(usize, usize), D> +} + +impl Copy for PermutationSequence + where DefaultAllocator: Allocator<(usize, usize), D>, + VectorN<(usize, usize), D>: Copy { } + +impl PermutationSequence + where DefaultAllocator: Allocator<(usize, usize), D> { + + /// Creates a new statically-allocated sequence of `D` identity permutations. + #[inline] + pub fn identity() -> Self { + Self::identity_generic(D::name()) + } +} + +impl PermutationSequence + where DefaultAllocator: Allocator<(usize, usize), Dynamic> { + + /// Creates a new dynamically-allocated sequence of `n` identity permutations. + #[inline] + pub fn identity(n: usize) -> Self { + Self::identity_generic(Dynamic::new(n)) + } +} + +impl PermutationSequence + where DefaultAllocator: Allocator<(usize, usize), D> { + /// Creates a new sequence of D identity permutations. + #[inline] + pub fn identity_generic(dim: D) -> Self { + unsafe { + PermutationSequence { + len: 0, + ipiv: VectorN::new_uninitialized_generic(dim, U1) + } + } + } + + /// Adds the interchange of the row (or column) `i` with the row (or column) `i2` to this + /// sequence of permutations. + #[inline] + pub fn append_permutation(&mut self, i: usize, i2: usize) { + if i != i2 { + assert!(self.len < self.ipiv.len(), "Maximum number of permutations exceeded."); + self.ipiv[self.len] = (i, i2); + self.len += 1; + } + } + + /// Applies this sequence of permutations to the rows of `rhs`. + #[inline] + pub fn permute_rows(&self, rhs: &mut Matrix) + where S2: StorageMut { + + for i in self.ipiv.rows_range(.. self.len).iter() { + rhs.swap_rows(i.0, i.1) + } + } + + /// Applies this sequence of permutations in reverse to the rows of `rhs`. + #[inline] + pub fn inv_permute_rows(&self, rhs: &mut Matrix) + where S2: StorageMut { + + for i in 0 .. self.len { + let (i1, i2) = self.ipiv[self.len - i - 1]; + rhs.swap_rows(i1, i2) + } + } + + /// Applies this sequence of permutations to the columns of `rhs`. + #[inline] + pub fn permute_columns(&self, rhs: &mut Matrix) + where S2: StorageMut { + + for i in self.ipiv.rows_range(.. self.len).iter() { + rhs.swap_columns(i.0, i.1) + } + } + + /// Applies this sequence of permutations in reverse to the columns of `rhs`. + #[inline] + pub fn inv_permute_columns(&self, rhs: &mut Matrix) + where S2: StorageMut { + + for i in 0 .. self.len { + let (i1, i2) = self.ipiv[self.len - i - 1]; + rhs.swap_columns(i1, i2) + } + } + + /// The number of non-identity permutations applied by this sequence. + pub fn len(&self) -> usize { + self.len + } + + /// The determinant of the matrix corresponding to this permutation. + #[inline] + pub fn determinant(&self) -> N { + if self.len % 2 == 0 { + N::one() + } + else { + -N::one() + } + } +} diff --git a/src/linalg/qr.rs b/src/linalg/qr.rs new file mode 100644 index 00000000..c9130dfe --- /dev/null +++ b/src/linalg/qr.rs @@ -0,0 +1,265 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; +use core::{Unit, Matrix, MatrixN, MatrixMN, VectorN, DefaultAllocator}; +use dimension::{Dim, DimMin, DimMinimum, U1}; +use storage::{Storage, StorageMut}; +use allocator::{Allocator, Reallocator}; +use constraint::{ShapeConstraint, SameNumberOfRows}; + +use linalg::householder; +use geometry::Reflection; + + +/// The QR decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixMN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct QR, C: Dim> + where DefaultAllocator: Allocator + + Allocator> { + qr: MatrixMN, + diag: VectorN>, +} + + +impl, C: Dim> Copy for QR + where DefaultAllocator: Allocator + + Allocator>, + MatrixMN: Copy, + VectorN>: Copy { } + +impl, C: Dim> QR + where DefaultAllocator: Allocator + + Allocator + + Allocator> { + + /// Computes the QR decomposition using householder reflections. + pub fn new(mut matrix: MatrixMN) -> Self { + let (nrows, ncols) = matrix.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + + let mut diag = unsafe { MatrixMN::new_uninitialized_generic(min_nrows_ncols, U1) }; + + if min_nrows_ncols.value() == 0 { + return QR { qr: matrix, diag: 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 } + } + + /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. + #[inline] + pub fn r(&self) -> MatrixMN, C> + where DefaultAllocator: Allocator, C>, + // FIXME: the following bound is ugly. + DimMinimum: DimMin> { + let (nrows, ncols) = self.qr.data.shape(); + let mut res = self.qr.rows_generic(0, nrows.min(ncols)).upper_triangle(); + res.set_diagonal(&self.diag); + res + } + + /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. + /// + /// This is usually faster than `r` but consumes `self`. + #[inline] + pub fn unpack_r(self) -> MatrixMN, C> + where DefaultAllocator: Reallocator, C>, + // FIXME: the following bound is ugly (needed by `set_diagonal`). + DimMinimum: DimMin> { + let (nrows, ncols) = self.qr.data.shape(); + let mut res = self.qr.resize_generic(nrows.min(ncols), ncols, N::zero()); + res.fill_lower_triangle(N::zero(), 1); + res.set_diagonal(&self.diag); + res + } + + /// Computes the orthogonal matrix `Q` of this decomposition. + pub fn q(&self) -> MatrixMN> + where DefaultAllocator: Allocator> { + let (nrows, ncols) = self.qr.data.shape(); + + // NOTE: we could build the identity matrix and call q_mul on it. + // Instead we don't so that we take in accout the matrix sparcity. + let mut res = Matrix::identity_generic(nrows, nrows.min(ncols)); + let dim = self.diag.len(); + + for i in (0 .. dim).rev() { + let axis = self.qr.slice_range(i .., i); + // FIXME: sometimes, the axis might have a zero magnitude. + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + let mut res_rows = res.slice_range_mut(i .., i ..); + refl.reflect(&mut res_rows); + } + + res + } + + /// Unpacks this decomposition into its two matrix factors. + pub fn unpack(self) -> (MatrixMN>, MatrixMN, C>) + where DimMinimum: DimMin>, + DefaultAllocator: Allocator> + + Reallocator, C> { + (self.q(), self.unpack_r()) + } + + #[doc(hidden)] + pub fn qr_internal(&self) -> &MatrixMN { + &self.qr + } + + + /// Multiplies the provided matrix by the transpose of the `Q` matrix of this decomposition. + pub fn q_tr_mul(&self, rhs: &mut Matrix) + // FIXME: do we need a static constraint on the number of rows of rhs? + where S2: StorageMut { + let dim = self.diag.len(); + + for i in 0 .. dim { + let axis = self.qr.slice_range(i .., i); + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + let mut rhs_rows = rhs.rows_range_mut(i ..); + refl.reflect(&mut rhs_rows); + } + } +} + +impl> QR + where DefaultAllocator: Allocator + + Allocator { + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// Returns `None` if `self` is not invertible. + pub fn solve(&self, b: &Matrix) -> Option> + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows, + DefaultAllocator: Allocator { + let mut res = b.clone_owned(); + + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. + /// + /// If the decomposed matrix is not invertible, this returns `false` and its input `b` is + /// overwritten with garbage. + pub fn solve_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + + assert_eq!(self.qr.nrows(), b.nrows(), "QR solve matrix dimension mismatch."); + assert!(self.qr.is_square(), "QR solve: unable to solve a non-square system."); + + self.q_tr_mul(b); + self.solve_upper_triangular_mut(b) + } + + // FIXME: duplicate code from the `solve` module. + fn solve_upper_triangular_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + + let dim = self.qr.nrows(); + + for k in 0 .. b.ncols() { + let mut b = b.column_mut(k); + for i in (0 .. dim).rev() { + let coeff; + + unsafe { + let diag = *self.diag.vget_unchecked(i); + + if diag.is_zero() { + return false; + } + + coeff = *b.vget_unchecked(i) / diag; + *b.vget_unchecked_mut(i) = coeff; + } + + b.rows_range_mut(.. i).axpy(-coeff, &self.qr.slice_range(.. i, i), N::one()); + } + } + + true + } + + /// Computes the inverse of the decomposed matrix. + /// + /// Returns `None` if the decomposed matrix is not invertible. + pub fn try_inverse(&self) -> Option> { + assert!(self.qr.is_square(), "QR inverse: unable to compute the inverse of a non-square matrix."); + + // FIXME: is there a less naive method ? + let (nrows, ncols) = self.qr.data.shape(); + let mut res = MatrixN::identity_generic(nrows, ncols); + + if self.solve_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Indicates if the decomposed matrix is invertible. + pub fn is_invertible(&self) -> bool { + assert!(self.qr.is_square(), "QR: unable to test the invertibility of a non-square matrix."); + + for i in 0 .. self.diag.len() { + if self.diag[i].is_zero() { + return false; + } + } + + true + } + + // /// Computes the determinant of the decomposed matrix. + // pub fn determinant(&self) -> N { + // let dim = self.qr.nrows(); + // assert!(self.qr.is_square(), "QR determinant: unable to compute the determinant of a non-square matrix."); + + // let mut res = N::one(); + // for i in 0 .. dim { + // res *= unsafe { *self.diag.vget_unchecked(i) }; + // } + + // res self.q_determinant() + // } +} + +impl, C: Dim, S: Storage> Matrix + where DefaultAllocator: Allocator + + Allocator + + Allocator> { + + /// Computes the QR decomposition of this matrix. + pub fn qr(self) -> QR { + QR::new(self.into_owned()) + } +} diff --git a/src/linalg/schur.rs b/src/linalg/schur.rs new file mode 100644 index 00000000..15e59211 --- /dev/null +++ b/src/linalg/schur.rs @@ -0,0 +1,530 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use std::cmp; +use num_complex::Complex; +use alga::general::Real; + +use core::{DefaultAllocator, SquareMatrix, VectorN, MatrixN, Unit, Vector2, Vector3}; +use core::dimension::{Dim, DimSub, DimDiff, Dynamic, U1, U2, U3}; +use core::storage::Storage; +use constraint::{ShapeConstraint, DimEq}; +use allocator::Allocator; + +use linalg::householder; +use linalg::Hessenberg; +use geometry::{Reflection, UnitComplex}; + + + +/// Real Schur decomposition of a square matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct RealSchur + where DefaultAllocator: Allocator { + q: MatrixN, + t: MatrixN +} + + +impl Copy for RealSchur + where DefaultAllocator: Allocator, + MatrixN: Copy { } + +impl RealSchur + where D: DimSub, // For Hessenberg. + ShapeConstraint: DimEq>, // For Hessenberg. + DefaultAllocator: Allocator> + // For Hessenberg. + Allocator> + // For Hessenberg. + Allocator + + Allocator { + + /// Computes the Schur decomposition of a square matrix. + pub fn new(m: MatrixN) -> RealSchur { + Self::try_new(m, N::default_epsilon(), 0).unwrap() + } + + /// Attempts to compute the Schur decomposition of a square matrix. + /// + /// If only eigenvalues are needed, it is more efficient to call the matrix method + /// `.eigenvalues()` instead. + /// + /// # Arguments + /// + /// * `eps` − tolerence used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_new(m: MatrixN, eps: N, 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)| + RealSchur { q: q.unwrap(), t: t }) + } + + fn do_decompose(mut m: MatrixN, work: &mut VectorN, eps: N, max_niter: usize, compute_q: bool) + -> Option<(Option>, MatrixN)> { + + assert!(m.is_square(), + "Unable to compute the eigenvectors and eigenvalues of a non-square matrix."); + + let dim = m.data.shape().0; + + if dim.value() == 0 { + let vecs = Some(MatrixN::from_element_generic(dim, dim, N::zero())); + let vals = MatrixN::from_element_generic(dim, dim, N::zero()); + return Some((vecs, vals)); + } + else if dim.value() == 1 { + if compute_q { + let q = MatrixN::from_element_generic(dim, dim, N::one()); + return Some((Some(q), m)); + } + else { + return Some((None, m)); + } + } + // Specialization would make this easier. + else if dim.value() == 2 { + return decompose_2x2(m, compute_q); + } + + let amax_m = m.amax(); + m /= amax_m; + + let hess = Hessenberg::new_with_workspace(m, work); + let mut q; + let mut t; + + if compute_q { + // FIXME: could we work without unpacking? Using only the internal representation of + // hessenberg decomposition. + let (vecs, vals) = hess.unpack(); + q = Some(vecs); + t = vals; + } + else { + q = None; + t = hess.unpack_h() + } + + // Implicit double-shift QR method. + let mut niter = 0; + let (mut start, mut end) = Self::delimit_subproblem(&mut t, eps, dim.value() - 1); + + while end != start { + let subdim = end - start + 1; + + if subdim > 2 { + 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 h22 = t[(start + 1, start + 1)]; + let h32 = t[(start + 2, start + 1)]; + + let hnn = t[(n, n)]; + let hmm = t[(m, m)]; + let hnm = t[(n, m)]; + let hmn = t[(m, n)]; + + let tra = hnn + hmm; + let det = hnn * hmm - hnm * hmn; + + let mut axis = Vector3::new(h11 * h11 + h12 * h21 - tra * h11 + det, + h21 * (h11 + h22 - tra), + h21 * h32); + + for k in start .. n - 1 { + let (norm, not_zero) = householder::reflection_axis_mut(&mut axis); + + if not_zero { + if k > start { + t[(k + 0, k - 1)] = norm; + t[(k + 1, k - 1)] = N::zero(); + t[(k + 2, k - 1)] = N::zero(); + } + + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + { + let krows = cmp::min(k + 4, end + 1); + let mut work = work.rows_mut(0, krows); + refl.reflect(&mut t.generic_slice_mut((k, k), (U3, Dynamic::new(dim.value() - k)))); + refl.reflect_rows(&mut t.generic_slice_mut((0, k), (Dynamic::new(krows), U3)), &mut work); + } + + if let Some(ref mut q) = q { + refl.reflect_rows(&mut q.generic_slice_mut((0, k), (dim, U3)), work); + } + } + + axis.x = t[(k + 1, k)]; + axis.y = t[(k + 2, k)]; + + if k < n - 2 { + axis.z = t[(k + 3, k)]; + } + } + + let mut axis = Vector2::new(axis.x, axis.y); + let (norm, not_zero) = householder::reflection_axis_mut(&mut axis); + + if not_zero { + let refl = Reflection::new(Unit::new_unchecked(axis), N::zero()); + + t[(m, m - 1)] = norm; + t[(n, m - 1)] = N::zero(); + + { + let mut work = work.rows_mut(0, end + 1); + refl.reflect(&mut t.generic_slice_mut((m, m), (U2, Dynamic::new(dim.value() - m)))); + refl.reflect_rows(&mut t.generic_slice_mut((0, m), (Dynamic::new(end + 1), U2)), &mut work); + } + + if let Some(ref mut q) = q { + refl.reflect_rows(&mut q.generic_slice_mut((0, m), (dim, U2)), work); + } + } + } + else { + // Decouple the 2x2 block if it has real eigenvalues. + if let Some(rot) = compute_2x2_basis(&t.fixed_slice::(start, start)) { + let inv_rot = rot.inverse(); + inv_rot.rotate(&mut t.generic_slice_mut((start, start), (U2, Dynamic::new(dim.value() - start)))); + rot.rotate_rows(&mut t.generic_slice_mut((0, start), (Dynamic::new(end + 1), U2))); + t[(end, start)] = N::zero(); + + if let Some(ref mut q) = q { + rot.rotate_rows(&mut q.generic_slice_mut((0, start), (dim, U2))); + } + } + + // Check if we reached the beginning of the matrix. + if end > 2 { + end -= 2; + } + else { + break; + } + } + + let sub = Self::delimit_subproblem(&mut t, eps, end); + + start = sub.0; + end = sub.1; + + niter += 1; + if niter == max_niter { + return None; + } + } + + t *= amax_m; + + Some((q, t)) + } + + /// Computes the eigenvalues of the decomposed matrix. + fn do_eigenvalues(t: &MatrixN, out: &mut VectorN) -> bool { + let dim = t.nrows(); + let mut m = 0; + + while m < dim - 1 { + let n = m + 1; + + if t[(n, m)].is_zero() { + out[m] = t[(m, m)]; + m += 1; + } + else { + // Complex eigenvalue. + return false; + } + } + + if m == dim - 1 { + out[m] = t[(m, m)]; + } + + true + } + + /// Computes the complex eigenvalues of the decomposed matrix. + fn do_complex_eigenvalues(t: &MatrixN, out: &mut VectorN, D>) + where DefaultAllocator: Allocator, D> { + let dim = t.nrows(); + let mut m = 0; + + while m < dim - 1 { + let n = m + 1; + + if t[(n, m)].is_zero() { + out[m] = Complex::new(t[(m, m)], N::zero()); + m += 1; + } + else { + // Solve the 2x2 eigenvalue subproblem. + let hmm = t[(m, m)]; + let hnm = t[(n, m)]; + let hmn = t[(m, n)]; + let hnn = t[(n, n)]; + + let tra = hnn + hmm; + let det = hnn * hmm - hnm * hmn; + let discr = tra * tra * ::convert(0.25) - det; + + // All 2x2 blocks have negative discriminant because we already decoupled those + // with positive eigenvalues.. + let sqrt_discr = Complex::new(N::zero(), (-discr).sqrt()); + + out[m] = Complex::new(tra * ::convert(0.5), N::zero()) + sqrt_discr; + out[m + 1] = Complex::new(tra * ::convert(0.5), N::zero()) - sqrt_discr; + + m += 2; + } + } + + if m == dim - 1 { + out[m] = Complex::new(t[(m, m)], N::zero()); + } + } + + fn delimit_subproblem(t: &mut MatrixN, eps: N, end: usize) -> (usize, usize) + where D: DimSub, + DefaultAllocator: Allocator> { + + let mut n = end; + + while n > 0 { + let m = n - 1; + + if t[(n, m)].abs() <= eps * (t[(n, n)].abs() + t[(m, m)].abs()) { + t[(n, m)] = N::zero(); + } + else { + break; + } + + n -= 1; + } + + if n == 0 { + return (0, 0); + } + + let mut new_start = n - 1; + while new_start > 0 { + let m = new_start - 1; + + let off_diag = t[(new_start, m)]; + if off_diag.is_zero() || + off_diag.abs() <= eps * (t[(new_start, new_start)].abs() + t[(m, m)].abs()) { + t[(new_start, m)] = N::zero(); + break; + } + + new_start -= 1; + } + + (new_start, n) + } + + /// Retrieves the unitary matrix `Q` and the upper-quasitriangular matrix `T` such that the + /// decomposed matrix equals `Q * T * Q.transpose()`. + pub fn unpack(self) -> (MatrixN, MatrixN) { + (self.q, self.t) + } + + /// Computes the real eigenvalues of the decomposed matrix. + /// + /// Return `None` if some eigenvalues are complex. + pub fn eigenvalues(&self) -> Option> { + let mut out = unsafe { VectorN::new_uninitialized_generic(self.t.data.shape().0, U1) }; + if Self::do_eigenvalues(&self.t, &mut out) { + Some(out) + } + else { + None + } + } + + /// Computes the complex eigenvalues of the decomposed matrix. + pub fn complex_eigenvalues(&self) -> VectorN, D> + where DefaultAllocator: Allocator, D> { + let mut out = unsafe { VectorN::new_uninitialized_generic(self.t.data.shape().0, U1) }; + Self::do_complex_eigenvalues(&self.t, &mut out); + out + } +} + +fn decompose_2x2(mut m: MatrixN, compute_q: bool) + -> Option<(Option>, MatrixN)> + where DefaultAllocator: Allocator { + + let dim = m.data.shape().0; + let mut q = None; + match compute_2x2_basis(&m.fixed_slice::(0, 0)) { + Some(rot) => { + let mut m = m.fixed_slice_mut::(0, 0); + let inv_rot = rot.inverse(); + inv_rot.rotate(&mut m); + rot.rotate_rows(&mut m); + + if compute_q { + let c = rot.unwrap(); + // XXX: we have to build the matrix manually because + // rot.to_rotation_matrix().unwrap() causes an ICE. + q = Some(MatrixN::from_column_slice_generic(dim, dim, &[c.re, c.im, + -c.im, c.re])); + } + }, + None => if compute_q { q = Some(MatrixN::identity_generic(dim, dim)); } + }; + + Some((q, m)) +} + +fn compute_2x2_eigvals>(m: &SquareMatrix) + -> Option<(N, N)> { + + // Solve the 2x2 eigenvalue subproblem. + let h00 = m[(0, 0)]; + let h10 = m[(1, 0)]; + let h01 = m[(0, 1)]; + let h11 = m[(1, 1)]; + + // NOTE: this discriminant computation is mor stable than the + // one based on the trace and determinant: 0.25 * tra * tra - det + // because et ensures positiveness for symmetric matrices. + let val = (h00 - h11) * ::convert(0.5); + let discr = h10 * h01 + val * val; + + if discr >= N::zero() { + let sqrt_discr = discr.sqrt(); + let half_tra = (h00 + h11) * ::convert(0.5); + Some((half_tra + sqrt_discr, half_tra - sqrt_discr)) + } + else { + None + } +} + +// Computes the 2x2 transformation that upper-triangulates a 2x2 matrix with real eigenvalues. +/// Computes the singular vectors for a 2x2 matrix. +/// +/// Returns `None` if the matrix has complex eigenvalues, or is upper-triangular. In both case, +/// the basis is the identity. +fn compute_2x2_basis>(m: &SquareMatrix) + -> Option> { + let h10 = m[(1, 0)]; + + if h10.is_zero() { + return None; + } + + if let Some((eigval1, eigval2)) = compute_2x2_eigvals(m) { + let x1 = m[(1, 1)] - eigval1; + let x2 = m[(1, 1)] - eigval2; + + // NOTE: Choose the one that yields a larger x component. + // This is necessary for numerical stability of the normalization of the complex + // number. + let basis = if x1.abs() > x2.abs() { + Complex::new(x1, -h10) + } + else { + Complex::new(x2, -h10) + }; + + Some(UnitComplex::from_complex(basis)) + } + else { + None + } +} + +impl> SquareMatrix + where D: DimSub, // For Hessenberg. + ShapeConstraint: DimEq>, // For Hessenberg. + DefaultAllocator: Allocator> + // For Hessenberg. + Allocator> + // For Hessenberg. + Allocator + + Allocator { + /// Computes the Schur decomposition of a square matrix. + pub fn real_schur(self) -> RealSchur { + RealSchur::new(self.into_owned()) + } + + /// Attempts to compute the Schur decomposition of a square matrix. + /// + /// If only eigenvalues are needed, it is more efficient to call the matrix method + /// `.eigenvalues()` instead. + /// + /// # Arguments + /// + /// * `eps` − tolerence used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_real_schur(self, eps: N, max_niter: usize) -> Option> { + RealSchur::try_new(self.into_owned(), eps, max_niter) + } + + /// Computes the eigenvalues of this matrix. + pub fn eigenvalues(&self) -> Option> { + assert!(self.is_square(), "Unable to compute eigenvalues of a non-square matrix."); + + let mut work = unsafe { + VectorN::new_uninitialized_generic(self.data.shape().0, U1) + }; + + // Special case for 2x2 natrices. + if self.nrows() == 2 { + // FIXME: can we avoid this slicing + // (which is needed here just to transform D to U2)? + let me = self.fixed_slice::(0, 0); + return match compute_2x2_eigvals(&me) { + Some((a, b)) => { + work[0] = a; + work[1] = b; + Some(work) + }, + None => None + } + } + + // FIXME: add balancing? + let schur = RealSchur::do_decompose(self.clone_owned(), &mut work, N::default_epsilon(), 0, false).unwrap(); + if RealSchur::do_eigenvalues(&schur.1, &mut work) { + Some(work) + } + else { + None + } + } + + /// Computes the eigenvalues of this matrix. + pub fn complex_eigenvalues(&self) -> VectorN, D> + // FIXME: add balancing? + where DefaultAllocator: Allocator, D> { + + let dim = self.data.shape().0; + let mut work = unsafe { VectorN::new_uninitialized_generic(dim, U1) }; + + let schur = RealSchur::do_decompose(self.clone_owned(), &mut work, N::default_epsilon(), 0, false).unwrap(); + let mut eig = unsafe { VectorN::new_uninitialized_generic(dim, U1) }; + RealSchur::do_complex_eigenvalues(&schur.1, &mut eig); + eig + } +} diff --git a/src/linalg/solve.rs b/src/linalg/solve.rs new file mode 100644 index 00000000..9a721c20 --- /dev/null +++ b/src/linalg/solve.rs @@ -0,0 +1,273 @@ +use alga::general::Real; + +use core::{DefaultAllocator, Matrix, SquareMatrix, Vector, MatrixMN}; +use core::dimension::{Dim, U1}; +use core::storage::{Storage, StorageMut}; +use core::allocator::Allocator; +use core::constraint::{ShapeConstraint, SameNumberOfRows}; + + + +impl> SquareMatrix { + /// Computes the solution of the linear system `self . x = b` where `x` is the unknown and only + /// the lower-triangular part of `self` (including the diagonal) is concidered not-zero. + #[inline] + pub fn solve_lower_triangular(&self, b: &Matrix) + -> Option> + where S2: StorageMut, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + let mut res = b.clone_owned(); + if self.solve_lower_triangular_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Computes the solution of the linear system `self . x = b` where `x` is the unknown and only + /// the upper-triangular part of `self` (including the diagonal) is concidered not-zero. + #[inline] + pub fn solve_upper_triangular(&self, b: &Matrix) + -> Option> + where S2: StorageMut, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + let mut res = b.clone_owned(); + if self.solve_upper_triangular_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self . x = b` where `x` is the unknown and only the + /// lower-triangular part of `self` (including the diagonal) is concidered not-zero. + pub fn solve_lower_triangular_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let cols = b.ncols(); + + for i in 0 .. cols { + if !self.solve_lower_triangular_vector_mut(&mut b.column_mut(i)) { + return false + } + } + + true + } + + fn solve_lower_triangular_vector_mut(&self, b: &mut Vector) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let dim = self.nrows(); + + for i in 0 .. dim { + let coeff; + + unsafe { + let diag = *self.get_unchecked(i, i); + + if diag.is_zero() { + return false; + } + + coeff = *b.vget_unchecked(i) / diag; + *b.vget_unchecked_mut(i) = coeff; + } + + b.rows_range_mut(i + 1 ..).axpy(-coeff, &self.slice_range(i + 1 .., i), N::one()); + } + + true + } + + // FIXME: add the same but for solving upper-triangular. + /// Solves the linear system `self . x = b` where `x` is the unknown and only the + /// lower-triangular part of `self` is concidered not-zero. The diagonal is never read as it is + /// assumed to be equal to `diag`. Returns `false` and does not modify its inputs if `diag` is zero. + pub fn solve_lower_triangular_with_diag_mut(&self, b: &mut Matrix, diag: N) + -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + if diag.is_zero() { + return false; + } + + let dim = self.nrows(); + let cols = b.ncols(); + + for k in 0 .. cols { + let mut bcol = b.column_mut(k); + + for i in 0 .. dim - 1 { + let coeff = unsafe { *bcol.vget_unchecked(i) } / diag; + bcol.rows_range_mut(i + 1 ..).axpy(-coeff, &self.slice_range(i + 1 .., i), N::one()); + } + } + + true + } + + /// Solves the linear system `self . x = b` where `x` is the unknown and only the + /// upper-triangular part of `self` (including the diagonal) is concidered not-zero. + pub fn solve_upper_triangular_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let cols = b.ncols(); + + for i in 0 .. cols { + if !self.solve_upper_triangular_vector_mut(&mut b.column_mut(i)) { + return false; + } + } + + true + } + + fn solve_upper_triangular_vector_mut(&self, b: &mut Vector) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let dim = self.nrows(); + + for i in (0 .. dim).rev() { + let coeff; + + unsafe { + let diag = *self.get_unchecked(i, i); + + if diag.is_zero() { + return false; + } + + coeff = *b.vget_unchecked(i) / diag; + *b.vget_unchecked_mut(i) = coeff; + } + + b.rows_range_mut(.. i).axpy(-coeff, &self.slice_range(.. i, i), N::one()); + } + + true + } + + /* + * + * Transpose versions + * + */ + + /// Computes the solution of the linear system `self.transpose() . x = b` where `x` is the unknown and only + /// the lower-triangular part of `self` (including the diagonal) is concidered not-zero. + #[inline] + pub fn tr_solve_lower_triangular(&self, b: &Matrix) + -> Option> + where S2: StorageMut, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + let mut res = b.clone_owned(); + if self.tr_solve_lower_triangular_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Computes the solution of the linear system `self.transpose() . x = b` where `x` is the unknown and only + /// the upper-triangular part of `self` (including the diagonal) is concidered not-zero. + #[inline] + pub fn tr_solve_upper_triangular(&self, b: &Matrix) + -> Option> + where S2: StorageMut, + DefaultAllocator: Allocator, + ShapeConstraint: SameNumberOfRows { + let mut res = b.clone_owned(); + if self.tr_solve_upper_triangular_mut(&mut res) { + Some(res) + } + else { + None + } + } + + /// Solves the linear system `self.transpose() . x = b` where `x` is the unknown and only the + /// lower-triangular part of `self` (including the diagonal) is concidered not-zero. + pub fn tr_solve_lower_triangular_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let cols = b.ncols(); + + for i in 0 .. cols { + if !self.tr_solve_lower_triangular_vector_mut(&mut b.column_mut(i)) { + return false; + } + } + + true + } + + fn tr_solve_lower_triangular_vector_mut(&self, b: &mut Vector) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let dim = self.nrows(); + + for i in (0 .. dim).rev() { + let dot = self.slice_range(i + 1 .., i).dot(&b.slice_range(i + 1 .., 0)); + + unsafe { + let b_i = b.vget_unchecked_mut(i); + + let diag = *self.get_unchecked(i, i); + + if diag.is_zero() { + return false; + } + + *b_i = (*b_i - dot) / diag; + } + } + + true + } + + /// Solves the linear system `self.transpose() . x = b` where `x` is the unknown and only the + /// upper-triangular part of `self` (including the diagonal) is concidered not-zero. + pub fn tr_solve_upper_triangular_mut(&self, b: &mut Matrix) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let cols = b.ncols(); + + for i in 0 .. cols { + if !self.tr_solve_upper_triangular_vector_mut(&mut b.column_mut(i)) { + return false; + } + } + + true + } + + fn tr_solve_upper_triangular_vector_mut(&self, b: &mut Vector) -> bool + where S2: StorageMut, + ShapeConstraint: SameNumberOfRows { + let dim = self.nrows(); + + for i in 0 .. dim { + let dot = self.slice_range(.. i, i).dot(&b.slice_range(.. i, 0)); + + unsafe { + let b_i = b.vget_unchecked_mut(i); + let diag = *self.get_unchecked(i, i); + + if diag.is_zero() { + return false; + } + + *b_i = (*b_i - dot) / diag; + } + } + + true + } +} diff --git a/src/linalg/svd.rs b/src/linalg/svd.rs new file mode 100644 index 00000000..c22ba6af --- /dev/null +++ b/src/linalg/svd.rs @@ -0,0 +1,560 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num_complex::Complex; +use std::ops::MulAssign; + +use alga::general::Real; +use core::{Matrix, MatrixMN, VectorN, DefaultAllocator, Matrix2x3, Vector2}; +use dimension::{Dim, DimMin, DimMinimum, DimSub, DimDiff, U1, U2}; +use storage::Storage; +use allocator::Allocator; +use constraint::{ShapeConstraint, SameNumberOfRows}; + +use linalg::givens; +use linalg::symmetric_eigen; +use linalg::Bidiagonal; +use geometry::UnitComplex; + + + +/// Singular Value Decomposition of a general matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator> + + Allocator, C> + + Allocator>, + MatrixMN>: serde::Serialize, + MatrixMN, C>: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator> + + Allocator, C> + + Allocator>, + MatrixMN>: serde::Deserialize<'de>, + MatrixMN, C>: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct SVD, C: Dim> + where DefaultAllocator: Allocator, C> + + Allocator> + + Allocator> { + /// The left-singular vectors `U` of this SVD. + pub u: Option>>, + /// The right-singular vectors `V^t` of this SVD. + pub v_t: Option, C>>, + /// The singular values of this SVD. + pub singular_values: VectorN>, +} + + +impl, C: Dim> Copy for SVD + where DefaultAllocator: Allocator, C> + + Allocator> + + Allocator>, + MatrixMN>: Copy, + MatrixMN, C>: Copy, + VectorN>: Copy { } + +impl, C: Dim> SVD + where DimMinimum: DimSub, // for Bidiagonal. + DefaultAllocator: Allocator + + Allocator + // for Bidiagonal + Allocator + // for Bidiagonal + Allocator, U1>> + // for Bidiagonal + Allocator, C> + + Allocator> + + Allocator> { + + /// Computes the Singular Value Decomposition of `matrix` using implicit shift. + pub fn new(matrix: MatrixMN, compute_u: bool, compute_v: bool) -> Self { + Self::try_new(matrix, compute_u, compute_v, N::default_epsilon(), 0).unwrap() + } + + /// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift. + /// + /// # Arguments + /// + /// * `compute_u` − set this to `true` to enable the computation of left-singular vectors. + /// * `compute_v` − set this to `true` to enable the computation of left-singular vectors. + /// * `eps` − tolerence used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_new(mut matrix: MatrixMN, + compute_u: bool, + compute_v: bool, + eps: N, + max_niter: usize) + -> Option { + assert!(matrix.len() != 0, "Cannot compute the SVD of an empty matrix."); + let (nrows, ncols) = matrix.data.shape(); + let min_nrows_ncols = nrows.min(ncols); + let dim = min_nrows_ncols.value(); + + let m_amax = matrix.amax(); + + if !m_amax.is_zero() { + matrix /= m_amax; + } + + let mut b = Bidiagonal::new(matrix); + let mut u = if compute_u { Some(b.u()) } else { None }; + let mut v_t = if compute_v { Some(b.v_t()) } else { None }; + + let mut niter = 0; + let (mut start, mut end) = Self::delimit_subproblem(&mut b, &mut u, &mut v_t, dim - 1, eps); + + while end != start { + let subdim = end - start + 1; + + // Solve the subproblem. + if subdim > 2 { + let m = end - 1; + let n = end; + + let mut vec; + { + let dm = b.diagonal[m]; + let dn = b.diagonal[n]; + let fm = b.off_diagonal[m]; + + let tmm = dm * dm + b.off_diagonal[m - 1] * b.off_diagonal[m - 1]; + let tmn = dm * fm; + let tnn = dn * dn + fm * fm; + + let shift = symmetric_eigen::wilkinson_shift(tmm, tnn, tmn); + + vec = Vector2::new(b.diagonal[start] * b.diagonal[start] - shift, + b.diagonal[start] * b.off_diagonal[start]); + } + + + for k in start .. n { + let m12 = if k == n - 1 { N::zero() } else { b.off_diagonal[k + 1] }; + + let mut subm = Matrix2x3::new( + b.diagonal[k], b.off_diagonal[k], N::zero(), + N::zero(), b.diagonal[k + 1], m12); + + if let Some((rot1, norm1)) = givens::cancel_y(&vec) { + rot1.conjugate().rotate_rows(&mut subm.fixed_columns_mut::(0)); + + if k > start { + // This is not the first iteration. + b.off_diagonal[k - 1] = norm1; + } + + let v = Vector2::new(subm[(0, 0)], subm[(1, 0)]); + // FIXME: does the case `v.y == 0` ever happen? + let (rot2, norm2) = givens::cancel_y(&v).unwrap_or((UnitComplex::identity(), subm[(0, 0)])); + rot2.rotate(&mut subm.fixed_columns_mut::(1)); + subm[(0, 0)] = norm2; + + if let Some(ref mut v_t) = v_t { + if b.is_upper_diagonal() { + rot1.rotate(&mut v_t.fixed_rows_mut::(k)); + } + else { + rot2.rotate(&mut v_t.fixed_rows_mut::(k)); + } + } + + if let Some(ref mut u) = u { + if b.is_upper_diagonal() { + rot2.inverse().rotate_rows(&mut u.fixed_columns_mut::(k)); + } + else { + rot1.inverse().rotate_rows(&mut u.fixed_columns_mut::(k)); + } + } + + b.diagonal[k + 0] = subm[(0, 0)]; + b.diagonal[k + 1] = subm[(1, 1)]; + b.off_diagonal[k + 0] = subm[(0, 1)]; + + if k != n - 1 { + b.off_diagonal[k + 1] = subm[(1, 2)]; + } + + vec.x = subm[(0, 1)]; + vec.y = subm[(0, 2)]; + } + else { + break; + } + } + } + else if subdim == 2 { + // Solve the remaining 2x2 subproblem. + let (u2, s, v2) = Self::compute_2x2_uptrig_svd( + b.diagonal[start], b.off_diagonal[start], b.diagonal[start + 1], + compute_u && b.is_upper_diagonal() || compute_v && !b.is_upper_diagonal(), + compute_v && b.is_upper_diagonal() || compute_u && !b.is_upper_diagonal()); + + b.diagonal[start + 0] = s[0]; + b.diagonal[start + 1] = s[1]; + b.off_diagonal[start] = N::zero(); + + if let Some(ref mut u) = u { + let rot = if b.is_upper_diagonal() { u2.unwrap() } else { v2.unwrap() }; + rot.rotate_rows(&mut u.fixed_columns_mut::(start)); + } + + if let Some(ref mut v_t) = v_t { + let rot = if b.is_upper_diagonal() { v2.unwrap() } else { u2.unwrap() }; + rot.inverse().rotate(&mut v_t.fixed_rows_mut::(start)); + } + + end -= 1; + } + + // Re-delimit the suproblem in case some decoupling occured. + let sub = Self::delimit_subproblem(&mut b, &mut u, &mut v_t, end, eps); + start = sub.0; + end = sub.1; + + niter += 1; + if niter == max_niter { + return None; + } + } + + b.diagonal *= m_amax; + + // Ensure all singular value are non-negative. + for i in 0 .. dim { + let sval = b.diagonal[i]; + if sval < N::zero() { + b.diagonal[i] = -sval; + + if let Some(ref mut u) = u { + u.column_mut(i).neg_mut(); + } + } + } + + Some(SVD { u: u, v_t: v_t, singular_values: b.diagonal }) + } + + // Explicit formulaes inspired from the paper "Computing the Singular Values of 2-by-2 Complex + // Matrices", Sanzheng Qiao and Xiaohong Wang. + // http://www.cas.mcmaster.ca/sqrl/papers/sqrl5.pdf + fn compute_2x2_uptrig_svd(m11: N, m12: N, m22: N, compute_u: bool, compute_v: bool) + -> (Option>, Vector2, Option>) { + + let two: N = ::convert(2.0f64); + let half: N = ::convert(0.5f64); + + let denom = (m11 + m22).hypot(m12) + (m11 - m22).hypot(m12); + + // NOTE: v1 is the singular value that is the closest to m22. + // This prevents cancellation issues when constructing the vector `csv` bellow. If we chose + // otherwise, we would have v1 ~= m11 when m12 is small. This would cause catastrofic + // cancellation on `v1 * v1 - m11 * m11` bellow. + let v1 = two * m11 * m22 / denom; + let v2 = half * denom; + + let mut u = None; + let mut v_t = None; + + if compute_u || compute_v { + let csv = Vector2::new(m11 * m12, v1 * v1 - m11 * m11).normalize(); + + if compute_v { + v_t = Some(UnitComplex::new_unchecked(Complex::new(csv.x, csv.y))); + } + + if compute_u { + let cu = (m11 * csv.x + m12 * csv.y) / v1; + let su = (m22 * csv.y) / v1; + + u = Some(UnitComplex::new_unchecked(Complex::new(cu, su))); + } + } + + (u, Vector2::new(v1, v2), v_t) + } + + /* + fn display_bidiag(b: &Bidiagonal, begin: usize, end: usize) { + for i in begin .. end { + for k in begin .. i { + print!(" "); + } + println!("{} {}", b.diagonal[i], b.off_diagonal[i]); + } + for k in begin .. end { + print!(" "); + } + println!("{}", b.diagonal[end]); + } + */ + + fn delimit_subproblem(b: &mut Bidiagonal, + u: &mut Option>>, + v_t: &mut Option, C>>, + end: usize, + eps: N) + -> (usize, usize) { + let mut n = end; + + while n > 0 { + let m = n - 1; + + if b.off_diagonal[m].is_zero() || + b.off_diagonal[m].abs() <= eps * (b.diagonal[n].abs() + b.diagonal[m].abs()) { + + b.off_diagonal[m] = N::zero(); + } + else if b.diagonal[m].abs() <= eps { + b.diagonal[m] = N::zero(); + Self::cancel_horizontal_off_diagonal_elt(b, u, v_t, m, m + 1); + + if m != 0 { + Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m - 1); + } + } + else if b.diagonal[n].abs() <= eps { + b.diagonal[n] = N::zero(); + Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m); + } + else { + break; + } + + n -= 1; + } + + if n == 0 { + return (0, 0); + } + + let mut new_start = n - 1; + while new_start > 0 { + let m = new_start - 1; + + if b.off_diagonal[m].abs() <= eps * (b.diagonal[new_start].abs() + b.diagonal[m].abs()) { + b.off_diagonal[m] = N::zero(); + break; + } + // FIXME: write a test that enters this case. + else if b.diagonal[m].abs() <= eps { + b.diagonal[m] = N::zero(); + Self::cancel_horizontal_off_diagonal_elt(b, u, v_t, m, n); + + if m != 0 { + Self::cancel_vertical_off_diagonal_elt(b, u, v_t, m - 1); + } + break; + } + + new_start -= 1; + } + + (new_start, n) + } + + // Cancels the i-th off-diagonal element using givens rotations. + fn cancel_horizontal_off_diagonal_elt(b: &mut Bidiagonal, + u: &mut Option>>, + v_t: &mut Option, C>>, + i: usize, + end: usize) { + let mut v = Vector2::new(b.off_diagonal[i], b.diagonal[i + 1]); + b.off_diagonal[i] = N::zero(); + + for k in i .. end { + if let Some((rot, norm)) = givens::cancel_x(&v) { + b.diagonal[k + 1] = norm; + + if b.is_upper_diagonal() { + if let Some(ref mut u) = *u { + rot.inverse().rotate_rows(&mut u.fixed_columns_with_step_mut::(i, k - i)); + } + } + else if let Some(ref mut v_t) = *v_t { + rot.rotate(&mut v_t.fixed_rows_with_step_mut::(i, k - i)); + } + + if k + 1 != end { + v.x = -rot.sin_angle() * b.off_diagonal[k + 1]; + v.y = b.diagonal[k + 2]; + b.off_diagonal[k + 1] *= rot.cos_angle(); + } + } + else { + break; + } + } + } + + // Cancels the i-th off-diagonal element using givens rotations. + fn cancel_vertical_off_diagonal_elt(b: &mut Bidiagonal, + u: &mut Option>>, + v_t: &mut Option, C>>, + i: usize) { + let mut v = Vector2::new(b.diagonal[i], b.off_diagonal[i]); + b.off_diagonal[i] = N::zero(); + + for k in (0 .. i + 1).rev() { + if let Some((rot, norm)) = givens::cancel_y(&v) { + b.diagonal[k] = norm; + + if b.is_upper_diagonal() { + if let Some(ref mut v_t) = *v_t { + rot.rotate(&mut v_t.fixed_rows_with_step_mut::(k, i - k)); + } + } + else if let Some(ref mut u) = *u { + rot.inverse().rotate_rows(&mut u.fixed_columns_with_step_mut::(k, i - k)); + } + + if k > 0 { + v.x = b.diagonal[k - 1]; + v.y = rot.sin_angle() * b.off_diagonal[k - 1]; + b.off_diagonal[k - 1] *= rot.cos_angle(); + } + } + else { + break; + } + } + } + + /// Computes the rank of the decomposed matrix, i.e., the number of singular values greater + /// than `eps`. + pub fn rank(&self, eps: N) -> usize { + assert!(eps >= N::zero(), "SVD rank: the epsilon must be non-negative."); + self.singular_values.iter().filter(|e| **e > eps).count() + } + + /// Rebuild the original matrix. + /// + /// This is useful if some of the singular values have been manually modified. Panics if the + /// right- and left- singular vectors have not been computed at construction-time. + pub fn recompose(self) -> MatrixMN { + let mut u = self.u.expect("SVD recomposition: U has not been computed."); + let v_t = self.v_t.expect("SVD recomposition: V^t has not been computed."); + + for i in 0 .. self.singular_values.len() { + let val = self.singular_values[i]; + u.column_mut(i).mul_assign(val); + } + + u * v_t + } + + /// Computes the pseudo-inverse of the decomposed matrix. + /// + /// Any singular value smaller than `eps` is assumed to be zero. + /// Panics if the right- and left- singular vectors have not been computed at + /// construction-time. + pub fn pseudo_inverse(mut self, eps: N) -> MatrixMN + where DefaultAllocator: Allocator { + + assert!(eps >= N::zero(), "SVD pseudo inverse: the epsilon must be non-negative."); + for i in 0 .. self.singular_values.len() { + let val = self.singular_values[i]; + + if val > eps { + self.singular_values[i] = N::one() / val; + } + else { + self.singular_values[i] = N::zero(); + } + } + + self.recompose().transpose() + } + + /// Solves the system `self * x = b` where `self` is the decomposed matrix and `x` the unknown. + /// + /// Any singular value smaller than `eps` is assumed to be zero. + /// Returns `None` if the singular vectors `U` and `V` have not been computed. + // FIXME: make this more generic wrt the storage types and the dimensions for `b`. + pub fn solve(&self, b: &Matrix, eps: N) -> MatrixMN + where S2: Storage, + DefaultAllocator: Allocator + + Allocator, C2>, + ShapeConstraint: SameNumberOfRows { + + assert!(eps >= N::zero(), "SVD solve: the epsilon must be non-negative."); + let u = self.u.as_ref().expect("SVD solve: U has not been computed."); + let v_t = self.v_t.as_ref().expect("SVD solve: V^t has not been computed."); + + let mut ut_b = u.tr_mul(b); + + for j in 0 .. ut_b.ncols() { + let mut col = ut_b.column_mut(j); + + for i in 0 .. self.singular_values.len() { + let val = self.singular_values[i]; + if val > eps { + col[i] /= val; + } + else { + col[i] = N::zero(); + } + } + } + + v_t.tr_mul(&ut_b) + } +} + + +impl, C: Dim, S: Storage> Matrix + where DimMinimum: DimSub, // for Bidiagonal. + DefaultAllocator: Allocator + + Allocator + // for Bidiagonal + Allocator + // for Bidiagonal + Allocator, U1>> + // for Bidiagonal + Allocator, C> + + Allocator> + + Allocator> { + /// Computes the Singular Value Decomposition using implicit shift. + pub fn svd(self, compute_u: bool, compute_v: bool) -> SVD { + SVD::new(self.into_owned(), compute_u, compute_v) + } + + /// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift. + /// + /// # Arguments + /// + /// * `compute_u` − set this to `true` to enable the computation of left-singular vectors. + /// * `compute_v` − set this to `true` to enable the computation of left-singular vectors. + /// * `eps` − tolerence used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_svd(self, compute_u: bool, compute_v: bool, eps: N, max_niter: usize) -> Option> { + SVD::try_new(self.into_owned(), compute_u, compute_v, eps, max_niter) + } + + /// Computes the singular values of this matrix. + pub fn singular_values(&self) -> VectorN> { + SVD::new(self.clone_owned(), false, false).singular_values + } + + /// Computes the rank of this matrix. + /// + /// All singular values bellow `eps` are considered equal to 0. + pub fn rank(&self, eps: N) -> usize { + let svd = SVD::new(self.clone_owned(), false, false); + svd.rank(eps) + } + + /// Computes the pseudo-inverse of this matrix. + /// + /// All singular values bellow `eps` are considered equal to 0. + pub fn pseudo_inverse(self, eps: N) -> MatrixMN + where DefaultAllocator: Allocator { + + SVD::new(self.clone_owned(), true, true).pseudo_inverse(eps) + } +} diff --git a/src/linalg/symmetric_eigen.rs b/src/linalg/symmetric_eigen.rs new file mode 100644 index 00000000..1d417dd1 --- /dev/null +++ b/src/linalg/symmetric_eigen.rs @@ -0,0 +1,397 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use num_complex::Complex; +use std::ops::MulAssign; + +use alga::general::Real; +use core::{MatrixN, VectorN, DefaultAllocator, Matrix2, Vector2, SquareMatrix}; +use dimension::{Dim, DimSub, DimDiff, U1, U2}; +use storage::Storage; +use allocator::Allocator; + +use linalg::givens; +use linalg::SymmetricTridiagonal; +use geometry::UnitComplex; + + +/// Eigendecomposition of a symmetric matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator, + VectorN: serde::Serialize, + MatrixN: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator, + VectorN: serde::Deserialize<'de>, + MatrixN: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct SymmetricEigen + where DefaultAllocator: Allocator + + Allocator { + /// The eigenvectors of the decomposed matrix. + pub eigenvectors: MatrixN, + + /// The unsorted eigenvalues of the decomposed matrix. + pub eigenvalues: VectorN +} + +impl Copy for SymmetricEigen + where DefaultAllocator: Allocator + + Allocator, + MatrixN: Copy, + VectorN: Copy { } + +impl SymmetricEigen + where DefaultAllocator: Allocator + + Allocator { + /// Computes the eigendecomposition of the given symmetric matrix. + /// + /// Only the lower-triangular parts (including its diagonal) of `m` is read. + pub fn new(m: MatrixN) -> Self + where D: DimSub, + DefaultAllocator: Allocator> { + + Self::try_new(m, N::default_epsilon(), 0).unwrap() + } + + /// Computes the eigendecomposition of the given symmetric matrix with user-specified + /// convergence parameters. + /// + /// Only the lower-triangular part (including its diagonal) of `m` is read. + /// + /// # Arguments + /// + /// * `eps` − tolerance used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_new(m: MatrixN, eps: N, max_niter: usize) -> Option + where D: DimSub, + DefaultAllocator: Allocator> { + Self::do_decompose(m, true, eps, max_niter).map(|(vals, vecs)| { + SymmetricEigen { + eigenvectors: vecs.unwrap(), + eigenvalues: vals + } + }) + } + + fn do_decompose(mut m: MatrixN, eigenvectors: bool, eps: N, max_niter: usize) + -> Option<(VectorN, Option>)> + where D: DimSub, + DefaultAllocator: Allocator> { + + assert!(m.is_square(), "Unable to compute the eigendecomposition of a non-square matrix."); + let dim = m.nrows(); + + let m_amax = m.amax(); + + if !m_amax.is_zero() { + m /= m_amax; + } + + let (mut q, mut diag, mut off_diag); + + if eigenvectors { + let res = SymmetricTridiagonal::new(m).unpack(); + q = Some(res.0); + diag = res.1; + off_diag = res.2; + } + else { + let res = SymmetricTridiagonal::new(m).unpack_tridiagonal(); + q = None; + diag = res.0; + off_diag = res.1; + } + + if dim == 1 { + diag *= m_amax; + return Some((diag, q)); + } + + let mut niter = 0; + let (mut start, mut end) = Self::delimit_subproblem(&diag, &mut off_diag, dim - 1, eps); + + while end != start { + let subdim = end - start + 1; + + if subdim > 2 { + let m = end - 1; + let n = end; + + let mut v = Vector2::new( + diag[start] - wilkinson_shift(diag[m], diag[n], off_diag[m]), + off_diag[start]); + + + for i in start .. n { + let j = i + 1; + + if let Some((rot, norm)) = givens::cancel_y(&v) { + if i > start { + // Not the first iteration. + off_diag[i - 1] = norm; + } + + let mii = diag[i]; + let mjj = diag[j]; + let mij = off_diag[i]; + + let cc = rot.cos_angle() * rot.cos_angle(); + let ss = rot.sin_angle() * rot.sin_angle(); + let cs = rot.cos_angle() * rot.sin_angle(); + + let b = cs * ::convert(2.0) * mij; + + diag[i] = (cc * mii + ss * mjj) - b; + diag[j] = (ss * mii + cc * mjj) + b; + off_diag[i] = cs * (mii - mjj) + mij * (cc - ss); + + if i != n - 1 { + v.x = off_diag[i]; + v.y = -rot.sin_angle() * off_diag[i + 1]; + off_diag[i + 1] *= rot.cos_angle(); + } + + if let Some(ref mut q) = q { + rot.inverse().rotate_rows(&mut q.fixed_columns_mut::(i)); + } + } + else { + break; + } + } + + if off_diag[m].abs() <= eps * (diag[m].abs() + diag[n].abs()) { + end -= 1; + } + } + else if subdim == 2 { + let m = Matrix2::new(diag[start], off_diag[start], + off_diag[start], diag[start + 1]); + let eigvals = m.eigenvalues().unwrap(); + let basis = Vector2::new(eigvals.x - diag[start + 1], off_diag[start]); + + diag[start + 0] = eigvals[0]; + diag[start + 1] = eigvals[1]; + + if let Some(ref mut q) = q { + if let Some(basis) = basis.try_normalize(eps) { + let rot = UnitComplex::new_unchecked(Complex::new(basis.x, basis.y)); + rot.rotate_rows(&mut q.fixed_columns_mut::(start)); + } + } + + end -= 1; + } + + // Re-delimit the suproblem in case some decoupling occured. + let sub = Self::delimit_subproblem(&diag, &mut off_diag, end, eps); + + start = sub.0; + end = sub.1; + + niter += 1; + if niter == max_niter { + return None; + } + } + + diag *= m_amax; + + Some((diag, q)) + } + + fn delimit_subproblem(diag: &VectorN, + off_diag: &mut VectorN>, + end: usize, + eps: N) + -> (usize, usize) + where D: DimSub, + DefaultAllocator: Allocator> { + + let mut n = end; + + while n > 0 { + let m = n - 1; + + if off_diag[m].abs() > eps * (diag[n].abs() + diag[m].abs()) { + break; + } + + n -= 1; + } + + if n == 0 { + return (0, 0); + } + + let mut new_start = n - 1; + while new_start > 0 { + let m = new_start - 1; + + if off_diag[m].is_zero() || + off_diag[m].abs() <= eps * (diag[new_start].abs() + diag[m].abs()) { + off_diag[m] = N::zero(); + break; + } + + new_start -= 1; + } + + (new_start, n) + } + + /// Rebuild the original matrix. + /// + /// This is useful if some of the eigenvalues have been manually modified. + pub fn recompose(&self) -> MatrixN { + let mut u_t = self.eigenvectors.clone(); + for i in 0 .. self.eigenvalues.len() { + let val = self.eigenvalues[i]; + u_t.column_mut(i).mul_assign(val); + } + u_t.transpose_mut(); + &self.eigenvectors * u_t + } +} + +/// Computes the wilkinson shift, i.e., the 2x2 symmetric matrix eigenvalue to its tailing +/// component `tnn`. +/// +/// The inputs are interpreted as the 2x2 matrix: +/// tmm tmn +/// tmn tnn +pub fn wilkinson_shift(tmm: N, tnn: N, tmn: N) -> N { + let sq_tmn = tmn * tmn; + if !sq_tmn.is_zero() { + // We have the guarantee thet the denominator won't be zero. + let d = (tmm - tnn) * ::convert(0.5); + tnn - sq_tmn / (d + d.signum() * (d * d + sq_tmn).sqrt()) + } + else { + tnn + } +} + + +/* + * + * Computations of eigenvalues for symmetric matrices. + * + */ +impl, S: Storage> SquareMatrix + where DefaultAllocator: Allocator + + Allocator + + Allocator> { + + /// Computes the eigendecomposition of this symmetric matrix. + /// + /// Only the lower-triangular part (including the diagonal) of `m` is read. + pub fn symmetric_eigen(self) -> SymmetricEigen { + SymmetricEigen::new(self.into_owned()) + } + + /// Computes the eigendecomposition of the given symmetric matrix with user-specified + /// convergence parameters. + /// + /// Only the lower-triangular part (including the diagonal) of `m` is read. + /// + /// # Arguments + /// + /// * `eps` − tolerance used to determine when a value converged to 0. + /// * `max_niter` − maximum total number of iterations performed by the algorithm. If this + /// number of iteration is exceeded, `None` is returned. If `niter == 0`, then the algorithm + /// continues indefinitely until convergence. + pub fn try_symmetric_eigen(self, eps: N, max_niter: usize) -> Option> { + SymmetricEigen::try_new(self.into_owned(), eps, max_niter) + } + + /// Computes the eigenvalues of this symmetric matrix. + /// + /// Only the lower-triangular part of the matrix is read. + pub fn symmetric_eigenvalues(&self) -> VectorN { + SymmetricEigen::do_decompose(self.clone_owned(), false, N::default_epsilon(), 0).unwrap().0 + } +} + + + + + +#[cfg(test)] +mod test { + use core::Matrix2; + + fn expected_shift(m: Matrix2) -> f64 { + let vals = m.eigenvalues().unwrap(); + + if (vals.x - m.m22).abs() < (vals.y - m.m22).abs() { + vals.x + } else { + vals.y + } + } + + #[test] + fn wilkinson_shift_random() { + for _ in 0 .. 1000 { + let m = Matrix2::new_random(); + let m = m * m.transpose(); + + let expected = expected_shift(m); + let computed = super::wilkinson_shift(m.m11, m.m22, m.m12); + println!("{} {}", expected, computed); + assert!(relative_eq!(expected, computed, epsilon = 1.0e-7)); + } + } + + #[test] + fn wilkinson_shift_zero() { + let m = Matrix2::new(0.0, 0.0, + 0.0, 0.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } + + + #[test] + fn wilkinson_shift_zero_diagonal() { + let m = Matrix2::new(0.0, 42.0, + 42.0, 0.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } + + #[test] + fn wilkinson_shift_zero_off_diagonal() { + let m = Matrix2::new(42.0, 0.0, + 0.0, 64.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } + + #[test] + fn wilkinson_shift_zero_trace() { + let m = Matrix2::new(42.0, 20.0, + 20.0, -42.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } + + #[test] + fn wilkinson_shift_zero_diag_diff_and_zero_off_diagonal() { + let m = Matrix2::new(42.0, 0.0, + 0.0, 42.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } + + #[test] + fn wilkinson_shift_zero_det() { + let m = Matrix2::new(2.0, 4.0, + 4.0, 8.0); + assert!(relative_eq!(expected_shift(m), super::wilkinson_shift(m.m11, m.m22, m.m12))); + } +} diff --git a/src/linalg/symmetric_tridiagonal.rs b/src/linalg/symmetric_tridiagonal.rs new file mode 100644 index 00000000..63dd35d8 --- /dev/null +++ b/src/linalg/symmetric_tridiagonal.rs @@ -0,0 +1,147 @@ +#[cfg(feature = "serde-serialize")] +use serde; + +use alga::general::Real; +use core::{SquareMatrix, MatrixN, MatrixMN, VectorN, DefaultAllocator}; +use dimension::{DimSub, DimDiff, U1}; +use storage::Storage; +use allocator::Allocator; + +use linalg::householder; + + +/// Tridiagonalization of a symmetric matrix. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(serialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Serialize, + VectorN>: serde::Serialize")))] +#[cfg_attr(feature = "serde-serialize", + serde(bound(deserialize = + "DefaultAllocator: Allocator + + Allocator>, + MatrixN: serde::Deserialize<'de>, + VectorN>: serde::Deserialize<'de>")))] +#[derive(Clone, Debug)] +pub struct SymmetricTridiagonal> + where DefaultAllocator: Allocator + + Allocator> { + tri: MatrixN, + off_diagonal: VectorN> +} + +impl> Copy for SymmetricTridiagonal + where DefaultAllocator: Allocator + + Allocator>, + MatrixN: Copy, + VectorN>: Copy { } + +impl> SymmetricTridiagonal + where DefaultAllocator: Allocator + + Allocator> { + + /// Computes the tridiagonalization of the symmetric matrix `m`. + /// + /// Only the lower-triangular part (including the diagonal) of `m` is read. + pub fn new(mut m: MatrixN) -> Self { + let dim = m.data.shape().0; + + assert!(m.is_square(), "Unable to compute the symmetric tridiagonal decomposition of a non-square matrix."); + assert!(dim.value() != 0, "Unable to compute the symmetric tridiagonal decomposition of an empty matrix."); + + let mut off_diagonal = unsafe { MatrixMN::new_uninitialized_generic(dim.sub(U1), U1) }; + let mut p = unsafe { MatrixMN::new_uninitialized_generic(dim.sub(U1), U1) }; + + for i in 0 .. dim.value() - 1 { + let mut m = m.rows_range_mut(i + 1 ..); + let (mut axis, mut m) = m.columns_range_pair_mut(i, i + 1 ..); + + let (norm, not_zero) = householder::reflection_axis_mut(&mut axis); + off_diagonal[i] = norm; + + if not_zero { + let mut p = p.rows_range_mut(i ..); + + p.gemv_symm(::convert(2.0), &m, &axis, N::zero()); + let dot = axis.dot(&p); + p.axpy(-dot, &axis, N::one()); + m.ger_symm(-N::one(), &p, &axis, N::one()); + m.ger_symm(-N::one(), &axis, &p, N::one()); + } + } + + SymmetricTridiagonal { + tri: m, + off_diagonal: off_diagonal + } + } + + #[doc(hidden)] + // For debugging. + pub fn internal_tri(&self) -> &MatrixN { + &self.tri + } + + /// Retrieve the orthogonal transformation, diagonal, and off diagonal elements of this + /// decomposition. + pub fn unpack(self) -> (MatrixN, VectorN, VectorN>) + where DefaultAllocator: Allocator { + let diag = self.diagonal(); + let q = self.q(); + + (q, diag, self.off_diagonal) + } + + /// Retrieve the diagonal, and off diagonal elements of this decomposition. + pub fn unpack_tridiagonal(self) -> (VectorN, VectorN>) + where DefaultAllocator: Allocator { + let diag = self.diagonal(); + + (diag, self.off_diagonal) + } + + /// The diagonal components of this decomposition. + pub fn diagonal(&self) -> VectorN + where DefaultAllocator: Allocator { + self.tri.diagonal() + } + + /// The off-diagonal components of this decomposition. + pub fn off_diagonal(&self) -> &VectorN> + where DefaultAllocator: Allocator { + &self.off_diagonal + } + + /// Computes the orthogonal matrix `Q` of this decomposition. + pub fn q(&self) -> MatrixN { + householder::assemble_q(&self.tri) + } + + /// Recomputes the original symmetric matrix. + pub fn recompose(mut self) -> MatrixN { + let q = self.q(); + self.tri.fill_lower_triangle(N::zero(), 2); + self.tri.fill_upper_triangle(N::zero(), 2); + + for i in 0 .. self.off_diagonal.len() { + self.tri[(i + 1, i)] = self.off_diagonal[i]; + self.tri[(i, i + 1)] = self.off_diagonal[i]; + } + + &q * self.tri * q.transpose() + } +} + +impl, S: Storage> SquareMatrix + where DefaultAllocator: Allocator + + Allocator> { + + /// Computes the tridiagonalization of this symmetric matrix. + /// + /// Only the lower-triangular part (including the diagonal) of `m` is read. + pub fn symmetric_tridiagonalize(self) -> SymmetricTridiagonal { + SymmetricTridiagonal::new(self.into_owned()) + } +} diff --git a/src/traits/axpy.rs b/src/traits/axpy.rs deleted file mode 100644 index 20fa7f3a..00000000 --- a/src/traits/axpy.rs +++ /dev/null @@ -1,38 +0,0 @@ -use alga::general::Field; - -use core::{Scalar, Matrix}; -use core::dimension::{Dim, DimName, U1}; -use core::storage::StorageMut; - -use geometry::PointBase; - -/// Operation that combines scalar multiplication and vector addition. -pub trait Axpy { - /// Computes `self = a * x + self`. - fn axpy(&mut self, a: A, x: &Self); -} - -impl Axpy for Matrix -where N: Scalar + Field, - S: StorageMut { - #[inline] - fn axpy(&mut self, a: N, x: &Self) { - for (me, x) in self.iter_mut().zip(x.iter()) { - *me += *x * a; - } - } -} - - -impl Axpy for PointBase -where N: Scalar + Field, - S: StorageMut { - #[inline] - fn axpy(&mut self, a: N, x: &Self) { - for (me, x) in self.coords.iter_mut().zip(x.coords.iter()) { - *me += *x * a; - } - } -} - -// FIXME: implemente Axpy with matrices and transforms. diff --git a/src/traits/mod.rs b/src/traits/mod.rs deleted file mode 100644 index af1918e6..00000000 --- a/src/traits/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub use self::axpy::Axpy; - -mod axpy; diff --git a/tests/core/blas.rs b/tests/core/blas.rs new file mode 100644 index 00000000..57fab2c6 --- /dev/null +++ b/tests/core/blas.rs @@ -0,0 +1,56 @@ +use std::cmp; + +use na::{DVector, DMatrix}; + + +#[cfg(feature = "arbitrary")] +quickcheck! { + /* + * + * Symmetric operators. + * + */ + fn gemv_symm(n: usize, alpha: f64, beta: f64) -> bool { + let n = cmp::max(1, cmp::min(n, 50)); + let a = DMatrix::::new_random(n, n); + let a = &a * a.transpose(); + + let x = DVector::new_random(n); + let mut y1 = DVector::new_random(n); + let mut y2 = y1.clone(); + + y1.gemv(alpha, &a, &x, beta); + y2.gemv_symm(alpha, &a.lower_triangle(), &x, beta); + + if !relative_eq!(y1, y2, epsilon = 1.0e-10) { + return false; + } + + y1.gemv(alpha, &a, &x, 0.0); + y2.gemv_symm(alpha, &a.lower_triangle(), &x, 0.0); + + relative_eq!(y1, y2, epsilon = 1.0e-10) + } + + fn ger_symm(n: usize, alpha: f64, beta: f64) -> bool { + let n = cmp::max(1, cmp::min(n, 50)); + let a = DMatrix::::new_random(n, n); + let mut a1 = &a * a.transpose(); + let mut a2 = a1.lower_triangle(); + + let x = DVector::new_random(n); + let y = DVector::new_random(n); + + a1.ger(alpha, &x, &y, beta); + a2.ger_symm(alpha, &x, &y, beta); + + if !relative_eq!(a1.lower_triangle(), a2) { + return false; + } + + a1.ger(alpha, &x, &y, 0.0); + a2.ger_symm(alpha, &x, &y, 0.0); + + relative_eq!(a1.lower_triangle(), a2) + } +} diff --git a/tests/conversion.rs b/tests/core/conversion.rs similarity index 97% rename from tests/conversion.rs rename to tests/core/conversion.rs index 0b93760b..ad5590da 100644 --- a/tests/conversion.rs +++ b/tests/core/conversion.rs @@ -1,14 +1,6 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use alga::linear::Transformation; use na::{ + self, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, RowVector1, RowVector2, RowVector3, RowVector4, RowVector5, RowVector6, Matrix2, Matrix3, Matrix4, Matrix5, Matrix6, @@ -64,7 +56,7 @@ quickcheck!{ r == na::try_convert(prj).unwrap() && r == na::try_convert(tr).unwrap() && - // NOTE: we need relative_eq because IsometryBase and SimilarityBase use quaternions. + // NOTE: we need relative_eq because Isometry and Similarity use quaternions. relative_eq!(r * v, uq * v, epsilon = 1.0e-7) && relative_eq!(r * v, iso * v, epsilon = 1.0e-7) && relative_eq!(r * v, sim * v, epsilon = 1.0e-7) && @@ -152,7 +144,7 @@ quickcheck!{ relative_eq!(sim * p, tr * p, epsilon = 1.0e-7) } - // XXX test TransformBase + // XXX test Transform } macro_rules! array_vector_conversion( diff --git a/tests/core/edition.rs b/tests/core/edition.rs new file mode 100644 index 00000000..f979be6c --- /dev/null +++ b/tests/core/edition.rs @@ -0,0 +1,471 @@ +use na::{Matrix, + DMatrix, + Matrix3, Matrix4, Matrix5, + Matrix4x3, Matrix3x4, Matrix5x3, Matrix3x5, Matrix4x5, Matrix5x4}; +use na::{Dynamic, U2, U3, U5}; + +#[test] +fn upper_lower_triangular() { + let m = Matrix4::new( + 11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0, + 41.0, 42.0, 43.0, 44.0); + + let um = Matrix4::new( + 11.0, 12.0, 13.0, 14.0, + 0.0, 22.0, 23.0, 24.0, + 0.0, 0.0, 33.0, 34.0, + 0.0, 0.0, 0.0, 44.0); + + let lm = Matrix4::new( + 11.0, 0.0, 0.0, 0.0, + 21.0, 22.0, 0.0, 0.0, + 31.0, 32.0, 33.0, 0.0, + 41.0, 42.0, 43.0, 44.0); + + let computed_um = m.upper_triangle(); + let computed_lm = m.lower_triangle(); + + assert_eq!(um, computed_um); + assert_eq!(lm, computed_lm); + + let symm_um = Matrix4::new( + 11.0, 12.0, 13.0, 14.0, + 12.0, 22.0, 23.0, 24.0, + 13.0, 23.0, 33.0, 34.0, + 14.0, 24.0, 34.0, 44.0); + + let symm_lm = Matrix4::new( + 11.0, 21.0, 31.0, 41.0, + 21.0, 22.0, 32.0, 42.0, + 31.0, 32.0, 33.0, 43.0, + 41.0, 42.0, 43.0, 44.0); + + let mut computed_symm_um = m.clone(); + let mut computed_symm_lm = m.clone(); + + computed_symm_um.fill_lower_triangle_with_upper_triangle(); + computed_symm_lm.fill_upper_triangle_with_lower_triangle(); + assert_eq!(symm_um, computed_symm_um); + assert_eq!(symm_lm, computed_symm_lm); + + + let m = Matrix5x3::new( + 11.0, 12.0, 13.0, + 21.0, 22.0, 23.0, + 31.0, 32.0, 33.0, + 41.0, 42.0, 43.0, + 51.0, 52.0, 53.0); + + let um = Matrix5x3::new( + 11.0, 12.0, 13.0, + 0.0, 22.0, 23.0, + 0.0, 0.0, 33.0, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + + let lm = Matrix5x3::new( + 11.0, 0.0, 0.0, + 21.0, 22.0, 0.0, + 31.0, 32.0, 33.0, + 41.0, 42.0, 43.0, + 51.0, 52.0, 53.0); + + let computed_um = m.upper_triangle(); + let computed_lm = m.lower_triangle(); + + assert_eq!(um, computed_um); + assert_eq!(lm, computed_lm); + + + let m = Matrix3x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 21.0, 22.0, 23.0, 24.0, 25.0, + 31.0, 32.0, 33.0, 34.0, 35.0); + + let um = Matrix3x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 0.0, 22.0, 23.0, 24.0, 25.0, + 0.0, 0.0, 33.0, 34.0, 35.0); + + let lm = Matrix3x5::new( + 11.0, 0.0, 0.0, 0.0, 0.0, + 21.0, 22.0, 0.0, 0.0, 0.0, + 31.0, 32.0, 33.0, 0.0, 0.0); + + let computed_um = m.upper_triangle(); + let computed_lm = m.lower_triangle(); + + assert_eq!(um, computed_um); + assert_eq!(lm, computed_lm); + + let mut m = Matrix4x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 21.0, 22.0, 23.0, 24.0, 25.0, + 31.0, 32.0, 33.0, 34.0, 35.0, + 41.0, 42.0, 43.0, 44.0, 45.0); + + let expected_m = Matrix4x5::new( + 11.0, 12.0, 0.0, 0.0, 0.0, + 21.0, 22.0, 23.0, 0.0, 0.0, + 31.0, 32.0, 33.0, 34.0, 0.0, + 41.0, 42.0, 43.0, 44.0, 45.0); + + m.fill_upper_triangle(0.0, 2); + + assert_eq!(m, expected_m); + + let mut m = Matrix4x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 21.0, 22.0, 23.0, 24.0, 25.0, + 31.0, 32.0, 33.0, 34.0, 35.0, + 41.0, 42.0, 43.0, 44.0, 45.0); + + let expected_m = Matrix4x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 21.0, 22.0, 23.0, 24.0, 25.0, + 0.0, 32.0, 33.0, 34.0, 35.0, + 0.0, 0.0, 43.0, 44.0, 45.0); + + m.fill_lower_triangle(0.0, 2); + + assert_eq!(m, expected_m); + + let mut m = Matrix5x4::new( + 11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0, + 41.0, 42.0, 43.0, 44.0, + 51.0, 52.0, 53.0, 54.0); + + let expected_m = Matrix5x4::new( + 11.0, 12.0, 0.0, 0.0, + 21.0, 22.0, 23.0, 0.0, + 31.0, 32.0, 33.0, 34.0, + 41.0, 42.0, 43.0, 44.0, + 51.0, 52.0, 53.0, 54.0); + + m.fill_upper_triangle(0.0, 2); + + assert_eq!(m, expected_m); + + let mut m = Matrix5x4::new( + 11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0, + 41.0, 42.0, 43.0, 44.0, + 51.0, 52.0, 53.0, 54.0); + + let expected_m = Matrix5x4::new( + 11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 0.0, 32.0, 33.0, 34.0, + 0.0, 0.0, 43.0, 44.0, + 0.0, 0.0, 0.0, 54.0); + + m.fill_lower_triangle(0.0, 2); + + assert_eq!(m, expected_m); +} + +#[test] +fn swap_rows() { + let mut m = Matrix5x3::new( + 11.0, 12.0, 13.0, + 21.0, 22.0, 23.0, + 31.0, 32.0, 33.0, + 41.0, 42.0, 43.0, + 51.0, 52.0, 53.0); + + let expected = Matrix5x3::new( + 11.0, 12.0, 13.0, + 41.0, 42.0, 43.0, + 31.0, 32.0, 33.0, + 21.0, 22.0, 23.0, + 51.0, 52.0, 53.0); + + m.swap_rows(1, 3); + + assert_eq!(m, expected); +} + +#[test] +fn swap_columns() { + let mut m = Matrix3x5::new( + 11.0, 12.0, 13.0, 14.0, 15.0, + 21.0, 22.0, 23.0, 24.0, 25.0, + 31.0, 32.0, 33.0, 34.0, 35.0); + + let expected = Matrix3x5::new( + 11.0, 14.0, 13.0, 12.0, 15.0, + 21.0, 24.0, 23.0, 22.0, 25.0, + 31.0, 34.0, 33.0, 32.0, 35.0); + + m.swap_columns(1, 3); + + assert_eq!(m, expected); +} + +#[test] +fn remove_columns() { + let m = Matrix3x5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35); + + let expected1 = Matrix3x4::new( + 12, 13, 14, 15, + 22, 23, 24, 25, + 32, 33, 34, 35); + + let expected2 = Matrix3x4::new( + 11, 12, 13, 14, + 21, 22, 23, 24, + 31, 32, 33, 34); + + let expected3 = Matrix3x4::new( + 11, 12, 14, 15, + 21, 22, 24, 25, + 31, 32, 34, 35); + + assert_eq!(m.remove_column(0), expected1); + assert_eq!(m.remove_column(4), expected2); + assert_eq!(m.remove_column(2), expected3); + + let expected1 = Matrix3::new( + 13, 14, 15, + 23, 24, 25, + 33, 34, 35); + + let expected2 = Matrix3::new( + 11, 12, 13, + 21, 22, 23, + 31, 32, 33); + + let expected3 = Matrix3::new( + 11, 12, 15, + 21, 22, 25, + 31, 32, 35); + + assert_eq!(m.remove_fixed_columns::(0), expected1); + assert_eq!(m.remove_fixed_columns::(3), expected2); + assert_eq!(m.remove_fixed_columns::(2), expected3); + + // The following is just to verify that the return type dimensions is correctly inferred. + let computed: Matrix<_, U3, Dynamic, _> = m.remove_columns(3, 2); + assert!(computed.eq(&expected2)); +} + + +#[test] +fn remove_rows() { + let m = Matrix5x3::new( + 11, 12, 13, + 21, 22, 23, + 31, 32, 33, + 41, 42, 43, + 51, 52, 53); + + let expected1 = Matrix4x3::new( + 21, 22, 23, + 31, 32, 33, + 41, 42, 43, + 51, 52, 53); + + let expected2 = Matrix4x3::new( + 11, 12, 13, + 21, 22, 23, + 31, 32, 33, + 41, 42, 43); + + let expected3 = Matrix4x3::new( + 11, 12, 13, + 21, 22, 23, + 41, 42, 43, + 51, 52, 53); + + assert_eq!(m.remove_row(0), expected1); + assert_eq!(m.remove_row(4), expected2); + assert_eq!(m.remove_row(2), expected3); + + let expected1 = Matrix3::new( + 31, 32, 33, + 41, 42, 43, + 51, 52, 53); + + let expected2 = Matrix3::new( + 11, 12, 13, + 21, 22, 23, + 31, 32, 33); + + let expected3 = Matrix3::new( + 11, 12, 13, + 21, 22, 23, + 51, 52, 53); + + assert_eq!(m.remove_fixed_rows::(0), expected1); + assert_eq!(m.remove_fixed_rows::(3), expected2); + assert_eq!(m.remove_fixed_rows::(2), expected3); + + // The following is just to verify that the return type dimensions is correctly inferred. + let computed: Matrix<_, Dynamic, U3, _> = m.remove_rows(3, 2); + assert!(computed.eq(&expected2)); +} + + +#[test] +fn insert_columns() { + let m = Matrix5x3::new( + 11, 12, 13, + 21, 22, 23, + 31, 32, 33, + 41, 42, 43, + 51, 52, 53); + + let expected1 = Matrix5x4::new( + 0, 11, 12, 13, + 0, 21, 22, 23, + 0, 31, 32, 33, + 0, 41, 42, 43, + 0, 51, 52, 53); + + let expected2 = Matrix5x4::new( + 11, 12, 13, 0, + 21, 22, 23, 0, + 31, 32, 33, 0, + 41, 42, 43, 0, + 51, 52, 53, 0); + + let expected3 = Matrix5x4::new( + 11, 12, 0, 13, + 21, 22, 0, 23, + 31, 32, 0, 33, + 41, 42, 0, 43, + 51, 52, 0, 53); + + assert_eq!(m.insert_column(0, 0), expected1); + assert_eq!(m.insert_column(3, 0), expected2); + assert_eq!(m.insert_column(2, 0), expected3); + + let expected1 = Matrix5::new( + 0, 0, 11, 12, 13, + 0, 0, 21, 22, 23, + 0, 0, 31, 32, 33, + 0, 0, 41, 42, 43, + 0, 0, 51, 52, 53); + + let expected2 = Matrix5::new( + 11, 12, 13, 0, 0, + 21, 22, 23, 0, 0, + 31, 32, 33, 0, 0, + 41, 42, 43, 0, 0, + 51, 52, 53, 0, 0); + + let expected3 = Matrix5::new( + 11, 12, 0, 0, 13, + 21, 22, 0, 0, 23, + 31, 32, 0, 0, 33, + 41, 42, 0, 0, 43, + 51, 52, 0, 0, 53); + + assert_eq!(m.insert_fixed_columns::(0, 0), expected1); + assert_eq!(m.insert_fixed_columns::(3, 0), expected2); + assert_eq!(m.insert_fixed_columns::(2, 0), expected3); + + // The following is just to verify that the return type dimensions is correctly inferred. + let computed: Matrix<_, U5, Dynamic, _> = m.insert_columns(3, 2, 0); + assert!(computed.eq(&expected2)); +} + + +#[test] +fn insert_rows() { + let m = Matrix3x5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35); + + let expected1 = Matrix4x5::new( + 0, 0, 0, 0, 0, + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35); + + let expected2 = Matrix4x5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35, + 0, 0, 0, 0, 0); + + let expected3 = Matrix4x5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 0, 0, 0, 0, 0, + 31, 32, 33, 34, 35); + + assert_eq!(m.insert_row(0, 0), expected1); + assert_eq!(m.insert_row(3, 0), expected2); + assert_eq!(m.insert_row(2, 0), expected3); + + let expected1 = Matrix5::new( + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35); + + let expected2 = Matrix5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0); + + let expected3 = Matrix5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 31, 32, 33, 34, 35); + + assert_eq!(m.insert_fixed_rows::(0, 0), expected1); + assert_eq!(m.insert_fixed_rows::(3, 0), expected2); + assert_eq!(m.insert_fixed_rows::(2, 0), expected3); + + // The following is just to verify that the return type dimensions is correctly inferred. + let computed: Matrix<_, Dynamic, U5, _> = m.insert_rows(3, 2, 0); + assert!(computed.eq(&expected2)); +} + +#[test] +fn resize() { + let m = Matrix3x5::new( + 11, 12, 13, 14, 15, + 21, 22, 23, 24, 25, + 31, 32, 33, 34, 35); + + let add_add = DMatrix::from_row_slice(5, 6, &[ + 11, 12, 13, 14, 15, 42, + 21, 22, 23, 24, 25, 42, + 31, 32, 33, 34, 35, 42, + 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42]); + + let del_del = DMatrix::from_row_slice(1, 2, &[11, 12]); + + let add_del = DMatrix::from_row_slice(5, 2, &[ + 11, 12, + 21, 22, + 31, 32, + 42, 42, + 42, 42]); + + let del_add = DMatrix::from_row_slice(1, 8, &[ + 11, 12, 13, 14, 15, 42, 42, 42]); + + assert_eq!(del_del, m.resize(1, 2, 42)); + assert_eq!(add_add, m.resize(5, 6, 42)); + assert_eq!(add_del, m.resize(5, 2, 42)); + assert_eq!(del_add, m.resize(1, 8, 42)); +} diff --git a/tests/matrix.rs b/tests/core/matrix.rs similarity index 85% rename from tests/matrix.rs rename to tests/core/matrix.rs index b41b1e34..e4557a30 100644 --- a/tests/matrix.rs +++ b/tests/core/matrix.rs @@ -1,23 +1,69 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use num::{Zero, One}; +use num::Float; use std::fmt::Display; use alga::linear::FiniteDimInnerSpace; -use na::{U8, U15, +use na::{self, DVector, DMatrix, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6, RowVector4, RowVector5, Matrix1, Matrix2, Matrix3, Matrix4, Matrix5, Matrix6, - MatrixNM, Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x3, Matrix2x4, Matrix4x5, Matrix4x6}; + Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x3, Matrix2x4, Matrix4x5, Matrix4x6, + MatrixMN}; +use na::dimension::{U8, U15}; + +#[test] +fn iter() { + let a = Matrix2x3::new(1.0, 2.0, 3.0, + 4.0, 5.0, 6.0); + + let mut it = a.iter(); + assert_eq!(*it.next().unwrap(), 1.0); + assert_eq!(*it.next().unwrap(), 4.0); + assert_eq!(*it.next().unwrap(), 2.0); + assert_eq!(*it.next().unwrap(), 5.0); + assert_eq!(*it.next().unwrap(), 3.0); + assert_eq!(*it.next().unwrap(), 6.0); + assert!(it.next().is_none()); + + let row = a.row(0); + let mut it = row.iter(); + assert_eq!(*it.next().unwrap(), 1.0); + assert_eq!(*it.next().unwrap(), 2.0); + assert_eq!(*it.next().unwrap(), 3.0); + assert!(it.next().is_none()); + + let row = a.row(1); + let mut it = row.iter(); + assert_eq!(*it.next().unwrap(), 4.0); + assert_eq!(*it.next().unwrap(), 5.0); + assert_eq!(*it.next().unwrap(), 6.0); + assert!(it.next().is_none()); + + let m22 = row.column(1); + let mut it = m22.iter(); + assert_eq!(*it.next().unwrap(), 5.0); + assert!(it.next().is_none()); + + let col = a.column(0); + let mut it = col.iter(); + assert_eq!(*it.next().unwrap(), 1.0); + assert_eq!(*it.next().unwrap(), 4.0); + assert!(it.next().is_none()); + + let col = a.column(1); + let mut it = col.iter(); + assert_eq!(*it.next().unwrap(), 2.0); + assert_eq!(*it.next().unwrap(), 5.0); + assert!(it.next().is_none()); + + let col = a.column(2); + let mut it = col.iter(); + assert_eq!(*it.next().unwrap(), 3.0); + assert_eq!(*it.next().unwrap(), 6.0); + assert!(it.next().is_none()); +} #[test] @@ -219,8 +265,8 @@ fn from_not_enough_columns() { #[should_panic] fn from_rows_with_different_dimensions() { let columns = &[ - DVector::from_row_slice(3, &[11, 21, 31]), - DVector::from_row_slice(3, &[12, 22, 32, 33]) + DVector::from_row_slice(3, &[ 11, 21, 31 ]), + DVector::from_row_slice(3, &[ 12, 22, 32, 33 ]) ]; let _ = DMatrix::from_columns(columns); @@ -231,8 +277,8 @@ fn to_homogeneous() { let a = Vector3::new(1.0, 2.0, 3.0); let expected_a = Vector4::new(1.0, 2.0, 3.0, 0.0); - let b = DVector::from_row_slice(3, &[1.0, 2.0, 3.0]); - let expected_b = DVector::from_row_slice(4, &[1.0, 2.0, 3.0, 0.0]); + let b = DVector::from_row_slice(3, &[ 1.0, 2.0, 3.0 ]); + let expected_b = DVector::from_row_slice(4, &[ 1.0, 2.0, 3.0, 0.0 ]); assert_eq!(a.to_homogeneous(), expected_a); assert_eq!(b.to_homogeneous(), expected_b); @@ -350,6 +396,66 @@ fn simple_scalar_conversion() { assert_eq!(expected, a_u32); } +#[test] +fn apply() { + let mut a = Matrix4::new( + 1.1, 2.2, 3.3, 4.4, + 5.5, 6.6, 7.7, 8.8, + 9.9, 8.8, 7.7, 6.6, + 5.5, 4.4, 3.3, 2.2); + + let expected = Matrix4::new( + 1.0, 2.0, 3.0, 4.0, + 6.0, 7.0, 8.0, 9.0, + 10.0, 9.0, 8.0, 7.0, + 6.0, 4.0, 3.0, 2.0); + + a.apply(|e| e.round()); + + assert_eq!(a, expected); +} + +#[test] +fn map() { + let a = Matrix4::new( + 1.1f64, 2.2, 3.3, 4.4, + 5.5, 6.6, 7.7, 8.8, + 9.9, 8.8, 7.7, 6.6, + 5.5, 4.4, 3.3, 2.2); + + let expected = Matrix4::new( + 1, 2, 3, 4, + 6, 7, 8, 9, + 10, 9, 8, 7, + 6, 4, 3, 2); + + let computed = a.map(|e| e.round() as i64); + + assert_eq!(computed, expected); +} + +#[test] +fn zip_map() { + let a = Matrix3::new( + 11i32, 12, 13, + 21, 22, 23, + 31, 32, 33); + + let b = Matrix3::new( + 11u32, 12, 13, + 21, 22, 23, + 31, 32, 33); + + let expected = Matrix3::new( + 22.0f32, 24.0, 26.0, + 42.0, 44.0, 46.0, + 62.0, 64.0, 66.0); + + let computed = a.zip_map(&b, |ea, eb| ea as f32 + eb as f32); + + assert_eq!(computed, expected); +} + #[test] #[should_panic] fn trace_panic() { @@ -487,7 +593,7 @@ fn kronecker() { 310, 320, 330, 340, 350, 410, 420, 430, 440, 450); - let expected = MatrixNM::<_, U8, U15>::from_row_slice(&[ + let expected = MatrixMN::<_, U8, U15>::from_row_slice(&[ 1210, 1320, 1430, 1540, 1650, 1320, 1440, 1560, 1680, 1800, 1430, 1560, 1690, 1820, 1950, 2310, 2420, 2530, 2640, 2750, 2520, 2640, 2760, 2880, 3000, 2730, 2860, 2990, 3120, 3250, 3410, 3520, 3630, 3740, 3850, 3720, 3840, 3960, 4080, 4200, 4030, 4160, 4290, 4420, 4550, @@ -549,7 +655,7 @@ fn set_row_column() { } #[cfg(feature = "arbitrary")] -quickcheck!{ +quickcheck! { /* * * Transposition. @@ -590,7 +696,7 @@ quickcheck!{ } fn tr_mul_is_transpose_then_mul(m: Matrix4x6, v: Vector4) -> bool { - m.transpose() * v == m.tr_mul(&v) + relative_eq!(m.transpose() * v, m.tr_mul(&v), epsilon = 1.0e-7) } /* @@ -633,6 +739,17 @@ quickcheck!{ } } + fn self_mul_inv_is_id_dim4(m: Matrix4) -> bool { + if let Some(im) = m.try_inverse() { + let id = Matrix4::one(); + relative_eq!(im * m, id, epsilon = 1.0e-7) && + relative_eq!(m * im, id, epsilon = 1.0e-7) + } + else { + true + } + } + fn self_mul_inv_is_id_dim6(m: Matrix6) -> bool { if let Some(im) = m.try_inverse() { let id = Matrix6::one(); @@ -660,7 +777,7 @@ quickcheck!{ fn normalized_vec_norm_is_one_dyn(v: DVector) -> bool { if let Some(nv) = v.try_normalize(1.0e-10) { - relative_eq!(nv.norm(), 1.0) + relative_eq!(nv.norm(), 1.0, epsilon = 1.0e-7) } else { true diff --git a/tests/matrix_slice.rs b/tests/core/matrix_slice.rs similarity index 65% rename from tests/matrix_slice.rs rename to tests/core/matrix_slice.rs index 6587fecb..70e88cf9 100644 --- a/tests/matrix_slice.rs +++ b/tests/core/matrix_slice.rs @@ -1,8 +1,7 @@ -extern crate num_traits as num; -extern crate nalgebra as na; - use na::{U2, U3, U4}; use na::{DMatrix, + RowVector4, + Vector3, Matrix2, Matrix3, Matrix3x4, Matrix4x2, Matrix2x4, Matrix6x2, Matrix2x6}; @@ -14,7 +13,7 @@ fn nested_fixed_slices() { let s1 = a.fixed_slice::(0, 1); // Simple slice. let s2 = s1.fixed_slice::(1, 1); // Slice of slice. - let s3 = s1.fixed_slice_with_steps::((0, 0), (2, 2)); // Slice of slice with steps. + let s3 = s1.fixed_slice_with_steps::((0, 0), (1, 1)); // Slice of slice with steps. let expected_owned_s1 = Matrix3::new(12.0, 13.0, 14.0, 22.0, 23.0, 24.0, @@ -39,7 +38,7 @@ fn nested_slices() { let s1 = a.slice((0, 1), (3, 3)); let s2 = s1.slice((1, 1), (2, 2)); - let s3 = s1.slice_with_steps((0, 0), (2, 2), (2, 2)); + let s3 = s1.slice_with_steps((0, 0), (2, 2), (1, 1)); let expected_owned_s1 = DMatrix::from_row_slice(3, 3, &[ 12.0, 13.0, 14.0, 22.0, 23.0, 24.0, @@ -64,7 +63,7 @@ fn slice_mut() { { // We modify `a` through the mutable slice. - let mut s1 = a.slice_with_steps_mut((0, 1), (2, 2), (2, 2)); + let mut s1 = a.slice_with_steps_mut((0, 1), (2, 2), (1, 1)); s1.fill(0.0); } @@ -84,7 +83,7 @@ fn nested_row_slices() { 51.0, 52.0, 61.0, 62.0); let s1 = a.fixed_rows::(1); - let s2 = s1.fixed_rows_with_step::(1, 2); + let s2 = s1.fixed_rows_with_step::(1, 1); let expected_owned_s1 = Matrix4x2::new(21.0, 22.0, 31.0, 32.0, @@ -108,7 +107,7 @@ fn row_slice_mut() { 61.0, 62.0); { // We modify `a` through the mutable slice. - let mut s1 = a.rows_with_step_mut(1, 3, 2); + let mut s1 = a.rows_with_step_mut(1, 3, 1); s1.fill(0.0); } @@ -127,7 +126,7 @@ fn nested_col_slices() { let a = Matrix2x6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0); let s1 = a.fixed_columns::(1); - let s2 = s1.fixed_columns_with_step::(1, 2); + let s2 = s1.fixed_columns_with_step::(1, 1); let expected_owned_s1 = Matrix2x4::new(12.0, 13.0, 14.0, 15.0, 22.0, 23.0, 24.0, 25.0); @@ -146,7 +145,7 @@ fn col_slice_mut() { { // We modify `a` through the mutable slice. - let mut s1 = a.columns_with_step_mut(1, 3, 2); + let mut s1 = a.columns_with_step_mut(1, 3, 1); s1.fill(0.0); } @@ -155,3 +154,94 @@ fn col_slice_mut() { assert_eq!(expected_a, a.clone_owned()); } + +#[test] +fn rows_range_pair() { + let a = Matrix3x4::new(11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + + let (l, r) = a.rows_range_pair(.. 3, 3 ..); + assert!(r.len() == 0 && l.eq(&a)); + + let (l, r) = a.rows_range_pair(0, 1 ..); + + let expected_l = RowVector4::new(11.0, 12.0, 13.0, 14.0); + let expected_r = Matrix2x4::new(21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + assert!(l.eq(&expected_l) && r.eq(&expected_r)); +} + +#[test] +fn columns_range_pair() { + let a = Matrix3x4::new(11.0, 12.0, 13.0, 14.0, + 21.0, 22.0, 23.0, 24.0, + 31.0, 32.0, 33.0, 34.0); + + let (l, r) = a.columns_range_pair(.. 4, 4 ..); + assert!(r.len() == 0 && l.eq(&a)); + + let (l, r) = a.columns_range_pair(0, 1 ..); + + let expected_l = Vector3::new(11.0, 21.0, 31.0); + let expected_r = Matrix3::new(12.0, 13.0, 14.0, + 22.0, 23.0, 24.0, + 32.0, 33.0, 34.0); + assert!(l.eq(&expected_l) && r.eq(&expected_r)); +} + +#[test] +#[should_panic] +fn row_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.row(3); +} + +#[test] +#[should_panic] +fn rows_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.rows(1, 3); +} + +#[test] +#[should_panic] +fn rows_with_step_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.rows_with_step(1, 2, 1); +} + +#[test] +#[should_panic] +fn column_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.column(4); +} + +#[test] +#[should_panic] +fn columns_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.columns(2, 3); +} + +#[test] +#[should_panic] +fn columns_with_step_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.columns_with_step(2, 2, 1); +} + +#[test] +#[should_panic] +fn slice_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.slice((1, 2), (3, 1)); +} + +#[test] +#[should_panic] +fn slice_with_steps_out_of_bounds() { + let a = Matrix3x4::::zeros(); + a.slice_with_steps((1, 2), (2, 2), (0, 1)); +} diff --git a/tests/core/mod.rs b/tests/core/mod.rs new file mode 100644 index 00000000..aa73d361 --- /dev/null +++ b/tests/core/mod.rs @@ -0,0 +1,6 @@ +mod conversion; +mod edition; +mod matrix; +mod matrix_slice; +mod blas; +mod serde; diff --git a/tests/serde.rs b/tests/core/serde.rs similarity index 95% rename from tests/serde.rs rename to tests/core/serde.rs index fadcc694..b0411eb8 100644 --- a/tests/serde.rs +++ b/tests/core/serde.rs @@ -1,8 +1,5 @@ -extern crate rand; -extern crate nalgebra as na; - -extern crate serde_json; - +use serde_json; +use rand; use na::{ DMatrix, Matrix3x4, diff --git a/tests/isometry.rs b/tests/geometry/isometry.rs similarity index 97% rename from tests/isometry.rs rename to tests/geometry/isometry.rs index 2a5990b3..2ad79c60 100644 --- a/tests/isometry.rs +++ b/tests/geometry/isometry.rs @@ -1,14 +1,5 @@ #![allow(non_snake_case)] -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use alga::linear::{Transformation, ProjectiveTransformation}; use na::{ Vector3, Point3, Rotation3, Isometry3, Translation3, UnitQuaternion, @@ -26,7 +17,7 @@ quickcheck!( fn rotation_wrt_point_invariance(r: UnitQuaternion, p: Point3) -> bool { let iso = Isometry3::rotation_wrt_point(r, p); - relative_eq!(iso * p, p) + relative_eq!(iso * p, p, epsilon = 1.0e-7) } fn look_at_rh_3(eye: Point3, target: Point3, up: Vector3) -> bool { diff --git a/tests/geometry/mod.rs b/tests/geometry/mod.rs new file mode 100644 index 00000000..ec9755a0 --- /dev/null +++ b/tests/geometry/mod.rs @@ -0,0 +1,7 @@ +mod isometry; +mod point; +mod projection; +mod quaternion; +mod rotation; +mod similarity; +mod unit_complex; diff --git a/tests/point.rs b/tests/geometry/point.rs similarity index 92% rename from tests/point.rs rename to tests/geometry/point.rs index 05fb9f23..9c813819 100644 --- a/tests/point.rs +++ b/tests/geometry/point.rs @@ -1,12 +1,3 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use num::Zero; use na::{Point3, Vector3, Vector4}; diff --git a/tests/projection.rs b/tests/geometry/projection.rs similarity index 89% rename from tests/projection.rs rename to tests/geometry/projection.rs index 6e25e5a3..84f3cf70 100644 --- a/tests/projection.rs +++ b/tests/geometry/projection.rs @@ -1,11 +1,3 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; - -extern crate nalgebra as na; - use na::{Point3, Perspective3, Orthographic3}; #[test] diff --git a/tests/quaternion.rs b/tests/geometry/quaternion.rs similarity index 86% rename from tests/quaternion.rs rename to tests/geometry/quaternion.rs index bd7d1962..eb3a42f2 100644 --- a/tests/quaternion.rs +++ b/tests/geometry/quaternion.rs @@ -1,18 +1,31 @@ #![allow(non_snake_case)] -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use na::{Unit, UnitQuaternion, Quaternion, Vector3, Point3, Rotation3}; quickcheck!( + /* + * + * Euler angles. + * + */ + fn from_euler_angles(r: f64, p: f64, y: f64) -> bool { + let roll = UnitQuaternion::from_euler_angles(r, 0.0, 0.0); + let pitch = UnitQuaternion::from_euler_angles(0.0, p, 0.0); + let yaw = UnitQuaternion::from_euler_angles(0.0, 0.0, y); + + let rpy = UnitQuaternion::from_euler_angles(r, p, y); + + let rroll = roll.to_rotation_matrix(); + let rpitch = pitch.to_rotation_matrix(); + let ryaw = yaw.to_rotation_matrix(); + + relative_eq!(rroll[(0, 0)], 1.0, epsilon = 1.0e-7) && // rotation wrt. x axis. + relative_eq!(rpitch[(1, 1)], 1.0, epsilon = 1.0e-7) && // rotation wrt. y axis. + relative_eq!(ryaw[(2, 2)], 1.0, epsilon = 1.0e-7) && // rotation wrt. z axis. + relative_eq!(yaw * pitch * roll, rpy, epsilon = 1.0e-7) + } + /* * @@ -30,7 +43,7 @@ quickcheck!( /* * - * PointBase/Vector transformation. + * Point/Vector transformation. * */ fn unit_quaternion_transformation(q: UnitQuaternion, v: Vector3, p: Point3) -> bool { diff --git a/tests/rotation.rs b/tests/geometry/rotation.rs similarity index 89% rename from tests/rotation.rs rename to tests/geometry/rotation.rs index d683c3a9..d5fa782c 100644 --- a/tests/rotation.rs +++ b/tests/geometry/rotation.rs @@ -1,15 +1,6 @@ -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use std::f64; use alga::general::Real; -use na::{Vector2, Vector3, Rotation2, Rotation3, Unit}; +use na::{self, Vector2, Vector3, Rotation2, Rotation3, Unit}; #[test] fn angle_2() { @@ -28,6 +19,24 @@ fn angle_3() { } quickcheck!( + /* + * + * Euler angles. + * + */ + fn from_euler_angles(r: f64, p: f64, y: f64) -> bool { + let roll = Rotation3::from_euler_angles(r, 0.0, 0.0); + let pitch = Rotation3::from_euler_angles(0.0, p, 0.0); + let yaw = Rotation3::from_euler_angles(0.0, 0.0, y); + + let rpy = Rotation3::from_euler_angles(r, p, y); + + roll[(0, 0)] == 1.0 && // rotation wrt. x axis. + pitch[(1, 1)] == 1.0 && // rotation wrt. y axis. + yaw[(2, 2)] == 1.0 && // rotation wrt. z axis. + yaw * pitch * roll == rpy + } + /* * * Inversion is transposition. diff --git a/tests/similarity.rs b/tests/geometry/similarity.rs similarity index 98% rename from tests/similarity.rs rename to tests/geometry/similarity.rs index c827eda8..ef4fd4c2 100644 --- a/tests/similarity.rs +++ b/tests/geometry/similarity.rs @@ -1,14 +1,5 @@ #![allow(non_snake_case)] -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use alga::linear::{Transformation, ProjectiveTransformation}; use na::{Vector3, Point3, Similarity3, Translation3, Isometry3, UnitQuaternion}; diff --git a/tests/unit_complex.rs b/tests/geometry/unit_complex.rs similarity index 95% rename from tests/unit_complex.rs rename to tests/geometry/unit_complex.rs index 3d578276..ff8d4a66 100644 --- a/tests/unit_complex.rs +++ b/tests/geometry/unit_complex.rs @@ -1,14 +1,5 @@ #![allow(non_snake_case)] -#[cfg(feature = "arbitrary")] -#[macro_use] -extern crate quickcheck; -#[macro_use] -extern crate approx; -extern crate num_traits as num; -extern crate alga; -extern crate nalgebra as na; - use na::{Unit, UnitComplex, Vector2, Point2, Rotation2}; @@ -30,7 +21,7 @@ quickcheck!( /* * - * PointBase/Vector transformation. + * Point/Vector transformation. * */ fn unit_complex_transformation(c: UnitComplex, v: Vector2, p: Point2) -> bool { diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 00000000..5e111eb0 --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "arbitrary")] +#[macro_use] +extern crate quickcheck; +#[macro_use] +extern crate approx; +extern crate num_traits as num; +extern crate serde_json; +extern crate rand; +extern crate alga; +extern crate nalgebra as na; + + +mod core; +mod linalg; +mod geometry; diff --git a/tests/linalg/balancing.rs b/tests/linalg/balancing.rs new file mode 100644 index 00000000..97af9ab6 --- /dev/null +++ b/tests/linalg/balancing.rs @@ -0,0 +1,25 @@ +use std::cmp; + +use na::{DMatrix, Matrix4}; +use na::balancing; + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn balancing_parlett_reinsch(n: usize) -> bool { + let n = cmp::min(n, 10); + let m = DMatrix::::new_random(n, n); + let mut balanced = m.clone(); + let d = balancing::balance_parlett_reinsch(&mut balanced); + balancing::unbalance(&mut balanced, &d); + + balanced == m + } + + fn balancing_parlett_reinsch_static(m: Matrix4) -> bool { + let mut balanced = m; + let d = balancing::balance_parlett_reinsch(&mut balanced); + balancing::unbalance(&mut balanced, &d); + + balanced == m + } +} diff --git a/tests/linalg/bidiagonal.rs b/tests/linalg/bidiagonal.rs new file mode 100644 index 00000000..30eddd4c --- /dev/null +++ b/tests/linalg/bidiagonal.rs @@ -0,0 +1,59 @@ +use na::{DMatrix, Matrix2, Matrix4, Matrix5x3, Matrix3x5}; + + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn bidiagonal(m: DMatrix) -> bool { + if m.len() == 0 { + return true; + } + + let bidiagonal = m.clone().bidiagonalize(); + let (u, d, v_t) = bidiagonal.unpack(); + + println!("{}{}{}", &u, &d, &v_t); + println!("{:.7}{:.7}", &u * &d * &v_t, m); + + relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) + } + + fn bidiagonal_static_5_3(m: Matrix5x3) -> bool { + let bidiagonal = m.bidiagonalize(); + let (u, d, v_t) = bidiagonal.unpack(); + + println!("{}{}{}", &u, &d, &v_t); + println!("{:.7}{:.7}", &u * &d * &v_t, m); + + relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) + } + + fn bidiagonal_static_3_5(m: Matrix3x5) -> bool { + let bidiagonal = m.bidiagonalize(); + let (u, d, v_t) = bidiagonal.unpack(); + + println!("{}{}{}", &u, &d, &v_t); + println!("{:.7}{:.7}", &u * &d * &v_t, m); + + relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) + } + + fn bidiagonal_static_square(m: Matrix4) -> bool { + let bidiagonal = m.bidiagonalize(); + let (u, d, v_t) = bidiagonal.unpack(); + + println!("{}{}{}", &u, &d, &v_t); + println!("{:.7}{:.7}", &u * &d * &v_t, m); + + relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) + } + + fn bidiagonal_static_square_2x2(m: Matrix2) -> bool { + let bidiagonal = m.bidiagonalize(); + let (u, d, v_t) = bidiagonal.unpack(); + + println!("{}{}{}", &u, &d, &v_t); + println!("{:.7}{:.7}", &u * &d * &v_t, m); + + relative_eq!(m, &u * d * &v_t, epsilon = 1.0e-7) + } +} diff --git a/tests/linalg/cholesky.rs b/tests/linalg/cholesky.rs new file mode 100644 index 00000000..18e577b9 --- /dev/null +++ b/tests/linalg/cholesky.rs @@ -0,0 +1,80 @@ +use std::cmp; +use na::{DMatrix, Matrix4x3, DVector, Vector4}; +use na::dimension::U4; +use na::debug::RandomSDP; + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn cholesky(m: RandomSDP) -> bool { + let mut m = m.unwrap(); + + // Put garbage on the upper triangle to make sure it is not read by the decomposition. + m.fill_upper_triangle(23.0, 1); + + let l = m.clone().cholesky().unwrap().unpack(); + m.fill_upper_triangle_with_lower_triangle(); + relative_eq!(m, &l * l.transpose(), epsilon = 1.0e-7) + } + + fn cholesky_static(m: RandomSDP) -> bool { + let m = m.unwrap(); + let chol = m.cholesky().unwrap(); + let l = chol.unpack(); + + if !relative_eq!(m, &l * l.transpose(), epsilon = 1.0e-7) { + false + } + else { + true + } + } + + + fn cholesky_solve(m: RandomSDP, nb: usize) -> bool { + let m = m.unwrap(); + let n = m.nrows(); + let nb = cmp::min(nb, 50); // To avoid slowing down the test too much. + + let chol = m.clone().cholesky().unwrap(); + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = chol.solve(&b1); + let sol2 = chol.solve(&b2); + + relative_eq!(&m * &sol1, b1, epsilon = 1.0e-7) && + relative_eq!(&m * &sol2, b2, epsilon = 1.0e-7) + } + + fn cholesky_solve_static(m: RandomSDP) -> bool { + let m = m.unwrap(); + let chol = m.clone().cholesky().unwrap(); + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + let sol1 = chol.solve(&b1); + let sol2 = chol.solve(&b2); + + relative_eq!(m * sol1, b1, epsilon = 1.0e-7) && + relative_eq!(m * sol2, b2, epsilon = 1.0e-7) + } + + fn cholesky_inverse(m: RandomSDP) -> bool { + let m = m.unwrap(); + + let m1 = m.clone().cholesky().unwrap().inverse(); + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-7) && id2.is_identity(1.0e-7) + } + + fn cholesky_inverse_static(m: RandomSDP) -> bool { + let m = m.unwrap(); + let m1 = m.clone().cholesky().unwrap().inverse(); + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-7) && id2.is_identity(1.0e-7) + } +} diff --git a/tests/linalg/eigen.rs b/tests/linalg/eigen.rs new file mode 100644 index 00000000..27c9b583 --- /dev/null +++ b/tests/linalg/eigen.rs @@ -0,0 +1,179 @@ +use std::cmp; + +use na::{DMatrix, Matrix2, Matrix3, Matrix4}; + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn symmetric_eigen(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let m = DMatrix::::new_random(n, n); + let eig = m.clone().symmetric_eigen(); + let recomp = eig.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } + + fn symmetric_eigen_singular(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let mut m = DMatrix::::new_random(n, n); + m.row_mut(n / 2).fill(0.0); + m.column_mut(n / 2).fill(0.0); + let eig = m.clone().symmetric_eigen(); + let recomp = eig.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } + + fn symmetric_eigen_static_square_4x4(m: Matrix4) -> bool { + let eig = m.symmetric_eigen(); + let recomp = eig.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } + + fn symmetric_eigen_static_square_3x3(m: Matrix3) -> bool { + let eig = m.symmetric_eigen(); + let recomp = eig.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } + + fn symmetric_eigen_static_square_2x2(m: Matrix2) -> bool { + let eig = m.symmetric_eigen(); + let recomp = eig.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5) + } +} + +// Test proposed on the issue #176 of rulinalg. +#[test] +fn symmetric_eigen_singular_24x24() { + let m = DMatrix::from_row_slice(24, 24, &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, + 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + let eig = m.clone().symmetric_eigen(); + let recomp = eig.recompose(); + + assert!(relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-5)); +} + + +// #[cfg(feature = "arbitrary")] +// quickcheck! { +// FIXME: full eigendecomposition is not implemented yet because of its complexity when some +// eigenvalues have multiplicity > 1. +// +// /* +// * NOTE: for the following tests, we use only upper-triangular matrices. +// * Thes ensures the schur decomposition will work, and allows use to test the eigenvector +// * computation. +// */ +// fn eigen(n: usize) -> bool { +// let n = cmp::max(1, cmp::min(n, 10)); +// let m = DMatrix::::new_random(n, n).upper_triangle(); +// +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// +// fn eigen_with_adjascent_duplicate_diagonals(n: usize) -> bool { +// let n = cmp::max(1, cmp::min(n, 10)); +// let mut m = DMatrix::::new_random(n, n).upper_triangle(); +// +// // Suplicate some adjascent diagonal elements. +// for i in 0 .. n / 2 { +// m[(i * 2 + 1, i * 2 + 1)] = m[(i * 2, i * 2)]; +// } +// +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// +// fn eigen_with_nonadjascent_duplicate_diagonals(n: usize) -> bool { +// let n = cmp::max(3, cmp::min(n, 10)); +// let mut m = DMatrix::::new_random(n, n).upper_triangle(); +// +// // Suplicate some diagonal elements. +// for i in n / 2 .. n { +// m[(i, i)] = m[(i - n / 2, i - n / 2)]; +// } +// +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// +// fn eigen_static_square_4x4(m: Matrix4) -> bool { +// let m = m.upper_triangle(); +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// +// fn eigen_static_square_3x3(m: Matrix3) -> bool { +// let m = m.upper_triangle(); +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// +// fn eigen_static_square_2x2(m: Matrix2) -> bool { +// let m = m.upper_triangle(); +// println!("{}", m); +// let eig = RealEigen::new(m.clone()).unwrap(); +// verify_eigenvectors(m, eig) +// } +// } +// +// fn verify_eigenvectors(m: MatrixN, mut eig: RealEigen) -> bool +// where DefaultAllocator: Allocator + +// Allocator + +// Allocator + +// Allocator, +// MatrixN: Display, +// VectorN: Display { +// let mv = &m * &eig.eigenvectors; +// +// println!("eigenvalues: {}eigenvectors: {}", eig.eigenvalues, eig.eigenvectors); +// +// let dim = m.nrows(); +// for i in 0 .. dim { +// let mut col = eig.eigenvectors.column_mut(i); +// col *= eig.eigenvalues[i]; +// } +// +// println!("{}{:.5}{:.5}", m, mv, eig.eigenvectors); +// +// relative_eq!(eig.eigenvectors, mv, epsilon = 1.0e-5) +// } diff --git a/tests/linalg/full_piv_lu.rs b/tests/linalg/full_piv_lu.rs new file mode 100644 index 00000000..2cfa2c29 --- /dev/null +++ b/tests/linalg/full_piv_lu.rs @@ -0,0 +1,155 @@ +use std::cmp; +use na::{DMatrix, Matrix3, Matrix4, Matrix4x3, Matrix5x3, Matrix3x5, + DVector, Vector4}; + + +#[test] +fn full_piv_lu_simple() { + let m = Matrix3::new( + 2.0, -1.0, 0.0, + -1.0, 2.0, -1.0, + 0.0, -1.0, 2.0); + + let lu = m.full_piv_lu(); + assert_eq!(lu.determinant(), 4.0); + + let (p, l, u, q) = lu.unpack(); + + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + assert!(relative_eq!(m, lu, epsilon = 1.0e-7)); +} + +#[test] +fn full_piv_lu_simple_with_pivot() { + let m = Matrix3::new( + 0.0, -1.0, 2.0, + -1.0, 2.0, -1.0, + 2.0, -1.0, 0.0); + + let lu = m.full_piv_lu(); + assert_eq!(lu.determinant(), -4.0); + + let (p, l, u, q) = lu.unpack(); + + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + assert!(relative_eq!(m, lu, epsilon = 1.0e-7)); +} + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn full_piv_lu(m: DMatrix) -> bool { + let mut m = m; + if m.len() == 0 { + m = DMatrix::new_random(1, 1); + } + + let lu = m.clone().full_piv_lu(); + let (p, l, u, q) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn full_piv_lu_static_3_5(m: Matrix3x5) -> bool { + let lu = m.full_piv_lu(); + let (p, l, u, q) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn full_piv_lu_static_5_3(m: Matrix5x3) -> bool { + let lu = m.full_piv_lu(); + let (p, l, u, q) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn full_piv_lu_static_square(m: Matrix4) -> bool { + let lu = m.full_piv_lu(); + let (p, l, u, q) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + q.inv_permute_columns(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn full_piv_lu_solve(n: usize, nb: usize) -> bool { + if n != 0 && nb != 0 { + let n = cmp::min(n, 50); // To avoid slowing down the test too much. + let nb = cmp::min(nb, 50); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let lu = m.clone().full_piv_lu(); + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = lu.solve(&b1); + let sol2 = lu.solve(&b2); + + return (sol1.is_none() || relative_eq!(&m * sol1.unwrap(), b1, epsilon = 1.0e-6)) && + (sol2.is_none() || relative_eq!(&m * sol2.unwrap(), b2, epsilon = 1.0e-6)) + } + + return true; + } + + fn full_piv_lu_solve_static(m: Matrix4) -> bool { + let lu = m.full_piv_lu(); + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + let sol1 = lu.solve(&b1); + let sol2 = lu.solve(&b2); + + return (sol1.is_none() || relative_eq!(&m * sol1.unwrap(), b1, epsilon = 1.0e-6)) && + (sol2.is_none() || relative_eq!(&m * sol2.unwrap(), b2, epsilon = 1.0e-6)) + } + + fn full_piv_lu_inverse(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 15)); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let mut l = m.lower_triangle(); + let mut u = m.upper_triangle(); + + // Ensure the matrix is well conditioned for inversion. + l.fill_diagonal(1.0); + u.fill_diagonal(1.0); + let m = l * u; + + let m1 = m.clone().full_piv_lu().try_inverse().unwrap(); + let id1 = &m * &m1; + let id2 = &m1 * &m; + + return id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5); + } + + fn full_piv_lu_inverse_static(m: Matrix4) -> bool { + let lu = m.full_piv_lu(); + + if let Some(m1) = lu.try_inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + } + else { + true + } + } +} diff --git a/tests/linalg/hessenberg.rs b/tests/linalg/hessenberg.rs new file mode 100644 index 00000000..6fce8579 --- /dev/null +++ b/tests/linalg/hessenberg.rs @@ -0,0 +1,37 @@ +use std::cmp; +use na::{DMatrix, Matrix2, Matrix4}; + + +#[test] +fn hessenberg_simple() { + let m = Matrix2::new(1.0, 0.0, + 1.0, 3.0); + let hess = m.hessenberg(); + let (p, h) = hess.unpack(); + assert!(relative_eq!(m, p * h * p.transpose(), epsilon = 1.0e-7)) +} + + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn hessenberg(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 50)); + let m = DMatrix::::new_random(n, n); + + let hess = m.clone().hessenberg(); + let (p, h) = hess.unpack(); + relative_eq!(m, &p * h * p.transpose(), epsilon = 1.0e-7) + } + + fn hessenberg_static_mat2(m: Matrix2) -> bool { + let hess = m.hessenberg(); + let (p, h) = hess.unpack(); + relative_eq!(m, p * h * p.transpose(), epsilon = 1.0e-7) + } + + fn hessenberg_static(m: Matrix4) -> bool { + let hess = m.hessenberg(); + let (p, h) = hess.unpack(); + relative_eq!(m, p * h * p.transpose(), epsilon = 1.0e-7) + } +} diff --git a/tests/matrix_inverse.rs b/tests/linalg/inverse.rs similarity index 70% rename from tests/matrix_inverse.rs rename to tests/linalg/inverse.rs index fe1af112..de90712a 100644 --- a/tests/matrix_inverse.rs +++ b/tests/linalg/inverse.rs @@ -1,9 +1,4 @@ -#[macro_use] -extern crate approx; - -extern crate nalgebra as na; - -use na::{Matrix1, Matrix2, Matrix3, Matrix5}; +use na::{Matrix1, Matrix2, Matrix3, Matrix4, Matrix5}; #[test] fn matrix1_try_inverse() { @@ -39,13 +34,32 @@ fn matrix3_try_inverse() { assert_relative_eq!(a_inv, expected_inverse); } +#[test] +fn matrix4_try_inverse_issue_214() { + let m1 = Matrix4::new( + -0.34727043, 0.00000005397217, -0.000000000000003822135, -0.000000000000003821371, + 0.0, -0.000000026986084, -1.0001999, -1.0, + 0.000000030359345, 0.61736965, -0.000000043720128, -0.00000004371139, + -0.0000000029144975, -0.05926739, 3.8007796, 4.0); + + + let m2 = Matrix4::new( + -0.34727043, 0.00000005397217, -0.000000000000003822135, -0.000000000000003821371, + 0.0, -0.000000026986084, -1.0001999, -1.0, + 0.000000030359345, 0.61736965, -0.000000043720128, -0.00000004371139, + -0.0000000029448568, -0.05988476, 3.8007796, 4.0); + + assert!(m1.try_inverse().is_some()); + assert!(m2.try_inverse().is_some()); + assert!(m1.transpose().try_inverse().is_some()); + assert!(m2.transpose().try_inverse().is_some()); +} + #[test] fn matrix5_try_inverse() { - // Dimension 5 is chosen so that the inversion - // happens by Gaussian elimination - // (at the time of writing dimensions <= 3 are implemented - // as analytic formulas, but we choose 5 in the case that 4 - // also gets an analytic implementation) + // Dimension 5 is chosen so that the inversion happens by Gaussian elimination. + // (at the time of writing dimensions <= 3 are implemented as analytic formulas, but we choose + // 5 in the case that 4 also gets an analytic implementation) let a = Matrix5::new(-2.0, 0.0, 2.0, 5.0, -5.0, -6.0, 4.0, 4.0, 13.0, -15.0, 4.0, 16.0, -14.0, -19.0, 12.0, @@ -57,7 +71,7 @@ fn matrix5_try_inverse() { -1.8233e+01, 5.7667e+00, -1.5667e+00, 2.3333e-01, -2.0000e-01, -4.3333e+00, 1.6667e+00, -6.6667e-01, 3.3333e-01, -4.6950e-19, -1.3400e+01, 4.6000e+00, -1.4000e+00, 4.0000e-01, -2.0000e-01); - let a_inv = a.try_inverse().expect("Matrix is invertible"); + let a_inv = a.try_inverse().expect("Matrix is not invertible"); assert_relative_eq!(a_inv, expected_inverse, max_relative=1e-4); } @@ -68,7 +82,7 @@ fn matrix1_try_inverse_scaled_identity() { // very small coefficients let a = Matrix1::new(1.0e-20); let expected_inverse = Matrix1::new(1.0e20); - let a_inv = a.try_inverse().expect("Matrix is invertible"); + let a_inv = a.try_inverse().expect("Matrix should be invertible"); assert_relative_eq!(a_inv, expected_inverse); } @@ -81,7 +95,7 @@ fn matrix2_try_inverse_scaled_identity() { 0.0, 1.0e-20); let expected_inverse = Matrix2::new(1.0e20, 0.0, 0.0, 1.0e20); - let a_inv = a.try_inverse().expect("Matrix is invertible"); + let a_inv = a.try_inverse().expect("Matrix should be invertible"); assert_relative_eq!(a_inv, expected_inverse); } @@ -96,7 +110,7 @@ fn matrix3_try_inverse_scaled_identity() { let expected_inverse = Matrix3::new(1.0e20, 0.0, 0.0, 0.0, 1.0e20, 0.0, 0.0, 0.0, 1.0e20); - let a_inv = a.try_inverse().expect("Matrix is invertible"); + let a_inv = a.try_inverse().expect("Matrix should be invertible"); assert_relative_eq!(a_inv, expected_inverse); } @@ -115,7 +129,7 @@ fn matrix5_try_inverse_scaled_identity() { 0.0, 0.0, 1.0e+20, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0e+20, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0e+20);; - let a_inv = a.try_inverse().expect("Matrix is invertible"); + let a_inv = a.try_inverse().expect("Matrix should be invertible"); assert_relative_eq!(a_inv, expected_inverse); } diff --git a/tests/linalg/lu.rs b/tests/linalg/lu.rs new file mode 100644 index 00000000..2e8cc0ce --- /dev/null +++ b/tests/linalg/lu.rs @@ -0,0 +1,149 @@ +use std::cmp; +use na::{DMatrix, Matrix3, Matrix4, Matrix4x3, Matrix5x3, Matrix3x5, + DVector, Vector4}; + + +#[test] +fn lu_simple() { + let m = Matrix3::new( + 2.0, -1.0, 0.0, + -1.0, 2.0, -1.0, + 0.0, -1.0, 2.0); + + let lu = m.lu(); + assert_eq!(lu.determinant(), 4.0); + + let (p, l, u) = lu.unpack(); + + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + assert!(relative_eq!(m, lu, epsilon = 1.0e-7)); +} + +#[test] +fn lu_simple_with_pivot() { + let m = Matrix3::new( + 0.0, -1.0, 2.0, + -1.0, 2.0, -1.0, + 2.0, -1.0, 0.0); + + let lu = m.lu(); + assert_eq!(lu.determinant(), -4.0); + + let (p, l, u) = lu.unpack(); + + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + assert!(relative_eq!(m, lu, epsilon = 1.0e-7)); +} + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn lu(m: DMatrix) -> bool { + let mut m = m; + if m.len() == 0 { + m = DMatrix::new_random(1, 1); + } + + let lu = m.clone().lu(); + let (p, l, u) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn lu_static_3_5(m: Matrix3x5) -> bool { + let lu = m.lu(); + let (p, l, u) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn lu_static_5_3(m: Matrix5x3) -> bool { + let lu = m.lu(); + let (p, l, u) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn lu_static_square(m: Matrix4) -> bool { + let lu = m.lu(); + let (p, l, u) = lu.unpack(); + let mut lu = l * u; + p.inv_permute_rows(&mut lu); + + relative_eq!(m, lu, epsilon = 1.0e-7) + } + + fn lu_solve(n: usize, nb: usize) -> bool { + if n != 0 && nb != 0 { + let n = cmp::min(n, 50); // To avoid slowing down the test too much. + let nb = cmp::min(nb, 50); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let lu = m.clone().lu(); + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = lu.solve(&b1); + let sol2 = lu.solve(&b2); + + return (sol1.is_none() || relative_eq!(&m * sol1.unwrap(), b1, epsilon = 1.0e-6)) && + (sol2.is_none() || relative_eq!(&m * sol2.unwrap(), b2, epsilon = 1.0e-6)) + } + + return true; + } + + fn lu_solve_static(m: Matrix4) -> bool { + let lu = m.lu(); + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + let sol1 = lu.solve(&b1); + let sol2 = lu.solve(&b2); + + return (sol1.is_none() || relative_eq!(&m * sol1.unwrap(), b1, epsilon = 1.0e-6)) && + (sol2.is_none() || relative_eq!(&m * sol2.unwrap(), b2, epsilon = 1.0e-6)) + } + + fn lu_inverse(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 15)); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let mut l = m.lower_triangle(); + let mut u = m.upper_triangle(); + + // Ensure the matrix is well conditioned for inversion. + l.fill_diagonal(1.0); + u.fill_diagonal(1.0); + let m = l * u; + + let m1 = m.clone().lu().try_inverse().unwrap(); + let id1 = &m * &m1; + let id2 = &m1 * &m; + + return id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5); + } + + fn lu_inverse_static(m: Matrix4) -> bool { + let lu = m.lu(); + + if let Some(m1) = lu.try_inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + } + else { + true + } + } +} diff --git a/tests/linalg/mod.rs b/tests/linalg/mod.rs new file mode 100644 index 00000000..6ce28085 --- /dev/null +++ b/tests/linalg/mod.rs @@ -0,0 +1,13 @@ +mod inverse; +mod solve; +mod qr; +mod cholesky; +mod hessenberg; +mod lu; +mod full_piv_lu; +mod bidiagonal; +mod real_schur; +mod svd; +mod balancing; +mod tridiagonal; +mod eigen; diff --git a/tests/linalg/qr.rs b/tests/linalg/qr.rs new file mode 100644 index 00000000..8bdc3ce8 --- /dev/null +++ b/tests/linalg/qr.rs @@ -0,0 +1,112 @@ +use std::cmp; +use na::{DMatrix, Matrix4, Matrix4x3, Matrix5x3, Matrix3x5, + DVector, Vector4}; + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn qr(m: DMatrix) -> bool { + let qr = m.clone().qr(); + let q = qr.q(); + let r = qr.r(); + + relative_eq!(m, &q * r, epsilon = 1.0e-7) && + q.is_orthogonal(1.0e-7) + } + + fn qr_static_5_3(m: Matrix5x3) -> bool { + let qr = m.qr(); + let q = qr.q(); + let r = qr.r(); + + relative_eq!(m, q * r, epsilon = 1.0e-7) && + q.is_orthogonal(1.0e-7) + } + + fn qr_static_3_5(m: Matrix3x5) -> bool { + let qr = m.qr(); + let q = qr.q(); + let r = qr.r(); + + relative_eq!(m, q * r, epsilon = 1.0e-7) && + q.is_orthogonal(1.0e-7) + } + + fn qr_static_square(m: Matrix4) -> bool { + let qr = m.qr(); + let q = qr.q(); + let r = qr.r(); + + println!("{}{}{}{}", q, r, q * r, m); + + relative_eq!(m, q * r, epsilon = 1.0e-7) && + q.is_orthogonal(1.0e-7) + } + + fn qr_solve(n: usize, nb: usize) -> bool { + if n != 0 && nb != 0 { + let n = cmp::min(n, 50); // To avoid slowing down the test too much. + let nb = cmp::min(nb, 50); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + let qr = m.clone().qr(); + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + if qr.is_invertible() { + let sol1 = qr.solve(&b1).unwrap(); + let sol2 = qr.solve(&b2).unwrap(); + + return relative_eq!(&m * sol1, b1, epsilon = 1.0e-6) && + relative_eq!(&m * sol2, b2, epsilon = 1.0e-6) + } + } + + return true; + } + + fn qr_solve_static(m: Matrix4) -> bool { + let qr = m.qr(); + let b1 = Vector4::new_random(); + let b2 = Matrix4x3::new_random(); + + if qr.is_invertible() { + let sol1 = qr.solve(&b1).unwrap(); + let sol2 = qr.solve(&b2).unwrap(); + + relative_eq!(m * sol1, b1, epsilon = 1.0e-6) && + relative_eq!(m * sol2, b2, epsilon = 1.0e-6) + } + else { + false + } + } + + fn qr_inverse(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 15)); // To avoid slowing down the test too much. + let m = DMatrix::::new_random(n, n); + + if let Some(m1) = m.clone().qr().try_inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + } + else { + true + } + } + + fn qr_inverse_static(m: Matrix4) -> bool { + let qr = m.qr(); + + if let Some(m1) = qr.try_inverse() { + let id1 = &m * &m1; + let id2 = &m1 * &m; + + id1.is_identity(1.0e-5) && id2.is_identity(1.0e-5) + } + else { + true + } + } +} diff --git a/tests/linalg/real_schur.rs b/tests/linalg/real_schur.rs new file mode 100644 index 00000000..3ef32297 --- /dev/null +++ b/tests/linalg/real_schur.rs @@ -0,0 +1,134 @@ +use std::cmp; +use na::{DMatrix, Matrix2, Matrix3, Matrix4}; + + +#[test] +fn schur_simpl_mat3() { + let m = Matrix3::new(-2.0, -4.0, 2.0, + -2.0, 1.0, 2.0, + 4.0, 2.0, 5.0); + + let schur = m.real_schur(); + let (vecs, vals) = schur.unpack(); + + assert!(relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7)); +} + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn schur(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let m = DMatrix::::new_random(n, n); + + let (vecs, vals) = m.clone().real_schur().unpack(); + + if !relative_eq!(&vecs * &vals * vecs.transpose(), m, epsilon = 1.0e-7) { + println!("{:.5}{:.5}", m, &vecs * &vals * vecs.transpose()); + } + + relative_eq!(&vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7) + } + + fn schur_static_mat2(m: Matrix2) -> bool { + let (vecs, vals) = m.clone().real_schur().unpack(); + + let ok = relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7); + if !ok { + println!("{:.5}{:.5}", vecs, vals); + println!("Reconstruction:{}{}", m, &vecs * &vals * vecs.transpose()); + } + ok + } + + fn schur_static_mat3(m: Matrix3) -> bool { + let (vecs, vals) = m.clone().real_schur().unpack(); + + let ok = relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7); + if !ok { + println!("{:.5}{:.5}", m, &vecs * &vals * vecs.transpose()); + } + ok + } + + fn schur_static_mat4(m: Matrix4) -> bool { + let (vecs, vals) = m.clone().real_schur().unpack(); + + let ok = relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7); + if !ok { + println!("{:.5}{:.5}", m, &vecs * &vals * vecs.transpose()); + } + ok + } +} + +#[test] +fn schur_static_mat4_fail() { + let m = Matrix4::new( + 33.32699857679677, 46.794945978960044, -20.792148817005838, 84.73945485997737, + -53.04896234480401, -4.031523330630989, 19.022858300892366, -93.2258351951158, + -94.61793793643038, -18.64216213611094, 88.32376703241675, -99.30169870309795, + 90.62661897246733, 96.74200696130146, 34.7421322611369, 84.86773307198098); + + let (vecs, vals) = m.clone().real_schur().unpack(); + println!("{:.6}{:.6}", m, &vecs * &vals * vecs.transpose()); + assert!(relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7)) +} + +#[test] +fn schur_static_mat4_fail2() { + let m = Matrix4::new( + 14.623586538485966, 7.646156622760756, -52.11923331576265, -97.50030223503413, + 53.829398131426785, -33.40560799661168, 70.31168286972388, -81.25248138434173, + 27.932377940728202, 82.94220150938, -35.5898884705951, 67.56447552434219, + 55.66754906908682, -42.14328890569226, -20.684709585152206, -87.9456949841046); + + let (vecs, vals) = m.clone().real_schur().unpack(); + println!("{:.6}{:.6}", m, &vecs * &vals * vecs.transpose()); + assert!(relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7)) +} + +#[test] +fn schur_static_mat3_fail() { + let m = Matrix3::new( + -21.58457553143394, -67.3881542667948, -14.619829849784338, + -7.525423104386547, -17.827350599642287, 11.297377444555849, + 38.080736654870464, -84.27428302131528, -95.88198590331922); + + let (vecs, vals) = m.clone().real_schur().unpack(); + println!("{:.6}{:.6}", m, &vecs * &vals * vecs.transpose()); + assert!(relative_eq!(vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7)) +} + +// Test proposed on the issue #176 of rulinalg. +#[test] +fn schur_singular() { + let m = DMatrix::from_row_slice(24, 24, &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, + 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + let (vecs, vals) = m.clone().real_schur().unpack(); + println!("{:.6}{:.6}", m, &vecs * &vals * vecs.transpose()); + assert!(relative_eq!(&vecs * vals * vecs.transpose(), m, epsilon = 1.0e-7)) +} diff --git a/tests/linalg/solve.rs b/tests/linalg/solve.rs new file mode 100644 index 00000000..3940c287 --- /dev/null +++ b/tests/linalg/solve.rs @@ -0,0 +1,56 @@ +use na::{Matrix4, Matrix4x5}; + +fn unzero_diagonal(a: &mut Matrix4) { + for i in 0 .. 4 { + if a[(i, i)] < 1.0e-7 { + a[(i, i)] = 1.0; + } + } +} + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn solve_lower_triangular(a: Matrix4, b: Matrix4x5) -> bool { + let mut a = a; + unzero_diagonal(&mut a); + let tri = a.lower_triangle(); + let x = a.solve_lower_triangular(&b).unwrap(); + + println!("{}\n{}\n{}\n{}", tri, x, tri * x, b); + + relative_eq!(tri * x, b, epsilon = 1.0e-7) + } + + fn solve_upper_triangular(a: Matrix4, b: Matrix4x5) -> bool { + let mut a = a; + unzero_diagonal(&mut a); + let tri = a.upper_triangle(); + let x = a.solve_upper_triangular(&b).unwrap(); + + println!("{}\n{}\n{}\n{}", tri, x, tri * x, b); + + relative_eq!(tri * x, b, epsilon = 1.0e-7) + } + + fn tr_solve_lower_triangular(a: Matrix4, b: Matrix4x5) -> bool { + let mut a = a; + unzero_diagonal(&mut a); + let tri = a.lower_triangle(); + let x = a.tr_solve_lower_triangular(&b).unwrap(); + + println!("{}\n{}\n{}\n{}", tri, x, tri * x, b); + + relative_eq!(tri.transpose() * x, b, epsilon = 1.0e-7) + } + + fn tr_solve_upper_triangular(a: Matrix4, b: Matrix4x5) -> bool { + let mut a = a; + unzero_diagonal(&mut a); + let tri = a.upper_triangle(); + let x = a.tr_solve_upper_triangular(&b).unwrap(); + + println!("{}\n{}\n{}\n{}", tri, x, tri * x, b); + + relative_eq!(tri.transpose() * x, b, epsilon = 1.0e-7) + } +} diff --git a/tests/linalg/svd.rs b/tests/linalg/svd.rs new file mode 100644 index 00000000..df107694 --- /dev/null +++ b/tests/linalg/svd.rs @@ -0,0 +1,333 @@ +use std::cmp; + +use na::{DMatrix, Matrix2, Matrix3, Matrix4, Matrix6, Matrix5x2, Matrix5x3, Matrix2x5, Matrix3x5, + DVector}; + + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn svd(m: DMatrix) -> bool { + if m.len() > 0 { + let svd = m.clone().svd(true, true); + let recomp_m = svd.clone().recompose(); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = DMatrix::from_diagonal(&s); + + println!("{}{}", &m, &u * &ds * &v_t); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(&u * ds * &v_t, recomp_m, epsilon = 1.0e-5) && + relative_eq!(m, recomp_m, epsilon = 1.0e-5) + } + else { + true + } + } + + fn svd_static_5_3(m: Matrix5x3) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = Matrix3::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, &u * ds * &v_t, epsilon = 1.0e-5) && + u.is_orthogonal(1.0e-5) && + v_t.is_orthogonal(1.0e-5) + } + + fn svd_static_5_2(m: Matrix5x2) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = Matrix2::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, &u * ds * &v_t, epsilon = 1.0e-5) && + u.is_orthogonal(1.0e-5) && + v_t.is_orthogonal(1.0e-5) + } + + fn svd_static_3_5(m: Matrix3x5) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + + let ds = Matrix3::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) + } + + fn svd_static_2_5(m: Matrix2x5) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = Matrix2::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) + } + + fn svd_static_square(m: Matrix4) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = Matrix4::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) && + u.is_orthogonal(1.0e-5) && + v_t.is_orthogonal(1.0e-5) + } + + fn svd_static_square_2x2(m: Matrix2) -> bool { + let svd = m.svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = Matrix2::from_diagonal(&s); + + s.iter().all(|e| *e >= 0.0) && + relative_eq!(m, u * ds * v_t, epsilon = 1.0e-5) && + u.is_orthogonal(1.0e-5) && + v_t.is_orthogonal(1.0e-5) + } + + fn svd_pseudo_inverse(m: DMatrix) -> bool { + if m.len() > 0 { + let svd = m.clone().svd(true, true); + let pinv = svd.pseudo_inverse(1.0e-10); + + if m.nrows() > m.ncols() { + println!("{}", &pinv * &m); + (pinv * m).is_identity(1.0e-5) + } + else { + println!("{}", &m * &pinv); + (m * pinv).is_identity(1.0e-5) + } + } + else { + true + } + } + + fn svd_solve(n: usize, nb: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 10)); + let nb = cmp::min(nb, 10); + let m = DMatrix::::new_random(n, n); + + let svd = m.clone().svd(true, true); + + if svd.rank(1.0e-7) == n { + let b1 = DVector::new_random(n); + let b2 = DMatrix::new_random(n, nb); + + let sol1 = svd.solve(&b1, 1.0e-7); + let sol2 = svd.solve(&b2, 1.0e-7); + + let recomp = svd.recompose(); + if !relative_eq!(m, recomp, epsilon = 1.0e-6) { + println!("{}{}", m, recomp); + } + + if !relative_eq!(&m * &sol1, b1, epsilon = 1.0e-6) { + println!("Problem 1: {:.6}{:.6}", b1, &m * sol1); + return false; + } + if !relative_eq!(&m * &sol2, b2, epsilon = 1.0e-6) { + println!("Problem 2: {:.6}{:.6}", b2, &m * sol2); + return false; + } + } + + true + } +} + +// Test proposed on the issue #176 of rulinalg. +#[test] +fn svd_singular() { + let m = DMatrix::from_row_slice(24, 24, &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, + 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + let svd = m.clone().svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = DMatrix::from_diagonal(&s); + + println!("{:.5}", &u * &ds * &v_t); + + assert!(s.iter().all(|e| *e >= 0.0)); + assert!(u.is_orthogonal(1.0e-5)); + assert!(v_t.is_orthogonal(1.0e-5)); + assert!(relative_eq!(m, &u * ds * &v_t, epsilon = 1.0e-5)); +} + +// Same as the previous test but with one additional row. +#[test] +fn svd_singular_vertical() { + let m = DMatrix::from_row_slice(25, 24, &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, + 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + + let svd = m.clone().svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = DMatrix::from_diagonal(&s); + + assert!(s.iter().all(|e| *e >= 0.0)); + assert!(relative_eq!(m, &u * ds * &v_t, epsilon = 1.0e-5)); +} + +// Same as the previous test but with one additional column. +#[test] +fn svd_singular_horizontal() { + let m = DMatrix::from_row_slice(24, 25, &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, + 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, -4.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + + let svd = m.clone().svd(true, true); + let (u, s, v_t) = (svd.u.unwrap(), svd.singular_values, svd.v_t.unwrap()); + let ds = DMatrix::from_diagonal(&s); + + assert!(s.iter().all(|e| *e >= 0.0)); + assert!(relative_eq!(m, &u * ds * &v_t, epsilon = 1.0e-5)); +} + + +#[test] +fn svd_zeros() { + let m = DMatrix::from_element(10, 10, 0.0); + let svd = m.clone().svd(true, true); + assert_eq!(m, svd.recompose()); +} + +#[test] +fn svd_identity() { + let m = DMatrix::::identity(10, 10); + let svd = m.clone().svd(true, true); + assert_eq!(m, svd.recompose()); + + let m = DMatrix::::identity(10, 15); + let svd = m.clone().svd(true, true); + assert_eq!(m, svd.recompose()); + + let m = DMatrix::::identity(15, 10); + let svd = m.clone().svd(true, true); + assert_eq!(m, svd.recompose()); +} + +#[test] +fn svd_with_delimited_subproblem() { + let mut m = DMatrix::::from_element(10, 10, 0.0); + m[(0,0)] = 1.0; m[(0,1)] = 2.0; + m[(1,1)] = 0.0; m[(1,2)] = 3.0; + m[(2,2)] = 4.0; m[(2,3)] = 5.0; + m[(3,3)] = 6.0; m[(3,4)] = 0.0; + m[(4,4)] = 8.0; m[(3,5)] = 9.0; + m[(5,5)] = 10.0; m[(3,6)] = 11.0; + m[(6,6)] = 12.0; m[(3,7)] = 12.0; + m[(7,7)] = 14.0; m[(3,8)] = 13.0; + m[(8,8)] = 16.0; m[(3,9)] = 17.0; + m[(9,9)] = 18.0; + let svd = m.clone().svd(true, true); + assert!(relative_eq!(m, svd.recompose(), epsilon = 1.0e-7)); + + // Rectangular versions. + let mut m = DMatrix::::from_element(15, 10, 0.0); + m[(0,0)] = 1.0; m[(0,1)] = 2.0; + m[(1,1)] = 0.0; m[(1,2)] = 3.0; + m[(2,2)] = 4.0; m[(2,3)] = 5.0; + m[(3,3)] = 6.0; m[(3,4)] = 0.0; + m[(4,4)] = 8.0; m[(3,5)] = 9.0; + m[(5,5)] = 10.0; m[(3,6)] = 11.0; + m[(6,6)] = 12.0; m[(3,7)] = 12.0; + m[(7,7)] = 14.0; m[(3,8)] = 13.0; + m[(8,8)] = 16.0; m[(3,9)] = 17.0; + m[(9,9)] = 18.0; + let svd = m.clone().svd(true, true); + assert!(relative_eq!(m, svd.recompose(), epsilon = 1.0e-7)); + + let svd = m.transpose().svd(true, true); + assert!(relative_eq!(m.transpose(), svd.recompose(), epsilon = 1.0e-7)); +} + +#[test] +fn svd_fail() { + let m = Matrix6::new( + 0.9299319121545955, 0.9955870335651049, 0.8824725266413644, 0.28966880207132295, 0.06102723649846409, 0.9311880746048009, + 0.5938395242304351, 0.8398522876024204, 0.06672831951963198, 0.9941213119963099, 0.9431846038057834, 0.8159885168706427, + 0.9121962883152357, 0.6471119669367571, 0.4823309702814407, 0.6420516076705516, 0.7731203925207113, 0.7424069470756647, + 0.07311092531259344, 0.5579247949052946, 0.14518764691585773, 0.03502980663114896, 0.7991329455957719, 0.4929930019965745, + 0.12293810556077789, 0.6617084679545999, 0.9002240700227326, 0.027153062135304884, 0.3630189466989524, 0.18207502727558866, + 0.843196731466686, 0.08951878746549924, 0.7533450877576973, 0.009558876499740077, 0.9429679490873482, 0.9355764454129878); + let svd = m.clone().svd(true, true); + println!("Singular values: {}", svd.singular_values); + println!("u: {:.5}", svd.u.unwrap()); + println!("v: {:.5}", svd.v_t.unwrap()); + let recomp = svd.recompose(); + println!("{:.5}{:.5}", m, recomp); + assert!(relative_eq!(m, recomp, epsilon = 1.0e-5)); +} diff --git a/tests/linalg/tridiagonal.rs b/tests/linalg/tridiagonal.rs new file mode 100644 index 00000000..2128d983 --- /dev/null +++ b/tests/linalg/tridiagonal.rs @@ -0,0 +1,35 @@ +use std::cmp; + +use na::{DMatrix, Matrix2, Matrix4}; + + +#[cfg(feature = "arbitrary")] +quickcheck! { + fn symm_tridiagonal(n: usize) -> bool { + let n = cmp::max(1, cmp::min(n, 50)); + let m = DMatrix::::new_random(n, n); + let tri = m.clone().symmetric_tridiagonalize(); + let recomp = tri.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7) + } + + fn symm_tridiagonal_static_square(m: Matrix4) -> bool { + let tri = m.symmetric_tridiagonalize(); + println!("{}{}", tri.internal_tri(), tri.off_diagonal()); + let recomp = tri.recompose(); + + println!("{}{}", m.lower_triangle(), recomp.lower_triangle()); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7) + } + + fn symm_tridiagonal_static_square_2x2(m: Matrix2) -> bool { + let tri = m.symmetric_tridiagonalize(); + let recomp = tri.recompose(); + + relative_eq!(m.lower_triangle(), recomp.lower_triangle(), epsilon = 1.0e-7) + } +}

angle(&self) -> N -///