// Copyright (C) 2020-2022 Yixuan Qiu // // This Source Code Form is subject to the terms of the Mozilla // Public License v. 2.0. If a copy of the MPL was not distributed // with this file, You can obtain one at https://mozilla.org/MPL/2.0/. #ifndef SPECTRA_SYM_SHIFT_INVERT_H #define SPECTRA_SYM_SHIFT_INVERT_H #include #include #include #include #include // std::conditional, std::is_same #include "../LinAlg/BKLDLT.h" #include "../Util/CompInfo.h" namespace Spectra { /// \cond // Compute and factorize A-sigma*B without unnecessary copying // Default case: A is sparse, B is sparse template class SymShiftInvertHelper { public: template static bool factorize(Fac& fac, const ArgA& A, const ArgB& B, const Scalar& sigma) { using SpMat = typename ArgA::PlainObject; SpMat matA = A.template selfadjointView(); SpMat matB = B.template selfadjointView(); SpMat mat = matA - sigma * matB; // SparseLU solver fac.isSymmetric(true); fac.compute(mat); // Return true if successful return fac.info() == Eigen::Success; } }; // A is dense, B is dense or sparse template class SymShiftInvertHelper { public: template static bool factorize(Fac& fac, const ArgA& A, const ArgB& B, const Scalar& sigma) { using Matrix = typename ArgA::PlainObject; // Make a copy of the triangular part of A Matrix mat(A.rows(), A.cols()); mat.template triangularView() = A; // Update triangular part of mat if (UploA == UploB) mat -= (B * sigma).template triangularView(); else mat -= (B * sigma).template triangularView().transpose(); // BKLDLT solver fac.compute(mat, UploA); // Return true if successful return fac.info() == CompInfo::Successful; } }; // A is sparse, B is dense template class SymShiftInvertHelper { public: template static bool factorize(Fac& fac, const ArgA& A, const ArgB& B, const Scalar& sigma) { using Matrix = typename ArgB::PlainObject; // Construct the triangular part of -sigma*B Matrix mat(B.rows(), B.cols()); mat.template triangularView() = -sigma * B; // Update triangular part of mat if (UploA == UploB) mat += A.template triangularView(); else mat += A.template triangularView().transpose(); // BKLDLT solver fac.compute(mat, UploB); // Return true if successful return fac.info() == CompInfo::Successful; } }; /// \endcond /// /// \ingroup MatOp /// /// This class defines matrix operations required by the generalized eigen solver /// in the shift-and-invert mode. Given two symmetric matrices \f$A\f$ and \f$B\f$, /// it solves the linear equation \f$y=(A-\sigma B)^{-1}x\f$, where \f$\sigma\f$ is a real shift. /// Each of \f$A\f$ and \f$B\f$ can be a dense or sparse matrix. /// /// This class is intended to be used with the SymGEigsShiftSolver generalized eigen solver. /// /// \tparam Scalar_ The element type of the matrices. /// Currently supported types are `float`, `double`, and `long double`. /// \tparam TypeA The type of the \f$A\f$ matrix, indicating whether \f$A\f$ is /// dense or sparse. Possible values are `Eigen::Dense` and `Eigen::Sparse`. /// \tparam TypeB The type of the \f$B\f$ matrix, indicating whether \f$B\f$ is /// dense or sparse. Possible values are `Eigen::Dense` and `Eigen::Sparse`. /// \tparam UploA Whether the lower or upper triangular part of \f$A\f$ should be used. /// Possible values are `Eigen::Lower` and `Eigen::Upper`. /// \tparam UploB Whether the lower or upper triangular part of \f$B\f$ should be used. /// Possible values are `Eigen::Lower` and `Eigen::Upper`. /// \tparam FlagsA Additional flags for the matrix class of \f$A\f$. /// Possible values are `Eigen::ColMajor` and `Eigen::RowMajor`. /// \tparam FlagsB Additional flags for the matrix class of \f$B\f$. /// Possible values are `Eigen::ColMajor` and `Eigen::RowMajor`. /// \tparam StorageIndexA The storage index type of the \f$A\f$ matrix, only used when \f$A\f$ /// is a sparse matrix. /// \tparam StorageIndexB The storage index type of the \f$B\f$ matrix, only used when \f$B\f$ /// is a sparse matrix. /// template class SymShiftInvert { public: /// /// Element type of the matrix. /// using Scalar = Scalar_; private: using Index = Eigen::Index; // Hypothetical type of the A matrix, either dense or sparse using DenseTypeA = Eigen::Matrix; using SparseTypeA = Eigen::SparseMatrix; // Whether A is sparse using ASparse = std::is_same; // Actual type of the A matrix using MatrixA = typename std::conditional::type; // Hypothetical type of the B matrix, either dense or sparse using DenseTypeB = Eigen::Matrix; using SparseTypeB = Eigen::SparseMatrix; // Whether B is sparse using BSparse = std::is_same; // Actual type of the B matrix using MatrixB = typename std::conditional::type; using Vector = Eigen::Matrix; using MapConstVec = Eigen::Map; using MapVec = Eigen::Map; // The type of A-sigma*B if one of A and B is dense // DenseType = if (A is dense) MatrixA else MatrixB using DenseType = typename std::conditional::type; // The type of A-sigma*B // If both A and B are sparse, the result is MatrixA; otherwise the result is DenseType using ResType = typename std::conditional::type; // If both A and B are sparse, then the result A-sigma*B is sparse, so we use // sparseLU for factorization; otherwise A-sigma*B is dense, and we use BKLDLT using FacType = typename std::conditional< ASparse::value && BSparse::value, Eigen::SparseLU, BKLDLT>::type; using ConstGenericMatrixA = const Eigen::Ref; using ConstGenericMatrixB = const Eigen::Ref; ConstGenericMatrixA m_matA; ConstGenericMatrixB m_matB; const Index m_n; FacType m_solver; public: /// /// Constructor to create the matrix operation object. /// /// \param A A dense or sparse matrix object, whose type can be `Eigen::Matrix<...>`, /// `Eigen::SparseMatrix<...>`, `Eigen::Map>`, /// `Eigen::Map>`, `Eigen::Ref>`, /// `Eigen::Ref>`, etc. /// \param B A dense or sparse matrix object. /// template SymShiftInvert(const Eigen::EigenBase& A, const Eigen::EigenBase& B) : m_matA(A.derived()), m_matB(B.derived()), m_n(A.rows()) { static_assert( static_cast(DerivedA::PlainObject::IsRowMajor) == static_cast(MatrixA::IsRowMajor), "SymShiftInvert: the \"FlagsA\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); static_assert( static_cast(DerivedB::PlainObject::IsRowMajor) == static_cast(MatrixB::IsRowMajor), "SymShiftInvert: the \"FlagsB\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); if (m_n != A.cols() || m_n != B.rows() || m_n != B.cols()) throw std::invalid_argument("SymShiftInvert: A and B must be square matrices of the same size"); } /// /// Return the number of rows of the underlying matrix. /// Index rows() const { return m_n; } /// /// Return the number of columns of the underlying matrix. /// Index cols() const { return m_n; } /// /// Set the real shift \f$\sigma\f$. /// void set_shift(const Scalar& sigma) { constexpr bool AIsSparse = ASparse::value; constexpr bool BIsSparse = BSparse::value; using Helper = SymShiftInvertHelper; const bool success = Helper::factorize(m_solver, m_matA, m_matB, sigma); if (!success) throw std::invalid_argument("SymShiftInvert: factorization failed with the given shift"); } /// /// Perform the shift-invert operation \f$y=(A-\sigma B)^{-1}x\f$. /// /// \param x_in Pointer to the \f$x\f$ vector. /// \param y_out Pointer to the \f$y\f$ vector. /// // y_out = inv(A - sigma * B) * x_in void perform_op(const Scalar* x_in, Scalar* y_out) const { MapConstVec x(x_in, m_n); MapVec y(y_out, m_n); y.noalias() = m_solver.solve(x); } }; } // namespace Spectra #endif // SPECTRA_SYM_SHIFT_INVERT_H