Skip to content

Do not scale an infinite matrix to zero in xSTEMR; report it from xLARRE - #1404

Open
rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:stemr-infinite-matrix
Open

Do not scale an infinite matrix to zero in xSTEMR; report it from xLARRE#1404
rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:stemr-infinite-matrix

Conversation

@rmlarsen

Copy link
Copy Markdown
Contributor

Disclaimer: This PR was prepared using Claude Code.

Summary

xSTEMR scales the matrix into the range its representations can handle, SCALE = RMAX / TNRM when the max-norm TNRM exceeds RMAX. For a matrix with an infinite entry TNRM is infinite, SCALE underflows to zero, the scaling turns the infinite entry into a NaN and every other entry into zero, and the solver ran on that matrix: RANGE = 'A' and 'I' returned INFO = 0 with every eigenvalue a NaN, RANGE = 'V' scaled the interval to (0, 0] and returned no eigenvalues, and with the infinite entry off the diagonal the representation search never returned (the NaN hang of #1389). This PR leaves such a matrix unscaled and lets xLARRE reject a matrix with an infinite entry as one for which no base representation exists, so xSTEMR returns INFO = 12 and its drivers fall back to xSTEBZ and xSTEIN. Six files: {s,d,c,z}stemr.f, {s,d}larre.f.

Description

         ELSE IF( TNRM.GT.RMAX ) THEN
            SCALE = RMAX / TNRM
         END IF
         IF( SCALE.NE.ONE ) THEN
            CALL DSCAL( N, SCALE, D, 1 )
            CALL DSCAL( N-1, SCALE, E, 1 )

RMAX is about 1e77 in double precision, so RMAX / TNRM is at least 5e-232 for every finite TNRM and is zero exactly when TNRM is infinite. The scaled matrix has Inf * 0 = NaN at the infinite entry and zeros elsewhere; xLARRE then computes Gershgorin bounds and representations from that, and xLARRV or xLARRJ refine them. At the end the eigenvalues are divided by SCALE, 0 * Inf = NaN.

Fix. xSTEMR keeps SCALE = ONE when the quotient is zero, which happens for no finite matrix. xLARRE tests the max-norm of the matrix it is given against the overflow threshold right after its Gershgorin bounds and returns INFO = 2, the code it already uses when the search for a base representation gives up (documented as "No base representation could be found in MAXTRY iterations", now "..., or the matrix has an infinite entry"). xSTEMR reports that as INFO = 12 under its existing 1X convention. xSTEVR, xSYEVR and xHEEVR treat any nonzero INFO from xSTEMR as a reason to fall back to xSTEBZ and xSTEIN, which #1384 makes pass an infinite matrix through unscaled. A NaN matrix is not touched by this change: its norm compares false against every threshold, on both sides, and it takes the same path as before, which #1389 bounds.

Minimal reproducer

! DSTEMR('V', 'A') on an 8x8 symmetric tridiagonal matrix with D(1) = +Inf.
program minimal
  implicit none
  integer, parameter :: n = 8
  double precision :: d(n), e(n), w(n), z(n,n), work(18*n), zero
  integer :: iwork(10*n), isuppz(2*n), info, m, i
  logical :: tryrac
  zero = 0d0
  do i = 1, n
    d(i) = i
    e(i) = 1d0 / (i + 1)
  end do
  d(1) = 1d0 / zero
  tryrac = .true.
  call dstemr('V', 'A', n, d, e, 0d0, 0d0, 1, n, m, w, z, n, n, isuppz, tryrac, &
              work, size(work), iwork, size(iwork), info)
  print '(a,i0,a,i0,a,i0)', 'info = ', info, '  m = ', m, '  NaN eigenvalues = ', count(w(1:m) /= w(1:m))
end program
BEFORE (master):     info = 0  m = 8  NaN eigenvalues = 8
AFTER (this branch): info = 12  m = 0  NaN eigenvalues = 0

With E(1) = +Inf instead, master never returns from the same call.

Regression test. xCHKST gets the case: after the size and type loops it calls xSTEMR with RANGE = 'A' on an N = NMAX tridiagonal matrix whose first diagonal entry is +Inf, once with JOBZ = 'V' and once with JOBZ = 'N', and reports INFO = 0 as a failure. On the parent commit all four calls return INFO = 0 with NaN eigenvalues in every precision; with the fix they return INFO = 12. The position was chosen so that the test fails rather than hangs on the parent.

Validation

  • The full LAPACK test suite passes on this branch: 0 numerical errors, 0 other errors (ctest: 100% of 215 tests passed). The 40 extra tests over the parent commit are the new calls, 2 job options times 5 parameter sets times 4 precisions.
  • Special-value sweep (xSTEMR with JOBZ = 'V' and RANGE = 'A', 'V', 'I'; +Inf and -Inf in D(1), D(2), D(N), E(1), E(N/2), E(N-1); N = 1, 2, 3, 5, 8, 26, 40, 64; DSTEMR and ZSTEMR; one process per case with a 3 s timeout): for N >= 3 the parent returned INFO = 0 with NaN eigenvalues in 52 cases, hung in 24 (RANGE = 'I' with the Inf off the diagonal), and returned INFO = 11, an xLARRE failure on the zero-scaled matrix, in the remaining 344; this branch returns INFO = 12 in all 420. The 1 by 1 and 2 by 2 cases, which xSTEMR solves in closed form before scaling, are unchanged.
  • The NaN cases of the same sweep are identical on both sides (hangs included, which are Bound the bracket widening in xLARRB and xLARRJ so that a NaN matrix cannot hang xSTEMR #1389's), and every finite case (the same matrices at scale 1, 2^-1060 and 2^1020) gives bit-identical eigenvalues.
  • The regression test fails on the parent and passes with the fix, and the reproducer prints the same before/after output, with gfortran 13 (x86-64 Release and Debug with -fcheck=all, and under QEMU on aarch64, ppc64le, s390x and riscv64), flang-19 and Intel ifx 2025.3 (-fp-model=strict).

Found while auditing the symmetric tridiagonal eigensolvers for the NaN and overflow handling of #1377-#1391; this is the xSTEMR instance of the scale-to-zero defect fixed for the bisection drivers in #1384.

xSTEMR scales the matrix into the range xLARRE can represent:

   ELSE IF( TNRM.GT.RMAX ) THEN
      SCALE = RMAX / TNRM

For a matrix with an infinite entry TNRM is infinite, SCALE underflows
to zero, the scaling turns the infinite entry into a NaN and every
other entry into zero, and the solver ran on that matrix: RANGE = 'A'
and 'I' returned INFO = 0 with every eigenvalue a NaN, RANGE = 'V'
scaled the interval to (0, 0] and returned no eigenvalues, and with
the infinite entry off the diagonal the representation tree search
never returned (the NaN hang of Reference-LAPACK#1389).

Leave a matrix whose scale factor would be zero unscaled, and let
xLARRE, which computes the base representations, reject a matrix with
an infinite entry as one for which no representation exists, INFO = 2,
the code it already uses when the search for a representation gives up.
xSTEMR returns INFO = 12 and the drivers that call it, xSTEVR, xSYEVR
and xHEEVR, fall back to xSTEBZ and xSTEIN as for any other xSTEMR
failure.  A NaN matrix is not touched by this change: its norm
compares false against every threshold and it takes the same path as
before.

xCHKST gets the case as a regression test: after the size and type
loops it calls xSTEMR with RANGE = 'A' on an N = NMAX tridiagonal
matrix whose first diagonal entry is +Inf, once with JOBZ = 'V' and
once with JOBZ = 'N', and reports INFO = 0 as a failure.  On the
parent commit all four calls return INFO = 0 with NaN eigenvalues;
here they return INFO = 12.  Over 420 placements of +Inf and -Inf
(n = 3 to 64, RANGE = 'A', 'V', 'I', real and complex) the parent
returned INFO = 0 with NaN eigenvalues in 52 cases, hung in 24 and
returned INFO = 11 in the rest; this branch returns INFO = 12 in all
of them.  The 1 by 1 and 2 by 2 matrices, which xSTEMR solves in closed
form before scaling, are unchanged, and every finite case is
bit-identical.

The full LAPACK test suite passes: 0 numerical errors, 0 other errors,
40 tests more than the parent from the new calls.

The regression test fails on the parent and passes with the fix, and the
reproducer prints the same before and after output, with gfortran 13
(x86-64 Release and Debug with -fcheck=all, and under QEMU on aarch64,
ppc64le, s390x and riscv64), flang-19 and Intel ifx 2025.3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.72727% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.36%. Comparing base (9eaccc1) to head (e0151a1).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
TESTING/EIG/cchkst.f 92.00% 2 Missing ⚠️
TESTING/EIG/dchkst.f 92.00% 2 Missing ⚠️
TESTING/EIG/schkst.f 92.00% 2 Missing ⚠️
TESTING/EIG/zchkst.f 92.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           master    #1404    +/-   ##
========================================
  Coverage   69.36%   69.36%            
========================================
  Files        6122     6122            
  Lines      486337   486447   +110     
  Branches    23268    23268            
========================================
+ Hits       337330   337440   +110     
  Misses     148569   148569            
  Partials      438      438            
Components Coverage Δ
BLAS 97.94% <ø> (ø)
CBLAS 96.98% <ø> (ø)
LAPACK 82.39% <100.00%> (+<0.01%) ⬆️
LAPACKE 2.17% <ø> (ø)
TMGLIB 55.69% <ø> (ø)
BLAS testing 88.33% <ø> (ø)
CBLAS testing 89.63% <ø> (ø)
LAPACK testing 82.25% <92.00%> (+<0.01%) ⬆️
LAPACKE testing ∅ <ø> (∅)
Files with missing lines Coverage Δ
SRC/cstemr.f 86.38% <100.00%> (+0.91%) ⬆️
SRC/dlarre.f 63.04% <100.00%> (+0.40%) ⬆️
SRC/dstemr.f 86.38% <100.00%> (+0.91%) ⬆️
SRC/slarre.f 63.04% <100.00%> (+0.40%) ⬆️
SRC/sstemr.f 86.38% <100.00%> (+0.91%) ⬆️
SRC/zstemr.f 86.38% <100.00%> (+0.91%) ⬆️
TESTING/EIG/cchkst.f 64.38% <92.00%> (+1.23%) ⬆️
TESTING/EIG/dchkst.f 64.40% <92.00%> (+1.25%) ⬆️
TESTING/EIG/schkst.f 64.40% <92.00%> (+1.25%) ⬆️
TESTING/EIG/zchkst.f 64.38% <92.00%> (+1.23%) ⬆️

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 9eaccc1...e0151a1. Read the comment docs.

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