use num::Zero; #[cfg(feature = "serde-serialize-no-std")] use serde::{Deserialize, Serialize}; use crate::allocator::{Allocator, Reallocator}; use crate::base::{DefaultAllocator, Matrix, OMatrix, OVector, Unit}; use crate::constraint::{SameNumberOfRows, ShapeConstraint}; use crate::dimension::{Const, Dim, DimMin, DimMinimum}; use crate::storage::{Storage, StorageMut}; use simba::scalar::ComplexField; use crate::geometry::Reflection; use crate::linalg::householder; use std::mem::MaybeUninit; /// The QR decomposition 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, OVector>: Serialize")) )] #[cfg_attr( feature = "serde-serialize-no-std", serde(bound(deserialize = "DefaultAllocator: Allocator + Allocator>, OMatrix: Deserialize<'de>, OVector>: Deserialize<'de>")) )] #[derive(Clone, Debug)] pub struct QR, C: Dim> where DefaultAllocator: Allocator + Allocator>, { qr: OMatrix, diag: OVector>, } impl, C: Dim> Copy for QR where DefaultAllocator: Allocator + Allocator>, OMatrix: Copy, OVector>: Copy, { } impl, C: Dim> QR where DefaultAllocator: Allocator + Allocator + Allocator>, { /// Computes the QR decomposition using householder reflections. pub fn new(mut matrix: OMatrix) -> Self { let (nrows, ncols) = matrix.shape_generic(); let min_nrows_ncols = nrows.min(ncols); if min_nrows_ncols.value() == 0 { return QR { qr: matrix, diag: Matrix::zeros_generic(min_nrows_ncols, Const::<1>), }; } let mut diag = Matrix::uninit(min_nrows_ncols, Const::<1>); for i in 0..min_nrows_ncols.value() { diag[i] = MaybeUninit::new(householder::clear_column_unchecked(&mut matrix, i, 0, None)); } // Safety: diag is now fully initialized. let diag = unsafe { diag.assume_init() }; QR { qr: matrix, 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.qr.shape_generic(); let mut res = self.qr.rows_generic(0, nrows.min(ncols)).upper_triangle(); res.set_partial_diagonal(self.diag.iter().map(|e| T::from_real(e.clone().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.qr.shape_generic(); let mut res = self.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.clone().modulus()))); res } /// Computes the orthogonal matrix `Q` of this decomposition. #[must_use] pub fn q(&self) -> OMatrix> where DefaultAllocator: Allocator>, { let (nrows, ncols) = self.qr.shape_generic(); // 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.qr.view_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.view_range_mut(i.., i..); refl.reflect_with_sign(&mut res_rows, self.diag[i].clone().signum()); } res } /// Unpacks this decomposition into its two matrix factors. pub fn unpack( self, ) -> ( OMatrix>, OMatrix, C>, ) where DimMinimum: DimMin>, DefaultAllocator: Allocator> + Reallocator, C>, { (self.q(), self.unpack_r()) } #[doc(hidden)] pub fn qr_internal(&self) -> &OMatrix { &self.qr } #[must_use] pub(crate) fn diag_internal(&self) -> &OVector> { &self.diag } /// Multiplies the provided matrix by the transpose of the `Q` matrix of this decomposition. pub fn q_tr_mul(&self, rhs: &mut Matrix) // TODO: do we need a static constraint on the number of rows of rhs? where S2: StorageMut, { let dim = self.diag.len(); for i in 0..dim { let axis = self.qr.view_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].clone().signum().conjugate()); } } } impl> QR where DefaultAllocator: Allocator + Allocator, { /// 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: 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` is /// overwritten with garbage. pub fn solve_mut(&self, b: &mut Matrix) -> bool where S2: StorageMut, ShapeConstraint: SameNumberOfRows, { assert_eq!( self.qr.nrows(), b.nrows(), "QR solve matrix dimension mismatch." ); assert!( self.qr.is_square(), "QR solve: unable to solve a non-square system." ); self.q_tr_mul(b); self.solve_upper_triangular_mut(b) } // 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.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).clone().modulus(); if diag.is_zero() { return false; } coeff = b.vget_unchecked(i).clone().unscale(diag); *b.vget_unchecked_mut(i) = coeff.clone(); } b.rows_range_mut(..i) .axpy(-coeff, &self.qr.view_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.qr.is_square(), "QR inverse: unable to compute the inverse of a non-square matrix." ); // TODO: is there a less naive method ? let (nrows, ncols) = self.qr.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.qr.is_square(), "QR: 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. // pub fn determinant(&self) -> T { // let dim = self.qr.nrows(); // assert!(self.qr.is_square(), "QR 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.q_determinant() // } }