Skip to content

evd: scale the input into range as ?GEEV does (#112) - #148

Closed
shinaoka wants to merge 3 commits into
mainfrom
fix/112-c32-evd
Closed

shinaoka wants to merge 3 commits into
mainfrom
fix/112-c32-evd

Conversation

@shinaoka

Copy link
Copy Markdown
Member

Closes #112.

Summary

As the maintainer asked (2026-10-29), I compared faer's nonsymmetric EVD with LAPACK ?GEEV stage by stage. faer skipped ?GEEV's first step: when the largest entry is below smlnum = sqrt(safmin) / eps or above bignum = 1 / smlnum, LAPACK scales the matrix to that bound before the Hessenberg reduction and scales the eigenvalues back at the end. evd_imp now does the same with a power of two, which scales every entry exactly and does not change the eigenvectors.

Stage-by-stage comparison (c32 kinds 6/7, n = 63 to 65)

stage as is scaled to unit size first
Hessenberg backward error / eps 16 to 20 16 to 20
complex Schur backward error / eps 226 to 258 67 to 93
GET22 46 to 58 16 to 20 (cgeev: 29 to 40)

So the gap arises in the complex QR iteration on the unscaled matrix.

Where the gap grows

I swept 372 matrices over f32/f64/c32/c64, n = 16, 64, 128 and 257, and four families: random, Grcar, near-Jordan, and graded D A D^-1 with D spanning 1e±6. Scales went up to the range limits, and LAPACK ?geev (scipy/OpenBLAS) was run on the same files. Below the bound the gap was far worse than #112's factor of 1.5:

scale (n = 64; Grcar, random, near-Jordan; max of right and left) faer before LAPACK faer after
f32/c32 2^-80 2.8 to 30 2.6 to 34 3.2 to 26
f32/c32 2^-90 27 to 1750; f32 near-Jordan NoConvergence 2.6 to 34 3.2 to 26
f32/c32 2^-100 2.4e4 to 3.5e5; f32 near-Jordan NoConvergence 2.6 to 34 3.0 to 29
f32/c32 2^-110, 2^-120 1.0e6 to 4.2e6 2.6 to 33 3.2 to 28
f64/c64 2^-900 3.9 to 31 3.1 to 37 3.9 to 44
f64/c64 2^-960 1.5e11 to 2.1e12 3.1 to 37 3.9 to 35
f64/c64 2^-1000, 2^-1010 5.4e14 to 2.3e15 3.1 to 41 3.9 to 44

At 2^-100, f32 near-Jordan n = 128 and 257 did not return at all. That hang is a separate defect, #144: with the scaling, Mat::eigen no longer reaches it.

After the change, every non-graded series is at most 1.74 times LAPACK, at every n, scale and type. The two cells above 1.5 are at n = 16, where the absolute values are small: f32 near-Jordan gives 9.15 against 5.26 (the same at scale 1), and c32 random at 2^-60 and 2^-100 gives 3.74 against 2.25.

The graded family has a different, serious defect in the real eigenvector back substitution. It is unrelated to scaling: GET22 reaches 2.6e9 against LAPACK below 5e-4, and upstream 0.24.4 also fails. It is filed as #145 and not touched here.

Changes

  • faer/src/linalg/evd/mod.rs: geev_scaling and its use in evd_imp. The bound is tested on the largest |re| or |im|, not on faer's complex modulus, which is inf for 2^1023 + 0i and zero for 2^-1074 + 0i (post-review finding: a modulus scan picked a zero factor and returned a NaN eigenvalue with Ok; evd_scaling_at_the_complex_range_ends covers it and fails with the modulus scan). Also the test geev_scaling_tests::evd_scales_its_input_into_range. The test covers Grcar and pseudo-random n = 32, all four types, 2^-120 to 2^120 (32-bit) and 2^-1010 to 2^1015 (64-bit), right and left vectors, and the GET22 threshold 20 * max(1, sqrt(n / 16)).
  • scripts/lapack-known-failures.json: the LAPACK validation: the nonsymmetric EVD fixtures exceed the GET22-style residual threshold (4 dtypes) #112 entry is removed, because the c32 group now passes.
  • docs/lapack-validation.md, PATCHSET.md: one row and one paragraph.
  • upstream-reports/faer-028-evd-input-not-scaled: reproduces the defect on faer 0.24.4: f64 2^-960 gives GET22 1.1e15, and f64 2^-1000 and f32 2^-100 return eigenvectors with NaN/inf entries, with Ok. Not posted upstream.

LAPACK sources were read only; no code was copied.

Verification (primerose, 4 cores)

  • The new test fails with the scaling disabled (f32 2^-120, right: GET22 1.7e6) and passes with it.
  • scripts/run-lapack-validation.py: every family exits 0, including nonsymmetric_evd 28/28, hessenberg, schur, balance and self_adjoint_evd. The known-failures list is now empty.
  • cargo test -p t4a-faer --lib linalg::evd: 106 passed. The LAPACK nonsymmetric EVD tests (--include-ignored), rerun after the post-review fix: 158 passed.
  • Independent post-review (gpt-6.1-sol): one BLOCKER (the complex modulus at the range ends) and one IMPORTANT (the test's GET22 helper dropped a NaN through f64::max), both fixed. The MINOR findings (the left side is checked as the right eigenvectors of A^H; universal wording in the docs) were addressed by narrowing the claims.
  • faer-028 against this branch prints 17.7 (f64) and 15.2 (f32) at every scale.
  • Performance: the change adds one pass over the input plus the eigenvalue unscaling. It was not timed.

🤖 Generated with Claude Code

shinaoka and others added 3 commits October 29, 2026 20:26
evd_imp reduced and iterated on the matrix as given. ?GEEV first scales it
into [smlnum, bignum] (smlnum = sqrt(safmin) / eps). Without that, entries
below about 2^-90 (32-bit) or 2^-960 (64-bit) went through the Hessenberg
reduction and the Schur iteration in subnormals and the eigenvectors lost
every digit (GET22 1e5 to 2e15, LAPACK 2 to 70), and the complex Schur
iteration near the top of the range lost the factor that kept the c32
LAPACK group failing. The scaling is a power of two, so it is exact, and the
eigenvalues are scaled back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
faer-029 reproduces the lost eigenvectors of a tiny matrix on faer 0.24.4
(f64 2^-960: GET22 1.1e15; f32 2^-100: 1.8e6; LAPACK 16 and 16) and on the
fork prints 17.7 and 15.2 at every scale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view)

faer's complex modulus is inf for 2^1023 + 0i and zero for 2^-1074 + 0i, so a
modulus-based scan selected a zero factor (NaN eigenvalue with Ok) or no
scaling at all. The tests reject nonfinite results before f64::max can drop
them; the report notes that faer 0.24.4 returns nonfinite eigenvectors with
Ok at 2^-1000 (f64) and 2^-100 (f32); the docs restrict their claims to the
measured families.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka

Copy link
Copy Markdown
Member Author

Combined into #154 together with the other two nonsymmetric EVD fixes (#148, #151, #152), as the maintainer directed on 2026-10-29. The commits are carried over with their authorship; the upstream reports are renumbered faer-029 (#112), faer-030 (#144) and faer-031 (#145).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LAPACK validation: the nonsymmetric EVD fixtures exceed the GET22-style residual threshold (4 dtypes)

1 participant