numerics 0.1.0
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_j
10/// alpha_j = v_j^T * w
11/// w = w - alpha_j*v_j - beta_{j-1}*v_{j-1}
12/// [reorthogonalise w against all previous v_1..v_j]
13/// beta_j = ||w||
14/// v_{j+1} = w / beta_j
15///
16/// This builds the symmetric tridiagonal T_k = V_k^T * A * V_k with diagonal
17/// alpha and off-diagonal beta. The Ritz values are the eigenvalues of T_k and
18/// the Ritz vectors are V_k * (eigenvectors of T_k).
19///
20/// T_k is diagonalised with eig_sym (Jacobi), which is O(k^3) -- cheap for k
21/// << n.
22
24#include "kernel/subspace.hpp"
26#include <cmath>
27#include <stdexcept>
28#include <algorithm>
29
30namespace num {
31
33 idx n,
34 idx k,
35 real tol,
36 idx max_steps,
37 Backend /*backend*/) {
38 if (k == 0 || k > n)
39 throw std::invalid_argument("lanczos: k must satisfy 0 < k <= n");
40
41 if (max_steps == 0)
42 max_steps = std::min(3 * k, n);
43 max_steps = std::min(max_steps, n);
44
45 // Lanczos basis: columns of V (n x max_steps)
46 // Store as a flat array for efficient column access
47 Matrix V(n, max_steps, 0.0);
48
49 // alpha (diagonal) and beta (off-diagonal) of the tridiagonal T
50 Vector alpha(max_steps, 0.0);
51 Vector beta(max_steps, 0.0); // beta[j] = beta_{j+1}, beta[0] unused
52
53 // Initialise v_1 = e_1 (deterministic, avoids Rng dependency)
54 for (idx i = 0; i < n; ++i)
55 V(i, 0) = (i == 0) ? 1.0 : 0.0;
56
57 idx steps = 0;
58
59 for (idx j = 0; j < max_steps; ++j) {
60 // Extract current Lanczos vector v_j from column j of V
61 Vector vj(n);
62 for (idx i = 0; i < n; ++i)
63 vj[i] = V(i, j);
64
65 // w = A * vj
66 Vector w(n, 0.0);
67 matvec(vj, w);
68
69 // alpha_j = v_j^T * w
70 const real a = dot(vj, w);
71 alpha[j] = a;
72
73 // w = w - alpha_j*v_j - beta_{j-1}*v_{j-1}
74 axpy(-a, vj, w);
75 if (j > 0) {
76 for (idx i = 0; i < n; ++i) {
77 w[i] -= beta[j - 1] * V(i, j - 1);
78 }
79 }
80
81 // Full reorthogonalisation (MGS against all previous columns of V)
82 // Also returns beta_j = ||w||
83 const real b = kernel::subspace::mgs_orthogonalize(V, j + 1, w);
84
85 ++steps;
86
87 // Invariant subspace found -- can't continue
88 if (b < 1e-12)
89 break;
90
91 beta[j] = b;
92
93 // Store v_{j+1} if room remains
94 if (j + 1 < max_steps) {
95 for (idx i = 0; i < n; ++i)
96 V(i, j + 1) = w[i] / b;
97 }
98 }
99
100 // Build steps x steps tridiagonal T from (alpha, beta) and diagonalise it.
101 // Using all 'steps' Lanczos vectors (not just k) gives far better Ritz
102 // approximations.
103 idx m = steps;
104 Matrix T(m, m, 0.0);
105 for (idx j = 0; j < m; ++j) {
106 T(j, j) = alpha[j];
107 if (j + 1 < m) {
108 T(j, j + 1) = beta[j];
109 T(j + 1, j) = beta[j];
110 }
111 }
112
113 EigenResult teig = eig_sym(T, tol * 1e-2); // tighter tol for inner solve
114 // teig.values are in ascending order; the top-k are at indices m-k .. m-1
115
116 idx nret = std::min(k, m); // how many Ritz pairs to return
117
118 // Ritz vectors for the top-nret eigenvalues: V_m * eigvecs_of_T[:,
119 // m-nret..m-1]
120 Matrix ritz_vecs(n, nret, 0.0);
121 for (idx i = 0; i < nret; ++i) {
122 idx ti = m - nret + i; // column in teig (ascending order; largest last)
123 for (idx j = 0; j < m; ++j) {
124 real coeff = teig.vectors(j, ti);
125 for (idx r = 0; r < n; ++r)
126 ritz_vecs(r, i) += coeff * V(r, j);
127 }
128 }
129
130 // Slice the top-nret eigenvalues (ascending)
131 Vector ritz_vals(nret);
132 for (idx i = 0; i < nret; ++i)
133 ritz_vals[i] = teig.values[m - nret + i];
134
135 // Check convergence of each Ritz pair: ||A*u - lambda*u||
136 bool all_converged = true;
137 for (idx i = 0; i < nret; ++i) {
138 Vector u(n);
139 for (idx r = 0; r < n; ++r)
140 u[r] = ritz_vecs(r, i);
141
142 Vector Au(n, 0.0);
143 matvec(u, Au);
144
145 real res = 0;
146 real lam = ritz_vals[i];
147 for (idx r = 0; r < n; ++r) {
148 real d = Au[r] - lam * u[r];
149 res += d * d;
150 }
151 if (std::sqrt(res) > tol) {
152 all_converged = false;
153 break;
154 }
155 }
156
157 return {ritz_vals, ritz_vecs, steps, all_converged};
158}
159
160} // 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.
real mgs_orthogonalize(const std::vector< Vector > &basis, Vector &v, std::vector< real > &h, idx k)
Modified Gram-Schmidt: orthogonalize v against basis[0..k-1].
Definition subspace.cpp:32
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:248
std::function< void(const Vector &, Vector &)> MatVecFn
Callable type for matrix-free matvec: computes y = A*x.
Definition cg.hpp:13
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:120
real dot(const Vector &x, const Vector &y, Backend b=default_backend)
dot product
Definition vector.cpp:79
constexpr real e
Definition math.hpp:43
void axpy(real alpha, const Vector &x, Vector &y, Backend b=default_backend)
y += alpha * x
Definition vector.cpp:58
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:32
EigenResult eig_sym(const Matrix &A, real tol=1e-12, idx max_sweeps=100, Backend backend=lapack_backend)
Full eigendecomposition of a real symmetric matrix.
Definition eig.cpp:17
Full eigendecomposition result: A = V * diag(values) * V^T.
Vector values
Eigenvalues in ascending order.
Matrix vectors
Eigenvectors as columns of an nxn orthogonal matrix.
Result of a Lanczos eigensolver.
Definition lanczos.hpp:33
Subspace construction and orthogonalization kernels. (namespace num::kernel::subspace)