Eigen  5.0.1
 
Loading...
Searching...
No Matches
TridiagonalBisection.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_BISECTION_H
12#define EIGEN_TRIDIAGONAL_BISECTION_H
13
14// IWYU pragma: private
15#include "./InternalHeaderCheck.h"
16
17namespace Eigen {
18
39 enum Kind { All, ByIndex, ByValue };
40
41 Kind kind;
42 Index il;
43 Index iu;
44 // Endpoints are held as long double so values() does not silently collapse two nearby endpoints
45 // to the same number when the solver's RealScalar is long double (they are narrowed on use).
46 long double vl;
47 long double vu;
48
50 static EigenvalueRange all() { return EigenvalueRange{All, 0, 0, 0.0L, 0.0L}; }
51
53 static EigenvalueRange indices(Index il, Index iu) { return EigenvalueRange{ByIndex, il, iu, 0.0L, 0.0L}; }
54
56 static EigenvalueRange values(long double vl, long double vu) { return EigenvalueRange{ByValue, 0, 0, vl, vu}; }
57};
58
59namespace internal {
60
85// Evaluates the Sturm count for \c kUnroll packets of shift points at once, fully unrolled so the
86// per-lane state (x, q, running count) stays in vector registers. The in-register count accumulates
87// in RealScalar (one padd per row, no cross-lane traffic) and is flushed into the exact int64 output
88// every kFlushPeriod rows -- comfortably before it could reach 2^digits, where RealScalar stops
89// representing consecutive integers (float: 2^24) -- so the stored counts are exact for any n. The
90// row loop is chunked on the flush period rather than testing per row, keeping the hot inner loop
91// unchanged; matrices with n below the period (the usual case) run it exactly once.
92// See tridiagonal_sturm_counts().
93template <int kUnroll, typename RealScalar>
94EIGEN_STRONG_INLINE void tridiagonal_sturm_block(const RealScalar* alpha, const RealScalar* beta_sq, Index n,
95 RealScalar pivmin, const RealScalar* eval_points,
96 numext::int64_t* count, Index start) {
97 using Packet = typename packet_traits<RealScalar>::type;
98 constexpr int kPacketSize = unpacket_traits<Packet>::size;
99 constexpr int kFlushShift = NumTraits<RealScalar>::digits() - 2 < 30 ? NumTraits<RealScalar>::digits() - 2 : 30;
100 constexpr Index kFlushPeriod = Index(1) << kFlushShift;
101 const Packet pivmin_p = pset1<Packet>(pivmin);
102 const Packet neg_pivmin_p = pset1<Packet>(-pivmin);
103 const Packet one_p = pset1<Packet>(RealScalar(1));
104
105 numext::int64_t* cnt = count + start;
106 Packet x[kUnroll], q[kUnroll], c[kUnroll];
107 const Packet alpha_0 = pset1<Packet>(alpha[0]);
108 EIGEN_UNROLL_LOOP
109 for (int k = 0; k < kUnroll; ++k) {
110 x[k] = ploadu<Packet>(eval_points + start + k * kPacketSize);
111 q[k] = psub(alpha_0, x[k]);
112 const Packet mask = pcmp_le(q[k], pivmin_p);
113 c[k] = pand(one_p, mask);
114 q[k] = pselect(mask, pmin(q[k], neg_pivmin_p), q[k]);
115 }
116 // Convert the in-register lane counts to the exact int64 output: overwrite on the first flush,
117 // add into the previously flushed partials thereafter. The scratch buffer lives outside the lambda:
118 // MSVC captures kPacketSize instead of reading it as a constant, rejecting the bound inside.
119 RealScalar buf[kPacketSize];
120 auto store_counts = [&](bool accumulate) {
121 EIGEN_UNROLL_LOOP
122 for (int k = 0; k < kUnroll; ++k) {
123 pstoreu(buf, c[k]);
124 EIGEN_UNROLL_LOOP
125 for (int l = 0; l < kPacketSize; ++l) {
126 if (accumulate)
127 cnt[k * kPacketSize + l] += numext::int64_t(buf[l]);
128 else
129 cnt[k * kPacketSize + l] = numext::int64_t(buf[l]);
130 }
131 }
132 };
133 bool flushed = false;
134 Index i = 1;
135 while (true) {
136 const Index i_end = numext::mini(n, i + kFlushPeriod);
137 for (; i < i_end; ++i) {
138 const Packet alpha_i = pset1<Packet>(alpha[i]);
139 const Packet beta_sq_im1 = pset1<Packet>(beta_sq[i - 1]);
140 EIGEN_UNROLL_LOOP
141 for (int k = 0; k < kUnroll; ++k) {
142 // q = (alpha_i - beta_{i-1}^2 / q) - x.
143 q[k] = psub(psub(alpha_i, pdiv(beta_sq_im1, q[k])), x[k]);
144 const Packet mask = pcmp_le(q[k], pivmin_p);
145 c[k] = padd(c[k], pand(one_p, mask));
146 q[k] = pselect(mask, pmin(q[k], neg_pivmin_p), q[k]);
147 }
148 }
149 if (i >= n) break;
150 // Flush the in-register counts (exact: at most kFlushPeriod + 1 increments so far) into the
151 // integer output and restart the accumulators. Only reached for n above the flush period.
152 store_counts(flushed);
153 EIGEN_UNROLL_LOOP
154 for (int k = 0; k < kUnroll; ++k) c[k] = pzero(c[k]);
155 flushed = true;
156 }
157 // Final conversion to the exact integer counts (adding any flushed partials).
158 store_counts(flushed);
159}
160
161template <typename RealScalar>
162void tridiagonal_sturm_counts(const RealScalar* alpha, const RealScalar* beta_sq, Index n, RealScalar pivmin,
163 const RealScalar* eval_points, numext::int64_t* count, Index num_points) {
164 using Packet = typename packet_traits<RealScalar>::type;
165 constexpr Index kPacketSize = Index(unpacket_traits<Packet>::size);
166 // num_points is a point count and so never negative; clearing the sign bit makes that explicit to
167 // the compiler, which lets the division by the power-of-two kPacketSize lower to a shift (and tidies
168 // the trailing cascade loops) instead of emitting signed-division sign-correction code.
169 num_points &= (std::numeric_limits<Index>::max)();
170 const Index full = num_points / kPacketSize; // number of whole packets
171
172 // Process whole packets in register-resident blocks, cascading 8 -> 4 -> 2 -> 1 so that even a
173 // small batch gets enough independent recurrences to hide the pdiv latency. Eight packets is the
174 // measured sweet spot on AVX2 (more would spill the per-lane state out of vector registers).
175 Index p = 0;
176 for (; p + 8 <= full; p += 8)
177 tridiagonal_sturm_block<8>(alpha, beta_sq, n, pivmin, eval_points, count, p * kPacketSize);
178 for (; p + 4 <= full; p += 4)
179 tridiagonal_sturm_block<4>(alpha, beta_sq, n, pivmin, eval_points, count, p * kPacketSize);
180 for (; p + 2 <= full; p += 2)
181 tridiagonal_sturm_block<2>(alpha, beta_sq, n, pivmin, eval_points, count, p * kPacketSize);
182 for (; p + 1 <= full; p += 1)
183 tridiagonal_sturm_block<1>(alpha, beta_sq, n, pivmin, eval_points, count, p * kPacketSize);
184
185 // Scalar tail for the remaining (< kPacketSize) points. The pivot recurrence is bit-identical to
186 // the packet path above; the count accumulates directly in the exact integer type.
187 for (Index j = full * kPacketSize; j < num_points; ++j) {
188 const RealScalar xj = eval_points[j];
189 RealScalar qj = alpha[0] - xj;
190 numext::int64_t cj = 0;
191 if (qj <= pivmin) {
192 ++cj;
193 qj = numext::mini(qj, -pivmin);
194 }
195 for (Index i = 1; i < n; ++i) {
196 qj = (alpha[i] - beta_sq[i - 1] / qj) - xj;
197 if (qj <= pivmin) {
198 ++cj;
199 qj = numext::mini(qj, -pivmin);
200 }
201 }
202 count[j] = cj;
203 }
204}
205
228template <typename RealScalar>
229void tridiagonal_bisection_block(const RealScalar* alpha, const RealScalar* beta_sq, Index n, RealScalar pivmin,
230 RealScalar bracket_lo, RealScalar bracket_hi, Index t_lo, Index t_hi, int max_iters,
231 RealScalar abs_tol, RealScalar* out) {
232 using ArrayType = Array<RealScalar, Dynamic, 1>;
233 using CountArrayType = Array<numext::int64_t, Dynamic, 1>;
234 const Index m = t_hi - t_lo;
235 if (m <= 0) return;
236
237 // The eigenvalue with 0-based index i is the value where count(x) crosses from <= i to > i.
238 // Counts and targets are exact 64-bit integers (see tridiagonal_sturm_counts()).
239 ArrayType lower = ArrayType::Constant(m, bracket_lo);
240 ArrayType upper = ArrayType::Constant(m, bracket_hi);
241 const CountArrayType targets = CountArrayType::LinSpaced(m, t_lo, t_hi - 1);
242 CountArrayType counts(m);
243 ArrayType mid = RealScalar(0.5) * (lower + upper);
244
245 // Each eigenvalue is recorded the iteration it first converges, using a per-element criterion that
246 // depends only on that element's own bracket. This makes the result independent of how the index
247 // range is grouped -- in particular bitwise identical for any number of threads -- because element
248 // i's converged value never depends on when its neighbors finish. (A single global convergence test
249 // would instead keep refining an already-converged eigenvalue until the slowest one in its group
250 // caught up, so the last bits would shift with the thread count.)
251 ArrayType result = mid;
252 Array<bool, Dynamic, 1> done = Array<bool, Dynamic, 1>::Constant(m, false);
253
254 // In the early iterations many eigenvalues still share a bracket, so their (sorted) midpoints are
255 // identical and the Sturm count need only be evaluated at the distinct values and scattered back.
256 // Once every midpoint is distinct the brackets only ever get finer, so we stop deduplicating and
257 // evaluate all midpoints directly, avoiding the per-iteration bookkeeping (and its unpredictable
258 // branch). Deduplication never changes the result: count(mid[i]) is unchanged. It only pays off
259 // when there are many more points than packets (otherwise the per-point kernel cost is too small
260 // to offset the bookkeeping), so the scratch is allocated only then.
261 constexpr int kPacketSize = unpacket_traits<typename packet_traits<RealScalar>::type>::size;
262 bool deduplicate = (m >= 64 * Index(kPacketSize));
263 ArrayType distinct;
264 CountArrayType counts_distinct;
265 if (deduplicate) {
266 distinct.resize(m);
267 counts_distinct.resize(m);
268 }
269 for (int iter = 0; iter < max_iters; ++iter) {
270 if (deduplicate) {
271 const RealScalar* midp = mid.data();
272 RealScalar* distp = distinct.data();
273 Index nd = 0;
274 distp[nd++] = midp[0];
275 for (Index i = 1; i < m; ++i)
276 if (midp[i] != midp[i - 1]) distp[nd++] = midp[i];
277 tridiagonal_sturm_counts<RealScalar>(alpha, beta_sq, n, pivmin, distp, counts_distinct.data(), nd);
278 const numext::int64_t* cdp = counts_distinct.data();
279 numext::int64_t* cp = counts.data();
280 Index g = 0;
281 cp[0] = cdp[g];
282 for (Index i = 1; i < m; ++i) {
283 if (midp[i] != midp[i - 1]) ++g;
284 cp[i] = cdp[g];
285 }
286 if (nd == m) deduplicate = false; // all distinct: finer brackets stay distinct
287 } else {
288 tridiagonal_sturm_counts<RealScalar>(alpha, beta_sq, n, pivmin, mid.data(), counts.data(), m);
289 }
290 // count(mid) <= target => eigenvalue is >= mid, raise the lower bound;
291 // otherwise the eigenvalue is < mid, so lower the upper bound.
292 const auto raise = (counts <= targets);
293 lower = raise.select(mid, lower);
294 upper = raise.select(upper, mid);
295 const ArrayType new_mid = RealScalar(0.5) * (lower + upper);
296 // Freeze each eigenvalue at the midpoint of its bracket the first time that bracket is tight
297 // enough (width within abs_tol) or stops moving. Already-frozen entries keep their value.
298 const auto converged = (new_mid == mid) || ((upper - lower) <= abs_tol);
299 result = (!done && converged).select(new_mid, result);
300 done = done || converged;
301 mid = new_mid;
302 if (done.all()) break;
303 }
304 // Any eigenvalue that did not converge within max_iters keeps its last midpoint, written straight
305 // into the caller's output (no aliasing temporary: select is coefficient-wise and out is disjoint).
306 Eigen::Map<ArrayType>(out, m) = done.select(result, mid);
307}
308
329template <typename RealScalar>
330Index tridiagonal_sturm_count_below(const RealScalar* alpha, const RealScalar* beta_sq, Index n, RealScalar pivmin,
331 RealScalar x) {
332 RealScalar q = alpha[0] - x;
333 Index count = (q < RealScalar(0)) ? 1 : 0;
334 if (numext::abs(q) < pivmin) q = numext::copysign(pivmin, q);
335 for (Index i = 1; i < n; ++i) {
336 q = (alpha[i] - beta_sq[i - 1] / q) - x;
337 if (q < RealScalar(0)) ++count;
338 if (numext::abs(q) < pivmin) q = numext::copysign(pivmin, q);
339 }
340 return count;
341}
342
362template <typename DiagType, typename SubdiagType, typename EivalType>
363Index tridiagonal_bisection(const DiagType& diag, const SubdiagType& subdiag, const EigenvalueRange& range,
364 typename DiagType::Scalar abs_tol, EivalType& eivalues) {
365 using RealScalar = typename DiagType::Scalar;
366 using ArrayType = Array<RealScalar, Dynamic, 1>;
367 EIGEN_STATIC_ASSERT(NumTraits<RealScalar>::IsInteger == 0 && NumTraits<RealScalar>::IsComplex == 0,
368 THIS_FUNCTION_IS_NOT_FOR_INTEGER_OR_COMPLEX_TYPES)
369
370 const Index n = diag.size();
371 if (n == 0) {
372 eivalues.derived().resize(0);
373 return 0;
374 }
375
376 // Normalize the matrix to O(1) to avoid overflow/underflow when squaring the
377 // off-diagonal and during the Sturm recurrence; eigenvalues scale linearly, so the
378 // scaling is undone at the very end. (The caller has already verified the input is
379 // finite.) This mirrors the uniform scaling done in SelfAdjointEigenSolver::compute().
380 // The rescan recovers an all-subnormal input that a flushing SIMD unit reads as zero.
381 RealScalar maxCoeff = safe_scaling<RealScalar>::recover_flushed_max_coeff(diag, diag.cwiseAbs().maxCoeff());
382 if (n >= 2) {
383 maxCoeff = max_preserving_subnormals(
384 maxCoeff, safe_scaling<RealScalar>::recover_flushed_max_coeff(subdiag, subdiag.cwiseAbs().maxCoeff()));
385 }
386
387 // Local contiguous copies of the scaled matrix data, |off-diagonal|, and its square.
388 ArrayType alpha(n), beta_abs(n - 1);
389 const auto factors = safe_scaling<RealScalar>::scale_to(alpha, diag.array(), maxCoeff);
390 if (n >= 2) {
391 safe_scaling<RealScalar>::scale_to(beta_abs, subdiag.array(), maxCoeff, factors);
392 // |e| from the representation after scaling: MSVC's fabsf goes through double, and narrowing a subnormal result
393 // back to float flushes it to zero under FTZ.
394 for (Index i = 0; i < n - 1; ++i) beta_abs(i) = abs_preserving_subnormals(beta_abs(i));
395 }
396 const RealScalar scale = factors.scale;
397 const ArrayType beta_sq = (n >= 2) ? ArrayType(beta_abs.square()) : ArrayType(0);
398
399 // Smallest pivot allowed during the Sturm recurrence (positive, floored so
400 // that a matrix with an all-zero off-diagonal still has pivmin > 0).
401 const RealScalar eps = NumTraits<RealScalar>::epsilon();
402 const RealScalar safemin = numext::maxi(RealScalar(1) / NumTraits<RealScalar>::highest(),
403 (RealScalar(1) + eps) * (std::numeric_limits<RealScalar>::min)());
404 const RealScalar max_beta_sq = (n >= 2) ? beta_sq.maxCoeff() : RealScalar(0);
405 const RealScalar pivmin = safemin * numext::maxi(max_beta_sq, RealScalar(1));
406
407 // Gershgorin bounds: row k has radius |beta_{k-1}| + |beta_k| (with the
408 // missing boundary off-diagonals taken as zero).
409 ArrayType radius(n);
410 if (n == 1) {
411 radius(0) = RealScalar(0);
412 } else {
413 radius(0) = beta_abs(0);
414 radius(n - 1) = beta_abs(n - 2);
415 if (n > 2) radius.segment(1, n - 2) = beta_abs.head(n - 2) + beta_abs.segment(1, n - 2);
416 }
417 RealScalar lambda_min = (alpha - radius).minCoeff();
418 RealScalar lambda_max = (alpha + radius).maxCoeff();
419
420 // Effective tolerance and outward expansion of the bracket so that
421 // count(lambda_min) == 0 and count(lambda_max) == n (cf. LAPACK xSTEBZ).
422 const RealScalar tnorm = numext::maxi(numext::abs(lambda_min), numext::abs(lambda_max));
423 // Convergence tolerance: refine each bracket to ~1 ulp of ||T||. This is the role of xSTEBZ's
424 // relative tolerance (it stops once b - a < RELFAC * ulp * max(|a|, |b|), RELFAC = 2), here
425 // specialized to the matrix norm tnorm and folded together with any absolute tolerance the caller
426 // requested.
427 // The 2*pivmin floor mirrors xSTEBZ/xLAEBZ (which converge on max(abstol, pivmin, reltol*...)):
428 // below the pivot floor the Sturm counts are meaningless anyway, and stopping there keeps the
429 // bracket midpoints out of the subnormal range, where hardware with flush-to-zero packet
430 // arithmetic (ARMv7 NEON) would evaluate them inconsistently between the packet and scalar paths.
431 abs_tol = numext::maxi(abs_tol / scale, numext::maxi(eps * tnorm, RealScalar(2) * pivmin));
432 // Widen the Gershgorin bracket so that, despite rounding in the Sturm recurrence, count() really does
433 // reach 0 at lambda_min and n at lambda_max. The n*eps*tnorm term bounds the worst-case count error
434 // accumulated over the n recurrence steps; the 2*pivmin term covers the pivot floor. The 2.1 prefactor
435 // is xSTEBZ's FUDGE factor: ideally 1 would suffice, but it is taken slightly larger to stay robust on
436 // sloppy arithmetic, and (per xSTEBZ) widening the bracket this way only loosens the initial search
437 // interval -- it has no effect on the accuracy of the converged eigenvalues.
438 const RealScalar expand = RealScalar(2.1) * (RealScalar(n) * eps * tnorm + RealScalar(2) * pivmin);
439 lambda_min -= expand;
440 lambda_max += expand;
441
442 // Determine the target indices [t_lo, t_hi) and the search bracket.
443 Index t_lo = 0, t_hi = n;
444 RealScalar bracket_lo = lambda_min, bracket_hi = lambda_max;
445 bool value_filter = false;
446 RealScalar vl_n = RealScalar(0), vu_n = RealScalar(0);
447 RealScalar end_tol_lo = RealScalar(0), end_tol_hi = RealScalar(0);
448 if (range.kind == EigenvalueRange::ByIndex) {
449 eigen_assert(range.il >= 0 && range.il <= range.iu && range.iu <= n && "invalid eigenvalue index range");
450 t_lo = range.il;
451 t_hi = range.iu;
452 } else if (range.kind == EigenvalueRange::ByValue) {
453 eigen_assert(range.vl <= range.vu && "invalid eigenvalue value range");
454 vl_n = RealScalar(range.vl) / scale;
455 vu_n = RealScalar(range.vu) / scale;
456 // A Sturm count taken exactly at an endpoint is unreliable when an eigenvalue sits there: the
457 // rounded terminal pivot's sign is arbitrary, so an eigenvalue equal to vl could be silently
458 // dropped, or one equal to vu kept, breaking the documented half-open [vl, vu) semantics.
459 // Instead, count at endpoints widened outward by a bound on the count's rounding displacement
460 // (the same form as the Gershgorin expansion above, per endpoint magnitude, plus the convergence
461 // tolerance) -- a guaranteed superset of [vl, vu) -- converge that superset, and resolve
462 // endpoint membership against the *converged* eigenvalues after the bisection (see below).
463 // An infinite endpoint (given as such, or a finite long double that narrowed to infinity in
464 // RealScalar) means unbounded on that side: the count is trivial there, and the tolerance
465 // arithmetic must not run (inf - inf = NaN would empty the result).
466 if ((numext::isfinite)(vl_n)) {
467 end_tol_lo =
468 RealScalar(2.1) * (RealScalar(n) * eps * numext::maxi(tnorm, numext::abs(vl_n)) + RealScalar(2) * pivmin) +
469 abs_tol;
470 t_lo = tridiagonal_sturm_count_below<RealScalar>(alpha.data(), beta_sq.data(), n, pivmin,
471 vl_n - RealScalar(2) * end_tol_lo);
472 bracket_lo = numext::maxi(lambda_min, vl_n - RealScalar(2) * end_tol_lo);
473 } else {
474 t_lo = (vl_n < RealScalar(0)) ? 0 : n;
475 }
476 if ((numext::isfinite)(vu_n)) {
477 end_tol_hi =
478 RealScalar(2.1) * (RealScalar(n) * eps * numext::maxi(tnorm, numext::abs(vu_n)) + RealScalar(2) * pivmin) +
479 abs_tol;
480 t_hi = tridiagonal_sturm_count_below<RealScalar>(alpha.data(), beta_sq.data(), n, pivmin, vu_n + end_tol_hi);
481 bracket_hi = numext::mini(lambda_max, vu_n + RealScalar(2) * end_tol_hi);
482 } else {
483 t_hi = (vu_n > RealScalar(0)) ? n : 0;
484 }
485 value_filter = true;
486 }
487
488 const Index m = t_hi - t_lo;
489 eivalues.derived().resize(m);
490 if (m <= 0) return 0;
491
492 const int max_iters = NumTraits<RealScalar>::digits() + 2;
493 ArrayType mid_all(m);
494 RealScalar* out = mid_all.data();
495 const RealScalar* alpha_p = alpha.data();
496 const RealScalar* beta_sq_p = beta_sq.data();
497
498 // The m eigenvalues are bisected independently, so the spectrum splits cleanly across threads with
499 // a single fork/join: thread t owns the contiguous index block [t_lo + lo, t_lo + hi) and writes
500 // its converged midpoints into the disjoint slice out[lo, hi). No communication until the join.
501#if defined(EIGEN_HAS_OPENMP)
502 int nthreads = 1;
503 // Don't nest inside an existing parallel region, and only fork when there is enough work to
504 // amortize the thread overhead while still leaving each thread a SIMD-friendly chunk of points.
505 if (omp_get_num_threads() == 1) {
506 constexpr Index kPacketSize = Index(unpacket_traits<typename packet_traits<RealScalar>::type>::size);
507 // One work unit ~ one Sturm step (a packet division); kMinTaskSize is the minimum per thread.
508 // Multiply in double: the product overflows a 32-bit Index for matrices well within reach.
509 const double work = double(m) * double(n) * double(max_iters);
510 const double kMinTaskSize = 131072.0;
511 const Index work_threads = Index(work / kMinTaskSize);
512 const Index point_threads = m / (8 * kPacketSize);
513 const Index pb = numext::maxi(Index(1), numext::mini(work_threads, point_threads));
514 nthreads = int(numext::mini(pb, Index(Eigen::nbThreads())));
515 }
516 if (nthreads > 1) {
517#pragma omp parallel num_threads(nthreads)
518 {
519 const Index nt = omp_get_num_threads();
520 const Index tid = omp_get_thread_num();
521 // Balanced split: every thread gets floor(m/nt) or ceil(m/nt) consecutive indices, none empty.
522 const Index lo = tid * m / nt;
523 const Index hi = (tid + 1) * m / nt;
524 tridiagonal_bisection_block<RealScalar>(alpha_p, beta_sq_p, n, pivmin, bracket_lo, bracket_hi, t_lo + lo,
525 t_lo + hi, max_iters, abs_tol, out + lo);
526 }
527 } else
528#endif
529 {
530 tridiagonal_bisection_block<RealScalar>(alpha_p, beta_sq_p, n, pivmin, bracket_lo, bracket_hi, t_lo, t_hi,
531 max_iters, abs_tol, out);
532 }
533
534 // Enforce the documented non-decreasing order. Under uniform IEEE arithmetic the independent
535 // brackets already yield sorted midpoints, but on hardware with non-uniform subnormal handling
536 // (ARMv7, where NEON packet lanes flush to zero while the scalar VFP tail does not) neighbouring
537 // targets refined through the two paths can disagree by ~pivmin at the subnormal boundary. The
538 // cumulative max restores the invariant at O(m) cost and is a no-op for already-sorted output.
539 for (Index j = 1; j < m; ++j) out[j] = numext::maxi(out[j], out[j - 1]);
540
541 // ByValue: resolve endpoint membership against the converged eigenvalues. Comparing against both
542 // endpoints shifted DOWN by the endpoint tolerance gives every eigenvalue within that tolerance of
543 // an endpoint the documented closed/open treatment of the endpoint itself: an eigenvalue equal to
544 // vl is deterministically kept and one equal to vu deterministically dropped, however the rounded
545 // Sturm counts at the endpoints came out. Eigenvalues near (but not on) an endpoint may resolve to
546 // either side, as with LAPACK xSTEBZ, whose endpoint counts carry the same rounding ambiguity.
547 Index m_out = m;
548 if (value_filter) {
549 // Infinite endpoints pass through unshifted: every finite eigenvalue satisfies >= -inf / < +inf.
550 const RealScalar keep_lo = (numext::isfinite)(vl_n) ? vl_n - end_tol_lo : vl_n;
551 const RealScalar keep_hi = (numext::isfinite)(vu_n) ? vu_n - end_tol_hi : vu_n;
552 Index k = 0;
553 for (Index j = 0; j < m; ++j)
554 if (out[j] >= keep_lo && out[j] < keep_hi) out[k++] = out[j];
555 m_out = k;
556 if (m_out != m) eivalues.derived().resize(m_out);
557 }
558
559 // Undo the normalization.
560 eivalues = mid_all.head(m_out).matrix();
561 safe_scaling<RealScalar>::unscale_in_place(eivalues, maxCoeff, factors);
562 return m_out;
563}
564
565} // namespace internal
566} // namespace Eigen
567
568#endif // EIGEN_TRIDIAGONAL_BISECTION_H
Selects which eigenvalues to compute in a spectral-bisection solve.
Definition TridiagonalBisection.h:38
static EigenvalueRange all()
Definition TridiagonalBisection.h:50
static EigenvalueRange indices(Index il, Index iu)
Definition TridiagonalBisection.h:53
static EigenvalueRange values(long double vl, long double vu)
Definition TridiagonalBisection.h:56