diff --git a/thirdparty/include/Spectra/DavidsonSymEigsSolver.h b/thirdparty/include/Spectra/DavidsonSymEigsSolver.h new file mode 100644 index 0000000..fe9f56c --- /dev/null +++ b/thirdparty/include/Spectra/DavidsonSymEigsSolver.h @@ -0,0 +1,93 @@ +// Copyright (C) 2020 Netherlands eScience Center +// +// 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_DAVIDSON_SYM_EIGS_SOLVER_H +#define SPECTRA_DAVIDSON_SYM_EIGS_SOLVER_H + +#include + +#include "JDSymEigsBase.h" +#include "Util/SelectionRule.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implement the DPR correction for the Davidson algorithms. +/// The algorithms in the Davidson family only differ in how the correction +/// vectors are computed and optionally in the initial orthogonal basis set. +/// +/// the DPR correction compute the new correction vector using the following expression: +/// \f[ correction = -(\boldsymbol{D} - \rho \boldsymbol{I})^{-1} \boldsymbol{r} \f] +/// where +/// \f$D\f$ is the diagonal of the target matrix, \f$\rho\f$ the Ritz eigenvalue, +/// \f$I\f$ the identity matrix and \f$r\f$ the residue vector. +/// +template +class DavidsonSymEigsSolver : public JDSymEigsBase, OpType> +{ +private: + using Index = Eigen::Index; + using Scalar = typename OpType::Scalar; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + + Vector m_diagonal; + +public: + DavidsonSymEigsSolver(OpType& op, Index nev, Index nvec_init, Index nvec_max) : + JDSymEigsBase, OpType>(op, nev, nvec_init, nvec_max) + { + m_diagonal.resize(this->m_matrix_operator.rows()); + for (Index i = 0; i < op.rows(); i++) + { + m_diagonal(i) = op(i, i); + } + } + + DavidsonSymEigsSolver(OpType& op, Index nev) : + DavidsonSymEigsSolver(op, nev, 2 * nev, 10 * nev) {} + + /// Create initial search space based on the diagonal + /// and the spectrum'target (highest or lowest) + /// + /// \param selection Spectrum section to target (e.g. lowest, etc.) + /// \return Matrix with the initial orthonormal basis + Matrix setup_initial_search_space(SortRule selection) const + { + std::vector indices_sorted = argsort(selection, m_diagonal); + + Matrix initial_basis = Matrix::Zero(this->m_matrix_operator.rows(), this->m_initial_search_space_size); + + for (Index k = 0; k < this->m_initial_search_space_size; k++) + { + Index row = indices_sorted[k]; + initial_basis(row, k) = 1.0; + } + return initial_basis; + } + + /// Compute the corrections using the DPR method. + /// + /// \return New correction vectors. + Matrix calculate_correction_vector() const + { + const Matrix& residues = this->m_ritz_pairs.residues(); + const Vector& eigvals = this->m_ritz_pairs.ritz_values(); + Matrix correction = Matrix::Zero(this->m_matrix_operator.rows(), this->m_correction_size); + for (Index k = 0; k < this->m_correction_size; k++) + { + Vector tmp = eigvals(k) - m_diagonal.array(); + correction.col(k) = residues.col(k).array() / tmp.array(); + } + return correction; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DAVIDSON_SYM_EIGS_SOLVER_H diff --git a/thirdparty/include/Spectra/GenEigsBase.h b/thirdparty/include/Spectra/GenEigsBase.h new file mode 100644 index 0000000..9f71b7c --- /dev/null +++ b/thirdparty/include/Spectra/GenEigsBase.h @@ -0,0 +1,532 @@ +// Copyright (C) 2018-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_GEN_EIGS_BASE_H +#define SPECTRA_GEN_EIGS_BASE_H + +#include +#include // std::vector +#include // std::abs, std::pow, std::sqrt +#include // std::min, std::copy +#include // std::complex, std::conj, std::norm, std::abs +#include // std::invalid_argument + +#include "Util/Version.h" +#include "Util/TypeTraits.h" +#include "Util/SelectionRule.h" +#include "Util/CompInfo.h" +#include "Util/SimpleRandom.h" +#include "MatOp/internal/ArnoldiOp.h" +#include "LinAlg/UpperHessenbergQR.h" +#include "LinAlg/DoubleShiftQR.h" +#include "LinAlg/UpperHessenbergEigen.h" +#include "LinAlg/Arnoldi.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This is the base class for general eigen solvers, mainly for internal use. +/// It is kept here to provide the documentation for member functions of concrete eigen solvers +/// such as GenEigsSolver and GenEigsRealShiftSolver. +/// +template +class GenEigsBase +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using Array = Eigen::Array; + using BoolArray = Eigen::Array; + using MapMat = Eigen::Map; + using MapVec = Eigen::Map; + using MapConstVec = Eigen::Map; + + using Complex = std::complex; + using ComplexMatrix = Eigen::Matrix; + using ComplexVector = Eigen::Matrix; + + using ArnoldiOpType = ArnoldiOp; + using ArnoldiFac = Arnoldi; + +protected: + // clang-format off + OpType& m_op; // object to conduct matrix operation, + // e.g. matrix-vector product + const Index m_n; // dimension of matrix A + const Index m_nev; // number of eigenvalues requested + const Index m_ncv; // dimension of Krylov subspace in the Arnoldi method + Index m_nmatop; // number of matrix operations called + Index m_niter; // number of restarting iterations + + ArnoldiFac m_fac; // Arnoldi factorization + + ComplexVector m_ritz_val; // Ritz values + ComplexMatrix m_ritz_vec; // Ritz vectors + ComplexVector m_ritz_est; // last row of m_ritz_vec, also called the Ritz estimates + +private: + BoolArray m_ritz_conv; // indicator of the convergence of Ritz values + CompInfo m_info; // status of the computation + // clang-format on + + // Real Ritz values calculated from UpperHessenbergEigen have exact zero imaginary part + // Complex Ritz values have exact conjugate pairs + // So we use exact tests here + static bool is_complex(const Complex& v) { return v.imag() != Scalar(0); } + static bool is_conj(const Complex& v1, const Complex& v2) { return v1 == Eigen::numext::conj(v2); } + + // Implicitly restarted Arnoldi factorization + void restart(Index k, SortRule selection) + { + using std::norm; + + if (k >= m_ncv) + return; + + DoubleShiftQR decomp_ds(m_ncv); + UpperHessenbergQR decomp_hb(m_ncv); + Matrix Q = Matrix::Identity(m_ncv, m_ncv); + + for (Index i = k; i < m_ncv; i++) + { + if (is_complex(m_ritz_val[i]) && is_conj(m_ritz_val[i], m_ritz_val[i + 1])) + { + // H - mu * I = Q1 * R1 + // H <- R1 * Q1 + mu * I = Q1' * H * Q1 + // H - conj(mu) * I = Q2 * R2 + // H <- R2 * Q2 + conj(mu) * I = Q2' * H * Q2 + // + // (H - mu * I) * (H - conj(mu) * I) = Q1 * Q2 * R2 * R1 = Q * R + const Scalar s = Scalar(2) * m_ritz_val[i].real(); + const Scalar t = norm(m_ritz_val[i]); + + decomp_ds.compute(m_fac.matrix_H(), s, t); + + // Q -> Q * Qi + decomp_ds.apply_YQ(Q); + // H -> Q'HQ + // Matrix Q = Matrix::Identity(m_ncv, m_ncv); + // decomp_ds.apply_YQ(Q); + // m_fac_H = Q.transpose() * m_fac_H * Q; + m_fac.compress_H(decomp_ds); + + i++; + } + else + { + // QR decomposition of H - mu * I, mu is real + decomp_hb.compute(m_fac.matrix_H(), m_ritz_val[i].real()); + + // Q -> Q * Qi + decomp_hb.apply_YQ(Q); + // H -> Q'HQ = RQ + mu * I + m_fac.compress_H(decomp_hb); + } + } + + m_fac.compress_V(Q); + m_fac.factorize_from(k, m_ncv, m_nmatop); + + retrieve_ritzpair(selection); + } + + // Calculates the number of converged Ritz values + Index num_converged(const Scalar& tol) + { + using std::pow; + + // The machine precision, ~= 1e-16 for the "double" type + constexpr Scalar eps = TypeTraits::epsilon(); + // std::pow() is not constexpr, so we do not declare eps23 to be constexpr + // But most compilers should be able to compute eps23 at compile time + const Scalar eps23 = pow(eps, Scalar(2) / 3); + + // thresh = tol * max(eps23, abs(theta)), theta for Ritz value + Array thresh = tol * m_ritz_val.head(m_nev).array().abs().max(eps23); + Array resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm(); + // Converged "wanted" Ritz values + m_ritz_conv = (resid < thresh); + + return m_ritz_conv.count(); + } + + // Returns the adjusted nev for restarting + Index nev_adjusted(Index nconv) + { + using std::abs; + + // A very small value, but 1.0 / near_0 does not overflow + // ~= 1e-307 for the "double" type + constexpr Scalar near_0 = TypeTraits::min() * Scalar(10); + + Index nev_new = m_nev; + for (Index i = m_nev; i < m_ncv; i++) + if (abs(m_ritz_est[i]) < near_0) + nev_new++; + + // Adjust nev_new, according to dnaup2.f line 660~674 in ARPACK + nev_new += (std::min)(nconv, (m_ncv - nev_new) / 2); + if (nev_new == 1 && m_ncv >= 6) + nev_new = m_ncv / 2; + else if (nev_new == 1 && m_ncv > 3) + nev_new = 2; + + if (nev_new > m_ncv - 2) + nev_new = m_ncv - 2; + + // Increase nev by one if ritz_val[nev - 1] and + // ritz_val[nev] are conjugate pairs + if (is_complex(m_ritz_val[nev_new - 1]) && + is_conj(m_ritz_val[nev_new - 1], m_ritz_val[nev_new])) + { + nev_new++; + } + + return nev_new; + } + + // Retrieves and sorts Ritz values and Ritz vectors + void retrieve_ritzpair(SortRule selection) + { + UpperHessenbergEigen decomp(m_fac.matrix_H()); + const ComplexVector& evals = decomp.eigenvalues(); + ComplexMatrix evecs = decomp.eigenvectors(); + + // Sort Ritz values and put the wanted ones at the beginning + std::vector ind; + switch (selection) + { + case SortRule::LargestMagn: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + case SortRule::LargestReal: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + case SortRule::LargestImag: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + case SortRule::SmallestMagn: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + case SortRule::SmallestReal: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + case SortRule::SmallestImag: + { + SortEigenvalue sorting(evals.data(), m_ncv); + sorting.swap(ind); + break; + } + default: + throw std::invalid_argument("unsupported selection rule"); + } + + // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively + for (Index i = 0; i < m_ncv; i++) + { + m_ritz_val[i] = evals[ind[i]]; + m_ritz_est[i] = evecs(m_ncv - 1, ind[i]); + } + for (Index i = 0; i < m_nev; i++) + { + m_ritz_vec.col(i).noalias() = evecs.col(ind[i]); + } + } + +protected: + // Sorts the first nev Ritz pairs in the specified order + // This is used to return the final results + virtual void sort_ritzpair(SortRule sort_rule) + { + std::vector ind; + switch (sort_rule) + { + case SortRule::LargestMagn: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + case SortRule::LargestReal: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + case SortRule::LargestImag: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + case SortRule::SmallestMagn: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + case SortRule::SmallestReal: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + case SortRule::SmallestImag: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + sorting.swap(ind); + break; + } + default: + throw std::invalid_argument("unsupported sorting rule"); + } + + ComplexVector new_ritz_val(m_ncv); + ComplexMatrix new_ritz_vec(m_ncv, m_nev); + BoolArray new_ritz_conv(m_nev); + + for (Index i = 0; i < m_nev; i++) + { + new_ritz_val[i] = m_ritz_val[ind[i]]; + new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]); + new_ritz_conv[i] = m_ritz_conv[ind[i]]; + } + + m_ritz_val.swap(new_ritz_val); + m_ritz_vec.swap(new_ritz_vec); + m_ritz_conv.swap(new_ritz_conv); + } + +public: + /// \cond + + GenEigsBase(OpType& op, const BOpType& Bop, Index nev, Index ncv) : + m_op(op), + m_n(m_op.rows()), + m_nev(nev), + m_ncv(ncv > m_n ? m_n : ncv), + m_nmatop(0), + m_niter(0), + m_fac(ArnoldiOpType(op, Bop), m_ncv), + m_info(CompInfo::NotComputed) + { + if (nev < 1 || nev > m_n - 2) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 2, n is the size of matrix"); + + if (ncv < nev + 2 || ncv > m_n) + throw std::invalid_argument("ncv must satisfy nev + 2 <= ncv <= n, n is the size of matrix"); + } + + /// + /// Virtual destructor + /// + virtual ~GenEigsBase() {} + + /// \endcond + + /// + /// Initializes the solver by providing an initial residual vector. + /// + /// \param init_resid Pointer to the initial residual vector. + /// + /// **Spectra** (and also **ARPACK**) uses an iterative algorithm + /// to find eigenvalues. This function allows the user to provide the initial + /// residual vector. + /// + void init(const Scalar* init_resid) + { + // Reset all matrices/vectors to zero + m_ritz_val.resize(m_ncv); + m_ritz_vec.resize(m_ncv, m_nev); + m_ritz_est.resize(m_ncv); + m_ritz_conv.resize(m_nev); + + m_ritz_val.setZero(); + m_ritz_vec.setZero(); + m_ritz_est.setZero(); + m_ritz_conv.setZero(); + + m_nmatop = 0; + m_niter = 0; + + // Initialize the Arnoldi factorization + MapConstVec v0(init_resid, m_n); + m_fac.init(v0, m_nmatop); + } + + /// + /// Initializes the solver by providing a random initial residual vector. + /// + /// This overloaded function generates a random initial residual vector + /// (with a fixed random seed) for the algorithm. Elements in the vector + /// follow independent Uniform(-0.5, 0.5) distribution. + /// + void init() + { + SimpleRandom rng(0); + Vector init_resid = rng.random_vec(m_n); + init(init_resid.data()); + } + + /// + /// Conducts the major computation procedure. + /// + /// \param selection An enumeration value indicating the selection rule of + /// the requested eigenvalues, for example `SortRule::LargestMagn` + /// to retrieve eigenvalues with the largest magnitude. + /// The full list of enumeration values can be found in + /// \ref Enumerations. + /// \param maxit Maximum number of iterations allowed in the algorithm. + /// \param tol Precision parameter for the calculated eigenvalues. + /// \param sorting Rule to sort the eigenvalues and eigenvectors. + /// Supported values are + /// `SortRule::LargestMagn`, `SortRule::LargestReal`, + /// `SortRule::LargestImag`, `SortRule::SmallestMagn`, + /// `SortRule::SmallestReal` and `SortRule::SmallestImag`, + /// for example `SortRule::LargestMagn` indicates that eigenvalues + /// with largest magnitude come first. + /// Note that this argument is only used to + /// **sort** the final result, and the **selection** rule + /// (e.g. selecting the largest or smallest eigenvalues in the + /// full spectrum) is specified by the parameter `selection`. + /// + /// \return Number of converged eigenvalues. + /// + Index compute(SortRule selection = SortRule::LargestMagn, Index maxit = 1000, + Scalar tol = 1e-10, SortRule sorting = SortRule::LargestMagn) + { + // The m-step Arnoldi factorization + m_fac.factorize_from(1, m_ncv, m_nmatop); + retrieve_ritzpair(selection); + // Restarting + Index i, nconv = 0, nev_adj; + for (i = 0; i < maxit; i++) + { + nconv = num_converged(tol); + if (nconv >= m_nev) + break; + + nev_adj = nev_adjusted(nconv); + restart(nev_adj, selection); + } + // Sorting results + sort_ritzpair(sorting); + + m_niter += i + 1; + m_info = (nconv >= m_nev) ? CompInfo::Successful : CompInfo::NotConverging; + + return (std::min)(m_nev, nconv); + } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Returns the number of iterations used in the computation. + /// + Index num_iterations() const { return m_niter; } + + /// + /// Returns the number of matrix operations used in the computation. + /// + Index num_operations() const { return m_nmatop; } + + /// + /// Returns the converged eigenvalues. + /// + /// \return A complex-valued vector containing the eigenvalues. + /// Returned vector type will be `Eigen::Vector, ...>`, depending on + /// the template parameter `Scalar` defined. + /// + ComplexVector eigenvalues() const + { + const Index nconv = m_ritz_conv.cast().sum(); + ComplexVector res(nconv); + + if (!nconv) + return res; + + Index j = 0; + for (Index i = 0; i < m_nev; i++) + { + if (m_ritz_conv[i]) + { + res[j] = m_ritz_val[i]; + j++; + } + } + + return res; + } + + /// + /// Returns the eigenvectors associated with the converged eigenvalues. + /// + /// \param nvec The number of eigenvectors to return. + /// + /// \return A complex-valued matrix containing the eigenvectors. + /// Returned matrix type will be `Eigen::Matrix, ...>`, + /// depending on the template parameter `Scalar` defined. + /// + ComplexMatrix eigenvectors(Index nvec) const + { + const Index nconv = m_ritz_conv.cast().sum(); + nvec = (std::min)(nvec, nconv); + ComplexMatrix res(m_n, nvec); + + if (!nvec) + return res; + + ComplexMatrix ritz_vec_conv(m_ncv, nvec); + Index j = 0; + for (Index i = 0; i < m_nev && j < nvec; i++) + { + if (m_ritz_conv[i]) + { + ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i); + j++; + } + } + + res.noalias() = m_fac.matrix_V() * ritz_vec_conv; + + return res; + } + + /// + /// Returns all converged eigenvectors. + /// + ComplexMatrix eigenvectors() const + { + return eigenvectors(m_nev); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_GEN_EIGS_BASE_H diff --git a/thirdparty/include/Spectra/GenEigsComplexShiftSolver.h b/thirdparty/include/Spectra/GenEigsComplexShiftSolver.h new file mode 100644 index 0000000..90040a8 --- /dev/null +++ b/thirdparty/include/Spectra/GenEigsComplexShiftSolver.h @@ -0,0 +1,159 @@ +// Copyright (C) 2016-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_GEN_EIGS_COMPLEX_SHIFT_SOLVER_H +#define SPECTRA_GEN_EIGS_COMPLEX_SHIFT_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenComplexShiftSolve.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices with +/// a complex shift value in the **shift-and-invert mode**. The background +/// knowledge of the shift-and-invert mode can be found in the documentation +/// of the SymEigsShiftSolver class. +/// +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseGenComplexShiftSolve and +/// SparseGenComplexShiftSolve, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseGenComplexShiftSolve. +/// +template > +class GenEigsComplexShiftSolver : public GenEigsBase +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Complex = std::complex; + using Vector = Eigen::Matrix; + using ComplexArray = Eigen::Array; + + using Base = GenEigsBase; + using Base::m_op; + using Base::m_n; + using Base::m_nev; + using Base::m_fac; + using Base::m_ritz_val; + using Base::m_ritz_vec; + + const Scalar m_sigmar; + const Scalar m_sigmai; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + using std::abs; + using std::sqrt; + using std::norm; + + // The eigenvalues we get from the iteration is + // nu = 0.5 * (1 / (lambda - sigma) + 1 / (lambda - conj(sigma))) + // So the eigenvalues of the original problem is + // 1 \pm sqrt(1 - 4 * nu^2 * sigmai^2) + // lambda = sigmar + ----------------------------------- + // 2 * nu + // We need to pick the correct root + // Let (lambdaj, vj) be the j-th eigen pair, then A * vj = lambdaj * vj + // and inv(A - r * I) * vj = 1 / (lambdaj - r) * vj + // where r is any shift value. + // We can use this identity to determine lambdaj + // + // op(v) computes Re(inv(A - r * I) * v) for any real v + // If r is real, then op(v) is also real. Let a = Re(vj), b = Im(vj), + // then op(vj) = op(a) + op(b) * i + // By comparing op(vj) and [1 / (lambdaj - r) * vj], we can determine + // which one is the correct root + + // Select a random shift value + SimpleRandom rng(0); + const Scalar shiftr = rng.random() * m_sigmar + rng.random(); + const Complex shift = Complex(shiftr, Scalar(0)); + m_op.set_shift(shiftr, Scalar(0)); + + // Calculate inv(A - r * I) * vj + Vector v_real(m_n), v_imag(m_n), OPv_real(m_n), OPv_imag(m_n); + constexpr Scalar eps = TypeTraits::epsilon(); + for (Index i = 0; i < m_nev; i++) + { + v_real.noalias() = m_fac.matrix_V() * m_ritz_vec.col(i).real(); + v_imag.noalias() = m_fac.matrix_V() * m_ritz_vec.col(i).imag(); + m_op.perform_op(v_real.data(), OPv_real.data()); + m_op.perform_op(v_imag.data(), OPv_imag.data()); + + // Two roots computed from the quadratic equation + const Complex nu = m_ritz_val[i]; + const Complex root_part1 = m_sigmar + Scalar(0.5) / nu; + const Complex root_part2 = Scalar(0.5) * sqrt(Scalar(1) - Scalar(4) * m_sigmai * m_sigmai * (nu * nu)) / nu; + const Complex root1 = root_part1 + root_part2; + const Complex root2 = root_part1 - root_part2; + + // Test roots + Scalar err1 = Scalar(0), err2 = Scalar(0); + for (int k = 0; k < m_n; k++) + { + const Complex rhs1 = Complex(v_real[k], v_imag[k]) / (root1 - shift); + const Complex rhs2 = Complex(v_real[k], v_imag[k]) / (root2 - shift); + const Complex OPv = Complex(OPv_real[k], OPv_imag[k]); + err1 += norm(OPv - rhs1); + err2 += norm(OPv - rhs2); + } + + const Complex lambdaj = (err1 < err2) ? root1 : root2; + m_ritz_val[i] = lambdaj; + + if (abs(Eigen::numext::imag(lambdaj)) > eps) + { + m_ritz_val[i + 1] = Eigen::numext::conj(lambdaj); + i++; + } + else + { + m_ritz_val[i] = Complex(Eigen::numext::real(lambdaj), Scalar(0)); + } + } + + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op The matrix operation object that implements + /// the complex shift-solve operation of \f$A\f$: calculating + /// \f$\mathrm{Re}\{(A-\sigma I)^{-1}v\}\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseGenComplexShiftSolve, or + /// define their own that implements all the public members + /// as in DenseGenComplexShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// \param sigmar The real part of the shift. + /// \param sigmai The imaginary part of the shift. + /// + GenEigsComplexShiftSolver(OpType& op, Index nev, Index ncv, const Scalar& sigmar, const Scalar& sigmai) : + Base(op, IdentityBOp(), nev, ncv), + m_sigmar(sigmar), m_sigmai(sigmai) + { + op.set_shift(m_sigmar, m_sigmai); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_GEN_EIGS_COMPLEX_SHIFT_SOLVER_H diff --git a/thirdparty/include/Spectra/GenEigsRealShiftSolver.h b/thirdparty/include/Spectra/GenEigsRealShiftSolver.h new file mode 100644 index 0000000..a1d08b8 --- /dev/null +++ b/thirdparty/include/Spectra/GenEigsRealShiftSolver.h @@ -0,0 +1,85 @@ +// Copyright (C) 2016-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_GEN_EIGS_REAL_SHIFT_SOLVER_H +#define SPECTRA_GEN_EIGS_REAL_SHIFT_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenRealShiftSolve.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices with +/// a real shift value in the **shift-and-invert mode**. The background +/// knowledge of the shift-and-invert mode can be found in the documentation +/// of the SymEigsShiftSolver class. +/// +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseGenRealShiftSolve and +/// SparseGenRealShiftSolve, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseGenRealShiftSolve. +/// +template > +class GenEigsRealShiftSolver : public GenEigsBase +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Complex = std::complex; + using ComplexArray = Eigen::Array; + + using Base = GenEigsBase; + using Base::m_nev; + using Base::m_ritz_val; + + const Scalar m_sigma; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + // The eigenvalues we get from the iteration is nu = 1 / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = 1 / nu + sigma + m_ritz_val.head(m_nev) = Scalar(1) / m_ritz_val.head(m_nev).array() + m_sigma; + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op The matrix operation object that implements + /// the shift-solve operation of \f$A\f$: calculating + /// \f$(A-\sigma I)^{-1}v\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseGenRealShiftSolve, or + /// define their own that implements all the public members + /// as in DenseGenRealShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// \param sigma The real-valued shift. + /// + GenEigsRealShiftSolver(OpType& op, Index nev, Index ncv, const Scalar& sigma) : + Base(op, IdentityBOp(), nev, ncv), + m_sigma(sigma) + { + op.set_shift(m_sigma); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_GEN_EIGS_REAL_SHIFT_SOLVER_H diff --git a/thirdparty/include/Spectra/GenEigsSolver.h b/thirdparty/include/Spectra/GenEigsSolver.h new file mode 100644 index 0000000..b6c37ca --- /dev/null +++ b/thirdparty/include/Spectra/GenEigsSolver.h @@ -0,0 +1,149 @@ +// Copyright (C) 2016-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_GEN_EIGS_SOLVER_H +#define SPECTRA_GEN_EIGS_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenMatProd.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices, i.e., +/// to solve \f$Ax=\lambda x\f$ for a possibly non-symmetric \f$A\f$ matrix. +/// +/// Most of the background information documented in the SymEigsSolver class +/// also applies to the GenEigsSolver class here, except that the eigenvalues +/// and eigenvectors of a general matrix can now be complex-valued. +/// +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseGenMatProd and +/// SparseGenMatProd, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseGenMatProd. +/// +/// An example that illustrates the usage of GenEigsSolver is give below: +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to calculate the eigenvalues of M +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(10, 10); +/// +/// // Construct matrix operation object using the wrapper class +/// DenseGenMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest +/// // (in magnitude, or norm) three eigenvalues +/// GenEigsSolver> eigs(op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(SortRule::LargestMagn); +/// +/// // Retrieve results +/// Eigen::VectorXcd evalues; +/// if (eigs.info() == CompInfo::Successful) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +/// +/// And also an example for sparse matrices: +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // A band matrix with 1 on the main diagonal, 2 on the below-main subdiagonal, +/// // and 3 on the above-main subdiagonal +/// const int n = 10; +/// Eigen::SparseMatrix M(n, n); +/// M.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for (int i = 0; i < n; i++) +/// { +/// M.insert(i, i) = 1.0; +/// if (i > 0) +/// M.insert(i - 1, i) = 3.0; +/// if (i < n - 1) +/// M.insert(i + 1, i) = 2.0; +/// } +/// +/// // Construct matrix operation object using the wrapper class SparseGenMatProd +/// SparseGenMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest three eigenvalues +/// GenEigsSolver> eigs(op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(SortRule::LargestMagn); +/// +/// // Retrieve results +/// Eigen::VectorXcd evalues; +/// if (eigs.info() == CompInfo::Successful) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +template > +class GenEigsSolver : public GenEigsBase +{ +private: + using Index = Eigen::Index; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The matrix operation object that implements + /// the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseGenMatProd, or + /// define their own that implements all the public members + /// as in DenseGenMatProd. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// + GenEigsSolver(OpType& op, Index nev, Index ncv) : + GenEigsBase(op, IdentityBOp(), nev, ncv) + {} +}; + +} // namespace Spectra + +#endif // SPECTRA_GEN_EIGS_SOLVER_H diff --git a/thirdparty/include/Spectra/JDSymEigsBase.h b/thirdparty/include/Spectra/JDSymEigsBase.h new file mode 100644 index 0000000..dca7d08 --- /dev/null +++ b/thirdparty/include/Spectra/JDSymEigsBase.h @@ -0,0 +1,190 @@ +// Copyright (C) 2020 Netherlands eScience Center +// +// 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_JD_SYM_EIGS_BASE_H +#define SPECTRA_JD_SYM_EIGS_BASE_H + +#include +#include // std::vector +#include // std::abs, std::pow +#include // std::min +#include // std::invalid_argument +#include + +#include "Util/SelectionRule.h" +#include "Util/CompInfo.h" +#include "LinAlg/SearchSpace.h" +#include "LinAlg/RitzPairs.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This is the base class for symmetric JD eigen solvers, mainly for internal use. +/// It is kept here to provide the documentation for member functions of concrete eigen solvers +/// such as DavidsonSymEigsSolver. +/// +/// This class uses the CRTP method to call functions from the derived class. +/// +template +class JDSymEigsBase +{ +protected: + using Index = Eigen::Index; + using Scalar = typename OpType::Scalar; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + + const OpType& m_matrix_operator; // object to conduct matrix operation, + // e.g. matrix-vector product + Index niter_ = 0; + const Index m_number_eigenvalues; // number of eigenvalues requested + Index m_max_search_space_size; + Index m_initial_search_space_size; + Index m_correction_size; // how many correction vectors are added in each iteration + RitzPairs m_ritz_pairs; // Ritz eigen pair structure + SearchSpace m_search_space; // search space + +private: + CompInfo m_info = CompInfo::NotComputed; // status of the computation + + void check_argument() const + { + if (m_number_eigenvalues < 1 || m_number_eigenvalues > m_matrix_operator.cols() - 1) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix"); + } + + void initialize() + { + // TODO better input validation and checks + if (m_matrix_operator.cols() < m_max_search_space_size) + { + m_max_search_space_size = m_matrix_operator.cols(); + } + if (m_matrix_operator.cols() < m_initial_search_space_size + m_correction_size) + { + m_initial_search_space_size = m_matrix_operator.cols() / 3; + m_correction_size = m_matrix_operator.cols() / 3; + } + } + +public: + JDSymEigsBase(OpType& op, Index nev, Index nvec_init, Index nvec_max) : + m_matrix_operator(op), + m_number_eigenvalues(nev), + m_max_search_space_size(nvec_max < op.rows() ? nvec_max : 10 * m_number_eigenvalues), + m_initial_search_space_size(nvec_init < op.rows() ? nvec_init : 2 * m_number_eigenvalues), + m_correction_size(m_number_eigenvalues) + { + check_argument(); + initialize(); + } + + JDSymEigsBase(OpType& op, Index nev) : + JDSymEigsBase(op, nev, 2 * nev, 10 * nev) {} + + /// + /// Sets the Maxmium SearchspaceSize after which is deflated + /// + void set_max_search_space_size(Index max_search_space_size) + { + m_max_search_space_size = max_search_space_size; + } + /// + /// Sets how many correction vectors are added in each iteration + /// + void set_correction_size(Index correction_size) + { + m_correction_size = correction_size; + } + + /// + /// Sets the Initial SearchspaceSize for Ritz values + /// + void set_initial_search_space_size(Index initial_search_space_size) + { + m_initial_search_space_size = initial_search_space_size; + } + + /// + /// Virtual destructor + /// + virtual ~JDSymEigsBase() {} + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Returns the number of iterations used in the computation. + /// + Index num_iterations() const { return niter_; } + + Vector eigenvalues() const { return m_ritz_pairs.ritz_values().head(m_number_eigenvalues); } + + Matrix eigenvectors() const { return m_ritz_pairs.ritz_vectors().leftCols(m_number_eigenvalues); } + + Index compute(SortRule selection = SortRule::LargestMagn, Index maxit = 100, + Scalar tol = 100 * Eigen::NumTraits::dummy_precision()) + { + Derived& derived = static_cast(*this); + Matrix intial_space = derived.setup_initial_search_space(selection); + return compute_with_guess(intial_space, selection, maxit, tol); + } + + Index compute_with_guess(const Eigen::Ref& initial_space, + SortRule selection = SortRule::LargestMagn, + Index maxit = 100, + Scalar tol = 100 * Eigen::NumTraits::dummy_precision()) + + { + m_search_space.initialize_search_space(initial_space); + niter_ = 0; + for (niter_ = 0; niter_ < maxit; niter_++) + { + bool do_restart = (m_search_space.size() > m_max_search_space_size); + + if (do_restart) + { + m_search_space.restart(m_ritz_pairs, m_initial_search_space_size); + } + + m_search_space.update_operator_basis_product(m_matrix_operator); + + Eigen::ComputationInfo small_problem_info = m_ritz_pairs.compute_eigen_pairs(m_search_space); + if (small_problem_info != Eigen::ComputationInfo::Success) + { + m_info = CompInfo::NumericalIssue; + break; + } + m_ritz_pairs.sort(selection); + + bool converged = m_ritz_pairs.check_convergence(tol, m_number_eigenvalues); + if (converged) + { + m_info = CompInfo::Successful; + break; + } + else if (niter_ == maxit - 1) + { + m_info = CompInfo::NotConverging; + break; + } + Derived& derived = static_cast(*this); + Matrix corr_vect = derived.calculate_correction_vector(); + + m_search_space.extend_basis(corr_vect); + } + return (m_ritz_pairs.converged_eigenvalues()).template cast().head(m_number_eigenvalues).sum(); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_JD_SYM_EIGS_BASE_H diff --git a/thirdparty/include/Spectra/LinAlg/Arnoldi.h b/thirdparty/include/Spectra/LinAlg/Arnoldi.h new file mode 100644 index 0000000..80b8252 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/Arnoldi.h @@ -0,0 +1,315 @@ +// Copyright (C) 2018-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_ARNOLDI_H +#define SPECTRA_ARNOLDI_H + +#include +#include // std::sqrt +#include // std::move +#include // std::invalid_argument + +#include "../MatOp/internal/ArnoldiOp.h" +#include "../Util/TypeTraits.h" +#include "../Util/SimpleRandom.h" +#include "UpperHessenbergQR.h" +#include "DoubleShiftQR.h" + +namespace Spectra { + +// Arnoldi factorization A * V = V * H + f * e' +// A: n x n +// V: n x k +// H: k x k +// f: n x 1 +// e: [0, ..., 0, 1] +// V and H are allocated of dimension m, so the maximum value of k is m +template +class Arnoldi +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapVec = Eigen::Map; + using MapConstMat = Eigen::Map; + using MapConstVec = Eigen::Map; + +protected: + // A very small value, but 1.0 / m_near_0 does not overflow + // ~= 1e-307 for the "double" type + static constexpr Scalar m_near_0 = TypeTraits::min() * Scalar(10); + // The machine precision, ~= 1e-16 for the "double" type + static constexpr Scalar m_eps = TypeTraits::epsilon(); + + ArnoldiOpType m_op; // Operators for the Arnoldi factorization + const Index m_n; // dimension of A + const Index m_m; // maximum dimension of subspace V + Index m_k; // current dimension of subspace V + Matrix m_fac_V; // V matrix in the Arnoldi factorization + Matrix m_fac_H; // H matrix in the Arnoldi factorization + Vector m_fac_f; // residual in the Arnoldi factorization + Scalar m_beta; // ||f||, B-norm of f + + // Given orthonormal basis V (w.r.t. B), find a nonzero vector f such that V'Bf = 0 + // With rounding errors, we hope V'B(f/||f||) < eps + // Assume that f has been properly allocated + void expand_basis(MapConstMat& V, const Index seed, Vector& f, Scalar& fnorm, Index& op_counter) + { + using std::sqrt; + + Vector v(m_n), Vf(V.cols()); + for (Index iter = 0; iter < 5; iter++) + { + // Randomly generate a new vector and orthogonalize it against V + SimpleRandom rng(seed + 123 * iter); + // The first try forces f to be in the range of A + if (iter == 0) + { + rng.random_vec(v); + m_op.perform_op(v.data(), f.data()); + op_counter++; + } + else + { + rng.random_vec(f); + } + // f <- f - V * V'Bf, so that f is orthogonal to V in B-norm + m_op.trans_product(V, f, Vf); + f.noalias() -= V * Vf; + // fnorm <- ||f|| + fnorm = m_op.norm(f); + + // Compute V'Bf again + m_op.trans_product(V, f, Vf); + // Test whether V'B(f/||f||) < eps + Scalar ortho_err = Vf.cwiseAbs().maxCoeff(); + // If not, iteratively correct the residual + int count = 0; + while (count < 3 && ortho_err >= m_eps * fnorm) + { + // f <- f - V * Vf + f.noalias() -= V * Vf; + // beta <- ||f|| + fnorm = m_op.norm(f); + + m_op.trans_product(V, f, Vf); + ortho_err = Vf.cwiseAbs().maxCoeff(); + count++; + } + + // If the condition is satisfied, simply return + // Otherwise, go to the next iteration and try a new random vector + if (ortho_err < m_eps * fnorm) + return; + } + } + +public: + // Copy an ArnoldiOp + Arnoldi(const ArnoldiOpType& op, Index m) : + m_op(op), m_n(op.rows()), m_m(m), m_k(0) + {} + + // Move an ArnoldiOp + Arnoldi(ArnoldiOpType&& op, Index m) : + m_op(std::move(op)), m_n(op.rows()), m_m(m), m_k(0) + {} + + // Const-reference to internal structures + const Matrix& matrix_V() const { return m_fac_V; } + const Matrix& matrix_H() const { return m_fac_H; } + const Vector& vector_f() const { return m_fac_f; } + Scalar f_norm() const { return m_beta; } + Index subspace_dim() const { return m_k; } + + // Initialize with an operator and an initial vector + void init(MapConstVec& v0, Index& op_counter) + { + m_fac_V.resize(m_n, m_m); + m_fac_H.resize(m_m, m_m); + m_fac_f.resize(m_n); + m_fac_H.setZero(); + + // Verify the initial vector + const Scalar v0norm = m_op.norm(v0); + if (v0norm < m_near_0) + throw std::invalid_argument("initial residual vector cannot be zero"); + + // Points to the first column of V + MapVec v(m_fac_V.data(), m_n); + // Force v to be in the range of A, i.e., v = A * v0 + m_op.perform_op(v0.data(), v.data()); + op_counter++; + + // Normalize + const Scalar vnorm = m_op.norm(v); + v /= vnorm; + + // Compute H and f + Vector w(m_n); + m_op.perform_op(v.data(), w.data()); + op_counter++; + + m_fac_H(0, 0) = m_op.inner_product(v, w); + m_fac_f.noalias() = w - v * m_fac_H(0, 0); + + // In some cases f is zero in exact arithmetics, but due to rounding errors + // it may contain tiny fluctuations. When this happens, we force f to be zero + if (m_fac_f.cwiseAbs().maxCoeff() < m_eps) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + } + else + { + m_beta = m_op.norm(m_fac_f); + } + + // Indicate that this is a step-1 factorization + m_k = 1; + } + + // Arnoldi factorization starting from step-k + virtual void factorize_from(Index from_k, Index to_m, Index& op_counter) + { + using std::sqrt; + + if (to_m <= from_k) + return; + + if (from_k > m_k) + { + std::string msg = "Arnoldi: from_k (= " + std::to_string(from_k) + + ") is larger than the current subspace dimension (= " + std::to_string(m_k) + ")"; + throw std::invalid_argument(msg); + } + + const Scalar beta_thresh = m_eps * sqrt(Scalar(m_n)); + + // Pre-allocate vectors + Vector Vf(to_m); + Vector w(m_n); + + // Keep the upperleft k x k submatrix of H and set other elements to 0 + m_fac_H.rightCols(m_m - from_k).setZero(); + m_fac_H.block(from_k, 0, m_m - from_k, from_k).setZero(); + + for (Index i = from_k; i <= to_m - 1; i++) + { + bool restart = false; + // If beta = 0, then the next V is not full rank + // We need to generate a new residual vector that is orthogonal + // to the current V, which we call a restart + if (m_beta < m_near_0) + { + MapConstMat V(m_fac_V.data(), m_n, i); // The first i columns + expand_basis(V, 2 * i, m_fac_f, m_beta, op_counter); + restart = true; + } + + // v <- f / ||f|| + m_fac_V.col(i).noalias() = m_fac_f / m_beta; // The (i+1)-th column + + // Note that H[i+1, i] equals to the unrestarted beta + m_fac_H(i, i - 1) = restart ? Scalar(0) : m_beta; + + // w <- A * v, v = m_fac_V.col(i) + m_op.perform_op(&m_fac_V(0, i), w.data()); + op_counter++; + + const Index i1 = i + 1; + // First i+1 columns of V + MapConstMat Vs(m_fac_V.data(), m_n, i1); + // h = m_fac_H(0:i, i) + MapVec h(&m_fac_H(0, i), i1); + // h <- V'Bw + m_op.trans_product(Vs, w, h); + + // f <- w - V * h + m_fac_f.noalias() = w - Vs * h; + m_beta = m_op.norm(m_fac_f); + + if (m_beta > Scalar(0.717) * m_op.norm(h)) + continue; + + // f/||f|| is going to be the next column of V, so we need to test + // whether V'B(f/||f||) ~= 0 + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + Scalar ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + // If not, iteratively correct the residual + int count = 0; + while (count < 5 && ortho_err > m_eps * m_beta) + { + // There is an edge case: when beta=||f|| is close to zero, f mostly consists + // of noises of rounding errors, so the test [ortho_err < eps * beta] is very + // likely to fail. In particular, if beta=0, then the test is ensured to fail. + // Hence when this happens, we force f to be zero, and then restart in the + // next iteration. + if (m_beta < beta_thresh) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + break; + } + + // f <- f - V * Vf + m_fac_f.noalias() -= Vs * Vf.head(i1); + // h <- h + Vf + h.noalias() += Vf.head(i1); + // beta <- ||f|| + m_beta = m_op.norm(m_fac_f); + + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + count++; + } + } + + // Indicate that this is a step-m factorization + m_k = to_m; + } + + // Apply H -> Q'HQ, where Q is from a double shift QR decomposition + void compress_H(const DoubleShiftQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k -= 2; + } + + // Apply H -> Q'HQ, where Q is from an upper Hessenberg QR decomposition + void compress_H(const UpperHessenbergQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k--; + } + + // Apply V -> VQ and compute the new f. + // Should be called after compress_H(), since m_k is updated there. + // Only need to update the first k+1 columns of V + // The first (m - k + i) elements of the i-th column of Q are non-zero, + // and the rest are zero + void compress_V(const Matrix& Q) + { + Matrix Vs(m_n, m_k + 1); + for (Index i = 0; i < m_k; i++) + { + const Index nnz = m_m - m_k + i + 1; + MapConstVec q(&Q(0, i), nnz); + Vs.col(i).noalias() = m_fac_V.leftCols(nnz) * q; + } + Vs.col(m_k).noalias() = m_fac_V * Q.col(m_k); + m_fac_V.leftCols(m_k + 1).noalias() = Vs; + + Vector fk = m_fac_f * Q(m_m - 1, m_k - 1) + m_fac_V.col(m_k) * m_fac_H(m_k, m_k - 1); + m_fac_f.swap(fk); + m_beta = m_op.norm(m_fac_f); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_ARNOLDI_H diff --git a/thirdparty/include/Spectra/LinAlg/BKLDLT.h b/thirdparty/include/Spectra/LinAlg/BKLDLT.h new file mode 100644 index 0000000..80b398b --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/BKLDLT.h @@ -0,0 +1,537 @@ +// Copyright (C) 2019-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_BK_LDLT_H +#define SPECTRA_BK_LDLT_H + +#include +#include +#include + +#include "../Util/CompInfo.h" + +namespace Spectra { + +// Bunch-Kaufman LDLT decomposition +// References: +// 1. Bunch, J. R., & Kaufman, L. (1977). Some stable methods for calculating inertia and solving symmetric linear systems. +// Mathematics of computation, 31(137), 163-179. +// 2. Golub, G. H., & Van Loan, C. F. (2012). Matrix computations (Vol. 3). JHU press. Section 4.4. +// 3. Bunch-Parlett diagonal pivoting +// 4. Ashcraft, C., Grimes, R. G., & Lewis, J. G. (1998). Accurate symmetric indefinite linear equation solvers. +// SIAM Journal on Matrix Analysis and Applications, 20(2), 513-561. +template +class BKLDLT +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapVec = Eigen::Map; + using MapConstVec = Eigen::Map; + using IntVector = Eigen::Matrix; + using GenericVector = Eigen::Ref; + using ConstGenericVector = const Eigen::Ref; + + Index m_n; + Vector m_data; // storage for a lower-triangular matrix + std::vector m_colptr; // pointers to columns + IntVector m_perm; // [-2, -1, 3, 1, 4, 5]: 0 <-> 2, 1 <-> 1, 2 <-> 3, 3 <-> 1, 4 <-> 4, 5 <-> 5 + std::vector> m_permc; // compressed version of m_perm: [(0, 2), (2, 3), (3, 1)] + + bool m_computed; + CompInfo m_info; + + // Access to elements + // Pointer to the k-th column + Scalar* col_pointer(Index k) { return m_colptr[k]; } + // A[i, j] -> m_colptr[j][i - j], i >= j + Scalar& coeff(Index i, Index j) { return m_colptr[j][i - j]; } + const Scalar& coeff(Index i, Index j) const { return m_colptr[j][i - j]; } + // A[i, i] -> m_colptr[i][0] + Scalar& diag_coeff(Index i) { return m_colptr[i][0]; } + const Scalar& diag_coeff(Index i) const { return m_colptr[i][0]; } + + // Compute column pointers + void compute_pointer() + { + m_colptr.clear(); + m_colptr.reserve(m_n); + Scalar* head = m_data.data(); + + for (Index i = 0; i < m_n; i++) + { + m_colptr.push_back(head); + head += (m_n - i); + } + } + + // Copy mat - shift * I to m_data + template + void copy_data(const Eigen::MatrixBase& mat, int uplo, const Scalar& shift) + { + // If mat is an expression, first evaluate it into a temporary object + // This can be achieved by assigning mat to a const Eigen::Ref& + // If mat is a plain object, no temporary object is created + const Eigen::Ref& src(mat); + + // Efficient copying for column-major matrices with lower triangular part + if ((!Derived::PlainObject::IsRowMajor) && uplo == Eigen::Lower) + { + for (Index j = 0; j < m_n; j++) + { + const Scalar* begin = &src.coeffRef(j, j); + const Index len = m_n - j; + std::copy(begin, begin + len, col_pointer(j)); + diag_coeff(j) -= shift; + } + return; + } + + Scalar* dest = m_data.data(); + for (Index j = 0; j < m_n; j++) + { + for (Index i = j; i < m_n; i++, dest++) + { + if (uplo == Eigen::Lower) + *dest = src.coeff(i, j); + else + *dest = src.coeff(j, i); + } + diag_coeff(j) -= shift; + } + } + + // Compute compressed permutations + void compress_permutation() + { + for (Index i = 0; i < m_n; i++) + { + // Recover the permutation action + const Index perm = (m_perm[i] >= 0) ? (m_perm[i]) : (-m_perm[i] - 1); + if (perm != i) + m_permc.push_back(std::make_pair(i, perm)); + } + } + + // Working on the A[k:end, k:end] submatrix + // Exchange k <-> r + // Assume r >= k + void pivoting_1x1(Index k, Index r) + { + // No permutation + if (k == r) + { + m_perm[k] = r; + return; + } + + // A[k, k] <-> A[r, r] + std::swap(diag_coeff(k), diag_coeff(r)); + + // A[(r+1):end, k] <-> A[(r+1):end, r] + std::swap_ranges(&coeff(r + 1, k), col_pointer(k + 1), &coeff(r + 1, r)); + + // A[(k+1):(r-1), k] <-> A[r, (k+1):(r-1)] + Scalar* src = &coeff(k + 1, k); + for (Index j = k + 1; j < r; j++, src++) + { + std::swap(*src, coeff(r, j)); + } + + m_perm[k] = r; + } + + // Working on the A[k:end, k:end] submatrix + // Exchange [k+1, k] <-> [r, p] + // Assume p >= k, r >= k+1 + void pivoting_2x2(Index k, Index r, Index p) + { + pivoting_1x1(k, p); + pivoting_1x1(k + 1, r); + + // A[k+1, k] <-> A[r, k] + std::swap(coeff(k + 1, k), coeff(r, k)); + + // Use negative signs to indicate a 2x2 block + // Also minus one to distinguish a negative zero from a positive zero + m_perm[k] = -m_perm[k] - 1; + m_perm[k + 1] = -m_perm[k + 1] - 1; + } + + // A[r1, c1:c2] <-> A[r2, c1:c2] + // Assume r2 >= r1 > c2 >= c1 + void interchange_rows(Index r1, Index r2, Index c1, Index c2) + { + if (r1 == r2) + return; + + for (Index j = c1; j <= c2; j++) + { + std::swap(coeff(r1, j), coeff(r2, j)); + } + } + + // lambda = |A[r, k]| = max{|A[k+1, k]|, ..., |A[end, k]|} + // Largest (in magnitude) off-diagonal element in the first column of the current reduced matrix + // r is the row index + // Assume k < end + Scalar find_lambda(Index k, Index& r) + { + using std::abs; + + const Scalar* head = col_pointer(k); // => A[k, k] + const Scalar* end = col_pointer(k + 1); + // Start with r=k+1, lambda=A[k+1, k] + r = k + 1; + Scalar lambda = abs(head[1]); + // Scan remaining elements + for (const Scalar* ptr = head + 2; ptr < end; ptr++) + { + const Scalar abs_elem = abs(*ptr); + if (lambda < abs_elem) + { + lambda = abs_elem; + r = k + (ptr - head); + } + } + + return lambda; + } + + // sigma = |A[p, r]| = max {|A[k, r]|, ..., |A[end, r]|} \ {A[r, r]} + // Largest (in magnitude) off-diagonal element in the r-th column of the current reduced matrix + // p is the row index + // Assume k < r < end + Scalar find_sigma(Index k, Index r, Index& p) + { + using std::abs; + + // First search A[r+1, r], ..., A[end, r], which has the same task as find_lambda() + // If r == end, we skip this search + Scalar sigma = Scalar(-1); + if (r < m_n - 1) + sigma = find_lambda(r, p); + + // Then search A[k, r], ..., A[r-1, r], which maps to A[r, k], ..., A[r, r-1] + for (Index j = k; j < r; j++) + { + const Scalar abs_elem = abs(coeff(r, j)); + if (sigma < abs_elem) + { + sigma = abs_elem; + p = j; + } + } + + return sigma; + } + + // Generate permutations and apply to A + // Return true if the resulting pivoting is 1x1, and false if 2x2 + bool permutate_mat(Index k, const Scalar& alpha) + { + using std::abs; + + Index r = k, p = k; + const Scalar lambda = find_lambda(k, r); + + // If lambda=0, no need to interchange + if (lambda > Scalar(0)) + { + const Scalar abs_akk = abs(diag_coeff(k)); + // If |A[k, k]| >= alpha * lambda, no need to interchange + if (abs_akk < alpha * lambda) + { + const Scalar sigma = find_sigma(k, r, p); + + // If sigma * |A[k, k]| >= alpha * lambda^2, no need to interchange + if (sigma * abs_akk < alpha * lambda * lambda) + { + if (abs_akk >= alpha * sigma) + { + // Permutation on A + pivoting_1x1(k, r); + + // Permutation on L + interchange_rows(k, r, 0, k - 1); + return true; + } + else + { + // There are two versions of permutation here + // 1. A[k+1, k] <-> A[r, k] + // 2. A[k+1, k] <-> A[r, p], where p >= k and r >= k+1 + // + // Version 1 and 2 are used by Ref[1] and Ref[2], respectively + + // Version 1 implementation + p = k; + + // Version 2 implementation + // [r, p] and [p, r] are symmetric, but we need to make sure + // p >= k and r >= k+1, so it is safe to always make r > p + // One exception is when min{r,p} == k+1, in which case we make + // r = k+1, so that only one permutation needs to be performed + /* const Index rp_min = std::min(r, p); + const Index rp_max = std::max(r, p); + if(rp_min == k + 1) + { + r = rp_min; p = rp_max; + } else { + r = rp_max; p = rp_min; + } */ + + // Right now we use Version 1 since it reduces the overhead of interchange + + // Permutation on A + pivoting_2x2(k, r, p); + // Permutation on L + interchange_rows(k, p, 0, k - 1); + interchange_rows(k + 1, r, 0, k - 1); + return false; + } + } + } + } + + return true; + } + + // E = [e11, e12] + // [e21, e22] + // Overwrite E with inv(E) + void inverse_inplace_2x2(Scalar& e11, Scalar& e21, Scalar& e22) const + { + // inv(E) = [d11, d12], d11 = e22/delta, d21 = -e21/delta, d22 = e11/delta + // [d21, d22] + const Scalar delta = e11 * e22 - e21 * e21; + std::swap(e11, e22); + e11 /= delta; + e22 /= delta; + e21 = -e21 / delta; + } + + // Return value is the status, CompInfo::Successful/NumericalIssue + CompInfo gaussian_elimination_1x1(Index k) + { + // D = 1 / A[k, k] + const Scalar akk = diag_coeff(k); + // Return CompInfo::NumericalIssue if not invertible + if (akk == Scalar(0)) + return CompInfo::NumericalIssue; + + diag_coeff(k) = Scalar(1) / akk; + + // B -= l * l' / A[k, k], B := A[(k+1):end, (k+1):end], l := L[(k+1):end, k] + Scalar* lptr = col_pointer(k) + 1; + const Index ldim = m_n - k - 1; + MapVec l(lptr, ldim); + for (Index j = 0; j < ldim; j++) + { + MapVec(col_pointer(j + k + 1), ldim - j).noalias() -= (lptr[j] / akk) * l.tail(ldim - j); + } + + // l /= A[k, k] + l /= akk; + + return CompInfo::Successful; + } + + // Return value is the status, CompInfo::Successful/NumericalIssue + CompInfo gaussian_elimination_2x2(Index k) + { + // D = inv(E) + Scalar& e11 = diag_coeff(k); + Scalar& e21 = coeff(k + 1, k); + Scalar& e22 = diag_coeff(k + 1); + // Return CompInfo::NumericalIssue if not invertible + if (e11 * e22 - e21 * e21 == Scalar(0)) + return CompInfo::NumericalIssue; + + inverse_inplace_2x2(e11, e21, e22); + + // X = l * inv(E), l := L[(k+2):end, k:(k+1)] + Scalar* l1ptr = &coeff(k + 2, k); + Scalar* l2ptr = &coeff(k + 2, k + 1); + const Index ldim = m_n - k - 2; + MapVec l1(l1ptr, ldim), l2(l2ptr, ldim); + + Eigen::Matrix X(ldim, 2); + X.col(0).noalias() = l1 * e11 + l2 * e21; + X.col(1).noalias() = l1 * e21 + l2 * e22; + + // B -= l * inv(E) * l' = X * l', B = A[(k+2):end, (k+2):end] + for (Index j = 0; j < ldim; j++) + { + MapVec(col_pointer(j + k + 2), ldim - j).noalias() -= (X.col(0).tail(ldim - j) * l1ptr[j] + X.col(1).tail(ldim - j) * l2ptr[j]); + } + + // l = X + l1.noalias() = X.col(0); + l2.noalias() = X.col(1); + + return CompInfo::Successful; + } + +public: + BKLDLT() : + m_n(0), m_computed(false), m_info(CompInfo::NotComputed) + {} + + // Factorize mat - shift * I + template + BKLDLT(const Eigen::MatrixBase& mat, int uplo = Eigen::Lower, const Scalar& shift = Scalar(0)) : + m_n(mat.rows()), m_computed(false), m_info(CompInfo::NotComputed) + { + compute(mat, uplo, shift); + } + + template + void compute(const Eigen::MatrixBase& mat, int uplo = Eigen::Lower, const Scalar& shift = Scalar(0)) + { + using std::abs; + + m_n = mat.rows(); + if (m_n != mat.cols()) + throw std::invalid_argument("BKLDLT: matrix must be square"); + + m_perm.setLinSpaced(m_n, 0, m_n - 1); + m_permc.clear(); + + // Copy data + m_data.resize((m_n * (m_n + 1)) / 2); + compute_pointer(); + copy_data(mat, uplo, shift); + + const Scalar alpha = (1.0 + std::sqrt(17.0)) / 8.0; + Index k = 0; + for (k = 0; k < m_n - 1; k++) + { + // 1. Interchange rows and columns of A, and save the result to m_perm + bool is_1x1 = permutate_mat(k, alpha); + + // 2. Gaussian elimination + if (is_1x1) + { + m_info = gaussian_elimination_1x1(k); + } + else + { + m_info = gaussian_elimination_2x2(k); + k++; + } + + // 3. Check status + if (m_info != CompInfo::Successful) + break; + } + // Invert the last 1x1 block if it exists + if (k == m_n - 1) + { + const Scalar akk = diag_coeff(k); + if (akk == Scalar(0)) + m_info = CompInfo::NumericalIssue; + + diag_coeff(k) = Scalar(1) / diag_coeff(k); + } + + compress_permutation(); + + m_computed = true; + } + + // Solve Ax=b + void solve_inplace(GenericVector b) const + { + if (!m_computed) + throw std::logic_error("BKLDLT: need to call compute() first"); + + // PAP' = LDL' + // 1. b -> Pb + Scalar* x = b.data(); + MapVec res(x, m_n); + Index npermc = m_permc.size(); + for (Index i = 0; i < npermc; i++) + { + std::swap(x[m_permc[i].first], x[m_permc[i].second]); + } + + // 2. Lz = Pb + // If m_perm[end] < 0, then end with m_n - 3, otherwise end with m_n - 2 + const Index end = (m_perm[m_n - 1] < 0) ? (m_n - 3) : (m_n - 2); + for (Index i = 0; i <= end; i++) + { + const Index b1size = m_n - i - 1; + const Index b2size = b1size - 1; + if (m_perm[i] >= 0) + { + MapConstVec l(&coeff(i + 1, i), b1size); + res.segment(i + 1, b1size).noalias() -= l * x[i]; + } + else + { + MapConstVec l1(&coeff(i + 2, i), b2size); + MapConstVec l2(&coeff(i + 2, i + 1), b2size); + res.segment(i + 2, b2size).noalias() -= (l1 * x[i] + l2 * x[i + 1]); + i++; + } + } + + // 3. Dw = z + for (Index i = 0; i < m_n; i++) + { + const Scalar e11 = diag_coeff(i); + if (m_perm[i] >= 0) + { + x[i] *= e11; + } + else + { + const Scalar e21 = coeff(i + 1, i), e22 = diag_coeff(i + 1); + const Scalar wi = x[i] * e11 + x[i + 1] * e21; + x[i + 1] = x[i] * e21 + x[i + 1] * e22; + x[i] = wi; + i++; + } + } + + // 4. L'y = w + // If m_perm[end] < 0, then start with m_n - 3, otherwise start with m_n - 2 + Index i = (m_perm[m_n - 1] < 0) ? (m_n - 3) : (m_n - 2); + for (; i >= 0; i--) + { + const Index ldim = m_n - i - 1; + MapConstVec l(&coeff(i + 1, i), ldim); + x[i] -= res.segment(i + 1, ldim).dot(l); + + if (m_perm[i] < 0) + { + MapConstVec l2(&coeff(i + 1, i - 1), ldim); + x[i - 1] -= res.segment(i + 1, ldim).dot(l2); + i--; + } + } + + // 5. x = P'y + for (Index i = npermc - 1; i >= 0; i--) + { + std::swap(x[m_permc[i].first], x[m_permc[i].second]); + } + } + + Vector solve(ConstGenericVector& b) const + { + Vector res = b; + solve_inplace(res); + return res; + } + + CompInfo info() const { return m_info; } +}; + +} // namespace Spectra + +#endif // SPECTRA_BK_LDLT_H diff --git a/thirdparty/include/Spectra/LinAlg/DoubleShiftQR.h b/thirdparty/include/Spectra/LinAlg/DoubleShiftQR.h new file mode 100644 index 0000000..02eeacb --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/DoubleShiftQR.h @@ -0,0 +1,440 @@ +// Copyright (C) 2016-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_DOUBLE_SHIFT_QR_H +#define SPECTRA_DOUBLE_SHIFT_QR_H + +#include +#include // std::vector +#include // std::min, std::fill, std::copy +#include // std::swap +#include // std::abs, std::sqrt, std::pow +#include // std::invalid_argument, std::logic_error + +#include "../Util/TypeTraits.h" + +namespace Spectra { + +template +class DoubleShiftQR +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Matrix3X = Eigen::Matrix; + using Vector = Eigen::Matrix; + using IntArray = Eigen::Array; + + using GenericMatrix = Eigen::Ref; + using ConstGenericMatrix = const Eigen::Ref; + + // A very small value, but 1.0 / m_near_0 does not overflow + // ~= 1e-307 for the "double" type + static constexpr Scalar m_near_0 = TypeTraits::min() * Scalar(10); + // The machine precision, ~= 1e-16 for the "double" type + static constexpr Scalar m_eps = TypeTraits::epsilon(); + + Index m_n; // Dimension of the matrix + Matrix m_mat_H; // A copy of the matrix to be factorized + Scalar m_shift_s; // Shift constant + Scalar m_shift_t; // Shift constant + Matrix3X m_ref_u; // Householder reflectors + IntArray m_ref_nr; // How many rows does each reflector affects + // 3 - A general reflector + // 2 - A Givens rotation + // 1 - An identity transformation + bool m_computed; // Whether matrix has been factorized + + // Compute sqrt(x1^2 + x2^2 + x3^2) wit high precision + static Scalar stable_norm3(Scalar x1, Scalar x2, Scalar x3) + { + using std::abs; + using std::sqrt; + + x1 = abs(x1); + x2 = abs(x2); + x3 = abs(x3); + // Make x1 >= {x2, x3} + if (x1 < x2) + std::swap(x1, x2); + if (x1 < x3) + std::swap(x1, x3); + // If x1 is too small, return 0 + if (x1 < m_near_0) + return Scalar(0); + + const Scalar r2 = x2 / x1, r3 = x3 / x1; + // We choose a cutoff such that cutoff^4 < eps + // If max(r2, r3) > cutoff, use the standard way; otherwise use Taylor series expansion + // to avoid an explicit sqrt() call that may lose precision + const Scalar cutoff = Scalar(0.1) * pow(m_eps, Scalar(0.25)); + Scalar r = r2 * r2 + r3 * r3; + r = (r2 >= cutoff || r3 >= cutoff) ? + sqrt(Scalar(1) + r) : + (Scalar(1) + r * (Scalar(0.5) - Scalar(0.125) * r)); // sqrt(1 + t) ~= 1 + t/2 - t^2/8 + return x1 * r; + } + + // x[i] <- x[i] / r, r = sqrt(x1^2 + x2^2 + x3^2) + // Assume |x1| >= {|x2|, |x3|}, x1 != 0 + static void stable_scaling(Scalar& x1, Scalar& x2, Scalar& x3) + { + using std::abs; + using std::pow; + using std::sqrt; + + const Scalar x1sign = (x1 > Scalar(0)) ? Scalar(1) : Scalar(-1); + x1 = abs(x1); + // Use the same method as in stable_norm3() + const Scalar r2 = x2 / x1, r3 = x3 / x1; + const Scalar cutoff = Scalar(0.1) * pow(m_eps, Scalar(0.25)); + Scalar r = r2 * r2 + r3 * r3; + // r = 1/sqrt(1 + r2^2 + r3^2) + r = (abs(r2) >= cutoff || abs(r3) >= cutoff) ? + Scalar(1) / sqrt(Scalar(1) + r) : + (Scalar(1) - r * (Scalar(0.5) - Scalar(0.375) * r)); // 1/sqrt(1 + t) ~= 1 - t * (1/2 - (3/8) * t) + x1 = x1sign * r; + x2 = r2 * r; + x3 = r3 * r; + } + + void compute_reflector(const Scalar& x1, const Scalar& x2, const Scalar& x3, Index ind) + { + using std::abs; + + Scalar* u = &m_ref_u.coeffRef(0, ind); + unsigned char* nr = m_ref_nr.data(); + const Scalar x2m = abs(x2), x3m = abs(x3); + // If both x2 and x3 are zero, nr is 1, and we early exit + if (x2m < m_near_0 && x3m < m_near_0) + { + nr[ind] = 1; + return; + } + + // In general case the reflector affects 3 rows + // If x3 is zero, decrease nr by 1 + nr[ind] = (x3m < m_near_0) ? 2 : 3; + const Scalar x_norm = (x3m < m_near_0) ? Eigen::numext::hypot(x1, x2) : stable_norm3(x1, x2, x3); + + // x1' = x1 - rho * ||x|| + // rho = -sign(x1), if x1 == 0, we choose rho = 1 + const Scalar rho = (x1 <= Scalar(0)) - (x1 > Scalar(0)); + const Scalar x1_new = x1 - rho * x_norm, x1m = abs(x1_new); + // Copy x to u + u[0] = x1_new; + u[1] = x2; + u[2] = x3; + if (x1m >= x2m && x1m >= x3m) + { + stable_scaling(u[0], u[1], u[2]); + } + else if (x2m >= x1m && x2m >= x3m) + { + stable_scaling(u[1], u[0], u[2]); + } + else + { + stable_scaling(u[2], u[0], u[1]); + } + } + + void compute_reflector(const Scalar* x, Index ind) + { + compute_reflector(x[0], x[1], x[2], ind); + } + + // Update the block X = H(il:iu, il:iu) + void update_block(Index il, Index iu) + { + // Block size + const Index bsize = iu - il + 1; + + // If block size == 1, there is no need to apply reflectors + if (bsize == 1) + { + m_ref_nr.coeffRef(il) = 1; + return; + } + + const Scalar x00 = m_mat_H.coeff(il, il), + x01 = m_mat_H.coeff(il, il + 1), + x10 = m_mat_H.coeff(il + 1, il), + x11 = m_mat_H.coeff(il + 1, il + 1); + // m00 = x00 * (x00 - s) + x01 * x10 + t + const Scalar m00 = x00 * (x00 - m_shift_s) + x01 * x10 + m_shift_t; + // m10 = x10 * (x00 + x11 - s) + const Scalar m10 = x10 * (x00 + x11 - m_shift_s); + + // For block size == 2, do a Givens rotation on M = X * X - s * X + t * I + if (bsize == 2) + { + // This causes nr=2 + compute_reflector(m00, m10, 0, il); + // Apply the reflector to X + apply_PX(m_mat_H.block(il, il, 2, m_n - il), m_n, il); + apply_XP(m_mat_H.block(0, il, il + 2, 2), m_n, il); + + m_ref_nr.coeffRef(il + 1) = 1; + return; + } + + // For block size >=3, use the regular strategy + // m20 = x21 * x10 + const Scalar m20 = m_mat_H.coeff(il + 2, il + 1) * m_mat_H.coeff(il + 1, il); + compute_reflector(m00, m10, m20, il); + + // Apply the first reflector + apply_PX(m_mat_H.block(il, il, 3, m_n - il), m_n, il); + apply_XP(m_mat_H.block(0, il, il + (std::min)(bsize, Index(4)), 3), m_n, il); + + // Calculate the following reflectors + // If entering this loop, block size is at least 4. + for (Index i = 1; i < bsize - 2; i++) + { + compute_reflector(&m_mat_H.coeffRef(il + i, il + i - 1), il + i); + // Apply the reflector to X + apply_PX(m_mat_H.block(il + i, il + i - 1, 3, m_n - il - i + 1), m_n, il + i); + apply_XP(m_mat_H.block(0, il + i, il + (std::min)(bsize, Index(i + 4)), 3), m_n, il + i); + } + + // The last reflector + // This causes nr=2 + compute_reflector(m_mat_H.coeff(iu - 1, iu - 2), m_mat_H.coeff(iu, iu - 2), 0, iu - 1); + // Apply the reflector to X + apply_PX(m_mat_H.block(iu - 1, iu - 2, 2, m_n - iu + 2), m_n, iu - 1); + apply_XP(m_mat_H.block(0, iu - 1, il + bsize, 2), m_n, iu - 1); + + m_ref_nr.coeffRef(iu) = 1; + } + + // P = I - 2 * u * u' = P' + // PX = X - 2 * u * (u'X) + void apply_PX(GenericMatrix X, Index stride, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if (nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), u1 = m_ref_u.coeff(1, u_ind); + const Scalar u0_2 = Scalar(2) * u0, u1_2 = Scalar(2) * u1; + + const Index nrow = X.rows(); + const Index ncol = X.cols(); + + Scalar* xptr = X.data(); + if (nr == 2 || nrow == 2) + { + for (Index i = 0; i < ncol; i++, xptr += stride) + { + const Scalar tmp = u0_2 * xptr[0] + u1_2 * xptr[1]; + xptr[0] -= tmp * u0; + xptr[1] -= tmp * u1; + } + } + else + { + const Scalar u2 = m_ref_u.coeff(2, u_ind); + const Scalar u2_2 = Scalar(2) * u2; + for (Index i = 0; i < ncol; i++, xptr += stride) + { + const Scalar tmp = u0_2 * xptr[0] + u1_2 * xptr[1] + u2_2 * xptr[2]; + xptr[0] -= tmp * u0; + xptr[1] -= tmp * u1; + xptr[2] -= tmp * u2; + } + } + } + + // x is a pointer to a vector + // Px = x - 2 * dot(x, u) * u + void apply_PX(Scalar* x, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if (nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), + u1 = m_ref_u.coeff(1, u_ind), + u2 = m_ref_u.coeff(2, u_ind); + + // When the reflector only contains two elements, u2 has been set to zero + const bool nr_is_2 = (nr == 2); + const Scalar dot2 = Scalar(2) * (x[0] * u0 + x[1] * u1 + (nr_is_2 ? 0 : (x[2] * u2))); + x[0] -= dot2 * u0; + x[1] -= dot2 * u1; + if (!nr_is_2) + x[2] -= dot2 * u2; + } + + // XP = X - 2 * (X * u) * u' + void apply_XP(GenericMatrix X, Index stride, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if (nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), u1 = m_ref_u.coeff(1, u_ind); + const Scalar u0_2 = Scalar(2) * u0, u1_2 = Scalar(2) * u1; + + const int nrow = X.rows(); + const int ncol = X.cols(); + Scalar *X0 = X.data(), *X1 = X0 + stride; // X0 => X.col(0), X1 => X.col(1) + + if (nr == 2 || ncol == 2) + { + // tmp = 2 * u0 * X0 + 2 * u1 * X1 + // X0 => X0 - u0 * tmp + // X1 => X1 - u1 * tmp + for (Index i = 0; i < nrow; i++) + { + const Scalar tmp = u0_2 * X0[i] + u1_2 * X1[i]; + X0[i] -= tmp * u0; + X1[i] -= tmp * u1; + } + } + else + { + Scalar* X2 = X1 + stride; // X2 => X.col(2) + const Scalar u2 = m_ref_u.coeff(2, u_ind); + const Scalar u2_2 = Scalar(2) * u2; + for (Index i = 0; i < nrow; i++) + { + const Scalar tmp = u0_2 * X0[i] + u1_2 * X1[i] + u2_2 * X2[i]; + X0[i] -= tmp * u0; + X1[i] -= tmp * u1; + X2[i] -= tmp * u2; + } + } + } + +public: + DoubleShiftQR(Index size) : + m_n(size), + m_computed(false) + {} + + DoubleShiftQR(ConstGenericMatrix& mat, const Scalar& s, const Scalar& t) : + m_n(mat.rows()), + m_mat_H(m_n, m_n), + m_shift_s(s), + m_shift_t(t), + m_ref_u(3, m_n), + m_ref_nr(m_n), + m_computed(false) + { + compute(mat, s, t); + } + + void compute(ConstGenericMatrix& mat, const Scalar& s, const Scalar& t) + { + using std::abs; + + m_n = mat.rows(); + if (m_n != mat.cols()) + throw std::invalid_argument("DoubleShiftQR: matrix must be square"); + + m_mat_H.resize(m_n, m_n); + m_shift_s = s; + m_shift_t = t; + m_ref_u.resize(3, m_n); + m_ref_nr.resize(m_n); + + // Make a copy of mat + m_mat_H.noalias() = mat; + + // Obtain the indices of zero elements in the subdiagonal, + // so that H can be divided into several blocks + const Scalar eps_abs = m_near_0 * (m_n / m_eps); + constexpr Scalar eps_rel = m_eps; + std::vector zero_ind; + zero_ind.reserve(m_n - 1); + zero_ind.push_back(0); + Scalar* Hii = m_mat_H.data(); + for (Index i = 0; i < m_n - 1; i++, Hii += (m_n + 1)) + { + // Hii[0] => m_mat_H(i, i) + // Hii[1] => m_mat_H(i + 1, i) + // Hii[m_n + 1] => m_mat_H(i + 1, i + 1) + const Scalar h = abs(Hii[1]); + // Deflate small sub-diagonal elements + const Scalar diag = abs(Hii[0]) + abs(Hii[m_n + 1]); + if (h <= eps_abs || h <= eps_rel * diag) + { + Hii[1] = 0; + zero_ind.push_back(i + 1); + } + // Make sure m_mat_H is upper Hessenberg + // Zero the elements below m_mat_H(i + 1, i) + std::fill(Hii + 2, Hii + m_n - i, Scalar(0)); + } + zero_ind.push_back(m_n); + + const Index len = zero_ind.size() - 1; + for (Index i = 0; i < len; i++) + { + const Index start = zero_ind[i]; + const Index end = zero_ind[i + 1] - 1; + // Compute refelctors and update each block + update_block(start, end); + } + + // Deflation on the computed result + Hii = m_mat_H.data(); + for (Index i = 0; i < m_n - 1; i++, Hii += (m_n + 1)) + { + const Scalar h = abs(Hii[1]); + const Scalar diag = abs(Hii[0]) + abs(Hii[m_n + 1]); + if (h <= eps_abs || h <= eps_rel * diag) + Hii[1] = 0; + } + + m_computed = true; + } + + void matrix_QtHQ(Matrix& dest) const + { + if (!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + dest.noalias() = m_mat_H; + } + + // Q = P0 * P1 * ... + // Q'y = P_{n-2} * ... * P1 * P0 * y + void apply_QtY(Vector& y) const + { + if (!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + Scalar* y_ptr = y.data(); + const Index n1 = m_n - 1; + for (Index i = 0; i < n1; i++, y_ptr++) + { + apply_PX(y_ptr, i); + } + } + + // Q = P0 * P1 * ... + // YQ = Y * P0 * P1 * ... + void apply_YQ(GenericMatrix Y) const + { + if (!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + const Index nrow = Y.rows(); + const Index n2 = m_n - 2; + for (Index i = 0; i < n2; i++) + { + apply_XP(Y.block(0, i, nrow, 3), nrow, i); + } + apply_XP(Y.block(0, n2, nrow, 2), nrow, n2); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DOUBLE_SHIFT_QR_H diff --git a/thirdparty/include/Spectra/LinAlg/Lanczos.h b/thirdparty/include/Spectra/LinAlg/Lanczos.h new file mode 100644 index 0000000..23eecd4 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/Lanczos.h @@ -0,0 +1,171 @@ +// Copyright (C) 2018-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_LANCZOS_H +#define SPECTRA_LANCZOS_H + +#include +#include // std::sqrt +#include // std::forward +#include // std::invalid_argument + +#include "Arnoldi.h" + +namespace Spectra { + +// Lanczos factorization A * V = V * H + f * e' +// A: n x n +// V: n x k +// H: k x k +// f: n x 1 +// e: [0, ..., 0, 1] +// V and H are allocated of dimension m, so the maximum value of k is m +template +class Lanczos : public Arnoldi +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapMat = Eigen::Map; + using MapVec = Eigen::Map; + using MapConstMat = Eigen::Map; + + using Arnoldi::m_op; + using Arnoldi::m_n; + using Arnoldi::m_m; + using Arnoldi::m_k; + using Arnoldi::m_fac_V; + using Arnoldi::m_fac_H; + using Arnoldi::m_fac_f; + using Arnoldi::m_beta; + using Arnoldi::m_near_0; + using Arnoldi::m_eps; + +public: + // Forward parameter `op` to the constructor of Arnoldi + template + Lanczos(T&& op, Index m) : + Arnoldi(std::forward(op), m) + {} + + // Lanczos factorization starting from step-k + void factorize_from(Index from_k, Index to_m, Index& op_counter) override + { + using std::sqrt; + + if (to_m <= from_k) + return; + + if (from_k > m_k) + { + std::string msg = "Lanczos: from_k (= " + std::to_string(from_k) + + ") is larger than the current subspace dimension (= " + std::to_string(m_k) + ")"; + throw std::invalid_argument(msg); + } + + const Scalar beta_thresh = m_eps * sqrt(Scalar(m_n)); + + // Pre-allocate vectors + Vector Vf(to_m); + Vector w(m_n); + + // Keep the upperleft k x k submatrix of H and set other elements to 0 + m_fac_H.rightCols(m_m - from_k).setZero(); + m_fac_H.block(from_k, 0, m_m - from_k, from_k).setZero(); + + for (Index i = from_k; i <= to_m - 1; i++) + { + bool restart = false; + // If beta = 0, then the next V is not full rank + // We need to generate a new residual vector that is orthogonal + // to the current V, which we call a restart + if (m_beta < m_near_0) + { + MapConstMat V(m_fac_V.data(), m_n, i); // The first i columns + this->expand_basis(V, 2 * i, m_fac_f, m_beta, op_counter); + restart = true; + } + + // v <- f / ||f|| + MapVec v(&m_fac_V(0, i), m_n); // The (i+1)-th column + v.noalias() = m_fac_f / m_beta; + + // Note that H[i+1, i] equals to the unrestarted beta + m_fac_H(i, i - 1) = restart ? Scalar(0) : m_beta; + m_fac_H(i - 1, i) = m_fac_H(i, i - 1); // Due to symmetry + + // w <- A * v + m_op.perform_op(v.data(), w.data()); + op_counter++; + + // f <- w - V * V'Bw = w - H[i+1, i] * V{i} - H[i+1, i+1] * V{i+1} + // If restarting, we know that H[i+1, i] = 0 + // First do w <- w - H[i+1, i] * V{i}, see the discussions in Section 2.3 of + // Cullum and Willoughby (2002). Lanczos Algorithms for Large Symmetric Eigenvalue Computations: Vol. 1 + if (!restart) + w.noalias() -= m_fac_H(i, i - 1) * m_fac_V.col(i - 1); + + // H[i+1, i+1] = = v'Bw + m_fac_H(i, i) = m_op.inner_product(v, w); + + // f <- w - H[i+1, i+1] * V{i+1} + m_fac_f.noalias() = w - m_fac_H(i, i) * v; + m_beta = m_op.norm(m_fac_f); + + // f/||f|| is going to be the next column of V, so we need to test + // whether V'B(f/||f||) ~= 0 + const Index i1 = i + 1; + MapMat Vs(m_fac_V.data(), m_n, i1); // The first (i+1) columns + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + Scalar ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + // If not, iteratively correct the residual + int count = 0; + while (count < 5 && ortho_err > m_eps * m_beta) + { + // There is an edge case: when beta=||f|| is close to zero, f mostly consists + // of noises of rounding errors, so the test [ortho_err < eps * beta] is very + // likely to fail. In particular, if beta=0, then the test is ensured to fail. + // Hence when this happens, we force f to be zero, and then restart in the + // next iteration. + if (m_beta < beta_thresh) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + break; + } + + // f <- f - V * Vf + m_fac_f.noalias() -= Vs * Vf.head(i1); + // h <- h + Vf + m_fac_H(i - 1, i) += Vf[i - 1]; + m_fac_H(i, i - 1) = m_fac_H(i - 1, i); + m_fac_H(i, i) += Vf[i]; + // beta <- ||f|| + m_beta = m_op.norm(m_fac_f); + + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + count++; + } + } + + // Indicate that this is a step-m factorization + m_k = to_m; + } + + // Apply H -> Q'HQ, where Q is from a tridiagonal QR decomposition + // Function overloading here, not overriding + void compress_H(const TridiagQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k--; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_LANCZOS_H diff --git a/thirdparty/include/Spectra/LinAlg/Orthogonalization.h b/thirdparty/include/Spectra/LinAlg/Orthogonalization.h new file mode 100644 index 0000000..22c9f45 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/Orthogonalization.h @@ -0,0 +1,141 @@ +// Copyright (C) 2020 Netherlands eScience Center +// +// 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_ORTHOGONALIZATION_H +#define SPECTRA_ORTHOGONALIZATION_H + +#include +#include + +namespace Spectra { + +/// Check if the number of columns to skip is +/// larger than 0 but smaller than the total number +/// of columns of the matrix +/// \param in_output Matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void assert_left_cols_to_skip(Matrix& in_output, Eigen::Index left_cols_to_skip) +{ + assert(in_output.cols() > left_cols_to_skip && "left_cols_to_skip is larger than columns of matrix"); + assert(left_cols_to_skip >= 0 && "left_cols_to_skip is negative"); +} + +/// If the the number of columns to skip is null, +/// normalize the first column and set left_cols_to_skip=1 +/// \param in_output Matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +/// \return Actual number of left columns to skip +template +Eigen::Index treat_first_col(Matrix& in_output, Eigen::Index left_cols_to_skip) +{ + if (left_cols_to_skip == 0) + { + in_output.col(0).normalize(); + left_cols_to_skip = 1; + } + return left_cols_to_skip; +} + +/// Orthogonalize the in_output matrix using a QR decomposition +/// \param in_output Matrix to be orthogonalized +template +void QR_orthogonalisation(Matrix& in_output) +{ + using InternalMatrix = Eigen::Matrix; + Eigen::Index nrows = in_output.rows(); + Eigen::Index ncols = in_output.cols(); + ncols = (std::min)(nrows, ncols); + InternalMatrix I = InternalMatrix::Identity(nrows, ncols); + Eigen::HouseholderQR qr(in_output); + in_output.leftCols(ncols).noalias() = qr.householderQ() * I; +} + +/// Orthogonalize the in_output matrix using a modified Gram Schmidt process +/// \param in_output matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void MGS_orthogonalisation(Matrix& in_output, Eigen::Index left_cols_to_skip = 0) +{ + assert_left_cols_to_skip(in_output, left_cols_to_skip); + left_cols_to_skip = treat_first_col(in_output, left_cols_to_skip); + + for (Eigen::Index k = left_cols_to_skip; k < in_output.cols(); ++k) + { + for (Eigen::Index j = 0; j < k; j++) + { + in_output.col(k) -= in_output.col(j).dot(in_output.col(k)) * in_output.col(j); + } + in_output.col(k).normalize(); + } +} + +/// Orthogonalize the in_output matrix using a Gram Schmidt process +/// \param in_output matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void GS_orthogonalisation(Matrix& in_output, Eigen::Index left_cols_to_skip = 0) +{ + assert_left_cols_to_skip(in_output, left_cols_to_skip); + left_cols_to_skip = treat_first_col(in_output, left_cols_to_skip); + + for (Eigen::Index j = left_cols_to_skip; j < in_output.cols(); ++j) + { + in_output.col(j) -= in_output.leftCols(j) * (in_output.leftCols(j).transpose() * in_output.col(j)); + in_output.col(j).normalize(); + } +} + +/// Orthogonalize the subspace spanned by right columns of in_output +/// against the subspace spanned by left columns +/// It assumes that the left columns are already orthogonal and normalized, +/// and it does not orthogonalize the left columns against each other +/// \param in_output Matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void subspace_orthogonalisation(Matrix& in_output, Eigen::Index left_cols_to_skip) +{ + assert_left_cols_to_skip(in_output, left_cols_to_skip); + if (left_cols_to_skip == 0) + { + return; + } + + Eigen::Index right_cols_to_ortho = in_output.cols() - left_cols_to_skip; + in_output.rightCols(right_cols_to_ortho) -= in_output.leftCols(left_cols_to_skip) * + (in_output.leftCols(left_cols_to_skip).transpose() * in_output.rightCols(right_cols_to_ortho)); +} + +/// Orthogonalize the in_output matrix using a Jens process +/// The subspace spanned by right columns are first orthogonalized +/// agains the left columns, and then a QR decomposition is applied on the right columns +/// to make them orthogonalized agains each other +/// \param in_output Matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void JensWehner_orthogonalisation(Matrix& in_output, Eigen::Index left_cols_to_skip = 0) +{ + assert_left_cols_to_skip(in_output, left_cols_to_skip); + + Eigen::Index right_cols_to_ortho = in_output.cols() - left_cols_to_skip; + subspace_orthogonalisation(in_output, left_cols_to_skip); + Eigen::Ref right_cols = in_output.rightCols(right_cols_to_ortho); + QR_orthogonalisation(right_cols); +} + +/// Orthogonalize the in_output matrix using a twice-is-enough Jens process +/// \param in_output Matrix to be orthogonalized +/// \param left_cols_to_skip Number of left columns to be left untouched +template +void twice_is_enough_orthogonalisation(Matrix& in_output, Eigen::Index left_cols_to_skip = 0) +{ + JensWehner_orthogonalisation(in_output, left_cols_to_skip); + JensWehner_orthogonalisation(in_output, left_cols_to_skip); +} + +} // namespace Spectra + +#endif // SPECTRA_ORTHOGONALIZATION_H diff --git a/thirdparty/include/Spectra/LinAlg/RitzPairs.h b/thirdparty/include/Spectra/LinAlg/RitzPairs.h new file mode 100644 index 0000000..09bda61 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/RitzPairs.h @@ -0,0 +1,130 @@ +// Copyright (C) 2020 Netherlands eScience Center +// +// 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_RITZ_PAIRS_H +#define SPECTRA_RITZ_PAIRS_H + +#include +#include + +#include "../Util/SelectionRule.h" + +namespace Spectra { + +template +class SearchSpace; + +/// This class handles the creation and manipulation of Ritz eigen pairs +/// for iterative eigensolvers such as Davidson, Jacobi-Davidson, etc. +template +class RitzPairs +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using Array = Eigen::Array; + using BoolArray = Eigen::Array; + + Vector m_values; // eigenvalues + Matrix m_small_vectors; // eigenvectors of the small problem, makes restart cheaper. + Matrix m_vectors; // Ritz (or harmonic Ritz) eigenvectors + Matrix m_residues; // residues of the pairs + BoolArray m_root_converged; + +public: + RitzPairs() = default; + + /// Compute the eigen values/vectors + /// + /// \param search_space Instance of the class handling the search space + /// \return Eigen::ComputationalInfo Whether small eigenvalue problem worked + Eigen::ComputationInfo compute_eigen_pairs(const SearchSpace& search_space); + + /// Returns the size of the ritz eigen pairs + /// + /// \return Eigen::Index Number of pairs + Index size() const { return m_values.size(); } + + /// Sort the eigen pairs according to the selection rule + /// + /// \param selection Sorting rule + void sort(SortRule selection) + { + std::vector ind = argsort(selection, m_values); + RitzPairs temp = *this; + for (Index i = 0; i < size(); i++) + { + m_values[i] = temp.m_values[ind[i]]; + m_vectors.col(i) = temp.m_vectors.col(ind[i]); + m_residues.col(i) = temp.m_residues.col(ind[i]); + m_small_vectors.col(i) = temp.m_small_vectors.col(ind[i]); + } + } + + /// Checks if the algorithm has converged and updates root_converged + /// + /// \param tol Tolerance for convergence + /// \param number_eigenvalue Number of request eigenvalues + /// \return bool true if all eigenvalues are converged + bool check_convergence(Scalar tol, Index number_eigenvalues) + { + const Array norms = m_residues.colwise().norm(); + bool converged = true; + m_root_converged = BoolArray::Zero(norms.size()); + for (Index j = 0; j < norms.size(); j++) + { + m_root_converged[j] = (norms[j] < tol); + if (j < number_eigenvalues) + { + converged &= (norms[j] < tol); + } + } + return converged; + } + + const Matrix& ritz_vectors() const { return m_vectors; } + const Vector& ritz_values() const { return m_values; } + const Matrix& small_ritz_vectors() const { return m_small_vectors; } + const Matrix& residues() const { return m_residues; } + const BoolArray& converged_eigenvalues() const { return m_root_converged; } +}; + +} // namespace Spectra + +#include "SearchSpace.h" + +namespace Spectra { + +/// Creates the small space matrix and computes its eigen pairs +/// Also computes the ritz vectors and residues +/// +/// \param search_space Instance of the SearchSpace class +template +Eigen::ComputationInfo RitzPairs::compute_eigen_pairs(const SearchSpace& search_space) +{ + const Matrix& basis_vectors = search_space.basis_vectors(); + const Matrix& op_basis_prod = search_space.operator_basis_product(); + + // Form the small eigenvalue + Matrix small_matrix = basis_vectors.transpose() * op_basis_prod; + + // Small eigenvalue problem + Eigen::SelfAdjointEigenSolver eigen_solver(small_matrix); + m_values = eigen_solver.eigenvalues(); + m_small_vectors = eigen_solver.eigenvectors(); + + // Ritz vectors + m_vectors = basis_vectors * m_small_vectors; + + // Residues + m_residues = op_basis_prod * m_small_vectors - m_vectors * m_values.asDiagonal(); + return eigen_solver.info(); +} + +} // namespace Spectra + +#endif // SPECTRA_RITZ_PAIRS_H diff --git a/thirdparty/include/Spectra/LinAlg/SearchSpace.h b/thirdparty/include/Spectra/LinAlg/SearchSpace.h new file mode 100644 index 0000000..2122c9f --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/SearchSpace.h @@ -0,0 +1,96 @@ +// Copyright (C) 2020 Netherlands eScience Center +// +// 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_SEARCH_SPACE_H +#define SPECTRA_SEARCH_SPACE_H + +#include + +#include "RitzPairs.h" +#include "Orthogonalization.h" + +namespace Spectra { + +/// This class handles the creation and manipulation of the search space +/// for iterative eigensolvers such as Davidson, Jacobi-Davidson, etc. +template +class SearchSpace +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + + Matrix m_basis_vectors; + Matrix m_op_basis_product; + + /// Append new vector to the basis + /// + /// \param new_vect Matrix of new correction vectors + void append_new_vectors_to_basis(const Matrix& new_vect) + { + Index num_update = new_vect.cols(); + m_basis_vectors.conservativeResize(Eigen::NoChange, m_basis_vectors.cols() + num_update); + m_basis_vectors.rightCols(num_update).noalias() = new_vect; + } + +public: + SearchSpace() = default; + + /// Returns the current size of the search space + Index size() const { return m_basis_vectors.cols(); } + + void initialize_search_space(const Eigen::Ref& initial_vectors) + { + m_basis_vectors = initial_vectors; + m_op_basis_product = Matrix(initial_vectors.rows(), 0); + } + + /// Updates the matrix formed by the operator applied to the search space + /// after the addition of new vectors in the search space. Only the product + /// of the operator with the new vectors is computed and the result is appended + /// to the op_basis_product member variable + /// + /// \param OpType Operator representing the matrix + template + void update_operator_basis_product(OpType& op) + { + Index nvec = m_basis_vectors.cols() - m_op_basis_product.cols(); + m_op_basis_product.conservativeResize(Eigen::NoChange, m_basis_vectors.cols()); + m_op_basis_product.rightCols(nvec).noalias() = op * m_basis_vectors.rightCols(nvec); + } + + /// Restart the search space by reducing the basis vector to the last + /// Ritz eigenvector + /// + /// \param ritz_pair Instance of a RitzPair class + /// \param size Size of the restart + void restart(const RitzPairs& ritz_pairs, Index size) + { + m_basis_vectors = ritz_pairs.ritz_vectors().leftCols(size); + m_op_basis_product = m_op_basis_product * ritz_pairs.small_ritz_vectors().leftCols(size); + } + + /// Append new vectors to the search space and + /// orthogonalize the resulting matrix + /// + /// \param new_vect Matrix of new correction vectors + void extend_basis(const Matrix& new_vect) + { + Index left_cols_to_skip = size(); + append_new_vectors_to_basis(new_vect); + twice_is_enough_orthogonalisation(m_basis_vectors, left_cols_to_skip); + } + + /// Returns the basis vectors + const Matrix& basis_vectors() const { return m_basis_vectors; } + + /// Returns the operator applied to basis vector + const Matrix& operator_basis_product() const { return m_op_basis_product; } +}; + +} // namespace Spectra + +#endif // SPECTRA_SEARCH_SPACE_H diff --git a/thirdparty/include/Spectra/LinAlg/TridiagEigen.h b/thirdparty/include/Spectra/LinAlg/TridiagEigen.h new file mode 100644 index 0000000..120aa63 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/TridiagEigen.h @@ -0,0 +1,230 @@ +// The code was adapted from Eigen/src/Eigenvaleus/SelfAdjointEigenSolver.h +// +// Copyright (C) 2008-2010 Gael Guennebaud +// Copyright (C) 2010 Jitse Niesen +// Copyright (C) 2016-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_TRIDIAG_EIGEN_H +#define SPECTRA_TRIDIAG_EIGEN_H + +#include +#include +#include + +#include "../Util/TypeTraits.h" + +namespace Spectra { + +template +class TridiagEigen +{ +private: + using Index = Eigen::Index; + // For convenience in adapting the tridiagonal_qr_step() function + using RealScalar = Scalar; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using GenericMatrix = Eigen::Ref; + using ConstGenericMatrix = const Eigen::Ref; + + Index m_n; + Vector m_main_diag; // Main diagonal elements of the matrix + Vector m_sub_diag; // Sub-diagonal elements of the matrix + Matrix m_evecs; // To store eigenvectors + bool m_computed; + + // Adapted from Eigen/src/Eigenvaleus/SelfAdjointEigenSolver.h + // Francis implicit QR step. + static void tridiagonal_qr_step(RealScalar* diag, + RealScalar* subdiag, Index start, + Index end, Scalar* matrixQ, + Index n) + { + using std::abs; + + // Wilkinson Shift. + RealScalar td = (diag[end - 1] - diag[end]) * RealScalar(0.5); + RealScalar e = subdiag[end - 1]; + // Note that thanks to scaling, e^2 or td^2 cannot overflow, however they can still + // underflow thus leading to inf/NaN values when using the following commented code: + // RealScalar e2 = numext::abs2(subdiag[end-1]); + // RealScalar mu = diag[end] - e2 / (td + (td>0 ? 1 : -1) * sqrt(td*td + e2)); + // This explain the following, somewhat more complicated, version: + RealScalar mu = diag[end]; + if (td == RealScalar(0)) + mu -= abs(e); + else if (e != RealScalar(0)) + { + const RealScalar e2 = Eigen::numext::abs2(e); + const RealScalar h = Eigen::numext::hypot(td, e); + if (e2 == RealScalar(0)) + mu -= e / ((td + (td > RealScalar(0) ? h : -h)) / e); + else + mu -= e2 / (td + (td > RealScalar(0) ? h : -h)); + } + + RealScalar x = diag[start] - mu; + RealScalar z = subdiag[start]; + Eigen::Map q(matrixQ, n, n); + // If z ever becomes zero, the Givens rotation will be the identity and + // z will stay zero for all future iterations. + for (Index k = start; k < end && z != RealScalar(0); ++k) + { + Eigen::JacobiRotation rot; + rot.makeGivens(x, z); + + const RealScalar s = rot.s(); + const RealScalar c = rot.c(); + + // do T = G' T G + RealScalar sdk = s * diag[k] + c * subdiag[k]; + RealScalar dkp1 = s * subdiag[k] + c * diag[k + 1]; + + diag[k] = c * (c * diag[k] - s * subdiag[k]) - s * (c * subdiag[k] - s * diag[k + 1]); + diag[k + 1] = s * sdk + c * dkp1; + subdiag[k] = c * sdk - s * dkp1; + + if (k > start) + subdiag[k - 1] = c * subdiag[k - 1] - s * z; + + // "Chasing the bulge" to return to triangular form. + x = subdiag[k]; + if (k < end - 1) + { + z = -s * subdiag[k + 1]; + subdiag[k + 1] = c * subdiag[k + 1]; + } + + // apply the givens rotation to the unit matrix Q = Q * G + if (matrixQ) + q.applyOnTheRight(k, k + 1, rot); + } + } + +public: + TridiagEigen() : + m_n(0), m_computed(false) + {} + + TridiagEigen(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_computed(false) + { + compute(mat); + } + + void compute(ConstGenericMatrix& mat) + { + using std::abs; + + // A very small value, but 1.0 / near_0 does not overflow + // ~= 1e-307 for the "double" type + constexpr Scalar near_0 = TypeTraits::min() * Scalar(10); + + m_n = mat.rows(); + if (m_n != mat.cols()) + throw std::invalid_argument("TridiagEigen: matrix must be square"); + + m_main_diag.resize(m_n); + m_sub_diag.resize(m_n - 1); + m_evecs.resize(m_n, m_n); + m_evecs.setIdentity(); + + // Scale matrix to improve stability + const Scalar scale = (std::max)(mat.diagonal().cwiseAbs().maxCoeff(), + mat.diagonal(-1).cwiseAbs().maxCoeff()); + // If scale=0, mat is a zero matrix, so we can early stop + if (scale < near_0) + { + // m_main_diag contains eigenvalues + m_main_diag.setZero(); + // m_evecs has been set identity + // m_evecs.setIdentity(); + m_computed = true; + return; + } + m_main_diag.noalias() = mat.diagonal() / scale; + m_sub_diag.noalias() = mat.diagonal(-1) / scale; + + Scalar* diag = m_main_diag.data(); + Scalar* subdiag = m_sub_diag.data(); + + Index end = m_n - 1; + Index start = 0; + Index iter = 0; // total number of iterations + int info = 0; // 0 for success, 1 for failure + + const Scalar considerAsZero = TypeTraits::min(); + const Scalar precision_inv = Scalar(1) / Eigen::NumTraits::epsilon(); + + while (end > 0) + { + for (Index i = start; i < end; i++) + { + if (abs(subdiag[i]) <= considerAsZero) + subdiag[i] = Scalar(0); + else + { + // abs(subdiag[i]) <= epsilon * sqrt(abs(diag[i]) + abs(diag[i+1])) + // Scaled to prevent underflows. + const Scalar scaled_subdiag = precision_inv * subdiag[i]; + if (scaled_subdiag * scaled_subdiag <= (abs(diag[i]) + abs(diag[i + 1]))) + subdiag[i] = Scalar(0); + } + } + + // find the largest unreduced block at the end of the matrix. + while (end > 0 && subdiag[end - 1] == Scalar(0)) + end--; + + if (end <= 0) + break; + + // if we spent too many iterations, we give up + iter++; + if (iter > 30 * m_n) + { + info = 1; + break; + } + + start = end - 1; + while (start > 0 && subdiag[start - 1] != Scalar(0)) + start--; + + tridiagonal_qr_step(diag, subdiag, start, end, m_evecs.data(), m_n); + } + + if (info > 0) + throw std::runtime_error("TridiagEigen: eigen decomposition failed"); + + // Scale eigenvalues back + m_main_diag *= scale; + + m_computed = true; + } + + const Vector& eigenvalues() const + { + if (!m_computed) + throw std::logic_error("TridiagEigen: need to call compute() first"); + + // After calling compute(), main_diag will contain the eigenvalues. + return m_main_diag; + } + + const Matrix& eigenvectors() const + { + if (!m_computed) + throw std::logic_error("TridiagEigen: need to call compute() first"); + + return m_evecs; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_TRIDIAG_EIGEN_H diff --git a/thirdparty/include/Spectra/LinAlg/UpperHessenbergEigen.h b/thirdparty/include/Spectra/LinAlg/UpperHessenbergEigen.h new file mode 100644 index 0000000..5522e96 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/UpperHessenbergEigen.h @@ -0,0 +1,314 @@ +// The code was adapted from Eigen/src/Eigenvaleus/EigenSolver.h +// +// Copyright (C) 2008 Gael Guennebaud +// Copyright (C) 2010,2012 Jitse Niesen +// Copyright (C) 2016-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_UPPER_HESSENBERG_EIGEN_H +#define SPECTRA_UPPER_HESSENBERG_EIGEN_H + +#include +#include + +#include "UpperHessenbergSchur.h" + +namespace Spectra { + +template +class UpperHessenbergEigen +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using GenericMatrix = Eigen::Ref; + using ConstGenericMatrix = const Eigen::Ref; + + using Complex = std::complex; + using ComplexMatrix = Eigen::Matrix; + using ComplexVector = Eigen::Matrix; + + Index m_n; // Size of the matrix + UpperHessenbergSchur m_schur; // Schur decomposition solver + Matrix m_matT; // Schur T matrix + Matrix m_eivec; // Storing eigenvectors + ComplexVector m_eivalues; // Eigenvalues + bool m_computed; + + void doComputeEigenvectors() + { + using std::abs; + + const Index size = m_eivec.cols(); + const Scalar eps = Eigen::NumTraits::epsilon(); + + // inefficient! this is already computed in RealSchur + Scalar norm(0); + for (Index j = 0; j < size; ++j) + { + norm += m_matT.row(j).segment((std::max)(j - 1, Index(0)), size - (std::max)(j - 1, Index(0))).cwiseAbs().sum(); + } + + // Backsubstitute to find vectors of upper triangular form + if (norm == Scalar(0)) + return; + + for (Index n = size - 1; n >= 0; n--) + { + Scalar p = m_eivalues.coeff(n).real(); + Scalar q = m_eivalues.coeff(n).imag(); + + // Scalar vector + if (q == Scalar(0)) + { + Scalar lastr(0), lastw(0); + Index l = n; + + m_matT.coeffRef(n, n) = Scalar(1); + for (Index i = n - 1; i >= 0; i--) + { + Scalar w = m_matT.coeff(i, i) - p; + Scalar r = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n).segment(l, n - l + 1)); + + if (m_eivalues.coeff(i).imag() < Scalar(0)) + { + lastw = w; + lastr = r; + } + else + { + l = i; + if (m_eivalues.coeff(i).imag() == Scalar(0)) + { + if (w != Scalar(0)) + m_matT.coeffRef(i, n) = -r / w; + else + m_matT.coeffRef(i, n) = -r / (eps * norm); + } + else // Solve real equations + { + Scalar x = m_matT.coeff(i, i + 1); + Scalar y = m_matT.coeff(i + 1, i); + Scalar denom = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag(); + Scalar t = (x * lastr - lastw * r) / denom; + m_matT.coeffRef(i, n) = t; + if (abs(x) > abs(lastw)) + m_matT.coeffRef(i + 1, n) = (-r - w * t) / x; + else + m_matT.coeffRef(i + 1, n) = (-lastr - y * t) / lastw; + } + + // Overflow control + Scalar t = abs(m_matT.coeff(i, n)); + if ((eps * t) * t > Scalar(1)) + m_matT.col(n).tail(size - i) /= t; + } + } + } + else if (q < Scalar(0) && n > 0) + { // Complex vector + Scalar lastra(0), lastsa(0), lastw(0); + Index l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (abs(m_matT.coeff(n, n - 1)) > abs(m_matT.coeff(n - 1, n))) + { + m_matT.coeffRef(n - 1, n - 1) = q / m_matT.coeff(n, n - 1); + m_matT.coeffRef(n - 1, n) = -(m_matT.coeff(n, n) - p) / m_matT.coeff(n, n - 1); + } + else + { + Complex cc = Complex(Scalar(0), -m_matT.coeff(n - 1, n)) / Complex(m_matT.coeff(n - 1, n - 1) - p, q); + m_matT.coeffRef(n - 1, n - 1) = Eigen::numext::real(cc); + m_matT.coeffRef(n - 1, n) = Eigen::numext::imag(cc); + } + m_matT.coeffRef(n, n - 1) = Scalar(0); + m_matT.coeffRef(n, n) = Scalar(1); + for (Index i = n - 2; i >= 0; i--) + { + Scalar ra = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n - 1).segment(l, n - l + 1)); + Scalar sa = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n).segment(l, n - l + 1)); + Scalar w = m_matT.coeff(i, i) - p; + + if (m_eivalues.coeff(i).imag() < Scalar(0)) + { + lastw = w; + lastra = ra; + lastsa = sa; + } + else + { + l = i; + if (m_eivalues.coeff(i).imag() == Scalar(0)) + { + Complex cc = Complex(-ra, -sa) / Complex(w, q); + m_matT.coeffRef(i, n - 1) = Eigen::numext::real(cc); + m_matT.coeffRef(i, n) = Eigen::numext::imag(cc); + } + else + { + // Solve complex equations + Scalar x = m_matT.coeff(i, i + 1); + Scalar y = m_matT.coeff(i + 1, i); + Scalar vr = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag() - q * q; + Scalar vi = (m_eivalues.coeff(i).real() - p) * Scalar(2) * q; + if ((vr == Scalar(0)) && (vi == Scalar(0))) + vr = eps * norm * (abs(w) + abs(q) + abs(x) + abs(y) + abs(lastw)); + + Complex cc = Complex(x * lastra - lastw * ra + q * sa, x * lastsa - lastw * sa - q * ra) / Complex(vr, vi); + m_matT.coeffRef(i, n - 1) = Eigen::numext::real(cc); + m_matT.coeffRef(i, n) = Eigen::numext::imag(cc); + if (abs(x) > (abs(lastw) + abs(q))) + { + m_matT.coeffRef(i + 1, n - 1) = (-ra - w * m_matT.coeff(i, n - 1) + q * m_matT.coeff(i, n)) / x; + m_matT.coeffRef(i + 1, n) = (-sa - w * m_matT.coeff(i, n) - q * m_matT.coeff(i, n - 1)) / x; + } + else + { + cc = Complex(-lastra - y * m_matT.coeff(i, n - 1), -lastsa - y * m_matT.coeff(i, n)) / Complex(lastw, q); + m_matT.coeffRef(i + 1, n - 1) = Eigen::numext::real(cc); + m_matT.coeffRef(i + 1, n) = Eigen::numext::imag(cc); + } + } + + // Overflow control + Scalar t = (std::max)(abs(m_matT.coeff(i, n - 1)), abs(m_matT.coeff(i, n))); + if ((eps * t) * t > Scalar(1)) + m_matT.block(i, n - 1, size - i, 2) /= t; + } + } + + // We handled a pair of complex conjugate eigenvalues, so need to skip them both + n--; + } + } + + // Back transformation to get eigenvectors of original matrix + Vector m_tmp(size); + for (Index j = size - 1; j >= 0; j--) + { + m_tmp.noalias() = m_eivec.leftCols(j + 1) * m_matT.col(j).segment(0, j + 1); + m_eivec.col(j) = m_tmp; + } + } + +public: + UpperHessenbergEigen() : + m_n(0), m_computed(false) + {} + + UpperHessenbergEigen(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_computed(false) + { + compute(mat); + } + + void compute(ConstGenericMatrix& mat) + { + using std::abs; + using std::sqrt; + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("UpperHessenbergEigen: matrix must be square"); + + m_n = mat.rows(); + // Scale matrix prior to the Schur decomposition + const Scalar scale = mat.cwiseAbs().maxCoeff(); + + // Reduce to real Schur form + m_schur.compute(mat / scale); + m_schur.swap_T(m_matT); + m_schur.swap_U(m_eivec); + + // Compute eigenvalues from matT + m_eivalues.resize(m_n); + Index i = 0; + while (i < m_n) + { + // Real eigenvalue + if (i == m_n - 1 || m_matT.coeff(i + 1, i) == Scalar(0)) + { + m_eivalues.coeffRef(i) = m_matT.coeff(i, i); + ++i; + } + else // Complex eigenvalues + { + Scalar p = Scalar(0.5) * (m_matT.coeff(i, i) - m_matT.coeff(i + 1, i + 1)); + Scalar z; + // Compute z = sqrt(abs(p * p + m_matT.coeff(i+1, i) * m_matT.coeff(i, i+1))); + // without overflow + { + Scalar t0 = m_matT.coeff(i + 1, i); + Scalar t1 = m_matT.coeff(i, i + 1); + Scalar maxval = (std::max)(abs(p), (std::max)(abs(t0), abs(t1))); + t0 /= maxval; + t1 /= maxval; + Scalar p0 = p / maxval; + z = maxval * sqrt(abs(p0 * p0 + t0 * t1)); + } + m_eivalues.coeffRef(i) = Complex(m_matT.coeff(i + 1, i + 1) + p, z); + m_eivalues.coeffRef(i + 1) = Complex(m_matT.coeff(i + 1, i + 1) + p, -z); + i += 2; + } + } + + // Compute eigenvectors + doComputeEigenvectors(); + + // Scale eigenvalues back + m_eivalues *= scale; + + m_computed = true; + } + + const ComplexVector& eigenvalues() const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); + + return m_eivalues; + } + + ComplexMatrix eigenvectors() + { + using std::abs; + + if (!m_computed) + throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); + + Index n = m_eivec.cols(); + ComplexMatrix matV(n, n); + for (Index j = 0; j < n; ++j) + { + // imaginary part of real eigenvalue is already set to exact zero + if (Eigen::numext::imag(m_eivalues.coeff(j)) == Scalar(0) || j + 1 == n) + { + // we have a real eigen value + matV.col(j) = m_eivec.col(j).template cast(); + matV.col(j).normalize(); + } + else + { + // we have a pair of complex eigen values + for (Index i = 0; i < n; ++i) + { + matV.coeffRef(i, j) = Complex(m_eivec.coeff(i, j), m_eivec.coeff(i, j + 1)); + matV.coeffRef(i, j + 1) = Complex(m_eivec.coeff(i, j), -m_eivec.coeff(i, j + 1)); + } + matV.col(j).normalize(); + matV.col(j + 1).normalize(); + ++j; + } + } + + return matV; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_UPPER_HESSENBERG_EIGEN_H diff --git a/thirdparty/include/Spectra/LinAlg/UpperHessenbergQR.h b/thirdparty/include/Spectra/LinAlg/UpperHessenbergQR.h new file mode 100644 index 0000000..dd5e2e7 --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/UpperHessenbergQR.h @@ -0,0 +1,786 @@ +// Copyright (C) 2016-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_UPPER_HESSENBERG_QR_H +#define SPECTRA_UPPER_HESSENBERG_QR_H + +#include +#include // std::abs, std::sqrt, std::pow +#include // std::fill +#include // std::logic_error + +#include "../Util/TypeTraits.h" + +namespace Spectra { + +/// +/// \defgroup Internals Internal Classes +/// +/// Classes for internal use. May be useful to developers. +/// + +/// +/// \ingroup Internals +/// @{ +/// + +/// +/// \defgroup LinearAlgebra Linear Algebra +/// +/// A number of classes for linear algebra operations. +/// + +/// +/// \ingroup LinearAlgebra +/// +/// Perform the QR decomposition of an upper Hessenberg matrix. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// +template +class UpperHessenbergQR +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using RowVector = Eigen::Matrix; + using Array = Eigen::Array; + + using GenericMatrix = Eigen::Ref; + using ConstGenericMatrix = const Eigen::Ref; + + Matrix m_mat_R; + +protected: + Index m_n; + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + // Q = G1 * G2 * ... * G_{n-1} + Scalar m_shift; + Array m_rot_cos; + Array m_rot_sin; + bool m_computed; + + // Given a >= b > 0, compute r = sqrt(a^2 + b^2), c = a / r, and s = b / r with high precision + static void stable_scaling(const Scalar& a, const Scalar& b, Scalar& r, Scalar& c, Scalar& s) + { + using std::sqrt; + using std::pow; + + // Let t = b / a, then 0 < t <= 1 + // c = 1 / sqrt(1 + t^2) + // s = t * c + // r = a * sqrt(1 + t^2) + const Scalar t = b / a; + // We choose a cutoff such that cutoff^4 < eps + // If t > cutoff, use the standard way; otherwise use Taylor series expansion + // to avoid an explicit sqrt() call that may lose precision + constexpr Scalar eps = TypeTraits::epsilon(); + // std::pow() is not constexpr, so we do not declare cutoff to be constexpr + // But most compilers should be able to compute cutoff at compile time + const Scalar cutoff = Scalar(0.1) * pow(eps, Scalar(0.25)); + if (t >= cutoff) + { + const Scalar denom = sqrt(Scalar(1) + t * t); + c = Scalar(1) / denom; + s = t * c; + r = a * denom; + } + else + { + // 1 / sqrt(1 + t^2) ~= 1 - (1/2) * t^2 + (3/8) * t^4 + // 1 / sqrt(1 + l^2) ~= 1 / l - (1/2) / l^3 + (3/8) / l^5 + // == t - (1/2) * t^3 + (3/8) * t^5, where l = 1 / t + // sqrt(1 + t^2) ~= 1 + (1/2) * t^2 - (1/8) * t^4 + (1/16) * t^6 + // + // c = 1 / sqrt(1 + t^2) ~= 1 - t^2 * (1/2 - (3/8) * t^2) + // s = 1 / sqrt(1 + l^2) ~= t * (1 - t^2 * (1/2 - (3/8) * t^2)) + // r = a * sqrt(1 + t^2) ~= a + (1/2) * b * t - (1/8) * b * t^3 + (1/16) * b * t^5 + // == a + (b/2) * t * (1 - t^2 * (1/4 - 1/8 * t^2)) + constexpr Scalar c1 = Scalar(1); + constexpr Scalar c2 = Scalar(0.5); + constexpr Scalar c4 = Scalar(0.25); + constexpr Scalar c8 = Scalar(0.125); + constexpr Scalar c38 = Scalar(0.375); + const Scalar t2 = t * t; + const Scalar tc = t2 * (c2 - c38 * t2); + c = c1 - tc; + s = t - t * tc; + r = a + c2 * b * t * (c1 - t2 * (c4 - c8 * t2)); + + /* const Scalar t_2 = Scalar(0.5) * t; + const Scalar t2_2 = t_2 * t; + const Scalar t3_2 = t2_2 * t; + const Scalar t4_38 = Scalar(1.5) * t2_2 * t2_2; + const Scalar t5_16 = Scalar(0.25) * t3_2 * t2_2; + c = Scalar(1) - t2_2 + t4_38; + s = t - t3_2 + Scalar(6) * t5_16; + r = a + b * (t_2 - Scalar(0.25) * t3_2 + t5_16); */ + } + } + + // Given x and y, compute 1) r = sqrt(x^2 + y^2), 2) c = x / r, 3) s = -y / r + // If both x and y are zero, set c = 1 and s = 0 + // We must implement it in a numerically stable way + // The implementation below is shown to be more accurate than directly computing + // r = std::hypot(x, y); c = x / r; s = -y / r; + static void compute_rotation(const Scalar& x, const Scalar& y, Scalar& r, Scalar& c, Scalar& s) + { + using std::abs; + + // Only need xsign when x != 0 + const Scalar xsign = (x > Scalar(0)) ? Scalar(1) : Scalar(-1); + const Scalar xabs = abs(x); + if (y == Scalar(0)) + { + c = (x == Scalar(0)) ? Scalar(1) : xsign; + s = Scalar(0); + r = xabs; + return; + } + + // Now we know y != 0 + const Scalar ysign = (y > Scalar(0)) ? Scalar(1) : Scalar(-1); + const Scalar yabs = abs(y); + if (x == Scalar(0)) + { + c = Scalar(0); + s = -ysign; + r = yabs; + return; + } + + // Now we know x != 0, y != 0 + if (xabs > yabs) + { + stable_scaling(xabs, yabs, r, c, s); + c = xsign * c; + s = -ysign * s; + } + else + { + stable_scaling(yabs, xabs, r, s, c); + c = xsign * c; + s = -ysign * s; + } + } + +public: + /// + /// Constructor to preallocate memory. Computation can + /// be performed later by calling the compute() method. + /// + UpperHessenbergQR(Index size) : + m_n(size), + m_rot_cos(m_n - 1), + m_rot_sin(m_n - 1), + m_computed(false) + {} + + /// + /// Constructor to create an object that performs and stores the + /// QR decomposition of an upper Hessenberg matrix `mat`, with an + /// optional shift: \f$H-sI=QR\f$. Here \f$H\f$ stands for the matrix + /// `mat`, and \f$s\f$ is the shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the upper triangular and the subdiagonal elements of + /// the matrix are used. + /// + UpperHessenbergQR(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) : + m_n(mat.rows()), + m_shift(shift), + m_rot_cos(m_n - 1), + m_rot_sin(m_n - 1), + m_computed(false) + { + compute(mat, shift); + } + + /// + /// Virtual destructor. + /// + virtual ~UpperHessenbergQR(){}; + + /// + /// Compute the QR decomposition of an upper Hessenberg matrix with + /// an optional shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the upper triangular and the subdiagonal elements of + /// the matrix are used. + /// + virtual void compute(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) + { + m_n = mat.rows(); + if (m_n != mat.cols()) + throw std::invalid_argument("UpperHessenbergQR: matrix must be square"); + + m_shift = shift; + m_mat_R.resize(m_n, m_n); + m_rot_cos.resize(m_n - 1); + m_rot_sin.resize(m_n - 1); + + // Make a copy of mat - s * I + m_mat_R.noalias() = mat; + m_mat_R.diagonal().array() -= m_shift; + + Scalar xi, xj, r, c, s; + Scalar *Rii, *ptr; + const Index n1 = m_n - 1; + for (Index i = 0; i < n1; i++) + { + Rii = &m_mat_R.coeffRef(i, i); + + // Make sure R is upper Hessenberg + // Zero the elements below R[i + 1, i] + std::fill(Rii + 2, Rii + m_n - i, Scalar(0)); + + xi = Rii[0]; // R[i, i] + xj = Rii[1]; // R[i + 1, i] + compute_rotation(xi, xj, r, c, s); + m_rot_cos.coeffRef(i) = c; + m_rot_sin.coeffRef(i) = s; + + // For a complete QR decomposition, + // we first obtain the rotation matrix + // G = [ cos sin] + // [-sin cos] + // and then do R[i:(i + 1), i:(n - 1)] = G' * R[i:(i + 1), i:(n - 1)] + + // Gt << c, -s, s, c; + // m_mat_R.block(i, i, 2, m_n - i) = Gt * m_mat_R.block(i, i, 2, m_n - i); + Rii[0] = r; // R[i, i] => r + Rii[1] = 0; // R[i + 1, i] => 0 + ptr = Rii + m_n; // R[i, k], k = i+1, i+2, ..., n-1 + for (Index j = i + 1; j < m_n; j++, ptr += m_n) + { + const Scalar tmp = ptr[0]; + ptr[0] = c * tmp - s * ptr[1]; + ptr[1] = s * tmp + c * ptr[1]; + } + + // If we do not need to calculate the R matrix, then + // only the cos and sin sequences are required. + // In such case we only update R[i + 1, (i + 1):(n - 1)] + // m_mat_R.block(i + 1, i + 1, 1, m_n - i - 1) *= c; + // m_mat_R.block(i + 1, i + 1, 1, m_n - i - 1) += s * m_mat_R.block(i, i + 1, 1, m_n - i - 1); + } + + m_computed = true; + } + + /// + /// Return the \f$R\f$ matrix in the QR decomposition, which is an + /// upper triangular matrix. + /// + /// \return Returned matrix type will be `Eigen::Matrix`, depending on + /// the template parameter `Scalar` defined. + /// + virtual Matrix matrix_R() const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + return m_mat_R; + } + + /// + /// Overwrite `dest` with \f$Q'HQ = RQ + sI\f$, where \f$H\f$ is the input matrix `mat`, + /// and \f$s\f$ is the shift. The result is an upper Hessenberg matrix. + /// + /// \param mat The matrix to be overwritten, whose type should be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + virtual void matrix_QtHQ(Matrix& dest) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + // Make a copy of the R matrix + dest.resize(m_n, m_n); + dest.noalias() = m_mat_R; + + // Compute the RQ matrix + const Index n1 = m_n - 1; + for (Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // RQ[, i:(i + 1)] = RQ[, i:(i + 1)] * Gi + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Scalar *Yi, *Yi1; + Yi = &dest.coeffRef(0, i); + Yi1 = Yi + m_n; // RQ[0, i + 1] + const Index i2 = i + 2; + for (Index j = 0; j < i2; j++) + { + const Scalar tmp = Yi[j]; + Yi[j] = c * tmp - s * Yi1[j]; + Yi1[j] = s * tmp + c * Yi1[j]; + } + + /* Vector dest = RQ.block(0, i, i + 2, 1); + dest.block(0, i, i + 2, 1) = c * Yi - s * dest.block(0, i + 1, i + 2, 1); + dest.block(0, i + 1, i + 2, 1) = s * Yi + c * dest.block(0, i + 1, i + 2, 1); */ + } + + // Add the shift to the diagonal + dest.diagonal().array() += m_shift; + } + + /// + /// Apply the \f$Q\f$ matrix to a vector \f$y\f$. + /// + /// \param Y A vector that will be overwritten by the matrix product \f$Qy\f$. + /// + /// Vector type can be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + // Y -> QY = G1 * G2 * ... * Y + void apply_QY(Vector& Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + for (Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1)] = Gi * Y[i:(i + 1)] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + const Scalar tmp = Y[i]; + Y[i] = c * tmp + s * Y[i + 1]; + Y[i + 1] = -s * tmp + c * Y[i + 1]; + } + } + + /// + /// Apply the \f$Q\f$ matrix to a vector \f$y\f$. + /// + /// \param Y A vector that will be overwritten by the matrix product \f$Q'y\f$. + /// + /// Vector type can be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + // Y -> Q'Y = G_{n-1}' * ... * G2' * G1' * Y + void apply_QtY(Vector& Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + const Index n1 = m_n - 1; + for (Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1)] = Gi' * Y[i:(i + 1)] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + const Scalar tmp = Y[i]; + Y[i] = c * tmp - s * Y[i + 1]; + Y[i + 1] = s * tmp + c * Y[i + 1]; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$QY\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> QY = G1 * G2 * ... * Y + void apply_QY(GenericMatrix Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + RowVector Yi(Y.cols()), Yi1(Y.cols()); + for (Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1), ] = Gi * Y[i:(i + 1), ] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.row(i); + Yi1.noalias() = Y.row(i + 1); + Y.row(i) = c * Yi + s * Yi1; + Y.row(i + 1) = -s * Yi + c * Yi1; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$Q'Y\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> Q'Y = G_{n-1}' * ... * G2' * G1' * Y + void apply_QtY(GenericMatrix Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + RowVector Yi(Y.cols()), Yi1(Y.cols()); + const Index n1 = m_n - 1; + for (Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1), ] = Gi' * Y[i:(i + 1), ] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.row(i); + Yi1.noalias() = Y.row(i + 1); + Y.row(i) = c * Yi - s * Yi1; + Y.row(i + 1) = s * Yi + c * Yi1; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$YQ\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> YQ = Y * G1 * G2 * ... + void apply_YQ(GenericMatrix Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + /*Vector Yi(Y.rows()); + for(Index i = 0; i < m_n - 1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[, i:(i + 1)] = Y[, i:(i + 1)] * Gi + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.col(i); + Y.col(i) = c * Yi - s * Y.col(i + 1); + Y.col(i + 1) = s * Yi + c * Y.col(i + 1); + }*/ + Scalar *Y_col_i, *Y_col_i1; + const Index n1 = m_n - 1; + const Index nrow = Y.rows(); + for (Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + + Y_col_i = &Y.coeffRef(0, i); + Y_col_i1 = &Y.coeffRef(0, i + 1); + for (Index j = 0; j < nrow; j++) + { + Scalar tmp = Y_col_i[j]; + Y_col_i[j] = c * tmp - s * Y_col_i1[j]; + Y_col_i1[j] = s * tmp + c * Y_col_i1[j]; + } + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$YQ'\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> YQ' = Y * G_{n-1}' * ... * G2' * G1' + void apply_YQt(GenericMatrix Y) const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + Vector Yi(Y.rows()); + for (Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[, i:(i + 1)] = Y[, i:(i + 1)] * Gi' + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.col(i); + Y.col(i) = c * Yi + s * Y.col(i + 1); + Y.col(i + 1) = -s * Yi + c * Y.col(i + 1); + } + } +}; + +/// +/// \ingroup LinearAlgebra +/// +/// Perform the QR decomposition of a tridiagonal matrix, a special +/// case of upper Hessenberg matrices. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// +template +class TridiagQR : public UpperHessenbergQR +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using ConstGenericMatrix = const Eigen::Ref; + + using UpperHessenbergQR::m_n; + using UpperHessenbergQR::m_shift; + using UpperHessenbergQR::m_rot_cos; + using UpperHessenbergQR::m_rot_sin; + using UpperHessenbergQR::m_computed; + + Vector m_T_diag; // diagonal elements of T + Vector m_T_subd; // 1st subdiagonal of T + Vector m_R_diag; // diagonal elements of R, where T = QR + Vector m_R_supd; // 1st superdiagonal of R + Vector m_R_supd2; // 2nd superdiagonal of R + +public: + /// + /// Constructor to preallocate memory. Computation can + /// be performed later by calling the compute() method. + /// + TridiagQR(Index size) : + UpperHessenbergQR(size) + {} + + /// + /// Constructor to create an object that performs and stores the + /// QR decomposition of a tridiagonal matrix `mat`, with an + /// optional shift: \f$T-sI=QR\f$. Here \f$T\f$ stands for the matrix + /// `mat`, and \f$s\f$ is the shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the diagonal and subdiagonal elements of the matrix are used. + /// + TridiagQR(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) : + UpperHessenbergQR(mat.rows()) + { + this->compute(mat, shift); + } + + /// + /// Compute the QR decomposition of a tridiagonal matrix with an + /// optional shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the diagonal and subdiagonal elements of the matrix are used. + /// + void compute(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) override + { + using std::abs; + + m_n = mat.rows(); + if (m_n != mat.cols()) + throw std::invalid_argument("TridiagQR: matrix must be square"); + + m_shift = shift; + m_rot_cos.resize(m_n - 1); + m_rot_sin.resize(m_n - 1); + + // Save the diagonal and subdiagonal elements of T + m_T_diag.resize(m_n); + m_T_subd.resize(m_n - 1); + m_T_diag.noalias() = mat.diagonal(); + m_T_subd.noalias() = mat.diagonal(-1); + + // Deflation of small sub-diagonal elements + constexpr Scalar eps = TypeTraits::epsilon(); + for (Index i = 0; i < m_n - 1; i++) + { + if (abs(m_T_subd[i]) <= eps * (abs(m_T_diag[i]) + abs(m_T_diag[i + 1]))) + m_T_subd[i] = Scalar(0); + } + + // Apply shift and copy T to R + m_R_diag.resize(m_n); + m_R_supd.resize(m_n - 1); + m_R_supd2.resize(m_n - 2); + m_R_diag.array() = m_T_diag.array() - m_shift; + m_R_supd.noalias() = m_T_subd; + + // A number of pointers to avoid repeated address calculation + Scalar *c = m_rot_cos.data(), // pointer to the cosine vector + *s = m_rot_sin.data(), // pointer to the sine vector + r; + const Index n1 = m_n - 1, n2 = m_n - 2; + for (Index i = 0; i < n1; i++) + { + // Rdiag[i] == R[i, i] + // Tsubd[i] == R[i + 1, i] + // r = sqrt(R[i, i]^2 + R[i + 1, i]^2) + // c = R[i, i] / r, s = -R[i + 1, i] / r + this->compute_rotation(m_R_diag.coeff(i), m_T_subd.coeff(i), r, *c, *s); + + // For a complete QR decomposition, + // we first obtain the rotation matrix + // G = [ cos sin] + // [-sin cos] + // and then do R[i:(i + 1), i:(i + 2)] = G' * R[i:(i + 1), i:(i + 2)] + + // Update R[i, i] and R[i + 1, i] + // The updated value of R[i, i] is known to be r + // The updated value of R[i + 1, i] is known to be 0 + m_R_diag.coeffRef(i) = r; + // Update R[i, i + 1] and R[i + 1, i + 1] + // Rsupd[i] == R[i, i + 1] + // Rdiag[i + 1] == R[i + 1, i + 1] + const Scalar Tii1 = m_R_supd.coeff(i); + const Scalar Ti1i1 = m_R_diag.coeff(i + 1); + m_R_supd.coeffRef(i) = (*c) * Tii1 - (*s) * Ti1i1; + m_R_diag.coeffRef(i + 1) = (*s) * Tii1 + (*c) * Ti1i1; + // Update R[i, i + 2] and R[i + 1, i + 2] + // Rsupd2[i] == R[i, i + 2] + // Rsupd[i + 1] == R[i + 1, i + 2] + if (i < n2) + { + m_R_supd2.coeffRef(i) = -(*s) * m_R_supd.coeff(i + 1); + m_R_supd.coeffRef(i + 1) *= (*c); + } + + c++; + s++; + + // If we do not need to calculate the R matrix, then + // only the cos and sin sequences are required. + // In such case we only update R[i + 1, (i + 1):(i + 2)] + // R[i + 1, i + 1] = c * R[i + 1, i + 1] + s * R[i, i + 1]; + // R[i + 1, i + 2] *= c; + } + + m_computed = true; + } + + /// + /// Return the \f$R\f$ matrix in the QR decomposition, which is an + /// upper triangular matrix. + /// + /// \return Returned matrix type will be `Eigen::Matrix`, depending on + /// the template parameter `Scalar` defined. + /// + Matrix matrix_R() const override + { + if (!m_computed) + throw std::logic_error("TridiagQR: need to call compute() first"); + + Matrix R = Matrix::Zero(m_n, m_n); + R.diagonal().noalias() = m_R_diag; + R.diagonal(1).noalias() = m_R_supd; + R.diagonal(2).noalias() = m_R_supd2; + + return R; + } + + /// + /// Overwrite `dest` with \f$Q'TQ = RQ + sI\f$, where \f$T\f$ is the input matrix `mat`, + /// and \f$s\f$ is the shift. The result is a tridiagonal matrix. + /// + /// \param mat The matrix to be overwritten, whose type should be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + void matrix_QtHQ(Matrix& dest) const override + { + using std::abs; + + if (!m_computed) + throw std::logic_error("TridiagQR: need to call compute() first"); + + // In exact arithmetics, Q'TQ = RQ + sI, so we can just apply Q to R and add the shift. + // However, some numerical examples show that this algorithm decreases the precision, + // so we directly apply Q' and Q to T. + + // Copy the saved diagonal and subdiagonal elements of T to `dest` + dest.resize(m_n, m_n); + dest.setZero(); + dest.diagonal().noalias() = m_T_diag; + dest.diagonal(-1).noalias() = m_T_subd; + + // Ti = [x y 0], Gi = [ cos[i] sin[i] 0], Gi' * Ti * Gi = [x' y' o'] + // [y z w] [-sin[i] cos[i] 0] [y' z' w'] + // [0 w u] [ 0 0 1] [o' w' u'] + // + // x' = c2*x - 2*c*s*y + s2*z + // y' = c*s*(x-z) + (c2-s2)*y + // z' = s2*x + 2*c*s*y + c2*z + // o' = -s*w, w' = c*w, u' = u + // + // In iteration (i+1), (y', o') will be further updated to (y'', o''), + // where o'' = 0, y'' = cos[i+1]*y' - sin[i+1]*o' + const Index n1 = m_n - 1, n2 = m_n - 2; + for (Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + const Scalar cs = c * s, c2 = c * c, s2 = s * s; + const Scalar x = dest.coeff(i, i), + y = dest.coeff(i + 1, i), + z = dest.coeff(i + 1, i + 1); + const Scalar c2x = c2 * x, s2x = s2 * x, c2z = c2 * z, s2z = s2 * z; + const Scalar csy2 = Scalar(2) * c * s * y; + + // Update the diagonal and the lower subdiagonal of dest + dest.coeffRef(i, i) = c2x - csy2 + s2z; // x' + dest.coeffRef(i + 1, i) = cs * (x - z) + (c2 - s2) * y; // y' + dest.coeffRef(i + 1, i + 1) = s2x + csy2 + c2z; // z' + + if (i < n2) + { + const Scalar ci1 = m_rot_cos.coeff(i + 1); + const Scalar si1 = m_rot_sin.coeff(i + 1); + const Scalar o = -s * m_T_subd.coeff(i + 1); // o' + dest.coeffRef(i + 2, i + 1) *= c; // w' + dest.coeffRef(i + 1, i) = ci1 * dest.coeff(i + 1, i) - si1 * o; // y'' + } + } + + // Deflation of small sub-diagonal elements + constexpr Scalar eps = TypeTraits::epsilon(); + for (Index i = 0; i < n1; i++) + { + const Scalar diag = abs(dest.coeff(i, i)) + abs(dest.coeff(i + 1, i + 1)); + if (abs(dest.coeff(i + 1, i)) <= eps * diag) + dest.coeffRef(i + 1, i) = Scalar(0); + } + + // Copy the lower subdiagonal to upper subdiagonal + dest.diagonal(1).noalias() = dest.diagonal(-1); + } +}; + +/// +/// @} +/// + +} // namespace Spectra + +#endif // SPECTRA_UPPER_HESSENBERG_QR_H diff --git a/thirdparty/include/Spectra/LinAlg/UpperHessenbergSchur.h b/thirdparty/include/Spectra/LinAlg/UpperHessenbergSchur.h new file mode 100644 index 0000000..41723ce --- /dev/null +++ b/thirdparty/include/Spectra/LinAlg/UpperHessenbergSchur.h @@ -0,0 +1,450 @@ +// The code was adapted from Eigen/src/Eigenvaleus/RealSchur.h +// +// Copyright (C) 2008 Gael Guennebaud +// Copyright (C) 2010,2012 Jitse Niesen +// Copyright (C) 2021-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_UPPER_HESSENBERG_SCHUR_H +#define SPECTRA_UPPER_HESSENBERG_SCHUR_H + +#include +#include +#include +#include + +#include "../Util/TypeTraits.h" + +namespace Spectra { + +template +class UpperHessenbergSchur +{ +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using Vector2s = Eigen::Matrix; + using Vector3s = Eigen::Matrix; + using GenericMatrix = Eigen::Ref; + using ConstGenericMatrix = const Eigen::Ref; + + Index m_n; // Size of the matrix + Matrix m_T; // T matrix, A = UTU' + Matrix m_U; // U matrix, A = UTU' + bool m_computed; + + // L1 norm of an upper Hessenberg matrix + static Scalar upper_hessenberg_l1_norm(ConstGenericMatrix& x) + { + const Index n = x.cols(); + Scalar norm(0); + for (Index j = 0; j < n; j++) + norm += x.col(j).segment(0, (std::min)(n, j + 2)).cwiseAbs().sum(); + return norm; + } + + // Look for single small sub-diagonal element and returns its index + Index find_small_subdiag(Index iu, const Scalar& near_0) const + { + using std::abs; + + const Scalar eps = Eigen::NumTraits::epsilon(); + Index res = iu; + while (res > 0) + { + Scalar s = abs(m_T.coeff(res - 1, res - 1)) + abs(m_T.coeff(res, res)); + s = Eigen::numext::maxi(s * eps, near_0); + if (abs(m_T.coeff(res, res - 1)) <= s) + break; + res--; + } + + return res; + } + + // Update T given that rows iu-1 and iu decouple from the rest + void split_off_two_rows(Index iu, const Scalar& ex_shift) + { + using std::sqrt; + using std::abs; + + // The eigenvalues of the 2x2 matrix [a b; c d] are + // trace +/- sqrt(discr/4) where discr = tr^2 - 4*det, tr = a + d, det = ad - bc + Scalar p = Scalar(0.5) * (m_T.coeff(iu - 1, iu - 1) - m_T.coeff(iu, iu)); + Scalar q = p * p + m_T.coeff(iu, iu - 1) * m_T.coeff(iu - 1, iu); // q = tr^2 / 4 - det = discr/4 + m_T.coeffRef(iu, iu) += ex_shift; + m_T.coeffRef(iu - 1, iu - 1) += ex_shift; + + if (q >= Scalar(0)) // Two real eigenvalues + { + Scalar z = sqrt(abs(q)); + Eigen::JacobiRotation rot; + rot.makeGivens((p >= Scalar(0)) ? (p + z) : (p - z), m_T.coeff(iu, iu - 1)); + m_T.rightCols(m_n - iu + 1).applyOnTheLeft(iu - 1, iu, rot.adjoint()); + m_T.topRows(iu + 1).applyOnTheRight(iu - 1, iu, rot); + m_T.coeffRef(iu, iu - 1) = Scalar(0); + m_U.applyOnTheRight(iu - 1, iu, rot); + } + if (iu > 1) + m_T.coeffRef(iu - 1, iu - 2) = Scalar(0); + } + + // Form shift in shift_info, and update ex_shift if an exceptional shift is performed + void compute_shift(Index iu, Index iter, Scalar& ex_shift, Vector3s& shift_info) + { + using std::sqrt; + using std::abs; + + shift_info.coeffRef(0) = m_T.coeff(iu, iu); + shift_info.coeffRef(1) = m_T.coeff(iu - 1, iu - 1); + shift_info.coeffRef(2) = m_T.coeff(iu, iu - 1) * m_T.coeff(iu - 1, iu); + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + ex_shift += shift_info.coeff(0); + for (Index i = 0; i <= iu; ++i) + m_T.coeffRef(i, i) -= shift_info.coeff(0); + Scalar s = abs(m_T.coeff(iu, iu - 1)) + abs(m_T.coeff(iu - 1, iu - 2)); + shift_info.coeffRef(0) = Scalar(0.75) * s; + shift_info.coeffRef(1) = Scalar(0.75) * s; + shift_info.coeffRef(2) = Scalar(-0.4375) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + Scalar s = (shift_info.coeff(1) - shift_info.coeff(0)) / Scalar(2); + s = s * s + shift_info.coeff(2); + if (s > Scalar(0)) + { + s = sqrt(s); + if (shift_info.coeff(1) < shift_info.coeff(0)) + s = -s; + s = s + (shift_info.coeff(1) - shift_info.coeff(0)) / Scalar(2); + s = shift_info.coeff(0) - shift_info.coeff(2) / s; + ex_shift += s; + for (Index i = 0; i <= iu; ++i) + m_T.coeffRef(i, i) -= s; + shift_info.setConstant(Scalar(0.964)); + } + } + } + + // Compute index im at which Francis QR step starts and the first Householder vector + void init_francis_qr_step(Index il, Index iu, const Vector3s& shift_info, Index& im, Vector3s& first_householder_vec) const + { + using std::abs; + + const Scalar eps = Eigen::NumTraits::epsilon(); + Vector3s& v = first_householder_vec; // alias to save typing + for (im = iu - 2; im >= il; --im) + { + const Scalar Tmm = m_T.coeff(im, im); + const Scalar r = shift_info.coeff(0) - Tmm; + const Scalar s = shift_info.coeff(1) - Tmm; + v.coeffRef(0) = (r * s - shift_info.coeff(2)) / m_T.coeff(im + 1, im) + m_T.coeff(im, im + 1); + v.coeffRef(1) = m_T.coeff(im + 1, im + 1) - Tmm - r - s; + v.coeffRef(2) = m_T.coeff(im + 2, im + 1); + if (im == il) + break; + const Scalar lhs = m_T.coeff(im, im - 1) * (abs(v.coeff(1)) + abs(v.coeff(2))); + const Scalar rhs = v.coeff(0) * (abs(m_T.coeff(im - 1, im - 1)) + abs(Tmm) + abs(m_T.coeff(im + 1, im + 1))); + if (abs(lhs) < eps * rhs) + break; + } + } + + // P = I - tau * v * v' = P' + // PX = X - tau * v * (v'X), X [3 x c] + static void apply_householder_left(const Vector2s& ess, const Scalar& tau, Scalar* x, Index ncol, Index stride) + { + const Scalar v1 = ess.coeff(0), v2 = ess.coeff(1); + const Scalar* const x_end = x + ncol * stride; + for (; x < x_end; x += stride) + { + const Scalar tvx = tau * (x[0] + v1 * x[1] + v2 * x[2]); + x[0] -= tvx; + x[1] -= tvx * v1; + x[2] -= tvx * v2; + } + } + + // P = I - tau * v * v' = P' + // XP = X - tau * (X * v) * v', X [r x 3] + static void apply_householder_right(const Vector2s& ess, const Scalar& tau, Scalar* x, Index nrow, Index stride) + { + const Scalar v1 = ess.coeff(0), v2 = ess.coeff(1); + Scalar* x0 = x; + Scalar* x1 = x + stride; + Scalar* x2 = x1 + stride; + for (Index i = 0; i < nrow; i++) + { + const Scalar txv = tau * (x0[i] + v1 * x1[i] + v2 * x2[i]); + x0[i] -= txv; + x1[i] -= txv * v1; + x2[i] -= txv * v2; + } + } + + // SIMD version of apply_householder_right() + // Inspired by apply_rotation_in_the_plane_selector() in Eigen/src/Jacobi/Jacobi.h + static void apply_householder_right_simd(const Vector2s& ess, const Scalar& tau, Scalar* x, Index nrow, Index stride) + { + // Packet type + using Eigen::internal::ploadu; + using Eigen::internal::pstoreu; + using Eigen::internal::pset1; + using Eigen::internal::padd; + using Eigen::internal::psub; + using Eigen::internal::pmul; + using Packet = typename Eigen::internal::packet_traits::type; + constexpr unsigned char PacketSize = Eigen::internal::packet_traits::size; + constexpr unsigned char Peeling = 2; + constexpr unsigned char Increment = Peeling * PacketSize; + + // Column heads + Scalar* x0 = x; + Scalar* x1 = x + stride; + Scalar* x2 = x1 + stride; + // Pointers for the current row + Scalar* px0 = x0; + Scalar* px1 = x1; + Scalar* px2 = x2; + + // Householder reflectors + const Scalar v1 = ess.coeff(0), v2 = ess.coeff(1); + // Vectorized versions + const Packet vtau = pset1(tau); + const Packet vv1 = pset1(v1); + const Packet vv2 = pset1(v2); + + // n % (2^k) == n & (2^k-1), see https://stackoverflow.com/q/3072665 + // const Index peeling_end = nrow - nrow % Increment; + const Index aligned_end = nrow - (nrow & (PacketSize - 1)); + const Index peeling_end = nrow - (nrow & (Increment - 1)); + for (Index i = 0; i < peeling_end; i += Increment) + { + Packet vx01 = ploadu(px0); + Packet vx02 = ploadu(px0 + PacketSize); + Packet vx11 = ploadu(px1); + Packet vx12 = ploadu(px1 + PacketSize); + Packet vx21 = ploadu(px2); + Packet vx22 = ploadu(px2 + PacketSize); + + // Packet txv1 = vtau * (vx01 + vv1 * vx11 + vv2 * vx21); + Packet txv1 = pmul(vtau, padd(padd(vx01, pmul(vv1, vx11)), pmul(vv2, vx21))); + Packet txv2 = pmul(vtau, padd(padd(vx02, pmul(vv1, vx12)), pmul(vv2, vx22))); + + pstoreu(px0, psub(vx01, txv1)); + pstoreu(px0 + PacketSize, psub(vx02, txv2)); + pstoreu(px1, psub(vx11, pmul(txv1, vv1))); + pstoreu(px1 + PacketSize, psub(vx12, pmul(txv2, vv1))); + pstoreu(px2, psub(vx21, pmul(txv1, vv2))); + pstoreu(px2 + PacketSize, psub(vx22, pmul(txv2, vv2))); + + px0 += Increment; + px1 += Increment; + px2 += Increment; + } + if (aligned_end != peeling_end) + { + px0 = x0 + peeling_end; + px1 = x1 + peeling_end; + px2 = x2 + peeling_end; + + Packet x0_p = ploadu(px0); + Packet x1_p = ploadu(px1); + Packet x2_p = ploadu(px2); + Packet txv = pmul(vtau, padd(padd(x0_p, pmul(vv1, x1_p)), pmul(vv2, x2_p))); + + pstoreu(px0, psub(x0_p, txv)); + pstoreu(px1, psub(x1_p, pmul(txv, vv1))); + pstoreu(px2, psub(x2_p, pmul(txv, vv2))); + } + + // Remaining rows + for (Index i = aligned_end; i < nrow; i++) + { + const Scalar txv = tau * (x0[i] + v1 * x1[i] + v2 * x2[i]); + x0[i] -= txv; + x1[i] -= txv * v1; + x2[i] -= txv * v2; + } + } + + // Perform a Francis QR step involving rows il:iu and columns im:iu + void perform_francis_qr_step(Index il, Index im, Index iu, const Vector3s& first_householder_vec, const Scalar& near_0) + { + using std::abs; + + for (Index k = im; k <= iu - 2; ++k) + { + const bool first_iter = (k == im); + Vector3s v; + if (first_iter) + v = first_householder_vec; + else + v = m_T.template block<3, 1>(k, k - 1); + + Scalar tau, beta; + Vector2s ess; + v.makeHouseholder(ess, tau, beta); + + if (abs(beta) > near_0) // if v is not zero + { + if (first_iter && k > il) + m_T.coeffRef(k, k - 1) = -m_T.coeff(k, k - 1); + else if (!first_iter) + m_T.coeffRef(k, k - 1) = beta; + + // These Householder transformations form the O(n^3) part of the algorithm + // m_T.block(k, k, 3, m_n - k).applyHouseholderOnTheLeft(ess, tau, workspace); + // m_T.block(0, k, (std::min)(iu, k + 3) + 1, 3).applyHouseholderOnTheRight(ess, tau, workspace); + // m_U.block(0, k, m_n, 3).applyHouseholderOnTheRight(ess, tau, workspace); + apply_householder_left(ess, tau, &m_T.coeffRef(k, k), m_n - k, m_n); + apply_householder_right_simd(ess, tau, &m_T.coeffRef(0, k), (std::min)(iu, k + 3) + 1, m_n); + apply_householder_right_simd(ess, tau, &m_U.coeffRef(0, k), m_n, m_n); + } + } + + // The last 2-row block + Eigen::JacobiRotation rot; + Scalar beta; + rot.makeGivens(m_T.coeff(iu - 1, iu - 2), m_T.coeff(iu, iu - 2), &beta); + + if (abs(beta) > near_0) // if v is not zero + { + m_T.coeffRef(iu - 1, iu - 2) = beta; + m_T.rightCols(m_n - iu + 1).applyOnTheLeft(iu - 1, iu, rot.adjoint()); + m_T.topRows(iu + 1).applyOnTheRight(iu - 1, iu, rot); + m_U.applyOnTheRight(iu - 1, iu, rot); + } + + // clean up pollution due to round-off errors + for (Index i = im + 2; i <= iu; ++i) + { + m_T.coeffRef(i, i - 2) = Scalar(0); + if (i > im + 2) + m_T.coeffRef(i, i - 3) = Scalar(0); + } + } + +public: + UpperHessenbergSchur() : + m_n(0), m_computed(false) + {} + + UpperHessenbergSchur(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_computed(false) + { + compute(mat); + } + + void compute(ConstGenericMatrix& mat) + { + using std::abs; + using std::sqrt; + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("UpperHessenbergSchur: matrix must be square"); + + m_n = mat.rows(); + m_T.resize(m_n, m_n); + m_U.resize(m_n, m_n); + constexpr Index max_iter_per_row = 40; + const Index max_iter = m_n * max_iter_per_row; + + m_T.noalias() = mat; + m_U.setIdentity(); + + // The matrix m_T is divided in three parts. + // Rows 0,...,il-1 are decoupled from the rest because m_T(il,il-1) is zero. + // Rows il,...,iu is the part we are working on (the active window). + // Rows iu+1,...,end are already brought in triangular form. + Index iu = m_n - 1; + Index iter = 0; // iteration count for current eigenvalue + Index total_iter = 0; // iteration count for whole matrix + Scalar ex_shift(0); // sum of exceptional shifts + const Scalar norm = upper_hessenberg_l1_norm(m_T); + // sub-diagonal entries smaller than near_0 will be treated as zero. + // We use eps^2 to enable more precision in small eigenvalues. + const Scalar eps = Eigen::NumTraits::epsilon(); + const Scalar near_0 = Eigen::numext::maxi(norm * eps * eps, TypeTraits::min()); + + if (norm != Scalar(0)) + { + while (iu >= 0) + { + Index il = find_small_subdiag(iu, near_0); + + // Check for convergence + if (il == iu) // One root found + { + m_T.coeffRef(iu, iu) += ex_shift; + if (iu > 0) + m_T.coeffRef(iu, iu - 1) = Scalar(0); + iu--; + iter = 0; + } + else if (il == iu - 1) // Two roots found + { + split_off_two_rows(iu, ex_shift); + iu -= 2; + iter = 0; + } + else // No convergence yet + { + Vector3s first_householder_vec = Vector3s::Zero(), shift_info; + compute_shift(iu, iter, ex_shift, shift_info); + iter++; + total_iter++; + if (total_iter > max_iter) + break; + Index im; + init_francis_qr_step(il, iu, shift_info, im, first_householder_vec); + perform_francis_qr_step(il, im, iu, first_householder_vec, near_0); + } + } + } + + if (total_iter > max_iter) + throw std::runtime_error("UpperHessenbergSchur: Schur decomposition failed"); + + m_computed = true; + } + + const Matrix& matrix_T() const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergSchur: need to call compute() first"); + + return m_T; + } + + const Matrix& matrix_U() const + { + if (!m_computed) + throw std::logic_error("UpperHessenbergSchur: need to call compute() first"); + + return m_U; + } + + void swap_T(Matrix& other) + { + m_T.swap(other); + } + + void swap_U(Matrix& other) + { + m_U.swap(other); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_UPPER_HESSENBERG_SCHUR_H diff --git a/thirdparty/include/Spectra/MatOp/DenseCholesky.h b/thirdparty/include/Spectra/MatOp/DenseCholesky.h new file mode 100644 index 0000000..28af0ee --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseCholesky.h @@ -0,0 +1,125 @@ +// Copyright (C) 2016-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_DENSE_CHOLESKY_H +#define SPECTRA_DENSE_CHOLESKY_H + +#include +#include +#include + +#include "../Util/CompInfo.h" + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the operations related to Cholesky decomposition on a +/// positive definite matrix, \f$B=LL'\f$, where \f$L\f$ is a lower triangular +/// matrix. It is mainly used in the SymGEigsSolver generalized eigen solver +/// in the Cholesky decomposition mode. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseCholesky +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + + const Index m_n; + Eigen::LLT m_decomp; + CompInfo m_info; // status of the decomposition + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseCholesky(const Eigen::MatrixBase& mat) : + m_n(mat.rows()), m_info(CompInfo::NotComputed) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseCholesky: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (m_n != mat.cols()) + throw std::invalid_argument("DenseCholesky: matrix must be square"); + + m_decomp.compute(mat); + m_info = (m_decomp.info() == Eigen::Success) ? + CompInfo::Successful : + CompInfo::NumericalIssue; + } + + /// + /// Returns the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Returns the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Performs the lower triangular solving operation \f$y=L^{-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(L) * x_in + void lower_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixL().solve(x); + } + + /// + /// Performs the upper triangular solving operation \f$y=(L')^{-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(L') * x_in + void upper_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixU().solve(x); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DENSE_CHOLESKY_H diff --git a/thirdparty/include/Spectra/MatOp/DenseGenComplexShiftSolve.h b/thirdparty/include/Spectra/MatOp/DenseGenComplexShiftSolve.h new file mode 100644 index 0000000..70079c2 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseGenComplexShiftSolve.h @@ -0,0 +1,118 @@ +// Copyright (C) 2016-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_DENSE_GEN_COMPLEX_SHIFT_SOLVE_H +#define SPECTRA_DENSE_GEN_COMPLEX_SHIFT_SOLVE_H + +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the complex shift-solve operation on a general real matrix \f$A\f$, +/// i.e., calculating \f$y=\mathrm{Re}\{(A-\sigma I)^{-1}x\}\f$ for any complex-valued +/// \f$\sigma\f$ and real-valued vector \f$x\f$. It is mainly used in the +/// GenEigsComplexShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseGenComplexShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + using Complex = std::complex; + using ComplexMatrix = Eigen::Matrix; + using ComplexVector = Eigen::Matrix; + + using ComplexSolver = Eigen::PartialPivLU; + + ConstGenericMatrix m_mat; + const Index m_n; + ComplexSolver m_solver; + mutable ComplexVector m_x_cache; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseGenComplexShiftSolve(const Eigen::MatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseGenComplexShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("DenseGenComplexShiftSolve: matrix must be square"); + } + + /// + /// 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 complex shift \f$\sigma\f$. + /// + /// \param sigmar Real part of \f$\sigma\f$. + /// \param sigmai Imaginary part of \f$\sigma\f$. + /// + void set_shift(const Scalar& sigmar, const Scalar& sigmai) + { + m_solver.compute(m_mat.template cast() - Complex(sigmar, sigmai) * ComplexMatrix::Identity(m_n, m_n)); + m_x_cache.resize(m_n); + m_x_cache.setZero(); + } + + /// + /// Perform the complex shift-solve operation + /// \f$y=\mathrm{Re}\{(A-\sigma I)^{-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 = Re( inv(A - sigma * I) * x_in ) + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_x_cache.real() = MapConstVec(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(m_x_cache).real(); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DENSE_GEN_COMPLEX_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/DenseGenMatProd.h b/thirdparty/include/Spectra/MatOp/DenseGenMatProd.h new file mode 100644 index 0000000..9e076fb --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseGenMatProd.h @@ -0,0 +1,112 @@ +// Copyright (C) 2016-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_DENSE_GEN_MAT_PROD_H +#define SPECTRA_DENSE_GEN_MAT_PROD_H + +#include + +namespace Spectra { + +/// +/// \defgroup MatOp Matrix Operations +/// +/// Define matrix operations on existing matrix objects +/// + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// general real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the GenEigsSolver and +/// SymEigsSolver eigen solvers. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseGenMatProd +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseGenMatProd(const Eigen::MatrixBase& mat) : + m_mat(mat) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseGenMatProd: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat * x; + } + + /// + /// Perform the matrix-matrix multiplication operation \f$y=Ax\f$. + /// + Matrix operator*(const Eigen::Ref& mat_in) const + { + return m_mat * mat_in; + } + + /// + /// Extract (i,j) element of the underlying matrix. + /// + Scalar operator()(Index i, Index j) const + { + return m_mat(i, j); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DENSE_GEN_MAT_PROD_H diff --git a/thirdparty/include/Spectra/MatOp/DenseGenRealShiftSolve.h b/thirdparty/include/Spectra/MatOp/DenseGenRealShiftSolve.h new file mode 100644 index 0000000..ef051c1 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseGenRealShiftSolve.h @@ -0,0 +1,104 @@ +// Copyright (C) 2016-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_DENSE_GEN_REAL_SHIFT_SOLVE_H +#define SPECTRA_DENSE_GEN_REAL_SHIFT_SOLVE_H + +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a general real matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the GenEigsRealShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseGenRealShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + const Index m_n; + Eigen::PartialPivLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseGenRealShiftSolve(const Eigen::MatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseGenRealShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("DenseGenRealShiftSolve: matrix must be square"); + } + + /// + /// 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) + { + m_solver.compute(m_mat - sigma * Matrix::Identity(m_n, m_n)); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-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 * I) * 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_DENSE_GEN_REAL_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/DenseSymMatProd.h b/thirdparty/include/Spectra/MatOp/DenseSymMatProd.h new file mode 100644 index 0000000..0bdff3a --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseSymMatProd.h @@ -0,0 +1,107 @@ +// Copyright (C) 2016-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_DENSE_SYM_MAT_PROD_H +#define SPECTRA_DENSE_SYM_MAT_PROD_H + +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// symmetric real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the SymEigsSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseSymMatProd +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseSymMatProd(const Eigen::MatrixBase& mat) : + m_mat(mat) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseSymMatProd: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat.template selfadjointView() * x; + } + + /// + /// Perform the matrix-matrix multiplication operation \f$y=Ax\f$. + /// + Matrix operator*(const Eigen::Ref& mat_in) const + { + return m_mat.template selfadjointView() * mat_in; + } + + /// + /// Extract (i,j) element of the underlying matrix. + /// + Scalar operator()(Index i, Index j) const + { + return m_mat(i, j); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_DENSE_SYM_MAT_PROD_H diff --git a/thirdparty/include/Spectra/MatOp/DenseSymShiftSolve.h b/thirdparty/include/Spectra/MatOp/DenseSymShiftSolve.h new file mode 100644 index 0000000..0430f60 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/DenseSymShiftSolve.h @@ -0,0 +1,110 @@ +// Copyright (C) 2016-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_DENSE_SYM_SHIFT_SOLVE_H +#define SPECTRA_DENSE_SYM_SHIFT_SOLVE_H + +#include +#include + +#include "../LinAlg/BKLDLT.h" +#include "../Util/CompInfo.h" + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a real symmetric matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the SymEigsShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// +template +class DenseSymShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + const Index m_n; + BKLDLT m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + template + DenseSymShiftSolve(const Eigen::MatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(Matrix::IsRowMajor), + "DenseSymShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (m_n != mat.cols()) + throw std::invalid_argument("DenseSymShiftSolve: matrix must be square"); + } + + /// + /// 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) + { + m_solver.compute(m_mat, Uplo, sigma); + if (m_solver.info() != CompInfo::Successful) + throw std::invalid_argument("DenseSymShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-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 * I) * 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_DENSE_SYM_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/SparseCholesky.h b/thirdparty/include/Spectra/MatOp/SparseCholesky.h new file mode 100644 index 0000000..3ca2774 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseCholesky.h @@ -0,0 +1,128 @@ +// Copyright (C) 2016-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_SPARSE_CHOLESKY_H +#define SPECTRA_SPARSE_CHOLESKY_H + +#include +#include +#include +#include + +#include "../Util/CompInfo.h" + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the operations related to Cholesky decomposition on a +/// sparse positive definite matrix, \f$B=LL'\f$, where \f$L\f$ is a lower triangular +/// matrix. It is mainly used in the SymGEigsSolver generalized eigen solver +/// in the Cholesky decomposition mode. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseCholesky +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using SparseMatrix = Eigen::SparseMatrix; + + const Index m_n; + Eigen::SimplicialLLT m_decomp; + CompInfo m_info; // status of the decomposition + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseCholesky(const Eigen::SparseMatrixBase& mat) : + m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseCholesky: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("SparseCholesky: matrix must be square"); + + m_decomp.compute(mat); + m_info = (m_decomp.info() == Eigen::Success) ? + CompInfo::Successful : + CompInfo::NumericalIssue; + } + + /// + /// Returns the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Returns the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Performs the lower triangular solving operation \f$y=L^{-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(L) * x_in + void lower_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.permutationP() * x; + m_decomp.matrixL().solveInPlace(y); + } + + /// + /// Performs the upper triangular solving operation \f$y=(L')^{-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(L') * x_in + void upper_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixU().solve(x); + y = m_decomp.permutationPinv() * y; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SPARSE_CHOLESKY_H diff --git a/thirdparty/include/Spectra/MatOp/SparseGenComplexShiftSolve.h b/thirdparty/include/Spectra/MatOp/SparseGenComplexShiftSolve.h new file mode 100644 index 0000000..f99274a --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseGenComplexShiftSolve.h @@ -0,0 +1,124 @@ +// 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_SPARSE_GEN_COMPLEX_SHIFT_SOLVE_H +#define SPECTRA_SPARSE_GEN_COMPLEX_SHIFT_SOLVE_H + +#include +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the complex shift-solve operation on a sparse real matrix \f$A\f$, +/// i.e., calculating \f$y=\mathrm{Re}\{(A-\sigma I)^{-1}x\}\f$ for any complex-valued +/// \f$\sigma\f$ and real-valued vector \f$x\f$. It is mainly used in the +/// GenEigsComplexShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseGenComplexShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + using Complex = std::complex; + using ComplexVector = Eigen::Matrix; + using SparseComplexMatrix = Eigen::SparseMatrix; + + using ComplexSolver = Eigen::SparseLU; + + ConstGenericSparseMatrix m_mat; + const Index m_n; + ComplexSolver m_solver; + mutable ComplexVector m_x_cache; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseGenComplexShiftSolve(const Eigen::SparseMatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseGenComplexShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("SparseGenComplexShiftSolve: matrix must be square"); + } + + /// + /// 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 complex shift \f$\sigma\f$. + /// + /// \param sigmar Real part of \f$\sigma\f$. + /// \param sigmai Imaginary part of \f$\sigma\f$. + /// + void set_shift(const Scalar& sigmar, const Scalar& sigmai) + { + // Create a sparse idendity matrix (1 + 0i on diagonal) + SparseComplexMatrix I(m_n, m_n); + I.setIdentity(); + // Sparse LU decomposition + m_solver.compute(m_mat.template cast() - Complex(sigmar, sigmai) * I); + // Set cache to zero + m_x_cache.resize(m_n); + m_x_cache.setZero(); + } + + /// + /// Perform the complex shift-solve operation + /// \f$y=\mathrm{Re}\{(A-\sigma I)^{-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 = Re( inv(A - sigma * I) * x_in ) + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_x_cache.real() = MapConstVec(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(m_x_cache).real(); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SPARSE_GEN_COMPLEX_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/SparseGenMatProd.h b/thirdparty/include/Spectra/MatOp/SparseGenMatProd.h new file mode 100644 index 0000000..8bbac92 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseGenMatProd.h @@ -0,0 +1,109 @@ +// Copyright (C) 2016-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_SPARSE_GEN_MAT_PROD_H +#define SPECTRA_SPARSE_GEN_MAT_PROD_H + +#include +#include + +namespace Spectra { +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// sparse real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the GenEigsSolver and SymEigsSolver +/// eigen solvers. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseGenMatProd +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using Matrix = Eigen::Matrix; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + const Eigen::Ref> m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseGenMatProd(const Eigen::SparseMatrixBase& mat) : + m_mat(mat) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseGenMatProd: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + Eigen::VectorXd t = m_mat * x; + Eigen::VectorXd t2 = t.transpose()*m_mat; + y.noalias() = t2; + } + + /// + /// Perform the matrix-matrix multiplication operation \f$y=Ax\f$. + /// + Matrix operator*(const Eigen::Ref& mat_in) const + { + return m_mat * mat_in; + } + + /// + /// Extract (i,j) element of the underlying matrix. + /// + Scalar operator()(Index i, Index j) const + { + return m_mat.coeff(i, j); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SPARSE_GEN_MAT_PROD_H diff --git a/thirdparty/include/Spectra/MatOp/SparseGenRealShiftSolve.h b/thirdparty/include/Spectra/MatOp/SparseGenRealShiftSolve.h new file mode 100644 index 0000000..ba2f64c --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseGenRealShiftSolve.h @@ -0,0 +1,110 @@ +// Copyright (C) 2016-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_SPARSE_GEN_REAL_SHIFT_SOLVE_H +#define SPECTRA_SPARSE_GEN_REAL_SHIFT_SOLVE_H + +#include +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a sparse real matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the GenEigsRealShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseGenRealShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + ConstGenericSparseMatrix m_mat; + const Index m_n; + Eigen::SparseLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseGenRealShiftSolve(const Eigen::SparseMatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseGenRealShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("SparseGenRealShiftSolve: matrix must be square"); + } + + /// + /// 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) + { + SparseMatrix I(m_n, m_n); + I.setIdentity(); + + m_solver.compute(m_mat - sigma * I); + if (m_solver.info() != Eigen::Success) + throw std::invalid_argument("SparseGenRealShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-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 * I) * 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_SPARSE_GEN_REAL_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/SparseRegularInverse.h b/thirdparty/include/Spectra/MatOp/SparseRegularInverse.h new file mode 100644 index 0000000..5aeabda --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseRegularInverse.h @@ -0,0 +1,135 @@ +// Copyright (C) 2017-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_SPARSE_REGULAR_INVERSE_H +#define SPECTRA_SPARSE_REGULAR_INVERSE_H + +#include +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines matrix operations required by the generalized eigen solver +/// in the regular inverse mode. For a sparse and positive definite matrix \f$B\f$, +/// it implements the matrix-vector product \f$y=Bx\f$ and the linear equation +/// solving operation \f$y=B^{-1}x\f$. +/// +/// This class is intended to be used with the SymGEigsSolver generalized eigen solver +/// in the regular inverse mode. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseRegularInverse +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + ConstGenericSparseMatrix m_mat; + const Index m_n; + Eigen::ConjugateGradient m_cg; + mutable CompInfo m_info; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseRegularInverse(const Eigen::SparseMatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseRegularInverse: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("SparseRegularInverse: matrix must be square"); + + m_cg.compute(mat); + m_info = (m_cg.info() == Eigen::Success) ? + CompInfo::Successful : + CompInfo::NumericalIssue; + } + + /// + /// 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; } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Perform the solving operation \f$y=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(B) * x_in + void solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_cg.solve(x); + + m_info = (m_cg.info() == Eigen::Success) ? + CompInfo::Successful : + CompInfo::NotConverging; + if (m_info != CompInfo::Successful) + throw std::runtime_error("SparseRegularInverse: CG solver does not converge"); + } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Bx\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = 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_mat.template selfadjointView() * x; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SPARSE_REGULAR_INVERSE_H diff --git a/thirdparty/include/Spectra/MatOp/SparseSymMatProd.h b/thirdparty/include/Spectra/MatOp/SparseSymMatProd.h new file mode 100644 index 0000000..132e59f --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseSymMatProd.h @@ -0,0 +1,108 @@ +// Copyright (C) 2016-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_SPARSE_SYM_MAT_PROD_H +#define SPECTRA_SPARSE_SYM_MAT_PROD_H + +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// sparse real symmetric matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the SymEigsSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseSymMatProd +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using Matrix = Eigen::Matrix; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + ConstGenericSparseMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseSymMatProd(const Eigen::SparseMatrixBase& mat) : + m_mat(mat) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseSymMatProd: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat.template selfadjointView() * x; + } + + /// + /// Perform the matrix-matrix multiplication operation \f$y=Ax\f$. + /// + Matrix operator*(const Eigen::Ref& mat_in) const + { + return m_mat.template selfadjointView() * mat_in; + } + + /// + /// Extract (i,j) element of the underlying matrix. + /// + Scalar operator()(Index i, Index j) const + { + return m_mat.coeff(i, j); + } +}; +} // namespace Spectra + +#endif // SPECTRA_SPARSE_SYM_MAT_PROD_H diff --git a/thirdparty/include/Spectra/MatOp/SparseSymShiftSolve.h b/thirdparty/include/Spectra/MatOp/SparseSymShiftSolve.h new file mode 100644 index 0000000..3206172 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SparseSymShiftSolve.h @@ -0,0 +1,114 @@ +// Copyright (C) 2016-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_SPARSE_SYM_SHIFT_SOLVE_H +#define SPECTRA_SPARSE_SYM_SHIFT_SOLVE_H + +#include +#include +#include +#include + +namespace Spectra { + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a sparse real symmetric matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the SymEigsShiftSolver eigen solver. +/// +/// \tparam Scalar_ The element type of the matrix, for example, +/// `float`, `double`, and `long double`. +/// \tparam Uplo Either `Eigen::Lower` or `Eigen::Upper`, indicating which +/// triangular part of the matrix is used. +/// \tparam Flags Either `Eigen::ColMajor` or `Eigen::RowMajor`, indicating +/// the storage format of the input matrix. +/// \tparam StorageIndex The type of the indices for the sparse matrix. +/// +template +class SparseSymShiftSolve +{ +public: + /// + /// Element type of the matrix. + /// + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using SparseMatrix = Eigen::SparseMatrix; + using ConstGenericSparseMatrix = const Eigen::Ref; + + ConstGenericSparseMatrix m_mat; + const Index m_n; + Eigen::SparseLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + template + SparseSymShiftSolve(const Eigen::SparseMatrixBase& mat) : + m_mat(mat), m_n(mat.rows()) + { + static_assert( + static_cast(Derived::PlainObject::IsRowMajor) == static_cast(SparseMatrix::IsRowMajor), + "SparseSymShiftSolve: the \"Flags\" template parameter does not match the input matrix (Eigen::ColMajor/Eigen::RowMajor)"); + + if (mat.rows() != mat.cols()) + throw std::invalid_argument("SparseSymShiftSolve: matrix must be square"); + } + + /// + /// 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) + { + SparseMatrix mat = m_mat.template selfadjointView(); + SparseMatrix identity(m_n, m_n); + identity.setIdentity(); + mat = mat - sigma * identity; + m_solver.isSymmetric(true); + m_solver.compute(mat); + if (m_solver.info() != Eigen::Success) + throw std::invalid_argument("SparseSymShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-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 * I) * 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_SPARSE_SYM_SHIFT_SOLVE_H diff --git a/thirdparty/include/Spectra/MatOp/SymShiftInvert.h b/thirdparty/include/Spectra/MatOp/SymShiftInvert.h new file mode 100644 index 0000000..e7c1daa --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/SymShiftInvert.h @@ -0,0 +1,245 @@ +// 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 diff --git a/thirdparty/include/Spectra/MatOp/internal/ArnoldiOp.h b/thirdparty/include/Spectra/MatOp/internal/ArnoldiOp.h new file mode 100644 index 0000000..7468de5 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/ArnoldiOp.h @@ -0,0 +1,158 @@ +// Copyright (C) 2018-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_ARNOLDI_OP_H +#define SPECTRA_ARNOLDI_OP_H + +#include +#include // std::sqrt + +namespace Spectra { + +/// +/// \ingroup Internals +/// @{ +/// + +/// +/// \defgroup Operators Operators +/// +/// Different types of operators. +/// + +/// +/// \ingroup Operators +/// +/// Operators used in the Arnoldi factorization. +/// +template +class ArnoldiOp +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + const OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; + +public: + ArnoldiOp(const OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + // Move constructor + ArnoldiOp(ArnoldiOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + inline Index rows() const { return m_op.rows(); } + + // In generalized eigenvalue problem Ax=lambda*Bx, define the inner product to be = x'By. + // For regular eigenvalue problems, it is the usual inner product = x'y + + // Compute = x'By + // x and y are two vectors + template + Scalar inner_product(const Arg1& x, const Arg2& y) const + { + m_Bop.perform_op(y.data(), m_cache.data()); + return x.dot(m_cache); + } + + // Compute res = = X'By + // X is a matrix, y is a vector, res is a vector + template + void trans_product(const Arg1& x, const Arg2& y, Eigen::Ref res) const + { + m_Bop.perform_op(y.data(), m_cache.data()); + res.noalias() = x.transpose() * m_cache; + } + + // B-norm of a vector, ||x||_B = sqrt(x'Bx) + template + Scalar norm(const Arg& x) const + { + using std::sqrt; + return sqrt(inner_product(x, x)); + } + + // The "A" operator to generate the Krylov subspace + inline void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_op.perform_op(x_in, y_out); + } +}; + +/// +/// \ingroup Operators +/// +/// Placeholder for the B-operator when \f$B = I\f$. +/// +class IdentityBOp +{}; + +/// +/// \ingroup Operators +/// +/// Partial specialization for the case \f$B = I\f$. +/// +template +class ArnoldiOp +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + const OpType& m_op; + +public: + ArnoldiOp(const OpType& op, const IdentityBOp& /*Bop*/) : + m_op(op) + {} + + inline Index rows() const { return m_op.rows(); } + + // Compute = x'y + // x and y are two vectors + template + Scalar inner_product(const Arg1& x, const Arg2& y) const + { + return x.dot(y); + } + + // Compute res = = X'y + // X is a matrix, y is a vector, res is a vector + template + void trans_product(const Arg1& x, const Arg2& y, Eigen::Ref res) const + { + res.noalias() = x.transpose() * y; + } + + // B-norm of a vector. For regular eigenvalue problems it is simply the L2 norm + template + Scalar norm(const Arg& x) const + { + return x.norm(); + } + + // The "A" operator to generate the Krylov subspace + inline void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_op.perform_op(x_in, y_out); + } +}; + +/// +/// @} +/// + +} // namespace Spectra + +#endif // SPECTRA_ARNOLDI_OP_H diff --git a/thirdparty/include/Spectra/MatOp/internal/SymGEigsBucklingOp.h b/thirdparty/include/Spectra/MatOp/internal/SymGEigsBucklingOp.h new file mode 100644 index 0000000..9488960 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/SymGEigsBucklingOp.h @@ -0,0 +1,95 @@ +// 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_GEIGS_BUCKLING_OP_H +#define SPECTRA_SYM_GEIGS_BUCKLING_OP_H + +#include + +#include "../SymShiftInvert.h" +#include "../SparseSymMatProd.h" + +namespace Spectra { + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// buckling mode. It computes \f$y=(K-\sigma K_G)^{-1}Kx\f$ for any +/// vector \f$x\f$, where \f$K\f$ is positive definite, \f$K_G\f$ is symmetric, +/// and \f$\sigma\f$ is a real shift. +/// This class is intended for internal use. +/// +template , + typename BOpType = SparseSymMatProd> +class SymGEigsBucklingOp +{ +public: + using Scalar = typename OpType::Scalar; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op The \f$(K-\sigma K_G)^{-1}\f$ matrix operation object. + /// \param Bop The \f$K\f$ matrix operation object. + /// + SymGEigsBucklingOp(OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Move constructor. + /// + SymGEigsBucklingOp(SymGEigsBucklingOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_op.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_op.rows(); } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(const Scalar& sigma) + { + m_op.set_shift(sigma); + } + + /// + /// Perform the matrix operation \f$y=(K-\sigma K_G)^{-1}Kx\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(K - sigma * K_G) * K * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_Bop.perform_op(x_in, m_cache.data()); + m_op.perform_op(m_cache.data(), y_out); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_BUCKLING_OP_H diff --git a/thirdparty/include/Spectra/MatOp/internal/SymGEigsCayleyOp.h b/thirdparty/include/Spectra/MatOp/internal/SymGEigsCayleyOp.h new file mode 100644 index 0000000..f9228db --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/SymGEigsCayleyOp.h @@ -0,0 +1,105 @@ +// 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_GEIGS_CAYLEY_OP_H +#define SPECTRA_SYM_GEIGS_CAYLEY_OP_H + +#include + +#include "../SymShiftInvert.h" +#include "../SparseSymMatProd.h" + +namespace Spectra { + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// Cayley mode. It computes \f$y=(A-\sigma B)^{-1}(A+\sigma B)x\f$ for any +/// vector \f$x\f$, where \f$A\f$ is a symmetric matrix, \f$B\f$ is positive definite, +/// and \f$\sigma\f$ is a real shift. +/// This class is intended for internal use. +/// +template , + typename BOpType = SparseSymMatProd> +class SymGEigsCayleyOp +{ +public: + using Scalar = typename OpType::Scalar; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + + OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; // temporary working space + Scalar m_sigma; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op The \f$(A-\sigma B)^{-1}\f$ matrix operation object. + /// \param Bop The \f$B\f$ matrix operation object. + /// + SymGEigsCayleyOp(OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Move constructor. + /// + SymGEigsCayleyOp(SymGEigsCayleyOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop), m_sigma(other.m_sigma) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_op.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_op.rows(); } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(const Scalar& sigma) + { + m_op.set_shift(sigma); + m_sigma = sigma; + } + + /// + /// Perform the matrix operation \f$y=(A-\sigma B)^{-1}(A+\sigma B)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) * (A + sigma * B) * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + // inv(A - sigma * B) * (A + sigma * B) * x + // = inv(A - sigma * B) * (A - sigma * B + 2 * sigma * B) * x + // = x + 2 * sigma * inv(A - sigma * B) * B * x + m_Bop.perform_op(x_in, m_cache.data()); + m_op.perform_op(m_cache.data(), y_out); + MapConstVec x(x_in, this->rows()); + MapVec y(y_out, this->rows()); + y.noalias() = x + (Scalar(2) * m_sigma) * y; + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_CAYLEY_OP_H diff --git a/thirdparty/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h b/thirdparty/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h new file mode 100644 index 0000000..412f33d --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h @@ -0,0 +1,87 @@ +// Copyright (C) 2016-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_GEIGS_CHOLESKY_OP_H +#define SPECTRA_SYM_GEIGS_CHOLESKY_OP_H + +#include + +#include "../DenseSymMatProd.h" +#include "../DenseCholesky.h" + +namespace Spectra { + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// Cholesky decomposition mode. It calculates \f$y=L^{-1}A(L')^{-1}x\f$ for any +/// vector \f$x\f$, where \f$L\f$ is the Cholesky decomposition of \f$B\f$. +/// This class is intended for internal use. +/// +template , + typename BOpType = DenseCholesky> +class SymGEigsCholeskyOp +{ +public: + using Scalar = typename OpType::Scalar; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + const OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op The \f$A\f$ matrix operation object. + /// \param Bop The \f$B\f$ matrix operation object. + /// + SymGEigsCholeskyOp(const OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Move constructor. + /// + SymGEigsCholeskyOp(SymGEigsCholeskyOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_Bop.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_Bop.rows(); } + + /// + /// Perform the matrix operation \f$y=L^{-1}A(L')^{-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(L) * A * inv(L') * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_Bop.upper_triangular_solve(x_in, y_out); + m_op.perform_op(y_out, m_cache.data()); + m_Bop.lower_triangular_solve(m_cache.data(), y_out); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_CHOLESKY_OP_H diff --git a/thirdparty/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h b/thirdparty/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h new file mode 100644 index 0000000..40c983e --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h @@ -0,0 +1,84 @@ +// Copyright (C) 2017-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_GEIGS_REG_INV_OP_H +#define SPECTRA_SYM_GEIGS_REG_INV_OP_H + +#include + +#include "../SparseSymMatProd.h" +#include "../SparseRegularInverse.h" + +namespace Spectra { + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// regular inverse mode. This class is intended for internal use. +/// +template , + typename BOpType = SparseRegularInverse> +class SymGEigsRegInvOp +{ +public: + using Scalar = typename OpType::Scalar; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + const OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op The \f$A\f$ matrix operation object. + /// \param Bop The \f$B\f$ matrix operation object. + /// + SymGEigsRegInvOp(const OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Move constructor. + /// + SymGEigsRegInvOp(SymGEigsRegInvOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_Bop.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_Bop.rows(); } + + /// + /// Perform the matrix operation \f$y=B^{-1}Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(B) * A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_op.perform_op(x_in, m_cache.data()); + m_Bop.solve(m_cache.data(), y_out); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_REG_INV_OP_H diff --git a/thirdparty/include/Spectra/MatOp/internal/SymGEigsShiftInvertOp.h b/thirdparty/include/Spectra/MatOp/internal/SymGEigsShiftInvertOp.h new file mode 100644 index 0000000..d1e31f9 --- /dev/null +++ b/thirdparty/include/Spectra/MatOp/internal/SymGEigsShiftInvertOp.h @@ -0,0 +1,95 @@ +// 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_GEIGS_SHIFT_INVERT_OP_H +#define SPECTRA_SYM_GEIGS_SHIFT_INVERT_OP_H + +#include + +#include "../SymShiftInvert.h" +#include "../SparseSymMatProd.h" + +namespace Spectra { + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// shift-and-invert mode. It computes \f$y=(A-\sigma B)^{-1}Bx\f$ for any +/// vector \f$x\f$, where \f$A\f$ is a symmetric matrix, \f$B\f$ is positive definite, +/// and \f$\sigma\f$ is a real shift. +/// This class is intended for internal use. +/// +template , + typename BOpType = SparseSymMatProd> +class SymGEigsShiftInvertOp +{ +public: + using Scalar = typename OpType::Scalar; + +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + OpType& m_op; + const BOpType& m_Bop; + mutable Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op The \f$(A-\sigma B)^{-1}\f$ matrix operation object. + /// \param Bop The \f$B\f$ matrix operation object. + /// + SymGEigsShiftInvertOp(OpType& op, const BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Move constructor. + /// + SymGEigsShiftInvertOp(SymGEigsShiftInvertOp&& other) : + m_op(other.m_op), m_Bop(other.m_Bop) + { + // We emulate the move constructor for Vector using Vector::swap() + m_cache.swap(other.m_cache); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_op.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_op.rows(); } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(const Scalar& sigma) + { + m_op.set_shift(sigma); + } + + /// + /// Perform the matrix operation \f$y=(A-\sigma B)^{-1}Bx\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) * B * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + m_Bop.perform_op(x_in, m_cache.data()); + m_op.perform_op(m_cache.data(), y_out); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_SHIFT_INVERT_OP_H diff --git a/thirdparty/include/Spectra/SymEigsBase.h b/thirdparty/include/Spectra/SymEigsBase.h new file mode 100644 index 0000000..6357248 --- /dev/null +++ b/thirdparty/include/Spectra/SymEigsBase.h @@ -0,0 +1,453 @@ +// Copyright (C) 2018-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_EIGS_BASE_H +#define SPECTRA_SYM_EIGS_BASE_H + +#include +#include // std::vector +#include // std::abs, std::pow +#include // std::min +#include // std::invalid_argument +#include // std::move + +#include "Util/Version.h" +#include "Util/TypeTraits.h" +#include "Util/SelectionRule.h" +#include "Util/CompInfo.h" +#include "Util/SimpleRandom.h" +#include "MatOp/internal/ArnoldiOp.h" +#include "LinAlg/UpperHessenbergQR.h" +#include "LinAlg/TridiagEigen.h" +#include "LinAlg/Lanczos.h" + +namespace Spectra { + +/// +/// \defgroup EigenSolver Eigen Solvers +/// +/// Eigen solvers for different types of problems. +/// + +/// +/// \ingroup EigenSolver +/// +/// This is the base class for symmetric eigen solvers, mainly for internal use. +/// It is kept here to provide the documentation for member functions of concrete eigen solvers +/// such as SymEigsSolver and SymEigsShiftSolver. +/// +template +class SymEigsBase +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using Array = Eigen::Array; + using BoolArray = Eigen::Array; + using MapMat = Eigen::Map; + using MapVec = Eigen::Map; + using MapConstVec = Eigen::Map; + + using ArnoldiOpType = ArnoldiOp; + using LanczosFac = Lanczos; + +protected: + // clang-format off + + // In SymEigsSolver and SymEigsShiftSolver, the A operator is an lvalue provided by + // the user. In SymGEigsSolver, the A operator is an rvalue. To avoid copying objects, + // we use the following scheme: + // 1. If the op parameter in the constructor is an lvalue, make m_op a const reference to op + // 2. If op is an rvalue, move op to m_op_container, and then make m_op a const + // reference to m_op_container[0] + std::vector m_op_container; + const OpType& m_op; // matrix operator for A + const Index m_n; // dimension of matrix A + const Index m_nev; // number of eigenvalues requested + const Index m_ncv; // dimension of Krylov subspace in the Lanczos method + Index m_nmatop; // number of matrix operations called + Index m_niter; // number of restarting iterations + + LanczosFac m_fac; // Lanczos factorization + Vector m_ritz_val; // Ritz values + +private: + Matrix m_ritz_vec; // Ritz vectors + Vector m_ritz_est; // last row of m_ritz_vec, also called the Ritz estimates + BoolArray m_ritz_conv; // indicator of the convergence of Ritz values + CompInfo m_info; // status of the computation + // clang-format on + + // Move rvalue object to the container + static std::vector create_op_container(OpType&& rval) + { + std::vector container; + container.emplace_back(std::move(rval)); + return container; + } + + // Implicitly restarted Lanczos factorization + void restart(Index k, SortRule selection) + { + using std::abs; + + if (k >= m_ncv) + return; + + TridiagQR decomp(m_ncv); + Matrix Q = Matrix::Identity(m_ncv, m_ncv); + + // Apply large shifts first + const int nshift = m_ncv - k; + Vector shifts = m_ritz_val.tail(nshift); + std::sort(shifts.data(), shifts.data() + nshift, [](const Scalar& v1, const Scalar& v2) { return abs(v1) > abs(v2); }); + + for (Index i = 0; i < nshift; i++) + { + // QR decomposition of H-mu*I, mu is the shift + decomp.compute(m_fac.matrix_H(), shifts[i]); + + // Q -> Q * Qi + decomp.apply_YQ(Q); + // H -> Q'HQ + // Since QR = H - mu * I, we have H = QR + mu * I + // and therefore Q'HQ = RQ + mu * I + m_fac.compress_H(decomp); + } + + m_fac.compress_V(Q); + m_fac.factorize_from(k, m_ncv, m_nmatop); + + retrieve_ritzpair(selection); + } + + // Calculates the number of converged Ritz values + Index num_converged(const Scalar& tol) + { + using std::pow; + + // The machine precision, ~= 1e-16 for the "double" type + constexpr Scalar eps = TypeTraits::epsilon(); + // std::pow() is not constexpr, so we do not declare eps23 to be constexpr + // But most compilers should be able to compute eps23 at compile time + const Scalar eps23 = pow(eps, Scalar(2) / 3); + + // thresh = tol * max(eps23, abs(theta)), theta for Ritz value + Array thresh = tol * m_ritz_val.head(m_nev).array().abs().max(eps23); + Array resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm(); + // Converged "wanted" Ritz values + m_ritz_conv = (resid < thresh); + + return m_ritz_conv.count(); + } + + // Returns the adjusted nev for restarting + Index nev_adjusted(Index nconv) + { + using std::abs; + + // A very small value, but 1.0 / near_0 does not overflow + // ~= 1e-307 for the "double" type + constexpr Scalar near_0 = TypeTraits::min() * Scalar(10); + + Index nev_new = m_nev; + for (Index i = m_nev; i < m_ncv; i++) + if (abs(m_ritz_est[i]) < near_0) + nev_new++; + + // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK + nev_new += (std::min)(nconv, (m_ncv - nev_new) / 2); + if (nev_new == 1 && m_ncv >= 6) + nev_new = m_ncv / 2; + else if (nev_new == 1 && m_ncv > 2) + nev_new = 2; + + if (nev_new > m_ncv - 1) + nev_new = m_ncv - 1; + + return nev_new; + } + + // Retrieves and sorts Ritz values and Ritz vectors + void retrieve_ritzpair(SortRule selection) + { + TridiagEigen decomp(m_fac.matrix_H()); + const Vector& evals = decomp.eigenvalues(); + const Matrix& evecs = decomp.eigenvectors(); + + // Sort Ritz values and put the wanted ones at the beginning + std::vector ind = argsort(selection, evals, m_ncv); + + // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively + for (Index i = 0; i < m_ncv; i++) + { + m_ritz_val[i] = evals[ind[i]]; + m_ritz_est[i] = evecs(m_ncv - 1, ind[i]); + } + for (Index i = 0; i < m_nev; i++) + { + m_ritz_vec.col(i).noalias() = evecs.col(ind[i]); + } + } + +protected: + // Sorts the first nev Ritz pairs in the specified order + // This is used to return the final results + virtual void sort_ritzpair(SortRule sort_rule) + { + if ((sort_rule != SortRule::LargestAlge) && (sort_rule != SortRule::LargestMagn) && + (sort_rule != SortRule::SmallestAlge) && (sort_rule != SortRule::SmallestMagn)) + throw std::invalid_argument("unsupported sorting rule"); + + std::vector ind = argsort(sort_rule, m_ritz_val, m_nev); + + Vector new_ritz_val(m_ncv); + Matrix new_ritz_vec(m_ncv, m_nev); + BoolArray new_ritz_conv(m_nev); + + for (Index i = 0; i < m_nev; i++) + { + new_ritz_val[i] = m_ritz_val[ind[i]]; + new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]); + new_ritz_conv[i] = m_ritz_conv[ind[i]]; + } + + m_ritz_val.swap(new_ritz_val); + m_ritz_vec.swap(new_ritz_vec); + m_ritz_conv.swap(new_ritz_conv); + } + +public: + /// \cond + + // If op is an lvalue + SymEigsBase(OpType& op, const BOpType& Bop, Index nev, Index ncv) : + m_op(op), + m_n(op.rows()), + m_nev(nev), + m_ncv(ncv > m_n ? m_n : ncv), + m_nmatop(0), + m_niter(0), + m_fac(ArnoldiOpType(op, Bop), m_ncv), + m_info(CompInfo::NotComputed) + { + if (nev < 1 || nev > m_n - 1) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix"); + + if (ncv <= nev || ncv > m_n) + throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix"); + } + + // If op is an rvalue + SymEigsBase(OpType&& op, const BOpType& Bop, Index nev, Index ncv) : + m_op_container(create_op_container(std::move(op))), + m_op(m_op_container.front()), + m_n(m_op.rows()), + m_nev(nev), + m_ncv(ncv > m_n ? m_n : ncv), + m_nmatop(0), + m_niter(0), + m_fac(ArnoldiOpType(m_op, Bop), m_ncv), + m_info(CompInfo::NotComputed) + { + if (nev < 1 || nev > m_n - 1) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix"); + + if (ncv <= nev || ncv > m_n) + throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix"); + } + + /// + /// Virtual destructor + /// + virtual ~SymEigsBase() {} + + /// \endcond + + /// + /// Initializes the solver by providing an initial residual vector. + /// + /// \param init_resid Pointer to the initial residual vector. + /// + /// **Spectra** (and also **ARPACK**) uses an iterative algorithm + /// to find eigenvalues. This function allows the user to provide the initial + /// residual vector. + /// + void init(const Scalar* init_resid) + { + // Reset all matrices/vectors to zero + m_ritz_val.resize(m_ncv); + m_ritz_vec.resize(m_ncv, m_nev); + m_ritz_est.resize(m_ncv); + m_ritz_conv.resize(m_nev); + + m_ritz_val.setZero(); + m_ritz_vec.setZero(); + m_ritz_est.setZero(); + m_ritz_conv.setZero(); + + m_nmatop = 0; + m_niter = 0; + + // Initialize the Lanczos factorization + MapConstVec v0(init_resid, m_n); + m_fac.init(v0, m_nmatop); + } + + /// + /// Initializes the solver by providing a random initial residual vector. + /// + /// This overloaded function generates a random initial residual vector + /// (with a fixed random seed) for the algorithm. Elements in the vector + /// follow independent Uniform(-0.5, 0.5) distribution. + /// + void init() + { + SimpleRandom rng(0); + Vector init_resid = rng.random_vec(m_n); + init(init_resid.data()); + } + + /// + /// Conducts the major computation procedure. + /// + /// \param selection An enumeration value indicating the selection rule of + /// the requested eigenvalues, for example `SortRule::LargestMagn` + /// to retrieve eigenvalues with the largest magnitude. + /// The full list of enumeration values can be found in + /// \ref Enumerations. + /// \param maxit Maximum number of iterations allowed in the algorithm. + /// \param tol Precision parameter for the calculated eigenvalues. + /// \param sorting Rule to sort the eigenvalues and eigenvectors. + /// Supported values are + /// `SortRule::LargestAlge`, `SortRule::LargestMagn`, + /// `SortRule::SmallestAlge`, and `SortRule::SmallestMagn`. + /// For example, `SortRule::LargestAlge` indicates that largest eigenvalues + /// come first. Note that this argument is only used to + /// **sort** the final result, and the **selection** rule + /// (e.g. selecting the largest or smallest eigenvalues in the + /// full spectrum) is specified by the parameter `selection`. + /// + /// \return Number of converged eigenvalues. + /// + Index compute(SortRule selection = SortRule::LargestMagn, Index maxit = 1000, + Scalar tol = 1e-10, SortRule sorting = SortRule::LargestAlge) + { + // The m-step Lanczos factorization + m_fac.factorize_from(1, m_ncv, m_nmatop); + retrieve_ritzpair(selection); + // Restarting + Index i, nconv = 0, nev_adj; + for (i = 0; i < maxit; i++) + { + nconv = num_converged(tol); + if (nconv >= m_nev) + break; + + nev_adj = nev_adjusted(nconv); + restart(nev_adj, selection); + } + // Sorting results + sort_ritzpair(sorting); + + m_niter += i + 1; + m_info = (nconv >= m_nev) ? CompInfo::Successful : CompInfo::NotConverging; + + return (std::min)(m_nev, nconv); + } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + CompInfo info() const { return m_info; } + + /// + /// Returns the number of iterations used in the computation. + /// + Index num_iterations() const { return m_niter; } + + /// + /// Returns the number of matrix operations used in the computation. + /// + Index num_operations() const { return m_nmatop; } + + /// + /// Returns the converged eigenvalues. + /// + /// \return A vector containing the eigenvalues. + /// Returned vector type will be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + Vector eigenvalues() const + { + const Index nconv = m_ritz_conv.count(); + Vector res(nconv); + + if (!nconv) + return res; + + Index j = 0; + for (Index i = 0; i < m_nev; i++) + { + if (m_ritz_conv[i]) + { + res[j] = m_ritz_val[i]; + j++; + } + } + + return res; + } + + /// + /// Returns the eigenvectors associated with the converged eigenvalues. + /// + /// \param nvec The number of eigenvectors to return. + /// + /// \return A matrix containing the eigenvectors. + /// Returned matrix type will be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + virtual Matrix eigenvectors(Index nvec) const + { + const Index nconv = m_ritz_conv.count(); + nvec = (std::min)(nvec, nconv); + Matrix res(m_n, nvec); + + if (!nvec) + return res; + + Matrix ritz_vec_conv(m_ncv, nvec); + Index j = 0; + for (Index i = 0; i < m_nev && j < nvec; i++) + { + if (m_ritz_conv[i]) + { + ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i); + j++; + } + } + + res.noalias() = m_fac.matrix_V() * ritz_vec_conv; + + return res; + } + + /// + /// Returns all converged eigenvectors. + /// + virtual Matrix eigenvectors() const + { + return eigenvectors(m_nev); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_EIGS_BASE_H diff --git a/thirdparty/include/Spectra/SymEigsShiftSolver.h b/thirdparty/include/Spectra/SymEigsShiftSolver.h new file mode 100644 index 0000000..a2bfea9 --- /dev/null +++ b/thirdparty/include/Spectra/SymEigsShiftSolver.h @@ -0,0 +1,200 @@ +// Copyright (C) 2016-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_EIGS_SHIFT_SOLVER_H +#define SPECTRA_SYM_EIGS_SHIFT_SOLVER_H + +#include + +#include "SymEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseSymShiftSolve.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for real symmetric matrices using +/// the **shift-and-invert mode**. The background information of the symmetric +/// eigen solver is documented in the SymEigsSolver class. Here we focus on +/// explaining the shift-and-invert mode. +/// +/// The shift-and-invert mode is based on the following fact: +/// If \f$\lambda\f$ and \f$x\f$ are a pair of eigenvalue and eigenvector of +/// matrix \f$A\f$, such that \f$Ax=\lambda x\f$, then for any \f$\sigma\f$, +/// we have +/// \f[(A-\sigma I)^{-1}x=\nu x\f] +/// where +/// \f[\nu=\frac{1}{\lambda-\sigma}\f] +/// which indicates that \f$(\nu, x)\f$ is an eigenpair of the matrix +/// \f$(A-\sigma I)^{-1}\f$. +/// +/// Therefore, if we pass the matrix operation \f$(A-\sigma I)^{-1}y\f$ +/// (rather than \f$Ay\f$) to the eigen solver, then we would get the desired +/// values of \f$\nu\f$, and \f$\lambda\f$ can also be easily obtained by noting +/// that \f$\lambda=\sigma+\nu^{-1}\f$. +/// +/// The reason why we need this type of manipulation is that +/// the algorithm of **Spectra** (and also **ARPACK**) +/// is good at finding eigenvalues with large magnitude, but may fail in looking +/// for eigenvalues that are close to zero. However, if we really need them, we +/// can set \f$\sigma=0\f$, find the largest eigenvalues of \f$A^{-1}\f$, and then +/// transform back to \f$\lambda\f$, since in this case largest values of \f$\nu\f$ +/// implies smallest values of \f$\lambda\f$. +/// +/// To summarize, in the shift-and-invert mode, the selection rule will apply to +/// \f$\nu=1/(\lambda-\sigma)\f$ rather than \f$\lambda\f$. So a selection rule +/// of `LARGEST_MAGN` combined with shift \f$\sigma\f$ will find eigenvalues of +/// \f$A\f$ that are closest to \f$\sigma\f$. But note that the eigenvalues() +/// method will always return the eigenvalues in the original problem (i.e., +/// returning \f$\lambda\f$ rather than \f$\nu\f$), and eigenvectors are the +/// same for both the original problem and the shifted-and-inverted problem. +/// +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseSymShiftSolve and +/// SparseSymShiftSolve, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseSymShiftSolve. +/// +/// Below is an example that illustrates the use of the shift-and-invert mode: +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // A size-10 diagonal matrix with elements 1, 2, ..., 10 +/// Eigen::MatrixXd M = Eigen::MatrixXd::Zero(10, 10); +/// for (int i = 0; i < M.rows(); i++) +/// M(i, i) = i + 1; +/// +/// // Construct matrix operation object using the wrapper class +/// DenseSymShiftSolve op(M); +/// +/// // Construct eigen solver object with shift 0 +/// // This will find eigenvalues that are closest to 0 +/// SymEigsShiftSolver> eigs(op, 3, 6, 0.0); +/// +/// eigs.init(); +/// eigs.compute(SortRule::LargestMagn); +/// if (eigs.info() == CompInfo::Successful) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (3.0, 2.0, 1.0) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +/// Also an example for user-supplied matrix shift-solve operation class: +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// // M = diag(1, 2, ..., 10) +/// class MyDiagonalTenShiftSolve +/// { +/// private: +/// double sigma_; +/// public: +/// using Scalar = double; // A typedef named "Scalar" is required +/// int rows() const { return 10; } +/// int cols() const { return 10; } +/// void set_shift(double sigma) { sigma_ = sigma; } +/// // y_out = inv(A - sigma * I) * x_in +/// // inv(A - sigma * I) = diag(1/(1-sigma), 1/(2-sigma), ...) +/// void perform_op(double *x_in, double *y_out) const +/// { +/// for (int i = 0; i < rows(); i++) +/// { +/// y_out[i] = x_in[i] / (i + 1 - sigma_); +/// } +/// } +/// }; +/// +/// int main() +/// { +/// MyDiagonalTenShiftSolve op; +/// // Find three eigenvalues that are closest to 3.14 +/// SymEigsShiftSolver eigs(op, 3, 6, 3.14); +/// eigs.init(); +/// eigs.compute(SortRule::LargestMagn); +/// if (eigs.info() == CompInfo::Successful) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (4.0, 3.0, 2.0) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +template > +class SymEigsShiftSolver : public SymEigsBase +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Array = Eigen::Array; + + using Base = SymEigsBase; + using Base::m_nev; + using Base::m_ritz_val; + + const Scalar m_sigma; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + // The eigenvalues we get from the iteration is nu = 1 / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = 1 / nu + sigma + m_ritz_val.head(m_nev).array() = Scalar(1) / m_ritz_val.head(m_nev).array() + m_sigma; + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op The matrix operation object that implements + /// the shift-solve operation of \f$A\f$: calculating + /// \f$(A-\sigma I)^{-1}v\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseSymShiftSolve, or + /// define their own that implements all the public members + /// as in DenseSymShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv_` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// \param sigma The value of the shift. + /// + SymEigsShiftSolver(OpType& op, Index nev, Index ncv, const Scalar& sigma) : + Base(op, IdentityBOp(), nev, ncv), + m_sigma(sigma) + { + op.set_shift(m_sigma); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_EIGS_SHIFT_SOLVER_H diff --git a/thirdparty/include/Spectra/SymEigsSolver.h b/thirdparty/include/Spectra/SymEigsSolver.h new file mode 100644 index 0000000..3dc7a7a --- /dev/null +++ b/thirdparty/include/Spectra/SymEigsSolver.h @@ -0,0 +1,164 @@ +// Copyright (C) 2016-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_EIGS_SOLVER_H +#define SPECTRA_SYM_EIGS_SOLVER_H + +#include + +#include "SymEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseSymMatProd.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for real symmetric matrices, i.e., +/// to solve \f$Ax=\lambda x\f$ where \f$A\f$ is symmetric. +/// +/// **Spectra** is designed to calculate a specified number (\f$k\f$) +/// of eigenvalues of a large square matrix (\f$A\f$). Usually \f$k\f$ is much +/// less than the size of the matrix (\f$n\f$), so that only a few eigenvalues +/// and eigenvectors are computed. +/// +/// Rather than providing the whole \f$A\f$ matrix, the algorithm only requires +/// the matrix-vector multiplication operation of \f$A\f$. Therefore, users of +/// this solver need to supply a class that computes the result of \f$Av\f$ +/// for any given vector \f$v\f$. The name of this class should be given to +/// the template parameter `OpType`, and instance of this class passed to +/// the constructor of SymEigsSolver. +/// +/// If the matrix \f$A\f$ is already stored as a matrix object in **Eigen**, +/// for example `Eigen::MatrixXd`, then there is an easy way to construct such a +/// matrix operation class, by using the built-in wrapper class DenseSymMatProd +/// that wraps an existing matrix object in **Eigen**. This is also the +/// default template parameter for SymEigsSolver. For sparse matrices, the +/// wrapper class SparseSymMatProd can be used similarly. +/// +/// If the users need to define their own matrix-vector multiplication operation +/// class, it should define a public type `Scalar` to indicate the element type, +/// and implement all the public member functions as in DenseSymMatProd. +/// +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseSymMatProd. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to calculate the eigenvalues of M +/// Eigen::MatrixXd A = Eigen::MatrixXd::Random(10, 10); +/// Eigen::MatrixXd M = A + A.transpose(); +/// +/// // Construct matrix operation object using the wrapper class DenseSymMatProd +/// DenseSymMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest three eigenvalues +/// SymEigsSolver> eigs(op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(SortRule::LargestAlge); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// if (eigs.info() == CompInfo::Successful) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +/// +/// And here is an example for user-supplied matrix operation class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// // M = diag(1, 2, ..., 10) +/// class MyDiagonalTen +/// { +/// public: +/// using Scalar = double; // A typedef named "Scalar" is required +/// int rows() const { return 10; } +/// int cols() const { return 10; } +/// // y_out = M * x_in +/// void perform_op(double *x_in, double *y_out) const +/// { +/// for (int i = 0; i < rows(); i++) +/// { +/// y_out[i] = x_in[i] * (i + 1); +/// } +/// } +/// }; +/// +/// int main() +/// { +/// MyDiagonalTen op; +/// SymEigsSolver eigs(op, 3, 6); +/// eigs.init(); +/// eigs.compute(SortRule::LargestAlge); +/// if (eigs.info() == CompInfo::Successful) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (10, 9, 8) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +template > +class SymEigsSolver : public SymEigsBase +{ +private: + using Index = Eigen::Index; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The matrix operation object that implements + /// the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseSymMatProd, or + /// define their own that implements all the public members + /// as in DenseSymMatProd. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymEigsSolver(OpType& op, Index nev, Index ncv) : + SymEigsBase(op, IdentityBOp(), nev, ncv) + {} +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_EIGS_SOLVER_H diff --git a/thirdparty/include/Spectra/SymGEigsShiftSolver.h b/thirdparty/include/Spectra/SymGEigsShiftSolver.h new file mode 100644 index 0000000..c7dc50f --- /dev/null +++ b/thirdparty/include/Spectra/SymGEigsShiftSolver.h @@ -0,0 +1,463 @@ +// 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_GEIGS_SHIFT_SOLVER_H +#define SPECTRA_SYM_GEIGS_SHIFT_SOLVER_H + +#include // std::move +#include "SymEigsBase.h" +#include "Util/GEigsMode.h" +#include "MatOp/internal/SymGEigsShiftInvertOp.h" +#include "MatOp/internal/SymGEigsBucklingOp.h" +#include "MatOp/internal/SymGEigsCayleyOp.h" + +namespace Spectra { + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices, i.e., to solve \f$Ax=\lambda Bx\f$ where \f$A\f$ and \f$B\f$ are symmetric +/// matrices. A spectral transform is applied to seek interior +/// generalized eigenvalues with respect to some shift \f$\sigma\f$. +/// +/// There are different modes of this solver, specified by the template parameter `Mode`. +/// See the pages for the specialized classes for details. +/// - The shift-and-invert mode transforms the problem into \f$(A-\sigma B)^{-1}Bx=\nu x\f$, +/// where \f$\nu=1/(\lambda-\sigma)\f$. This mode assumes that \f$B\f$ is positive definite. +/// See \ref SymGEigsShiftSolver +/// "SymGEigsShiftSolver (Shift-and-invert mode)" for more details. +/// - The buckling mode transforms the problem into \f$(A-\sigma B)^{-1}Ax=\nu x\f$, +/// where \f$\nu=\lambda/(\lambda-\sigma)\f$. This mode assumes that \f$A\f$ is positive definite. +/// See \ref SymGEigsShiftSolver +/// "SymGEigsShiftSolver (Buckling mode)" for more details. +/// - The Cayley mode transforms the problem into \f$(A-\sigma B)^{-1}(A+\sigma B)x=\nu x\f$, +/// where \f$\nu=(\lambda+\sigma)/(\lambda-\sigma)\f$. This mode assumes that \f$B\f$ is positive definite. +/// See \ref SymGEigsShiftSolver +/// "SymGEigsShiftSolver (Cayley mode)" for more details. + +// Empty class template +template +class SymGEigsShiftSolver +{}; + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices using the shift-and-invert spectral transformation. The original problem is +/// to solve \f$Ax=\lambda Bx\f$, where \f$A\f$ is symmetric and \f$B\f$ is positive definite. +/// The transformed problem is \f$(A-\sigma B)^{-1}Bx=\nu x\f$, where +/// \f$\nu=1/(\lambda-\sigma)\f$, and \f$\sigma\f$ is a user-specified shift. +/// +/// This solver requires two matrix operation objects: one to compute \f$y=(A-\sigma B)^{-1}x\f$ +/// for any vector \f$v\f$, and one for the matrix multiplication \f$Bv\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation object +/// can be created using the SymShiftInvert class, and the second one can be created +/// using the DenseSymMatProd or SparseSymMatProd classes. If the users need to define their +/// own operation classes, then they should implement all the public member functions as +/// in those built-in classes. +/// +/// \tparam OpType The type of the first operation object. Users could either +/// use the wrapper class SymShiftInvert, or define their own that implements +/// the type definition `Scalar` and all the public member functions as in SymShiftInvert. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements all the +/// public member functions as in DenseSymMatProd. +/// \tparam Mode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEigsMode::ShiftInvert. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to solve the generalized eigenvalue problem +/// // A * x = lambda * B * x, +/// // where A is symmetric and B is positive definite +/// const int n = 100; +/// +/// // Define the A matrix +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(n, n); +/// Eigen::MatrixXd A = M + M.transpose(); +/// +/// // Define the B matrix, a tridiagonal matrix with 2 on the diagonal +/// // and 1 on the subdiagonals +/// Eigen::SparseMatrix B(n, n); +/// B.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for (int i = 0; i < n; i++) +/// { +/// B.insert(i, i) = 2.0; +/// if (i > 0) +/// B.insert(i - 1, i) = 1.0; +/// if (i < n - 1) +/// B.insert(i + 1, i) = 1.0; +/// } +/// +/// // Construct matrix operation objects using the wrapper classes +/// // A is dense, B is sparse +/// using OpType = SymShiftInvert; +/// using BOpType = SparseSymMatProd; +/// OpType op(A, B); +/// BOpType Bop(B); +/// +/// // Construct generalized eigen solver object, seeking three generalized +/// // eigenvalues that are closest to zero. This is equivalent to specifying +/// // a shift sigma = 0.0 combined with the SortRule::LargestMagn selection rule +/// SymGEigsShiftSolver +/// geigs(op, Bop, 3, 6, 0.0); +/// +/// // Initialize and compute +/// geigs.init(); +/// int nconv = geigs.compute(SortRule::LargestMagn); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// Eigen::MatrixXd evecs; +/// if (geigs.info() == CompInfo::Successful) +/// { +/// evalues = geigs.eigenvalues(); +/// evecs = geigs.eigenvectors(); +/// } +/// +/// std::cout << "Number of converged generalized eigenvalues: " << nconv << std::endl; +/// std::cout << "Generalized eigenvalues found:\n" << evalues << std::endl; +/// std::cout << "Generalized eigenvectors found:\n" << evecs.topRows(10) << std::endl; +/// +/// return 0; +/// } +/// \endcode + +// Partial specialization for mode = GEigsMode::ShiftInvert +template +class SymGEigsShiftSolver : + public SymEigsBase, BOpType> +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Array = Eigen::Array; + + using ModeMatOp = SymGEigsShiftInvertOp; + using Base = SymEigsBase; + using Base::m_nev; + using Base::m_ritz_val; + + const Scalar m_sigma; + + // Set shift and forward + static ModeMatOp set_shift_and_move(ModeMatOp&& op, const Scalar& sigma) + { + op.set_shift(sigma); + return std::move(op); + } + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + // The eigenvalues we get from the iteration is nu = 1 / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = 1 / nu + sigma + m_ritz_val.head(m_nev).array() = Scalar(1) / m_ritz_val.head(m_nev).array() + m_sigma; + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The matrix operation object that computes \f$y=(A-\sigma B)^{-1}v\f$ + /// for any vector \f$v\f$. Users could either create the object from the + /// wrapper class SymShiftInvert, or define their own that implements all + /// the public members as in SymShiftInvert. + /// \param Bop The \f$B\f$ matrix operation object that implements the matrix-vector + /// multiplication \f$Bv\f$. Users could either create the object from the + /// wrapper classes such as DenseSymMatProd and SparseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. \f$B\f$ needs to be positive definite. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// \param sigma The value of the shift. + /// + SymGEigsShiftSolver(OpType& op, BOpType& Bop, Index nev, Index ncv, const Scalar& sigma) : + Base(set_shift_and_move(ModeMatOp(op, Bop), sigma), Bop, nev, ncv), + m_sigma(sigma) + {} +}; + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices in the buckling mode. The original problem is +/// to solve \f$Kx=\lambda K_G x\f$, where \f$K\f$ is positive definite and \f$K_G\f$ is symmetric. +/// The transformed problem is \f$(K-\sigma K_G)^{-1}Kx=\nu x\f$, where +/// \f$\nu=\lambda/(\lambda-\sigma)\f$, and \f$\sigma\f$ is a user-specified shift. +/// +/// This solver requires two matrix operation objects: one to compute \f$y=(K-\sigma K_G)^{-1}x\f$ +/// for any vector \f$v\f$, and one for the matrix multiplication \f$Kv\f$. +/// +/// If \f$K\f$ and \f$K_G\f$ are stored as Eigen matrices, then the first operation object +/// can be created using the SymShiftInvert class, and the second one can be created +/// using the DenseSymMatProd or SparseSymMatProd classes. If the users need to define their +/// own operation classes, then they should implement all the public member functions as +/// in those built-in classes. +/// +/// \tparam OpType The type of the first operation object. Users could either +/// use the wrapper class SymShiftInvert, or define their own that implements +/// the type definition `Scalar` and all the public member functions as in SymShiftInvert. +/// \tparam BOpType The name of the matrix operation class for \f$K\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements all the +/// public member functions as in DenseSymMatProd. +/// \tparam Mode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEigsMode::Buckling. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to solve the generalized eigenvalue problem +/// // K * x = lambda * KG * x, +/// // where K is positive definite, and KG is symmetric +/// const int n = 100; +/// +/// // Define the K matrix, a tridiagonal matrix with 2 on the diagonal +/// // and 1 on the subdiagonals +/// Eigen::SparseMatrix K(n, n); +/// K.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for (int i = 0; i < n; i++) +/// { +/// K.insert(i, i) = 2.0; +/// if (i > 0) +/// K.insert(i - 1, i) = 1.0; +/// if (i < n - 1) +/// K.insert(i + 1, i) = 1.0; +/// } +/// +/// // Define the KG matrix +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(n, n); +/// Eigen::MatrixXd KG = M + M.transpose(); +/// +/// // Construct matrix operation objects using the wrapper classes +/// // K is sparse, KG is dense +/// using OpType = SymShiftInvert; +/// using BOpType = SparseSymMatProd; +/// OpType op(K, KG); +/// BOpType Bop(K); +/// +/// // Construct generalized eigen solver object, seeking three generalized +/// // eigenvalues that are closest to and larger than 1.0. This is equivalent to +/// // specifying a shift sigma = 1.0 combined with the SortRule::LargestAlge +/// // selection rule +/// SymGEigsShiftSolver +/// geigs(op, Bop, 3, 6, 1.0); +/// +/// // Initialize and compute +/// geigs.init(); +/// int nconv = geigs.compute(SortRule::LargestAlge); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// Eigen::MatrixXd evecs; +/// if (geigs.info() == CompInfo::Successful) +/// { +/// evalues = geigs.eigenvalues(); +/// evecs = geigs.eigenvectors(); +/// } +/// +/// std::cout << "Number of converged generalized eigenvalues: " << nconv << std::endl; +/// std::cout << "Generalized eigenvalues found:\n" << evalues << std::endl; +/// std::cout << "Generalized eigenvectors found:\n" << evecs.topRows(10) << std::endl; +/// +/// return 0; +/// } +/// \endcode + +// Partial specialization for mode = GEigsMode::Buckling +template +class SymGEigsShiftSolver : + public SymEigsBase, BOpType> +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Array = Eigen::Array; + + using ModeMatOp = SymGEigsBucklingOp; + using Base = SymEigsBase; + using Base::m_nev; + using Base::m_ritz_val; + + const Scalar m_sigma; + + // Set shift and forward + static ModeMatOp set_shift_and_move(ModeMatOp&& op, const Scalar& sigma) + { + if (sigma == Scalar(0)) + throw std::invalid_argument("SymGEigsShiftSolver: sigma cannot be zero in the buckling mode"); + op.set_shift(sigma); + return std::move(op); + } + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + // The eigenvalues we get from the iteration is nu = lambda / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = sigma * nu / (nu - 1) + m_ritz_val.head(m_nev).array() = m_sigma * m_ritz_val.head(m_nev).array() / + (m_ritz_val.head(m_nev).array() - Scalar(1)); + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The matrix operation object that computes \f$y=(K-\sigma K_G)^{-1}v\f$ + /// for any vector \f$v\f$. Users could either create the object from the + /// wrapper class SymShiftInvert, or define their own that implements all + /// the public members as in SymShiftInvert. + /// \param Bop The \f$K\f$ matrix operation object that implements the matrix-vector + /// multiplication \f$Kv\f$. Users could either create the object from the + /// wrapper classes such as DenseSymMatProd and SparseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. \f$K\f$ needs to be positive definite. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// \param sigma The value of the shift. + /// + SymGEigsShiftSolver(OpType& op, BOpType& Bop, Index nev, Index ncv, const Scalar& sigma) : + Base(set_shift_and_move(ModeMatOp(op, Bop), sigma), Bop, nev, ncv), + m_sigma(sigma) + {} +}; + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices using the Cayley spectral transformation. The original problem is +/// to solve \f$Ax=\lambda Bx\f$, where \f$A\f$ is symmetric and \f$B\f$ is positive definite. +/// The transformed problem is \f$(A-\sigma B)^{-1}(A+\sigma B)x=\nu x\f$, where +/// \f$\nu=(\lambda+\sigma)/(\lambda-\sigma)\f$, and \f$\sigma\f$ is a user-specified shift. +/// +/// This solver requires two matrix operation objects: one to compute \f$y=(A-\sigma B)^{-1}x\f$ +/// for any vector \f$v\f$, and one for the matrix multiplication \f$Bv\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation object +/// can be created using the SymShiftInvert class, and the second one can be created +/// using the DenseSymMatProd or SparseSymMatProd classes. If the users need to define their +/// own operation classes, then they should implement all the public member functions as +/// in those built-in classes. +/// +/// \tparam OpType The type of the first operation object. Users could either +/// use the wrapper class SymShiftInvert, or define their own that implements +/// the type definition `Scalar` and all the public member functions as in SymShiftInvert. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements all the +/// public member functions as in DenseSymMatProd. +/// \tparam Mode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEigsMode::Cayley. + +// Partial specialization for mode = GEigsMode::Cayley +template +class SymGEigsShiftSolver : + public SymEigsBase, BOpType> +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Array = Eigen::Array; + + using ModeMatOp = SymGEigsCayleyOp; + using Base = SymEigsBase; + using Base::m_nev; + using Base::m_ritz_val; + + const Scalar m_sigma; + + // Set shift and forward + static ModeMatOp set_shift_and_move(ModeMatOp&& op, const Scalar& sigma) + { + if (sigma == Scalar(0)) + throw std::invalid_argument("SymGEigsShiftSolver: sigma cannot be zero in the Cayley mode"); + op.set_shift(sigma); + return std::move(op); + } + + // First transform back the Ritz values, and then sort + void sort_ritzpair(SortRule sort_rule) override + { + // The eigenvalues we get from the iteration is nu = (lambda + sigma) / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = sigma * (nu + 1) / (nu - 1) + m_ritz_val.head(m_nev).array() = m_sigma * (m_ritz_val.head(m_nev).array() + Scalar(1)) / + (m_ritz_val.head(m_nev).array() - Scalar(1)); + Base::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The matrix operation object that computes \f$y=(A-\sigma B)^{-1}v\f$ + /// for any vector \f$v\f$. Users could either create the object from the + /// wrapper class SymShiftInvert, or define their own that implements all + /// the public members as in SymShiftInvert. + /// \param Bop The \f$B\f$ matrix operation object that implements the matrix-vector + /// multiplication \f$Bv\f$. Users could either create the object from the + /// wrapper classes such as DenseSymMatProd and SparseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. \f$B\f$ needs to be positive definite. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// \param sigma The value of the shift. + /// + SymGEigsShiftSolver(OpType& op, BOpType& Bop, Index nev, Index ncv, const Scalar& sigma) : + Base(set_shift_and_move(ModeMatOp(op, Bop), sigma), Bop, nev, ncv), + m_sigma(sigma) + {} +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_SHIFT_SOLVER_H diff --git a/thirdparty/include/Spectra/SymGEigsSolver.h b/thirdparty/include/Spectra/SymGEigsSolver.h new file mode 100644 index 0000000..69ccc54 --- /dev/null +++ b/thirdparty/include/Spectra/SymGEigsSolver.h @@ -0,0 +1,290 @@ +// Copyright (C) 2016-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_GEIGS_SOLVER_H +#define SPECTRA_SYM_GEIGS_SOLVER_H + +#include "SymEigsBase.h" +#include "Util/GEigsMode.h" +#include "MatOp/internal/SymGEigsCholeskyOp.h" +#include "MatOp/internal/SymGEigsRegInvOp.h" + +namespace Spectra { + +/// +/// \defgroup GEigenSolver Generalized Eigen Solvers +/// +/// Generalized eigen solvers for different types of problems. +/// + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices, i.e., to solve \f$Ax=\lambda Bx\f$ where \f$A\f$ is symmetric and +/// \f$B\f$ is positive definite. +/// +/// There are two modes of this solver, specified by the template parameter `Mode`. +/// See the pages for the specialized classes for details. +/// - The Cholesky mode assumes that \f$B\f$ can be factorized using Cholesky +/// decomposition, which is the preferred mode when the decomposition is +/// available. (This can be easily done in Eigen using the dense or sparse +/// Cholesky solver.) +/// See \ref SymGEigsSolver "SymGEigsSolver (Cholesky mode)" for more details. +/// - The regular inverse mode requires the matrix-vector product \f$Bv\f$ and the +/// linear equation solving operation \f$B^{-1}v\f$. This mode should only be +/// used when the Cholesky decomposition of \f$B\f$ is hard to implement, or +/// when computing \f$B^{-1}v\f$ is much faster than the Cholesky decomposition. +/// See \ref SymGEigsSolver "SymGEigsSolver (Regular inverse mode)" for more details. + +// Empty class template +template +class SymGEigsSolver +{}; + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices using Cholesky decomposition, i.e., to solve \f$Ax=\lambda Bx\f$ +/// where \f$A\f$ is symmetric and \f$B\f$ is positive definite with the Cholesky +/// decomposition \f$B=LL'\f$. +/// +/// This solver requires two matrix operation objects: one for \f$A\f$ that implements +/// the matrix multiplication \f$Av\f$, and one for \f$B\f$ that implements the lower +/// and upper triangular solving \f$L^{-1}v\f$ and \f$(L')^{-1}v\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation +/// can be created using the DenseSymMatProd or SparseSymMatProd classes, and +/// the second operation can be created using the DenseCholesky or SparseCholesky +/// classes. If the users need to define their own operation classes, then they +/// should implement all the public member functions as in those built-in classes. +/// +/// \tparam OpType The name of the matrix operation class for \f$A\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseSymMatProd. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper classes such as DenseCholesky and +/// SparseCholesky, or define their own that implements all the +/// public member functions as in DenseCholesky. +/// \tparam Mode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEigsMode::Cholesky. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to solve the generalized eigenvalue problem A * x = lambda * B * x +/// const int n = 100; +/// +/// // Define the A matrix +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(n, n); +/// Eigen::MatrixXd A = M + M.transpose(); +/// +/// // Define the B matrix, a band matrix with 2 on the diagonal and 1 on the subdiagonals +/// Eigen::SparseMatrix B(n, n); +/// B.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for (int i = 0; i < n; i++) +/// { +/// B.insert(i, i) = 2.0; +/// if (i > 0) +/// B.insert(i - 1, i) = 1.0; +/// if (i < n - 1) +/// B.insert(i + 1, i) = 1.0; +/// } +/// +/// // Construct matrix operation objects using the wrapper classes +/// DenseSymMatProd op(A); +/// SparseCholesky Bop(B); +/// +/// // Construct generalized eigen solver object, requesting the largest three generalized eigenvalues +/// SymGEigsSolver, SparseCholesky, GEigsMode::Cholesky> +/// geigs(op, Bop, 3, 6); +/// +/// // Initialize and compute +/// geigs.init(); +/// int nconv = geigs.compute(SortRule::LargestAlge); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// Eigen::MatrixXd evecs; +/// if (geigs.info() == CompInfo::Successful) +/// { +/// evalues = geigs.eigenvalues(); +/// evecs = geigs.eigenvectors(); +/// } +/// +/// std::cout << "Generalized eigenvalues found:\n" << evalues << std::endl; +/// std::cout << "Generalized eigenvectors found:\n" << evecs.topRows(10) << std::endl; +/// +/// // Verify results using the generalized eigen solver in Eigen +/// Eigen::MatrixXd Bdense = B; +/// Eigen::GeneralizedSelfAdjointEigenSolver es(A, Bdense); +/// +/// std::cout << "Generalized eigenvalues:\n" << es.eigenvalues().tail(3) << std::endl; +/// std::cout << "Generalized eigenvectors:\n" << es.eigenvectors().rightCols(3).topRows(10) << std::endl; +/// +/// return 0; +/// } +/// \endcode + +// Partial specialization for mode = GEigsMode::Cholesky +template +class SymGEigsSolver : + public SymEigsBase, IdentityBOp> +{ +private: + using Scalar = typename OpType::Scalar; + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + + using ModeMatOp = SymGEigsCholeskyOp; + using Base = SymEigsBase; + + const BOpType& m_Bop; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The \f$A\f$ matrix operation object that implements the matrix-vector + /// multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper classes such as DenseSymMatProd, or + /// define their own that implements all the public members + /// as in DenseSymMatProd. + /// \param Bop The \f$B\f$ matrix operation object that represents a Cholesky decomposition of \f$B\f$. + /// It should implement the lower and upper triangular solving operations: + /// calculating \f$L^{-1}v\f$ and \f$(L')^{-1}v\f$ for any vector + /// \f$v\f$, where \f$LL'=B\f$. Users could either + /// create the object from the wrapper classes such as DenseCholesky, or + /// define their own that implements all the public member functions + /// as in DenseCholesky. \f$B\f$ needs to be positive definite. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymGEigsSolver(OpType& op, BOpType& Bop, Index nev, Index ncv) : + Base(ModeMatOp(op, Bop), IdentityBOp(), nev, ncv), + m_Bop(Bop) + {} + + /// \cond + + Matrix eigenvectors(Index nvec) const override + { + Matrix res = Base::eigenvectors(nvec); + Vector tmp(res.rows()); + const Index nconv = res.cols(); + for (Index i = 0; i < nconv; i++) + { + m_Bop.upper_triangular_solve(&res(0, i), tmp.data()); + res.col(i).noalias() = tmp; + } + + return res; + } + + Matrix eigenvectors() const override + { + return SymGEigsSolver::eigenvectors(this->m_nev); + } + + /// \endcond +}; + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices in the regular inverse mode, i.e., to solve \f$Ax=\lambda Bx\f$ +/// where \f$A\f$ is symmetric, and \f$B\f$ is positive definite with the operations +/// defined below. +/// +/// This solver requires two matrix operation objects: one for \f$A\f$ that implements +/// the matrix multiplication \f$Av\f$, and one for \f$B\f$ that implements the +/// matrix-vector product \f$Bv\f$ and the linear equation solving operation \f$B^{-1}v\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation +/// can be created using the DenseSymMatProd or SparseSymMatProd classes, and +/// the second operation can be created using the SparseRegularInverse class. There is no +/// wrapper class for a dense \f$B\f$ matrix since in this case the Cholesky mode +/// is always preferred. If the users need to define their own operation classes, then they +/// should implement all the public member functions as in those built-in classes. +/// +/// \tparam OpType The name of the matrix operation class for \f$A\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their own that implements the type +/// definition `Scalar` and all the public member functions as in +/// DenseSymMatProd. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper class SparseRegularInverse, or define their +/// own that implements all the public member functions as in +/// SparseRegularInverse. +/// \tparam Mode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEigsMode::RegularInverse. +/// + +// Partial specialization for mode = GEigsMode::RegularInverse +template +class SymGEigsSolver : + public SymEigsBase, BOpType> +{ +private: + using Index = Eigen::Index; + + using ModeMatOp = SymGEigsRegInvOp; + using Base = SymEigsBase; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op The \f$A\f$ matrix operation object that implements the matrix-vector + /// multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper classes such as DenseSymMatProd, or + /// define their own that implements all the public members + /// as in DenseSymMatProd. + /// \param Bop The \f$B\f$ matrix operation object that implements the multiplication operation + /// \f$Bv\f$ and the linear equation solving operation \f$B^{-1}v\f$ for any vector \f$v\f$. + /// Users could either create the object from the wrapper class SparseRegularInverse, or + /// define their own that implements all the public member functions + /// as in SparseRegularInverse. \f$B\f$ needs to be positive definite. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymGEigsSolver(OpType& op, BOpType& Bop, Index nev, Index ncv) : + Base(ModeMatOp(op, Bop), Bop, nev, ncv) + {} +}; + +} // namespace Spectra + +#endif // SPECTRA_SYM_GEIGS_SOLVER_H diff --git a/thirdparty/include/Spectra/Util/CompInfo.h b/thirdparty/include/Spectra/Util/CompInfo.h new file mode 100644 index 0000000..f97d542 --- /dev/null +++ b/thirdparty/include/Spectra/Util/CompInfo.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016-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_COMP_INFO_H +#define SPECTRA_COMP_INFO_H + +namespace Spectra { + +/// +/// \ingroup Enumerations +/// +/// The enumeration to report the status of computation. +/// +enum class CompInfo +{ + Successful, ///< Computation was successful. + + NotComputed, ///< Used in eigen solvers, indicating that computation + ///< has not been conducted. Users should call + ///< the `compute()` member function of solvers. + + NotConverging, ///< Used in eigen solvers, indicating that some eigenvalues + ///< did not converge. The `compute()` + ///< function returns the number of converged eigenvalues. + + NumericalIssue ///< Used in various matrix factorization classes, for example in + ///< Cholesky decomposition it indicates that the + ///< matrix is not positive definite. +}; + +} // namespace Spectra + +#endif // SPECTRA_COMP_INFO_H diff --git a/thirdparty/include/Spectra/Util/GEigsMode.h b/thirdparty/include/Spectra/Util/GEigsMode.h new file mode 100644 index 0000000..68740f5 --- /dev/null +++ b/thirdparty/include/Spectra/Util/GEigsMode.h @@ -0,0 +1,28 @@ +// Copyright (C) 2016-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_GEIGS_MODE_H +#define SPECTRA_GEIGS_MODE_H + +namespace Spectra { + +/// +/// \ingroup Enumerations +/// +/// The enumeration to specify the mode of generalized eigenvalue solver. +/// +enum class GEigsMode +{ + Cholesky, ///< Using Cholesky decomposition to solve generalized eigenvalues. + RegularInverse, ///< Regular inverse mode for generalized eigenvalue solver. + ShiftInvert, ///< Shift-and-invert mode for generalized eigenvalue solver. + Buckling, ///< Buckling mode for generalized eigenvalue solver. + Cayley ///< Cayley transformation mode for generalized eigenvalue solver. +}; + +} // namespace Spectra + +#endif // SPECTRA_GEIGS_MODE_H diff --git a/thirdparty/include/Spectra/Util/SelectionRule.h b/thirdparty/include/Spectra/Util/SelectionRule.h new file mode 100644 index 0000000..9d12a34 --- /dev/null +++ b/thirdparty/include/Spectra/Util/SelectionRule.h @@ -0,0 +1,300 @@ +// Copyright (C) 2016-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_SELECTION_RULE_H +#define SPECTRA_SELECTION_RULE_H + +#include // std::vector +#include // std::abs +#include // std::sort +#include // std::complex +#include // std::pair +#include // std::invalid_argument + +#include +#include "TypeTraits.h" + +namespace Spectra { + +/// +/// \defgroup Enumerations Enumerations +/// +/// Enumeration types for the selection rule of eigenvalues. +/// + +/// +/// \ingroup Enumerations +/// +/// The enumeration of selection rules of desired eigenvalues. +/// +enum class SortRule +{ + LargestMagn, ///< Select eigenvalues with largest magnitude. Magnitude + ///< means the absolute value for real numbers and norm for + ///< complex numbers. Applies to both symmetric and general + ///< eigen solvers. + + LargestReal, ///< Select eigenvalues with largest real part. Only for general eigen solvers. + + LargestImag, ///< Select eigenvalues with largest imaginary part (in magnitude). Only for general eigen solvers. + + LargestAlge, ///< Select eigenvalues with largest algebraic value, considering + ///< any negative sign. Only for symmetric eigen solvers. + + SmallestMagn, ///< Select eigenvalues with smallest magnitude. Applies to both symmetric and general + ///< eigen solvers. + + SmallestReal, ///< Select eigenvalues with smallest real part. Only for general eigen solvers. + + SmallestImag, ///< Select eigenvalues with smallest imaginary part (in magnitude). Only for general eigen solvers. + + SmallestAlge, ///< Select eigenvalues with smallest algebraic value. Only for symmetric eigen solvers. + + BothEnds ///< Select eigenvalues half from each end of the spectrum. When + ///< `nev` is odd, compute more from the high end. Only for symmetric eigen solvers. +}; + +/// \cond + +// When comparing eigenvalues, we first calculate the "target" to sort. +// For example, if we want to choose the eigenvalues with +// largest magnitude, the target will be -abs(x). +// The minus sign is due to the fact that std::sort() sorts in ascending order. + +// Default target: throw an exception +template +class SortingTarget +{ +public: + static ElemType get(const Scalar& val) + { + using std::abs; + throw std::invalid_argument("incompatible selection rule"); + return -abs(val); + } +}; + +// Specialization for SortRule::LargestMagn +// This covers [float, double, complex] x [SortRule::LargestMagn] +template +class SortingTarget +{ +public: + static ElemType get(const Scalar& val) + { + using std::abs; + return -abs(val); + } +}; + +// Specialization for SortRule::LargestReal +// This covers [complex] x [SortRule::LargestReal] +template +class SortingTarget, SortRule::LargestReal> +{ +public: + static RealType get(const std::complex& val) + { + return -val.real(); + } +}; + +// Specialization for SortRule::LargestImag +// This covers [complex] x [SortRule::LargestImag] +template +class SortingTarget, SortRule::LargestImag> +{ +public: + static RealType get(const std::complex& val) + { + using std::abs; + return -abs(val.imag()); + } +}; + +// Specialization for SortRule::LargestAlge +// This covers [float, double] x [SortRule::LargestAlge] +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return -val; + } +}; + +// Here SortRule::BothEnds is the same as SortRule::LargestAlge, but +// we need some additional steps, which are done in +// SymEigsSolver.h => retrieve_ritzpair(). +// There we move the smallest values to the proper locations. +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return -val; + } +}; + +// Specialization for SortRule::SmallestMagn +// This covers [float, double, complex] x [SortRule::SmallestMagn] +template +class SortingTarget +{ +public: + static ElemType get(const Scalar& val) + { + using std::abs; + return abs(val); + } +}; + +// Specialization for SortRule::SmallestReal +// This covers [complex] x [SortRule::SmallestReal] +template +class SortingTarget, SortRule::SmallestReal> +{ +public: + static RealType get(const std::complex& val) + { + return val.real(); + } +}; + +// Specialization for SortRule::SmallestImag +// This covers [complex] x [SortRule::SmallestImag] +template +class SortingTarget, SortRule::SmallestImag> +{ +public: + static RealType get(const std::complex& val) + { + using std::abs; + return abs(val.imag()); + } +}; + +// Specialization for SortRule::SmallestAlge +// This covers [float, double] x [SortRule::SmallestAlge] +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return val; + } +}; + +// Sort eigenvalues +template +class SortEigenvalue +{ +private: + using Index = Eigen::Index; + using IndexArray = std::vector; + + const T* m_evals; + IndexArray m_index; + +public: + // Sort indices according to the eigenvalues they point to + inline bool operator()(Index i, Index j) + { + return SortingTarget::get(m_evals[i]) < SortingTarget::get(m_evals[j]); + } + + SortEigenvalue(const T* start, Index size) : + m_evals(start), m_index(size) + { + for (Index i = 0; i < size; i++) + { + m_index[i] = i; + } + std::sort(m_index.begin(), m_index.end(), *this); + } + + inline IndexArray index() const { return m_index; } + inline void swap(IndexArray& other) { m_index.swap(other); } +}; + +// Sort values[:len] according to the selection rule, and return the indices +template +std::vector argsort(SortRule selection, const Eigen::Matrix& values, Eigen::Index len) +{ + using Index = Eigen::Index; + + // Sort Ritz values and put the wanted ones at the beginning + std::vector ind; + switch (selection) + { + case SortRule::LargestMagn: + { + SortEigenvalue sorting(values.data(), len); + sorting.swap(ind); + break; + } + case SortRule::BothEnds: + case SortRule::LargestAlge: + { + SortEigenvalue sorting(values.data(), len); + sorting.swap(ind); + break; + } + case SortRule::SmallestMagn: + { + SortEigenvalue sorting(values.data(), len); + sorting.swap(ind); + break; + } + case SortRule::SmallestAlge: + { + SortEigenvalue sorting(values.data(), len); + sorting.swap(ind); + break; + } + default: + throw std::invalid_argument("unsupported selection rule"); + } + + // For SortRule::BothEnds, the eigenvalues are sorted according to the + // SortRule::LargestAlge rule, so we need to move those smallest values to the left + // The order would be + // Largest => Smallest => 2nd largest => 2nd smallest => ... + // We keep this order since the first k values will always be + // the wanted collection, no matter k is nev_updated (used in SymEigsBase::restart()) + // or is nev (used in SymEigsBase::sort_ritzpair()) + if (selection == SortRule::BothEnds) + { + std::vector ind_copy(ind); + for (Index i = 0; i < len; i++) + { + // If i is even, pick values from the left (large values) + // If i is odd, pick values from the right (small values) + if (i % 2 == 0) + ind[i] = ind_copy[i / 2]; + else + ind[i] = ind_copy[len - 1 - i / 2]; + } + } + + return ind; +} + +// Default vector length +template +std::vector argsort(SortRule selection, const Eigen::Matrix& values) +{ + return argsort(selection, values, values.size()); +} + +/// \endcond + +} // namespace Spectra + +#endif // SPECTRA_SELECTION_RULE_H diff --git a/thirdparty/include/Spectra/Util/SimpleRandom.h b/thirdparty/include/Spectra/Util/SimpleRandom.h new file mode 100644 index 0000000..f29a233 --- /dev/null +++ b/thirdparty/include/Spectra/Util/SimpleRandom.h @@ -0,0 +1,99 @@ +// Copyright (C) 2016-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_SIMPLE_RANDOM_H +#define SPECTRA_SIMPLE_RANDOM_H + +#include + +/// \cond + +namespace Spectra { + +// We need a simple pseudo random number generator here: +// 1. It is used to generate initial and restarted residual vector. +// 2. It is not necessary to be so "random" and advanced. All we hope +// is that the residual vector is not in the space spanned by the +// current Krylov space. This should be met almost surely. +// 3. We don't want to call RNG in C++, since we actually want the +// algorithm to be deterministic. Also, calling RNG in C/C++ is not +// allowed in R packages submitted to CRAN. +// 4. The method should be as simple as possible, so an LCG is enough. +// 5. Based on public domain code by Ray Gardner +// http://stjarnhimlen.se/snippets/rg_rand.c + +template +class SimpleRandom +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + + static constexpr unsigned int m_a = 16807; // multiplier + static constexpr unsigned long m_max = 2147483647L; // 2^31 - 1 + long m_rand; // RNG state + + inline long next_long_rand(long seed) const + { + unsigned long lo, hi; + + lo = m_a * (long) (seed & 0xFFFF); + hi = m_a * (long) ((unsigned long) seed >> 16); + lo += (hi & 0x7FFF) << 16; + if (lo > m_max) + { + lo &= m_max; + ++lo; + } + lo += hi >> 15; + if (lo > m_max) + { + lo &= m_max; + ++lo; + } + return (long) lo; + } + +public: + SimpleRandom(unsigned long init_seed) : + m_rand(init_seed ? (init_seed & m_max) : 1) + {} + + // Return a single random number, ranging from -0.5 to 0.5 + Scalar random() + { + m_rand = next_long_rand(m_rand); + return Scalar(m_rand) / Scalar(m_max) - Scalar(0.5); + } + + // Fill the given vector with random numbers + // Ranging from -0.5 to 0.5 + void random_vec(Vector& vec) + { + const Index len = vec.size(); + for (Index i = 0; i < len; i++) + { + m_rand = next_long_rand(m_rand); + vec[i] = Scalar(m_rand); + } + vec.array() = vec.array() / Scalar(m_max) - Scalar(0.5); + } + + // Return a vector of random numbers + // Ranging from -0.5 to 0.5 + Vector random_vec(const Index len) + { + Vector res(len); + random_vec(res); + return res; + } +}; + +} // namespace Spectra + +/// \endcond + +#endif // SPECTRA_SIMPLE_RANDOM_H diff --git a/thirdparty/include/Spectra/Util/TypeTraits.h b/thirdparty/include/Spectra/Util/TypeTraits.h new file mode 100644 index 0000000..6c1a7f6 --- /dev/null +++ b/thirdparty/include/Spectra/Util/TypeTraits.h @@ -0,0 +1,99 @@ +// Copyright (C) 2018-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_TYPE_TRAITS_H +#define SPECTRA_TYPE_TRAITS_H + +#include +#include + +/// \cond + +// Clang-Format will have unintended effects: +// static constexpr Scalar(min)() +// So we turn it off here +// +// clang-format off + +namespace Spectra { + +// For a real value type "Scalar", we want to know its smallest +// positive value, i.e., std::numeric_limits::min(). +// However, we must take non-standard value types into account, +// so we rely on Eigen::NumTraits. +// +// Eigen::NumTraits has defined epsilon() and lowest(), but +// lowest() means negative highest(), which is a very small +// negative value. +// +// Therefore, we manually define this limit, and use eplison()^3 +// to mimic it for non-standard types. + +// Generic definition +template +struct TypeTraits +{ + static constexpr Scalar epsilon() + { + return Eigen::numext::numeric_limits::epsilon(); + } + static constexpr Scalar (min)() + { + return epsilon() * epsilon() * epsilon(); + } +}; + +// Full specialization +template <> +struct TypeTraits +{ + static constexpr float epsilon() + { + return std::numeric_limits::epsilon(); + } + static constexpr float (min)() + { + return (std::numeric_limits::min)(); + } +}; + +template <> +struct TypeTraits +{ + static constexpr double epsilon() + { + return std::numeric_limits::epsilon(); + } + static constexpr double (min)() + { + return (std::numeric_limits::min)(); + } +}; + +template <> +struct TypeTraits +{ + static constexpr long double epsilon() + { + return std::numeric_limits::epsilon(); + } + static constexpr long double (min)() + { + return (std::numeric_limits::min)(); + } +}; + +// Get the element type of a "scalar" +// ElemType => double +// ElemType> => double +template +using ElemType = typename Eigen::NumTraits::Real; + +} // namespace Spectra + +/// \endcond + +#endif // SPECTRA_TYPE_TRAITS_H diff --git a/thirdparty/include/Spectra/Util/Version.h b/thirdparty/include/Spectra/Util/Version.h new file mode 100644 index 0000000..0cde96a --- /dev/null +++ b/thirdparty/include/Spectra/Util/Version.h @@ -0,0 +1,16 @@ +// 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_VERSION_H +#define SPECTRA_VERSION_H + +#define SPECTRA_MAJOR_VERSION 1 +#define SPECTRA_MINOR_VERSION 0 +#define SPECTRA_PATCH_VERSION 1 + +#define SPECTRA_VERSION (SPECTRA_MAJOR_VERSION * 10000 + SPECTRA_MINOR_VERSION * 100 + SPECTRA_PATCH_VERSION) + +#endif // SPECTRA_VERSION_H diff --git a/thirdparty/include/Spectra/contrib/LOBPCGSolver.h b/thirdparty/include/Spectra/contrib/LOBPCGSolver.h new file mode 100644 index 0000000..7002324 --- /dev/null +++ b/thirdparty/include/Spectra/contrib/LOBPCGSolver.h @@ -0,0 +1,551 @@ +// Written by Anna Araslanova +// Modified by Yixuan Qiu +// License: MIT + +#ifndef SPECTRA_LOBPCG_SOLVER_H +#define SPECTRA_LOBPCG_SOLVER_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "../SymGEigsSolver.h" + +namespace Spectra { + +/// +/// \ingroup EigenSolver +/// + +/// *** METHOD +/// The class represent the LOBPCG algorithm, which was invented by Andrew Knyazev +/// Theoretical background of the procedure can be found in the articles below +/// - Knyazev, A.V., 2001. Toward the optimal preconditioned eigensolver : Locally optimal block preconditioned conjugate gradient method.SIAM journal on scientific computing, 23(2), pp.517 - 541. +/// - Knyazev, A.V., Argentati, M.E., Lashuk, I. and Ovtchinnikov, E.E., 2007. Block locally optimal preconditioned eigenvalue xolvers(BLOPEX) in HYPRE and PETSc.SIAM Journal on Scientific Computing, 29(5), pp.2224 - 2239. +/// +/// *** CONDITIONS OF USE +/// Locally Optimal Block Preconditioned Conjugate Gradient(LOBPCG) is a method for finding the M smallest eigenvalues +/// and eigenvectors of a large symmetric positive definite generalized eigenvalue problem +/// \f$Ax=\lambda Bx,\f$ +/// where \f$A_{NxN}\f$ is a symmetric matrix, \f$B\f$ is symmetric and positive - definite. \f$A and B\f$ are also assumed large and sparse +/// \f$\textit{M}\f$ should be \f$\<< textit{N}\f$ (at least \f$\textit{5M} < \textit{N} \f$) +/// +/// *** ARGUMENTS +/// Eigen::SparseMatrix A; // N*N - Ax = lambda*Bx, lrage and sparse +/// Eigen::SparseMatrix X; // N*M - initial approximations to eigenvectors (random in general case) +/// Spectra::LOBPCGSolver solver(A, X); +/// *Eigen::SparseMatrix B; // N*N - Ax = lambda*Bx, sparse, positive definite +/// solver.setConstraints(B); +/// *Eigen::SparseMatrix Y; // N*K - constraints, already found eigenvectors +/// solver.setB(B); +/// *Eigen::SparseMatrix T; // N*N - preconditioner ~ A^-1 +/// solver.setPreconditioner(T); +/// +/// *** OUTCOMES +/// solver.solve(); // compute eigenpairs // void +/// solver.info(); // state of converjance // int +/// solver.residuals(); // get residuals to evaluate biases // Eigen::Matrix +/// solver.eigenvalues(); // get eigenvalues // Eigen::Matrix +/// solver.eigenvectors(); // get eigenvectors // Eigen::Matrix +/// +/// *** EXAMPLE +/// \code{.cpp} +/// #include +/// +/// // random A +/// Matrix a; +/// a = (Matrix::Random(10, 10).array() > 0.6).cast() * Matrix::Random(10, 10).array() * 5; +/// a = Matrix((a).triangularView()) + Matrix((a).triangularView()).transpose(); +/// for (int i = 0; i < 10; i++) +/// a(i, i) = i + 0.5; +/// std::cout << a << "\n"; +/// Eigen::SparseMatrix A(a.sparseView()); +/// // random X +/// Eigen::Matrix x; +/// x = Matrix::Random(10, 2).array(); +/// Eigen::SparseMatrix X(x.sparseView()); +/// // solve Ax = lambda*x +/// Spectra::LOBPCGSolver solver(A, X); +/// solver.compute(10, 1e-4); // 10 iterations, L2_tolerance = 1e-4*N +/// std::cout << "info\n" << solver.info() << std::endl; +/// std::cout << "eigenvalues\n" << solver.eigenvalues() << std::endl; +/// std::cout << "eigenvectors\n" << solver.eigenvectors() << std::endl; +/// std::cout << "residuals\n" << solver.residuals() << std::endl; +/// \endcode +/// + +template +class LOBPCGSolver +{ +private: + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + typedef std::complex Complex; + typedef Eigen::Matrix ComplexMatrix; + typedef Eigen::Matrix ComplexVector; + + typedef Eigen::SparseMatrix SparseMatrix; + typedef Eigen::SparseMatrix SparseComplexMatrix; + + const int m_n; // dimension of matrix A + const int m_nev; // number of eigenvalues requested + SparseMatrix A, X; + SparseMatrix m_Y, m_B, m_preconditioner; + bool flag_with_constraints, flag_with_B, flag_with_preconditioner; + +public: + SparseMatrix m_residuals; + Matrix m_evectors; + Vector m_evalues; + int m_info; + +private: + // B-orthonormalize matrix M + int orthogonalizeInPlace(SparseMatrix& M, SparseMatrix& B, + SparseMatrix& true_BM, bool has_true_BM = false) + { + SparseMatrix BM; + + if (has_true_BM == false) + { + if (flag_with_B) + { + BM = B * M; + } + else + { + BM = M; + } + } + else + { + BM = true_BM; + } + + Eigen::SimplicialLDLT chol_MBM(M.transpose() * BM); + + if (chol_MBM.info() != Eigen::Success) + { + // LDLT decomposition fail + m_info = chol_MBM.info(); + return chol_MBM.info(); + } + + SparseComplexMatrix Upper_MBM = chol_MBM.matrixU().template cast(); + ComplexVector D_MBM_vec = chol_MBM.vectorD().template cast(); + + D_MBM_vec = D_MBM_vec.cwiseSqrt(); + + for (int i = 0; i < D_MBM_vec.rows(); i++) + { + D_MBM_vec(i) = Complex(1.0, 0.0) / D_MBM_vec(i); + } + + SparseComplexMatrix D_MBM_mat(D_MBM_vec.asDiagonal()); + + SparseComplexMatrix U_inv(Upper_MBM.rows(), Upper_MBM.cols()); + U_inv.setIdentity(); + Upper_MBM.template triangularView().solveInPlace(U_inv); + + SparseComplexMatrix right_product = U_inv * D_MBM_mat; + M = M * right_product.real(); + if (flag_with_B) + { + true_BM = B * M; + } + else + { + true_BM = M; + } + + return Eigen::Success; + } + + void applyConstraintsInPlace(SparseMatrix& X, SparseMatrix& Y, + SparseMatrix& B) + { + SparseMatrix BY; + if (flag_with_B) + { + BY = B * Y; + } + else + { + BY = Y; + } + + SparseMatrix YBY = Y.transpose() * BY; + SparseMatrix BYX = BY.transpose() * X; + + SparseMatrix YBY_XYX = (Matrix(YBY).bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(Matrix(BYX))).sparseView(); + X = X - Y * YBY_XYX; + } + + /* + return + 'AB + CD' + */ + Matrix stack_4_matricies(Matrix A, Matrix B, + Matrix C, Matrix D) + { + Matrix result(A.rows() + C.rows(), A.cols() + B.cols()); + result.topLeftCorner(A.rows(), A.cols()) = A; + result.topRightCorner(B.rows(), B.cols()) = B; + result.bottomLeftCorner(C.rows(), C.cols()) = C; + result.bottomRightCorner(D.rows(), D.cols()) = D; + return result; + } + + Matrix stack_9_matricies(Matrix A, Matrix B, Matrix C, + Matrix D, Matrix E, Matrix F, + Matrix G, Matrix H, Matrix I) + { + Matrix result(A.rows() + D.rows() + G.rows(), A.cols() + B.cols() + C.cols()); + result.block(0, 0, A.rows(), A.cols()) = A; + result.block(0, A.cols(), B.rows(), B.cols()) = B; + result.block(0, A.cols() + B.cols(), C.rows(), C.cols()) = C; + result.block(A.rows(), 0, D.rows(), D.cols()) = D; + result.block(A.rows(), A.cols(), E.rows(), E.cols()) = E; + result.block(A.rows(), A.cols() + B.cols(), F.rows(), F.cols()) = F; + result.block(A.rows() + D.rows(), 0, G.rows(), G.cols()) = G; + result.block(A.rows() + D.rows(), A.cols(), H.rows(), H.cols()) = H; + result.block(A.rows() + D.rows(), A.cols() + B.cols(), I.rows(), I.cols()) = I; + + return result; + } + + void sort_epairs(Vector& evalues, Matrix& evectors, SortRule SelectionRule) + { + std::function cmp; + if (SelectionRule == SortRule::SmallestAlge) + cmp = std::less{}; + else + cmp = std::greater{}; + + std::map epairs(cmp); + for (int i = 0; i < m_evectors.cols(); ++i) + epairs.insert(std::make_pair(evalues(i), evectors.col(i))); + + int i = 0; + for (auto& epair : epairs) + { + evectors.col(i) = epair.second; + evalues(i) = epair.first; + i++; + } + } + + void removeColumns(SparseMatrix& matrix, std::vector& colToRemove) + { + // remove columns through matrix multiplication + SparseMatrix new_matrix(matrix.cols(), matrix.cols() - int(colToRemove.size())); + int iCol = 0; + std::vector> tripletList; + tripletList.reserve(matrix.cols() - int(colToRemove.size())); + + for (int iRow = 0; iRow < matrix.cols(); iRow++) + { + if (std::find(colToRemove.begin(), colToRemove.end(), iRow) == colToRemove.end()) + { + tripletList.push_back(Eigen::Triplet(iRow, iCol, 1)); + iCol++; + } + } + + new_matrix.setFromTriplets(tripletList.begin(), tripletList.end()); + matrix = matrix * new_matrix; + } + + int checkConvergence_getBlocksize(SparseMatrix& m_residuals, Scalar tolerance_L2, std::vector& columnsToDelete) + { + // square roots from sum of squares by column + int BlockSize = m_nev; + Scalar sum, buffer; + + for (int iCol = 0; iCol < m_nev; iCol++) + { + sum = 0; + for (int iRow = 0; iRow < m_n; iRow++) + { + buffer = m_residuals.coeff(iRow, iCol); + sum += buffer * buffer; + } + + if (sqrt(sum) < tolerance_L2) + { + BlockSize--; + columnsToDelete.push_back(iCol); + } + } + return BlockSize; + } + +public: + LOBPCGSolver(const SparseMatrix& A, const SparseMatrix X) : + m_n(A.rows()), + m_nev(X.cols()), + A(A), + X(X), + flag_with_constraints(false), + flag_with_B(false), + flag_with_preconditioner(false), + m_info(Eigen::InvalidInput) + { + if (A.rows() != X.rows() || A.rows() != A.cols()) + throw std::invalid_argument("Wrong size"); + + // if (m_n < 5* m_nev) + // throw std::invalid_argument("The problem size is small compared to the block size. Use standard eigensolver"); + } + + void setConstraints(const SparseMatrix& Y) + { + m_Y = Y; + flag_with_constraints = true; + } + + void setB(const SparseMatrix& B) + { + if (B.rows() != A.rows() || B.cols() != A.cols()) + throw std::invalid_argument("Wrong size"); + m_B = B; + flag_with_B = true; + } + + void setPreconditioner(const SparseMatrix& preconditioner) + { + m_preconditioner = preconditioner; + flag_with_preconditioner = true; + } + + void compute(int maxit = 10, Scalar tol_div_n = 1e-7) + { + Scalar tolerance_L2 = tol_div_n * m_n; + int BlockSize; + int max_iter = std::min(m_n, maxit); + + SparseMatrix directions, AX, AR, BX, AD, ADD, DD, BDD, BD, XAD, RAD, DAD, XBD, RBD, BR, sparse_eVecX, sparse_eVecR, sparse_eVecD, inverse_matrix; + Matrix XAR, RAR, XBR, gramA, gramB, eVecX, eVecR, eVecD; + std::vector columnsToDelete; + + if (flag_with_constraints) + { + // Apply the constraints Y to X + applyConstraintsInPlace(X, m_Y, m_B); + } + + // Make initial vectors orthonormal + // implicit BX declaration + if (orthogonalizeInPlace(X, m_B, BX) != Eigen::Success) + { + max_iter = 0; + } + + AX = A * X; + // Solve the following NxN eigenvalue problem for all N eigenvalues and -vectors: + // first approximation via a dense problem + Eigen::EigenSolver eigs(Matrix(X.transpose() * AX)); + + if (eigs.info() != Eigen::Success) + { + m_info = eigs.info(); + max_iter = 0; + } + else + { + m_evalues = eigs.eigenvalues().real(); + m_evectors = eigs.eigenvectors().real(); + sort_epairs(m_evalues, m_evectors, SortRule::SmallestAlge); + sparse_eVecX = m_evectors.sparseView(); + + X = X * sparse_eVecX; + AX = AX * sparse_eVecX; + BX = BX * sparse_eVecX; + } + + for (int iter_num = 0; iter_num < max_iter; iter_num++) + { + m_residuals.resize(m_n, m_nev); + for (int i = 0; i < m_nev; i++) + { + m_residuals.col(i) = AX.col(i) - m_evalues(i) * BX.col(i); + } + BlockSize = checkConvergence_getBlocksize(m_residuals, tolerance_L2, columnsToDelete); + + if (BlockSize == 0) + { + m_info = Eigen::Success; + break; + } + + // substitution of the original active mask + if (columnsToDelete.size() > 0) + { + removeColumns(m_residuals, columnsToDelete); + if (iter_num > 0) + { + removeColumns(directions, columnsToDelete); + removeColumns(AD, columnsToDelete); + removeColumns(BD, columnsToDelete); + } + columnsToDelete.clear(); // for next iteration + } + + if (flag_with_preconditioner) + { + // Apply the preconditioner to the residuals + m_residuals = m_preconditioner * m_residuals; + } + + if (flag_with_constraints) + { + // Apply the constraints Y to residuals + applyConstraintsInPlace(m_residuals, m_Y, m_B); + } + + if (orthogonalizeInPlace(m_residuals, m_B, BR) != Eigen::Success) + { + break; + } + AR = A * m_residuals; + + // Orthonormalize conjugate directions + if (iter_num > 0) + { + if (orthogonalizeInPlace(directions, m_B, BD, true) != Eigen::Success) + { + break; + } + AD = A * directions; + } + + // Perform the Rayleigh Ritz Procedure + XAR = Matrix(X.transpose() * AR); + RAR = Matrix(m_residuals.transpose() * AR); + XBR = Matrix(X.transpose() * BR); + + if (iter_num > 0) + { + XAD = X.transpose() * AD; + RAD = m_residuals.transpose() * AD; + DAD = directions.transpose() * AD; + XBD = X.transpose() * BD; + RBD = m_residuals.transpose() * BD; + + gramA = stack_9_matricies(m_evalues.asDiagonal(), XAR, XAD, XAR.transpose(), RAR, RAD, XAD.transpose(), RAD.transpose(), DAD.transpose()); + gramB = stack_9_matricies(Matrix::Identity(m_nev, m_nev), XBR, XBD, XBR.transpose(), Matrix::Identity(BlockSize, BlockSize), RBD, XBD.transpose(), RBD.transpose(), Matrix::Identity(BlockSize, BlockSize)); + } + else + { + gramA = stack_4_matricies(m_evalues.asDiagonal(), XAR, XAR.transpose(), RAR); + gramB = stack_4_matricies(Matrix::Identity(m_nev, m_nev), XBR, XBR.transpose(), Matrix::Identity(BlockSize, BlockSize)); + } + + // Calculate the lowest/largest m eigenpairs; Solve the generalized eigenvalue problem. + DenseSymMatProd Aop(gramA); + DenseCholesky Bop(gramB); + + SymGEigsSolver, DenseCholesky, GEigsMode::Cholesky> + geigs(Aop, Bop, m_nev, (std::min)(10, int(gramA.rows()) - 1)); + + geigs.init(); + geigs.compute(SortRule::SmallestAlge); + + // Mat evecs + if (geigs.info() == CompInfo::Successful) + { + m_evalues = geigs.eigenvalues(); + m_evectors = geigs.eigenvectors(); + sort_epairs(m_evalues, m_evectors, SortRule::SmallestAlge); + } + else + { + // Problem With General EgenVec + m_info = Eigen::NoConvergence; + break; + } + + // Compute Ritz vectors + if (iter_num > 0) + { + eVecX = m_evectors.block(0, 0, m_nev, m_nev); + eVecR = m_evectors.block(m_nev, 0, BlockSize, m_nev); + eVecD = m_evectors.block(m_nev + BlockSize, 0, BlockSize, m_nev); + + sparse_eVecX = eVecX.sparseView(); + sparse_eVecR = eVecR.sparseView(); + sparse_eVecD = eVecD.sparseView(); + + DD = m_residuals * sparse_eVecR; // new conjugate directions + ADD = AR * sparse_eVecR; + BDD = BR * sparse_eVecR; + + DD = DD + directions * sparse_eVecD; + ADD = ADD + AD * sparse_eVecD; + BDD = BDD + BD * sparse_eVecD; + } + else + { + eVecX = m_evectors.block(0, 0, m_nev, m_nev); + eVecR = m_evectors.block(m_nev, 0, BlockSize, m_nev); + + sparse_eVecX = eVecX.sparseView(); + sparse_eVecR = eVecR.sparseView(); + + DD = m_residuals * sparse_eVecR; + ADD = AR * sparse_eVecR; + BDD = BR * sparse_eVecR; + } + + X = X * sparse_eVecX + DD; + AX = AX * sparse_eVecX + ADD; + BX = BX * sparse_eVecX + BDD; + + directions = DD; + AD = ADD; + BD = BDD; + + } // iteration loop + + // calculate last residuals + m_residuals.resize(m_n, m_nev); + for (int i = 0; i < m_nev; i++) + { + m_residuals.col(i) = AX.col(i) - m_evalues(i) * BX.col(i); + } + BlockSize = checkConvergence_getBlocksize(m_residuals, tolerance_L2, columnsToDelete); + + if (BlockSize == 0) + { + m_info = Eigen::Success; + } + } // compute + + Vector eigenvalues() + { + return m_evalues; + } + + Matrix eigenvectors() + { + return m_evectors; + } + + Matrix residuals() + { + return Matrix(m_residuals); + } + + int info() { return m_info; } +}; + +} // namespace Spectra + +#endif // SPECTRA_LOBPCG_SOLVER_H diff --git a/thirdparty/include/Spectra/contrib/PartialSVDSolver.h b/thirdparty/include/Spectra/contrib/PartialSVDSolver.h new file mode 100644 index 0000000..c37d856 --- /dev/null +++ b/thirdparty/include/Spectra/contrib/PartialSVDSolver.h @@ -0,0 +1,211 @@ +// Copyright (C) 2018-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_PARTIAL_SVD_SOLVER_H +#define SPECTRA_PARTIAL_SVD_SOLVER_H + +#include +#include "../SymEigsSolver.h" + +namespace Spectra { + +// Abstract class for matrix operation +template +class SVDMatOp +{ +public: + using Scalar = Scalar_; + +private: + using Index = Eigen::Index; + +public: + virtual Index rows() const = 0; + virtual Index cols() const = 0; + + // y_out = A' * A * x_in or y_out = A * A' * x_in + virtual void perform_op(const Scalar* x_in, Scalar* y_out) const = 0; + + virtual ~SVDMatOp() {} +}; + +// Operation of a tall matrix in SVD +// We compute the eigenvalues of A' * A +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template +class SVDTallMatOp : public SVDMatOp +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + const Index m_dim; + mutable Vector m_cache; + +public: + // Constructor + SVDTallMatOp(ConstGenericMatrix& mat) : + m_mat(mat), + m_dim((std::min)(mat.rows(), mat.cols())), + m_cache(mat.rows()) + {} + + // These are the rows and columns of A' * A + Index rows() const override { return m_dim; } + Index cols() const override { return m_dim; } + + // y_out = A' * A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const override + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.cols()); + m_cache.noalias() = m_mat * x; + y.noalias() = m_mat.transpose() * m_cache; + } +}; + +// Operation of a wide matrix in SVD +// We compute the eigenvalues of A * A' +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template +class SVDWideMatOp : public SVDMatOp +{ +private: + using Index = Eigen::Index; + using Vector = Eigen::Matrix; + using MapConstVec = Eigen::Map; + using MapVec = Eigen::Map; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + const Index m_dim; + mutable Vector m_cache; + +public: + // Constructor + SVDWideMatOp(ConstGenericMatrix& mat) : + m_mat(mat), + m_dim((std::min)(mat.rows(), mat.cols())), + m_cache(mat.cols()) + {} + + // These are the rows and columns of A * A' + Index rows() const override { return m_dim; } + Index cols() const override { return m_dim; } + + // y_out = A * A' * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const override + { + MapConstVec x(x_in, m_mat.rows()); + MapVec y(y_out, m_mat.rows()); + m_cache.noalias() = m_mat.transpose() * x; + y.noalias() = m_mat * m_cache; + } +}; + +// Partial SVD solver +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template > +class PartialSVDSolver +{ +private: + using Scalar = typename MatrixType::Scalar; + using Index = Eigen::Index; + using Matrix = Eigen::Matrix; + using Vector = Eigen::Matrix; + using ConstGenericMatrix = const Eigen::Ref; + + ConstGenericMatrix m_mat; + const Index m_m; + const Index m_n; + SVDMatOp* m_op; + SymEigsSolver>* m_eigs; + Index m_nconv; + Matrix m_evecs; + +public: + // Constructor + PartialSVDSolver(ConstGenericMatrix& mat, Index ncomp, Index ncv) : + m_mat(mat), m_m(mat.rows()), m_n(mat.cols()), m_evecs(0, 0) + { + // Determine the matrix type, tall or wide + if (m_m > m_n) + { + m_op = new SVDTallMatOp(mat); + } + else + { + m_op = new SVDWideMatOp(mat); + } + + // Solver object + m_eigs = new SymEigsSolver>(*m_op, ncomp, ncv); + } + + // Destructor + virtual ~PartialSVDSolver() + { + delete m_eigs; + delete m_op; + } + + // Computation + Index compute(Index maxit = 1000, Scalar tol = 1e-10) + { + m_eigs->init(); + m_nconv = m_eigs->compute(SortRule::LargestAlge, maxit, tol); + + return m_nconv; + } + + // The converged singular values + Vector singular_values() const + { + Vector svals = m_eigs->eigenvalues().cwiseSqrt(); + + return svals; + } + + // The converged left singular vectors + Matrix matrix_U(Index nu) + { + if (m_evecs.cols() < 1) + { + m_evecs = m_eigs->eigenvectors(); + } + nu = (std::min)(nu, m_nconv); + if (m_m <= m_n) + { + return m_evecs.leftCols(nu); + } + + return m_mat * (m_evecs.leftCols(nu).array().rowwise() / m_eigs->eigenvalues().head(nu).transpose().array().sqrt()).matrix(); + } + + // The converged right singular vectors + Matrix matrix_V(Index nv) + { + if (m_evecs.cols() < 1) + { + m_evecs = m_eigs->eigenvectors(); + } + nv = (std::min)(nv, m_nconv); + if (m_m > m_n) + { + return m_evecs.leftCols(nv); + } + + return m_mat.transpose() * (m_evecs.leftCols(nv).array().rowwise() / m_eigs->eigenvalues().head(nv).transpose().array().sqrt()).matrix(); + } +}; + +} // namespace Spectra + +#endif // SPECTRA_PARTIAL_SVD_SOLVER_H