Spectra 1.1.0
Header-only C++ Library for Large Scale Eigenvalue Problems
Loading...
Searching...
No Matches
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 using Scalar = typename OpType::Scalar;
48 // The real part type of the matrix element, e.g.,
49 // Scalar = double => RealScalar = double
50 // Scalar = std::complex<double> => RealScalar = double
51 using RealScalar = typename Eigen::NumTraits<Scalar>::Real;
52 using Index = Eigen::Index;
53 using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
54 using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
55 using RealMatrix = Eigen::Matrix<RealScalar, Eigen::Dynamic, Eigen::Dynamic>;
56 using RealVector = Eigen::Matrix<RealScalar, Eigen::Dynamic, 1>;
57 using RealArray = Eigen::Array<RealScalar, Eigen::Dynamic, 1>;
58 using BoolArray = Eigen::Array<bool, Eigen::Dynamic, 1>;
59 using MapMat = Eigen::Map<Matrix>;
60 using MapVec = Eigen::Map<Vector>;
61 using MapConstVec = Eigen::Map<const Vector>;
62
63 using ArnoldiOpType = ArnoldiOp<Scalar, OpType, BOpType>;
64 using LanczosFac = Lanczos<Scalar, ArnoldiOpType>;
65
66protected:
67 // clang-format off
68
69 // In SymEigsSolver and SymEigsShiftSolver, the A operator is an lvalue provided by
70 // the user. In SymGEigsSolver, the A operator is an rvalue. To avoid copying objects,
71 // we use the following scheme:
72 // 1. If the op parameter in the constructor is an lvalue, make m_op a const reference to op
73 // 2. If op is an rvalue, move op to m_op_container, and then make m_op a const
74 // reference to m_op_container[0]
75 std::vector<OpType> m_op_container;
76 const OpType& m_op; // matrix operator for A
77 const Index m_n; // dimension of matrix A
78 const Index m_nev; // number of eigenvalues requested
79 const Index m_ncv; // dimension of Krylov subspace in the Lanczos method
80 Index m_nmatop; // number of matrix operations called
81 Index m_niter; // number of restarting iterations
82
83 LanczosFac m_fac; // Lanczos factorization
84 RealVector m_ritz_val; // Ritz values
85
86private:
87 RealMatrix m_ritz_vec; // Ritz vectors
88 RealVector m_ritz_est; // last row of m_ritz_vec, also called the Ritz estimates
89 BoolArray m_ritz_conv; // indicator of the convergence of Ritz values
90 CompInfo m_info; // status of the computation
91 // clang-format on
92
93 // Move rvalue object to the container
94 static std::vector<OpType> create_op_container(OpType&& rval)
95 {
96 std::vector<OpType> container;
97 container.emplace_back(std::move(rval));
98 return container;
99 }
100
101 // Implicitly restarted Lanczos factorization
102 void restart(Index k, SortRule selection)
103 {
104 using std::abs;
105
106 if (k >= m_ncv)
107 return;
108
109 // QR decomposition on a real symmetric matrix
110 TridiagQR<RealScalar> decomp(m_ncv);
111 // Q is a real orthogonal matrix
112 RealMatrix Q = RealMatrix::Identity(m_ncv, m_ncv);
113
114 // Apply large shifts first
115 const int nshift = m_ncv - k;
116 RealVector shifts = m_ritz_val.tail(nshift);
117 std::sort(shifts.data(), shifts.data() + nshift,
118 [](const RealScalar& v1, const RealScalar& v2) { return abs(v1) > abs(v2); });
119
120 for (Index i = 0; i < nshift; i++)
121 {
122 // QR decomposition of H-mu*I, mu is the shift
123 // H is known to be a real symmetric matrix
124 decomp.compute(m_fac.matrix_H().real(), shifts[i]);
125
126 // Q -> Q * Qi
127 decomp.apply_YQ(Q);
128 // H -> Q'HQ
129 // Since QR = H - mu * I, we have H = QR + mu * I
130 // and therefore Q'HQ = RQ + mu * I
131 m_fac.compress_H(decomp);
132 // Note that in our setting, mu is an eigenvalue of H,
133 // so after applying Q'HQ, H must have be of the following form
134 // H = [X 0 0]
135 // [0 mu 0]
136 // [0 0 D]
137 // Then we can force H[k, k-1] = H[k-1, k] = 0 and H[k, k] = mu,
138 // where k is the size of X
139 //
140 // Currently disabled due to numerical stability
141 //
142 // m_fac.deflate_H(m_ncv - i - 1, shifts[i]);
143 }
144
145 m_fac.compress_V(Q);
146 m_fac.factorize_from(k, m_ncv, m_nmatop);
147
148 retrieve_ritzpair(selection);
149 }
150
151 // Calculates the number of converged Ritz values
152 Index num_converged(const RealScalar& tol)
153 {
154 using std::pow;
155
156 // The machine precision, ~= 1e-16 for the "double" type
157 const RealScalar eps = TypeTraits<RealScalar>::epsilon();
158 // std::pow() is not constexpr, so we do not declare eps23 to be constexpr
159 // But most compilers should be able to compute eps23 at compile time
160 const RealScalar eps23 = pow(eps, RealScalar(2) / 3);
161
162 // thresh = tol * max(eps23, abs(theta)), theta for Ritz value
163 RealArray thresh = tol * m_ritz_val.head(m_nev).array().abs().max(eps23);
164 RealArray resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm();
165 // Converged "wanted" Ritz values
166 m_ritz_conv = (resid < thresh);
167
168 return m_ritz_conv.count();
169 }
170
171 // Returns the adjusted nev for restarting
172 Index nev_adjusted(Index nconv)
173 {
174 using std::abs;
175
176 // A very small value, but 1.0 / near_0 does not overflow
177 // ~= 1e-307 for the "double" type
178 const RealScalar near_0 = TypeTraits<RealScalar>::min() * RealScalar(10);
179
180 Index nev_new = m_nev;
181 for (Index i = m_nev; i < m_ncv; i++)
182 if (abs(m_ritz_est[i]) < near_0)
183 nev_new++;
184
185 // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK
186 nev_new += (std::min)(nconv, (m_ncv - nev_new) / 2);
187 if (nev_new == 1 && m_ncv >= 6)
188 nev_new = m_ncv / 2;
189 else if (nev_new == 1 && m_ncv > 2)
190 nev_new = 2;
191
192 if (nev_new > m_ncv - 1)
193 nev_new = m_ncv - 1;
194
195 return nev_new;
196 }
197
198 // Retrieves and sorts Ritz values and Ritz vectors
199 void retrieve_ritzpair(SortRule selection)
200 {
201 TridiagEigen<RealScalar> decomp(m_fac.matrix_H().real());
202 const RealVector& evals = decomp.eigenvalues();
203 const RealMatrix& evecs = decomp.eigenvectors();
204
205 // Sort Ritz values and put the wanted ones at the beginning
206 std::vector<Index> ind = argsort(selection, evals, m_ncv);
207
208 // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively
209 for (Index i = 0; i < m_ncv; i++)
210 {
211 m_ritz_val[i] = evals[ind[i]];
212 m_ritz_est[i] = evecs(m_ncv - 1, ind[i]);
213 }
214 for (Index i = 0; i < m_nev; i++)
215 {
216 m_ritz_vec.col(i).noalias() = evecs.col(ind[i]);
217 }
218 }
219
220protected:
221 // Sorts the first nev Ritz pairs in the specified order
222 // This is used to return the final results
223 virtual void sort_ritzpair(SortRule sort_rule)
224 {
225 if ((sort_rule != SortRule::LargestAlge) && (sort_rule != SortRule::LargestMagn) &&
226 (sort_rule != SortRule::SmallestAlge) && (sort_rule != SortRule::SmallestMagn))
227 throw std::invalid_argument("unsupported sorting rule");
228
229 std::vector<Index> ind = argsort(sort_rule, m_ritz_val, m_nev);
230
231 RealVector new_ritz_val(m_ncv);
232 RealMatrix new_ritz_vec(m_ncv, m_nev);
233 BoolArray new_ritz_conv(m_nev);
234
235 for (Index i = 0; i < m_nev; i++)
236 {
237 new_ritz_val[i] = m_ritz_val[ind[i]];
238 new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]);
239 new_ritz_conv[i] = m_ritz_conv[ind[i]];
240 }
241
242 m_ritz_val.swap(new_ritz_val);
243 m_ritz_vec.swap(new_ritz_vec);
244 m_ritz_conv.swap(new_ritz_conv);
245 }
246
247public:
249
250 // If op is an lvalue
251 HermEigsBase(OpType& op, const BOpType& Bop, Index nev, Index ncv) :
252 m_op(op),
253 m_n(op.rows()),
254 m_nev(nev),
255 m_ncv(ncv > m_n ? m_n : ncv),
256 m_nmatop(0),
257 m_niter(0),
258 m_fac(ArnoldiOpType(op, Bop), m_ncv),
260 {
261 if (nev < 1 || nev > m_n - 1)
262 throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix");
263
264 if (ncv <= nev || ncv > m_n)
265 throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix");
266 }
267
268 // If op is an rvalue
269 HermEigsBase(OpType&& op, const BOpType& Bop, Index nev, Index ncv) :
270 m_op_container(create_op_container(std::move(op))),
271 m_op(m_op_container.front()),
272 m_n(m_op.rows()),
273 m_nev(nev),
274 m_ncv(ncv > m_n ? m_n : ncv),
275 m_nmatop(0),
276 m_niter(0),
277 m_fac(ArnoldiOpType(m_op, Bop), m_ncv),
279 {
280 if (nev < 1 || nev > m_n - 1)
281 throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix");
282
283 if (ncv <= nev || ncv > m_n)
284 throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix");
285 }
286
290 virtual ~HermEigsBase() {}
291
293
303 void init(const Scalar* init_resid)
304 {
305 // Reset all matrices/vectors to zero
306 m_ritz_val.resize(m_ncv);
307 m_ritz_vec.resize(m_ncv, m_nev);
308 m_ritz_est.resize(m_ncv);
309 m_ritz_conv.resize(m_nev);
310
311 m_ritz_val.setZero();
312 m_ritz_vec.setZero();
313 m_ritz_est.setZero();
314 m_ritz_conv.setZero();
315
316 m_nmatop = 0;
317 m_niter = 0;
318
319 // Initialize the Lanczos factorization
320 MapConstVec v0(init_resid, m_n);
321 m_fac.init(v0, m_nmatop);
322 }
323
331 void init()
332 {
333 SimpleRandom<Scalar> rng(0);
334 Vector init_resid = rng.random_vec(m_n);
335 init(init_resid.data());
336 }
337
360 Index compute(SortRule selection = SortRule::LargestMagn, Index maxit = 1000,
361 RealScalar tol = 1e-10, SortRule sorting = SortRule::LargestAlge)
362 {
363 // The m-step Lanczos factorization
364 m_fac.factorize_from(1, m_ncv, m_nmatop);
365 retrieve_ritzpair(selection);
366 // Restarting
367 Index i, nconv = 0, nev_adj;
368 for (i = 0; i < maxit; i++)
369 {
370 nconv = num_converged(tol);
371 if (nconv >= m_nev)
372 break;
373
374 nev_adj = nev_adjusted(nconv);
375 restart(nev_adj, selection);
376 }
377 // Sorting results
378 sort_ritzpair(sorting);
379
380 m_niter += i + 1;
381 m_info = (nconv >= m_nev) ? CompInfo::Successful : CompInfo::NotConverging;
382
383 return (std::min)(m_nev, nconv);
384 }
385
390 CompInfo info() const { return m_info; }
391
395 Index num_iterations() const { return m_niter; }
396
400 Index num_operations() const { return m_nmatop; }
401
411 RealVector eigenvalues() const
412 {
413 const Index nconv = m_ritz_conv.count();
414 RealVector res(nconv);
415
416 if (!nconv)
417 return res;
418
419 Index j = 0;
420 for (Index i = 0; i < m_nev; i++)
421 {
422 if (m_ritz_conv[i])
423 {
424 res[j] = m_ritz_val[i];
425 j++;
426 }
427 }
428
429 return res;
430 }
431
441 virtual Matrix eigenvectors(Index nvec) const
442 {
443 const Index nconv = m_ritz_conv.count();
444 nvec = (std::min)(nvec, nconv);
445 Matrix res(m_n, nvec);
446
447 if (!nvec)
448 return res;
449
450 RealMatrix ritz_vec_conv(m_ncv, nvec);
451 Index j = 0;
452 for (Index i = 0; i < m_nev && j < nvec; i++)
453 {
454 if (m_ritz_conv[i])
455 {
456 ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i);
457 j++;
458 }
459 }
460
461 res.noalias() = m_fac.matrix_V() * ritz_vec_conv;
462
463 return res;
464 }
465
469 virtual Matrix eigenvectors() const
470 {
471 return eigenvectors(m_nev);
472 }
473};
474
475} // namespace Spectra
476
477#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