diff --git a/benches/lib.rs b/benches/lib.rs index 48633317d..d63b7b50d 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -6,8 +6,9 @@ extern crate rand_package as rand; #[macro_use] extern crate criterion; -use na::DMatrix; +use na::{DMatrix, Matrix, Scalar}; use rand::Rng; +use rand_distr::{Distribution, Standard}; use rand_isaac::IsaacRng; pub mod core; @@ -20,6 +21,19 @@ fn reproducible_dmatrix(nrows: usize, ncols: usize) -> DMatrix { DMatrix::::from_fn(nrows, ncols, |_, _| rng.gen()) } +fn reproducible_matrix( +) -> Matrix, na::Const, na::ArrayStorage> +where + na::ArrayStorage: Default, + Standard: Distribution, +{ + use rand::SeedableRng; + let mut rng = IsaacRng::seed_from_u64(0); + let mut m: Matrix, na::Const, na::ArrayStorage> = Matrix::default(); + m.iter_mut().for_each(|x| *x = rng.gen()); + return m; +} + criterion_main!( core::matrix, core::vector, @@ -34,4 +48,5 @@ criterion_main!( linalg::solve, linalg::svd, linalg::symmetric_eigen, + linalg::matrix_inversion ); diff --git a/benches/linalg/inverse.rs b/benches/linalg/inverse.rs new file mode 100644 index 000000000..2dc31f419 --- /dev/null +++ b/benches/linalg/inverse.rs @@ -0,0 +1,19 @@ +use na::LU; + +use crate::reproducible_matrix; + +fn inverse_4(bh: &mut criterion::Criterion) { + let m = reproducible_matrix::(); + bh.bench_function("4x4_matrix_inverse_det", move |bh| { + bh.iter(|| std::hint::black_box(m.try_inverse().unwrap())) + }); +} + +fn inverse_lu(bh: &mut criterion::Criterion) { + let m = reproducible_matrix::(); + bh.bench_function("4x4_matrix_inverse_lu", move |bh| { + bh.iter(|| std::hint::black_box(LU::new(m).try_inverse().unwrap())) + }); +} + +criterion_group!(matrix_inversion, inverse_4, inverse_lu); diff --git a/benches/linalg/mod.rs b/benches/linalg/mod.rs index cbd4df4a7..4477654d2 100644 --- a/benches/linalg/mod.rs +++ b/benches/linalg/mod.rs @@ -2,6 +2,7 @@ pub use self::bidiagonal::bidiagonal; pub use self::cholesky::cholesky; pub use self::full_piv_lu::full_piv_lu; pub use self::hessenberg::hessenberg; +pub use self::inverse::matrix_inversion; pub use self::lu::lu; pub use self::qr::qr; pub use self::schur::schur; @@ -13,6 +14,7 @@ mod bidiagonal; mod cholesky; mod full_piv_lu; mod hessenberg; +mod inverse; mod lu; mod qr; mod schur; diff --git a/src/linalg/inverse.rs b/src/linalg/inverse.rs index b11eae13a..cc4793f11 100644 --- a/src/linalg/inverse.rs +++ b/src/linalg/inverse.rs @@ -10,6 +10,11 @@ use crate::linalg::lu; impl> SquareMatrix { /// Attempts to invert this square matrix. /// + /// # Returns + /// + /// - `Some`: The inverted matrix, if the inversion succeeds and all entries are finite. + /// - `None`: If the inversion fails or produces `Inf` or `NaN` values. + /// /// # Panics /// /// Panics if `self` isn’t a square matrix. @@ -20,7 +25,7 @@ impl> SquareMatrix { DefaultAllocator: Allocator, { let mut me = self.into_owned(); - if me.try_inverse_mut() { + if me.try_inverse_mut() && me.iter().all(|x| x.is_finite()) { Some(me) } else { None @@ -32,6 +37,8 @@ impl> SquareMatrix { /// Attempts to invert this square matrix in-place. Returns `false` and leaves `self` untouched if /// inversion fails. /// + /// Calling this function on near singular matrices may lead to`Inf` or `NaN` values! + /// /// # Panics /// /// Panics if `self` isn’t a square matrix. diff --git a/tests/linalg/inverse.rs b/tests/linalg/inverse.rs index ab641a791..aa4054fb2 100644 --- a/tests/linalg/inverse.rs +++ b/tests/linalg/inverse.rs @@ -140,3 +140,70 @@ fn matrix5_try_inverse_scaled_identity() { assert_relative_eq!(a_inv, expected_inverse); } + +#[test] +#[rustfmt::skip] +fn matrix2_try_inverse_singular(){ + let m1 = Matrix2::new( + 1.0, 0.0, + 0.0,0.0 + ); + assert!(m1.try_inverse().is_none()); +} + +#[test] +#[rustfmt::skip] +fn matrix2_try_inverse_near_singular(){ + let m1 = Matrix2::new( + 1.0, 1e1, + 0.0, f64::MIN_POSITIVE); + if let Some(inv) = m1.try_inverse(){ + assert!(inv.iter().all(|x|x.is_finite())); + } +} + +#[test] +#[rustfmt::skip] +fn matrix3_try_inverse_singular(){ + let m1 = Matrix3::new( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0); + assert!(m1.try_inverse().is_none()); +} + +#[test] +#[rustfmt::skip] +fn matrix3_try_inverse_near_singular(){ + let m1 = Matrix3::new( + 1.0, 1e1, 1e1, + 0.0, 1.0, 1e1, + 0.0, 0.0, f64::MIN_POSITIVE); + if let Some(inv) = m1.try_inverse(){ + assert!(inv.iter().all(|x|x.is_finite())); + } +} + +#[test] +#[rustfmt::skip] +fn matrix4_try_inverse_singular(){ + let m1 = Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0 ); + assert!(m1.try_inverse().is_none()); +} + +#[test] +#[rustfmt::skip] +fn matrix4_try_inverse_near_singular(){ + let m1 = Matrix4::new( + 1.0, 1e1, 1e1, 1e1, + 0.0, 1.0, 1e1, 1e1, + 0.0, 0.0, 1.0, 1e1, + 0.0, 0.0, 0.0, f64::MIN_POSITIVE ); + if let Some(inv) = m1.try_inverse(){ + assert!(inv.iter().all(|x|x.is_finite())); + } +}