Merge pull request #1057 from dimforge/dev

Release v0.30.0
This commit is contained in:
Sébastien Crozet 2022-01-02 15:40:03 +01:00 committed by GitHub
commit 7b6f4c6547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 4706 additions and 600 deletions

View File

@ -36,14 +36,20 @@ jobs:
run: cargo build;
- name: Build --features serde-serialize
run: cargo build --features serde-serialize
- name: Build --all-features
run: cargo build --all-features;
- name: Build nalgebra-glm
run: cargo build -p nalgebra-glm --all-features;
- name: Build nalgebra-lapack
run: cd nalgebra-lapack; cargo build;
- name: Build nalgebra-sparse
run: cd nalgebra-sparse; cargo build;
# Run this on its own job because it alone takes a lot of time.
# So its best to let it run in parallel to the other jobs.
build-nalgebra-all-features:
runs-on: ubuntu-latest
steps:
# Needed because the --all-features build which enables cuda support.
- uses: Jimver/cuda-toolkit@v0.2.4
- uses: actions/checkout@v2
- run: cargo build --all-features;
- run: cargo build -p nalgebra-glm --all-features;
test-nalgebra:
runs-on: ubuntu-latest
# env:
@ -65,10 +71,10 @@ jobs:
- name: test nalgebra-sparse
# Manifest-path is necessary because cargo otherwise won't correctly forward features
# We increase number of proptest cases to hopefully catch more potential bugs
run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support
run: PROPTEST_CASES=10000 cargo test --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io
- name: test nalgebra-sparse (slow tests)
# Unfortunately, the "slow-tests" take so much time that we need to run them with --release
run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,slow-tests slow
run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,io,slow-tests slow
test-nalgebra-macros:
runs-on: ubuntu-latest
steps:
@ -106,3 +112,20 @@ jobs:
run: xargo build --verbose --no-default-features --features alloc --target=x86_64-unknown-linux-gnu;
- name: build thumbv7em-none-eabihf
run: xargo build --verbose --no-default-features --target=thumbv7em-none-eabihf;
- name: build x86_64-unknown-linux-gnu nalgebra-glm
run: xargo build --verbose --no-default-features -p nalgebra-glm --target=x86_64-unknown-linux-gnu;
- name: build thumbv7em-none-eabihf nalgebra-glm
run: xargo build --verbose --no-default-features -p nalgebra-glm --target=thumbv7em-none-eabihf;
build-cuda:
runs-on: ubuntu-latest
steps:
- uses: Jimver/cuda-toolkit@v0.2.4
- name: Install nightly-2021-12-04
uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2021-12-04
override: true
- uses: actions/checkout@v2
- run: rustup target add nvptx64-nvidia-cuda
- run: cargo build --no-default-features --features cuda
- run: cargo build --no-default-features --features cuda --target=nvptx64-nvidia-cuda

View File

@ -4,6 +4,52 @@ documented here.
This project adheres to [Semantic Versioning](https://semver.org/).
## [0.30.0] (02 Jan. 2022)
### Breaking changes
- The `Dim` trait is now marked as unsafe.
- The `Matrix::pow` and `Matrix::pow_mut` methods only allow positive integer exponents now. To compute negative
exponents, the user is free to invert the matrix before calling `pow` with the exponents absolute value.
### Modified
- Use more concise debug impls for matrices and geometric transformation types.
- The singular values computed by the SVD are now sorted in increasing order by default. Use `SVD::new_unordered`
instead to reproduce the older behavior without the sorting overhead.
- The `UnitDualQuaternion::sclerp` method will no longer panic when given two equal rotations.
- The `Matrix::select_rows` and `Matrix::select_columns` methods no longer require the matrix components to implement
the trait `Zero`.
- The `Matrix::pow` and `Matrix::pow_mut` methods will now also work with integer matrices.
### Added
- Added the conversion trait `From<Vec<T>>` and method `from_vec_storage` for `RowDVector`.
- Added implementation of `From` and `Into` for converting between `nalgebra` types and types from
`glam 0.18`. These can be enabled by enabling the `convert-glam018` cargo features.
- Added the methods `Matrix::product`, `::row_product`, `::row_product_tr`, and `::column_product` to compute the
product of the components, rows, or columns, of a single matrix or vector.
- The `Default` trait is now implemented for most geometric types: `Point`, `Isometry`, `Rotation`, `Similarity`,
`Transform`, `UnitComplex`, and `UnitQuaternion`.
- Added the `Scale` geometric type for representing non-uniform scaling.
- Added `Cholesky::new_with_substitute` that will replace diagonal elements by a given constant whenever `Cholesky`
meets a non-definite-positiveness.
- Re-added the conversion from a vector/matrix slice to a static array.
- Added the `cuda` feature that enables the support of [rust-cuda](https://github.com/Rust-GPU/Rust-CUDA) for using
`nalgebra` features with CUDA kernels written in Rust.
- Added special-cases implementations for the 2x2 and 3x3 SVDs for better accuracy and performances.
- Added the methods `Matrix::polar`, `Matrix::try_polar`, and `SVD::to_polar` to compute the polar decomposition of
a matrix, based on its SVD.
- `nalgebra-sparse`: provide constructors for unsorted but otherwise valid data using the CSR format.
- `nalgebra-sparse`: added reading MatrixMarked data files to a sparse `CooMatrix`.
### Fixed
- Fixed a potential unsoundness with `matrix.get(i)` and `matrix.get_mut(i)` where `i` is an `usize`, and `matrix`
is a matrix slice with non-default strides.
- Fixed potential unsoundness with `vector.perp` where `vector` isnt actually a 2D vector as expected.
- Fixed linkage issue with `nalgebra-lapack`: the user of `nalgebra-lapack` no longer have to add
`extern crate lapack-src` to their `main.rs`.
- Fixed the `no-std` build of `nalgebra-glm`.
- Fix the `pow` and `pow_mut` functions (the result was incorrect for some exponent values).
## [0.29.0]
### Breaking changes
- We updated to the version 0.6 of `simba`. This means that the trait bounds `T: na::RealField`, `na::ComplexField`,
@ -242,21 +288,21 @@ for details about this switch and its benefits.
geometric types.
### Modified
* Use of traits like `alga::general::{RealField, ComplexField}` have now been replaced by
`simba::scalar::{RealField, ComplexField}`.
`simba::scalar::{RealField, ComplexField}`.
* The implementation of traits from the __alga__ crate (and well as the dependency to _alga__) are now
omitted unless the `alga` cargo feature is activated.
### Removed
* The `Neg` unary operator is no longer implemented for `UnitComplex` and `UnitQuaternion`. This caused
hard-to-track errors when we mistakenly write, e.g., `-q * v` instead of `-(q * v)`.
* The `na::convert_unchecked` is no longer marked as unsafe.
## [0.20.0]
### Added
* `cholesky.rank_one_update(...)` which performs a rank-one update on the cholesky decomposition of a matrix.
* `From<&Matrix>` is now implemented for matrix slices.
* `.try_set_magnitude(...)` which sets the magnitude of a vector, while keeping its direction.
* Implementations of `From` and `Into` for the conversion between matrix slices and standard (`&[N]` `&mut [N]`) slices.
### Modified
* We started some major changes in order to allow non-Copy types to be used as scalar types inside of matrices/vectors.
@ -264,16 +310,16 @@ for details about this switch and its benefits.
### Added
* `.remove_rows_at` and `remove_columns_at` which removes a set of rows or columns (specified by indices) from a matrix.
* Several formatting traits have been implemented for all matrices/vectors: `LowerExp`, `UpperExp`, `Octal`, `LowerHex`,
`UpperHex`, `Binary`, `Pointer`.
`UpperHex`, `Binary`, `Pointer`.
* `UnitQuaternion::quaternions_mean(...)` which computes the mean rotation of a set of unit quaternions. This implements
the algorithm from _Oshman, Yaakov, and Avishy Carmi, "Attitude estimation from vector observations using a genetic-algorithm-embedded quaternion particle filter."
the algorithm from _Oshman, Yaakov, and Avishy Carmi, "Attitude estimation from vector observations using a genetic-algorithm-embedded quaternion particle filter."
### Modified
* It is now possible to get the `min/max` element of unsigned integer matrices.
### Added to nalgebra-glm
* Some infinite and reversed perspectives: `::infinite_perspective_rh_no`, `::infinite_perspective_rh_zo`,
`::reversed_perspective_rh_zo`, and `::reversed_infinite_perspective_rh_zo`.
`::reversed_perspective_rh_zo`, and `::reversed_infinite_perspective_rh_zo`.
## [0.18.0]
This release adds full complex number support to nalgebra. This includes all common vector/matrix operations as well
@ -287,12 +333,12 @@ as matrix decomposition. This excludes geometric type (like `Isometry`, `Rotatio
* Add `.left_div, .right_div` for quaternions.
* Add `.renormalize` to `Unit<...>` and `Rotation3` to correct potential drift due to repeated operations.
Those drifts could cause them not to be pure rotations anymore.
#### Convolution
* `.convolve_full(kernel)` returns the convolution of `self` by `kernel`.
* `.convolve_valid(kernel)` returns the convolution of `self` by `kernel` after removal of all the elements relying on zero-padding.
* `.convolve_same(kernel)` returns the convolution of `self` by `kernel` with a result of the same size as `self`.
#### Complex number support
* Add the `::from_matrix` constructor too all rotation types to extract a rotation from a raw matrix.
* Add the `::from_matrix_eps` constructor too all rotation types to extract a rotation from a raw matrix. This takes
@ -329,22 +375,22 @@ Note that all the other BLAS operation will continue to work for all fields, inc
* Add `.slerp` and `.try_slerp` to unit vectors.
* Add `.lerp` to vectors.
* Add `.to_projective` and `.as_projective` to `Perspective3` and `Orthographic3` in order to
use them as `Projective3` structures.
use them as `Projective3` structures.
* Add `From/Into` impls to allow the conversion of any transformation type to a matrix.
* Add `Into` impls to convert a matrix slice into an owned matrix.
* Add `Point*::from_slice` to create a point from a slice.
* Add `.map_with_location` to matrices to apply a map which passes the component indices to the user-defined closure alongside
the component itself.
the component itself.
* Add impl `From<Vector>` for `Point`.
* Add impl `From<Vector4>` for `Quaternion`.
* Add impl `From<Vector>` for `Translation`.
* Add the `::from_vec` constructor to construct a matrix from a `Vec` (a `DMatrix` will reuse the original `Vec`
as-is for its storage).
as-is for its storage).
* Add `.to_homogeneous` to square matrices (and with dimensions higher than 1x1). This will increase their number of row
and columns by 1. The new column and row are filled with 0, except for the diagonal element which is set to 1.
and columns by 1. The new column and row are filled with 0, except for the diagonal element which is set to 1.
* Implement `Extend<Vec>` for matrices with a dynamic storage. The provided `Vec` is assumed to represent a column-major
matrix with the same number of rows as the one being extended. This will effectively append new columns on the right of
the matrix being extended.
matrix with the same number of rows as the one being extended. This will effectively append new columns on the right of
the matrix being extended.
* Implement `Extend<Vec>` for vectors with a dynamic storage. This will concatenate the vector with the given `Vec`.
* Implement `Extend<Matrix<...>>` for matrices with dynamic storage. This will concatenate the columns of both matrices.
* Implement `Into<Vec>` for the `MatrixVec` storage.
@ -353,10 +399,10 @@ Note that all the other BLAS operation will continue to work for all fields, inc
### Modified
* The orthographic projection no longer require that `bottom < top`, that `left < right`, and that `znear < zfar`. The
only restriction now ith that they must not be equal (in which case the projection would be singular).
only restriction now ith that they must not be equal (in which case the projection would be singular).
* The `Point::from_coordinates` methods is deprecated. Use `Point::from` instead.
* The `.transform_point` and `.transform_vector` methods are now inherent methods for matrices so that the user does not have to
explicitly import the `Transform` trait from the alga crate.
explicitly import the `Transform` trait from the alga crate.
* Renamed the matrix storage types: `MatrixArray` -> `ArrayStorage` and `MatrixVec` -> `VecStorage`.
* Renamed `.unwrap()` to `.into_inner()` for geometric types that wrap another type.
This is for the case of `Unit`, `Transform`, `Orthographic3`, `Perspective3`, `Rotation`.
@ -552,8 +598,8 @@ overview of all the added/modified features.
This version is a major rewrite of the library. Major changes are:
* Algebraic traits are now defined by the [alga](https://crates.io/crates/alga) crate.
All other mathematical traits, except `Axpy` have been removed from
**nalgebra**.
All other mathematical traits, except `Axpy` have been removed from
**nalgebra**.
* Methods are now preferred to free functions because they do not require any
trait to be used anymore.
* Most algebraic entities can be parametrized by type-level integers
@ -746,11 +792,11 @@ you [there](https://users.nphysics.org)!
### Modified
* Vectors are now multipliable with isometries. This will result into a pure rotation (this is how
vectors differ from point semantically: they design directions, so they are not translatable).
vectors differ from point semantically: they design directions, so they are not translatable).
* `{Isometry3, Rotation3}::look_at` reimplemented and renamed to `::look_at_rh` and `::look_at_lh` to agree
with the computer graphics community (in particular, the GLM library). Use the `::look_at_rh`
variant to build a view matrix that
may be successfully used with `Persp` and `Ortho`.
with the computer graphics community (in particular, the GLM library). Use the `::look_at_rh`
variant to build a view matrix that
may be successfully used with `Persp` and `Ortho`.
* The old `{Isometry3, Rotation3}::look_at` implementations are now called `::new_observer_frame`.
* Rename every `fov` on `Persp` to `fovy`.
* Fixed the perspective and orthographic projection matrices.

View File

@ -1,6 +1,6 @@
[package]
name = "nalgebra"
version = "0.29.0"
version = "0.30.0"
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
description = "General-purpose linear algebra library with transformations and statically-sized or dynamically-sized matrices."
@ -32,6 +32,7 @@ compare = [ "matrixcompare-core" ]
libm = [ "simba/libm" ]
libm-force = [ "simba/libm_force" ]
macros = [ "nalgebra-macros" ]
cuda = [ "cust", "simba/cuda" ]
# Conversion
convert-mint = [ "mint" ]
@ -41,6 +42,7 @@ convert-glam014 = [ "glam014" ]
convert-glam015 = [ "glam015" ]
convert-glam016 = [ "glam016" ]
convert-glam017 = [ "glam017" ]
convert-glam018 = [ "glam018" ]
# Serialization
## To use serde in a #[no-std] environment, enable the
@ -72,7 +74,7 @@ num-traits = { version = "0.2", default-features = false }
num-complex = { version = "0.4", default-features = false }
num-rational = { version = "0.4", default-features = false }
approx = { version = "0.5", default-features = false }
simba = { version = "0.6", default-features = false }
simba = { version = "0.7", default-features = false }
alga = { version = "0.9", default-features = false, optional = true }
rand_distr = { version = "0.4", default-features = false, optional = true }
matrixmultiply = { version = "0.3", optional = true }
@ -85,12 +87,16 @@ pest = { version = "2", optional = true }
pest_derive = { version = "2", optional = true }
bytemuck = { version = "1.5", optional = true }
matrixcompare-core = { version = "0.1", optional = true }
proptest = { version = "1", optional = true, default-features = false, features = ["std"] }
proptest = { version = "1", optional = true, default-features = false, features = ["std"] }
glam013 = { package = "glam", version = "0.13", optional = true }
glam014 = { package = "glam", version = "0.14", optional = true }
glam015 = { package = "glam", version = "0.15", optional = true }
glam016 = { package = "glam", version = "0.16", optional = true }
glam017 = { package = "glam", version = "0.17", optional = true }
glam018 = { package = "glam", version = "0.18", optional = true }
[target.'cfg(not(target_os = "cuda"))'.dependencies]
cust = { version = "0.2", optional = true }
[dev-dependencies]
@ -127,3 +133,4 @@ lto = true
[package.metadata.docs.rs]
# Enable certain features when building docs for docs.rs
features = [ "proptest-support", "compare", "macros", "rand" ]

View File

@ -1,12 +0,0 @@
all:
cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare"
# cargo check --features "debug arbitrary serde-serialize"
doc:
cargo doc --no-deps --features "debug arbitrary serde-serialize abomonation"
bench:
cargo bench
test:
cargo test --features "debug arbitrary serde-serialize abomonation-serialize compare"

View File

@ -9,7 +9,7 @@
<img src="https://circleci.com/gh/dimforge/nalgebra.svg?style=svg" alt="Build status">
</a>
<a href="https://crates.io/crates/nalgebra">
<img src="https://meritbadge.herokuapp.com/nalgebra?style=flat-square" alt="crates.io">
<img src="https://img.shields.io/crates/v/nalgebra.svg?style=flat-square" alt="crates.io">
</a>
<a href="https://opensource.org/licenses/Apache-2.0">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg">
@ -30,10 +30,18 @@
-----
## Platinum sponsors
Rapier is supported by:
## Acknowledgements
nalgebra is supported by our **platinum** sponsors:
<p>
<a href="https://embark-studios.com">
<img src="https://www.embark.dev/img/logo_black.png" width="401px">
<img src="https://www.embark.dev/img/logo_black.png" width="301px">
</a>
</p>
And our gold sponsors:
<p>
<a href="https://fragcolor.com">
<img src="https://dimforge.com/img/fragcolor_logo1_color_black.svg" width="151px">
</a>
</p>

View File

@ -1,4 +1,18 @@
use na::{Matrix4, SVD};
use na::{Matrix2, Matrix3, Matrix4, SVD};
fn svd_decompose_2x2_f32(bh: &mut criterion::Criterion) {
let m = Matrix2::<f32>::new_random();
bh.bench_function("svd_decompose_2x2", move |bh| {
bh.iter(|| std::hint::black_box(SVD::new_unordered(m.clone(), true, true)))
});
}
fn svd_decompose_3x3_f32(bh: &mut criterion::Criterion) {
let m = Matrix3::<f32>::new_random();
bh.bench_function("svd_decompose_3x3", move |bh| {
bh.iter(|| std::hint::black_box(SVD::new_unordered(m.clone(), true, true)))
});
}
fn svd_decompose_4x4(bh: &mut criterion::Criterion) {
let m = Matrix4::<f64>::new_random();
@ -114,6 +128,8 @@ fn pseudo_inverse_200x200(bh: &mut criterion::Criterion) {
criterion_group!(
svd,
svd_decompose_2x2_f32,
svd_decompose_3x3_f32,
svd_decompose_4x4,
svd_decompose_10x10,
svd_decompose_100x100,

View File

@ -4,7 +4,7 @@ version = "0.0.0"
authors = [ "You" ]
[dependencies]
nalgebra = "0.29.0"
nalgebra = "0.30.0"
[[bin]]
name = "example"

View File

@ -3,6 +3,7 @@
extern crate nalgebra as na;
use na::{Isometry3, Perspective3, Point3, Vector3};
use std::f32::consts;
fn main() {
// Our object is translated along the x axis.
@ -15,7 +16,7 @@ fn main() {
let view = Isometry3::look_at_rh(&eye, &target, &Vector3::y());
// A perspective projection.
let projection = Perspective3::new(16.0 / 9.0, 3.14 / 2.0, 1.0, 1000.0);
let projection = Perspective3::new(16.0 / 9.0, consts::PI / 2.0, 1.0, 1000.0);
// The combination of the model with the view is still an isometry.
let model_view = view * model;

View File

@ -19,6 +19,7 @@ fn main() {
/* Then pass the raw pointers to some graphics API. */
#[allow(clippy::float_cmp)]
unsafe {
assert_eq!(*v_pointer, 1.0);
assert_eq!(*v_pointer.offset(1), 0.0);

View File

@ -3,9 +3,10 @@
extern crate nalgebra as na;
use na::{Perspective3, Point2, Point3, Unit};
use std::f32::consts;
fn main() {
let projection = Perspective3::new(800.0 / 600.0, 3.14 / 2.0, 1.0, 1000.0);
let projection = Perspective3::new(800.0 / 600.0, consts::PI / 2.0, 1.0, 1000.0);
let screen_point = Point2::new(10.0f32, 20.0);
// Compute two points in clip-space.

View File

@ -1,6 +1,7 @@
extern crate nalgebra as na;
use na::{Isometry2, Similarity2, Vector2};
use std::f32::consts;
fn main() {
// Isometry -> Similarity conversion always succeeds.
@ -8,8 +9,8 @@ fn main() {
let _: Similarity2<f32> = na::convert(iso);
// Similarity -> Isometry conversion fails if the scaling factor is not 1.0.
let sim_without_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), 3.14, 1.0);
let sim_with_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), 3.14, 2.0);
let sim_without_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), consts::PI, 1.0);
let sim_with_scaling = Similarity2::new(Vector2::new(1.0f32, 2.0), consts::PI, 2.0);
let iso_success: Option<Isometry2<f32>> = na::try_convert(sim_without_scaling);
let iso_fail: Option<Isometry2<f32>> = na::try_convert(sim_with_scaling);

View File

@ -3,6 +3,7 @@ extern crate approx;
extern crate nalgebra as na;
use na::{Matrix4, Point3, Vector3};
use std::f32::consts;
fn main() {
// Create a uniform scaling matrix with scaling factor 2.
@ -28,7 +29,7 @@ fn main() {
);
// Create rotation.
let rot = Matrix4::from_scaled_axis(&Vector3::x() * 3.14);
let rot = Matrix4::from_scaled_axis(Vector3::x() * consts::PI);
let rot_then_m = m * rot; // Right-multiplication is equivalent to prepending `rot` to `m`.
let m_then_rot = rot * m; // Left-multiplication is equivalent to appending `rot` to `m`.

View File

@ -12,6 +12,7 @@ fn main() {
/* Then pass the raw pointer to some graphics API. */
#[allow(clippy::float_cmp)]
unsafe {
assert_eq!(*iso_pointer, 1.0);
assert_eq!(*iso_pointer.offset(5), 1.0);

View File

@ -1,3 +1,4 @@
#![allow(clippy::float_cmp)]
extern crate nalgebra as na;
use na::{Unit, Vector3};

View File

@ -1,6 +1,6 @@
[package]
name = "nalgebra-glm"
version = "0.15.0"
version = "0.16.0"
authors = ["sebcrozet <developer@crozet.re>"]
description = "A computer-graphics oriented API for nalgebra, inspired by the C++ GLM library."
@ -22,9 +22,20 @@ std = [ "nalgebra/std", "simba/std" ]
arbitrary = [ "nalgebra/arbitrary" ]
serde-serialize = [ "nalgebra/serde-serialize-no-std" ]
abomonation-serialize = [ "nalgebra/abomonation-serialize" ]
cuda = [ "nalgebra/cuda" ]
# Conversion
convert-mint = [ "nalgebra/mint" ]
convert-bytemuck = [ "nalgebra/bytemuck" ]
convert-glam013 = [ "nalgebra/glam013" ]
convert-glam014 = [ "nalgebra/glam014" ]
convert-glam015 = [ "nalgebra/glam015" ]
convert-glam016 = [ "nalgebra/glam016" ]
convert-glam017 = [ "nalgebra/glam017" ]
convert-glam018 = [ "nalgebra/glam018" ]
[dependencies]
num-traits = { version = "0.2", default-features = false }
approx = { version = "0.5", default-features = false }
simba = { version = "0.6", default-features = false }
nalgebra = { path = "..", version = "0.29", default-features = false }
simba = { version = "0.7", default-features = false }
nalgebra = { path = "..", version = "0.30", default-features = false }

View File

@ -75,6 +75,21 @@ pub fn mat2x4<T: Scalar>(m11: T, m12: T, m13: T, m14: T,
}
/// Create a new 3x3 matrix.
///
/// # Example
/// ```
/// # use nalgebra_glm::mat3;
/// let m = mat3(
/// 1.0, 2.0, 3.0,
/// 4.0, 5.0, 6.0,
/// 7.0, 8.0, 9.0
/// );
/// assert!(
/// m.m11 == 1.0 && m.m12 == 2.0 && m.m13 == 3.0 &&
/// m.m21 == 4.0 && m.m22 == 5.0 && m.m23 == 6.0 &&
/// m.m31 == 7.0 && m.m32 == 8.0 && m.m33 == 9.0
/// );
/// ```
#[rustfmt::skip]
pub fn mat3<T: Scalar>(m11: T, m12: T, m13: T,
m21: T, m22: T, m23: T,
@ -105,8 +120,8 @@ pub fn mat3x3<T: Scalar>(m11: T, m12: T, m13: T,
m31: T, m32: T, m33: T) -> TMat3<T> {
TMat::<T, 3, 3>::new(
m11, m12, m13,
m31, m32, m33,
m21, m22, m23,
m31, m32, m33,
)
}

View File

@ -1,9 +1,9 @@
use approx::AbsDiffEq;
use num::{Bounded, Signed};
use core::cmp::PartialOrd;
use na::Scalar;
use simba::scalar::{ClosedAdd, ClosedMul, ClosedSub, RealField};
use std::cmp::PartialOrd;
/// A number that can either be an integer or a float.
pub trait Number:

View File

@ -53,7 +53,7 @@ pub fn degrees<T: RealNumber, const D: usize>(radians: &TVec<T, D>) -> TVec<T, D
radians.map(|e| e * na::convert(180.0) / T::pi())
}
/// Component-wise conversion fro degrees to radians.
/// Component-wise conversion from degrees to radians.
pub fn radians<T: RealNumber, const D: usize>(degrees: &TVec<T, D>) -> TVec<T, D> {
degrees.map(|e| e * T::pi() / na::convert(180.0))
}

View File

@ -1,6 +1,6 @@
[package]
name = "nalgebra-lapack"
version = "0.20.0"
version = "0.21.0"
authors = [ "Sébastien Crozet <developer@crozet.re>", "Andrew Straw <strawman@astraw.com>" ]
description = "Matrix decompositions using nalgebra matrices and Lapack bindings."
@ -29,17 +29,17 @@ accelerate = ["lapack-src/accelerate"]
intel-mkl = ["lapack-src/intel-mkl"]
[dependencies]
nalgebra = { version = "0.29", path = ".." }
nalgebra = { version = "0.30", path = ".." }
num-traits = "0.2"
num-complex = { version = "0.4", default-features = false }
simba = "0.5"
simba = "0.7"
serde = { version = "1.0", features = [ "derive" ], optional = true }
lapack = { version = "0.19", default-features = false }
lapack-src = { version = "0.8", default-features = false }
# clippy = "*"
[dev-dependencies]
nalgebra = { version = "0.29", features = [ "arbitrary", "rand" ], path = ".." }
nalgebra = { version = "0.30", features = [ "arbitrary", "rand" ], path = ".." }
proptest = { version = "1", default-features = false, features = ["std"] }
quickcheck = "1"
approx = "0.5"

View File

@ -294,7 +294,7 @@ where
let mut res = Matrix::zeros_generic(nrows, Const::<1>);
for i in 0..res.len() {
res[i] = Complex::new(wr[i], wi[i]);
res[i] = Complex::new(wr[i].clone(), wi[i].clone());
}
res
@ -306,7 +306,7 @@ where
pub fn determinant(&self) -> T {
let mut det = T::one();
for e in self.eigenvalues.iter() {
det *= *e;
det *= e.clone();
}
det

View File

@ -73,6 +73,9 @@
html_root_url = "https://nalgebra.org/rustdoc"
)]
extern crate lapack;
extern crate lapack_src;
extern crate nalgebra as na;
extern crate num_traits as num;

View File

@ -155,7 +155,7 @@ where
let mut out = Matrix::zeros_generic(self.t.shape_generic().0, Const::<1>);
for i in 0..out.len() {
out[i] = Complex::new(self.re[i], self.im[i])
out[i] = Complex::new(self.re[i].clone(), self.im[i].clone())
}
out

View File

@ -140,7 +140,7 @@ where
pub fn determinant(&self) -> T {
let mut det = T::one();
for e in self.eigenvalues.iter() {
det *= *e;
det *= e.clone();
}
det
@ -153,7 +153,7 @@ where
pub fn recompose(&self) -> OMatrix<T, D, D> {
let mut u_t = self.eigenvectors.clone();
for i in 0..self.eigenvalues.len() {
let val = self.eigenvalues[i];
let val = self.eigenvalues[i].clone();
u_t.column_mut(i).mul_assign(val);
}
u_t.transpose_mut();

View File

@ -6,9 +6,6 @@ compile_error!("Tests must be run with `proptest-support`");
extern crate nalgebra as na;
extern crate nalgebra_lapack as nl;
extern crate lapack;
extern crate lapack_src;
mod linalg;
#[path = "../../tests/proptest/mod.rs"]
mod proptest;

View File

@ -21,5 +21,5 @@ quote = "1.0"
proc-macro2 = "1.0"
[dev-dependencies]
nalgebra = { version = "0.29.0", path = ".." }
nalgebra = { version = "0.30.0", path = ".." }
trybuild = "1.0.42"

View File

@ -1,6 +1,6 @@
[package]
name = "nalgebra-sparse"
version = "0.5.0"
version = "0.6.0"
authors = [ "Andreas Longva", "Sébastien Crozet <developer@crozet.re>" ]
edition = "2018"
description = "Sparse matrix computation based on nalgebra."
@ -16,19 +16,24 @@ license = "Apache-2.0"
proptest-support = ["proptest", "nalgebra/proptest-support"]
compare = [ "matrixcompare-core" ]
# Enable matrix market I/O
io = [ "pest", "pest_derive" ]
# Enable to enable running some tests that take a lot of time to run
slow-tests = []
[dependencies]
nalgebra = { version="0.29", path = "../" }
nalgebra = { version="0.30", path = "../" }
num-traits = { version = "0.2", default-features = false }
proptest = { version = "1.0", optional = true }
matrixcompare-core = { version = "0.1.0", optional = true }
pest = { version = "2", optional = true }
pest_derive = { version = "2", optional = true }
[dev-dependencies]
itertools = "0.10"
matrixcompare = { version = "0.3.0", features = [ "proptest-support" ] }
nalgebra = { version="0.29", path = "../", features = ["compare"] }
nalgebra = { version="0.30", path = "../", features = ["compare"] }
[package.metadata.docs.rs]
# Enable certain features when building docs for docs.rs

View File

@ -10,6 +10,7 @@ use crate::{SparseEntry, SparseEntryMut, SparseFormatError, SparseFormatErrorKin
use nalgebra::Scalar;
use num_traits::One;
use std::iter::FromIterator;
use std::slice::{Iter, IterMut};
/// A CSR representation of a sparse matrix.
@ -170,6 +171,77 @@ impl<T> CsrMatrix<T> {
Self::try_from_pattern_and_values(pattern, values)
}
/// Try to construct a CSR matrix from raw CSR data with unsorted column indices.
///
/// It is assumed that each row contains unique column indices that are in
/// bounds with respect to the number of columns in the matrix. If this is not the case,
/// an error is returned to indicate the failure.
///
/// An error is returned if the data given does not conform to the CSR storage format
/// with the exception of having unsorted column indices and values.
/// See the documentation for [CsrMatrix](struct.CsrMatrix.html) for more information.
pub fn try_from_unsorted_csr_data(
num_rows: usize,
num_cols: usize,
row_offsets: Vec<usize>,
col_indices: Vec<usize>,
values: Vec<T>,
) -> Result<Self, SparseFormatError>
where
T: Scalar,
{
use SparsityPatternFormatError::*;
let count = col_indices.len();
let mut p: Vec<usize> = (0..count).collect();
if col_indices.len() != values.len() {
return Err(SparseFormatError::from_kind_and_msg(
SparseFormatErrorKind::InvalidStructure,
"Number of values and column indices must be the same",
));
}
if row_offsets.len() == 0 {
return Err(SparseFormatError::from_kind_and_msg(
SparseFormatErrorKind::InvalidStructure,
"Number of offsets should be greater than 0",
));
}
for (index, &offset) in row_offsets[0..row_offsets.len() - 1].iter().enumerate() {
let next_offset = row_offsets[index + 1];
if next_offset > count {
return Err(SparseFormatError::from_kind_and_msg(
SparseFormatErrorKind::InvalidStructure,
"No row offset should be greater than the number of column indices",
));
}
if offset > next_offset {
return Err(NonmonotonicOffsets).map_err(pattern_format_error_to_csr_error);
}
p[offset..next_offset].sort_by(|a, b| {
let x = &col_indices[*a];
let y = &col_indices[*b];
x.partial_cmp(y).unwrap()
});
}
// permute indices
let sorted_col_indices: Vec<usize> =
Vec::from_iter((p.iter().map(|i| &col_indices[*i])).cloned());
// permute values
let sorted_values: Vec<T> = Vec::from_iter((p.iter().map(|i| &values[*i])).cloned());
return Self::try_from_csr_data(
num_rows,
num_cols,
row_offsets,
sorted_col_indices,
sorted_values,
);
}
/// Try to construct a CSR matrix from a sparsity pattern and associated non-zero values.
///
/// Returns an error if the number of values does not match the number of minor indices

View File

@ -0,0 +1,53 @@
WHITESPACE = _{ " "|"\t" }
//
Sparsity = {^"coordinate" | ^"array"}
DataType = {^"real" | ^"complex" | ^"pattern" | ^"integer" }
StorageScheme = {^"symmetric" | ^"general" | ^"skew-symmetric" | ^"hermitian"}
// Only consider matrices here.
Header = { ^"%%matrixmarket matrix" ~ Sparsity ~ DataType ~ StorageScheme }
//
Comments = _{ "%" ~ (!NEWLINE ~ ANY)* }
//
Dimension = @{ ASCII_DIGIT+ }
SparseShape = { Dimension ~ Dimension ~ Dimension}
DenseShape = { Dimension ~ Dimension}
Shape = {SparseShape | DenseShape }
//
// grammar from https://doc.rust-lang.org/std/primitive.f64.html#grammar
Sign = {("+" | "-")}
Exp = @{ ^"e" ~ Sign? ~ ASCII_DIGIT+}
Number = @{ ((ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT*) | (ASCII_DIGIT* ~ "." ~ASCII_DIGIT+) | ASCII_DIGIT+ ) ~ Exp? }
Real = @{ Sign? ~ ("inf" | "NaN" | Number) }
SparseReal = {Dimension~ Dimension~ Real }
SparseComplex = {Dimension ~ Dimension ~ Real ~ Real}
SparsePattern = {Dimension ~ Dimension}
DenseReal = {Real}
DenseComplex = {Real ~ Real}
Entry = { SparseComplex | SparseReal | SparsePattern | DenseComplex | DenseReal }
// end of file, a silent way, see https://github.com/pest-parser/pest/issues/304#issuecomment-427198507
eoi = _{ !ANY }
Document = {
SOI ~
NEWLINE* ~
Header ~
(NEWLINE ~ Comments)* ~
(NEWLINE ~ Shape) ~
(NEWLINE ~ Entry?)* ~
eoi
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
//! Functionality for importing and exporting sparse matrices to and from files.
//!
//! **Available only when the `io` feature is enabled.**
//!
//! The following formats are currently supported:
//!
//! | Format | Import | Export |
//! | ------------------------------------------------|------------|------------|
//! | [Matrix market](#matrix-market-format) | Yes | No |
//!
//! [Matrix market]: https://math.nist.gov/MatrixMarket/formats.html
//!
//! ## Matrix Market format
//!
//! The Matrix Market format is a simple ASCII-based file format for sparse matrices, and was initially developed for
//! the [NIST Matrix Market](https://math.nist.gov/MatrixMarket/), a repository of example sparse matrices.
//! In later years it has largely been superseded by the
//! [SuiteSparse Matrix Collection](https://sparse.tamu.edu/) (formerly University of Florida Sparse Matrix Collection),
//! which also uses the Matrix Market file format.
//!
//! We currently offer functionality for importing a Matrix market file to an instance of a
//! [CooMatrix](crate::CooMatrix) through the function [load_coo_from_matrix_market_file]. It is also possible to load
//! a matrix stored in the matrix market format with the function [load_coo_from_matrix_market_str].
//!
//! Export is currently not implemented, but [planned](https://github.com/dimforge/nalgebra/issues/1037).
//!
//! Our implementation is based on the [format description](https://math.nist.gov/MatrixMarket/formats.html)
//! on the Matrix Market website and the
//! [following NIST whitepaper](https://math.nist.gov/MatrixMarket/reports/MMformat.ps):
//!
//! > Boisvert, Ronald F., Roldan Pozo, and Karin A. Remington.<br/>
//! > "*The Matrix Market Exchange Formats: Initial Design.*" (1996).
pub use self::matrix_market::{
load_coo_from_matrix_market_file, load_coo_from_matrix_market_str, MatrixMarketError,
MatrixMarketErrorKind, MatrixMarketScalar,
};
mod matrix_market;

View File

@ -19,6 +19,7 @@
//! - Sparsity patterns in CSR and CSC matrices are explicitly represented by the
//! [SparsityPattern](pattern::SparsityPattern) type, which encodes the invariants of the
//! associated index data structures.
//! - [Matrix market format support](`io`) when the `io` feature is enabled.
//! - [proptest strategies](`proptest`) for sparse matrices when the feature
//! `proptest-support` is enabled.
//! - [matrixcompare support](https://crates.io/crates/matrixcompare) for effortless
@ -142,11 +143,19 @@
)]
pub extern crate nalgebra as na;
#[cfg(feature = "io")]
extern crate pest;
#[macro_use]
#[cfg(feature = "io")]
extern crate pest_derive;
pub mod convert;
pub mod coo;
pub mod csc;
pub mod csr;
pub mod factorization;
#[cfg(feature = "io")]
pub mod io;
pub mod ops;
pub mod pattern;

View File

@ -67,7 +67,7 @@
//! As can be seen from the table, only `CSR * Dense` and `CSC * Dense` are supported.
//! The other way around, i.e. `Dense * CSR` and `Dense * CSC` are not implemented.
//!
//! Additionally, [CsrMatrix](`crate::csr::CsrMatrix`) and [CooMatrix](`crate::coo::CooMatrix`)
//! Additionally, [CsrMatrix](`crate::csr::CsrMatrix`) and [CscMatrix](`crate::csc::CscMatrix`)
//! support multiplication with scalars, in addition to division by a scalar.
//! Note that only `Matrix * Scalar` works in a generic context, although `Scalar * Matrix`
//! has been implemented for many of the built-in arithmetic types. This is due to a fundamental

View File

@ -5,11 +5,6 @@
//! The strategies provided here are generally expected to be able to generate the entire range
//! of possible outputs given the constraints on dimensions and values. However, there are no
//! particular guarantees on the distribution of possible values.
// Contains some patched code from proptest that we can remove in the (hopefully near) future.
// See docs in file for more details.
mod proptest_patched;
use crate::coo::CooMatrix;
use crate::csc::CscMatrix;
use crate::csr::CsrMatrix;
@ -31,16 +26,10 @@ fn dense_row_major_coord_strategy(
let mut booleans = vec![true; nnz];
booleans.append(&mut vec![false; (nrows * ncols) - nnz]);
// Make sure that exactly `nnz` of the booleans are true
// TODO: We cannot use the below code because of a bug in proptest, see
// https://github.com/AltSysrq/proptest/pull/217
// so for now we're using a patched version of the Shuffle adapter
// (see also docs in `proptest_patched`
// Just(booleans)
// // Need to shuffle to make sure they are randomly distributed
// .prop_shuffle()
proptest_patched::Shuffle(Just(booleans)).prop_map(move |booleans| {
Just(booleans)
// Need to shuffle to make sure they are randomly distributed
.prop_shuffle()
.prop_map(move |booleans| {
booleans
.into_iter()
.enumerate()

View File

@ -1,146 +0,0 @@
//! Contains a modified implementation of `proptest::strategy::Shuffle`.
//!
//! The current implementation in `proptest` does not generate all permutations, which is
//! problematic for our proptest generators. The issue has been fixed in
//! https://github.com/AltSysrq/proptest/pull/217
//! but it has yet to be merged and released. As soon as this fix makes it into a new release,
//! the modified code here can be removed.
//!
/*!
This code has been copied and adapted from
https://github.com/AltSysrq/proptest/blob/master/proptest/src/strategy/shuffle.rs
The original licensing text is:
//-
// Copyright 2017 Jason Lingle
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
*/
use proptest::num;
use proptest::prelude::Rng;
use proptest::strategy::{NewTree, Shuffleable, Strategy, ValueTree};
use proptest::test_runner::{TestRng, TestRunner};
use std::cell::Cell;
#[derive(Clone, Debug)]
#[must_use = "strategies do nothing unless used"]
pub struct Shuffle<S>(pub(super) S);
impl<S: Strategy> Strategy for Shuffle<S>
where
S::Value: Shuffleable,
{
type Tree = ShuffleValueTree<S::Tree>;
type Value = S::Value;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let rng = runner.new_rng();
self.0.new_tree(runner).map(|inner| ShuffleValueTree {
inner,
rng,
dist: Cell::new(None),
simplifying_inner: false,
})
}
}
#[derive(Clone, Debug)]
pub struct ShuffleValueTree<V> {
inner: V,
rng: TestRng,
dist: Cell<Option<num::usize::BinarySearch>>,
simplifying_inner: bool,
}
impl<V: ValueTree> ShuffleValueTree<V>
where
V::Value: Shuffleable,
{
fn init_dist(&self, dflt: usize) -> usize {
if self.dist.get().is_none() {
self.dist.set(Some(num::usize::BinarySearch::new(dflt)));
}
self.dist.get().unwrap().current()
}
fn force_init_dist(&self) {
if self.dist.get().is_none() {
let _ = self.init_dist(self.current().shuffle_len());
}
}
}
impl<V: ValueTree> ValueTree for ShuffleValueTree<V>
where
V::Value: Shuffleable,
{
type Value = V::Value;
fn current(&self) -> V::Value {
let mut value = self.inner.current();
let len = value.shuffle_len();
// The maximum distance to swap elements. This could be larger than
// `value` if `value` has reduced size during shrinking; that's OK,
// since we only use this to filter swaps.
let max_swap = self.init_dist(len);
// If empty collection or all swaps will be filtered out, there's
// nothing to shuffle.
if 0 == len || 0 == max_swap {
return value;
}
let mut rng = self.rng.clone();
for start_index in 0..len - 1 {
// Determine the other index to be swapped, then skip the swap if
// it is too far. This ordering is critical, as it ensures that we
// generate the same sequence of random numbers every time.
// NOTE: The below line is the whole reason for the existence of this adapted code
// We need to be able to swap with the same element, so that some elements remain in
// place rather being swapped
// let end_index = rng.gen_range(start_index + 1..len);
let end_index = rng.gen_range(start_index..len);
if end_index - start_index <= max_swap {
value.shuffle_swap(start_index, end_index);
}
}
value
}
fn simplify(&mut self) -> bool {
if self.simplifying_inner {
self.inner.simplify()
} else {
// Ensure that we've initialised `dist` to *something* to give
// consistent non-panicking behaviour even if called in an
// unexpected sequence.
self.force_init_dist();
if self.dist.get_mut().as_mut().unwrap().simplify() {
true
} else {
self.simplifying_inner = true;
self.inner.simplify()
}
}
}
fn complicate(&mut self) -> bool {
if self.simplifying_inner {
self.inner.complicate()
} else {
self.force_init_dist();
self.dist.get_mut().as_mut().unwrap().complicate()
}
}
}

View File

@ -1,6 +1,9 @@
//! Unit tests
#[cfg(any(not(feature = "proptest-support"), not(feature = "compare")))]
compile_error!("Tests must be run with features `proptest-support` and `compare`");
#[cfg(not(all(feature = "proptest-support", feature = "compare", feature = "io",)))]
compile_error!(
"Please enable the `proptest-support`, `compare` and `io` features in order to compile and run the tests.
Example: `cargo test -p nalgebra-sparse --features proptest-support,compare,io`"
);
mod unit_tests;

View File

@ -5,6 +5,8 @@ use nalgebra_sparse::{SparseEntry, SparseEntryMut, SparseFormatErrorKind};
use proptest::prelude::*;
use proptest::sample::subsequence;
use super::test_data_examples::InvalidCsrDataExamples;
use crate::assert_panics;
use crate::common::csr_strategy;
@ -171,11 +173,36 @@ fn csr_matrix_valid_data() {
}
}
#[test]
fn csr_matrix_valid_data_unsorted_column_indices() {
let csr = CsrMatrix::try_from_unsorted_csr_data(
4,
5,
vec![0, 3, 5, 8, 11],
vec![4, 1, 3, 3, 1, 2, 3, 0, 3, 4, 1],
vec![5, 1, 4, 7, 4, 2, 3, 1, 8, 9, 6],
)
.unwrap();
let expected_csr = CsrMatrix::try_from_csr_data(
4,
5,
vec![0, 3, 5, 8, 11],
vec![1, 3, 4, 1, 3, 0, 2, 3, 1, 3, 4],
vec![1, 4, 5, 4, 7, 1, 2, 3, 6, 8, 9],
)
.unwrap();
assert_eq!(csr, expected_csr);
}
#[test]
fn csr_matrix_try_from_invalid_csr_data() {
let invalid_data: InvalidCsrDataExamples = InvalidCsrDataExamples::new();
{
// Empty offset array (invalid length)
let matrix = CsrMatrix::try_from_csr_data(0, 0, Vec::new(), Vec::new(), Vec::<u32>::new());
let (offsets, indices, values) = invalid_data.empty_offset_array;
let matrix = CsrMatrix::try_from_csr_data(0, 0, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
@ -184,10 +211,8 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Offset array invalid length for arbitrary data
let offsets = vec![0, 3, 5];
let indices = vec![0, 1, 2, 3, 5];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) =
invalid_data.offset_array_invalid_length_for_arbitrary_data;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -197,9 +222,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Invalid first entry in offsets array
let offsets = vec![1, 2, 2, 5];
let indices = vec![0, 5, 1, 2, 3];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.invalid_first_entry_in_offsets_array;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -209,9 +232,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Invalid last entry in offsets array
let offsets = vec![0, 2, 2, 4];
let indices = vec![0, 5, 1, 2, 3];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.invalid_last_entry_in_offsets_array;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -221,9 +242,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Invalid length of offsets array
let offsets = vec![0, 2, 2];
let indices = vec![0, 5, 1, 2, 3];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.invalid_length_of_offsets_array;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -233,9 +252,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Nonmonotonic offsets
let offsets = vec![0, 3, 2, 5];
let indices = vec![0, 1, 2, 3, 4];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.nonmonotonic_offsets;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -245,9 +262,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Nonmonotonic minor indices
let offsets = vec![0, 2, 2, 5];
let indices = vec![0, 2, 3, 1, 4];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.nonmonotonic_minor_indices;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -257,9 +272,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Minor index out of bounds
let offsets = vec![0, 2, 2, 5];
let indices = vec![0, 6, 1, 2, 3];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.minor_index_out_of_bounds;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -269,9 +282,7 @@ fn csr_matrix_try_from_invalid_csr_data() {
{
// Duplicate entry
let offsets = vec![0, 2, 2, 5];
let indices = vec![0, 5, 2, 2, 3];
let values = vec![0, 1, 2, 3, 4];
let (offsets, indices, values) = invalid_data.duplicate_entry;
let matrix = CsrMatrix::try_from_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
@ -280,6 +291,91 @@ fn csr_matrix_try_from_invalid_csr_data() {
}
}
#[test]
fn csr_matrix_try_from_unsorted_invalid_csr_data() {
let invalid_data: InvalidCsrDataExamples = InvalidCsrDataExamples::new();
{
// Empty offset array (invalid length)
let (offsets, indices, values) = invalid_data.empty_offset_array;
let matrix = CsrMatrix::try_from_unsorted_csr_data(0, 0, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Offset array invalid length for arbitrary data
let (offsets, indices, values) =
invalid_data.offset_array_invalid_length_for_arbitrary_data;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Invalid first entry in offsets array
let (offsets, indices, values) = invalid_data.invalid_first_entry_in_offsets_array;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Invalid last entry in offsets array
let (offsets, indices, values) = invalid_data.invalid_last_entry_in_offsets_array;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Invalid length of offsets array
let (offsets, indices, values) = invalid_data.invalid_length_of_offsets_array;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Nonmonotonic offsets
let (offsets, indices, values) = invalid_data.nonmonotonic_offsets;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::InvalidStructure
);
}
{
// Minor index out of bounds
let (offsets, indices, values) = invalid_data.minor_index_out_of_bounds;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::IndexOutOfBounds
);
}
{
// Duplicate entry
let (offsets, indices, values) = invalid_data.duplicate_entry;
let matrix = CsrMatrix::try_from_unsorted_csr_data(3, 6, offsets, indices, values);
assert_eq!(
matrix.unwrap_err().kind(),
&SparseFormatErrorKind::DuplicateEntry
);
}
}
#[test]
fn csr_disassemble_avoids_clone_when_owned() {
// Test that disassemble avoids cloning the sparsity pattern when it holds the sole reference

View File

@ -0,0 +1,345 @@
use matrixcompare::assert_matrix_eq;
use nalgebra::dmatrix;
use nalgebra::Complex;
use nalgebra_sparse::io::load_coo_from_matrix_market_str;
use nalgebra_sparse::CooMatrix;
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_real_general_empty() {
// Test several valid zero-shapes of a sparse matrix
let shapes = vec![ (0, 0), (1, 0), (0, 1) ];
let strings: Vec<String> = shapes
.iter()
.map(|(m, n)| format!("%%MatrixMarket matrix coordinate real general\n {} {} 0", m, n))
.collect();
for (shape,string) in shapes.iter().zip(strings.iter()) {
let sparse_mat = load_coo_from_matrix_market_str::<f32>(string).unwrap();
assert_eq!(sparse_mat.nrows(), shape.0);
assert_eq!(sparse_mat.ncols(), shape.1);
assert_eq!(sparse_mat.nnz(), 0);
}
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_real_general_empty() {
// Test several valid zero-shapes of a dense matrix
let shapes = vec![ (0, 0), (1, 0), (0, 1) ];
let strings: Vec<String> = shapes
.iter()
.map(|(m, n)| format!("%%MatrixMarket matrix array real general\n {} {}", m, n))
.collect();
for (shape,string) in shapes.iter().zip(strings.iter()) {
let sparse_mat = load_coo_from_matrix_market_str::<f32>(string).unwrap();
assert_eq!(sparse_mat.nrows(), shape.0);
assert_eq!(sparse_mat.ncols(), shape.1);
assert_eq!(sparse_mat.nnz(), 0);
}
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_real_general() {
let file_str = r#"
%%MatrixMarket matrix CoOrdinate real general
% This is also an example of free-format features.
%=================================================================================
%
% This ASCII file represents a sparse MxN matrix with L
% nonzeros in the following Matrix Market format:
%
% +----------------------------------------------+
% |%%MatrixMarket matrix coordinate real general | <--- header line
% |% | <--+
% |% comments | |-- 0 or more comment lines
% |% | <--+
% | M T L | <--- rows, columns, entries
% | I1 J1 A(I1, J1) | <--+
% | I2 J2 A(I2, J2) | |
% | I3 J3 A(I3, J3) | |-- L lines
% | . . . | |
% | IL JL A(IL, JL) | <--+
% +----------------------------------------------+
%
% Indices are 1-based, i.e. A(1,1) is the first element.
%
%=================================================================================
5 5 8
1 1 1
2 2 1.050e+01
3 3 1.500e-02
1 4 6.000e+00
4 2 2.505e+02
4 4 -2.800e+02
4 5 3.332e+01
5 5 1.200e+01
"#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![
1.0, 0.0, 0.0, 6.0, 0.0;
0.0, 10.5, 0.0, 0.0, 0.0;
0.0, 0.0, 0.015, 0.0, 0.0;
0.0, 250.5, 0.0, -280.0, 33.32;
0.0, 0.0, 0.0, 0.0, 12.0;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_int_symmetric() {
let file_str = r#"
%%MatrixMarket matrix coordinate integer symmetric
%
5 5 9
1 1 11
2 2 22
3 2 23
3 3 33
4 2 24
4 4 44
5 1 -15
5 3 35
5 5 55
"#;
let sparse_mat = load_coo_from_matrix_market_str::<i128>(file_str).unwrap();
let expected = dmatrix![
11, 0, 0, 0, -15;
0, 22, 23, 24, 0;
0, 23, 33, 0, 35;
0, 24, 0, 44, 0;
-15, 0, 35, 0, 55;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_complex_hermitian() {
let file_str = r#"
%%MatrixMarket matrix coordinate complex hermitian
%
5 5 7
1 1 1.0 0.0
2 2 10.5 0.0
4 2 250.5 22.22
3 3 0.015 0.0
4 4 -2.8e2 0.0
5 5 12.0 0.0
5 4 0.0 33.32
"#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f64>>(file_str).unwrap();
let expected = dmatrix![
Complex::<f64>{re:1.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0},Complex::<f64>{re:0.0,im:0.0};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:10.5,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:-22.22},Complex::<f64>{re:0.0,im:0.0};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.015,im:0.0}, Complex::<f64>{re:0.0,im:0.0},Complex::<f64>{re:0.0,im:0.0};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:250.5,im:22.22}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:-280.0,im:0.0},Complex::<f64>{re:0.0,im:-33.32};
Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:0.0}, Complex::<f64>{re:0.0,im:33.32},Complex::<f64>{re:12.0,im:0.0};
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_real_skew() {
let file_str = r#"
%%MatrixMarket matrix coordinate real skew-symmetric
%
5 5 4
3 2 -23.0
4 2 -24.0
5 1 -15.0
5 3 -35.0
"#;
let sparse_mat = load_coo_from_matrix_market_str::<f64>(file_str).unwrap();
let expected = dmatrix![
0.0, 0.0, 0.0, 0.0, 15.0;
0.0, 0.0, 23.0, 24.0, 0.0;
0.0, -23.0, 0.0, 0.0, 35.0;
0.0, -24.0, 0.0, 0.0, 0.0;
-15.0, 0.0, -35.0, 0.0, 0.0;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_sparse_pattern_general() {
let file_str = r#"
%%MatrixMarket matrix coordinate pattern general
%
5 5 10
1 1
1 5
2 3
2 4
3 2
3 5
4 1
5 2
5 4
5 5
"#;
let pattern_matrix = load_coo_from_matrix_market_str::<()>(file_str).unwrap();
let nrows = pattern_matrix.nrows();
let ncols = pattern_matrix.ncols();
let (row_idx, col_idx, val) = pattern_matrix.disassemble();
let values = vec![1; val.len()];
let sparse_mat = CooMatrix::try_from_triplets(nrows, ncols, row_idx, col_idx, values).unwrap();
let expected = dmatrix![
1, 0, 0, 0, 1;
0, 0, 1, 1, 0;
0, 1, 0, 0, 1;
1, 0, 0, 0, 0;
0, 1, 0, 1, 1;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_real_general() {
let file_str = r#"
%%MatrixMarket matrix array real general
%
4 3
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
"#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![
1.0, 5.0, 9.0;
2.0, 6.0, 10.0;
3.0, 7.0, 11.0;
4.0, 8.0, 12.0;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_real_symmetric() {
let file_str = r#"
%%MatrixMarket matrix array real symmetric
%
4 4
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
"#;
let sparse_mat = load_coo_from_matrix_market_str::<f32>(file_str).unwrap();
let expected = dmatrix![
1.0, 2.0, 3.0, 4.0;
2.0, 5.0, 6.0, 7.0;
3.0, 6.0, 8.0, 9.0;
4.0, 7.0, 9.0, 10.0;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_complex_hermitian() {
let file_str = r#"
%%MatrixMarket matrix array complex hermitian
%
4 4
1.0 0.0
2.0 2.0
3.0 3.0
4.0 4.0
5.0 0.0
6.0 6.0
7.0 7.0
8.0 0.0
9.0 9.0
10.0 0.0
"#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f64>>(file_str).unwrap();
let expected = dmatrix![
Complex::<f64>{re:1.0,im:0.0}, Complex::<f64>{re:2.0,im:-2.0} ,Complex::<f64>{re:3.0,im:-3.0} ,Complex::<f64>{re:4.0,im:-4.0};
Complex::<f64>{re:2.0,im:2.0}, Complex::<f64>{re:5.0,im:0.0} ,Complex::<f64>{re:6.0,im:-6.0} ,Complex::<f64>{re:7.0,im:-7.0};
Complex::<f64>{re:3.0,im:3.0}, Complex::<f64>{re:6.0,im:6.0} ,Complex::<f64>{re:8.0,im:0.0} ,Complex::<f64>{re:9.0,im:-9.0};
Complex::<f64>{re:4.0,im:4.0}, Complex::<f64>{re:7.0,im:7.0} ,Complex::<f64>{re:9.0,im:9.0} ,Complex::<f64>{re:10.0,im:0.0};
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_int_skew() {
let file_str = r#"
%%MatrixMarket matrix array integer skew-symmetric
%
4 4
1
2
3
4
5
6
"#;
let sparse_mat = load_coo_from_matrix_market_str::<i32>(file_str).unwrap();
let expected = dmatrix![
0,-1,-2,-3;
1, 0,-4,-5;
2, 4, 0,-6;
3, 5, 6, 0;
];
assert_matrix_eq!(sparse_mat, expected);
}
#[test]
#[rustfmt::skip]
fn test_matrixmarket_dense_complex_general() {
let file_str = r#"
%%MatrixMarket matrix array complex general
%
2 2
1 0
1 0
1 0
1 0
"#;
let sparse_mat = load_coo_from_matrix_market_str::<Complex<f32>>(file_str).unwrap();
let expected = dmatrix![
Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{re:1.0,im:0.0};
Complex::<f32>{re:1.0,im:0.0},Complex::<f32>{re:1.0,im:0.0};
];
assert_matrix_eq!(sparse_mat, expected);
}

View File

@ -3,6 +3,8 @@ mod convert_serial;
mod coo;
mod csc;
mod csr;
mod matrix_market;
mod ops;
mod pattern;
mod proptest;
mod test_data_examples;

View File

@ -0,0 +1,44 @@
/// Examples of *invalid* raw CSR data `(offsets, indices, values)`.
pub struct InvalidCsrDataExamples {
pub empty_offset_array: (Vec<usize>, Vec<usize>, Vec<i32>),
pub offset_array_invalid_length_for_arbitrary_data: (Vec<usize>, Vec<usize>, Vec<i32>),
pub invalid_first_entry_in_offsets_array: (Vec<usize>, Vec<usize>, Vec<i32>),
pub invalid_last_entry_in_offsets_array: (Vec<usize>, Vec<usize>, Vec<i32>),
pub invalid_length_of_offsets_array: (Vec<usize>, Vec<usize>, Vec<i32>),
pub nonmonotonic_offsets: (Vec<usize>, Vec<usize>, Vec<i32>),
pub nonmonotonic_minor_indices: (Vec<usize>, Vec<usize>, Vec<i32>),
pub minor_index_out_of_bounds: (Vec<usize>, Vec<usize>, Vec<i32>),
pub duplicate_entry: (Vec<usize>, Vec<usize>, Vec<i32>),
}
impl InvalidCsrDataExamples {
pub fn new() -> Self {
let empty_offset_array = (Vec::<usize>::new(), Vec::<usize>::new(), Vec::<i32>::new());
let offset_array_invalid_length_for_arbitrary_data =
(vec![0, 3, 5], vec![0, 1, 2, 3, 5], vec![0, 1, 2, 3, 4]);
let invalid_first_entry_in_offsets_array =
(vec![1, 2, 2, 5], vec![0, 5, 1, 2, 3], vec![0, 1, 2, 3, 4]);
let invalid_last_entry_in_offsets_array =
(vec![0, 2, 2, 4], vec![0, 5, 1, 2, 3], vec![0, 1, 2, 3, 4]);
let invalid_length_of_offsets_array =
(vec![0, 2, 2], vec![0, 5, 1, 2, 3], vec![0, 1, 2, 3, 4]);
let nonmonotonic_offsets = (vec![0, 3, 2, 5], vec![0, 1, 2, 3, 4], vec![0, 1, 2, 3, 4]);
let nonmonotonic_minor_indices =
(vec![0, 2, 2, 5], vec![0, 2, 3, 1, 4], vec![0, 1, 2, 3, 4]);
let minor_index_out_of_bounds =
(vec![0, 2, 2, 5], vec![0, 6, 1, 2, 3], vec![0, 1, 2, 3, 4]);
let duplicate_entry = (vec![0, 2, 2, 5], vec![0, 5, 2, 2, 3], vec![0, 1, 2, 3, 4]);
return Self {
empty_offset_array,
offset_array_invalid_length_for_arbitrary_data,
invalid_first_entry_in_offsets_array,
invalid_last_entry_in_offsets_array,
invalid_length_of_offsets_array,
nonmonotonic_minor_indices,
nonmonotonic_offsets,
minor_index_out_of_bounds,
duplicate_entry,
};
}
}

View File

@ -32,6 +32,10 @@ use std::mem;
/// A array-based statically sized matrix data storage.
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct ArrayStorage<T, const R: usize, const C: usize>(pub [[T; R]; C]);
impl<T, const R: usize, const C: usize> ArrayStorage<T, R, C> {

View File

@ -20,10 +20,10 @@ use crate::base::{
MatrixSliceMut, OMatrix, Scalar,
};
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::base::{DVector, VecStorage};
use crate::base::{DVector, RowDVector, VecStorage};
use crate::base::{SliceStorage, SliceStorageMut};
use crate::constraint::DimEq;
use crate::{IsNotStaticOne, RowSVector, SMatrix, SVector};
use crate::{IsNotStaticOne, RowSVector, SMatrix, SVector, VectorSlice, VectorSliceMut};
use std::mem::MaybeUninit;
// TODO: too bad this won't work for slice conversions.
@ -125,6 +125,24 @@ impl<T: Scalar, const D: usize> From<SVector<T, D>> for [T; D] {
}
}
impl<'a, T: Scalar, RStride: Dim, CStride: Dim, const D: usize>
From<VectorSlice<'a, T, Const<D>, RStride, CStride>> for [T; D]
{
#[inline]
fn from(vec: VectorSlice<'a, T, Const<D>, RStride, CStride>) -> Self {
vec.into_owned().into()
}
}
impl<'a, T: Scalar, RStride: Dim, CStride: Dim, const D: usize>
From<VectorSliceMut<'a, T, Const<D>, RStride, CStride>> for [T; D]
{
#[inline]
fn from(vec: VectorSliceMut<'a, T, Const<D>, RStride, CStride>) -> Self {
vec.into_owned().into()
}
}
impl<T: Scalar, const D: usize> From<[T; D]> for RowSVector<T, D>
where
Const<D>: IsNotStaticOne,
@ -197,8 +215,26 @@ impl<T: Scalar, const R: usize, const C: usize> From<[[T; R]; C]> for SMatrix<T,
impl<T: Scalar, const R: usize, const C: usize> From<SMatrix<T, R, C>> for [[T; R]; C] {
#[inline]
fn from(vec: SMatrix<T, R, C>) -> Self {
vec.data.0
fn from(mat: SMatrix<T, R, C>) -> Self {
mat.data.0
}
}
impl<'a, T: Scalar, RStride: Dim, CStride: Dim, const R: usize, const C: usize>
From<MatrixSlice<'a, T, Const<R>, Const<C>, RStride, CStride>> for [[T; R]; C]
{
#[inline]
fn from(mat: MatrixSlice<'a, T, Const<R>, Const<C>, RStride, CStride>) -> Self {
mat.into_owned().into()
}
}
impl<'a, T: Scalar, RStride: Dim, CStride: Dim, const R: usize, const C: usize>
From<MatrixSliceMut<'a, T, Const<R>, Const<C>, RStride, CStride>> for [[T; R]; C]
{
#[inline]
fn from(mat: MatrixSliceMut<'a, T, Const<R>, Const<C>, RStride, CStride>) -> Self {
mat.into_owned().into()
}
}
@ -453,6 +489,14 @@ impl<'a, T: Scalar> From<Vec<T>> for DVector<T> {
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
impl<'a, T: Scalar> From<Vec<T>> for RowDVector<T> {
#[inline]
fn from(vec: Vec<T>) -> Self {
Self::from_vec(vec)
}
}
impl<'a, T: Scalar + Copy, R: Dim, C: Dim, S: RawStorage<T, R, C> + IsContiguous>
From<&'a Matrix<T, R, C, S>> for &'a [T]
{

View File

@ -195,7 +195,7 @@ where
unsafe fn reallocate_copy(
rto: Const<RTO>,
cto: Const<CTO>,
mut buf: <Self as Allocator<T, RFrom, CFrom>>::Buffer,
buf: <Self as Allocator<T, RFrom, CFrom>>::Buffer,
) -> ArrayStorage<MaybeUninit<T>, RTO, CTO> {
let mut res = <Self as Allocator<T, Const<RTO>, Const<CTO>>>::allocate_uninit(rto, cto);
@ -226,7 +226,7 @@ where
unsafe fn reallocate_copy(
rto: Dynamic,
cto: CTo,
mut buf: ArrayStorage<T, RFROM, CFROM>,
buf: ArrayStorage<T, RFROM, CFROM>,
) -> VecStorage<MaybeUninit<T>, Dynamic, CTo> {
let mut res = <Self as Allocator<T, Dynamic, CTo>>::allocate_uninit(rto, cto);
@ -257,7 +257,7 @@ where
unsafe fn reallocate_copy(
rto: RTo,
cto: Dynamic,
mut buf: ArrayStorage<T, RFROM, CFROM>,
buf: ArrayStorage<T, RFROM, CFROM>,
) -> VecStorage<MaybeUninit<T>, RTo, Dynamic> {
let mut res = <Self as Allocator<T, RTo, Dynamic>>::allocate_uninit(rto, cto);

View File

@ -13,6 +13,10 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// Dim of dynamically-sized algebraic entities.
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct Dynamic {
value: usize,
}
@ -55,7 +59,7 @@ 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 + Sync {
pub unsafe trait Dim: Any + Debug + Copy + PartialEq + Send + Sync {
#[inline(always)]
fn is<D: Dim>() -> bool {
TypeId::of::<Self>() == TypeId::of::<D>()
@ -74,7 +78,7 @@ pub trait Dim: Any + Debug + Copy + PartialEq + Send + Sync {
fn from_usize(dim: usize) -> Self;
}
impl Dim for Dynamic {
unsafe impl Dim for Dynamic {
#[inline]
fn try_to_usize() -> Option<usize> {
None
@ -197,6 +201,10 @@ dim_ops!(
);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct Const<const R: usize>;
/// Trait implemented exclusively by type-level integers.
@ -270,7 +278,7 @@ pub trait ToTypenum {
type Typenum: Unsigned;
}
impl<const T: usize> Dim for Const<T> {
unsafe impl<const T: usize> Dim for Const<T> {
fn try_to_usize() -> Option<usize> {
Some(T)
}

View File

@ -14,7 +14,7 @@ use crate::base::{DefaultAllocator, Matrix, OMatrix, RowVector, Scalar, Vector};
use crate::{Storage, UninitMatrix};
use std::mem::MaybeUninit;
/// # Rows and columns extraction
/// # Triangular matrix extraction
impl<T: Scalar + Zero, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
/// Extracts the upper triangular part of this matrix (including the diagonal).
#[inline]
@ -41,7 +41,10 @@ impl<T: Scalar + Zero, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
res
}
}
/// # Rows and columns extraction
impl<T: Scalar, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
/// Creates a new matrix by extracting the given set of rows from `self`.
#[cfg(any(feature = "std", feature = "alloc"))]
#[must_use]
@ -95,9 +98,7 @@ impl<T: Scalar + Zero, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
for (destination, source) in icols.enumerate() {
// NOTE: this is basically a copy_frow but wrapping the values insnide of MaybeUninit.
res.column_mut(destination)
.zip_apply(&self.column(*source), |out, e| {
*out = MaybeUninit::new(e.clone())
});
.zip_apply(&self.column(*source), |out, e| *out = MaybeUninit::new(e));
}
// Safety: res is now fully initialized.
@ -1094,7 +1095,7 @@ unsafe fn compress_rows<T: Scalar>(
if new_nrows == 0 || ncols == 0 {
// The output matrix is empty, drop everything.
ptr::drop_in_place(data.as_mut());
ptr::drop_in_place(data);
return;
}

View File

@ -1,4 +1,5 @@
//! Indexing
#![allow(clippy::reversed_empty_ranges)]
use crate::base::storage::{RawStorage, RawStorageMut};
use crate::base::{
@ -43,7 +44,7 @@ impl<D: Dim> DimRange<D> for usize {
#[test]
fn dimrange_usize() {
assert_eq!(DimRange::contained_by(&0, Const::<0>), false);
assert!(!DimRange::contained_by(&0, Const::<0>));
assert!(DimRange::contained_by(&0, Const::<1>));
}
@ -68,8 +69,8 @@ impl<D: Dim> DimRange<D> for ops::Range<usize> {
#[test]
fn dimrange_range_usize() {
assert_eq!(DimRange::contained_by(&(0..0), Const::<0>), false);
assert_eq!(DimRange::contained_by(&(0..1), Const::<0>), false);
assert!(!DimRange::contained_by(&(0..0), Const::<0>));
assert!(!DimRange::contained_by(&(0..1), Const::<0>));
assert!(DimRange::contained_by(&(0..1), Const::<1>));
assert!(DimRange::contained_by(
&((usize::MAX - 1)..usize::MAX),
@ -110,8 +111,8 @@ impl<D: Dim> DimRange<D> for ops::RangeFrom<usize> {
#[test]
fn dimrange_rangefrom_usize() {
assert_eq!(DimRange::contained_by(&(0..), Const::<0>), false);
assert_eq!(DimRange::contained_by(&(0..), Const::<0>), false);
assert!(!DimRange::contained_by(&(0..), Const::<0>));
assert!(!DimRange::contained_by(&(0..), Const::<0>));
assert!(DimRange::contained_by(&(0..), Const::<1>));
assert!(DimRange::contained_by(
&((usize::MAX - 1)..),
@ -204,16 +205,16 @@ impl<D: Dim> DimRange<D> for ops::RangeInclusive<usize> {
#[test]
fn dimrange_rangeinclusive_usize() {
assert_eq!(DimRange::contained_by(&(0..=0), Const::<0>), false);
assert!(!DimRange::contained_by(&(0..=0), Const::<0>));
assert!(DimRange::contained_by(&(0..=0), Const::<1>));
assert_eq!(
DimRange::contained_by(&(usize::MAX..=usize::MAX), Dynamic::new(usize::MAX)),
false
);
assert_eq!(
DimRange::contained_by(&((usize::MAX - 1)..=usize::MAX), Dynamic::new(usize::MAX)),
false
);
assert!(!DimRange::contained_by(
&(usize::MAX..=usize::MAX),
Dynamic::new(usize::MAX)
));
assert!(!DimRange::contained_by(
&((usize::MAX - 1)..=usize::MAX),
Dynamic::new(usize::MAX)
));
assert!(DimRange::contained_by(
&((usize::MAX - 1)..=(usize::MAX - 1)),
Dynamic::new(usize::MAX)
@ -255,7 +256,7 @@ impl<D: Dim> DimRange<D> for ops::RangeTo<usize> {
#[test]
fn dimrange_rangeto_usize() {
assert!(DimRange::contained_by(&(..0), Const::<0>));
assert_eq!(DimRange::contained_by(&(..1), Const::<0>), false);
assert!(!DimRange::contained_by(&(..1), Const::<0>));
assert!(DimRange::contained_by(&(..0), Const::<1>));
assert!(DimRange::contained_by(
&(..(usize::MAX - 1)),
@ -292,13 +293,13 @@ impl<D: Dim> DimRange<D> for ops::RangeToInclusive<usize> {
#[test]
fn dimrange_rangetoinclusive_usize() {
assert_eq!(DimRange::contained_by(&(..=0), Const::<0>), false);
assert_eq!(DimRange::contained_by(&(..=1), Const::<0>), false);
assert!(!DimRange::contained_by(&(..=0), Const::<0>));
assert!(!DimRange::contained_by(&(..=1), Const::<0>));
assert!(DimRange::contained_by(&(..=0), Const::<1>));
assert_eq!(
DimRange::contained_by(&(..=(usize::MAX)), Dynamic::new(usize::MAX)),
false
);
assert!(!DimRange::contained_by(
&(..=(usize::MAX)),
Dynamic::new(usize::MAX)
));
assert!(DimRange::contained_by(
&(..=(usize::MAX - 1)),
Dynamic::new(usize::MAX)
@ -566,7 +567,10 @@ where
#[doc(hidden)]
#[inline(always)]
unsafe fn get_unchecked(self, matrix: &'a Matrix<T, R, C, S>) -> Self::Output {
matrix.data.get_unchecked_linear(self)
let nrows = matrix.shape().0;
let row = self % nrows;
let col = self / nrows;
matrix.data.get_unchecked(row, col)
}
}
@ -585,7 +589,10 @@ where
where
S: RawStorageMut<T, R, C>,
{
matrix.data.get_unchecked_linear_mut(self)
let nrows = matrix.shape().0;
let row = self % nrows;
let col = self / nrows;
matrix.data.get_unchecked_mut(row, col)
}
}

View File

@ -32,7 +32,7 @@ use crate::{ArrayStorage, SMatrix, SimdComplexField, Storage, UninitMatrix};
use crate::storage::IsContiguous;
use crate::uninit::{Init, InitStatus, Uninit};
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::{DMatrix, DVector, Dynamic, VecStorage};
use crate::{DMatrix, DVector, Dynamic, RowDVector, VecStorage};
use std::mem::MaybeUninit;
/// A square matrix.
@ -92,6 +92,7 @@ pub type MatrixCross<T, R1, C1, R2, C2> =
/// - [Interpolation <span style="float:right;">`lerp`, `slerp`…</span>](#interpolation)
/// - [BLAS functions <span style="float:right;">`gemv`, `gemm`, `syger`…</span>](#blas-functions)
/// - [Swizzling <span style="float:right;">`xx`, `yxz`…</span>](#swizzling)
/// - [Triangular matrix extraction <span style="float:right;">`upper_triangle`, `lower_triangle`</span>](#triangular-matrix-extraction)
///
/// #### Statistics
/// - [Common operations <span style="float:right;">`row_sum`, `column_mean`, `variance`…</span>](#common-statistics-operations)
@ -154,6 +155,10 @@ pub type MatrixCross<T, R1, C1, R2, C2> =
/// some concrete types for `T` and a compatible data storage type `S`).
#[repr(C)]
#[derive(Clone, Copy)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct Matrix<T, R, C, S> {
/// The data storage that contains all the matrix components. Disappointed?
///
@ -188,17 +193,14 @@ pub struct Matrix<T, R, C, S> {
// Note that it would probably make sense to just have
// the type `Matrix<S>`, and have `T, R, C` be associated-types
// of the `RawStorage` trait. However, because we don't have
// specialization, this is not bossible because these `T, R, C`
// specialization, this is not possible because these `T, R, C`
// allows us to desambiguate a lot of configurations.
_phantoms: PhantomData<(T, R, C)>,
}
impl<T, R: Dim, C: Dim, S: fmt::Debug> fmt::Debug for Matrix<T, R, C, S> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
formatter
.debug_struct("Matrix")
.field("data", &self.data)
.finish()
self.data.fmt(formatter)
}
}
@ -411,6 +413,21 @@ impl<T> DVector<T> {
}
}
// TODO: Consider removing/deprecating `from_vec_storage` once we are able to make
// `from_data` const fn compatible
#[cfg(any(feature = "std", feature = "alloc"))]
impl<T> RowDVector<T> {
/// Creates a new heap-allocated matrix from the given [`VecStorage`].
///
/// This method exists primarily as a workaround for the fact that `from_data` can not
/// work in `const fn` contexts.
pub const fn from_vec_storage(storage: VecStorage<T, U1, Dynamic>) -> Self {
// This is sound because the dimensions of the matrix and the storage are guaranteed
// to be the same
unsafe { Self::from_data_statically_unchecked(storage) }
}
}
impl<T, R: Dim, C: Dim> UninitMatrix<T, R, C>
where
DefaultAllocator: Allocator<T, R, C>,
@ -681,7 +698,7 @@ impl<T, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
#[inline]
fn transpose_to_uninit<Status, R2, C2, SB>(
&self,
status: Status,
_status: Status,
out: &mut Matrix<Status::Value, R2, C2, SB>,
) where
Status: InitStatus<T>,
@ -1377,7 +1394,7 @@ impl<T: SimdComplexField, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C
#[inline]
fn adjoint_to_uninit<Status, R2, C2, SB>(
&self,
status: Status,
_status: Status,
out: &mut Matrix<Status::Value, R2, C2, SB>,
) where
Status: InitStatus<T>,
@ -1777,7 +1794,7 @@ where
assert!(self.shape() == other.shape());
self.iter()
.zip(other.iter())
.all(|(a, b)| a.ulps_eq(b, epsilon.clone(), max_ulps.clone()))
.all(|(a, b)| a.ulps_eq(b, epsilon.clone(), max_ulps))
}
}
@ -2021,16 +2038,26 @@ impl<T: Scalar + ClosedAdd + ClosedSub + ClosedMul, R: Dim, C: Dim, S: RawStorag
+ SameNumberOfRows<R2, U2>
+ SameNumberOfColumns<C2, U1>,
{
assert!(
self.shape() == (2, 1),
"2D perpendicular product requires (2, 1) vector but found {:?}",
self.shape()
let shape = self.shape();
assert_eq!(
shape,
b.shape(),
"2D vector perpendicular product dimension mismatch."
);
assert_eq!(
shape,
(2, 1),
"2D perpendicular product requires (2, 1) vectors {:?}",
shape
);
unsafe {
self.get_unchecked((0, 0)).clone() * b.get_unchecked((1, 0)).clone()
- self.get_unchecked((1, 0)).clone() * b.get_unchecked((0, 0)).clone()
}
// SAFETY: assertion above ensures correct shape
let ax = unsafe { self.get_unchecked((0, 0)).clone() };
let ay = unsafe { self.get_unchecked((1, 0)).clone() };
let bx = unsafe { b.get_unchecked((0, 0)).clone() };
let by = unsafe { b.get_unchecked((1, 0)).clone() };
ax * by - ay * bx
}
// TODO: use specialization instead of an assertion.
@ -2051,17 +2078,14 @@ impl<T: Scalar + ClosedAdd + ClosedSub + ClosedMul, R: Dim, C: Dim, S: RawStorag
let shape = self.shape();
assert_eq!(shape, b.shape(), "Vector cross product dimension mismatch.");
assert!(
(shape.0 == 3 && shape.1 == 1) || (shape.0 == 1 && shape.1 == 3),
shape == (3, 1) || shape == (1, 3),
"Vector cross product dimension mismatch: must be (3, 1) or (1, 3) but found {:?}.",
shape
);
if shape.0 == 3 {
unsafe {
// TODO: soooo ugly!
let nrows = SameShapeR::<R, R2>::from_usize(3);
let ncols = SameShapeC::<C, C2>::from_usize(1);
let mut res = Matrix::uninit(nrows, ncols);
let mut res = Matrix::uninit(Dim::from_usize(3), Dim::from_usize(1));
let ax = self.get_unchecked((0, 0));
let ay = self.get_unchecked((1, 0));
@ -2083,10 +2107,7 @@ impl<T: Scalar + ClosedAdd + ClosedSub + ClosedMul, R: Dim, C: Dim, S: RawStorag
}
} else {
unsafe {
// TODO: ugly!
let nrows = SameShapeR::<R, R2>::from_usize(1);
let ncols = SameShapeC::<C, C2>::from_usize(3);
let mut res = Matrix::uninit(nrows, ncols);
let mut res = Matrix::uninit(Dim::from_usize(1), Dim::from_usize(3));
let ax = self.get_unchecked((0, 0));
let ay = self.get_unchecked((0, 1));

View File

@ -42,14 +42,14 @@ where
#[inline]
fn replace(&mut self, i: usize, val: Self::Element) {
self.zip_apply(&val, |mut a, b| {
self.zip_apply(&val, |a, b| {
a.replace(i, b);
})
}
#[inline]
unsafe fn replace_unchecked(&mut self, i: usize, val: Self::Element) {
self.zip_apply(&val, |mut a, b| {
self.zip_apply(&val, |a, b| {
a.replace_unchecked(i, b);
})
}

View File

@ -73,7 +73,6 @@ macro_rules! slice_storage_impl(
S: $Storage<T, RStor, CStor>,
RStride: Dim,
CStride: Dim {
$T::from_raw_parts(storage.$get_addr(start.0, start.1), shape, strides)
}
}

View File

@ -60,7 +60,7 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
T: SimdPartialOrd + Zero,
{
self.fold_with(
|e| e.map(|e| e.clone()).unwrap_or_else(T::zero),
|e| e.cloned().unwrap_or_else(T::zero),
|a, b| a.simd_max(b.clone()),
)
}
@ -123,7 +123,7 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
T: SimdPartialOrd + Zero,
{
self.fold_with(
|e| e.map(|e| e.clone()).unwrap_or_else(T::zero),
|e| e.cloned().unwrap_or_else(T::zero),
|a, b| a.simd_min(b.clone()),
)
}

View File

@ -434,12 +434,7 @@ impl<T: Scalar, R: Dim, C: Dim, S: StorageMut<T, R, C>> Matrix<T, R, C, S> {
{
let n = self.norm();
let le = n.clone().simd_le(min_norm);
self.apply(|e| {
*e = e
.clone()
.simd_unscale(n.clone())
.select(le.clone(), e.clone())
});
self.apply(|e| *e = e.clone().simd_unscale(n.clone()).select(le, e.clone()));
SimdOption::new(n, le)
}

View File

@ -146,7 +146,7 @@ macro_rules! componentwise_binop_impl(
#[inline]
fn $method_to_statically_unchecked_uninit<Status, R2: Dim, C2: Dim, SB,
R3: Dim, C3: Dim, SC>(&self,
status: Status,
_status: Status,
rhs: &Matrix<T, R2, C2, SB>,
out: &mut Matrix<Status::Value, R3, C3, SC>)
where Status: InitStatus<T>,
@ -699,7 +699,7 @@ where
#[inline(always)]
fn xx_mul_to_uninit<Status, R2: Dim, C2: Dim, SB, R3: Dim, C3: Dim, SC>(
&self,
status: Status,
_status: Status,
rhs: &Matrix<T, R2, C2, SB>,
out: &mut Matrix<Status::Value, R3, C3, SC>,
dot: impl Fn(

View File

@ -7,10 +7,10 @@ use simba::scalar::{ClosedAdd, ClosedMul, ComplexField, RealField};
use crate::base::allocator::Allocator;
use crate::base::dimension::{Dim, DimMin};
use crate::base::storage::Storage;
use crate::base::{DefaultAllocator, Matrix, Scalar, SquareMatrix};
use crate::base::{DefaultAllocator, Matrix, SquareMatrix};
use crate::RawStorage;
impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
impl<T, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
/// The total number of elements of this matrix.
///
/// # Examples:
@ -63,50 +63,18 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
T::Epsilon: Clone,
{
let (nrows, ncols) = self.shape();
let d;
if nrows > ncols {
d = ncols;
for i in d..nrows {
for j in 0..ncols {
if !relative_eq!(self[(i, j)], T::zero(), epsilon = eps.clone()) {
return false;
}
}
}
} else {
// nrows <= ncols
d = nrows;
for j in 0..ncols {
for i in 0..nrows {
for j in d..ncols {
if !relative_eq!(self[(i, j)], T::zero(), epsilon = eps.clone()) {
return false;
}
}
}
}
// Off-diagonal elements of the sub-square matrix.
for i in 1..d {
for j in 0..i {
// TODO: use unsafe indexing.
if !relative_eq!(self[(i, j)], T::zero(), epsilon = eps.clone())
|| !relative_eq!(self[(j, i)], T::zero(), epsilon = eps.clone())
let el = unsafe { self.get_unchecked((i, j)) };
if (i == j && !relative_eq!(*el, T::one(), epsilon = eps.clone()))
|| (i != j && !relative_eq!(*el, T::zero(), epsilon = eps.clone()))
{
return false;
}
}
}
// Diagonal elements of the sub-square matrix.
for i in 0..d {
if !relative_eq!(self[(i, i)], T::one(), epsilon = eps.clone()) {
return false;
}
}
true
}
}

View File

@ -1,8 +1,8 @@
use crate::allocator::Allocator;
use crate::storage::RawStorage;
use crate::{Const, DefaultAllocator, Dim, Matrix, OVector, RowOVector, Scalar, VectorSlice, U1};
use num::Zero;
use simba::scalar::{ClosedAdd, Field, SupersetOf};
use num::{One, Zero};
use simba::scalar::{ClosedAdd, ClosedMul, Field, SupersetOf};
use std::mem::MaybeUninit;
/// # Folding on columns and rows
@ -123,7 +123,9 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.row_sum(), RowVector3::new(5.0, 7.0, 9.0));
///
/// let mint = Matrix3x2::new(1,2,3,4,5,6);
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.row_sum(), RowVector2::new(9,12));
/// ```
#[inline]
@ -148,8 +150,10 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.row_sum_tr(), Vector3::new(5.0, 7.0, 9.0));
///
/// let mint = Matrix3x2::new(1,2,3,4,5,6);
/// assert_eq!(mint.row_sum_tr(), Vector2::new(9,12));
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.row_sum_tr(), Vector2::new(9, 12));
/// ```
#[inline]
#[must_use]
@ -173,8 +177,10 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.column_sum(), Vector2::new(6.0, 15.0));
///
/// let mint = Matrix3x2::new(1,2,3,4,5,6);
/// assert_eq!(mint.column_sum(), Vector3::new(3,7,11));
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.column_sum(), Vector3::new(3, 7, 11));
/// ```
#[inline]
#[must_use]
@ -189,6 +195,120 @@ impl<T: Scalar, R: Dim, C: Dim, S: RawStorage<T, R, C>> Matrix<T, R, C, S> {
})
}
/*
*
* Product computation.
*
*/
/// The product of all the elements of this matrix.
///
/// # Example
///
/// ```
/// # use nalgebra::Matrix2x3;
///
/// let m = Matrix2x3::new(1.0, 2.0, 3.0,
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.product(), 720.0);
/// ```
#[inline]
#[must_use]
pub fn product(&self) -> T
where
T: ClosedMul + One,
{
self.iter().cloned().fold(T::one(), |a, b| a * b)
}
/// The product of all the rows of this matrix.
///
/// Use `.row_sum_tr` if you need the result in a column vector instead.
///
/// # Example
///
/// ```
/// # use nalgebra::{Matrix2x3, Matrix3x2};
/// # use nalgebra::{RowVector2, RowVector3};
///
/// let m = Matrix2x3::new(1.0, 2.0, 3.0,
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.row_product(), RowVector3::new(4.0, 10.0, 18.0));
///
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.row_product(), RowVector2::new(15, 48));
/// ```
#[inline]
#[must_use]
pub fn row_product(&self) -> RowOVector<T, C>
where
T: ClosedMul + One,
DefaultAllocator: Allocator<T, U1, C>,
{
self.compress_rows(|col| col.product())
}
/// The product of all the rows of this matrix. The result is transposed and returned as a column vector.
///
/// # Example
///
/// ```
/// # use nalgebra::{Matrix2x3, Matrix3x2};
/// # use nalgebra::{Vector2, Vector3};
///
/// let m = Matrix2x3::new(1.0, 2.0, 3.0,
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.row_product_tr(), Vector3::new(4.0, 10.0, 18.0));
///
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.row_product_tr(), Vector2::new(15, 48));
/// ```
#[inline]
#[must_use]
pub fn row_product_tr(&self) -> OVector<T, C>
where
T: ClosedMul + One,
DefaultAllocator: Allocator<T, C>,
{
self.compress_rows_tr(|col| col.product())
}
/// The product of all the columns of this matrix.
///
/// # Example
///
/// ```
/// # use nalgebra::{Matrix2x3, Matrix3x2};
/// # use nalgebra::{Vector2, Vector3};
///
/// let m = Matrix2x3::new(1.0, 2.0, 3.0,
/// 4.0, 5.0, 6.0);
/// assert_eq!(m.column_product(), Vector2::new(6.0, 120.0));
///
/// let mint = Matrix3x2::new(1, 2,
/// 3, 4,
/// 5, 6);
/// assert_eq!(mint.column_product(), Vector3::new(2, 12, 30));
/// ```
#[inline]
#[must_use]
pub fn column_product(&self) -> OVector<T, R>
where
T: ClosedMul + One,
DefaultAllocator: Allocator<T, R>,
{
let nrows = self.shape_generic().0;
self.compress_columns(
OVector::repeat_generic(nrows, Const::<1>, T::one()),
|out, col| {
out.component_mul_assign(&col);
},
)
}
/*
*
* Variance computation.

View File

@ -66,11 +66,11 @@ unsafe impl<T> InitStatus<T> for Uninit {
#[inline(always)]
unsafe fn assume_init_ref(t: &MaybeUninit<T>) -> &T {
std::mem::transmute(t.as_ptr()) // TODO: use t.assume_init_ref()
&*t.as_ptr() // TODO: use t.assume_init_ref()
}
#[inline(always)]
unsafe fn assume_init_mut(t: &mut MaybeUninit<T>) -> &mut T {
std::mem::transmute(t.as_mut_ptr()) // TODO: use t.assume_init_mut()
&mut *t.as_mut_ptr() // TODO: use t.assume_init_mut()
}
}

View File

@ -1,3 +1,4 @@
use std::fmt;
#[cfg(feature = "abomonation-serialize")]
use std::io::{Result as IOResult, Write};
use std::ops::Deref;
@ -24,11 +25,21 @@ use crate::{Dim, Matrix, OMatrix, RealField, Scalar, SimdComplexField, SimdRealF
/// and [`UnitQuaternion`](crate::UnitQuaternion); both built on top of `Unit`. If you are interested
/// in their documentation, read their dedicated pages directly.
#[repr(transparent)]
#[derive(Clone, Hash, Debug, Copy)]
#[derive(Clone, Hash, Copy)]
// #[cfg_attr(
// all(not(target_os = "cuda"), feature = "cuda"),
// derive(cust::DeviceCopy)
// )]
pub struct Unit<T> {
pub(crate) value: T,
}
impl<T: fmt::Debug> fmt::Debug for Unit<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.value.fmt(formatter)
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<T> bytemuck::Zeroable for Unit<T> where T: bytemuck::Zeroable {}
@ -111,6 +122,17 @@ mod rkyv_impl {
}
}
#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
unsafe impl<T: cust::memory::DeviceCopy, R, C, S> cust::memory::DeviceCopy
for Unit<Matrix<T, R, C, S>>
where
T: Scalar,
R: Dim,
C: Dim,
S: RawStorage<T, R, C> + Copy,
{
}
impl<T, R, C, S> PartialEq for Unit<Matrix<T, R, C, S>>
where
T: Scalar + PartialEq,

View File

@ -39,6 +39,10 @@ use simba::scalar::{ClosedNeg, RealField};
/// See <https://github.com/dimforge/nalgebra/issues/487>
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct DualQuaternion<T> {
/// The real component of the quaternion
pub real: Quaternion<T>,
@ -351,13 +355,14 @@ impl<T: RealField + UlpsEq<Epsilon = T>> UlpsEq for DualQuaternion<T> {
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.clone().to_vector().ulps_eq(&other.clone().to_vector(), epsilon.clone(), max_ulps.clone()) ||
self.clone().to_vector().ulps_eq(&other.clone().to_vector(), epsilon.clone(), max_ulps) ||
// Account for the double-covering of S², i.e. q = -q.
self.clone().to_vector().iter().zip(other.clone().to_vector().iter()).all(|(a, b)| a.ulps_eq(&-b.clone(), epsilon.clone(), max_ulps.clone()))
self.clone().to_vector().iter().zip(other.clone().to_vector().iter()).all(|(a, b)| a.ulps_eq(&-b.clone(), epsilon.clone(), max_ulps))
}
}
/// A unit quaternions. May be used to represent a rotation followed by a translation.
/// A unit dual quaternion. May be used to represent a rotation followed by a
/// translation.
pub type UnitDualQuaternion<T> = Unit<DualQuaternion<T>>;
impl<T: Scalar + ClosedNeg + PartialEq + SimdRealField> PartialEq for UnitDualQuaternion<T> {
@ -593,8 +598,9 @@ where
/// Screw linear interpolation between two unit quaternions. This creates a
/// smooth arc from one dual-quaternion to another.
///
/// Panics if the angle between both quaternion is 180 degrees (in which case the interpolation
/// is not well-defined). Use `.try_sclerp` instead to avoid the panic.
/// Panics if the angle between both quaternion is 180 degrees (in which
/// case the interpolation is not well-defined). Use `.try_sclerp`
/// instead to avoid the panic.
///
/// # Example
/// ```
@ -627,15 +633,16 @@ where
.expect("DualQuaternion sclerp: ambiguous configuration.")
}
/// Computes the screw-linear interpolation between two unit quaternions or returns `None`
/// if both quaternions are approximately 180 degrees apart (in which case the interpolation is
/// not well-defined).
/// Computes the screw-linear interpolation between two unit quaternions or
/// returns `None` if both quaternions are approximately 180 degrees
/// apart (in which case the interpolation is not well-defined).
///
/// # Arguments
/// * `self`: the first quaternion to interpolate from.
/// * `other`: the second quaternion to interpolate toward.
/// * `t`: the interpolation parameter. Should be between 0 and 1.
/// * `epsilon`: the value below which the sinus of the angle separating both quaternion
/// * `epsilon`: the value below which the sinus of the angle separating
/// both quaternion
/// must be to return `None`.
#[inline]
#[must_use]
@ -650,6 +657,10 @@ where
// interpolation.
let other = {
let dot_product = self.as_ref().real.coords.dot(&other.as_ref().real.coords);
if relative_eq!(dot_product, T::zero(), epsilon = epsilon.clone()) {
return None;
}
if dot_product < T::zero() {
-other.clone()
} else {
@ -660,13 +671,21 @@ where
let difference = self.as_ref().conjugate() * other.as_ref();
let norm_squared = difference.real.vector().norm_squared();
if relative_eq!(norm_squared, T::zero(), epsilon = epsilon) {
return None;
return Some(Self::from_parts(
self.translation()
.vector
.lerp(&other.translation().vector, t)
.into(),
self.rotation(),
));
}
let inverse_norm_squared = T::one() / norm_squared;
let scalar: T = difference.real.scalar();
let mut angle = two.clone() * scalar.acos();
let inverse_norm_squared: T = T::one() / norm_squared;
let inverse_norm = inverse_norm_squared.sqrt();
let mut angle = two.clone() * difference.real.scalar().acos();
let mut pitch = -two * difference.dual.scalar() * inverse_norm.clone();
let direction = difference.real.vector() * inverse_norm.clone();
let moment = (difference.dual.vector()
@ -678,6 +697,7 @@ where
let sin = (half.clone() * angle.clone()).sin();
let cos = (half.clone() * angle).cos();
let real = Quaternion::from_parts(cos.clone(), direction.clone() * sin.clone());
let dual = Quaternion::from_parts(
-pitch.clone() * half.clone() * sin.clone(),

View File

@ -54,7 +54,11 @@ use crate::geometry::{AbstractRotation, Point, Translation};
/// * [Conversion to a matrix <span style="float:right;">`to_matrix`…</span>](#conversion-to-a-matrix)
///
#[repr(C)]
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde-serialize-no-std",
@ -170,20 +174,6 @@ where
}
}
impl<T: Scalar + Copy, R: Copy, const D: usize> Copy for Isometry<T, R, D> where
Owned<T, Const<D>>: Copy
{
}
impl<T: Scalar, R: Clone, const D: usize> Clone for Isometry<T, R, D> {
#[inline]
fn clone(&self) -> Self {
Self {
rotation: self.rotation.clone(),
translation: self.translation.clone(),
}
}
}
/// # From the translation and rotation parts
impl<T: Scalar, R: AbstractRotation<T, D>, const D: usize> Isometry<T, R, D> {
/// Creates a new isometry from its rotational and translational parts.
@ -629,7 +619,7 @@ where
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.translation
.ulps_eq(&other.translation, epsilon.clone(), max_ulps.clone())
.ulps_eq(&other.translation, epsilon.clone(), max_ulps)
&& self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
}
}

View File

@ -21,6 +21,15 @@ use crate::{
UnitQuaternion,
};
impl<T: SimdRealField, R: AbstractRotation<T, D>, const D: usize> Default for Isometry<T, R, D>
where
T::Element: SimdRealField,
{
fn default() -> Self {
Self::identity()
}
}
impl<T: SimdRealField, R: AbstractRotation<T, D>, const D: usize> Isometry<T, R, D>
where
T::Element: SimdRealField,

View File

@ -48,6 +48,14 @@ mod translation_coordinates;
mod translation_ops;
mod translation_simba;
mod scale;
mod scale_alias;
mod scale_construction;
mod scale_conversion;
mod scale_coordinates;
mod scale_ops;
mod scale_simba;
mod isometry;
mod isometry_alias;
mod isometry_construction;
@ -95,6 +103,9 @@ pub use self::unit_complex::*;
pub use self::translation::*;
pub use self::translation_alias::*;
pub use self::scale::*;
pub use self::scale_alias::*;
pub use self::isometry::*;
pub use self::isometry_alias::*;

View File

@ -19,19 +19,15 @@ use crate::geometry::{Point3, Projective3};
/// A 3D orthographic projection stored as a homogeneous 4x4 matrix.
#[repr(C)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[derive(Copy, Clone)]
pub struct Orthographic3<T> {
matrix: Matrix4<T>,
}
impl<T: RealField + Copy> Copy for Orthographic3<T> {}
impl<T: RealField> Clone for Orthographic3<T> {
#[inline]
fn clone(&self) -> Self {
Self::from_matrix_unchecked(self.matrix.clone())
}
}
impl<T: RealField> fmt::Debug for Orthographic3<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.matrix.fmt(f)
@ -175,7 +171,7 @@ impl<T: RealField> Orthographic3<T> {
);
let half: T = crate::convert(0.5);
let width = zfar.clone() * (vfov.clone() * half.clone()).tan();
let width = zfar.clone() * (vfov * half.clone()).tan();
let height = width.clone() / aspect;
Self::new(

View File

@ -20,19 +20,15 @@ use crate::geometry::{Point3, Projective3};
/// A 3D perspective projection stored as a homogeneous 4x4 matrix.
#[repr(C)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[derive(Copy, Clone)]
pub struct Perspective3<T> {
matrix: Matrix4<T>,
}
impl<T: RealField + Copy> Copy for Perspective3<T> {}
impl<T: RealField> Clone for Perspective3<T> {
#[inline]
fn clone(&self) -> Self {
Self::from_matrix_unchecked(self.matrix.clone())
}
}
impl<T: RealField> fmt::Debug for Perspective3<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.matrix.fmt(f)

View File

@ -40,7 +40,7 @@ use std::mem::MaybeUninit;
/// may have some other methods, e.g., `isometry.inverse_transform_point(&point)`. See the documentation
/// of said transformations for details.
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct OPoint<T: Scalar, D: DimName>
where
DefaultAllocator: Allocator<T, D>,
@ -49,6 +49,15 @@ where
pub coords: OVector<T, D>,
}
impl<T: Scalar + fmt::Debug, D: DimName> fmt::Debug for OPoint<T, D>
where
DefaultAllocator: Allocator<T, D>,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.coords.as_slice().fmt(formatter)
}
}
impl<T: Scalar + hash::Hash, D: DimName> hash::Hash for OPoint<T, D>
where
DefaultAllocator: Allocator<T, D>,
@ -65,6 +74,15 @@ where
{
}
#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
unsafe impl<T: Scalar + cust::memory::DeviceCopy, D: DimName> cust::memory::DeviceCopy
for OPoint<T, D>
where
DefaultAllocator: Allocator<T, D>,
OVector<T, D>: cust::memory::DeviceCopy,
{
}
#[cfg(feature = "bytemuck")]
unsafe impl<T: Scalar, D: DimName> bytemuck::Zeroable for OPoint<T, D>
where

View File

@ -19,6 +19,15 @@ use simba::scalar::{ClosedDiv, SupersetOf};
use crate::geometry::Point;
impl<T: Scalar + Zero, D: DimName> Default for OPoint<T, D>
where
DefaultAllocator: Allocator<T, D>,
{
fn default() -> Self {
Self::origin()
}
}
/// # Other construction methods
impl<T: Scalar, D: DimName> OPoint<T, D>
where

View File

@ -27,12 +27,22 @@ use crate::geometry::{Point3, Rotation};
/// A quaternion. See the type alias `UnitQuaternion = Unit<Quaternion>` for a quaternion
/// that may be used as a rotation.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[derive(Copy, Clone)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub struct Quaternion<T> {
/// This quaternion as a 4D vector of coordinates in the `[ x, y, z, w ]` storage order.
pub coords: Vector4<T>,
}
impl<T: fmt::Debug> fmt::Debug for Quaternion<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.coords.as_slice().fmt(formatter)
}
}
impl<T: Scalar + Hash> Hash for Quaternion<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.coords.hash(state)
@ -1039,9 +1049,9 @@ impl<T: RealField + UlpsEq<Epsilon = T>> UlpsEq for Quaternion<T> {
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.as_vector().ulps_eq(other.as_vector(), epsilon.clone(), max_ulps.clone()) ||
self.as_vector().ulps_eq(other.as_vector(), epsilon.clone(), max_ulps) ||
// Account for the double-covering of S², i.e. q = -q.
self.as_vector().iter().zip(other.as_vector().iter()).all(|(a, b)| a.ulps_eq(&-b.clone(), epsilon.clone(), max_ulps.clone()))
self.as_vector().iter().zip(other.as_vector().iter()).all(|(a, b)| a.ulps_eq(&-b.clone(), epsilon.clone(), max_ulps))
}
}
@ -1058,6 +1068,9 @@ impl<T: RealField + fmt::Display> fmt::Display for Quaternion<T> {
/// A unit quaternions. May be used to represent a rotation.
pub type UnitQuaternion<T> = Unit<Quaternion<T>>;
#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
unsafe impl<T: cust::memory::DeviceCopy> cust::memory::DeviceCopy for UnitQuaternion<T> {}
impl<T: Scalar + ClosedNeg + PartialEq> PartialEq for UnitQuaternion<T> {
#[inline]
fn eq(&self, rhs: &Self) -> bool {
@ -1492,18 +1505,18 @@ where
let wk = w.clone() * k.clone() * crate::convert(2.0f64);
let wj = w.clone() * j.clone() * crate::convert(2.0f64);
let ik = i.clone() * k.clone() * crate::convert(2.0f64);
let jk = j.clone() * k.clone() * crate::convert(2.0f64);
let wi = w.clone() * i.clone() * crate::convert(2.0f64);
let jk = j * k * crate::convert(2.0f64);
let wi = w * i * crate::convert(2.0f64);
Rotation::from_matrix_unchecked(Matrix3::new(
ww.clone() + ii.clone() - jj.clone() - kk.clone(),
ij.clone() - wk.clone(),
wj.clone() + ik.clone(),
wk.clone() + ij.clone(),
wk + ij,
ww.clone() - ii.clone() + jj.clone() - kk.clone(),
jk.clone() - wi.clone(),
ik.clone() - wj.clone(),
wi.clone() + jk.clone(),
ik - wj,
wi + jk,
ww - ii - jj + kk,
))
}

View File

@ -54,11 +54,21 @@ use crate::geometry::Point;
/// * [Conversion to a matrix <span style="float:right;">`matrix`, `to_homogeneous`…</span>](#conversion-to-a-matrix)
///
#[repr(C)]
#[derive(Debug)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[derive(Copy, Clone)]
pub struct Rotation<T, const D: usize> {
matrix: SMatrix<T, D, D>,
}
impl<T: fmt::Debug, const D: usize> fmt::Debug for Rotation<T, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.matrix.fmt(formatter)
}
}
impl<T: Scalar + hash::Hash, const D: usize> hash::Hash for Rotation<T, D>
where
<DefaultAllocator as Allocator<T, Const<D>, Const<D>>>::Buffer: hash::Hash,
@ -68,21 +78,6 @@ where
}
}
impl<T: Scalar + Copy, const D: usize> Copy for Rotation<T, D> where
<DefaultAllocator as Allocator<T, Const<D>, Const<D>>>::Buffer: Copy
{
}
impl<T: Scalar, const D: usize> Clone for Rotation<T, D>
where
<DefaultAllocator as Allocator<T, Const<D>, Const<D>>>::Buffer: Clone,
{
#[inline]
fn clone(&self) -> Self {
Self::from_matrix_unchecked(self.matrix.clone())
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<T, const D: usize> bytemuck::Zeroable for Rotation<T, D>
where

View File

@ -6,6 +6,15 @@ use crate::base::{SMatrix, Scalar};
use crate::geometry::Rotation;
impl<T, const D: usize> Default for Rotation<T, D>
where
T: Scalar + Zero + One,
{
fn default() -> Self {
Self::identity()
}
}
/// # Identity
impl<T, const D: usize> Rotation<T, D>
where
@ -15,9 +24,16 @@ where
///
/// # Example
/// ```
/// # use nalgebra::Quaternion;
/// let rot1 = Quaternion::identity();
/// let rot2 = Quaternion::new(1.0, 2.0, 3.0, 4.0);
/// # use nalgebra::{Rotation2, Rotation3};
/// # use nalgebra::Vector3;
/// let rot1 = Rotation2::identity();
/// let rot2 = Rotation2::new(std::f32::consts::FRAC_PI_2);
///
/// assert_eq!(rot1 * rot2, rot2);
/// assert_eq!(rot2 * rot1, rot2);
///
/// let rot1 = Rotation3::identity();
/// let rot2 = Rotation3::from_axis_angle(&Vector3::z_axis(), std::f32::consts::FRAC_PI_2);
///
/// assert_eq!(rot1 * rot2, rot2);
/// assert_eq!(rot2 * rot1, rot2);

View File

@ -60,7 +60,7 @@ impl<T: SimdRealField> Rotation2<T> {
impl<T: SimdRealField> Rotation2<T> {
/// Builds a rotation from a basis assumed to be orthonormal.
///
/// In order to get a valid unit-quaternion, the input must be an
/// In order to get a valid rotation matrix, the input must be an
/// orthonormal basis, i.e., all vectors are normalized, and the are
/// all orthogonal to each other. These invariants are not checked
/// by this method.
@ -204,7 +204,7 @@ impl<T: SimdRealField> Rotation2<T> {
*self = Self::from_matrix_eps(self.matrix(), T::default_epsilon(), 0, c.into())
}
/// Raise the quaternion to a given floating power, i.e., returns the rotation with the angle
/// Raise the rotation to a given floating power, i.e., returns the rotation with the angle
/// of `self` multiplied by `n`.
///
/// # Example
@ -660,7 +660,7 @@ where
other * self.inverse()
}
/// Raise the quaternion to a given floating power, i.e., returns the rotation with the same
/// Raise the rotation 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`.
///
/// # Example
@ -692,7 +692,7 @@ where
/// Builds a rotation from a basis assumed to be orthonormal.
///
/// In order to get a valid unit-quaternion, the input must be an
/// In order to get a valid rotation matrix, the input must be an
/// orthonormal basis, i.e., all vectors are normalized, and the are
/// all orthogonal to each other. These invariants are not checked
/// by this method.
@ -846,7 +846,7 @@ impl<T: SimdRealField> Rotation3<T> {
}
}
/// The rotation axis and angle in ]0, pi] of this unit quaternion.
/// The rotation axis and angle in ]0, pi] of this rotation matrix.
///
/// Returns `None` if the angle is zero.
///

443
src/geometry/scale.rs Executable file
View File

@ -0,0 +1,443 @@
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
use num::{One, Zero};
use std::fmt;
use std::hash;
#[cfg(feature = "abomonation-serialize")]
use std::io::{Result as IOResult, Write};
#[cfg(feature = "serde-serialize-no-std")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "abomonation-serialize")]
use abomonation::Abomonation;
use crate::base::allocator::Allocator;
use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
use crate::base::storage::Owned;
use crate::base::{Const, DefaultAllocator, OMatrix, OVector, SVector, Scalar};
use crate::ClosedDiv;
use crate::ClosedMul;
use crate::geometry::Point;
/// A scale which supports non-uniform scaling.
#[repr(C)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[derive(Copy, Clone)]
pub struct Scale<T, const D: usize> {
/// The scale coordinates, i.e., how much is multiplied to a point's coordinates when it is
/// scaled.
pub vector: SVector<T, D>,
}
impl<T: fmt::Debug, const D: usize> fmt::Debug for Scale<T, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.vector.as_slice().fmt(formatter)
}
}
impl<T: Scalar + hash::Hash, const D: usize> hash::Hash for Scale<T, D>
where
Owned<T, Const<D>>: hash::Hash,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.vector.hash(state)
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<T, const D: usize> bytemuck::Zeroable for Scale<T, D>
where
T: Scalar + bytemuck::Zeroable,
SVector<T, D>: bytemuck::Zeroable,
{
}
#[cfg(feature = "bytemuck")]
unsafe impl<T, const D: usize> bytemuck::Pod for Scale<T, D>
where
T: Scalar + bytemuck::Pod,
SVector<T, D>: bytemuck::Pod,
{
}
#[cfg(feature = "abomonation-serialize")]
impl<T, const D: usize> Abomonation for Scale<T, D>
where
T: Scalar,
SVector<T, D>: Abomonation,
{
unsafe fn entomb<W: Write>(&self, writer: &mut W) -> IOResult<()> {
self.vector.entomb(writer)
}
fn extent(&self) -> usize {
self.vector.extent()
}
unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> {
self.vector.exhume(bytes)
}
}
#[cfg(feature = "serde-serialize-no-std")]
impl<T: Scalar, const D: usize> Serialize for Scale<T, D>
where
Owned<T, Const<D>>: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.vector.serialize(serializer)
}
}
#[cfg(feature = "serde-serialize-no-std")]
impl<'a, T: Scalar, const D: usize> Deserialize<'a> for Scale<T, D>
where
Owned<T, Const<D>>: Deserialize<'a>,
{
fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
where
Des: Deserializer<'a>,
{
let matrix = SVector::<T, D>::deserialize(deserializer)?;
Ok(Scale::from(matrix))
}
}
#[cfg(feature = "rkyv-serialize-no-std")]
mod rkyv_impl {
use super::Scale;
use crate::base::SVector;
use rkyv::{offset_of, project_struct, Archive, Deserialize, Fallible, Serialize};
impl<T: Archive, const D: usize> Archive for Scale<T, D> {
type Archived = Scale<T::Archived, D>;
type Resolver = <SVector<T, D> as Archive>::Resolver;
fn resolve(
&self,
pos: usize,
resolver: Self::Resolver,
out: &mut core::mem::MaybeUninit<Self::Archived>,
) {
self.vector.resolve(
pos + offset_of!(Self::Archived, vector),
resolver,
project_struct!(out: Self::Archived => vector),
);
}
}
impl<T: Serialize<S>, S: Fallible + ?Sized, const D: usize> Serialize<S> for Scale<T, D> {
fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
self.vector.serialize(serializer)
}
}
impl<T: Archive, _D: Fallible + ?Sized, const D: usize> Deserialize<Scale<T, D>, _D>
for Scale<T::Archived, D>
where
T::Archived: Deserialize<T, _D>,
{
fn deserialize(&self, deserializer: &mut _D) -> Result<Scale<T, D>, _D::Error> {
Ok(Scale {
vector: self.vector.deserialize(deserializer)?,
})
}
}
}
impl<T: Scalar, const D: usize> Scale<T, D> {
/// Inverts `self`.
///
/// # Example
/// ```
/// # use nalgebra::{Scale2, Scale3};
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// assert_eq!(t * t.try_inverse().unwrap(), Scale3::identity());
/// assert_eq!(t.try_inverse().unwrap() * t, Scale3::identity());
///
/// // Work in all dimensions.
/// let t = Scale2::new(1.0, 2.0);
/// assert_eq!(t * t.try_inverse().unwrap(), Scale2::identity());
/// assert_eq!(t.try_inverse().unwrap() * t, Scale2::identity());
///
/// // Returns None if any coordinate is 0.
/// let t = Scale2::new(0.0, 2.0);
/// assert_eq!(t.try_inverse(), None);
/// ```
#[inline]
#[must_use = "Did you mean to use try_inverse_mut()?"]
pub fn try_inverse(&self) -> Option<Scale<T, D>>
where
T: ClosedDiv + One + Zero,
{
for i in 0..D {
if self.vector[i] == T::zero() {
return None;
}
}
return Some(self.vector.map(|e| T::one() / e).into());
}
/// Inverts `self`.
///
/// # Example
/// ```
/// # use nalgebra::{Scale2, Scale3};
///
/// unsafe {
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// assert_eq!(t * t.inverse_unchecked(), Scale3::identity());
/// assert_eq!(t.inverse_unchecked() * t, Scale3::identity());
///
/// // Work in all dimensions.
/// let t = Scale2::new(1.0, 2.0);
/// assert_eq!(t * t.inverse_unchecked(), Scale2::identity());
/// assert_eq!(t.inverse_unchecked() * t, Scale2::identity());
/// }
/// ```
#[inline]
#[must_use]
pub unsafe fn inverse_unchecked(&self) -> Scale<T, D>
where
T: ClosedDiv + One,
{
return self.vector.map(|e| T::one() / e).into();
}
/// Inverts `self`.
///
/// # Example
/// ```
/// # use nalgebra::{Scale2, Scale3};
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// assert_eq!(t * t.pseudo_inverse(), Scale3::identity());
/// assert_eq!(t.pseudo_inverse() * t, Scale3::identity());
///
/// // Work in all dimensions.
/// let t = Scale2::new(1.0, 2.0);
/// assert_eq!(t * t.pseudo_inverse(), Scale2::identity());
/// assert_eq!(t.pseudo_inverse() * t, Scale2::identity());
///
/// // Inverts only non-zero coordinates.
/// let t = Scale2::new(0.0, 2.0);
/// assert_eq!(t * t.pseudo_inverse(), Scale2::new(0.0, 1.0));
/// assert_eq!(t.pseudo_inverse() * t, Scale2::new(0.0, 1.0));
/// ```
#[inline]
#[must_use]
pub fn pseudo_inverse(&self) -> Scale<T, D>
where
T: ClosedDiv + One + Zero,
{
return self
.vector
.map(|e| {
if e != T::zero() {
T::one() / e
} else {
T::zero()
}
})
.into();
}
/// Converts this Scale into its equivalent homogeneous transformation matrix.
///
/// # Example
/// ```
/// # use nalgebra::{Scale2, Scale3, Matrix3, Matrix4};
/// let t = Scale3::new(10.0, 20.0, 30.0);
/// let expected = Matrix4::new(10.0, 0.0, 0.0, 0.0,
/// 0.0, 20.0, 0.0, 0.0,
/// 0.0, 0.0, 30.0, 0.0,
/// 0.0, 0.0, 0.0, 1.0);
/// assert_eq!(t.to_homogeneous(), expected);
///
/// let t = Scale2::new(10.0, 20.0);
/// let expected = Matrix3::new(10.0, 0.0, 0.0,
/// 0.0, 20.0, 0.0,
/// 0.0, 0.0, 1.0);
/// assert_eq!(t.to_homogeneous(), expected);
/// ```
#[inline]
#[must_use]
pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
where
T: Zero + One + Clone,
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
+ Allocator<T, DimNameSum<Const<D>, U1>, U1>,
{
// TODO: use self.vector.push() instead. We cant right now because
// that would require the DimAdd bound (but here we use DimNameAdd).
// This should be fixable once Rust gets a more complete support of
// const-generics.
let mut v = OVector::from_element(T::one());
for i in 0..D {
v[i] = self.vector[i].clone();
}
return OMatrix::from_diagonal(&v);
}
/// Inverts `self` in-place.
///
/// # Example
/// ```
/// # use nalgebra::{Scale2, Scale3};
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// let mut inv_t = Scale3::new(1.0, 2.0, 3.0);
/// assert!(inv_t.try_inverse_mut());
/// assert_eq!(t * inv_t, Scale3::identity());
/// assert_eq!(inv_t * t, Scale3::identity());
///
/// // Work in all dimensions.
/// let t = Scale2::new(1.0, 2.0);
/// let mut inv_t = Scale2::new(1.0, 2.0);
/// assert!(inv_t.try_inverse_mut());
/// assert_eq!(t * inv_t, Scale2::identity());
/// assert_eq!(inv_t * t, Scale2::identity());
///
/// // Does not perform any operation if a coordinate is 0.
/// let mut t = Scale2::new(0.0, 2.0);
/// assert!(!t.try_inverse_mut());
/// ```
#[inline]
pub fn try_inverse_mut(&mut self) -> bool
where
T: ClosedDiv + One + Zero,
{
if let Some(v) = self.try_inverse() {
self.vector = v.vector;
true
} else {
false
}
}
}
impl<T: Scalar + ClosedMul, const D: usize> Scale<T, D> {
/// Translate the given point.
///
/// This is the same as the multiplication `self * pt`.
///
/// # Example
/// ```
/// # use nalgebra::{Scale3, Point3};
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// let transformed_point = t.transform_point(&Point3::new(4.0, 5.0, 6.0));
/// assert_eq!(transformed_point, Point3::new(4.0, 10.0, 18.0));
/// ```
#[inline]
#[must_use]
pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
self * pt
}
}
impl<T: Scalar + ClosedDiv + ClosedMul + One + Zero, const D: usize> Scale<T, D> {
/// Translate the given point by the inverse of this Scale.
///
/// # Example
/// ```
/// # use nalgebra::{Scale3, Point3};
/// let t = Scale3::new(1.0, 2.0, 3.0);
/// let transformed_point = t.try_inverse_transform_point(&Point3::new(4.0, 6.0, 6.0)).unwrap();
/// assert_eq!(transformed_point, Point3::new(4.0, 3.0, 2.0));
///
/// // Returns None if the inverse doesn't exist.
/// let t = Scale3::new(1.0, 0.0, 3.0);
/// let transformed_point = t.try_inverse_transform_point(&Point3::new(4.0, 6.0, 6.0));
/// assert_eq!(transformed_point, None);
/// ```
#[inline]
#[must_use]
pub fn try_inverse_transform_point(&self, pt: &Point<T, D>) -> Option<Point<T, D>> {
self.try_inverse().map(|s| s * pt)
}
}
impl<T: Scalar + Eq, const D: usize> Eq for Scale<T, D> {}
impl<T: Scalar + PartialEq, const D: usize> PartialEq for Scale<T, D> {
#[inline]
fn eq(&self, right: &Scale<T, D>) -> bool {
self.vector == right.vector
}
}
impl<T: Scalar + AbsDiffEq, const D: usize> AbsDiffEq for Scale<T, D>
where
T::Epsilon: Clone,
{
type Epsilon = T::Epsilon;
#[inline]
fn default_epsilon() -> Self::Epsilon {
T::default_epsilon()
}
#[inline]
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.vector.abs_diff_eq(&other.vector, epsilon)
}
}
impl<T: Scalar + RelativeEq, const D: usize> RelativeEq for Scale<T, D>
where
T::Epsilon: Clone,
{
#[inline]
fn default_max_relative() -> Self::Epsilon {
T::default_max_relative()
}
#[inline]
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.vector
.relative_eq(&other.vector, epsilon, max_relative)
}
}
impl<T: Scalar + UlpsEq, const D: usize> UlpsEq for Scale<T, D>
where
T::Epsilon: Clone,
{
#[inline]
fn default_max_ulps() -> u32 {
T::default_max_ulps()
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.vector.ulps_eq(&other.vector, epsilon, max_ulps)
}
}
/*
*
* Display
*
*/
impl<T: Scalar + fmt::Display, const D: usize> fmt::Display for Scale<T, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let precision = f.precision().unwrap_or(3);
writeln!(f, "Scale {{")?;
write!(f, "{:.*}", precision, self.vector)?;
writeln!(f, "}}")
}
}

View File

@ -0,0 +1,19 @@
use crate::geometry::Scale;
/// A 1-dimensional scale.
pub type Scale1<T> = Scale<T, 1>;
/// A 2-dimensional scale.
pub type Scale2<T> = Scale<T, 2>;
/// A 3-dimensional scale.
pub type Scale3<T> = Scale<T, 3>;
/// A 4-dimensional scale.
pub type Scale4<T> = Scale<T, 4>;
/// A 5-dimensional scale.
pub type Scale5<T> = Scale<T, 5>;
/// A 6-dimensional scale.
pub type Scale6<T> = Scale<T, 6>;

View File

@ -0,0 +1,123 @@
#[cfg(feature = "arbitrary")]
use crate::base::storage::Owned;
#[cfg(feature = "arbitrary")]
use quickcheck::{Arbitrary, Gen};
use num::One;
#[cfg(feature = "rand-no-std")]
use rand::{
distributions::{Distribution, Standard},
Rng,
};
use simba::scalar::{ClosedMul, SupersetOf};
use crate::base::{SVector, Scalar};
use crate::geometry::Scale;
impl<T: Scalar, const D: usize> Scale<T, D> {
/// Creates a new identity scale.
///
/// # Example
/// ```
/// # use nalgebra::{Point2, Point3, Scale2, Scale3};
/// let t = Scale2::identity();
/// let p = Point2::new(1.0, 2.0);
/// assert_eq!(t * p, p);
///
/// // Works in all dimensions.
/// let t = Scale3::identity();
/// let p = Point3::new(1.0, 2.0, 3.0);
/// assert_eq!(t * p, p);
/// ```
#[inline]
pub fn identity() -> Scale<T, D>
where
T: One,
{
Scale::from(SVector::from_element(T::one()))
}
/// Cast the components of `self` to another type.
///
/// # Example
/// ```
/// # use nalgebra::Scale2;
/// let tra = Scale2::new(1.0f64, 2.0);
/// let tra2 = tra.cast::<f32>();
/// assert_eq!(tra2, Scale2::new(1.0f32, 2.0));
/// ```
pub fn cast<To: Scalar>(self) -> Scale<To, D>
where
Scale<To, D>: SupersetOf<Self>,
{
crate::convert(self)
}
}
impl<T: Scalar + One + ClosedMul, const D: usize> One for Scale<T, D> {
#[inline]
fn one() -> Self {
Self::identity()
}
}
#[cfg(feature = "rand-no-std")]
impl<T: Scalar, const D: usize> Distribution<Scale<T, D>> for Standard
where
Standard: Distribution<T>,
{
/// Generate an arbitrary random variate for testing purposes.
#[inline]
fn sample<G: Rng + ?Sized>(&self, rng: &mut G) -> Scale<T, D> {
Scale::from(rng.gen::<SVector<T, D>>())
}
}
#[cfg(feature = "arbitrary")]
impl<T: Scalar + Arbitrary + Send, const D: usize> Arbitrary for Scale<T, D>
where
Owned<T, crate::Const<D>>: Send,
{
#[inline]
fn arbitrary(rng: &mut Gen) -> Self {
let v: SVector<T, D> = Arbitrary::arbitrary(rng);
Self::from(v)
}
}
/*
*
* Small Scale construction from components.
*
*/
macro_rules! componentwise_constructors_impl(
($($doc: expr; $D: expr, $($args: ident:$irow: expr),*);* $(;)*) => {$(
impl<T> Scale<T, $D>
{
#[doc = "Initializes this Scale from its components."]
#[doc = "# Example\n```"]
#[doc = $doc]
#[doc = "```"]
#[inline]
pub const fn new($($args: T),*) -> Self {
Self { vector: SVector::<T, $D>::new($($args),*) }
}
}
)*}
);
componentwise_constructors_impl!(
"# use nalgebra::Scale1;\nlet t = Scale1::new(1.0);\nassert!(t.vector.x == 1.0);";
1, x:0;
"# use nalgebra::Scale2;\nlet t = Scale2::new(1.0, 2.0);\nassert!(t.vector.x == 1.0 && t.vector.y == 2.0);";
2, x:0, y:1;
"# use nalgebra::Scale3;\nlet t = Scale3::new(1.0, 2.0, 3.0);\nassert!(t.vector.x == 1.0 && t.vector.y == 2.0 && t.vector.z == 3.0);";
3, x:0, y:1, z:2;
"# use nalgebra::Scale4;\nlet t = Scale4::new(1.0, 2.0, 3.0, 4.0);\nassert!(t.vector.x == 1.0 && t.vector.y == 2.0 && t.vector.z == 3.0 && t.vector.w == 4.0);";
4, x:0, y:1, z:2, w:3;
"# use nalgebra::Scale5;\nlet t = Scale5::new(1.0, 2.0, 3.0, 4.0, 5.0);\nassert!(t.vector.x == 1.0 && t.vector.y == 2.0 && t.vector.z == 3.0 && t.vector.w == 4.0 && t.vector.a == 5.0);";
5, x:0, y:1, z:2, w:3, a:4;
"# use nalgebra::Scale6;\nlet t = Scale6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);\nassert!(t.vector.x == 1.0 && t.vector.y == 2.0 && t.vector.z == 3.0 && t.vector.w == 4.0 && t.vector.a == 5.0 && t.vector.b == 6.0);";
6, x:0, y:1, z:2, w:3, a:4, b:5;
);

View File

@ -0,0 +1,233 @@
use num::{One, Zero};
use simba::scalar::{RealField, SubsetOf, SupersetOf};
use simba::simd::PrimitiveSimdValue;
use crate::base::allocator::Allocator;
use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
use crate::base::{Const, DefaultAllocator, OMatrix, OVector, SVector, Scalar};
use crate::geometry::{Scale, SuperTCategoryOf, TAffine, Transform};
use crate::Point;
/*
* This file provides the following conversions:
* =============================================
*
* Scale -> Scale
* Scale -> Transform
* Scale -> Matrix (homogeneous)
*/
impl<T1, T2, const D: usize> SubsetOf<Scale<T2, D>> for Scale<T1, D>
where
T1: Scalar,
T2: Scalar + SupersetOf<T1>,
{
#[inline]
fn to_superset(&self) -> Scale<T2, D> {
Scale::from(self.vector.to_superset())
}
#[inline]
fn is_in_subset(rot: &Scale<T2, D>) -> bool {
crate::is_convertible::<_, SVector<T1, D>>(&rot.vector)
}
#[inline]
fn from_superset_unchecked(rot: &Scale<T2, D>) -> Self {
Scale {
vector: rot.vector.to_subset_unchecked(),
}
}
}
impl<T1, T2, C, const D: usize> SubsetOf<Transform<T2, C, D>> for Scale<T1, D>
where
T1: RealField,
T2: RealField + SupersetOf<T1>,
C: SuperTCategoryOf<TAffine>,
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T1, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
+ Allocator<T1, DimNameSum<Const<D>, U1>, U1>
+ Allocator<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
{
#[inline]
fn to_superset(&self) -> Transform<T2, C, D> {
Transform::from_matrix_unchecked(self.to_homogeneous().to_superset())
}
#[inline]
fn is_in_subset(t: &Transform<T2, C, D>) -> bool {
<Self as SubsetOf<_>>::is_in_subset(t.matrix())
}
#[inline]
fn from_superset_unchecked(t: &Transform<T2, C, D>) -> Self {
Self::from_superset_unchecked(t.matrix())
}
}
impl<T1, T2, const D: usize>
SubsetOf<OMatrix<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>> for Scale<T1, D>
where
T1: RealField,
T2: RealField + SupersetOf<T1>,
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T1, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
+ Allocator<T1, DimNameSum<Const<D>, U1>, U1>
+ Allocator<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
{
#[inline]
fn to_superset(&self) -> OMatrix<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
self.to_homogeneous().to_superset()
}
#[inline]
fn is_in_subset(m: &OMatrix<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>) -> bool {
if m[(D, D)] != T2::one() {
return false;
}
for i in 0..D + 1 {
for j in 0..D + 1 {
if i != j && m[(i, j)] != T2::zero() {
return false;
}
}
}
true
}
#[inline]
fn from_superset_unchecked(
m: &OMatrix<T2, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
) -> Self {
let v = m.fixed_slice::<D, D>(0, 0).diagonal();
Self {
vector: crate::convert_unchecked(v),
}
}
}
impl<T: Scalar + Zero + One, const D: usize> From<Scale<T, D>>
for OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
where
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
+ Allocator<T, DimNameSum<Const<D>, U1>, U1>
+ Allocator<T, Const<D>>,
{
#[inline]
fn from(t: Scale<T, D>) -> Self {
t.to_homogeneous()
}
}
impl<T: Scalar, const D: usize> From<OVector<T, Const<D>>> for Scale<T, D> {
#[inline]
fn from(vector: OVector<T, Const<D>>) -> Self {
Scale { vector }
}
}
impl<T: Scalar, const D: usize> From<[T; D]> for Scale<T, D> {
#[inline]
fn from(coords: [T; D]) -> Self {
Scale {
vector: coords.into(),
}
}
}
impl<T: Scalar, const D: usize> From<Point<T, D>> for Scale<T, D> {
#[inline]
fn from(pt: Point<T, D>) -> Self {
Scale { vector: pt.coords }
}
}
impl<T: Scalar, const D: usize> From<Scale<T, D>> for [T; D] {
#[inline]
fn from(t: Scale<T, D>) -> Self {
t.vector.into()
}
}
impl<T: Scalar + PrimitiveSimdValue, const D: usize> From<[Scale<T::Element, D>; 2]> for Scale<T, D>
where
T: From<[<T as simba::simd::SimdValue>::Element; 2]>,
T::Element: Scalar,
{
#[inline]
fn from(arr: [Scale<T::Element, D>; 2]) -> Self {
Self::from(OVector::from([
arr[0].vector.clone(),
arr[1].vector.clone(),
]))
}
}
impl<T: Scalar + PrimitiveSimdValue, const D: usize> From<[Scale<T::Element, D>; 4]> for Scale<T, D>
where
T: From<[<T as simba::simd::SimdValue>::Element; 4]>,
T::Element: Scalar,
{
#[inline]
fn from(arr: [Scale<T::Element, D>; 4]) -> Self {
Self::from(OVector::from([
arr[0].vector.clone(),
arr[1].vector.clone(),
arr[2].vector.clone(),
arr[3].vector.clone(),
]))
}
}
impl<T: Scalar + PrimitiveSimdValue, const D: usize> From<[Scale<T::Element, D>; 8]> for Scale<T, D>
where
T: From<[<T as simba::simd::SimdValue>::Element; 8]>,
T::Element: Scalar,
{
#[inline]
fn from(arr: [Scale<T::Element, D>; 8]) -> Self {
Self::from(OVector::from([
arr[0].vector.clone(),
arr[1].vector.clone(),
arr[2].vector.clone(),
arr[3].vector.clone(),
arr[4].vector.clone(),
arr[5].vector.clone(),
arr[6].vector.clone(),
arr[7].vector.clone(),
]))
}
}
impl<T: Scalar + PrimitiveSimdValue, const D: usize> From<[Scale<T::Element, D>; 16]>
for Scale<T, D>
where
T: From<[<T as simba::simd::SimdValue>::Element; 16]>,
T::Element: Scalar,
{
#[inline]
fn from(arr: [Scale<T::Element, D>; 16]) -> Self {
Self::from(OVector::from([
arr[0].vector.clone(),
arr[1].vector.clone(),
arr[2].vector.clone(),
arr[3].vector.clone(),
arr[4].vector.clone(),
arr[5].vector.clone(),
arr[6].vector.clone(),
arr[7].vector.clone(),
arr[8].vector.clone(),
arr[9].vector.clone(),
arr[10].vector.clone(),
arr[11].vector.clone(),
arr[12].vector.clone(),
arr[13].vector.clone(),
arr[14].vector.clone(),
arr[15].vector.clone(),
]))
}
}

View File

@ -0,0 +1,39 @@
use std::ops::{Deref, DerefMut};
use crate::base::coordinates::{X, XY, XYZ, XYZW, XYZWA, XYZWAB};
use crate::base::Scalar;
use crate::geometry::Scale;
/*
*
* Give coordinates to Scale{1 .. 6}
*
*/
macro_rules! deref_impl(
($D: expr, $Target: ident $(, $comps: ident)*) => {
impl<T: Scalar> Deref for Scale<T, $D> {
type Target = $Target<T>;
#[inline]
fn deref(&self) -> &Self::Target {
self.vector.deref()
}
}
impl<T: Scalar> DerefMut for Scale<T, $D> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.vector.deref_mut()
}
}
}
);
deref_impl!(1, X, x);
deref_impl!(2, XY, x, y);
deref_impl!(3, XYZ, x, y, z);
deref_impl!(4, XYZW, x, y, z, w);
deref_impl!(5, XYZWA, x, y, z, w, a);
deref_impl!(6, XYZWAB, x, y, z, w, a, b);

125
src/geometry/scale_ops.rs Normal file
View File

@ -0,0 +1,125 @@
use std::ops::{Mul, MulAssign};
use simba::scalar::ClosedMul;
use crate::base::constraint::{SameNumberOfColumns, SameNumberOfRows, ShapeConstraint};
use crate::base::dimension::U1;
use crate::base::{Const, SVector, Scalar};
use crate::geometry::{Point, Scale};
// Scale × Scale
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: &'b Scale<T, D>, Output = Scale<T, D>;
Scale::from(self.vector.component_mul(&right.vector));
'a, 'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: Scale<T, D>, Output = Scale<T, D>;
Scale::from(self.vector.component_mul(&right.vector));
'a);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: &'b Scale<T, D>, Output = Scale<T, D>;
Scale::from(self.vector.component_mul(&right.vector));
'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: Scale<T, D>, Output = Scale<T, D>;
Scale::from(self.vector.component_mul(&right.vector)); );
// Scale × scalar
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: T, Output = Scale<T, D>;
Scale::from(&self.vector * right);
'a);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: T, Output = Scale<T, D>;
Scale::from(self.vector * right); );
// Scale × Point
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: &'b Point<T, D>, Output = Point<T, D>;
Point::from(self.vector.component_mul(&right.coords));
'a, 'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: Point<T, D>, Output = Point<T, D>;
Point::from(self.vector.component_mul(&right.coords));
'a);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: &'b Point<T, D>, Output = Point<T, D>;
Point::from(self.vector.component_mul(&right.coords));
'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: Point<T, D>, Output = Point<T, D>;
Point::from(self.vector.component_mul(&right.coords)); );
// Scale * Vector
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: &'b SVector<T, D>, Output = SVector<T, D>;
SVector::from(self.vector.component_mul(&right));
'a, 'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: &'a Scale<T, D>, right: SVector<T, D>, Output = SVector<T, D>;
SVector::from(self.vector.component_mul(&right));
'a);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: &'b SVector<T, D>, Output = SVector<T, D>;
SVector::from(self.vector.component_mul(&right));
'b);
add_sub_impl!(Mul, mul, ClosedMul;
(Const<D>, U1), (Const<D>, U1) -> (Const<D>, U1)
const D; for; where;
self: Scale<T, D>, right: SVector<T, D>, Output = SVector<T, D>;
SVector::from(self.vector.component_mul(&right)); );
// Scale *= Scale
add_sub_assign_impl!(MulAssign, mul_assign, ClosedMul;
const D;
self: Scale<T, D>, right: &'b Scale<T, D>;
self.vector.component_mul_assign(&right.vector);
'b);
add_sub_assign_impl!(MulAssign, mul_assign, ClosedMul;
const D;
self: Scale<T, D>, right: Scale<T, D>;
self.vector.component_mul_assign(&right.vector); );
// Scale ×= scalar
add_sub_assign_impl!(MulAssign, mul_assign, ClosedMul;
const D;
self: Scale<T, D>, right: T;
self.vector *= right; );

49
src/geometry/scale_simba.rs Executable file
View File

@ -0,0 +1,49 @@
use simba::simd::SimdValue;
use crate::base::OVector;
use crate::Scalar;
use crate::geometry::Scale;
impl<T: Scalar + SimdValue, const D: usize> SimdValue for Scale<T, D>
where
T::Element: Scalar,
{
type Element = Scale<T::Element, D>;
type SimdBool = T::SimdBool;
#[inline]
fn lanes() -> usize {
T::lanes()
}
#[inline]
fn splat(val: Self::Element) -> Self {
OVector::splat(val.vector).into()
}
#[inline]
fn extract(&self, i: usize) -> Self::Element {
self.vector.extract(i).into()
}
#[inline]
unsafe fn extract_unchecked(&self, i: usize) -> Self::Element {
self.vector.extract_unchecked(i).into()
}
#[inline]
fn replace(&mut self, i: usize, val: Self::Element) {
self.vector.replace(i, val.vector)
}
#[inline]
unsafe fn replace_unchecked(&mut self, i: usize, val: Self::Element) {
self.vector.replace_unchecked(i, val.vector)
}
#[inline]
fn select(self, cond: Self::SimdBool, other: Self) -> Self {
self.vector.select(cond, other.vector).into()
}
}

View File

@ -23,7 +23,11 @@ use crate::geometry::{AbstractRotation, Isometry, Point, Translation};
/// A similarity, i.e., an uniform scaling, followed by a rotation, followed by a translation.
#[repr(C)]
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde-serialize-no-std",
@ -73,22 +77,6 @@ where
}
}
impl<T: Scalar + Copy + Zero, R: AbstractRotation<T, D> + Copy, const D: usize> Copy
for Similarity<T, R, D>
where
Owned<T, Const<D>>: Copy,
{
}
impl<T: Scalar + Zero, R: AbstractRotation<T, D> + Clone, const D: usize> Clone
for Similarity<T, R, D>
{
#[inline]
fn clone(&self) -> Self {
Similarity::from_isometry(self.isometry.clone(), self.scaling.clone())
}
}
impl<T: Scalar + Zero, R, const D: usize> Similarity<T, R, D>
where
R: AbstractRotation<T, D>,
@ -415,7 +403,7 @@ where
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.isometry
.ulps_eq(&other.isometry, epsilon.clone(), max_ulps.clone())
.ulps_eq(&other.isometry, epsilon.clone(), max_ulps)
&& self.scaling.ulps_eq(&other.scaling, epsilon, max_ulps)
}
}

View File

@ -20,6 +20,16 @@ use crate::{
Translation, UnitComplex, UnitQuaternion,
};
impl<T: SimdRealField, R, const D: usize> Default for Similarity<T, R, D>
where
T::Element: SimdRealField,
R: AbstractRotation<T, D>,
{
fn default() -> Self {
Self::identity()
}
}
impl<T: SimdRealField, R, const D: usize> Similarity<T, R, D>
where
T::Element: SimdRealField,

View File

@ -1,6 +1,6 @@
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
use std::any::Any;
use std::fmt::Debug;
use std::fmt::{self, Debug};
use std::hash;
use std::marker::PhantomData;
@ -60,14 +60,26 @@ where
/// Tag representing the most general (not necessarily inversible) `Transform` type.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub enum TGeneral {}
/// Tag representing the most general inversible `Transform` type.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub enum TProjective {}
/// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
pub enum TAffine {}
impl TCategory for TGeneral {
@ -157,7 +169,6 @@ super_tcategory_impl!(
/// 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)]
pub struct Transform<T: RealField, C: TCategory, const D: usize>
where
Const<D>: DimNameAdd<U1>,
@ -167,6 +178,16 @@ where
_phantom: PhantomData<C>,
}
impl<T: RealField + Debug, C: TCategory, const D: usize> Debug for Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.matrix.fmt(formatter)
}
}
impl<T: RealField + hash::Hash, C: TCategory, const D: usize> hash::Hash for Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,
@ -186,6 +207,16 @@ where
{
}
#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
unsafe impl<T: RealField + cust::memory::DeviceCopy, C: TCategory, const D: usize>
cust::memory::DeviceCopy for Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: cust::memory::DeviceCopy,
{
}
impl<T: RealField, C: TCategory, const D: usize> Clone for Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,

View File

@ -8,6 +8,16 @@ use crate::base::{Const, DefaultAllocator, OMatrix};
use crate::geometry::{TCategory, Transform};
impl<T: RealField, C: TCategory, const D: usize> Default for Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,
DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
{
fn default() -> Self {
Self::identity()
}
}
impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
where
Const<D>: DimNameAdd<U1>,

View File

@ -22,13 +22,23 @@ use crate::geometry::Point;
/// A translation.
#[repr(C)]
#[derive(Debug)]
#[cfg_attr(
all(not(target_os = "cuda"), feature = "cuda"),
derive(cust::DeviceCopy)
)]
#[derive(Copy, Clone)]
pub struct Translation<T, const D: usize> {
/// The translation coordinates, i.e., how much is added to a point's coordinates when it is
/// translated.
pub vector: SVector<T, D>,
}
impl<T: fmt::Debug, const D: usize> fmt::Debug for Translation<T, D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.vector.as_slice().fmt(formatter)
}
}
impl<T: Scalar + hash::Hash, const D: usize> hash::Hash for Translation<T, D>
where
Owned<T, Const<D>>: hash::Hash,
@ -38,18 +48,6 @@ where
}
}
impl<T: Scalar + Copy, const D: usize> Copy for Translation<T, D> {}
impl<T: Scalar, const D: usize> Clone for Translation<T, D>
where
Owned<T, Const<D>>: Clone,
{
#[inline]
fn clone(&self) -> Self {
Translation::from(self.vector.clone())
}
}
#[cfg(feature = "bytemuck")]
unsafe impl<T, const D: usize> bytemuck::Zeroable for Translation<T, D>
where

View File

@ -15,6 +15,12 @@ use simba::scalar::{ClosedAdd, SupersetOf};
use crate::base::{SVector, Scalar};
use crate::geometry::Translation;
impl<T: Scalar + Zero, const D: usize> Default for Translation<T, D> {
fn default() -> Self {
Self::identity()
}
}
impl<T: Scalar, const D: usize> Translation<T, D> {
/// Creates a new identity translation.
///

View File

@ -31,6 +31,9 @@ use std::cmp::{Eq, PartialEq};
/// * [Conversion to a matrix <span style="float:right;">`to_rotation_matrix`, `to_homogeneous`…</span>](#conversion-to-a-matrix)
pub type UnitComplex<T> = Unit<Complex<T>>;
#[cfg(all(not(target_os = "cuda"), feature = "cuda"))]
unsafe impl<T: cust::memory::DeviceCopy> cust::memory::DeviceCopy for UnitComplex<T> {}
impl<T: Scalar + PartialEq> PartialEq for UnitComplex<T> {
#[inline]
fn eq(&self, rhs: &Self) -> bool {
@ -458,8 +461,7 @@ impl<T: RealField> UlpsEq for UnitComplex<T> {
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.re
.ulps_eq(&other.re, epsilon.clone(), max_ulps.clone())
self.re.ulps_eq(&other.re, epsilon.clone(), max_ulps)
&& self.im.ulps_eq(&other.im, epsilon, max_ulps)
}
}

View File

@ -17,6 +17,15 @@ use crate::geometry::{Rotation2, UnitComplex};
use simba::scalar::{RealField, SupersetOf};
use simba::simd::SimdRealField;
impl<T: SimdRealField> Default for UnitComplex<T>
where
T::Element: SimdRealField,
{
fn default() -> Self {
Self::identity()
}
}
/// # Identity
impl<T: SimdRealField> UnitComplex<T>
where

View File

@ -71,10 +71,11 @@ an optimized set of tools for computer graphics and physics. Those features incl
* Insertion and removal of rows of columns of a matrix.
*/
#![allow(unused_variables, unused_mut)]
#![deny(
missing_docs,
nonstandard_style,
unused_variables,
unused_mut,
unused_parens,
unused_qualifications,
unused_results,

View File

@ -74,6 +74,14 @@ where
Cholesky { chol: matrix }
}
/// Uses the given matrix as-is without any checks or modifications as the
/// Cholesky decomposition.
///
/// It is up to the user to ensure all invariants hold.
pub fn pack_dirty(matrix: OMatrix<T, D, D>) -> Self {
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) -> OMatrix<T, D, D> {
@ -163,7 +171,32 @@ where
///
/// Returns `None` if the input matrix is not definite-positive. The input matrix is assumed
/// to be symmetric and only the lower-triangular part is read.
pub fn new(mut matrix: OMatrix<T, D, D>) -> Option<Self> {
pub fn new(matrix: OMatrix<T, D, D>) -> Option<Self> {
Self::new_internal(matrix, None)
}
/// Attempts to approximate the Cholesky decomposition of `matrix` by
/// replacing non-positive values on the diagonals during the decomposition
/// with the given `substitute`.
///
/// [`try_sqrt`](ComplexField::try_sqrt) will be applied to the `substitute`
/// when it has to be used.
///
/// If your input matrix results only in positive values on the diagonals
/// during the decomposition, `substitute` is unused and the result is just
/// the same as if you used [`new`](Cholesky::new).
///
/// This method allows to compensate for matrices with very small or even
/// negative values due to numerical errors but necessarily results in only
/// an approximation: it is basically a hack. If you don't specifically need
/// Cholesky, it may be better to consider alternatives like the
/// [`LU`](crate::linalg::LU) decomposition/factorization.
pub fn new_with_substitute(matrix: OMatrix<T, D, D>, substitute: T) -> Option<Self> {
Self::new_internal(matrix, Some(substitute))
}
/// Common implementation for `new` and `new_with_substitute`.
fn new_internal(mut matrix: OMatrix<T, D, D>, substitute: Option<T>) -> Option<Self> {
assert!(matrix.is_square(), "The input matrix must be square.");
let n = matrix.nrows();
@ -179,17 +212,25 @@ where
col_j.axpy(factor.conjugate(), &col_k, T::one());
}
let diag = unsafe { matrix.get_unchecked((j, j)).clone() };
if !diag.is_zero() {
if let Some(denom) = diag.try_sqrt() {
unsafe {
*matrix.get_unchecked_mut((j, j)) = denom.clone();
}
let mut col = matrix.slice_range_mut(j + 1.., j);
col /= denom;
continue;
let sqrt_denom = |v: T| {
if v.is_zero() {
return None;
}
v.try_sqrt()
};
let diag = unsafe { matrix.get_unchecked((j, j)).clone() };
if let Some(denom) =
sqrt_denom(diag).or_else(|| substitute.clone().and_then(sqrt_denom))
{
unsafe {
*matrix.get_unchecked_mut((j, j)) = denom.clone();
}
let mut col = matrix.slice_range_mut(j + 1.., j);
col /= denom;
continue;
}
// The diagonal element is either zero or its square root could not

View File

@ -1,8 +1,8 @@
use crate::storage::Storage;
use crate::{
Allocator, Bidiagonal, Cholesky, ColPivQR, ComplexField, DefaultAllocator, Dim, DimDiff,
DimMin, DimMinimum, DimSub, FullPivLU, Hessenberg, Matrix, RealField, Schur, SymmetricEigen,
SymmetricTridiagonal, LU, QR, SVD, U1, UDU,
DimMin, DimMinimum, DimSub, FullPivLU, Hessenberg, Matrix, OMatrix, RealField, Schur,
SymmetricEigen, SymmetricTridiagonal, LU, QR, SVD, U1, UDU,
};
/// # Rectangular matrix decomposition
@ -17,6 +17,7 @@ use crate::{
/// | LU with partial pivoting | `P⁻¹ * L * U` | `L` is lower-triangular with a diagonal filled with `1` and `U` is upper-triangular. `P` is a permutation matrix. |
/// | LU with full pivoting | `P⁻¹ * L * U * Q⁻¹` | `L` is lower-triangular with a diagonal filled with `1` and `U` is upper-triangular. `P` and `Q` are permutation matrices. |
/// | SVD | `U * Σ * Vᵀ` | `U` and `V` are two orthogonal matrices and `Σ` is a diagonal matrix containing the singular values. |
/// | Polar (Left Polar) | `P' * U` | `U` is semi-unitary/unitary and `P'` is a positive semi-definite Hermitian Matrix
impl<T: ComplexField, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
/// Computes the bidiagonalization using householder reflections.
pub fn bidiagonalize(self) -> Bidiagonal<T, R, C>
@ -74,7 +75,31 @@ impl<T: ComplexField, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
}
/// Computes the Singular Value Decomposition using implicit shift.
/// The singular values are guaranteed to be sorted in descending order.
/// If this order is not required consider using `svd_unordered`.
pub fn svd(self, compute_u: bool, compute_v: bool) -> SVD<T, R, C>
where
R: DimMin<C>,
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<(usize, usize), DimMinimum<R, C>>
+ Allocator<(T::RealField, usize), DimMinimum<R, C>>,
{
SVD::new(self.into_owned(), compute_u, compute_v)
}
/// Computes the Singular Value Decomposition using implicit shift.
/// The singular values are not guaranteed to be sorted in any particular order.
/// If a descending order is required, consider using `svd` instead.
pub fn svd_unordered(self, compute_u: bool, compute_v: bool) -> SVD<T, R, C>
where
R: DimMin<C>,
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
@ -88,10 +113,12 @@ impl<T: ComplexField, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
SVD::new(self.into_owned(), compute_u, compute_v)
SVD::new_unordered(self.into_owned(), compute_u, compute_v)
}
/// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift.
/// The singular values are guaranteed to be sorted in descending order.
/// If this order is not required consider using `try_svd_unordered`.
///
/// # Arguments
///
@ -119,10 +146,103 @@ impl<T: ComplexField, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<(usize, usize), DimMinimum<R, C>>
+ Allocator<(T::RealField, usize), DimMinimum<R, C>>,
{
SVD::try_new(self.into_owned(), compute_u, compute_v, eps, max_niter)
}
/// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift.
/// The singular values are not guaranteed to be sorted in any particular order.
/// If a descending order is required, consider using `try_svd` instead.
///
/// # 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 right-singular vectors.
/// * `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_svd_unordered(
self,
compute_u: bool,
compute_v: bool,
eps: T::RealField,
max_niter: usize,
) -> Option<SVD<T, R, C>>
where
R: DimMin<C>,
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
SVD::try_new_unordered(self.into_owned(), compute_u, compute_v, eps, max_niter)
}
/// Computes the Polar Decomposition of a `matrix` (indirectly uses SVD).
pub fn polar(self) -> (OMatrix<T, R, R>, OMatrix<T, R, C>)
where
R: DimMin<C>,
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, DimMinimum<R, C>, R>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T, R, R>
+ Allocator<T, DimMinimum<R, C>, DimMinimum<R, C>>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
SVD::new_unordered(self.into_owned(), true, true)
.to_polar()
.unwrap()
}
/// Attempts to compute the Polar Decomposition of a `matrix` (indirectly uses SVD).
///
/// # Arguments
///
/// * `eps` tolerance used to determine when a value converged to 0 when computing the SVD.
/// * `max_niter` maximum total number of iterations performed by the SVD computation algorithm.
pub fn try_polar(
self,
eps: T::RealField,
max_niter: usize,
) -> Option<(OMatrix<T, R, R>, OMatrix<T, R, C>)>
where
R: DimMin<C>,
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, DimMinimum<R, C>, R>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T, R, R>
+ Allocator<T, DimMinimum<R, C>, DimMinimum<R, C>>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
SVD::try_new_unordered(self.into_owned(), true, true, eps, max_niter)
.and_then(|svd| svd.to_polar())
}
}
/// # Square matrix decomposition

View File

@ -49,7 +49,7 @@ impl<T: ComplexField, D: DimMin<D, Output = D>, S: Storage<T, D, D>> SquareMatri
let m33 = self.get_unchecked((2, 2)).clone();
let minor_m12_m23 = m22.clone() * m33.clone() - m32.clone() * m23.clone();
let minor_m11_m23 = m21.clone() * m33.clone() - m31.clone() * m23.clone();
let minor_m11_m23 = m21.clone() * m33 - m31.clone() * m23;
let minor_m11_m22 = m21 * m32 - m31 * m22;
m11 * minor_m12_m23 - m12 * minor_m11_m23 + m13 * minor_m11_m22

View File

@ -11,6 +11,47 @@ use crate::{
use crate::num::Zero;
/// Precomputed factorials for integers in range `0..=34`.
/// Note: `35!` does not fit into 128 bits.
// TODO: find a better place for this array?
const FACTORIAL: [u128; 35] = [
1,
1,
2,
6,
24,
120,
720,
5040,
40320,
362880,
3628800,
39916800,
479001600,
6227020800,
87178291200,
1307674368000,
20922789888000,
355687428096000,
6402373705728000,
121645100408832000,
2432902008176640000,
51090942171709440000,
1124000727777607680000,
25852016738884976640000,
620448401733239439360000,
15511210043330985984000000,
403291461126605635584000000,
10888869450418352160768000000,
304888344611713860501504000000,
8841761993739701954543616000000,
265252859812191058636308480000000,
8222838654177922817725562880000000,
263130836933693530167218012160000000,
8683317618811886495518194401280000000,
295232799039604140847618609643520000000,
];
// https://github.com/scipy/scipy/blob/c1372d8aa90a73d8a52f135529293ff4edb98fc8/scipy/sparse/linalg/matfuncs.py
struct ExpmPadeHelper<T, D>
where
@ -321,8 +362,8 @@ where
self.calc_a2();
self.calc_a4();
self.calc_a6();
let mb2 = self.a2.as_ref().unwrap() * convert::<f64, T>(2.0_f64.powf(-2.0 * s.clone()));
let mb4 = self.a4.as_ref().unwrap() * convert::<f64, T>(2.0.powf(-4.0 * s.clone()));
let mb2 = self.a2.as_ref().unwrap() * convert::<f64, T>(2.0_f64.powf(-2.0 * s));
let mb4 = self.a4.as_ref().unwrap() * convert::<f64, T>(2.0.powf(-4.0 * s));
let mb6 = self.a6.as_ref().unwrap() * convert::<f64, T>(2.0.powf(-6.0 * s));
let u2 = &mb6 * (&mb6 * b[13].clone() + &mb4 * b[11].clone() + &mb2 * b[9].clone());
@ -342,15 +383,17 @@ where
}
}
fn factorial(n: u128) -> u128 {
if n == 1 {
return 1;
/// Compute `n!`
#[inline(always)]
fn factorial(n: usize) -> u128 {
match FACTORIAL.get(n) {
Some(f) => *f,
None => panic!("{}! is greater than u128::MAX", n),
}
n * factorial(n - 1)
}
/// Compute the 1-norm of a non-negative integer power of a non-negative matrix.
fn onenorm_matrix_power_nonm<T, D>(a: &OMatrix<T, D, D>, p: u64) -> T
fn onenorm_matrix_power_nonm<T, D>(a: &OMatrix<T, D, D>, p: usize) -> T
where
T: RealField,
D: Dim,
@ -367,7 +410,7 @@ where
v.max()
}
fn ell<T, D>(a: &OMatrix<T, D, D>, m: u64) -> u64
fn ell<T, D>(a: &OMatrix<T, D, D>, m: usize) -> u64
where
T: ComplexField,
D: Dim,
@ -376,8 +419,6 @@ where
+ Allocator<T::RealField, D>
+ Allocator<T::RealField, D, D>,
{
// 2m choose m = (2m)!/(m! * (2m-m)!)
let a_abs = a.map(|x| x.abs());
let a_abs_onenorm = onenorm_matrix_power_nonm(&a_abs, 2 * m + 1);
@ -386,9 +427,11 @@ where
return 0;
}
let choose_2m_m =
factorial(2 * m as u128) / (factorial(m as u128) * factorial(2 * m as u128 - m as u128));
let abs_c_recip = choose_2m_m * factorial(2 * m as u128 + 1);
// 2m choose m = (2m)!/(m! * (2m-m)!) = (2m)!/((m!)^2)
let m_factorial = factorial(m);
let choose_2m_m = factorial(2 * m) / (m_factorial * m_factorial);
let abs_c_recip = choose_2m_m * factorial(2 * m + 1);
let alpha = a_abs_onenorm / one_norm(a);
let alpha: f64 = try_convert(alpha).unwrap() / abs_c_recip as f64;
@ -510,6 +553,7 @@ where
#[cfg(test)]
mod tests {
#[test]
#[allow(clippy::float_cmp)]
fn one_norm() {
use crate::Matrix3;
let m = Matrix3::new(-3.0, 5.0, 7.0, 2.0, 6.0, 4.0, 0.0, 2.0, 8.0);

View File

@ -47,7 +47,7 @@ impl<T: ComplexField> GivensRotation<T> {
if denom > eps {
let norm = sign0.scale(denom.clone());
let c = mod0 / denom;
let s = s.clone() / norm.clone();
let s = s / norm.clone();
Some((Self { c, s }, norm))
} else {
None

View File

@ -317,7 +317,7 @@ where
pub fn is_invertible(&self) -> bool {
assert!(
self.lu.is_square(),
"QR: unable to test the invertibility of a non-square matrix."
"LU: unable to test the invertibility of a non-square matrix."
);
for i in 0..self.lu.nrows() {

View File

@ -24,6 +24,8 @@ mod qr;
mod schur;
mod solve;
mod svd;
mod svd2;
mod svd3;
mod symmetric_eigen;
mod symmetric_tridiagonal;
mod udu;

View File

@ -1,83 +1,71 @@
//! This module provides the matrix exponential (pow) function to square matrices.
use std::ops::DivAssign;
use crate::{
allocator::Allocator,
storage::{Storage, StorageMut},
DefaultAllocator, DimMin, Matrix, OMatrix,
DefaultAllocator, DimMin, Matrix, OMatrix, Scalar,
};
use num::PrimInt;
use simba::scalar::ComplexField;
use num::{One, Zero};
use simba::scalar::{ClosedAdd, ClosedMul};
impl<T: ComplexField, D, S> Matrix<T, D, D, S>
impl<T, D, S> Matrix<T, D, D, S>
where
T: Scalar + Zero + One + ClosedAdd + ClosedMul,
D: DimMin<D, Output = D>,
S: StorageMut<T, D, D>,
DefaultAllocator: Allocator<T, D, D> + Allocator<T, D>,
{
/// Attempts to raise this matrix to an integral power `e` in-place. If this
/// matrix is non-invertible and `e` is negative, it leaves this matrix
/// untouched and returns `false`. Otherwise, it returns `true` and
/// overwrites this matrix with the result.
pub fn pow_mut<I: PrimInt + DivAssign>(&mut self, mut e: I) -> bool {
let zero = I::zero();
/// Raises this matrix to an integral power `exp` in-place.
pub fn pow_mut(&mut self, mut exp: u32) {
// A matrix raised to the zeroth power is just the identity.
if e == zero {
if exp == 0 {
self.fill_with_identity();
return true;
}
} else if exp > 1 {
// We use the buffer to hold the result of multiplier^2, thus avoiding
// extra allocations.
let mut x = self.clone_owned();
let mut workspace = self.clone_owned();
// If e is negative, we compute the inverse matrix, then raise it to the
// power of -e.
if e < zero && !self.try_inverse_mut() {
return false;
}
let one = I::one();
let two = I::from(2u8).unwrap();
// We use the buffer to hold the result of multiplier ^ 2, thus avoiding
// extra allocations.
let mut multiplier = self.clone_owned();
let mut buf = self.clone_owned();
// Exponentiation by squares.
loop {
if e % two == one {
self.mul_to(&multiplier, &mut buf);
self.copy_from(&buf);
if exp % 2 == 0 {
self.fill_with_identity();
} else {
// Avoid an useless multiplication by the identity
// if the exponent is odd.
exp -= 1;
}
e /= two;
multiplier.mul_to(&multiplier, &mut buf);
multiplier.copy_from(&buf);
// Exponentiation by squares.
loop {
if exp % 2 == 1 {
self.mul_to(&x, &mut workspace);
self.copy_from(&workspace);
}
if e == zero {
return true;
exp /= 2;
if exp == 0 {
break;
}
x.mul_to(&x, &mut workspace);
x.copy_from(&workspace);
}
}
}
}
impl<T: ComplexField, D, S: Storage<T, D, D>> Matrix<T, D, D, S>
impl<T, D, S: Storage<T, D, D>> Matrix<T, D, D, S>
where
T: Scalar + Zero + One + ClosedAdd + ClosedMul,
D: DimMin<D, Output = D>,
S: StorageMut<T, D, D>,
DefaultAllocator: Allocator<T, D, D> + Allocator<T, D>,
{
/// Attempts to raise this matrix to an integral power `e`. If this matrix
/// is non-invertible and `e` is negative, it returns `None`. Otherwise, it
/// returns the result as a new matrix. Uses exponentiation by squares.
/// Raise this matrix to an integral power `exp`.
#[must_use]
pub fn pow<I: PrimInt + DivAssign>(&self, e: I) -> Option<OMatrix<T, D, D>> {
let mut clone = self.clone_owned();
if clone.pow_mut(e) {
Some(clone)
} else {
None
}
pub fn pow(&self, exp: u32) -> OMatrix<T, D, D> {
let mut result = self.clone_owned();
result.pow_mut(exp);
result
}
}

View File

@ -147,6 +147,11 @@ where
&self.qr
}
#[must_use]
pub(crate) fn diag_internal(&self) -> &OVector<T, DimMinimum<R, C>> {
&self.diag
}
/// Multiplies the provided matrix by the transpose of the `Q` matrix of this decomposition.
pub fn q_tr_mul<R2: Dim, C2: Dim, S2>(&self, rhs: &mut Matrix<T, R2, C2, S2>)
// TODO: do we need a static constraint on the number of rows of rhs?

View File

@ -1,5 +1,6 @@
#[cfg(feature = "serde-serialize-no-std")]
use serde::{Deserialize, Serialize};
use std::any::TypeId;
use approx::AbsDiffEq;
use num::{One, Zero};
@ -9,6 +10,7 @@ use crate::base::{DefaultAllocator, Matrix, Matrix2x3, OMatrix, OVector, Vector2
use crate::constraint::{SameNumberOfRows, ShapeConstraint};
use crate::dimension::{Dim, DimDiff, DimMin, DimMinimum, DimSub, U1};
use crate::storage::Storage;
use crate::{Matrix2, Matrix3, RawStorage, U2, U3};
use simba::scalar::{ComplexField, RealField};
use crate::linalg::givens::GivensRotation;
@ -78,9 +80,21 @@ where
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
fn use_special_always_ordered_svd2() -> bool {
TypeId::of::<OMatrix<T, R, C>>() == TypeId::of::<Matrix2<T::RealField>>()
&& TypeId::of::<Self>() == TypeId::of::<SVD<T::RealField, U2, U2>>()
}
fn use_special_always_ordered_svd3() -> bool {
TypeId::of::<OMatrix<T, R, C>>() == TypeId::of::<Matrix3<T::RealField>>()
&& TypeId::of::<Self>() == TypeId::of::<SVD<T::RealField, U3, U3>>()
}
/// Computes the Singular Value Decomposition of `matrix` using implicit shift.
pub fn new(matrix: OMatrix<T, R, C>, compute_u: bool, compute_v: bool) -> Self {
Self::try_new(
/// The singular values are not guaranteed to be sorted in any particular order.
/// If a descending order is required, consider using `new` instead.
pub fn new_unordered(matrix: OMatrix<T, R, C>, compute_u: bool, compute_v: bool) -> Self {
Self::try_new_unordered(
matrix,
compute_u,
compute_v,
@ -91,6 +105,8 @@ where
}
/// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift.
/// The singular values are not guaranteed to be sorted in any particular order.
/// If a descending order is required, consider using `try_new` instead.
///
/// # Arguments
///
@ -100,7 +116,7 @@ where
/// * `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(
pub fn try_new_unordered(
mut matrix: OMatrix<T, R, C>,
compute_u: bool,
compute_v: bool,
@ -113,6 +129,21 @@ where
);
let (nrows, ncols) = matrix.shape_generic();
let min_nrows_ncols = nrows.min(ncols);
if Self::use_special_always_ordered_svd2() {
// SAFETY: the reference transmutes are OK since we checked that the types match exactly.
let matrix: &Matrix2<T::RealField> = unsafe { std::mem::transmute(&matrix) };
let result = super::svd2::svd_ordered2(matrix, compute_u, compute_v);
let typed_result: &Self = unsafe { std::mem::transmute(&result) };
return Some(typed_result.clone());
} else if Self::use_special_always_ordered_svd3() {
// SAFETY: the reference transmutes are OK since we checked that the types match exactly.
let matrix: &Matrix3<T::RealField> = unsafe { std::mem::transmute(&matrix) };
let result = super::svd3::svd_ordered3(matrix, compute_u, compute_v, eps, max_niter);
let typed_result: &Self = unsafe { std::mem::transmute(&result) };
return Some(typed_result.clone());
}
let dim = min_nrows_ncols.value();
let m_amax = matrix.camax();
@ -610,6 +641,144 @@ where
}
}
}
/// converts SVD results to Polar decomposition form of the original Matrix: `A = P' * U`.
///
/// The polar decomposition used here is Left Polar Decomposition (or Reverse Polar Decomposition)
/// Returns None if the singular vectors of the SVD haven't been calculated
pub fn to_polar(&self) -> Option<(OMatrix<T, R, R>, OMatrix<T, R, C>)>
where
DefaultAllocator: Allocator<T, R, C> //result
+ Allocator<T, DimMinimum<R, C>, R> // adjoint
+ Allocator<T, DimMinimum<R, C>> // mapped vals
+ Allocator<T, R, R> // result
+ Allocator<T, DimMinimum<R, C>, DimMinimum<R, C>>, // square matrix
{
match (&self.u, &self.v_t) {
(Some(u), Some(v_t)) => Some((
u * OMatrix::from_diagonal(&self.singular_values.map(|e| T::from_real(e)))
* u.adjoint(),
u * v_t,
)),
_ => None,
}
}
}
impl<T: ComplexField, R: DimMin<C>, C: Dim> SVD<T, R, C>
where
DimMinimum<R, C>: DimSub<U1>, // for Bidiagonal.
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<(usize, usize), DimMinimum<R, C>> // for sorted singular values
+ Allocator<(T::RealField, usize), DimMinimum<R, C>>, // for sorted singular values
{
/// Computes the Singular Value Decomposition of `matrix` using implicit shift.
/// The singular values are guaranteed to be sorted in descending order.
/// If this order is not required consider using `new_unordered`.
pub fn new(matrix: OMatrix<T, R, C>, compute_u: bool, compute_v: bool) -> Self {
let mut svd = Self::new_unordered(matrix, compute_u, compute_v);
if !Self::use_special_always_ordered_svd3() && !Self::use_special_always_ordered_svd2() {
svd.sort_by_singular_values();
}
svd
}
/// Attempts to compute the Singular Value Decomposition of `matrix` using implicit shift.
/// The singular values are guaranteed to be sorted in descending order.
/// If this order is not required consider using `try_new_unordered`.
///
/// # 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 right-singular vectors.
/// * `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(
matrix: OMatrix<T, R, C>,
compute_u: bool,
compute_v: bool,
eps: T::RealField,
max_niter: usize,
) -> Option<Self> {
Self::try_new_unordered(matrix, compute_u, compute_v, eps, max_niter).map(|mut svd| {
if !Self::use_special_always_ordered_svd3() && !Self::use_special_always_ordered_svd2()
{
svd.sort_by_singular_values();
}
svd
})
}
/// Sort the estimated components of the SVD by its singular values in descending order.
/// Such an ordering is often implicitly required when the decompositions are used for estimation or fitting purposes.
/// Using this function is only required if `new_unordered` or `try_new_unorderd` were used and the specific sorting is required afterward.
pub fn sort_by_singular_values(&mut self) {
const VALUE_PROCESSED: usize = usize::MAX;
// Collect the singular values with their original index, ...
let mut singular_values = self.singular_values.map_with_location(|r, _, e| (e, r));
assert_ne!(
singular_values.data.shape().0.value(),
VALUE_PROCESSED,
"Too many singular values"
);
// ... sort the singular values, ...
singular_values
.as_mut_slice()
.sort_unstable_by(|(a, _), (b, _)| b.partial_cmp(a).expect("Singular value was NaN"));
// ... and store them.
self.singular_values
.zip_apply(&singular_values, |value, (new_value, _)| {
value.clone_from(&new_value)
});
// Calculate required permutations given the sorted indices.
// We need to identify all circles to calculate the required swaps.
let mut permutations =
crate::PermutationSequence::identity_generic(singular_values.data.shape().0);
for i in 0..singular_values.len() {
let mut index_1 = i;
let mut index_2 = singular_values[i].1;
// Check whether the value was already visited ...
while index_2 != VALUE_PROCESSED // ... or a "double swap" must be avoided.
&& singular_values[index_2].1 != VALUE_PROCESSED
{
// Add the permutation ...
permutations.append_permutation(index_1, index_2);
// ... and mark the value as visited.
singular_values[index_1].1 = VALUE_PROCESSED;
index_1 = index_2;
index_2 = singular_values[index_1].1;
}
}
// Permute the optional components
if let Some(u) = self.u.as_mut() {
permutations.permute_columns(u);
}
if let Some(v_t) = self.v_t.as_mut() {
permutations.permute_rows(v_t);
}
}
}
impl<T: ComplexField, R: DimMin<C>, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S>
@ -626,9 +795,11 @@ where
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>,
{
/// Computes the singular values of this matrix.
/// The singular values are not guaranteed to be sorted in any particular order.
/// If a descending order is required, consider using `singular_values` instead.
#[must_use]
pub fn singular_values(&self) -> OVector<T::RealField, DimMinimum<R, C>> {
SVD::new(self.clone_owned(), false, false).singular_values
pub fn singular_values_unordered(&self) -> OVector<T::RealField, DimMinimum<R, C>> {
SVD::new_unordered(self.clone_owned(), false, false).singular_values
}
/// Computes the rank of this matrix.
@ -636,7 +807,7 @@ where
/// All singular values below `eps` are considered equal to 0.
#[must_use]
pub fn rank(&self, eps: T::RealField) -> usize {
let svd = SVD::new(self.clone_owned(), false, false);
let svd = SVD::new_unordered(self.clone_owned(), false, false);
svd.rank(eps)
}
@ -647,7 +818,31 @@ where
where
DefaultAllocator: Allocator<T, C, R>,
{
SVD::new(self.clone_owned(), true, true).pseudo_inverse(eps)
SVD::new_unordered(self.clone_owned(), true, true).pseudo_inverse(eps)
}
}
impl<T: ComplexField, R: DimMin<C>, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S>
where
DimMinimum<R, C>: DimSub<U1>,
DefaultAllocator: Allocator<T, R, C>
+ Allocator<T, C>
+ Allocator<T, R>
+ Allocator<T, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<T, DimMinimum<R, C>, C>
+ Allocator<T, R, DimMinimum<R, C>>
+ Allocator<T, DimMinimum<R, C>>
+ Allocator<T::RealField, DimMinimum<R, C>>
+ Allocator<T::RealField, DimDiff<DimMinimum<R, C>, U1>>
+ Allocator<(usize, usize), DimMinimum<R, C>>
+ Allocator<(T::RealField, usize), DimMinimum<R, C>>,
{
/// Computes the singular values of this matrix.
/// The singular values are guaranteed to be sorted in descending order.
/// If this order is not required consider using `singular_values_unordered`.
#[must_use]
pub fn singular_values(&self) -> OVector<T::RealField, DimMinimum<R, C>> {
SVD::new(self.clone_owned(), false, false).singular_values
}
}

50
src/linalg/svd2.rs Normal file
View File

@ -0,0 +1,50 @@
use crate::{Matrix2, RealField, Vector2, SVD, U2};
// Implementation of the 2D SVD from https://ieeexplore.ieee.org/document/486688
// See also https://scicomp.stackexchange.com/questions/8899/robust-algorithm-for-2-times-2-svd
pub fn svd_ordered2<T: RealField>(
m: &Matrix2<T>,
compute_u: bool,
compute_v: bool,
) -> SVD<T, U2, U2> {
let half: T = crate::convert(0.5);
let one: T = crate::convert(1.0);
let e = (m.m11.clone() + m.m22.clone()) * half.clone();
let f = (m.m11.clone() - m.m22.clone()) * half.clone();
let g = (m.m21.clone() + m.m12.clone()) * half.clone();
let h = (m.m21.clone() - m.m12.clone()) * half.clone();
let q = (e.clone() * e.clone() + h.clone() * h.clone()).sqrt();
let r = (f.clone() * f.clone() + g.clone() * g.clone()).sqrt();
// Note that the singular values are always sorted because sx >= sy
// because q >= 0 and r >= 0.
let sx = q.clone() + r.clone();
let sy = q - r;
let sy_sign = if sy < T::zero() { -one.clone() } else { one };
let singular_values = Vector2::new(sx, sy * sy_sign.clone());
if compute_u || compute_v {
let a1 = g.atan2(f);
let a2 = h.atan2(e);
let theta = (a2.clone() - a1.clone()) * half.clone();
let phi = (a2 + a1) * half;
let (st, ct) = theta.sin_cos();
let (sp, cp) = phi.sin_cos();
let u = Matrix2::new(cp.clone(), -sp.clone(), sp, cp);
let v_t = Matrix2::new(ct.clone(), -st.clone(), st * sy_sign.clone(), ct * sy_sign);
SVD {
u: if compute_u { Some(u) } else { None },
singular_values,
v_t: if compute_v { Some(v_t) } else { None },
}
} else {
SVD {
u: None,
singular_values,
v_t: None,
}
}
}

55
src/linalg/svd3.rs Normal file
View File

@ -0,0 +1,55 @@
use crate::{Matrix3, SVD, U3};
use simba::scalar::RealField;
// For the 3x3 case, on the GPU, it is much more efficient to compute the SVD
// using an eigendecomposition followed by a QR decomposition.
//
// This is based on the paper "Computing the Singular Value Decomposition of 3 x 3 matrices with
// minimal branching and elementary floating point operations" from McAdams, et al.
pub fn svd_ordered3<T: RealField>(
m: &Matrix3<T>,
compute_u: bool,
compute_v: bool,
eps: T,
niter: usize,
) -> Option<SVD<T, U3, U3>> {
let s = m.tr_mul(&m);
let mut v = s.try_symmetric_eigen(eps, niter)?.eigenvectors;
let mut b = m * &v;
// Sort singular values. This is a necessary step to ensure that
// the QR decompositions R matrix ends up diagonal.
let mut rho0 = b.column(0).norm_squared();
let mut rho1 = b.column(1).norm_squared();
let mut rho2 = b.column(2).norm_squared();
if rho0 < rho1 {
b.swap_columns(0, 1);
b.column_mut(1).neg_mut();
v.swap_columns(0, 1);
v.column_mut(1).neg_mut();
std::mem::swap(&mut rho0, &mut rho1);
}
if rho0 < rho2 {
b.swap_columns(0, 2);
b.column_mut(2).neg_mut();
v.swap_columns(0, 2);
v.column_mut(2).neg_mut();
std::mem::swap(&mut rho0, &mut rho2);
}
if rho1 < rho2 {
b.swap_columns(1, 2);
b.column_mut(2).neg_mut();
v.swap_columns(1, 2);
v.column_mut(2).neg_mut();
std::mem::swap(&mut rho0, &mut rho2);
}
let qr = b.qr();
Some(SVD {
u: if compute_u { Some(qr.q()) } else { None },
singular_values: qr.diag_internal().map(|e| e.abs()),
v_t: if compute_v { Some(v.transpose()) } else { None },
})
}

View File

@ -8,3 +8,5 @@ mod v015;
mod v016;
#[cfg(feature = "glam017")]
mod v017;
#[cfg(feature = "glam018")]
mod v018;

18
src/third_party/glam/v018/mod.rs vendored Normal file
View File

@ -0,0 +1,18 @@
#[path = "../common/glam_isometry.rs"]
mod glam_isometry;
#[path = "../common/glam_matrix.rs"]
mod glam_matrix;
#[path = "../common/glam_point.rs"]
mod glam_point;
#[path = "../common/glam_quaternion.rs"]
mod glam_quaternion;
#[path = "../common/glam_rotation.rs"]
mod glam_rotation;
#[path = "../common/glam_similarity.rs"]
mod glam_similarity;
#[path = "../common/glam_translation.rs"]
mod glam_translation;
#[path = "../common/glam_unit_complex.rs"]
mod glam_unit_complex;
pub(self) use glam018 as glam;

View File

@ -80,8 +80,8 @@ fn iter() {
#[test]
fn debug_output_corresponds_to_data_container() {
let m = Matrix2::new(1.0, 2.0, 3.0, 4.0);
let output_stable = "Matrix { data: [[1, 3], [2, 4]] }"; // Current output on the stable channel.
let output_nightly = "Matrix { data: [[1.0, 3.0], [2.0, 4.0]] }"; // Current output on the nightly channel.
let output_stable = "[[1, 3], [2, 4]]"; // Current output on the stable channel.
let output_nightly = "[[1.0, 3.0], [2.0, 4.0]]"; // Current output on the nightly channel.
let current_output = format!("{:?}", m);
dbg!(output_stable);
dbg!(output_nightly);

View File

@ -1,7 +1,7 @@
#![cfg(feature = "proptest-support")]
#![allow(non_snake_case)]
use na::{DualQuaternion, Point3, UnitDualQuaternion, Vector3};
use na::{DualQuaternion, Point3, Unit, UnitDualQuaternion, UnitQuaternion, Vector3};
use crate::proptest::*;
use proptest::{prop_assert, proptest};
@ -74,6 +74,98 @@ proptest!(
prop_assert!(relative_eq!((dq * t) * p, dq * (t * p), epsilon = 1.0e-7));
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
fn sclerp_is_defined_for_identical_orientations(
dq in unit_dual_quaternion(),
s in -1.0f64..2.0f64,
t in translation3(),
) {
// Should not panic.
prop_assert!(relative_eq!(dq.sclerp(&dq, 0.0), dq, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq, 0.5), dq, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq, 1.0), dq, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq, s), dq, epsilon = 1.0e-7));
let unit = UnitDualQuaternion::identity();
prop_assert!(relative_eq!(unit.sclerp(&unit, 0.0), unit, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit, 0.5), unit, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit, 1.0), unit, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit, s), unit, epsilon = 1.0e-7));
// ScLERPing two unit dual quaternions with nearly equal rotation
// components should result in a unit dual quaternion with a rotation
// component nearly equal to either input.
let dq2 = t * dq;
prop_assert!(relative_eq!(dq.sclerp(&dq2, 0.0).real, dq.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq2, 0.5).real, dq.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq2, 1.0).real, dq.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(dq.sclerp(&dq2, s).real, dq.real, epsilon = 1.0e-7));
// ScLERPing two unit dual quaternions with nearly equal rotation
// components should result in a unit dual quaternion with a translation
// component which is nearly equal to linearly interpolating the
// translation components of the inputs.
prop_assert!(relative_eq!(
dq.sclerp(&dq2, s).translation().vector,
dq.translation().vector.lerp(&dq2.translation().vector, s),
epsilon = 1.0e-7
));
let unit2 = t * unit;
prop_assert!(relative_eq!(unit.sclerp(&unit2, 0.0).real, unit.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit2, 0.5).real, unit.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit2, 1.0).real, unit.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(unit.sclerp(&unit2, s).real, unit.real, epsilon = 1.0e-7));
prop_assert!(relative_eq!(
unit.sclerp(&unit2, s).translation().vector,
unit.translation().vector.lerp(&unit2.translation().vector, s),
epsilon = 1.0e-7
));
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
fn sclerp_is_not_defined_for_opposite_orientations(
dq in unit_dual_quaternion(),
s in 0.1f64..0.9f64,
t in translation3(),
t2 in translation3(),
v in vector3(),
) {
let iso = dq.to_isometry();
let rot = iso.rotation;
if let Some((axis, angle)) = rot.axis_angle() {
let flipped = UnitQuaternion::from_axis_angle(&axis, angle + std::f64::consts::PI);
let dqf = flipped * rot.inverse() * dq.clone();
prop_assert!(dq.try_sclerp(&dqf, 0.5, 1.0e-7).is_none());
prop_assert!(dq.try_sclerp(&dqf, s, 1.0e-7).is_none());
}
let dq2 = t * dq;
let iso2 = dq2.to_isometry();
let rot2 = iso2.rotation;
if let Some((axis, angle)) = rot2.axis_angle() {
let flipped = UnitQuaternion::from_axis_angle(&axis, angle + std::f64::consts::PI);
let dq3f = t2 * flipped * rot.inverse() * dq.clone();
prop_assert!(dq2.try_sclerp(&dq3f, 0.5, 1.0e-7).is_none());
prop_assert!(dq2.try_sclerp(&dq3f, s, 1.0e-7).is_none());
}
if let Some(axis) = Unit::try_new(v, 1.0e-7) {
let unit = UnitDualQuaternion::identity();
let flip = UnitQuaternion::from_axis_angle(&axis, std::f64::consts::PI);
let unitf = flip * unit;
prop_assert!(unit.try_sclerp(&unitf, 0.5, 1.0e-7).is_none());
prop_assert!(unit.try_sclerp(&unitf, s, 1.0e-7).is_none());
let unit2f = t * unit * flip;
prop_assert!(unit.try_sclerp(&unit2f, 0.5, 1.0e-7).is_none());
prop_assert!(unit.try_sclerp(&unit2f, s, 1.0e-7).is_none());
}
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
fn all_op_exist(

View File

@ -33,3 +33,10 @@ mod proptest;
//#[cfg(all(feature = "debug", feature = "compare", feature = "rand"))]
//#[cfg(feature = "sparse")]
//mod sparse;
mod utils {
/// Checks if a slice is sorted in descending order.
pub fn is_sorted_descending<T: PartialOrd>(slice: &[T]) -> bool {
slice.windows(2).all(|elts| elts[0] >= elts[1])
}
}

Some files were not shown because too many files have changed in this diff Show More