Spectra 1.2.0
Header-only C++ Library for Large Scale Eigenvalue Problems
HermEigsBase.h
1// Copyright (C) 2018-2025 Yixuan Qiu <yixuan.qiu@cos.name>
2//
3// This Source Code Form is subject to the terms of the Mozilla
4// Public License v. 2.0. If a copy of the MPL was not distributed
5// with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7#ifndef SPECTRA_HERM_EIGS_BASE_H
8#define SPECTRA_HERM_EIGS_BASE_H
9
10#include <Eigen/Core>
11#include <vector> // std::vector
12#include <cmath> // std::abs, std::pow
13#include <algorithm> // std::min
14#include <stdexcept> // std::invalid_argument
15#include <utility> // std::move
16
17#include "Util/Version.h"
18#include "Util/TypeTraits.h"
19#include "Util/SelectionRule.h"
20#include "Util/CompInfo.h"
21#include "Util/SimpleRandom.h"
22#include "MatOp/internal/ArnoldiOp.h"
23#include "LinAlg/UpperHessenbergQR.h"
24#include "LinAlg/TridiagEigen.h"
25#include "LinAlg/Lanczos.h"
26
27namespace Spectra {
28
34
43template <typename OpType, typename BOpType>
45{
46private:
47 // Scalar is the type of the matrix element
48 // Can be real or complex
49 using Scalar = typename OpType::Scalar;
50 // The real part type of the matrix element, e.g.,
51 // Scalar = double => RealScalar = double
52 // Scalar = std::complex<double> => RealScalar = double
53 // The eigenvalues are known to be real numbers
54 using RealScalar = typename Eigen::NumTraits<Scalar>::Real;
55 using Index = Eigen::Index;
56 using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
57 using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
58 using RealMatrix = Eigen::Matrix<RealScalar, Eigen::Dynamic, Eigen::Dynamic>;
59 using RealVector = Eigen::Matrix<RealScalar, Eigen::Dynamic, 1>;
60 using RealArray = Eigen::Array<RealScalar, Eigen::Dynamic, 1>;
61 using BoolArray = Eigen::Array<bool, Eigen::Dynamic, 1>;
62 using MapMat = Eigen::Map<Matrix>;
63 using MapVec = Eigen::Map<Vector>;
64 using MapConstVec = Eigen::Map<const Vector>;
65
66 using ArnoldiOpType = ArnoldiOp<OpType, BOpType>;
67 using LanczosFac = Lanczos<ArnoldiOpType>;
68
69protected:
70 // clang-format off
71
72 // In SymEigsSolver and SymEigsShiftSolver, the A operator is an lvalue provided by
73 // the user. In SymGEigsSolver, the A operator is an rvalue. To avoid copying objects,
74 // we use the following scheme:
75 // 1. If the op parameter in the constructor is an lvalue, make m_op a const reference to op
76 // 2. If op is an rvalue, move op to m_op_container, and then make m_op a const
77 // reference to m_op_container[0]
78 std::vector<OpType> m_op_container;
79 const OpType& m_op; // matrix operator for A
80 const Index m_n; // dimension of matrix A
81 const Index m_nev; // number of eigenvalues requested
82 const Index m_ncv; // dimension of Krylov subspace in the Lanczos method
83 Index m_nmatop; // number of matrix operations called
84 Index m_niter; // number of restarting iterations
85
86 LanczosFac m_fac; // Lanczos factorization
87 RealVector m_ritz_val; // Ritz values
88
89private:
90 RealMatrix m_ritz_vec; // Ritz vectors
91 RealVector m_ritz_est; // last row of m_ritz_vec, also called the Ritz estimates
92 BoolArray m_ritz_conv; // indicator of the convergence of Ritz values
93 CompInfo m_info; // status of the computation
94 // clang-format on
95
96 // Move rvalue object to the container
97 static std::vector<OpType> create_op_container(OpType&& rval)
98 {
99 std::vector<OpType> container;
100 container.emplace_back(std::move(rval));
101 return container;
102 }
103
104 // Implicitly restarted Lanczos factorization
105 void restart(Index k, SortRule selection)
106 {
107 using std::abs;
108
109 if (k >= m_ncv)
110 return;
111
112 // QR decomposition on a real symmetric matrix
113 TridiagQR<RealScalar> decomp(m_ncv);
114 // Q is a real orthogonal matrix
115 RealMatrix Q = RealMatrix::Identity(m_ncv, m_ncv);
116
117 // Apply large shifts first
118 const int nshift = m_ncv - k;
119 RealVector shifts = m_ritz_val.tail(nshift);
120 std::sort(shifts.data(), shifts.data() + nshift,
121 [](const RealScalar& v1, const RealScalar& v2) { return abs(v1) > abs(v2); });
122
123 // Apply shifts and update H and Q
124 for (Index i = 0; i < nshift; i++)
125 {
126 // QR decomposition of H - mu[i] * I, mu[i] is the shift
127 // H is known to be a real symmetric matrix
128 decomp.compute(m_fac.matrix_H().real(), shifts[i]);
129
130 // Q <- Q * Qi
131 decomp.apply_YQ(Q);
132 // H <- Qi' * H * Qi
133 // Since Qi * Ri = H - mu[i] * I, we have H = Qi * Ri + mu[i] * I,
134 // and then Qi' * H * Qi = Ri * Qi + mu[i] * I
135 m_fac.compress_H(decomp);
136 // Note that in our setting, mu[i] is an eigenvalue of H,
137 // so after applying Qi' * H * Qi, H must be of the following form
138 // H = [X 0 0]
139 // [0 mu 0]
140 // [0 0 D]
141 // Then we can force H[k, k-1] = H[k-1, k] = 0 and H[k, k] = mu,
142 // where k is the size of X
143 //
144 // Currently disabled due to numerical stability
145 //
146 // m_fac.deflate_H(m_ncv - i - 1, shifts[i]);
147 }
148 // Apply orthogonal transformation to V, V <- V * Q
149 m_fac.compress_V(Q);
150 // It can be verified that the updated V and H admit a k-step
151 // Arnoldi factorization, and we expand it to m-step
152 m_fac.factorize_from(k, m_ncv, m_nmatop);
153 // Retrieve the new Ritz pairs
154 retrieve_ritzpair(selection);
155 }
156
157 // Calculates the number of converged Ritz values
158 Index num_converged(const RealScalar& tol)
159 {
160 using std::pow;
161
162 // The machine precision, ~= 1e-16 for the "double" type
163 const RealScalar eps = TypeTraits<RealScalar>::epsilon();
164 // std::pow() is not constexpr, so we do not declare eps23 to be constexpr
165 // But most compilers should be able to compute eps23 at compile time
166 const RealScalar eps23 = pow(eps, RealScalar(2) / 3);
167
168 // thresh = tol * max(eps23, abs(theta)), theta for Ritz value
169 RealArray thresh = tol * m_ritz_val.head(m_nev).array().abs().max(eps23);
170 RealArray resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm();
171 // Converged "wanted" Ritz values
172 m_ritz_conv = (resid < thresh);
173
174 return m_ritz_conv.count();
175 }
176
177 // Returns the adjusted nev for restarting
178 Index nev_adjusted(Index nconv)
179 {
180 using std::abs;
181
182 // A very small value, but 1.0 / near_0 does not overflow
183 // ~= 1e-307 for the "double" type
184 const RealScalar near_0 = TypeTraits<RealScalar>::min() * RealScalar(10);
185
186 Index nev_new = m_nev;
187 for (Index i = m_nev; i < m_ncv; i++)
188 if (abs(m_ritz_est[i]) < near_0)
189 nev_new++;
190
191 // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK
192 nev_new += (std::min)(nconv, (m_ncv - nev_new) / 2);
193 if (nev_new == 1 && m_ncv >= 6)
194 nev_new = m_ncv / 2;
195 else if (nev_new == 1 && m_ncv > 2)
196 nev_new = 2;
197
198 if (nev_new > m_ncv - 1)
199 nev_new = m_ncv - 1;
200
201 return nev_new;
202 }
203
204 // Retrieves and sorts Ritz values and Ritz vectors
205 void retrieve_ritzpair(SortRule selection)
206 {
207 TridiagEigen<RealScalar> decomp(m_fac.matrix_H().real());
208 const RealVector& evals = decomp.eigenvalues();
209 const RealMatrix& evecs = decomp.eigenvectors();
210
211 // Sort Ritz values and put the wanted ones at the beginning
212 std::vector<Index> ind = argsort(selection, evals, m_ncv);
213
214 // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively
215 for (Index i = 0; i < m_ncv; i++)
216 {
217 m_ritz_val[i] = evals[ind[i]];
218 m_ritz_est[i] = evecs(m_ncv - 1, ind[i]);
219 }
220 for (Index i = 0; i < m_nev; i++)
221 {
222 m_ritz_vec.col(i).noalias() = evecs.col(ind[i]);
223 }
224 }
225
226protected:
227 // Sorts the first nev Ritz pairs in the specified order
228 // This is used to return the final results
229 virtual void sort_ritzpair(SortRule sort_rule)
230 {
231 if ((sort_rule != SortRule::LargestAlge) && (sort_rule != SortRule::LargestMagn) &&
232 (sort_rule != SortRule::SmallestAlge) && (sort_rule != SortRule::SmallestMagn))
233 throw std::invalid_argument("unsupported sorting rule");
234
235 std::vector<Index> ind = argsort(sort_rule, m_ritz_val, m_nev);
236
237 RealVector new_ritz_val(m_ncv);
238 RealMatrix new_ritz_vec(m_ncv, m_nev);
239 BoolArray new_ritz_conv(m_nev);
240
241 for (Index i = 0; i < m_nev; i++)
242 {
243 new_ritz_val[i] = m_ritz_val[ind[i]];
244 new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]);
245 new_ritz_conv[i] = m_ritz_conv[ind[i]];
246 }
247
248 m_ritz_val.swap(new_ritz_val);
249 m_ritz_vec.swap(new_ritz_vec);
250 m_ritz_conv.swap(new_ritz_conv);
251 }
252
253public:
255
256 // If op is an lvalue
257 HermEigsBase(OpType& op, const BOpType& Bop, Index nev, Index ncv) :
258 m_op(op),
259 m_n(op.rows()),
260 m_nev(nev),
261 m_ncv(ncv > m_n ? m_n : ncv),
262 m_nmatop(0),
263 m_niter(0),
264 m_fac(ArnoldiOpType(op, Bop), m_ncv),
266 {
267 if (nev < 1 || nev > m_n - 1)
268 throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix");
269
270 if (ncv <= nev || ncv > m_n)
271 throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix");
272 }
273
274 // If op is an rvalue
275 HermEigsBase(OpType&& op, const BOpType& Bop, Index nev, Index ncv) :
276 m_op_container(create_op_container(std::move(op))),
277 m_op(m_op_container.front()),
278 m_n(m_op.rows()),
279 m_nev(nev),
280 m_ncv(ncv > m_n ? m_n : ncv),
281 m_nmatop(0),
282 m_niter(0),
283 m_fac(ArnoldiOpType(m_op, Bop), m_ncv),
285 {
286 if (nev < 1 || nev > m_n - 1)
287 throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix");
288
289 if (ncv <= nev || ncv > m_n)
290 throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix");
291 }
292
296 virtual ~HermEigsBase() {}
297
299
309 void init(const Scalar* init_resid)
310 {
311 // Reset all matrices/vectors to zero
312 m_ritz_val.resize(m_ncv);
313 m_ritz_vec.resize(m_ncv, m_nev);
314 m_ritz_est.resize(m_ncv);
315 m_ritz_conv.resize(m_nev);
316
317 m_ritz_val.setZero();
318 m_ritz_vec.setZero();
319 m_ritz_est.setZero();
320 m_ritz_conv.setZero();
321
322 m_nmatop = 0;
323 m_niter = 0;
324
325 // Initialize the Lanczos factorization
326 MapConstVec v0(init_resid, m_n);
327 m_fac.init(v0, m_nmatop);
328 }
329
337 void init()
338 {
339 SimpleRandom<Scalar> rng(0);
340 Vector init_resid = rng.random_vec(m_n);
341 init(init_resid.data());
342 }
343
366 Index compute(SortRule selection = SortRule::LargestMagn, Index maxit = 1000,
367 RealScalar tol = 1e-10, SortRule sorting = SortRule::LargestAlge)
368 {
369 // The m-step Lanczos factorization
370 m_fac.factorize_from(1, m_ncv, m_nmatop);
371 retrieve_ritzpair(selection);
372 // Restarting
373 Index i, nconv = 0, nev_adj;
374 for (i = 0; i < maxit; i++)
375 {
376 nconv = num_converged(tol);
377 if (nconv >= m_nev)
378 break;
379
380 nev_adj = nev_adjusted(nconv);
381 restart(nev_adj, selection);
382 }
383 // Sorting results
384 sort_ritzpair(sorting);
385
386 m_niter += (i + 1);
387 m_info = (nconv >= m_nev) ? CompInfo::Successful : CompInfo::NotConverging;
388
389 return (std::min)(m_nev, nconv);
390 }
391
396 CompInfo info() const { return m_info; }
397
401 Index num_iterations() const { return m_niter; }
402
406 Index num_operations() const { return m_nmatop; }
407
417 RealVector eigenvalues() const
418 {
419 const Index nconv = m_ritz_conv.count();
420 RealVector res(nconv);
421
422 if (!nconv)
423 return res;
424
425 Index j = 0;
426 for (Index i = 0; i < m_nev; i++)
427 {
428 if (m_ritz_conv[i])
429 {
430 res[j] = m_ritz_val[i];
431 j++;
432 }
433 }
434
435 return res;
436 }
437
447 virtual Matrix eigenvectors(Index nvec) const
448 {
449 const Index nconv = m_ritz_conv.count();
450 nvec = (std::min)(nvec, nconv);
451 Matrix res(m_n, nvec);
452
453 if (!nvec)
454 return res;
455
456 RealMatrix ritz_vec_conv(m_ncv, nvec);
457 Index j = 0;
458 for (Index i = 0; i < m_nev && j < nvec; i++)
459 {
460 if (m_ritz_conv[i])
461 {
462 ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i);
463 j++;
464 }
465 }
466
467 res.noalias() = m_fac.matrix_V() * ritz_vec_conv;
468
469 return res;
470 }
471
475 virtual Matrix eigenvectors() const
476 {
477 return eigenvectors(m_nev);
478 }
479};
480
481} // namespace Spectra
482
483#endif // SPECTRA_HERM_EIGS_BASE_H
Index num_iterations() const
Index num_operations() const
void init(const Scalar *init_resid)
RealVector eigenvalues() const
Index compute(SortRule selection=SortRule::LargestMagn, Index maxit=1000, RealScalar tol=1e-10, SortRule sorting=SortRule::LargestAlge)
virtual Matrix eigenvectors(Index nvec) const
virtual Matrix eigenvectors() const
CompInfo info() const
@ SmallestAlge
Select eigenvalues with smallest algebraic value. Only for symmetric eigen solvers.
@ Successful
Computation was successful.
Definition CompInfo.h:19