use num::Zero; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Serialize}; use crate::allocator::{Allocator, Reallocator}; use crate::base::{Const, DefaultAllocator, Matrix, OMatrix, OVector, Unit}; use crate::constraint::{SameNumberOfRows, ShapeConstraint}; use crate::dimension::{Dim, DimMin, DimMinimum}; use crate::storage::{Storage, StorageMut}; use crate::ComplexField; use crate::geometry::Reflection; use crate::linalg::{householder, PermutationSequence}; /// The QR decomposition (with column pivoting) of a general matrix. #[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde-serialize-no-std", serde(bound(serialize = "DefaultAllocator: Allocator + Allocator>, OMatrix: Serialize, PermutationSequence>: Serialize, OVector>: Serialize")) )] #[cfg_attr( feature = "serde-serialize-no-std", serde(bound(deserialize = "DefaultAllocator: Allocator + Allocator>, OMatrix: Deserialize<'de>, PermutationSequence>: Deserialize<'de>, OVector>: Deserialize<'de>")) )] #[derive(Clone, Debug)] pub struct ColPivQR, C: Dim> where DefaultAllocator: Allocator + Allocator> + Allocator<(usize, usize), DimMinimum>, { col_piv_qr: OMatrix, p: PermutationSequence>, diag: OVector>, } impl, C: Dim> Copy for ColPivQR where DefaultAllocator: Allocator + Allocator> + Allocator<(usize, usize), DimMinimum>, OMatrix: Copy, PermutationSequence>: Copy, OVector>: Copy, { } impl, C: Dim> ColPivQR where DefaultAllocator: Allocator + Allocator + Allocator> + Allocator<(usize, usize), DimMinimum>, { /// Computes the ColPivQR decomposition using householder reflections. pub fn new(mut matrix: OMatrix) -> 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 diag = unsafe { crate::unimplemented_or_uninitialized_generic!(min_nrows_ncols, Const::<1>) }; if min_nrows_ncols.value() == 0 { return ColPivQR { col_piv_qr: matrix, p, diag, }; } for i in 0..min_nrows_ncols.value() { let piv = matrix.slice_range(i.., i..).icamax_full(); let col_piv = piv.1 + i; matrix.swap_columns(i, col_piv); p.append_permutation(i, col_piv); householder::clear_column_unchecked(&mut matrix, &mut diag[i], i, 0, None); } ColPivQR { col_piv_qr: matrix, p, diag, } } /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. #[inline] #[must_use] pub fn r(&self) -> OMatrix, C> where DefaultAllocator: Allocator, C>, { let (nrows, ncols) = self.col_piv_qr.data.shape(); let mut res = self .col_piv_qr .rows_generic(0, nrows.min(ncols)) .upper_triangle(); res.set_partial_diagonal(self.diag.iter().map(|e| T::from_real(e.modulus()))); res } /// Retrieves the upper trapezoidal submatrix `R` of this decomposition. /// /// This is usually faster than `r` but consumes `self`. #[inline] pub fn unpack_r(self) -> OMatrix, C> where DefaultAllocator: Reallocator, C>, { let (nrows, ncols) = self.col_piv_qr.data.shape(); let mut res = self .col_piv_qr .resize_generic(nrows.min(ncols), ncols, T::zero()); res.fill_lower_triangle(T::zero(), 1); res.set_partial_diagonal(self.diag.iter().map(|e| T::from_real(e.modulus()))); res } /// Computes the orthogonal matrix `Q` of this decomposition. #[must_use] pub fn q(&self) -> OMatrix> where DefaultAllocator: Allocator>, { let (nrows, ncols) = self.col_piv_qr.data.shape(); // NOTE: we could build the identity matrix and call q_mul on it. // Instead we don't so that we take in account the matrix sparseness. let mut res = Matrix::identity_generic(nrows, nrows.min(ncols)); let dim = self.diag.len(); for i in (0..dim).rev() { let axis = self.col_piv_qr.slice_range(i.., i); // TODO: sometimes, the axis might have a zero magnitude. let refl = Reflection::new(Unit::new_unchecked(axis), T::zero()); let mut res_rows = res.slice_range_mut(i.., i..); refl.reflect_with_sign(&mut res_rows, self.diag[i].signum()); } res } /// Retrieves the column permutation of this decomposition. #[inline] #[must_use] pub fn p(&self) -> &PermutationSequence> { &self.p } /// Unpacks this decomposition into its two matrix factors. pub fn unpack( self, ) -> ( OMatrix>, OMatrix, C>, PermutationSequence>, ) where DimMinimum: DimMin>, DefaultAllocator: Allocator> + Reallocator, C> + Allocator<(usize, usize), DimMinimum>, { (self.q(), self.r(), self.p) } #[doc(hidden)] pub fn col_piv_qr_internal(&self) -> &OMatrix { &self.col_piv_qr } /// Multiplies the provided matrix by the transpose of the `Q` matrix of this decomposition. pub fn q_tr_mul(&self, rhs: &mut Matrix) where S2: StorageMut, { let dim = self.diag.len(); for i in 0..dim { let axis = self.col_piv_qr.slice_range(i.., i); let refl = Reflection::new(Unit::new_unchecked(axis), T::zero()); let mut rhs_rows = rhs.rows_range_mut(i..); refl.reflect_with_sign(&mut rhs_rows, self.diag[i].signum().conjugate()); } } } impl> ColPivQR where DefaultAllocator: Allocator + Allocator + Allocator<(usize, usize), DimMinimum>, { /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// Returns `None` if `self` is not invertible. #[must_use = "Did you mean to use solve_mut()?"] pub fn solve( &self, b: &Matrix, ) -> Option> where S2: StorageMut, ShapeConstraint: SameNumberOfRows, DefaultAllocator: Allocator, { let mut res = b.clone_owned(); if self.solve_mut(&mut res) { Some(res) } else { None } } /// Solves the linear system `self * x = b`, where `x` is the unknown to be determined. /// /// If the decomposed matrix is not invertible, this returns `false` and its input `b` is /// overwritten with garbage. pub fn solve_mut(&self, b: &mut Matrix) -> bool where S2: StorageMut, ShapeConstraint: SameNumberOfRows, { assert_eq!( self.col_piv_qr.nrows(), b.nrows(), "ColPivQR solve matrix dimension mismatch." ); assert!( self.col_piv_qr.is_square(), "ColPivQR solve: unable to solve a non-square system." ); self.q_tr_mul(b); let solved = self.solve_upper_triangular_mut(b); self.p.inv_permute_rows(b); solved } // TODO: duplicate code from the `solve` module. fn solve_upper_triangular_mut( &self, b: &mut Matrix, ) -> bool where S2: StorageMut, ShapeConstraint: SameNumberOfRows, { let dim = self.col_piv_qr.nrows(); for k in 0..b.ncols() { let mut b = b.column_mut(k); for i in (0..dim).rev() { let coeff; unsafe { let diag = self.diag.vget_unchecked(i).modulus(); if diag.is_zero() { return false; } coeff = b.vget_unchecked(i).unscale(diag); *b.vget_unchecked_mut(i) = coeff; } b.rows_range_mut(..i) .axpy(-coeff, &self.col_piv_qr.slice_range(..i, i), T::one()); } } true } /// 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.col_piv_qr.is_square(), "ColPivQR inverse: unable to compute the inverse of a non-square matrix." ); // TODO: is there a less naive method ? let (nrows, ncols) = self.col_piv_qr.data.shape(); 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.col_piv_qr.is_square(), "ColPivQR: unable to test the invertibility of a non-square matrix." ); for i in 0..self.diag.len() { if self.diag[i].is_zero() { return false; } } true } /// Computes the determinant of the decomposed matrix. #[must_use] pub fn determinant(&self) -> T { let dim = self.col_piv_qr.nrows(); assert!( self.col_piv_qr.is_square(), "ColPivQR determinant: unable to compute the determinant of a non-square matrix." ); let mut res = T::one(); for i in 0..dim { res *= unsafe { *self.diag.vget_unchecked(i) }; } res * self.p.determinant() } }