Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 09:06:20

0001 // This file is part of Eigen, a lightweight C++ template library
0002 // for linear algebra.
0003 //
0004 // Copyright (C) 2012 Désiré Nuentsa-Wakam <desire.nuentsa_wakam@inria.fr>
0005 // Copyright (C) 2015 Gael Guennebaud <gael.guennebaud@inria.fr>
0006 //
0007 // This Source Code Form is subject to the terms of the Mozilla
0008 // Public License v. 2.0. If a copy of the MPL was not distributed
0009 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
0010 
0011 #ifndef EIGEN_INCOMPLETE_CHOlESKY_H
0012 #define EIGEN_INCOMPLETE_CHOlESKY_H
0013 
0014 #include <vector>
0015 #include <list>
0016 
0017 namespace RivetEigen {
0018 /**
0019   * \brief Modified Incomplete Cholesky with dual threshold
0020   *
0021   * References : C-J. Lin and J. J. Moré, Incomplete Cholesky Factorizations with
0022   *              Limited memory, SIAM J. Sci. Comput.  21(1), pp. 24-45, 1999
0023   *
0024   * \tparam Scalar the scalar type of the input matrices
0025   * \tparam _UpLo The triangular part that will be used for the computations. It can be Lower
0026     *               or Upper. Default is Lower.
0027   * \tparam _OrderingType The ordering method to use, either AMDOrdering<> or NaturalOrdering<>. Default is AMDOrdering<int>,
0028   *                       unless EIGEN_MPL2_ONLY is defined, in which case the default is NaturalOrdering<int>.
0029   *
0030   * \implsparsesolverconcept
0031   *
0032   * It performs the following incomplete factorization: \f$ S P A P' S \approx L L' \f$
0033   * where L is a lower triangular factor, S is a diagonal scaling matrix, and P is a
0034   * fill-in reducing permutation as computed by the ordering method.
0035   *
0036   * \b Shifting \b strategy: Let \f$ B = S P A P' S \f$  be the scaled matrix on which the factorization is carried out,
0037   * and \f$ \beta \f$ be the minimum value of the diagonal. If \f$ \beta > 0 \f$ then, the factorization is directly performed
0038   * on the matrix B. Otherwise, the factorization is performed on the shifted matrix \f$ B + (\sigma+|\beta| I \f$ where
0039   * \f$ \sigma \f$ is the initial shift value as returned and set by setInitialShift() method. The default value is \f$ \sigma = 10^{-3} \f$.
0040   * If the factorization fails, then the shift in doubled until it succeed or a maximum of ten attempts. If it still fails, as returned by
0041   * the info() method, then you can either increase the initial shift, or better use another preconditioning technique.
0042   *
0043   */
0044 template <typename Scalar, int _UpLo = Lower, typename _OrderingType = AMDOrdering<int> >
0045 class IncompleteCholesky : public SparseSolverBase<IncompleteCholesky<Scalar,_UpLo,_OrderingType> >
0046 {
0047   protected:
0048     typedef SparseSolverBase<IncompleteCholesky<Scalar,_UpLo,_OrderingType> > Base;
0049     using Base::m_isInitialized;
0050   public:
0051     typedef typename NumTraits<Scalar>::Real RealScalar;
0052     typedef _OrderingType OrderingType;
0053     typedef typename OrderingType::PermutationType PermutationType;
0054     typedef typename PermutationType::StorageIndex StorageIndex;
0055     typedef SparseMatrix<Scalar,ColMajor,StorageIndex> FactorType;
0056     typedef Matrix<Scalar,Dynamic,1> VectorSx;
0057     typedef Matrix<RealScalar,Dynamic,1> VectorRx;
0058     typedef Matrix<StorageIndex,Dynamic, 1> VectorIx;
0059     typedef std::vector<std::list<StorageIndex> > VectorList;
0060     enum { UpLo = _UpLo };
0061     enum {
0062       ColsAtCompileTime = Dynamic,
0063       MaxColsAtCompileTime = Dynamic
0064     };
0065   public:
0066 
0067     /** Default constructor leaving the object in a partly non-initialized stage.
0068       *
0069       * You must call compute() or the pair analyzePattern()/factorize() to make it valid.
0070       *
0071       * \sa IncompleteCholesky(const MatrixType&)
0072       */
0073     IncompleteCholesky() : m_initialShift(1e-3),m_analysisIsOk(false),m_factorizationIsOk(false) {}
0074 
0075     /** Constructor computing the incomplete factorization for the given matrix \a matrix.
0076       */
0077     template<typename MatrixType>
0078     IncompleteCholesky(const MatrixType& matrix) : m_initialShift(1e-3),m_analysisIsOk(false),m_factorizationIsOk(false)
0079     {
0080       compute(matrix);
0081     }
0082 
0083     /** \returns number of rows of the factored matrix */
0084     EIGEN_CONSTEXPR Index rows() const EIGEN_NOEXCEPT { return m_L.rows(); }
0085 
0086     /** \returns number of columns of the factored matrix */
0087     EIGEN_CONSTEXPR Index cols() const EIGEN_NOEXCEPT { return m_L.cols(); }
0088 
0089 
0090     /** \brief Reports whether previous computation was successful.
0091       *
0092       * It triggers an assertion if \c *this has not been initialized through the respective constructor,
0093       * or a call to compute() or analyzePattern().
0094       *
0095       * \returns \c Success if computation was successful,
0096       *          \c NumericalIssue if the matrix appears to be negative.
0097       */
0098     ComputationInfo info() const
0099     {
0100       eigen_assert(m_isInitialized && "IncompleteCholesky is not initialized.");
0101       return m_info;
0102     }
0103 
0104     /** \brief Set the initial shift parameter \f$ \sigma \f$.
0105       */
0106     void setInitialShift(RealScalar shift) { m_initialShift = shift; }
0107 
0108     /** \brief Computes the fill reducing permutation vector using the sparsity pattern of \a mat
0109       */
0110     template<typename MatrixType>
0111     void analyzePattern(const MatrixType& mat)
0112     {
0113       OrderingType ord;
0114       PermutationType pinv;
0115       ord(mat.template selfadjointView<UpLo>(), pinv);
0116       if(pinv.size()>0) m_perm = pinv.inverse();
0117       else              m_perm.resize(0);
0118       m_L.resize(mat.rows(), mat.cols());
0119       m_analysisIsOk = true;
0120       m_isInitialized = true;
0121       m_info = Success;
0122     }
0123 
0124     /** \brief Performs the numerical factorization of the input matrix \a mat
0125       *
0126       * The method analyzePattern() or compute() must have been called beforehand
0127       * with a matrix having the same pattern.
0128       *
0129       * \sa compute(), analyzePattern()
0130       */
0131     template<typename MatrixType>
0132     void factorize(const MatrixType& mat);
0133 
0134     /** Computes or re-computes the incomplete Cholesky factorization of the input matrix \a mat
0135       *
0136       * It is a shortcut for a sequential call to the analyzePattern() and factorize() methods.
0137       *
0138       * \sa analyzePattern(), factorize()
0139       */
0140     template<typename MatrixType>
0141     void compute(const MatrixType& mat)
0142     {
0143       analyzePattern(mat);
0144       factorize(mat);
0145     }
0146 
0147     // internal
0148     template<typename Rhs, typename Dest>
0149     void _solve_impl(const Rhs& b, Dest& x) const
0150     {
0151       eigen_assert(m_factorizationIsOk && "factorize() should be called first");
0152       if (m_perm.rows() == b.rows())  x = m_perm * b;
0153       else                            x = b;
0154       x = m_scale.asDiagonal() * x;
0155       x = m_L.template triangularView<Lower>().solve(x);
0156       x = m_L.adjoint().template triangularView<Upper>().solve(x);
0157       x = m_scale.asDiagonal() * x;
0158       if (m_perm.rows() == b.rows())
0159         x = m_perm.inverse() * x;
0160     }
0161 
0162     /** \returns the sparse lower triangular factor L */
0163     const FactorType& matrixL() const { eigen_assert("m_factorizationIsOk"); return m_L; }
0164 
0165     /** \returns a vector representing the scaling factor S */
0166     const VectorRx& scalingS() const { eigen_assert("m_factorizationIsOk"); return m_scale; }
0167 
0168     /** \returns the fill-in reducing permutation P (can be empty for a natural ordering) */
0169     const PermutationType& permutationP() const { eigen_assert("m_analysisIsOk"); return m_perm; }
0170 
0171   protected:
0172     FactorType m_L;              // The lower part stored in CSC
0173     VectorRx m_scale;            // The vector for scaling the matrix
0174     RealScalar m_initialShift;   // The initial shift parameter
0175     bool m_analysisIsOk;
0176     bool m_factorizationIsOk;
0177     ComputationInfo m_info;
0178     PermutationType m_perm;
0179 
0180   private:
0181     inline void updateList(Ref<const VectorIx> colPtr, Ref<VectorIx> rowIdx, Ref<VectorSx> vals, const Index& col, const Index& jk, VectorIx& firstElt, VectorList& listCol);
0182 };
0183 
0184 // Based on the following paper:
0185 //   C-J. Lin and J. J. Moré, Incomplete Cholesky Factorizations with
0186 //   Limited memory, SIAM J. Sci. Comput.  21(1), pp. 24-45, 1999
0187 //   http://ftp.mcs.anl.gov/pub/tech_reports/reports/P682.pdf
0188 template<typename Scalar, int _UpLo, typename OrderingType>
0189 template<typename _MatrixType>
0190 void IncompleteCholesky<Scalar,_UpLo, OrderingType>::factorize(const _MatrixType& mat)
0191 {
0192   using std::sqrt;
0193   eigen_assert(m_analysisIsOk && "analyzePattern() should be called first");
0194 
0195   // Dropping strategy : Keep only the p largest elements per column, where p is the number of elements in the column of the original matrix. Other strategies will be added
0196 
0197   // Apply the fill-reducing permutation computed in analyzePattern()
0198   if (m_perm.rows() == mat.rows() ) // To detect the null permutation
0199   {
0200     // The temporary is needed to make sure that the diagonal entry is properly sorted
0201     FactorType tmp(mat.rows(), mat.cols());
0202     tmp = mat.template selfadjointView<_UpLo>().twistedBy(m_perm);
0203     m_L.template selfadjointView<Lower>() = tmp.template selfadjointView<Lower>();
0204   }
0205   else
0206   {
0207     m_L.template selfadjointView<Lower>() = mat.template selfadjointView<_UpLo>();
0208   }
0209 
0210   Index n = m_L.cols();
0211   Index nnz = m_L.nonZeros();
0212   Map<VectorSx> vals(m_L.valuePtr(), nnz);         //values
0213   Map<VectorIx> rowIdx(m_L.innerIndexPtr(), nnz);  //Row indices
0214   Map<VectorIx> colPtr( m_L.outerIndexPtr(), n+1); // Pointer to the beginning of each row
0215   VectorIx firstElt(n-1); // for each j, points to the next entry in vals that will be used in the factorization
0216   VectorList listCol(n);  // listCol(j) is a linked list of columns to update column j
0217   VectorSx col_vals(n);   // Store a  nonzero values in each column
0218   VectorIx col_irow(n);   // Row indices of nonzero elements in each column
0219   VectorIx col_pattern(n);
0220   col_pattern.fill(-1);
0221   StorageIndex col_nnz;
0222 
0223 
0224   // Computes the scaling factors
0225   m_scale.resize(n);
0226   m_scale.setZero();
0227   for (Index j = 0; j < n; j++)
0228     for (Index k = colPtr[j]; k < colPtr[j+1]; k++)
0229     {
0230       m_scale(j) += numext::abs2(vals(k));
0231       if(rowIdx[k]!=j)
0232         m_scale(rowIdx[k]) += numext::abs2(vals(k));
0233     }
0234 
0235   m_scale = m_scale.cwiseSqrt().cwiseSqrt();
0236 
0237   for (Index j = 0; j < n; ++j)
0238     if(m_scale(j)>(std::numeric_limits<RealScalar>::min)())
0239       m_scale(j) = RealScalar(1)/m_scale(j);
0240     else
0241       m_scale(j) = 1;
0242 
0243   // TODO disable scaling if not needed, i.e., if it is roughly uniform? (this will make solve() faster)
0244 
0245   // Scale and compute the shift for the matrix
0246   RealScalar mindiag = NumTraits<RealScalar>::highest();
0247   for (Index j = 0; j < n; j++)
0248   {
0249     for (Index k = colPtr[j]; k < colPtr[j+1]; k++)
0250       vals[k] *= (m_scale(j)*m_scale(rowIdx[k]));
0251     eigen_internal_assert(rowIdx[colPtr[j]]==j && "IncompleteCholesky: only the lower triangular part must be stored");
0252     mindiag = numext::mini(numext::real(vals[colPtr[j]]), mindiag);
0253   }
0254 
0255   FactorType L_save = m_L;
0256 
0257   RealScalar shift = 0;
0258   if(mindiag <= RealScalar(0.))
0259     shift = m_initialShift - mindiag;
0260 
0261   m_info = NumericalIssue;
0262 
0263   // Try to perform the incomplete factorization using the current shift
0264   int iter = 0;
0265   do
0266   {
0267     // Apply the shift to the diagonal elements of the matrix
0268     for (Index j = 0; j < n; j++)
0269       vals[colPtr[j]] += shift;
0270 
0271     // jki version of the Cholesky factorization
0272     Index j=0;
0273     for (; j < n; ++j)
0274     {
0275       // Left-looking factorization of the j-th column
0276       // First, load the j-th column into col_vals
0277       Scalar diag = vals[colPtr[j]];  // It is assumed that only the lower part is stored
0278       col_nnz = 0;
0279       for (Index i = colPtr[j] + 1; i < colPtr[j+1]; i++)
0280       {
0281         StorageIndex l = rowIdx[i];
0282         col_vals(col_nnz) = vals[i];
0283         col_irow(col_nnz) = l;
0284         col_pattern(l) = col_nnz;
0285         col_nnz++;
0286       }
0287       {
0288         typename std::list<StorageIndex>::iterator k;
0289         // Browse all previous columns that will update column j
0290         for(k = listCol[j].begin(); k != listCol[j].end(); k++)
0291         {
0292           Index jk = firstElt(*k); // First element to use in the column
0293           eigen_internal_assert(rowIdx[jk]==j);
0294           Scalar v_j_jk = numext::conj(vals[jk]);
0295 
0296           jk += 1;
0297           for (Index i = jk; i < colPtr[*k+1]; i++)
0298           {
0299             StorageIndex l = rowIdx[i];
0300             if(col_pattern[l]<0)
0301             {
0302               col_vals(col_nnz) = vals[i] * v_j_jk;
0303               col_irow[col_nnz] = l;
0304               col_pattern(l) = col_nnz;
0305               col_nnz++;
0306             }
0307             else
0308               col_vals(col_pattern[l]) -= vals[i] * v_j_jk;
0309           }
0310           updateList(colPtr,rowIdx,vals, *k, jk, firstElt, listCol);
0311         }
0312       }
0313 
0314       // Scale the current column
0315       if(numext::real(diag) <= 0)
0316       {
0317         if(++iter>=10)
0318           return;
0319 
0320         // increase shift
0321         shift = numext::maxi(m_initialShift,RealScalar(2)*shift);
0322         // restore m_L, col_pattern, and listCol
0323         vals = Map<const VectorSx>(L_save.valuePtr(), nnz);
0324         rowIdx = Map<const VectorIx>(L_save.innerIndexPtr(), nnz);
0325         colPtr = Map<const VectorIx>(L_save.outerIndexPtr(), n+1);
0326         col_pattern.fill(-1);
0327         for(Index i=0; i<n; ++i)
0328           listCol[i].clear();
0329 
0330         break;
0331       }
0332 
0333       RealScalar rdiag = sqrt(numext::real(diag));
0334       vals[colPtr[j]] = rdiag;
0335       for (Index k = 0; k<col_nnz; ++k)
0336       {
0337         Index i = col_irow[k];
0338         //Scale
0339         col_vals(k) /= rdiag;
0340         //Update the remaining diagonals with col_vals
0341         vals[colPtr[i]] -= numext::abs2(col_vals(k));
0342       }
0343       // Select the largest p elements
0344       // p is the original number of elements in the column (without the diagonal)
0345       Index p = colPtr[j+1] - colPtr[j] - 1 ;
0346       Ref<VectorSx> cvals = col_vals.head(col_nnz);
0347       Ref<VectorIx> cirow = col_irow.head(col_nnz);
0348       internal::QuickSplit(cvals,cirow, p);
0349       // Insert the largest p elements in the matrix
0350       Index cpt = 0;
0351       for (Index i = colPtr[j]+1; i < colPtr[j+1]; i++)
0352       {
0353         vals[i] = col_vals(cpt);
0354         rowIdx[i] = col_irow(cpt);
0355         // restore col_pattern:
0356         col_pattern(col_irow(cpt)) = -1;
0357         cpt++;
0358       }
0359       // Get the first smallest row index and put it after the diagonal element
0360       Index jk = colPtr(j)+1;
0361       updateList(colPtr,rowIdx,vals,j,jk,firstElt,listCol);
0362     }
0363 
0364     if(j==n)
0365     {
0366       m_factorizationIsOk = true;
0367       m_info = Success;
0368     }
0369   } while(m_info!=Success);
0370 }
0371 
0372 template<typename Scalar, int _UpLo, typename OrderingType>
0373 inline void IncompleteCholesky<Scalar,_UpLo, OrderingType>::updateList(Ref<const VectorIx> colPtr, Ref<VectorIx> rowIdx, Ref<VectorSx> vals, const Index& col, const Index& jk, VectorIx& firstElt, VectorList& listCol)
0374 {
0375   if (jk < colPtr(col+1) )
0376   {
0377     Index p = colPtr(col+1) - jk;
0378     Index minpos;
0379     rowIdx.segment(jk,p).minCoeff(&minpos);
0380     minpos += jk;
0381     if (rowIdx(minpos) != rowIdx(jk))
0382     {
0383       //Swap
0384       std::swap(rowIdx(jk),rowIdx(minpos));
0385       std::swap(vals(jk),vals(minpos));
0386     }
0387     firstElt(col) = internal::convert_index<StorageIndex,Index>(jk);
0388     listCol[rowIdx(jk)].push_back(internal::convert_index<StorageIndex,Index>(col));
0389   }
0390 }
0391 
0392 } // end namespace RivetEigen
0393 
0394 #endif