Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 1 | // This file is part of Eigen, a lightweight C++ template library |
| 2 | // for linear algebra. |
| 3 | // |
| 4 | // Copyright (C) 2012-2013 Desire Nuentsa <desire.nuentsa_wakam@inria.fr> |
| 5 | // Copyright (C) 2012-2014 Gael Guennebaud <gael.guennebaud@inria.fr> |
| 6 | // |
| 7 | // This Source Code Form is subject to the terms of the Mozilla |
| 8 | // Public License v. 2.0. If a copy of the MPL was not distributed |
| 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 10 | |
| 11 | #ifndef EIGEN_SPARSE_QR_H |
| 12 | #define EIGEN_SPARSE_QR_H |
| 13 | |
| 14 | namespace Eigen { |
| 15 | |
| 16 | template<typename MatrixType, typename OrderingType> class SparseQR; |
| 17 | template<typename SparseQRType> struct SparseQRMatrixQReturnType; |
| 18 | template<typename SparseQRType> struct SparseQRMatrixQTransposeReturnType; |
| 19 | template<typename SparseQRType, typename Derived> struct SparseQR_QProduct; |
| 20 | namespace internal { |
| 21 | template <typename SparseQRType> struct traits<SparseQRMatrixQReturnType<SparseQRType> > |
| 22 | { |
| 23 | typedef typename SparseQRType::MatrixType ReturnType; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 24 | typedef typename ReturnType::StorageIndex StorageIndex; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 25 | typedef typename ReturnType::StorageKind StorageKind; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 26 | enum { |
| 27 | RowsAtCompileTime = Dynamic, |
| 28 | ColsAtCompileTime = Dynamic |
| 29 | }; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 30 | }; |
| 31 | template <typename SparseQRType> struct traits<SparseQRMatrixQTransposeReturnType<SparseQRType> > |
| 32 | { |
| 33 | typedef typename SparseQRType::MatrixType ReturnType; |
| 34 | }; |
| 35 | template <typename SparseQRType, typename Derived> struct traits<SparseQR_QProduct<SparseQRType, Derived> > |
| 36 | { |
| 37 | typedef typename Derived::PlainObject ReturnType; |
| 38 | }; |
| 39 | } // End namespace internal |
| 40 | |
| 41 | /** |
| 42 | * \ingroup SparseQR_Module |
| 43 | * \class SparseQR |
| 44 | * \brief Sparse left-looking rank-revealing QR factorization |
| 45 | * |
| 46 | * This class implements a left-looking rank-revealing QR decomposition |
| 47 | * of sparse matrices. When a column has a norm less than a given tolerance |
| 48 | * it is implicitly permuted to the end. The QR factorization thus obtained is |
| 49 | * given by A*P = Q*R where R is upper triangular or trapezoidal. |
| 50 | * |
| 51 | * P is the column permutation which is the product of the fill-reducing and the |
| 52 | * rank-revealing permutations. Use colsPermutation() to get it. |
| 53 | * |
| 54 | * Q is the orthogonal matrix represented as products of Householder reflectors. |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 55 | * Use matrixQ() to get an expression and matrixQ().adjoint() to get the adjoint. |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 56 | * You can then apply it to a vector. |
| 57 | * |
| 58 | * R is the sparse triangular or trapezoidal matrix. The later occurs when A is rank-deficient. |
| 59 | * matrixR().topLeftCorner(rank(), rank()) always returns a triangular factor of full rank. |
| 60 | * |
| 61 | * \tparam _MatrixType The type of the sparse matrix A, must be a column-major SparseMatrix<> |
| 62 | * \tparam _OrderingType The fill-reducing ordering method. See the \link OrderingMethods_Module |
| 63 | * OrderingMethods \endlink module for the list of built-in and external ordering methods. |
| 64 | * |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 65 | * \implsparsesolverconcept |
| 66 | * |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 67 | * \warning The input sparse matrix A must be in compressed mode (see SparseMatrix::makeCompressed()). |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 68 | * \warning For complex matrices matrixQ().transpose() will actually return the adjoint matrix. |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 69 | * |
| 70 | */ |
| 71 | template<typename _MatrixType, typename _OrderingType> |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 72 | class SparseQR : public SparseSolverBase<SparseQR<_MatrixType,_OrderingType> > |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 73 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 74 | protected: |
| 75 | typedef SparseSolverBase<SparseQR<_MatrixType,_OrderingType> > Base; |
| 76 | using Base::m_isInitialized; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 77 | public: |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 78 | using Base::_solve_impl; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 79 | typedef _MatrixType MatrixType; |
| 80 | typedef _OrderingType OrderingType; |
| 81 | typedef typename MatrixType::Scalar Scalar; |
| 82 | typedef typename MatrixType::RealScalar RealScalar; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 83 | typedef typename MatrixType::StorageIndex StorageIndex; |
| 84 | typedef SparseMatrix<Scalar,ColMajor,StorageIndex> QRMatrixType; |
| 85 | typedef Matrix<StorageIndex, Dynamic, 1> IndexVector; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 86 | typedef Matrix<Scalar, Dynamic, 1> ScalarVector; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 87 | typedef PermutationMatrix<Dynamic, Dynamic, StorageIndex> PermutationType; |
| 88 | |
| 89 | enum { |
| 90 | ColsAtCompileTime = MatrixType::ColsAtCompileTime, |
| 91 | MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime |
| 92 | }; |
| 93 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 94 | public: |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 95 | SparseQR () : m_analysisIsok(false), m_lastError(""), m_useDefaultThreshold(true),m_isQSorted(false),m_isEtreeOk(false) |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 96 | { } |
| 97 | |
| 98 | /** Construct a QR factorization of the matrix \a mat. |
| 99 | * |
| 100 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). |
| 101 | * |
| 102 | * \sa compute() |
| 103 | */ |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 104 | explicit SparseQR(const MatrixType& mat) : m_analysisIsok(false), m_lastError(""), m_useDefaultThreshold(true),m_isQSorted(false),m_isEtreeOk(false) |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 105 | { |
| 106 | compute(mat); |
| 107 | } |
| 108 | |
| 109 | /** Computes the QR factorization of the sparse matrix \a mat. |
| 110 | * |
| 111 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). |
| 112 | * |
| 113 | * \sa analyzePattern(), factorize() |
| 114 | */ |
| 115 | void compute(const MatrixType& mat) |
| 116 | { |
| 117 | analyzePattern(mat); |
| 118 | factorize(mat); |
| 119 | } |
| 120 | void analyzePattern(const MatrixType& mat); |
| 121 | void factorize(const MatrixType& mat); |
| 122 | |
| 123 | /** \returns the number of rows of the represented matrix. |
| 124 | */ |
| 125 | inline Index rows() const { return m_pmat.rows(); } |
| 126 | |
| 127 | /** \returns the number of columns of the represented matrix. |
| 128 | */ |
| 129 | inline Index cols() const { return m_pmat.cols();} |
| 130 | |
| 131 | /** \returns a const reference to the \b sparse upper triangular matrix R of the QR factorization. |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 132 | * \warning The entries of the returned matrix are not sorted. This means that using it in algorithms |
| 133 | * expecting sorted entries will fail. This include random coefficient accesses (SpaseMatrix::coeff()), |
| 134 | * and coefficient-wise operations. Matrix products and triangular solves are fine though. |
| 135 | * |
| 136 | * To sort the entries, you can assign it to a row-major matrix, and if a column-major matrix |
| 137 | * is required, you can copy it again: |
| 138 | * \code |
| 139 | * SparseMatrix<double> R = qr.matrixR(); // column-major, not sorted! |
| 140 | * SparseMatrix<double,RowMajor> Rr = qr.matrixR(); // row-major, sorted |
| 141 | * SparseMatrix<double> Rc = Rr; // column-major, sorted |
| 142 | * \endcode |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 143 | */ |
| 144 | const QRMatrixType& matrixR() const { return m_R; } |
| 145 | |
| 146 | /** \returns the number of non linearly dependent columns as determined by the pivoting threshold. |
| 147 | * |
| 148 | * \sa setPivotThreshold() |
| 149 | */ |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 150 | Index rank() const |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 151 | { |
| 152 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); |
| 153 | return m_nonzeropivots; |
| 154 | } |
| 155 | |
| 156 | /** \returns an expression of the matrix Q as products of sparse Householder reflectors. |
| 157 | * The common usage of this function is to apply it to a dense matrix or vector |
| 158 | * \code |
| 159 | * VectorXd B1, B2; |
| 160 | * // Initialize B1 |
| 161 | * B2 = matrixQ() * B1; |
| 162 | * \endcode |
| 163 | * |
| 164 | * To get a plain SparseMatrix representation of Q: |
| 165 | * \code |
| 166 | * SparseMatrix<double> Q; |
| 167 | * Q = SparseQR<SparseMatrix<double> >(A).matrixQ(); |
| 168 | * \endcode |
| 169 | * Internally, this call simply performs a sparse product between the matrix Q |
| 170 | * and a sparse identity matrix. However, due to the fact that the sparse |
| 171 | * reflectors are stored unsorted, two transpositions are needed to sort |
| 172 | * them before performing the product. |
| 173 | */ |
| 174 | SparseQRMatrixQReturnType<SparseQR> matrixQ() const |
| 175 | { return SparseQRMatrixQReturnType<SparseQR>(*this); } |
| 176 | |
| 177 | /** \returns a const reference to the column permutation P that was applied to A such that A*P = Q*R |
| 178 | * It is the combination of the fill-in reducing permutation and numerical column pivoting. |
| 179 | */ |
| 180 | const PermutationType& colsPermutation() const |
| 181 | { |
| 182 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); |
| 183 | return m_outputPerm_c; |
| 184 | } |
| 185 | |
| 186 | /** \returns A string describing the type of error. |
| 187 | * This method is provided to ease debugging, not to handle errors. |
| 188 | */ |
| 189 | std::string lastErrorMessage() const { return m_lastError; } |
| 190 | |
| 191 | /** \internal */ |
| 192 | template<typename Rhs, typename Dest> |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 193 | bool _solve_impl(const MatrixBase<Rhs> &B, MatrixBase<Dest> &dest) const |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 194 | { |
| 195 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); |
| 196 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); |
| 197 | |
| 198 | Index rank = this->rank(); |
| 199 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 200 | // Compute Q^* * b; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 201 | typename Dest::PlainObject y, b; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 202 | y = this->matrixQ().adjoint() * B; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 203 | b = y; |
| 204 | |
| 205 | // Solve with the triangular matrix R |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 206 | y.resize((std::max<Index>)(cols(),y.rows()),y.cols()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 207 | y.topRows(rank) = this->matrixR().topLeftCorner(rank, rank).template triangularView<Upper>().solve(b.topRows(rank)); |
| 208 | y.bottomRows(y.rows()-rank).setZero(); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 209 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 210 | // Apply the column permutation |
| 211 | if (m_perm_c.size()) dest = colsPermutation() * y.topRows(cols()); |
| 212 | else dest = y.topRows(cols()); |
| 213 | |
| 214 | m_info = Success; |
| 215 | return true; |
| 216 | } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 217 | |
| 218 | /** Sets the threshold that is used to determine linearly dependent columns during the factorization. |
| 219 | * |
| 220 | * In practice, if during the factorization the norm of the column that has to be eliminated is below |
| 221 | * this threshold, then the entire column is treated as zero, and it is moved at the end. |
| 222 | */ |
| 223 | void setPivotThreshold(const RealScalar& threshold) |
| 224 | { |
| 225 | m_useDefaultThreshold = false; |
| 226 | m_threshold = threshold; |
| 227 | } |
| 228 | |
| 229 | /** \returns the solution X of \f$ A X = B \f$ using the current decomposition of A. |
| 230 | * |
| 231 | * \sa compute() |
| 232 | */ |
| 233 | template<typename Rhs> |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 234 | inline const Solve<SparseQR, Rhs> solve(const MatrixBase<Rhs>& B) const |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 235 | { |
| 236 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); |
| 237 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 238 | return Solve<SparseQR, Rhs>(*this, B.derived()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 239 | } |
| 240 | template<typename Rhs> |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 241 | inline const Solve<SparseQR, Rhs> solve(const SparseMatrixBase<Rhs>& B) const |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 242 | { |
| 243 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); |
| 244 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 245 | return Solve<SparseQR, Rhs>(*this, B.derived()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 246 | } |
| 247 | |
| 248 | /** \brief Reports whether previous computation was successful. |
| 249 | * |
| 250 | * \returns \c Success if computation was successful, |
| 251 | * \c NumericalIssue if the QR factorization reports a numerical problem |
| 252 | * \c InvalidInput if the input matrix is invalid |
| 253 | * |
| 254 | * \sa iparm() |
| 255 | */ |
| 256 | ComputationInfo info() const |
| 257 | { |
| 258 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); |
| 259 | return m_info; |
| 260 | } |
| 261 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 262 | |
| 263 | /** \internal */ |
| 264 | inline void _sort_matrix_Q() |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 265 | { |
| 266 | if(this->m_isQSorted) return; |
| 267 | // The matrix Q is sorted during the transposition |
| 268 | SparseMatrix<Scalar, RowMajor, Index> mQrm(this->m_Q); |
| 269 | this->m_Q = mQrm; |
| 270 | this->m_isQSorted = true; |
| 271 | } |
| 272 | |
| 273 | |
| 274 | protected: |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 275 | bool m_analysisIsok; |
| 276 | bool m_factorizationIsok; |
| 277 | mutable ComputationInfo m_info; |
| 278 | std::string m_lastError; |
| 279 | QRMatrixType m_pmat; // Temporary matrix |
| 280 | QRMatrixType m_R; // The triangular factor matrix |
| 281 | QRMatrixType m_Q; // The orthogonal reflectors |
| 282 | ScalarVector m_hcoeffs; // The Householder coefficients |
| 283 | PermutationType m_perm_c; // Fill-reducing Column permutation |
| 284 | PermutationType m_pivotperm; // The permutation for rank revealing |
| 285 | PermutationType m_outputPerm_c; // The final column permutation |
| 286 | RealScalar m_threshold; // Threshold to determine null Householder reflections |
| 287 | bool m_useDefaultThreshold; // Use default threshold |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 288 | Index m_nonzeropivots; // Number of non zero pivots found |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 289 | IndexVector m_etree; // Column elimination tree |
| 290 | IndexVector m_firstRowElt; // First element in each row |
| 291 | bool m_isQSorted; // whether Q is sorted or not |
| 292 | bool m_isEtreeOk; // whether the elimination tree match the initial input matrix |
| 293 | |
| 294 | template <typename, typename > friend struct SparseQR_QProduct; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 295 | |
| 296 | }; |
| 297 | |
| 298 | /** \brief Preprocessing step of a QR factorization |
| 299 | * |
| 300 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). |
| 301 | * |
| 302 | * In this step, the fill-reducing permutation is computed and applied to the columns of A |
| 303 | * and the column elimination tree is computed as well. Only the sparsity pattern of \a mat is exploited. |
| 304 | * |
| 305 | * \note In this step it is assumed that there is no empty row in the matrix \a mat. |
| 306 | */ |
| 307 | template <typename MatrixType, typename OrderingType> |
| 308 | void SparseQR<MatrixType,OrderingType>::analyzePattern(const MatrixType& mat) |
| 309 | { |
| 310 | eigen_assert(mat.isCompressed() && "SparseQR requires a sparse matrix in compressed mode. Call .makeCompressed() before passing it to SparseQR"); |
| 311 | // Copy to a column major matrix if the input is rowmajor |
| 312 | typename internal::conditional<MatrixType::IsRowMajor,QRMatrixType,const MatrixType&>::type matCpy(mat); |
| 313 | // Compute the column fill reducing ordering |
| 314 | OrderingType ord; |
| 315 | ord(matCpy, m_perm_c); |
| 316 | Index n = mat.cols(); |
| 317 | Index m = mat.rows(); |
| 318 | Index diagSize = (std::min)(m,n); |
| 319 | |
| 320 | if (!m_perm_c.size()) |
| 321 | { |
| 322 | m_perm_c.resize(n); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 323 | m_perm_c.indices().setLinSpaced(n, 0,StorageIndex(n-1)); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 324 | } |
| 325 | |
| 326 | // Compute the column elimination tree of the permuted matrix |
| 327 | m_outputPerm_c = m_perm_c.inverse(); |
| 328 | internal::coletree(matCpy, m_etree, m_firstRowElt, m_outputPerm_c.indices().data()); |
| 329 | m_isEtreeOk = true; |
| 330 | |
| 331 | m_R.resize(m, n); |
| 332 | m_Q.resize(m, diagSize); |
| 333 | |
| 334 | // Allocate space for nonzero elements : rough estimation |
| 335 | m_R.reserve(2*mat.nonZeros()); //FIXME Get a more accurate estimation through symbolic factorization with the etree |
| 336 | m_Q.reserve(2*mat.nonZeros()); |
| 337 | m_hcoeffs.resize(diagSize); |
| 338 | m_analysisIsok = true; |
| 339 | } |
| 340 | |
| 341 | /** \brief Performs the numerical QR factorization of the input matrix |
| 342 | * |
| 343 | * The function SparseQR::analyzePattern(const MatrixType&) must have been called beforehand with |
| 344 | * a matrix having the same sparsity pattern than \a mat. |
| 345 | * |
| 346 | * \param mat The sparse column-major matrix |
| 347 | */ |
| 348 | template <typename MatrixType, typename OrderingType> |
| 349 | void SparseQR<MatrixType,OrderingType>::factorize(const MatrixType& mat) |
| 350 | { |
| 351 | using std::abs; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 352 | |
| 353 | eigen_assert(m_analysisIsok && "analyzePattern() should be called before this step"); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 354 | StorageIndex m = StorageIndex(mat.rows()); |
| 355 | StorageIndex n = StorageIndex(mat.cols()); |
| 356 | StorageIndex diagSize = (std::min)(m,n); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 357 | IndexVector mark((std::max)(m,n)); mark.setConstant(-1); // Record the visited nodes |
| 358 | IndexVector Ridx(n), Qidx(m); // Store temporarily the row indexes for the current column of R and Q |
| 359 | Index nzcolR, nzcolQ; // Number of nonzero for the current column of R and Q |
| 360 | ScalarVector tval(m); // The dense vector used to compute the current column |
| 361 | RealScalar pivotThreshold = m_threshold; |
| 362 | |
| 363 | m_R.setZero(); |
| 364 | m_Q.setZero(); |
| 365 | m_pmat = mat; |
| 366 | if(!m_isEtreeOk) |
| 367 | { |
| 368 | m_outputPerm_c = m_perm_c.inverse(); |
| 369 | internal::coletree(m_pmat, m_etree, m_firstRowElt, m_outputPerm_c.indices().data()); |
| 370 | m_isEtreeOk = true; |
| 371 | } |
| 372 | |
| 373 | m_pmat.uncompress(); // To have the innerNonZeroPtr allocated |
| 374 | |
| 375 | // Apply the fill-in reducing permutation lazily: |
| 376 | { |
| 377 | // If the input is row major, copy the original column indices, |
| 378 | // otherwise directly use the input matrix |
| 379 | // |
| 380 | IndexVector originalOuterIndicesCpy; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 381 | const StorageIndex *originalOuterIndices = mat.outerIndexPtr(); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 382 | if(MatrixType::IsRowMajor) |
| 383 | { |
| 384 | originalOuterIndicesCpy = IndexVector::Map(m_pmat.outerIndexPtr(),n+1); |
| 385 | originalOuterIndices = originalOuterIndicesCpy.data(); |
| 386 | } |
| 387 | |
| 388 | for (int i = 0; i < n; i++) |
| 389 | { |
| 390 | Index p = m_perm_c.size() ? m_perm_c.indices()(i) : i; |
| 391 | m_pmat.outerIndexPtr()[p] = originalOuterIndices[i]; |
| 392 | m_pmat.innerNonZeroPtr()[p] = originalOuterIndices[i+1] - originalOuterIndices[i]; |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | /* Compute the default threshold as in MatLab, see: |
| 397 | * Tim Davis, "Algorithm 915, SuiteSparseQR: Multifrontal Multithreaded Rank-Revealing |
| 398 | * Sparse QR Factorization, ACM Trans. on Math. Soft. 38(1), 2011, Page 8:3 |
| 399 | */ |
| 400 | if(m_useDefaultThreshold) |
| 401 | { |
| 402 | RealScalar max2Norm = 0.0; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 403 | for (int j = 0; j < n; j++) max2Norm = numext::maxi(max2Norm, m_pmat.col(j).norm()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 404 | if(max2Norm==RealScalar(0)) |
| 405 | max2Norm = RealScalar(1); |
| 406 | pivotThreshold = 20 * (m + n) * max2Norm * NumTraits<RealScalar>::epsilon(); |
| 407 | } |
| 408 | |
| 409 | // Initialize the numerical permutation |
| 410 | m_pivotperm.setIdentity(n); |
| 411 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 412 | StorageIndex nonzeroCol = 0; // Record the number of valid pivots |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 413 | m_Q.startVec(0); |
| 414 | |
| 415 | // Left looking rank-revealing QR factorization: compute a column of R and Q at a time |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 416 | for (StorageIndex col = 0; col < n; ++col) |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 417 | { |
| 418 | mark.setConstant(-1); |
| 419 | m_R.startVec(col); |
| 420 | mark(nonzeroCol) = col; |
| 421 | Qidx(0) = nonzeroCol; |
| 422 | nzcolR = 0; nzcolQ = 1; |
| 423 | bool found_diag = nonzeroCol>=m; |
| 424 | tval.setZero(); |
| 425 | |
| 426 | // Symbolic factorization: find the nonzero locations of the column k of the factors R and Q, i.e., |
| 427 | // all the nodes (with indexes lower than rank) reachable through the column elimination tree (etree) rooted at node k. |
| 428 | // Note: if the diagonal entry does not exist, then its contribution must be explicitly added, |
| 429 | // thus the trick with found_diag that permits to do one more iteration on the diagonal element if this one has not been found. |
| 430 | for (typename QRMatrixType::InnerIterator itp(m_pmat, col); itp || !found_diag; ++itp) |
| 431 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 432 | StorageIndex curIdx = nonzeroCol; |
| 433 | if(itp) curIdx = StorageIndex(itp.row()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 434 | if(curIdx == nonzeroCol) found_diag = true; |
| 435 | |
| 436 | // Get the nonzeros indexes of the current column of R |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 437 | StorageIndex st = m_firstRowElt(curIdx); // The traversal of the etree starts here |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 438 | if (st < 0 ) |
| 439 | { |
| 440 | m_lastError = "Empty row found during numerical factorization"; |
| 441 | m_info = InvalidInput; |
| 442 | return; |
| 443 | } |
| 444 | |
| 445 | // Traverse the etree |
| 446 | Index bi = nzcolR; |
| 447 | for (; mark(st) != col; st = m_etree(st)) |
| 448 | { |
| 449 | Ridx(nzcolR) = st; // Add this row to the list, |
| 450 | mark(st) = col; // and mark this row as visited |
| 451 | nzcolR++; |
| 452 | } |
| 453 | |
| 454 | // Reverse the list to get the topological ordering |
| 455 | Index nt = nzcolR-bi; |
| 456 | for(Index i = 0; i < nt/2; i++) std::swap(Ridx(bi+i), Ridx(nzcolR-i-1)); |
| 457 | |
| 458 | // Copy the current (curIdx,pcol) value of the input matrix |
| 459 | if(itp) tval(curIdx) = itp.value(); |
| 460 | else tval(curIdx) = Scalar(0); |
| 461 | |
| 462 | // Compute the pattern of Q(:,k) |
| 463 | if(curIdx > nonzeroCol && mark(curIdx) != col ) |
| 464 | { |
| 465 | Qidx(nzcolQ) = curIdx; // Add this row to the pattern of Q, |
| 466 | mark(curIdx) = col; // and mark it as visited |
| 467 | nzcolQ++; |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | // Browse all the indexes of R(:,col) in reverse order |
| 472 | for (Index i = nzcolR-1; i >= 0; i--) |
| 473 | { |
| 474 | Index curIdx = Ridx(i); |
| 475 | |
| 476 | // Apply the curIdx-th householder vector to the current column (temporarily stored into tval) |
| 477 | Scalar tdot(0); |
| 478 | |
| 479 | // First compute q' * tval |
| 480 | tdot = m_Q.col(curIdx).dot(tval); |
| 481 | |
| 482 | tdot *= m_hcoeffs(curIdx); |
| 483 | |
| 484 | // Then update tval = tval - q * tau |
| 485 | // FIXME: tval -= tdot * m_Q.col(curIdx) should amount to the same (need to check/add support for efficient "dense ?= sparse") |
| 486 | for (typename QRMatrixType::InnerIterator itq(m_Q, curIdx); itq; ++itq) |
| 487 | tval(itq.row()) -= itq.value() * tdot; |
| 488 | |
| 489 | // Detect fill-in for the current column of Q |
| 490 | if(m_etree(Ridx(i)) == nonzeroCol) |
| 491 | { |
| 492 | for (typename QRMatrixType::InnerIterator itq(m_Q, curIdx); itq; ++itq) |
| 493 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 494 | StorageIndex iQ = StorageIndex(itq.row()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 495 | if (mark(iQ) != col) |
| 496 | { |
| 497 | Qidx(nzcolQ++) = iQ; // Add this row to the pattern of Q, |
| 498 | mark(iQ) = col; // and mark it as visited |
| 499 | } |
| 500 | } |
| 501 | } |
| 502 | } // End update current column |
| 503 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 504 | Scalar tau = RealScalar(0); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 505 | RealScalar beta = 0; |
| 506 | |
| 507 | if(nonzeroCol < diagSize) |
| 508 | { |
| 509 | // Compute the Householder reflection that eliminate the current column |
| 510 | // FIXME this step should call the Householder module. |
| 511 | Scalar c0 = nzcolQ ? tval(Qidx(0)) : Scalar(0); |
| 512 | |
| 513 | // First, the squared norm of Q((col+1):m, col) |
| 514 | RealScalar sqrNorm = 0.; |
| 515 | for (Index itq = 1; itq < nzcolQ; ++itq) sqrNorm += numext::abs2(tval(Qidx(itq))); |
| 516 | if(sqrNorm == RealScalar(0) && numext::imag(c0) == RealScalar(0)) |
| 517 | { |
| 518 | beta = numext::real(c0); |
| 519 | tval(Qidx(0)) = 1; |
| 520 | } |
| 521 | else |
| 522 | { |
| 523 | using std::sqrt; |
| 524 | beta = sqrt(numext::abs2(c0) + sqrNorm); |
| 525 | if(numext::real(c0) >= RealScalar(0)) |
| 526 | beta = -beta; |
| 527 | tval(Qidx(0)) = 1; |
| 528 | for (Index itq = 1; itq < nzcolQ; ++itq) |
| 529 | tval(Qidx(itq)) /= (c0 - beta); |
| 530 | tau = numext::conj((beta-c0) / beta); |
| 531 | |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | // Insert values in R |
| 536 | for (Index i = nzcolR-1; i >= 0; i--) |
| 537 | { |
| 538 | Index curIdx = Ridx(i); |
| 539 | if(curIdx < nonzeroCol) |
| 540 | { |
| 541 | m_R.insertBackByOuterInnerUnordered(col, curIdx) = tval(curIdx); |
| 542 | tval(curIdx) = Scalar(0.); |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | if(nonzeroCol < diagSize && abs(beta) >= pivotThreshold) |
| 547 | { |
| 548 | m_R.insertBackByOuterInner(col, nonzeroCol) = beta; |
| 549 | // The householder coefficient |
| 550 | m_hcoeffs(nonzeroCol) = tau; |
| 551 | // Record the householder reflections |
| 552 | for (Index itq = 0; itq < nzcolQ; ++itq) |
| 553 | { |
| 554 | Index iQ = Qidx(itq); |
| 555 | m_Q.insertBackByOuterInnerUnordered(nonzeroCol,iQ) = tval(iQ); |
| 556 | tval(iQ) = Scalar(0.); |
| 557 | } |
| 558 | nonzeroCol++; |
| 559 | if(nonzeroCol<diagSize) |
| 560 | m_Q.startVec(nonzeroCol); |
| 561 | } |
| 562 | else |
| 563 | { |
| 564 | // Zero pivot found: move implicitly this column to the end |
| 565 | for (Index j = nonzeroCol; j < n-1; j++) |
| 566 | std::swap(m_pivotperm.indices()(j), m_pivotperm.indices()[j+1]); |
| 567 | |
| 568 | // Recompute the column elimination tree |
| 569 | internal::coletree(m_pmat, m_etree, m_firstRowElt, m_pivotperm.indices().data()); |
| 570 | m_isEtreeOk = false; |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | m_hcoeffs.tail(diagSize-nonzeroCol).setZero(); |
| 575 | |
| 576 | // Finalize the column pointers of the sparse matrices R and Q |
| 577 | m_Q.finalize(); |
| 578 | m_Q.makeCompressed(); |
| 579 | m_R.finalize(); |
| 580 | m_R.makeCompressed(); |
| 581 | m_isQSorted = false; |
| 582 | |
| 583 | m_nonzeropivots = nonzeroCol; |
| 584 | |
| 585 | if(nonzeroCol<n) |
| 586 | { |
| 587 | // Permute the triangular factor to put the 'dead' columns to the end |
| 588 | QRMatrixType tempR(m_R); |
| 589 | m_R = tempR * m_pivotperm; |
| 590 | |
| 591 | // Update the column permutation |
| 592 | m_outputPerm_c = m_outputPerm_c * m_pivotperm; |
| 593 | } |
| 594 | |
| 595 | m_isInitialized = true; |
| 596 | m_factorizationIsok = true; |
| 597 | m_info = Success; |
| 598 | } |
| 599 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 600 | template <typename SparseQRType, typename Derived> |
| 601 | struct SparseQR_QProduct : ReturnByValue<SparseQR_QProduct<SparseQRType, Derived> > |
| 602 | { |
| 603 | typedef typename SparseQRType::QRMatrixType MatrixType; |
| 604 | typedef typename SparseQRType::Scalar Scalar; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 605 | // Get the references |
| 606 | SparseQR_QProduct(const SparseQRType& qr, const Derived& other, bool transpose) : |
| 607 | m_qr(qr),m_other(other),m_transpose(transpose) {} |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 608 | inline Index rows() const { return m_qr.matrixQ().rows(); } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 609 | inline Index cols() const { return m_other.cols(); } |
| 610 | |
| 611 | // Assign to a vector |
| 612 | template<typename DesType> |
| 613 | void evalTo(DesType& res) const |
| 614 | { |
| 615 | Index m = m_qr.rows(); |
| 616 | Index n = m_qr.cols(); |
| 617 | Index diagSize = (std::min)(m,n); |
| 618 | res = m_other; |
| 619 | if (m_transpose) |
| 620 | { |
| 621 | eigen_assert(m_qr.m_Q.rows() == m_other.rows() && "Non conforming object sizes"); |
| 622 | //Compute res = Q' * other column by column |
| 623 | for(Index j = 0; j < res.cols(); j++){ |
| 624 | for (Index k = 0; k < diagSize; k++) |
| 625 | { |
| 626 | Scalar tau = Scalar(0); |
| 627 | tau = m_qr.m_Q.col(k).dot(res.col(j)); |
| 628 | if(tau==Scalar(0)) continue; |
| 629 | tau = tau * m_qr.m_hcoeffs(k); |
| 630 | res.col(j) -= tau * m_qr.m_Q.col(k); |
| 631 | } |
| 632 | } |
| 633 | } |
| 634 | else |
| 635 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 636 | eigen_assert(m_qr.matrixQ().cols() == m_other.rows() && "Non conforming object sizes"); |
| 637 | |
| 638 | res.conservativeResize(rows(), cols()); |
| 639 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 640 | // Compute res = Q * other column by column |
| 641 | for(Index j = 0; j < res.cols(); j++) |
| 642 | { |
| 643 | for (Index k = diagSize-1; k >=0; k--) |
| 644 | { |
| 645 | Scalar tau = Scalar(0); |
| 646 | tau = m_qr.m_Q.col(k).dot(res.col(j)); |
| 647 | if(tau==Scalar(0)) continue; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 648 | tau = tau * numext::conj(m_qr.m_hcoeffs(k)); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 649 | res.col(j) -= tau * m_qr.m_Q.col(k); |
| 650 | } |
| 651 | } |
| 652 | } |
| 653 | } |
| 654 | |
| 655 | const SparseQRType& m_qr; |
| 656 | const Derived& m_other; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 657 | bool m_transpose; // TODO this actually means adjoint |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 658 | }; |
| 659 | |
| 660 | template<typename SparseQRType> |
| 661 | struct SparseQRMatrixQReturnType : public EigenBase<SparseQRMatrixQReturnType<SparseQRType> > |
| 662 | { |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 663 | typedef typename SparseQRType::Scalar Scalar; |
| 664 | typedef Matrix<Scalar,Dynamic,Dynamic> DenseMatrix; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 665 | enum { |
| 666 | RowsAtCompileTime = Dynamic, |
| 667 | ColsAtCompileTime = Dynamic |
| 668 | }; |
| 669 | explicit SparseQRMatrixQReturnType(const SparseQRType& qr) : m_qr(qr) {} |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 670 | template<typename Derived> |
| 671 | SparseQR_QProduct<SparseQRType, Derived> operator*(const MatrixBase<Derived>& other) |
| 672 | { |
| 673 | return SparseQR_QProduct<SparseQRType,Derived>(m_qr,other.derived(),false); |
| 674 | } |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 675 | // To use for operations with the adjoint of Q |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 676 | SparseQRMatrixQTransposeReturnType<SparseQRType> adjoint() const |
| 677 | { |
| 678 | return SparseQRMatrixQTransposeReturnType<SparseQRType>(m_qr); |
| 679 | } |
| 680 | inline Index rows() const { return m_qr.rows(); } |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 681 | inline Index cols() const { return m_qr.rows(); } |
| 682 | // To use for operations with the transpose of Q FIXME this is the same as adjoint at the moment |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 683 | SparseQRMatrixQTransposeReturnType<SparseQRType> transpose() const |
| 684 | { |
| 685 | return SparseQRMatrixQTransposeReturnType<SparseQRType>(m_qr); |
| 686 | } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 687 | const SparseQRType& m_qr; |
| 688 | }; |
| 689 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 690 | // TODO this actually represents the adjoint of Q |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 691 | template<typename SparseQRType> |
| 692 | struct SparseQRMatrixQTransposeReturnType |
| 693 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 694 | explicit SparseQRMatrixQTransposeReturnType(const SparseQRType& qr) : m_qr(qr) {} |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 695 | template<typename Derived> |
| 696 | SparseQR_QProduct<SparseQRType,Derived> operator*(const MatrixBase<Derived>& other) |
| 697 | { |
| 698 | return SparseQR_QProduct<SparseQRType,Derived>(m_qr,other.derived(), true); |
| 699 | } |
| 700 | const SparseQRType& m_qr; |
| 701 | }; |
| 702 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame^] | 703 | namespace internal { |
| 704 | |
| 705 | template<typename SparseQRType> |
| 706 | struct evaluator_traits<SparseQRMatrixQReturnType<SparseQRType> > |
| 707 | { |
| 708 | typedef typename SparseQRType::MatrixType MatrixType; |
| 709 | typedef typename storage_kind_to_evaluator_kind<typename MatrixType::StorageKind>::Kind Kind; |
| 710 | typedef SparseShape Shape; |
| 711 | }; |
| 712 | |
| 713 | template< typename DstXprType, typename SparseQRType> |
| 714 | struct Assignment<DstXprType, SparseQRMatrixQReturnType<SparseQRType>, internal::assign_op<typename DstXprType::Scalar,typename DstXprType::Scalar>, Sparse2Sparse> |
| 715 | { |
| 716 | typedef SparseQRMatrixQReturnType<SparseQRType> SrcXprType; |
| 717 | typedef typename DstXprType::Scalar Scalar; |
| 718 | typedef typename DstXprType::StorageIndex StorageIndex; |
| 719 | static void run(DstXprType &dst, const SrcXprType &src, const internal::assign_op<Scalar,Scalar> &/*func*/) |
| 720 | { |
| 721 | typename DstXprType::PlainObject idMat(src.rows(), src.cols()); |
| 722 | idMat.setIdentity(); |
| 723 | // Sort the sparse householder reflectors if needed |
| 724 | const_cast<SparseQRType *>(&src.m_qr)->_sort_matrix_Q(); |
| 725 | dst = SparseQR_QProduct<SparseQRType, DstXprType>(src.m_qr, idMat, false); |
| 726 | } |
| 727 | }; |
| 728 | |
| 729 | template< typename DstXprType, typename SparseQRType> |
| 730 | struct Assignment<DstXprType, SparseQRMatrixQReturnType<SparseQRType>, internal::assign_op<typename DstXprType::Scalar,typename DstXprType::Scalar>, Sparse2Dense> |
| 731 | { |
| 732 | typedef SparseQRMatrixQReturnType<SparseQRType> SrcXprType; |
| 733 | typedef typename DstXprType::Scalar Scalar; |
| 734 | typedef typename DstXprType::StorageIndex StorageIndex; |
| 735 | static void run(DstXprType &dst, const SrcXprType &src, const internal::assign_op<Scalar,Scalar> &/*func*/) |
| 736 | { |
| 737 | dst = src.m_qr.matrixQ() * DstXprType::Identity(src.m_qr.rows(), src.m_qr.rows()); |
| 738 | } |
| 739 | }; |
| 740 | |
| 741 | } // end namespace internal |
| 742 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 743 | } // end namespace Eigen |
| 744 | |
| 745 | #endif |