#[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Serialize}; use crate::allocator::Allocator; use crate::base::{DefaultAllocator, Matrix, OMatrix}; use crate::constraint::{SameNumberOfRows, ShapeConstraint}; use crate::dimension::{Dim, DimMin, DimMinimum}; use crate::storage::{Storage, StorageMut}; use simba::scalar::ComplexField; use crate::linalg::lu; use crate::linalg::PermutationSequence; /// LU decomposition with full row and column pivoting. #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", serde(bound(serialize = "DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, OMatrix: Serialize, PermutationSequence>: Serialize")) )] #[cfg_attr( feature = "serde-serialize-no-std", serde(bound(deserialize = "DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, OMatrix: Deserialize<'de>, PermutationSequence>: Deserialize<'de>")) )] #[derive(Clone, Debug)] pub struct FullPivLU, C: Dim> where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, { lu: OMatrix, p: PermutationSequence>, q: PermutationSequence>, } impl, C: Dim> Copy for FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, OMatrix: Copy, PermutationSequence>: Copy, { } impl, C: Dim> FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, { /// Computes the LU decomposition with full pivoting of `matrix`. /// /// This effectively computes `P, L, U, Q` such that `P * matrix * Q = LU`. pub fn new(mut matrix: OMatrix) -> Self { let (nrows, ncols) = matrix.shape_generic(); let min_nrows_ncols = nrows.min(ncols); let mut p = PermutationSequence::identity_generic(min_nrows_ncols); let mut q = PermutationSequence::identity_generic(min_nrows_ncols); if min_nrows_ncols.value() == 0 { return Self { lu: matrix, p, q }; } for i in 0..min_nrows_ncols.value() { let piv = matrix.slice_range(i.., i..).icamax_full(); let row_piv = piv.0 + i; let col_piv = piv.1 + i; let diag = matrix[(row_piv, col_piv)]; if diag.is_zero() { // The remaining of the matrix is zero. break; } matrix.swap_columns(i, col_piv); q.append_permutation(i, col_piv); if row_piv != i { p.append_permutation(i, row_piv); matrix.columns_range_mut(..i).swap_rows(i, row_piv); lu::gauss_step_swap(&mut matrix, diag, i, row_piv); } else { lu::gauss_step(&mut matrix, diag, i); } } Self { lu: matrix, p, q } } #[doc(hidden)] pub fn lu_internal(&self) -> &OMatrix { &self.lu } /// The lower triangular matrix of this decomposition. #[inline] #[must_use] pub fn l(&self) -> OMatrix> where DefaultAllocator: Allocator>, { let (nrows, ncols) = self.lu.shape_generic(); let mut m = self.lu.columns_generic(0, nrows.min(ncols)).into_owned(); m.fill_upper_triangle(T::zero(), 1); m.fill_diagonal(T::one()); m } /// The upper triangular matrix of this decomposition. #[inline] #[must_use] pub fn u(&self) -> OMatrix, C> where DefaultAllocator: Allocator, C>, { let (nrows, ncols) = self.lu.shape_generic(); self.lu.rows_generic(0, nrows.min(ncols)).upper_triangle() } /// The row permutations of this decomposition. #[inline] #[must_use] pub fn p(&self) -> &PermutationSequence> { &self.p } /// The column permutations of this decomposition. #[inline] #[must_use] pub fn q(&self) -> &PermutationSequence> { &self.q } /// The two matrices of this decomposition and the row and column permutations: `(P, L, U, Q)`. #[inline] pub fn unpack( self, ) -> ( PermutationSequence>, OMatrix>, OMatrix, C>, PermutationSequence>, ) where DefaultAllocator: Allocator> + Allocator, C>, { // Use reallocation for either l or u. let l = self.l(); let u = self.u(); let p = self.p; let q = self.q; (p, l, u, q) } } impl> FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), D>, { /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// Returns `None` if the decomposed matrix is not invertible. #[must_use = "Did you mean to use solve_mut()?"] pub fn solve( &self, b: &Matrix, ) -> Option> where S2: Storage, ShapeConstraint: SameNumberOfRows, DefaultAllocator: Allocator, { let mut res = b.clone_owned(); if self.solve_mut(&mut res) { Some(res) } else { None } } /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// If the decomposed matrix is not invertible, this returns `false` and its input `b` may /// be overwritten with garbage. pub fn solve_mut(&self, b: &mut Matrix) -> bool where S2: StorageMut, ShapeConstraint: SameNumberOfRows, { assert_eq!( self.lu.nrows(), b.nrows(), "FullPivLU solve matrix dimension mismatch." ); assert!( self.lu.is_square(), "FullPivLU solve: unable to solve a non-square system." ); if self.is_invertible() { self.p.permute_rows(b); let _ = self.lu.solve_lower_triangular_with_diag_mut(b, T::one()); let _ = self.lu.solve_upper_triangular_mut(b); self.q.inv_permute_rows(b); true } else { false } } /// Computes the inverse of the decomposed matrix. /// /// Returns `None` if the decomposed matrix is not invertible. #[must_use] pub fn try_inverse(&self) -> Option> { assert!( self.lu.is_square(), "FullPivLU inverse: unable to compute the inverse of a non-square matrix." ); let (nrows, ncols) = self.lu.shape_generic(); let mut res = OMatrix::identity_generic(nrows, ncols); if self.solve_mut(&mut res) { Some(res) } else { None } } /// Indicates if the decomposed matrix is invertible. #[must_use] pub fn is_invertible(&self) -> bool { assert!( self.lu.is_square(), "FullPivLU: unable to test the invertibility of a non-square matrix." ); let dim = self.lu.nrows(); !self.lu[(dim - 1, dim - 1)].is_zero() } /// Computes the determinant of the decomposed matrix. #[must_use] pub fn determinant(&self) -> T { assert!( self.lu.is_square(), "FullPivLU determinant: unable to compute the determinant of a non-square matrix." ); let dim = self.lu.nrows(); let mut res = self.lu[(dim - 1, dim - 1)]; if !res.is_zero() { for i in 0..dim - 1 { res *= unsafe { *self.lu.get_unchecked((i, i)) }; } res * self.p.determinant() * self.q.determinant() } else { T::zero() } } }