numerics
Loading...
Searching...
No Matches
lanczos.cpp
Go to the documentation of this file.
1/// @file eigen/lanczos.cpp
2/// @brief Lanczos algorithm with full reorthogonalisation
3///
4/// The k-step Lanczos recurrence:
5///
6/// beta_0 = 0, v_0 = 0
7/// v_1 = random unit vector
8/// for j = 1..k:
9/// w = A*vⱼ
10/// alphaⱼ = vⱼᵀ*w
11/// w = w - alphaⱼ*vⱼ - betaⱼ₋_1*vⱼ₋_1
12/// [reorthogonalise w against all previous v_1..vⱼ]
13/// betaⱼ = ||w||
14/// vⱼ₊_1 = w / betaⱼ
15///
16/// This builds the symmetric tridiagonal T_k = Vₖᵀ A Vₖ with diagonal alpha
17/// and off-diagonal beta. The Ritz values are the eigenvalues of T_k and the
18/// Ritz vectors are Vₖ * (eigenvectors of T_k).
19///
20/// T_k is diagonalised with eig_sym (Jacobi), which is O(k^3) -- cheap for k << n.
21
24#include <cmath>
25#include <stdexcept>
26#include <algorithm>
27
28namespace num {
29
31 real tol, idx max_steps, Backend /*backend*/) {
32 if (k == 0 || k > n)
33 throw std::invalid_argument("lanczos: k must satisfy 0 < k <= n");
34
35 if (max_steps == 0) max_steps = std::min(3*k, n);
36 max_steps = std::min(max_steps, n);
37
38 // Lanczos basis: columns of V (n x max_steps)
39 // Store as a flat array for efficient column access
40 Matrix V(n, max_steps, 0.0);
41
42 // alpha (diagonal) and beta (off-diagonal) of the tridiagonal T
44 Vector beta(max_steps, 0.0); // beta[j] = beta_{j+1}, beta[0] unused
45
46 // Initialise v_1 = e_1 (deterministic, avoids Rng dependency)
47 for (idx i = 0; i < n; ++i) V(i, 0) = (i == 0) ? 1.0 : 0.0;
48
49 idx steps = 0;
50
51 for (idx j = 0; j < max_steps; ++j) {
52 // Extract current Lanczos vector v_j from column j of V
53 Vector vj(n);
54 for (idx i = 0; i < n; ++i) vj[i] = V(i, j);
55
56 // w = A * vj
57 Vector w(n, 0.0);
58 matvec(vj, w);
59
60 // alphaⱼ = vⱼᵀ * w
61 real a = 0;
62 for (idx i = 0; i < n; ++i) a += vj[i] * w[i];
63 alpha[j] = a;
64
65 // w = w - alphaⱼ*vⱼ - betaⱼ₋_1*vⱼ₋_1
66 for (idx i = 0; i < n; ++i) w[i] -= a * vj[i];
67 if (j > 0) {
68 for (idx i = 0; i < n; ++i) w[i] -= beta[j-1] * V(i, j-1);
69 }
70
71 // Full reorthogonalisation (modified Gram-Schmidt against all previous)
72 for (idx l = 0; l <= j; ++l) {
73 real proj = 0;
74 for (idx i = 0; i < n; ++i) proj += V(i, l) * w[i];
75 for (idx i = 0; i < n; ++i) w[i] -= proj * V(i, l);
76 }
77
78 // betaⱼ = ||w||
79 real b = 0;
80 for (idx i = 0; i < n; ++i) b += w[i]*w[i];
81 b = std::sqrt(b);
82
83 ++steps;
84
85 // Invariant subspace found -- can't continue
86 if (b < 1e-12) break;
87
88 beta[j] = b;
89
90 // Store v_{j+1} if room remains
91 if (j + 1 < max_steps) {
92 for (idx i = 0; i < n; ++i) V(i, j+1) = w[i] / b;
93 }
94 }
95
96 // Build kxk tridiagonal matrix T_k from (alpha, beta) and compute its eigendecomposition
97 idx m = std::min(steps, k);
98 Matrix T(m, m, 0.0);
99 for (idx j = 0; j < m; ++j) {
100 T(j, j) = alpha[j];
101 if (j + 1 < m) {
102 T(j, j+1) = beta[j];
103 T(j+1, j) = beta[j];
104 }
105 }
106
107 EigenResult teig = eig_sym(T, tol * 1e-2); // tighter tol for inner solve
108
109 // Ritz vectors: V_m * (eigenvectors of T_m)
110 // Each Ritz vector u_i = sum_j T_eig.vectors[j,i] * V[:,j]
111 Matrix ritz_vecs(n, m, 0.0);
112 for (idx i = 0; i < m; ++i) {
113 for (idx j = 0; j < m; ++j) {
114 real coeff = teig.vectors(j, i);
115 for (idx r = 0; r < n; ++r)
116 ritz_vecs(r, i) += coeff * V(r, j);
117 }
118 }
119
120 // Check convergence of each Ritz pair: ||A*u - lambda*u||
121 bool all_converged = true;
122 for (idx i = 0; i < m; ++i) {
123 Vector u(n);
124 for (idx r = 0; r < n; ++r) u[r] = ritz_vecs(r, i);
125
126 Vector Au(n, 0.0);
127 matvec(u, Au);
128
129 real res = 0;
130 real lam = teig.values[i];
131 for (idx r = 0; r < n; ++r) {
132 real d = Au[r] - lam * u[r];
133 res += d*d;
134 }
135 if (std::sqrt(res) > tol) { all_converged = false; break; }
136 }
137
138 return {teig.values, ritz_vecs, steps, all_converged};
139}
140
141} // namespace num
Dense row-major matrix with optional GPU storage.
Definition matrix.hpp:12
Full symmetric eigendecomposition via cyclic Jacobi sweeps.
Lanczos algorithm – k extreme eigenvalues of a large sparse matrix.
double real
Definition types.hpp:10
Backend
Selects which backend handles a linalg operation.
Definition policy.hpp:19
real beta(real a, real b)
B(a, b) – beta function.
Definition math.hpp:242
constexpr T ipow(T x) noexcept
Compute x^N at compile time via repeated squaring.
std::size_t idx
Definition types.hpp:11
void matvec(const Matrix &A, const Vector &x, Vector &y, Backend b=default_backend)
y = A * x
Definition matrix.cpp:94
constexpr real e
Definition math.hpp:41
EigenResult eig_sym(const Matrix &A, real tol=1e-12, idx max_sweeps=100, Backend backend=Backend::seq)
Full eigendecomposition of a real symmetric matrix.
LanczosResult lanczos(MatVecFn matvec, idx n, idx k, real tol=1e-10, idx max_steps=0, Backend backend=Backend::seq)
Lanczos eigensolver for large sparse symmetric matrices.
Definition lanczos.cpp:30
std::function< void(const Vector &, Vector &)> MatVecFn
Callable type for matrix-free matvec: computes y = A*x.
Definition cg.hpp:13
Full eigendecomposition result: A = V * diag(values) * V^T.
Result of a Lanczos eigensolver.
Definition lanczos.hpp:32