Skip to content

Scale ABSTOL with the matrix in xSTEVX and xSTEVR - #1386

Open
rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:stevx-abstol-scaling
Open

rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:stevx-abstol-scaling

Conversation

@rmlarsen

@rmlarsen rmlarsen commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Disclaimer: This PR was prepared using Claude Code.

Summary

xSTEVX and xSTEVR scale the tridiagonal matrix into [RMIN, RMAX] before calling xSTEBZ but, unlike the other 28 drivers with the same scaling block, pass the caller's ABSTOL unscaled. A positive ABSTOL is then applied to a matrix that has been multiplied by SIGMA = RMAX/|T| when |T| > RMAX = SAFMIN**(-1/4) (1.6e77 in double precision, 3e9 in single), or by RMIN/|T| when |T| < RMIN. Above RMAX bisection therefore stops as soon as an interval is narrower than ABSTOL, which in the scaled units is ABSTOL/SIGMA and can exceed the norm of the scaled matrix: SSTEVX on tridiag(1e12, 5e11, 1e12) with ABSTOL = 1e-6 |T| returns eigenvalues with relative errors of 2e-4 (3e-2 at 1e14), DSTEVX at |T| = 1e100 with ABSTOL = 1e-10 |T| returns 0.4, all with INFO = 0; SSYEVX/DSYEVX on the same matrices honour the tolerance. Below RMIN the tolerance is only applied more tightly than requested. This PR scales ABSTOL with the matrix in the four tridiagonal drivers, as ABSTLL = MAX( MIN( ABSTOL, |T| )*SIGMA, SAFMIN ), so that the scaled tolerance can neither underflow to zero nor overflow.

Description

The scaling in these drivers exists so that the caller does not have to (the drivers' RMAX is exactly the largest entry xSTEBZ documents as safe), so a matrix with entries above 3e9 in single precision or 1.6e77 in double is an ordinary input, and the routines' contract is that each eigenvalue is found to within ABSTOL (plus EPS |T|). Maximum relative eigenvalue error for T = s * tridiag(1, 0.5, 1), n = 6, whose eigenvalues are $s\,(1 + \cos(k\pi/7))$, with ABSTOL = rel * |T|:

precision s ABSTOL / |T| xSTEVX master xSTEVX this branch xSYEVX
single 1e14 1e-6 3.0e-2 7.2e-7 7.2e-7
single 1e12 1e-6 2.0e-4 7.2e-7 7.2e-7
single 1e10 1e-6 1.2e-6 6.0e-7 6.0e-7
single 1e8 1e-6 7.2e-7 7.2e-7 7.2e-7
double 1e100 1e-10 4.0e-1 5.4e-11 5.4e-11
double 1e84 1e-10 8.4e-4 5.4e-11 5.4e-11
double 1e80 1e-10 6.4e-8 5.4e-11 5.4e-11
double 1e76 1e-10 5.4e-11 5.4e-11 5.4e-11

xSTEVR reaches xSTEBZ for RANGE = 'V' and whenever xSTEMR fails, with the same unscaled ABSTOL; it is fixed the same way. Its TRYRAC decision (ABSTOL .LE. 2 N EPS) is left as in xSYEVR.

The plain ABSTLL = ABSTOL*SIGMA of xSYEVX is not enough. For the documented "most accurate" ABSTOL = 2*SAFMIN the product underflows to zero once |T| exceeds roughly RMAX/EPS (1e93 in double precision, 5e16 in single), and xSTEBZ reads a zero tolerance as a request for its default EPS*|T|, which for a graded matrix is far worse than the unscaled 2*SAFMIN master passes: DSTEVX on D = (1e100, 1), E = 5e49 (eigenvalues 1e100 and 0.75) with ABSTOL = 2*DLAMCH('S') returns 0.75 on master and 1.1e83 with the plain product, INFO = 0 (SSTEVX on D = (1e20, 1), E = 5e9: 1.8e12). In xLAEBZ an interval has converged once its width is below MAX( ABSTOL, PIVMIN, RELTOL*|lambda| ), and xSTEBZ sets PIVMIN >= SAFMIN, so every positive tolerance below SAFMIN acts exactly like SAFMIN: the floor changes nothing that did not underflow. When a tiny matrix is scaled up (|T| < RMIN, SIGMA up to about 1e154) the product can instead overflow, which traps under -ffpe-trap=overflow; a tolerance above |T| is already met by the Gershgorin interval, so capping ABSTOL at |T| before scaling changes nothing that could not overflow. xSYEVX and the other 27 drivers with the ABSTLL = ABSTOL*SIGMA block have both defects on master (DSYEVX returns 1.1e83 on the matrix above); they are left for a separate PR.

Minimal reproducer

! SSTEVX on T = 1e12 * tridiag(1, 0.5, 1), n = 6, ABSTOL = 1e-6 * |T|; exact: 1e12 * (1 + cos(k pi / 7)).
program minimal
  implicit none
  integer, parameter :: n = 6
  real :: d(n), e(n-1), w(n), z(n,n), work(8*n), abstol, pi, a(n,n), w2(n), wex(n)
  integer :: iwork(5*n), ifail(n), info, m, k, i
  pi = 4*atan(1.0)
  d = 1e12
  e = 0.5e12
  abstol = 1e-6*2e12
  a = 0
  do i = 1, n
    a(i,i) = d(i)
  end do
  do i = 1, n-1
    a(i,i+1) = e(i); a(i+1,i) = e(i)
  end do
  do k = 1, n
    wex(n+1-k) = 1e12*(1 + cos(k*pi/(n+1)))
  end do
  call sstevx('N', 'A', n, d, e, 0.0, 0.0, 0, 0, abstol, m, w, z, n, work, iwork, ifail, info)
  print '(a,i2,a,6es12.5)', 'SSTEVX info = ', info, '  w = ', w
  call ssyevx('N', 'A', 'U', n, a, n, 0.0, 0.0, 0, 0, abstol, m, w2, z, n, work, 8*n, iwork, ifail, info)
  print '(a,i2,a,6es12.5)', 'SSYEVX info = ', info, '  w = ', w2
  print '(a,6es12.5)', 'exact               ', wex
end program
BEFORE (master):
SSTEVX info =  0  w =  9.88740E+10 3.76707E+11 7.77587E+11 1.22241E+12 1.62329E+12 1.90113E+12
SSYEVX info =  0  w =  9.90314E+10 3.76509E+11 7.77479E+11 1.22252E+12 1.62349E+12 1.90097E+12
exact               9.90311E+10 3.76510E+11 7.77479E+11 1.22252E+12 1.62349E+12 1.90097E+12
AFTER (this branch):
SSTEVX info =  0  w =  9.90314E+10 3.76509E+11 7.77479E+11 1.22252E+12 1.62349E+12 1.90097E+12
SSYEVX info =  0  w =  9.90314E+10 3.76509E+11 7.77479E+11 1.22252E+12 1.62349E+12 1.90097E+12
exact               9.90311E+10 3.76510E+11 7.77479E+11 1.22252E+12 1.62349E+12 1.90097E+12

Regression test. xDRVST gets a matrix type 19: a tridiagonal matrix with equal diagonal entries and half that off the diagonal, at the "large" magnitude the existing types already use, which is above the norm from which the drivers scale down. It is the only type that asks for a positive ABSTOL, ten ulps of the matrix norm.

Nothing in the suite could have caught this. Every type passes ABSTOL = 2*SAFMIN, which is at or below the smallest pivot and therefore inert inside xLAEBZ, and the tridiagonal drivers were exercised only on the diagonal matrices of types 1 to 7, where every block of the bisection is one by one and the tolerance never enters. The new type lifts that restriction for itself alone, so xSTEV, xSTEVX, xSTEVR and xSTEVD see a genuine tridiagonal for the first time.

On the parent commit the type fails 18 ratios in single precision and 40 in double, comparing xSTEVX and xSTEVR against xSTEV; with the fix all of them pass.

Validation

  • The full LAPACK test suite passes on this branch: 5449821 LAPACK tests, 0 numerical errors, 0 other errors (ctest: 100% of 215 tests passed). The 7920 tests above the parent commit are the new type.
  • The table above; after the change xSTEVX and xSYEVX agree to the last digit at every scale.
  • ABSTOL = 2*SAFMIN on the graded matrices above: DSTEVX (RANGE = 'A' and 'V'), DSTEVR ('V'), SSTEVX and SSTEVR return 0.75 as on master. Under -ffpe-trap=overflow, D = (1e-300, 1e-301), E = 1e-301 with ABSTOL = 1e200 runs through the four drivers without a trap.
  • For ABSTOL <= 0, and for any matrix the driver does not scale, the call to xSTEBZ is unchanged.

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.37%. Comparing base (a6c6e74) to head (936f6a3).
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1386      +/-   ##
==========================================
+ Coverage   69.36%   69.37%   +0.01%     
==========================================
  Files        6122     6122              
  Lines      486711   486742      +31     
  Branches    23268    23268              
==========================================
+ Hits       337584   337668      +84     
+ Misses     148689   148636      -53     
  Partials      438      438              
Components Coverage Δ
BLAS 97.94% <ø> (ø)
CBLAS 96.98% <ø> (ø)
LAPACK 82.41% <100.00%> (+0.02%) ⬆️
LAPACKE 2.17% <ø> (ø)
TMGLIB 55.69% <ø> (ø)
BLAS testing 88.33% <ø> (ø)
CBLAS testing 89.63% <ø> (ø)
LAPACK testing 82.21% <100.00%> (+<0.01%) ⬆️
LAPACKE testing ∅ <ø> (∅)
Files with missing lines Coverage Δ
SRC/dstevr.f 96.21% <100.00%> (+0.86%) ⬆️
SRC/dstevx.f 82.94% <100.00%> (+0.40%) ⬆️
SRC/sstevr.f 96.12% <100.00%> (+0.88%) ⬆️
SRC/sstevx.f 82.94% <100.00%> (+0.40%) ⬆️
TESTING/EIG/dchkee.F 79.17% <ø> (ø)
TESTING/EIG/ddrvst.f 70.00% <100.00%> (+0.21%) ⬆️
TESTING/EIG/schkee.F 79.20% <ø> (ø)
TESTING/EIG/sdrvst.f 70.00% <100.00%> (+0.21%) ⬆️

... and 8 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a6c6e74...936f6a3. Read the comment docs.

@rmlarsen
rmlarsen force-pushed the stevx-abstol-scaling branch from 7885655 to b364dad Compare September 8, 2026 04:19
@rmlarsen

rmlarsen commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Verified on an Apple M4 (macOS, Homebrew gfortran 16.2, Release build with the CI flags). With this branch merged onto current master, the full test suite passes, the new tests fail without the fix, and the reproducer behaves as described above.

xSTEVX and xSTEVR scale the tridiagonal matrix into [RMIN, RMAX]
before calling xSTEBZ but, unlike the other 28 drivers with the same
scaling block, pass the caller's ABSTOL unscaled.  A positive ABSTOL is
then applied to a matrix multiplied by SIGMA = RMAX / |T| whenever
|T| > RMAX = SAFMIN**(-1/4), which is 1.6e77 in double precision and
3e9 in single, so bisection stops as soon as an interval is narrower
than ABSTOL / SIGMA in the caller's units, a width that can exceed the
norm of the scaled matrix.  SSTEVX on tridiag(1e12, 5e11, 1e12) with
ABSTOL = 1e-6 |T| returns eigenvalues with relative errors of 2e-4,
3e-2 at 1e14, and DSTEVX at |T| = 1e100 with ABSTOL = 1e-10 |T| returns
0.4, all with INFO = 0, while SSYEVX and DSYEVX honour the tolerance.
Below RMIN the tolerance is only applied more tightly than requested.

Scale a positive ABSTOL with the matrix and pass
ABSTLL = MAX( MIN( ABSTOL, |T| )*SIGMA, SAFMIN ) to xSTEBZ.  The plain
product of xSYEVX is not enough: for the documented "most accurate"
ABSTOL = 2*SAFMIN it underflows to zero once |T| > RMAX/EPS, and xSTEBZ
reads a zero tolerance as a request for its default EPS*|T|, so DSTEVX
on D = (1e100, 1), E = 5e49 would return 1.1e83 for the eigenvalue
0.75.  Every positive tolerance below PIVMIN >= SAFMIN is equivalent in
xLAEBZ, so the floor changes nothing that did not underflow; a
tolerance above |T| is met by the Gershgorin interval, so the cap
changes nothing that could not overflow when a tiny matrix is scaled
up.  Nothing changes for ABSTOL <= 0 or for a matrix the driver does
not scale.

xDRVST gets a matrix type for it: type 19 is a tridiagonal matrix with
equal diagonal entries and half that off the diagonal, scaled to the
"large" magnitude of the existing types, which is above the norm the
drivers scale down from, and it is the only type that asks for a
positive ABSTOL, ten ulps of the matrix norm.  Every other type passes
2*SAFMIN, which is inert in the bisection and so cannot show the
defect, and the tridiagonal drivers were tested on diagonal matrices
alone, where each block is one by one and the tolerance never matters.
On the parent commit the type fails 18 ratios in single precision and
40 in double.

With the change xSTEVX and xSYEVX agree to the last digit
on the matrices above at every scale, and ABSTOL = 2*SAFMIN gives the
same eigenvalues as on master.  The full LAPACK test suite passes:
5449821 LAPACK tests, 0 numerical errors, 0 other errors; the 7920
tests above the parent are the new type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rmlarsen
rmlarsen force-pushed the stevx-abstol-scaling branch from b364dad to 936f6a3 Compare September 15, 2026 20:28
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.

1 participant