use alga::general::Real; use core::{Matrix, MatrixN, MatrixMN, DefaultAllocator}; use dimension::{Dim, DimMin, DimMinimum}; use storage::{Storage, StorageMut}; use allocator::Allocator; use constraint::{ShapeConstraint, SameNumberOfRows}; use linalg::lu; use linalg::PermutationSequence; /// LU decomposition with full pivoting. #[derive(Clone, Debug)] pub struct FullPivLU, C: Dim> where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum> { lu: MatrixMN, p: PermutationSequence>, q: PermutationSequence> } impl, C: Dim> Copy for FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum>, MatrixMN: Copy, PermutationSequence>: Copy { } impl, C: Dim> FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum> { /// Computes the LU decomposition with full-pivoting of `matrix`. /// /// This effectively computes `P, L, U, Q` such that `P * matrix * Q = LU`. pub fn new(mut matrix: MatrixMN) -> Self { let (nrows, ncols) = matrix.data.shape(); let min_nrows_ncols = nrows.min(ncols); let mut p = PermutationSequence::identity_generic(min_nrows_ncols); let mut q = PermutationSequence::identity_generic(min_nrows_ncols); if min_nrows_ncols.value() == 0 { return FullPivLU { lu: matrix, p: p, q: q }; } for i in 0 .. min_nrows_ncols.value() { let piv = matrix.slice_range(i .., i ..).iamax_full(); let row_piv = piv.0 + i; let col_piv = piv.1 + i; let diag = matrix[(row_piv, col_piv)]; if diag.is_zero() { // The remaining of the matrix is zero. break; } matrix.swap_columns(i, col_piv); q.append_permutation(i, col_piv); if row_piv != i { p.append_permutation(i, row_piv); matrix.columns_range_mut(.. i).swap_rows(i, row_piv); lu::gauss_step_swap(&mut matrix, diag, i, row_piv); } else { lu::gauss_step(&mut matrix, diag, i); } } FullPivLU { lu: matrix, p: p, q: q } } #[doc(hidden)] pub fn lu_internal(&self) -> &MatrixMN { &self.lu } /// The lower triangular matrix of this decomposition. #[inline] pub fn l(&self) -> MatrixMN> where DefaultAllocator: Allocator> { let (nrows, ncols) = self.lu.data.shape(); let mut m = self.lu.columns_generic(0, nrows.min(ncols)).into_owned(); m.fill_upper_triangle(N::zero(), 1); m.fill_diagonal(N::one()); m } /// The upper triangular matrix of this decomposition. #[inline] pub fn u(&self) -> MatrixMN, C> where DefaultAllocator: Allocator, C> { let (nrows, ncols) = self.lu.data.shape(); self.lu.rows_generic(0, nrows.min(ncols)).upper_triangle() } /// The row permutations of this decomposition. #[inline] pub fn p(&self) -> &PermutationSequence> { &self.p } /// The q permutations of this decomposition. #[inline] pub fn q(&self) -> &PermutationSequence> { &self.q } /// The two matrix of this decomposition and the permutation matrix: `(P, L, U, Q)`. #[inline] pub fn unpack(self) -> (PermutationSequence>, MatrixMN>, MatrixMN, C>, PermutationSequence>) where DefaultAllocator: Allocator> + Allocator, C> { // Use reallocation for either l or u. let l = self.l(); let u = self.u(); let p = self.p; let q = self.q; (p, l, u, q) } } impl> FullPivLU where DefaultAllocator: Allocator + Allocator<(usize, usize), D> { /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// Retuns `None` if the decomposed matrix is not invertible. pub fn solve(&self, b: &Matrix) -> Option> where S2: StorageMut, ShapeConstraint: SameNumberOfRows, DefaultAllocator: Allocator { let mut res = b.clone_owned(); if self.solve_mut(&mut res) { Some(res) } else { None } } /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// If the decomposed matrix is not invertible, this returns `false` and its input `b` is /// left unchanged. 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); self.lu.solve_lower_triangular_with_diag_mut(b, N::one()); self.lu.solve_upper_triangular_mut(b); self.q.inv_permute_rows(b); true } else { false } } /// Computes the inverse of the decomposed matrix. /// /// Returns `None` if the decomposed matrix is not invertible. pub fn try_inverse(&self) -> Option> { assert!(self.lu.is_square(), "FullPivLU inverse: unable to compute the inverse of a non-square matrix."); let (nrows, ncols) = self.lu.data.shape(); let mut res = MatrixN::identity_generic(nrows, ncols); if self.solve_mut(&mut res) { Some(res) } else { None } } /// Indicates if the decomposed matrix is invertible. pub fn is_invertible(&self) -> bool { assert!(self.lu.is_square(), "FullPivLU: unable to test the invertibility of a non-square matrix."); let dim = self.lu.nrows(); !self.lu[(dim - 1, dim - 1)].is_zero() } /// Computes the determinant of the decomposed matrix. pub fn determinant(&self) -> N { assert!(self.lu.is_square(), "FullPivLU determinant: unable to compute the determinant of a non-square matrix."); let dim = self.lu.nrows(); let mut res = self.lu[(dim - 1, dim - 1)]; if !res.is_zero() { for i in 0 .. dim - 1 { res *= unsafe { *self.lu.get_unchecked(i, i) }; } res * self.p.determinant() * self.q.determinant() } else { N::zero() } } } impl, C: Dim, S: Storage> Matrix where DefaultAllocator: Allocator + Allocator<(usize, usize), DimMinimum> { /// Computes the LU decomposition with full-pivoting of `matrix`. /// /// This effectively computes `P, L, U, Q` such that `P * matrix * Q = LU`. pub fn full_piv_lu(self) -> FullPivLU { FullPivLU::new(self.into_owned()) } }