Skip to content

Return INFO = 1 from xLASQ1 instead of stopping in XERBLA when SIGMX is a NaN - #1387

Open
rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:lasq1-nan-info
Open

rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:lasq1-nan-info

Conversation

@rmlarsen

@rmlarsen rmlarsen commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Disclaimer: This PR was prepared using Claude Code.

Summary

xLASQ1 scales the bidiagonal matrix by its largest entry SIGMX before calling the dqds algorithm. When D or E contains a NaN that survives the MAX reductions that compute SIGMX (with gfortran: a NaN in D(N)), SIGMX is a NaN and xLASCL rejects it by stopping the process in XERBLA (On entry to DLASCL parameter number 4 had an illegal value). xBDSQR without singular vectors takes this path, and so does xGESVD with JOBU = JOBVT = 'N'. This PR returns INFO = 1 from xLASQ1 instead, which is the same mechanism #1382 uses for xLALSD and xBDSDC. xBDSQR treats every nonzero INFO from xLASQ1 as "try the QR algorithm", so it then finishes the QR iteration on the NaN data and reports the non-convergence through its own INFO, as it does for other non-finite input. Two files: {s,d}lasq1.f.

Description

      DO 20 I = 1, N
         SIGMX = MAX( SIGMX, D( I ) )
   20 CONTINUE
      ...
      CALL DLASCL( 'G', 0, 0, SIGMX, SCALE, 2*N-1, 1, WORK, 2*N-1, IINFO )

Which NaN positions reach DLASCL depends on the compiler's MAX (gfortran returns the second argument when the first is a NaN, so only a NaN in D(N) propagates; a NaN in D(1) or D(3) is dropped and the routine returns INFO = 0 with NaN output). The guard tests exactly the quantity DLASCL will reject, so it removes the abort on every compiler without changing the outcome for a NaN that does not reach SIGMX. INFO = 1 is documented ("a split was marked by a positive value in E, or the input contains a NaN").

Minimal reproducer

! DBDSQR without singular vectors on a 5x5 upper bidiagonal matrix with D(5) = NaN,
! and DGESVD('N', 'N') on the same matrix.
program minimal
  implicit none
  integer, parameter :: n = 5
  double precision :: d(n), e(n), a(n,n), s(n), u(n,n), vt(n,n), work(8*n*n+40), zero, c(1,1)
  integer :: info, i
  zero = 0
  d = [1d0, 2d0, 3d0, 4d0, 5d0]; e = 0.3d0; e(n) = 0
  d(n) = zero/zero
  a = 0
  do i = 1, n
    a(i,i) = d(i)
  end do
  do i = 1, n-1
    a(i,i+1) = e(i)
  end do
  call dbdsqr('U', n, 0, 0, 0, d, e, vt, n, u, n, c, 1, work, info)
  print '(a,i3)', 'DBDSQR (no vectors): info = ', info
  call dgesvd('N', 'N', n, n, a, n, s, u, n, vt, n, work, 8*n*n+40, info)
  print '(a,i3)', 'DGESVD(N, N):        info = ', info
end program
BEFORE (master):      ** On entry to DLASCL parameter number  4 had an illegal value   (process stops)
AFTER (this branch):  DBDSQR (no vectors): info =   4
                      DGESVD(N, N):        info =   4

Regression test. xERRBD gets the case: a 4 by 4 bidiagonal with a NaN in D(N), passed to xBDSQR without singular vectors, which must return without calling XERBLA. All four files carry it, since the complex xBDSQR reaches the same real xLASQ1. It belongs with the error-exit tests rather than in xCHKBD, because the routine returns no meaningful output for such a matrix and the point of the test is that it returns at all; as a matrix type it would also be misleading, since xCHKBD counts the fixed behaviour (IINFO > 0) as a failure while master's INFO = 0 with NaN output passes every RESULT( J ) .GE. THRESH comparison. On the parent commit the four sections report the two xLASCL illegal-argument lines and *** xBDSQR called XERBLA for a matrix with a NaN ***; with the fix they pass.

Validation

  • The full LAPACK test suite passes on this branch: 5441901 LAPACK tests, 0 numerical errors, 0 other errors, the same totals as the parent commit (ctest: 100% of 215 tests passed).
  • 120 cases (DBDSQR without vectors and DGESVD; NaN, +Inf, -Inf in five positions; N = 2, 3, 5, 8), one process per case: master stops in XERBLA in 3 of them (NaN in D(N), N >= 3, through DBDSQR), this branch in none; every other case returns the same INFO as on master.
  • DLASQ1 itself: NaN in D(1) and D(3) return INFO = 0 on both; NaN in D(5) stops the process on master and returns INFO = 1 here.
  • Finite input never takes the new branch (DISNAN( SIGMX ) is false), so results are unchanged.

DGESDD is not affected: it checks its input norm for a NaN up front and returns INFO = -4.

The INFO = 0 returns for a NaN in D(1) or D(3) above are a separate defect, reported as #1392: xLASQ3's deflation tests are written as "keep iterating while Z(...) .GT. TOL2*(...)", so a NaN operand makes every comparison false and the NaN is deflated as a converged singular value. Widening the check here from SIGMX to the whole bidiagonal would cover that too; this PR keeps to the position that stops the process.

This PR touches TESTING/EIG/{s,d,c,z}errbd.f, as #1390 does; the two blocks are independent and the textual conflict between them is a trivial one.

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.57143% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.36%. Comparing base (a6c6e74) to head (5af1cc2).
⚠️ Report is 2 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
TESTING/EIG/cerrbd.f 87.50% 2 Missing ⚠️
TESTING/EIG/derrbd.f 87.50% 2 Missing ⚠️
TESTING/EIG/serrbd.f 87.50% 2 Missing ⚠️
TESTING/EIG/zerrbd.f 87.50% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1387   +/-   ##
=======================================
  Coverage   69.36%   69.36%           
=======================================
  Files        6122     6122           
  Lines      486711   486781   +70     
  Branches    23268    23268           
=======================================
+ Hits       337584   337674   +90     
+ Misses     148689   148669   -20     
  Partials      438      438           
Components Coverage Δ
BLAS 97.94% <ø> (ø)
CBLAS 96.98% <ø> (ø)
LAPACK 82.40% <100.00%> (+0.01%) ⬆️
LAPACKE 2.17% <ø> (ø)
TMGLIB 55.69% <ø> (ø)
BLAS testing 88.33% <ø> (ø)
CBLAS testing 89.63% <ø> (ø)
LAPACK testing 82.21% <87.50%> (+<0.01%) ⬆️
LAPACKE testing ∅ <ø> (∅)
Files with missing lines Coverage Δ
SRC/dlasq1.f 77.77% <100.00%> (+1.30%) ⬆️
SRC/slasq1.f 77.77% <100.00%> (+1.30%) ⬆️
TESTING/EIG/cerrbd.f 98.07% <87.50%> (-1.21%) ⬇️
TESTING/EIG/derrbd.f 98.57% <87.50%> (-0.91%) ⬇️
TESTING/EIG/serrbd.f 98.57% <87.50%> (-0.91%) ⬇️
TESTING/EIG/zerrbd.f 98.07% <87.50%> (-1.21%) ⬇️

... and 4 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...5af1cc2. Read the comment docs.

@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.

Difference from x86: the defect does not occur on this machine. gfortran compiles MAX to fmaxnm, which returns the non-NaN operand, so SIGMX never becomes a NaN and xLASCL is never reached. On master and on this branch alike, DLASQ1 returns INFO = 0 with NaN output for the reproducer (the behavior of #1392). The new xERRBD case therefore passes even without the fix here. On x86, where maxsd propagates the NaN, it should fail as described.

…is a NaN

xLASQ1 scales the bidiagonal matrix by its largest entry SIGMX before
running dqds.  When D or E contains a NaN that survives the MAX
reductions computing SIGMX (with gfortran, a NaN in D(N)), SIGMX is a
NaN and xLASCL rejects it by stopping the process in XERBLA.  xBDSQR
without singular vectors and xGESVD with JOBU = JOBVT = 'N' take this
path; a NaN elsewhere in D returns INFO = 0 with NaN output.

Test SIGMX with xISNAN and return INFO = 1, the mechanism Reference-LAPACK#1382 uses
for xLALSD and xBDSDC.  xBDSQR treats every nonzero INFO from xLASQ1
as a request to finish with the QR algorithm, so it then reports the
non-convergence on the NaN data through its own INFO like it does for
other non-finite input.  Finite input never takes the new branch.

xERRBD gets the case as a regression test: a 4 by 4 bidiagonal with a
NaN in D(N), passed to xBDSQR without singular vectors, which must
return without calling XERBLA.  The error-exit tests are where it
belongs, since the routine returns no meaningful output for such a
matrix and the point of the test is that it returns at all.  All four
xERRBD files carry it, because the complex xBDSQR takes the same path
through the real xLASQ1.

Over 120 NaN/Inf cases of DBDSQR without vectors and DGESVD the parent
stops in XERBLA 3 times and this branch never; every other case
returns the same INFO on both.  The full LAPACK test suite passes:
5441901 LAPACK tests, 0 numerical errors, 0 other errors, the same
totals as the parent.

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

This branch has not been deployed

No deployments
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