Eigen  5.0.1
 
Loading...
Searching...
No Matches
TridiagonalInverseIteration.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11#ifndef EIGEN_TRIDIAGONAL_INVERSE_ITERATION_H
12#define EIGEN_TRIDIAGONAL_INVERSE_ITERATION_H
13
14#include "./SelfAdjointEigenSolver.h"
15// For tridiagonal_sturm_count_below(), used to assign eigenvalues to disconnected blocks.
16#include "./TridiagonalBisection.h"
17
18// IWYU pragma: private
19#include "./InternalHeaderCheck.h"
20
21namespace Eigen {
22namespace internal {
23
34struct inverse_iteration_rng {
35 numext::uint64_t state;
36
37 explicit inverse_iteration_rng(numext::uint64_t seed) : state(seed) {}
38
39 template <typename RealScalar>
40 RealScalar next() {
41 // SplitMix64: a strong avalanche so that consecutive per-column seeds yield well-separated
42 // (uncorrelated) start vectors -- consecutive LCG seeds would produce near-parallel starts that
43 // collapse under the cluster reorthogonalization.
44 state += 0x9E3779B97F4A7C15ULL;
45 const numext::uint64_t z = splitmix64_mix(state);
46 // Top 32 bits give a uniform integer in [0, 2^32); map to [0, 1) and then to (-1, 1).
47 // RealScalar must be float or wider: a narrower scalar overflows RealScalar(hi) to infinity
48 // (inf * 0 = NaN start vectors), which is why TridiagonalEigenSolver computes scalars narrower
49 // than float in float (see its ComputeScalar) and none of this code ever runs on them.
50 const numext::uint32_t hi = numext::uint32_t(z >> 32);
51 const RealScalar u = RealScalar(hi) * (RealScalar(1) / RealScalar(4294967296.0));
52 return RealScalar(2) * u - RealScalar(1);
53 }
54};
55
83template <typename RealScalar>
84void tridiagonal_lagtf(RealScalar* d, RealScalar* du, RealScalar* dl, RealScalar* du2, Index* piv, RealScalar lambda,
85 Index n) {
86 d[0] -= lambda;
87 piv[n - 1] = 0;
88 if (n == 1) return;
89
90 RealScalar scale1 = numext::abs(d[0]) + numext::abs(du[0]);
91 for (Index k = 0; k < n - 1; ++k) {
92 d[k + 1] -= lambda;
93 RealScalar scale2 = numext::abs(dl[k]) + numext::abs(d[k + 1]);
94 if (k < n - 2) scale2 += numext::abs(du[k + 1]);
95 // Scaled magnitudes of the two candidate pivots; pick the larger (partial pivoting).
96 const RealScalar piv1 = numext::is_exactly_zero(d[k]) ? RealScalar(0) : numext::abs(d[k]) / scale1;
97 if (numext::is_exactly_zero(dl[k])) {
98 piv[k] = 0;
99 scale1 = scale2;
100 if (k < n - 2) du2[k] = RealScalar(0);
101 } else {
102 const RealScalar piv2 = numext::abs(dl[k]) / scale2;
103 if (piv2 <= piv1) {
104 // No interchange: eliminate using the diagonal pivot d[k] (non-zero, since piv2 > 0).
105 piv[k] = 0;
106 scale1 = scale2;
107 dl[k] = dl[k] / d[k];
108 d[k + 1] -= dl[k] * du[k];
109 if (k < n - 2) du2[k] = RealScalar(0);
110 } else {
111 // Interchange rows k and k+1; the sub-diagonal entry becomes the pivot.
112 piv[k] = 1;
113 const RealScalar mult = d[k] / dl[k];
114 d[k] = dl[k];
115 const RealScalar temp = d[k + 1];
116 d[k + 1] = du[k] - mult * temp;
117 if (k < n - 2) {
118 du2[k] = du[k + 1];
119 du[k + 1] = -mult * du2[k];
120 }
121 du[k] = temp;
122 dl[k] = mult;
123 }
124 }
125 }
126}
127
149template <typename RealScalar>
150void tridiagonal_lagts(const RealScalar* d, const RealScalar* rcp, const RealScalar* du, const RealScalar* dl,
151 const RealScalar* du2, const Index* piv, RealScalar* b, Index n) {
152 const RealScalar eps = NumTraits<RealScalar>::epsilon();
153 const RealScalar sfmin = (std::numeric_limits<RealScalar>::min)();
154 const RealScalar bignum = RealScalar(1) / sfmin;
155
156 // Perturbation floor: eps times the largest magnitude entry of U.
157 using ArrayBuf = Map<const Array<RealScalar, Dynamic, 1>>;
158 RealScalar tol = ArrayBuf(d, n).abs().maxCoeff();
159 if (n > 1) tol = numext::maxi(tol, ArrayBuf(du, n - 1).abs().maxCoeff());
160 if (n > 2) tol = numext::maxi(tol, ArrayBuf(du2, n - 2).abs().maxCoeff());
161 tol *= eps;
162 if (numext::is_exactly_zero(tol)) tol = eps;
163
164 // Forward substitution: apply P then L^{-1}. Written branchlessly in the (data-dependent, roughly
165 // 50/50) pivot-interchange flag so a misprediction cannot stall the serial recurrence; both arms
166 // are evaluated and selected, reproducing the two branches bit for bit.
167 for (Index k = 1; k < n; ++k) {
168 const RealScalar dlk = dl[k - 1];
169 const RealScalar bkm1 = b[k - 1];
170 const RealScalar bk = b[k];
171 const bool interchange = piv[k - 1] != 0;
172 b[k - 1] = interchange ? bk : bkm1;
173 b[k] = interchange ? (bkm1 - dlk * bk) : (bk - dlk * bkm1);
174 }
175
176 // Back substitution: solve U x = b. Away from the singularity the pivot is safe and the quotient is
177 // a reciprocal multiply (rcp[k] = 1/d[k], exact and precomputed once for all the solves); only a
178 // dangerously small pivot -- the rare near-singular element of the deliberately singular system --
179 // falls back to the LAPACK xLAGTS perturbation/rescale with a true division. The guard is written so
180 // the fast path is taken exactly when the original would divide by the unperturbed pivot, and is
181 // virtually always predicted taken.
182 for (Index k = n - 1; k >= 0; --k) {
183 RealScalar temp = b[k];
184 if (k <= n - 2) temp -= du[k] * b[k + 1];
185 if (k <= n - 3) temp -= du2[k] * b[k + 2];
186 const RealScalar ak = d[k];
187 const RealScalar absak = numext::abs(ak);
188 if (EIGEN_PREDICT_TRUE(absak >= RealScalar(1) || (absak >= sfmin && numext::abs(temp) <= absak * bignum))) {
189 b[k] = temp * rcp[k];
190 } else {
191 // Tiny pivot: perturb (or rescale) exactly as LAPACK xLAGTS so the quotient cannot overflow.
192 RealScalar a = ak;
193 RealScalar t = temp;
194 RealScalar pert = (a >= RealScalar(0)) ? tol : -tol;
195 while (true) {
196 const RealScalar aa = numext::abs(a);
197 if (aa < RealScalar(1)) {
198 if (aa < sfmin) {
199 if (numext::is_exactly_zero(aa) || numext::abs(t) * sfmin > aa) {
200 a += pert;
201 pert *= RealScalar(2);
202 continue;
203 } else {
204 t *= bignum;
205 a *= bignum;
206 }
207 } else if (numext::abs(t) > aa * bignum) {
208 a += pert;
209 pert *= RealScalar(2);
210 continue;
211 }
212 }
213 break;
214 }
215 b[k] = t / a;
216 }
217 }
218}
219
246template <typename RealScalar, typename EivecType>
247Index tridiagonal_inverse_iteration_block(const RealScalar* sdiag, const RealScalar* ssub, const RealScalar* xj_scaled,
248 const Index* clstart, Index n, RealScalar onenrm, RealScalar dtpcrt,
249 int maxits, int extra, EivecType& eivecs, Index j_lo, Index j_hi) {
250 using RealVectorType = Matrix<RealScalar, Dynamic, 1>;
251 const RealScalar eps = NumTraits<RealScalar>::epsilon();
252
253 // Work arrays for the LU factors of T - xj*I (reused across the block's columns) and the iterate.
254 // lu_rcp holds the exact reciprocal of the U diagonal, computed once per factorization and reused by
255 // the inverse-iteration back-solves. Being locals, each thread gets its own copy.
256 RealVectorType lu_d(n), lu_dl(n), lu_du(n), lu_du2(n), lu_rcp(n), b(n);
257 Matrix<Index, Dynamic, 1> piv(n);
258
259 Index nonconv = 0; // columns that exhausted maxits without satisfying the growth test
260 for (Index j = j_lo; j < j_hi; ++j) {
261 const RealScalar xj = xj_scaled[j];
262 const Index gpind = clstart[j]; // first column of j's cluster (>= j_lo by block alignment)
263
264 // Factor T - xj*I = P L U once; the inverse-iteration loop reuses it.
265 lu_d = Map<const RealVectorType>(sdiag, n);
266 lu_du.head(n - 1) = Map<const RealVectorType>(ssub, n - 1);
267 lu_dl.head(n - 1) = Map<const RealVectorType>(ssub, n - 1);
268 tridiagonal_lagtf<RealScalar>(lu_d.data(), lu_du.data(), lu_dl.data(), lu_du2.data(), piv.data(), xj, n);
269 // Exact reciprocals of the U diagonal (pdiv, not the approximate preciprocal of cwiseInverse());
270 // a vanishing pivot yields inf, which the back-solve fast path never consumes.
271 lu_rcp.array() = RealScalar(1) / lu_d.array();
272
273 // Deterministic pseudo-random start vector (seeded per column for thread independence).
274 inverse_iteration_rng rng(numext::uint64_t(j) + 1);
275 for (Index i = 0; i < n; ++i) b[i] = rng.template next<RealScalar>();
276
277 int nrmchk = 0;
278 bool converged = false;
279 for (int its = 0; its < maxits; ++its) {
280 // Scale the right-hand side so the near-singular solve neither overflows nor underflows.
281 const RealScalar bmax = b.cwiseAbs().maxCoeff();
282 if (numext::is_exactly_zero(bmax)) break; // degenerate iterate; cannot grow -> not converged
283 const RealScalar target = RealScalar(n) * onenrm * numext::maxi(eps, numext::abs(lu_d[n - 1]));
284 const RealScalar scl = target / bmax;
285 if (EIGEN_PREDICT_TRUE(scl >= (std::numeric_limits<RealScalar>::min)())) {
286 b *= scl;
287 } else {
288 // scl underflowed (huge iterate from a shift within ~pivmin of an exact eigenvalue). A
289 // subnormal or flushed-to-zero scale would zero the iterate on flush-to-zero hardware
290 // (ARMv7 NEON), so scale in two in-range steps instead.
291 b /= bmax;
292 b *= target;
293 }
294 tridiagonal_lagts<RealScalar>(lu_d.data(), lu_rcp.data(), lu_du.data(), lu_dl.data(), lu_du2.data(), piv.data(),
295 b.data(), n);
296
297 // Modified Gram-Schmidt against the already-accepted eigenvectors of this cluster.
298 for (Index i = gpind; i < j; ++i) b -= b.dot(eivecs.col(i)) * eivecs.col(i);
299
300 const RealScalar nrm = b.cwiseAbs().maxCoeff();
301 if (nrm < dtpcrt) continue; // not yet grown into the eigenvector; iterate
302 if (++nrmchk < extra + 1) continue; // a couple of safety iterations past the threshold
303 converged = true;
304 break;
305 }
306 // LAPACK xSTEIN reports vectors that never satisfied the growth test; mirror that. The column is
307 // still written (best effort), but the caller surfaces the count as ComputationInfo::NoConvergence.
308 if (!converged) ++nonconv;
309
310 // Normalize to unit 2-norm with a deterministic sign (largest-magnitude entry positive). The
311 // iterate can carry huge entries (~1/eps times the start) on a nearly singular solve, so scale
312 // before taking the norm -- otherwise squaredNorm() would overflow and zero out the vector.
313 Index jmax = 0;
314 const RealScalar binf = b.cwiseAbs().maxCoeff(&jmax);
315 safe_scaling<RealScalar>::scale_to(b, b, binf);
316 const RealScalar nrm2 = b.norm();
317 RealScalar scl = numext::is_exactly_zero(nrm2) ? RealScalar(1) : RealScalar(1) / nrm2;
318 if (b[jmax] < RealScalar(0)) scl = -scl;
319 eivecs.col(j) = b * scl;
320 }
321 return nonconv;
322}
323
345template <typename DiagType, typename SubdiagType, typename EivalType, typename EivecType>
346Index tridiagonal_inverse_iteration_connected(const DiagType& diag, const SubdiagType& subdiag, const EivalType& eivals,
347 EivecType& eivecs) {
348 using RealScalar = typename DiagType::Scalar;
349 EIGEN_STATIC_ASSERT(NumTraits<RealScalar>::IsInteger == 0 && NumTraits<RealScalar>::IsComplex == 0,
350 THIS_FUNCTION_IS_NOT_FOR_INTEGER_OR_COMPLEX_TYPES)
351
352 const Index n = diag.size();
353 const Index m = eivals.size();
354 if (n == 0 || m == 0) return 0;
355 if (n == 1) {
356 eivecs.setOnes();
357 return 0;
358 }
359
360 const RealScalar eps = NumTraits<RealScalar>::epsilon();
361
362 // Normalize T (and the shifts) to O(1) so the deliberately near-singular factor/solve cannot
363 // overflow or underflow; eigenvectors are invariant under this uniform scaling.
364 // The rescan recovers an all-subnormal input that a flushing SIMD unit reads as zero.
365 const RealScalar maxCoeff = max_preserving_subnormals(
366 safe_scaling<RealScalar>::recover_flushed_max_coeff(diag, diag.cwiseAbs().maxCoeff()),
367 safe_scaling<RealScalar>::recover_flushed_max_coeff(subdiag, subdiag.cwiseAbs().maxCoeff()));
368 Matrix<RealScalar, Dynamic, 1> sdiag(n), ssub(n - 1);
369 const auto factors = safe_scaling<RealScalar>::scale_to(sdiag, diag, maxCoeff);
370 safe_scaling<RealScalar>::scale_to(ssub, subdiag, maxCoeff, factors);
371 Matrix<RealScalar, Dynamic, 1> xj_scaled(m);
372 safe_scaling<RealScalar>::scale_to(xj_scaled, eivals, maxCoeff, factors);
373 if (maxCoeff > RealScalar(0) && maxCoeff < (std::numeric_limits<RealScalar>::min)()) {
374 // The first scale is clamped to normal range. Finish normalization in a second finite step
375 // so the absolute pivot floor and growth threshold still see an O(1) matrix.
376 const RealScalar scaledMax = numext::maxi(sdiag.cwiseAbs().maxCoeff(), ssub.cwiseAbs().maxCoeff());
377 const auto remaining = safe_scaling<RealScalar>::scale_in_place(sdiag, scaledMax);
378 safe_scaling<RealScalar>::scale_in_place(ssub, scaledMax, remaining);
379 safe_scaling<RealScalar>::scale_in_place(xj_scaled, scaledMax, remaining);
380 }
381
382 // Infinity norm of the scaled T: max_i (|e_{i-1}| + |d_i| + |e_i|), missing boundary off-diagonals zero.
383 RealScalar onenrm = numext::abs(sdiag[0]) + numext::abs(ssub[0]);
384 onenrm = numext::maxi(onenrm, numext::abs(sdiag[n - 1]) + numext::abs(ssub[n - 2]));
385 if (n > 2)
386 onenrm = numext::maxi(onenrm, (ssub.head(n - 2).array().abs() + sdiag.segment(1, n - 2).array().abs() +
387 ssub.segment(1, n - 2).array().abs())
388 .maxCoeff());
389 if (numext::is_exactly_zero(onenrm)) onenrm = RealScalar(1); // T == 0: any orthonormal basis works
390
391 // Cluster threshold and convergence threshold (LAPACK xSTEIN constants).
392 const RealScalar ortol = RealScalar(1e-3) * onenrm;
393 const RealScalar dtpcrt = numext::sqrt(RealScalar(0.1) / RealScalar(n));
394 const int maxits = 5;
395 const int extra = 2;
396
397 // Pre-pass (sequential, O(m)): the perturbed, scaled shift xj_scaled[j] and the cluster start
398 // clstart[j] (index of the first eigenvalue of j's cluster). Reproduced exactly as the serial sweep
399 // would, so the parallel column blocks below match the serial result bit for bit. A shift sitting on
400 // top of its predecessor is nudged up by pertol so the two factorizations stay distinct; a gap larger
401 // than ortol starts a new cluster (within which the eigenvectors are reorthogonalized). The
402 // perturbation is at most ~pertol << ortol, so it never moves a column across a cluster boundary.
403 Matrix<Index, Dynamic, 1> clstart(m);
404 {
405 Index gpind = 0;
406 RealScalar xjm = RealScalar(0); // previous (possibly perturbed) shift
407 for (Index j = 0; j < m; ++j) {
408 RealScalar xj = xj_scaled[j];
409 if (j > 0) {
410 // The xSTEIN nudge 10*eps*|xj| separates coincident shifts so their factorizations differ.
411 // Capped at a fraction of the cluster threshold: at low precision (bfloat16: 10*eps ~ 0.08)
412 // the un-capped nudge can push a shift across a cluster boundary into a neighbouring
413 // eigenspace. Shifts the cap leaves coincident still yield an orthonormal basis, via the
414 // per-cluster Gram-Schmidt on independent random starts.
415 const RealScalar pertol = numext::mini(RealScalar(10) * numext::abs(eps * xj), RealScalar(0.25) * ortol);
416 if (xj - xjm < pertol) xj = xjm + pertol;
417 }
418 if (j == 0 || numext::abs(xj - xjm) > ortol) gpind = j;
419 xj_scaled[j] = xj;
420 clstart[j] = gpind;
421 xjm = xj;
422 }
423 }
424
425 const RealScalar* sdiag_p = sdiag.data();
426 const RealScalar* ssub_p = ssub.data();
427 const RealScalar* xj_p = xj_scaled.data();
428 const Index* cl_p = clstart.data();
429
430 // Distinct clusters are independent, so the columns split across threads with a single fork/join:
431 // thread t owns a contiguous slice of columns snapped to cluster boundaries (so no cluster straddles
432 // two threads) and writes only those columns of eivecs. No communication until the join, and the
433 // result is bit-identical to the serial path for any thread count. Each thread tallies its own
434 // non-converged columns; the reduction sums them into the value returned to the caller.
435 Index nonconv = 0;
436#if defined(EIGEN_HAS_OPENMP)
437 int nthreads = 1;
438 // Don't nest inside an existing parallel region, and only fork when there is enough work to amortize
439 // the thread overhead. Each column costs ~one O(n) factorization plus a few O(n) back-solves.
440 if (omp_get_num_threads() == 1) {
441 // Work ~ m columns * n rows (a factorization plus a few O(n) back-solves each). kMinTaskSize is the
442 // minimum such work to give each thread before adding one: m*n / kMinTaskSize threads, capped at
443 // the pool size. The value is measured -- at m*n = 2*kMinTaskSize (the two-thread point) inverse
444 // iteration is already ~2x faster than serial, with the gain growing to the core count for larger n.
445 const double work = double(m) * double(n);
446 const double kMinTaskSize = 2048.0;
447 const Index work_threads = Index(work / kMinTaskSize);
448 nthreads = int(numext::maxi(Index(1), numext::mini(work_threads, Index(Eigen::nbThreads()))));
449 }
450 if (nthreads > 1) {
451#pragma omp parallel num_threads(nthreads) reduction(+ : nonconv)
452 {
453 const Index nt = omp_get_num_threads();
454 const Index tid = omp_get_thread_num();
455 // Balanced split snapped up to the next cluster boundary (clstart[j]==j marks a cluster start).
456 // Adjacent threads snap the same raw index to the same boundary, so the blocks tile [0, m)
457 // exactly; a thread whose whole share falls inside one cluster simply gets an empty block.
458 Index lo = tid * m / nt;
459 Index hi = (tid + 1) * m / nt;
460 while (lo < m && cl_p[lo] != lo) ++lo;
461 while (hi < m && cl_p[hi] != hi) ++hi;
462 nonconv += tridiagonal_inverse_iteration_block<RealScalar>(sdiag_p, ssub_p, xj_p, cl_p, n, onenrm, dtpcrt, maxits,
463 extra, eivecs, lo, hi);
464 }
465 } else
466#endif
467 {
468 nonconv = tridiagonal_inverse_iteration_block<RealScalar>(sdiag_p, ssub_p, xj_p, cl_p, n, onenrm, dtpcrt, maxits,
469 extra, eivecs, 0, m);
470 }
471 return nonconv;
472}
473
513template <typename DiagType, typename SubdiagType, typename EivalType, typename EivecType>
514Index tridiagonal_inverse_iteration(const DiagType& diag, const SubdiagType& subdiag, const EivalType& eivals,
515 EivecType& eivecs) {
516 using RealScalar = typename DiagType::Scalar;
517 using VectorType = Matrix<RealScalar, Dynamic, 1>;
518 const Index n = diag.size();
519 const Index m = eivals.size();
520 if (n == 0 || m == 0) return 0;
521
522 // A matrix whose largest entry is below the recovery threshold min / eps can hold significant subnormal
523 // couplings, which FTZ/DAZ hardware reads as zero in the comparisons below (ARMv7 NEON also in the packet maxima).
524 // Scale it into the normal range first, exactly, through integer significands: the eigenvectors are invariant
525 // under the scaling, and every representable entry becomes normal. The test reads the exponent from the
526 // representation, since the maximum itself may be subnormal and a floating-point comparison on it is what FTZ/DAZ
527 // breaks; a zero or non-finite maximum has no exponent in range and is left alone.
528 {
529 RealScalar maxCoeff = safe_scaling<RealScalar>::recover_flushed_max_coeff(diag, diag.cwiseAbs().maxCoeff());
530 if (n >= 2) {
531 maxCoeff = max_preserving_subnormals(
532 maxCoeff, safe_scaling<RealScalar>::recover_flushed_max_coeff(subdiag, subdiag.cwiseAbs().maxCoeff()));
533 }
534 // 2^(e - 1) <= maxCoeff < 2^e, so maxCoeff < 2^recovery iff e <= recovery; e == 0 for a zero maximum and one
535 // above the largest finite exponent for infinities and NaN.
536 const int e = frexp_exponent_preserving_subnormals(maxCoeff);
537 if (e != 0 && e <= safe_scaling<RealScalar>::subnormal_recovery_exponent()) {
538 VectorType sdiag(n), ssub(n - 1), seivals(m);
539 const auto factors = safe_scaling<RealScalar>::scale_to(sdiag, diag, maxCoeff);
540 safe_scaling<RealScalar>::scale_to(ssub, subdiag, maxCoeff, factors);
541 safe_scaling<RealScalar>::scale_to(seivals, eivals, maxCoeff, factors);
542 return tridiagonal_inverse_iteration(sdiag, ssub, seivals, eivecs);
543 }
544 }
545
546 // Split at negligible couplings (cf. xSTEBZ): |e_k| <= eps * sqrt(|d_k|) * sqrt(|d_k+1|). The
547 // geometric-mean form is scale-invariant on its own (both sides scale linearly) and needs no
548 // additive floor: a floor expressed at any single scale falsely splits strongly connected blocks
549 // living far below that scale. Taking square roots before multiplying keeps every intermediate in
550 // range, so the comparison stays exact down to subnormal entries; a zero coupling always splits.
551 const RealScalar eps = NumTraits<RealScalar>::epsilon();
552 Matrix<Index, Dynamic, 1> bstart(n + 1); // block b spans rows [bstart(b), bstart(b+1))
553 Index nblocks = 1;
554 bstart(0) = 0;
555 if (n > 1) {
556 const Array<bool, Dynamic, 1> split = (subdiag.array().abs() <= eps * (diag.head(n - 1).array().abs().sqrt() *
557 diag.tail(n - 1).array().abs().sqrt()));
558 for (Index k = 0; k + 1 < n; ++k)
559 if (split(k)) bstart(nblocks++) = k + 1;
560 }
561 bstart(nblocks) = n;
562
563 if (nblocks == 1) return tridiagonal_inverse_iteration_connected(diag, subdiag, eivals, eivecs);
564
565 // Per-block normalized data (block-local scale) for the assignment Sturm counts, and the
566 // per-block localization tolerance btol: the count's rounding-displacement bound at the block's
567 // own scale. The subset-edge windows below are widened by btol so that a requested value equal to
568 // a block eigenvalue (up to that block's rounding) is found, without a window at any other
569 // block's scale swallowing well-separated foreign eigenvalues -- a tolerance expressed at the
570 // global scale would hand a small block's eigenvalue to whichever block comes first.
571 const RealScalar safemin = numext::maxi(RealScalar(1) / NumTraits<RealScalar>::highest(),
572 (RealScalar(1) + eps) * (std::numeric_limits<RealScalar>::min)());
573 VectorType alpha_all(n), beta_sq_all(n), bscale(nblocks), bpivmin(nblocks), btol(nblocks);
574 RealScalar gscale = RealScalar(0);
575 for (Index b = 0; b < nblocks; ++b) {
576 const Index b0 = bstart(b), nb = bstart(b + 1) - b0;
577 RealScalar s = diag.segment(b0, nb).cwiseAbs().maxCoeff();
578 if (nb > 1) s = numext::maxi(s, subdiag.segment(b0, nb - 1).cwiseAbs().maxCoeff());
579 if (numext::is_exactly_zero(s)) s = RealScalar(1);
580 gscale = numext::maxi(gscale, s);
581 auto alpha = alpha_all.segment(b0, nb);
582 const auto factors = safe_scaling<RealScalar>::scale_to(alpha, diag.segment(b0, nb), s);
583 bscale(b) = factors.scale;
584 RealScalar max_bsq = RealScalar(0);
585 if (nb > 1) {
586 auto beta = beta_sq_all.segment(b0, nb - 1);
587 safe_scaling<RealScalar>::scale_to(beta, subdiag.segment(b0, nb - 1), s, factors);
588 beta = beta.array().square();
589 max_bsq = beta.maxCoeff();
590 }
591 bpivmin(b) = safemin * numext::maxi(max_bsq, RealScalar(1));
592 // In original units, block row sums are bounded by 3*s, independently of the chosen scaling factor.
593 btol(b) = RealScalar(2.1) * (RealScalar(3) * RealScalar(nb) * eps + RealScalar(4) * safemin) * s;
594 }
595
596 // Assign each requested eigenvalue to a block by capacity. Groups are runs of exactly-equal
597 // requested values; a block's capacity for a group is its Sturm count over the group's value
598 // interval, bounded by the midpoints to the neighbouring distinct values and, at the subset's
599 // edges, widened by a tolerance. The edge tolerance is tiered: the per-block btol first, so that
600 // exactly supplied eigenvalues can only be claimed by the block that owns them, and -- when the
601 // edge group still cannot cover its copies -- the matrix-scale gtol, which admits requested
602 // values whose error is at the scale of the whole matrix (e.g. the staged bisection's output for
603 // a block much smaller than the matrix norm). The intervals partition the requested span and
604 // adjacent intervals share their boundary evaluation, so each block eigenvalue is counted exactly
605 // once even at count knife-edges. A copy its own interval cannot supply is carried into the next
606 // interval; copies left at the end pair up with the blocks holding leftover span capacity (a
607 // miscount that let a block absorb a foreign copy freed exactly one such slot elsewhere).
608 const RealScalar gtol = RealScalar(2.1) * (RealScalar(3) * RealScalar(n) * eps + RealScalar(4) * safemin) * gscale;
609 Matrix<Index, Dynamic, 1> blockof(m), assigned(nblocks), caps(nblocks), below_prev(nblocks), below_cur(nblocks),
610 below_edge(nblocks), carry(m), carry_next(m);
611 assigned.setZero();
612 Matrix<Index, Dynamic, 1> local_index(m);
613 Array<bool, Dynamic, 1> claimed = Array<bool, Dynamic, 1>::Constant(n, false);
614 // Sturm count over block b of its eigenvalues strictly below the unnormalized shift x (normalized
615 // by the block's own scale, as the block's alpha/beta data is).
616 auto count_below = [&](Index b, RealScalar x) -> Index {
617 const Index b0 = bstart(b), nb = bstart(b + 1) - b0;
618 return tridiagonal_sturm_count_below<RealScalar>(alpha_all.data() + b0, beta_sq_all.data() + b0, nb, bpivmin(b),
619 x / bscale(b));
620 };
621 for (Index b = 0; b < nblocks; ++b) below_prev(b) = count_below(b, eivals[0] - btol(b));
622 below_edge = below_prev;
623 Index ncarry = 0;
624 Index g_begin = 0;
625 while (g_begin < m) {
626 Index g_end = g_begin + 1;
627 while (g_end < m && !(eivals[g_begin] < eivals[g_end])) ++g_end;
628 // Upper boundary: midpoint to the next distinct value, or the per-block widened edge at the end.
629 const bool first = (g_begin == 0);
630 const bool last = (g_end >= m);
631 const RealScalar mid_bound =
632 last ? RealScalar(0) : RealScalar(0.5) * eivals[g_end - 1] + RealScalar(0.5) * eivals[g_end];
633 bool widened = false;
634 while (true) {
635 Index total = 0;
636 for (Index b = 0; b < nblocks; ++b) {
637 const RealScalar bound = last ? eivals[m - 1] + (widened ? gtol : btol(b)) : mid_bound;
638 below_cur(b) = count_below(b, bound);
639 caps(b) = numext::maxi(Index(0), below_cur(b) - below_prev(b));
640 total += caps(b);
641 }
642 // Retry an edge group once with the matrix-scale tolerance if the block-scale windows left it
643 // short; interior deficits are handled by the carry chain instead.
644 if (widened || !(first || last) || total >= g_end - g_begin + (last ? ncarry : 0)) break;
645 widened = true;
646 if (first) {
647 for (Index b = 0; b < nblocks; ++b) below_prev(b) = count_below(b, eivals[0] - gtol);
648 below_edge = below_prev;
649 }
650 }
651 // The group's own copies first, then the carried ones; whatever finds no capacity carries on.
652 Index ncarry_next = 0;
653 for (Index pass = 0; pass < 2; ++pass) {
654 const Index count = (pass == 0) ? g_end - g_begin : ncarry;
655 for (Index c = 0; c < count; ++c) {
656 const Index j = (pass == 0) ? g_begin + c : carry(c);
657 Index chosen = -1;
658 for (Index b = 0; b < nblocks && chosen < 0; ++b)
659 if (caps(b) > 0) chosen = b;
660 if (chosen < 0) {
661 carry_next(ncarry_next++) = j;
662 continue;
663 }
664 const Index index = below_cur(chosen) - caps(chosen);
665 local_index(j) = index;
666 claimed(bstart(chosen) + index) = true;
667 --caps(chosen);
668 ++assigned(chosen);
669 blockof(j) = chosen;
670 }
671 }
672 carry.swap(carry_next);
673 ncarry = ncarry_next;
674 below_prev = below_cur;
675 g_begin = g_end;
676 }
677 // Leftover copies: first blocks with leftover span capacity, then any block with spare rows.
678 for (Index c = 0; c < ncarry; ++c) {
679 const Index j = carry(c);
680 Index chosen = -1;
681 for (Index b = 0; b < nblocks && chosen < 0; ++b)
682 if (below_cur(b) - below_edge(b) - assigned(b) > 0) chosen = b;
683 if (chosen < 0)
684 for (Index b = 0; b < nblocks && chosen < 0; ++b)
685 if (bstart(b + 1) - bstart(b) - assigned(b) > 0) chosen = b;
686 if (chosen < 0) chosen = 0;
687 {
688 const Index b0 = bstart(chosen), nb = bstart(chosen + 1) - b0;
689 Index index = numext::mini(below_edge(chosen), nb - 1);
690 while (index < nb && claimed(b0 + index)) ++index;
691 if (index == nb) {
692 index = 0;
693 while (index < nb && claimed(b0 + index)) ++index;
694 }
695 if (index == nb) {
696 eivecs.setZero();
697 return m;
698 }
699 local_index(j) = index;
700 claimed(b0 + index) = true;
701 }
702 ++assigned(chosen);
703 blockof(j) = chosen;
704 }
705
706 // Run each block independently and scatter its columns; rows outside the block stay exactly zero.
707 eivecs.setZero();
708 Index nonconv = 0;
709 VectorType wloc, refined, endpoints;
710 Matrix<numext::int64_t, Dynamic, 1> counts;
711 Matrix<Index, Dynamic, 1> indices;
712 Matrix<RealScalar, Dynamic, Dynamic> vloc;
713 Matrix<Index, Dynamic, 1> colmap(m);
714 for (Index b = 0; b < nblocks; ++b) {
715 const Index b0 = bstart(b), nb = bstart(b + 1) - b0;
716 Index mb = 0;
717 for (Index j = 0; j < m; ++j)
718 if (blockof(j) == b) colmap(mb++) = j;
719 if (mb == 0) continue;
720 wloc.resize(mb);
721 for (Index k = 0; k < mb; ++k) wloc(k) = eivals[colmap(k)];
722 vloc.resize(nb, mb);
723 const VectorType bdiag = diag.segment(b0, nb);
724 const VectorType bsub = subdiag.segment(b0, nb > 1 ? nb - 1 : 0);
725 if (nb > 1) {
726 // Retain the capacity assignment, including multiplicities across blocks. As in LAPACK
727 // xSTEBZ, use block-local bisection accuracy rather than a global reorthogonalization floor.
728 indices.resize(mb);
729 for (Index k = 0; k < mb; ++k) indices(k) = local_index(colmap(k));
730 std::sort(indices.data(), indices.data() + mb);
731 endpoints.resize(2 * mb);
732 counts.resize(2 * mb);
733 endpoints.head(mb) = (wloc.array() - btol(b)) / bscale(b);
734 endpoints.tail(mb) = (wloc.array() + btol(b)) / bscale(b);
735 tridiagonal_sturm_counts(alpha_all.data() + b0, beta_sq_all.data() + b0, nb, bpivmin(b), endpoints.data(),
736 counts.data(), 2 * mb);
737 bool needs_refinement = false;
738 // A supplied shift is locally resolved if its rounding-sized interval contains the
739 // assigned eigenvalue index. This also detects repeated coarse shifts claiming one root.
740 for (Index k = 0; k < mb && !needs_refinement; ++k) {
741 needs_refinement = counts(k) > indices(k) || counts(mb + k) <= indices(k);
742 }
743 if (needs_refinement) {
744 for (Index first = 0; first < mb;) {
745 Index last = first + 1;
746 while (last < mb && indices(last) == indices(last - 1) + 1) ++last;
747 tridiagonal_bisection(bdiag, bsub, EigenvalueRange::indices(indices(first), indices(last - 1) + 1),
748 RealScalar(0), refined);
749 wloc.segment(first, last - first) = refined;
750 first = last;
751 }
752 }
753 }
754 nonconv += tridiagonal_inverse_iteration_connected(bdiag, bsub, wloc, vloc);
755 for (Index k = 0; k < mb; ++k) eivecs.col(colmap(k)).segment(b0, nb) = vloc.col(k);
756 }
757 return nonconv;
758}
759
794template <typename DiagType, typename SubdiagType, typename EivalType, typename EivecType>
795void tridiagonal_rayleigh_ritz_refine(const DiagType& diag, const SubdiagType& subdiag, const EivalType& eivals,
796 EivecType& eivecs) {
797 using RealScalar = typename DiagType::Scalar;
798 using DenseType = Matrix<RealScalar, Dynamic, Dynamic>;
799 const Index n = diag.size();
800 const Index m = eivals.size();
801 if (n < 2 || m < 2) return;
802
803 // Inf-norm of T and the cluster threshold, matching tridiagonal_inverse_iteration()'s grouping.
804 RealScalar onenrm = numext::abs(diag[0]) + numext::abs(subdiag[0]);
805 onenrm = numext::maxi(onenrm, numext::abs(diag[n - 1]) + numext::abs(subdiag[n - 2]));
806 for (Index i = 1; i < n - 1; ++i)
807 onenrm = numext::maxi(onenrm, numext::abs(subdiag[i - 1]) + numext::abs(diag[i]) + numext::abs(subdiag[i]));
808 const RealScalar ortol = RealScalar(1e-3) * onenrm;
809 if (!(ortol > RealScalar(0))) return; // T == 0: nothing to refine
810 const RealScalar inv_onenrm = RealScalar(1) / onenrm;
811 const RealScalar refine_threshold = RealScalar(16) * NumTraits<RealScalar>::epsilon();
812
813 SelfAdjointEigenSolver<DenseType> block_solver;
814 // Per-cluster scratch, hoisted out of the loop and reused across clusters: each assignment below
815 // resizes only when a cluster is larger than any seen so far, so the common many-small-clusters case
816 // allocates once rather than per cluster. Bsym is the distinct target for the symmetrization, which
817 // also lets B + B.transpose() avoid an aliasing temporary.
818 DenseType Vc, TVc, B, Bsym;
819 Index s = 0;
820 while (s < m) {
821 Index t = s;
822 while (t + 1 < m && (eivals[t + 1] - eivals[t]) < ortol) ++t;
823 const Index c = t - s + 1;
824 if (c > 1) {
825 // Project T onto the cluster basis. T V_c via the tridiagonal structure:
826 // (T V_c)(i,:) = e_{i-1} V_c(i-1,:) + d_i V_c(i,:) + e_i V_c(i+1,:).
827 Vc = eivecs.middleCols(s, c); // copy so the write-back below cannot alias
828 TVc = diag.asDiagonal() * Vc;
829 TVc.topRows(n - 1) += subdiag.asDiagonal() * Vc.bottomRows(n - 1);
830 TVc.bottomRows(n - 1) += subdiag.asDiagonal() * Vc.topRows(n - 1);
831 // Worst per-vector residual in the cluster (scaled by 1/||T|| so it cannot overflow). Skip the
832 // refinement when the cluster is already at machine precision -- refining it would only add the
833 // subspace error to vectors that are individually more accurate.
834 const RealScalar cluster_resid =
835 ((TVc - Vc * eivals.segment(s, c).asDiagonal()) * inv_onenrm).colwise().norm().maxCoeff();
836 if (cluster_resid > refine_threshold) {
837 B.noalias() = Vc.transpose() * TVc;
838 Bsym = B + B.transpose(); // enforce exact symmetry (distinct target avoids an aliasing temp)
839 Bsym *= RealScalar(0.5);
840 block_solver.compute(Bsym, ComputeEigenvectors);
841 eivecs.middleCols(s, c).noalias() = Vc * block_solver.eigenvectors();
842 }
843 }
844 s = t + 1;
845 }
846}
847
848} // namespace internal
849} // namespace Eigen
850
851#endif // EIGEN_TRIDIAGONAL_INVERSE_ITERATION_H
@ ComputeEigenvectors
Definition Constants.h:406
static EigenvalueRange indices(Index il, Index iu)
Definition TridiagonalBisection.h:53