Skip to content

Test that a NaN pivot is reported by the packed symmetric-indefinite factorizations - #1393

Open
rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:packed-bunch-kaufman-nan-test
Open

rmlarsen wants to merge 1 commit into
Reference-LAPACK:masterfrom
rmlarsen:packed-bunch-kaufman-nan-test

Conversation

@rmlarsen

@rmlarsen rmlarsen commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Adds 144 tests — 24 in each of the SSP, DSP, CSP, ZSP, CHP and ZHP paths — for the NaN pivot guard merged in #1378 (f96546f). With the six factorizations restored to their state before that commit, every one of those six paths now dies of SIGSEGV, so what the suite previously could not see at all is a hard failure.

xSPTRF and xHPTRF rejected a column that cannot be pivoted on with

IF( MAX( ABSAKK, COLMAX ).EQ.ZERO ) THEN

alone until #1378 added the DISNAN( ABSAKK ) term that their dense twins xSYTF2 and xHETF2 have carried since 2006. Nothing in the test suite covered the difference: no matrix type contains a NaN, so the routines were only ever asked about a pivot that MAX can classify.

What the test does

After the matrix-type loops of xCHKSP and xCHKHP, for each N in NVAL and each UPLO, factor a matrix whose only NaN sits on the diagonal of the last column the pivot loop visits — column N for UPLO = 'L', column 1 for UPLO = 'U'. That column has no off-diagonal candidate, so COLMAX is zero and IMAX is never assigned; unless the pivot itself is tested for a NaN, every later comparison is false, the code reaches the 2-by-2 branch, and it writes IPIV( K+1 ) or IPIV( K-1 ), one element past an end of IPIV, with INFO left at zero.

The NaN column is otherwise zero and the rest of the matrix is $I + \mathbf{1}\mathbf{1}^T$, which is positive definite, so the leading part is factored without an interchange and INFO must come back as the index of the NaN column. Two things are checked per case:

  • INFO equals that index;
  • IPIV was written only inside its own bounds. IPIV is passed as IWORK( 2 ), so the write below its first element lands in IWORK( 1 ), where a sentinel catches it; a second sentinel in IWORK( N+2 ) catches the write above its last.

Evidence

With {s,d,c,z}sptrf.f and {c,z}hptrf.f restored to f96546f^ and everything else at master, each of the six paths crashes on its first case, N = 1, where the uninitialized IMAX is dereferenced immediately. Running the cases one process each shows what the remaining ones do — gfortran 13.3, -O2:

DSPTRF, before and after #1378, one process per case
===== DSPTRF, SRC at f96546fc9^ =====
 UPLO=U N=1    *** SIGSEGV ***
 UPLO=L N=1    *** SIGSEGV ***
 UPLO=U N=  2 INFO=     0 expected=   1 guards=     -1   -9999
 UPLO=L N=  2 INFO=     0 expected=   2 guards=  -9999      -2
 UPLO=U N=  3 INFO=     0 expected=   1 guards=     -1   -9999
 UPLO=L N=  3 INFO=     0 expected=   3 guards=  -9999      -3
 UPLO=U N=  5 INFO=     0 expected=   1 guards=     -1   -9999
 UPLO=L N=  5 INFO=     0 expected=   5 guards=  -9999      -5
 UPLO=U N= 10 INFO=     0 expected=   1 guards=     -1   -9999
 UPLO=L N= 10 INFO=     0 expected=  10 guards=  -9999     -10
 UPLO=U N= 50 INFO=     0 expected=   1 guards=     -1   -9999
 UPLO=L N= 50 INFO=     0 expected=  50 guards=  -9999     -50

===== DSPTRF, SRC at master =====
 UPLO=U N=  1 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N=  1 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=U N=  2 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N=  2 INFO=     2 expected=   2 guards=  -9999   -9999
 UPLO=U N=  3 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N=  3 INFO=     3 expected=   3 guards=  -9999   -9999
 UPLO=U N=  5 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N=  5 INFO=     5 expected=   5 guards=  -9999   -9999
 UPLO=U N= 10 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N= 10 INFO=    10 expected=  10 guards=  -9999   -9999
 UPLO=U N= 50 INFO=     1 expected=   1 guards=  -9999   -9999
 UPLO=L N= 50 INFO=    50 expected=  50 guards=  -9999   -9999

-9999 is the sentinel; IPIV( 0 ) = -1 for UPLO = 'U' and IPIV( N+1 ) = -N for UPLO = 'L' are the out-of-bounds writes. Both of the new checks fire in all ten non-crashing cases.

Whether MAX( ABSAKK, COLMAX ).EQ.ZERO catches a NaN is compiler- and optimization-dependent, so a build on which the old code happens to classify the pivot correctly would see this test pass. That is the same dependence #1378 removed; the test asserts the behavior the guard now makes unconditional.

xCHKSP and xCHKHP were added to the NAG -Onopropagate list beside the xERRCXX files, for the same reason: they construct a NaN.

Validation

Test-only change; nothing under SRC/ is touched. The full suite passes — 5442045 LAPACK and 315872 BLAS tests, 0 errors, 215 of 215 ctest cases, against 5441901 LAPACK tests on the parent. The 144 new tests are the difference.

@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.36%. Comparing base (a6c6e74) to head (dfcff8d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
TESTING/LIN/cchkhp.f 81.81% 8 Missing ⚠️
TESTING/LIN/cchksp.f 81.81% 8 Missing ⚠️
TESTING/LIN/dchksp.f 81.81% 8 Missing ⚠️
TESTING/LIN/schksp.f 81.81% 8 Missing ⚠️
TESTING/LIN/zchkhp.f 81.81% 8 Missing ⚠️
TESTING/LIN/zchksp.f 81.81% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           master    #1393    +/-   ##
========================================
  Coverage   69.36%   69.36%            
========================================
  Files        6122     6122            
  Lines      486711   486975   +264     
  Branches    23268    23268            
========================================
+ Hits       337584   337800   +216     
- Misses     148689   148737    +48     
  Partials      438      438            
Components Coverage Δ
BLAS 97.94% <ø> (ø)
CBLAS 96.98% <ø> (ø)
LAPACK 82.38% <ø> (ø)
LAPACKE 2.17% <ø> (ø)
TMGLIB 55.69% <ø> (ø)
BLAS testing 88.33% <ø> (ø)
CBLAS testing 89.63% <ø> (ø)
LAPACK testing 82.20% <81.81%> (-0.01%) ⬇️
LAPACKE testing ∅ <ø> (∅)
Files with missing lines Coverage Δ
TESTING/LIN/cchkhp.f 84.57% <81.81%> (-0.72%) ⬇️
TESTING/LIN/cchksp.f 83.01% <81.81%> (-0.32%) ⬇️
TESTING/LIN/dchksp.f 84.28% <81.81%> (-0.66%) ⬇️
TESTING/LIN/schksp.f 84.28% <81.81%> (-0.66%) ⬇️
TESTING/LIN/zchkhp.f 84.57% <81.81%> (-0.72%) ⬇️
TESTING/LIN/zchksp.f 83.01% <81.81%> (-0.32%) ⬇️

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...dfcff8d. 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. With the six factorizations restored to their state before #1378, the new tests fail in all six paths: the double and complex test binaries crash, and the single-precision one reports the wrong INFO and the write outside IPIV.

…factorizations

xSPTRF and xHPTRF rejected a column that cannot be pivoted on with

   IF( MAX( ABSAKK, COLMAX ).EQ.ZERO ) THEN

alone until f96546f added the DISNAN( ABSAKK ) term their dense twins
xSYTF2 and xHETF2 have carried since 2006.  Nothing in the test suite
covered it: no matrix type contains a NaN, so the routines were only
ever asked about a pivot MAX can classify.

Add that question to xCHKSP and xCHKHP, after the matrix-type loops.
For each N in NVAL and each UPLO, factor a matrix whose only NaN sits
on the diagonal of the last column the loop visits -- column N for
UPLO = 'L', column 1 for UPLO = 'U'.  That column has no off-diagonal
candidate, so COLMAX is zero and IMAX is never assigned; unless the
pivot itself is tested for a NaN, every later comparison is false, the
code reaches the 2-by-2 branch, and it writes IPIV( K+1 ) or
IPIV( K-1 ), one element past an end of IPIV, with INFO left at zero.
The NaN column is otherwise zero and the rest of the matrix is
I + ones, so it is factored without an interchange and INFO must come
back as the index of the NaN column.  IPIV is passed as IWORK( 2 ) so
that the write below its first element lands in IWORK( 1 ), where a
sentinel catches it; a second sentinel in IWORK( N+2 ) catches the
write above its last.

With the six routines restored to their state before f96546f, every
one of the six paths dies of SIGSEGV on the first case (N = 1, where
the uninitialized IMAX is dereferenced immediately).  Running the
cases one process each, gfortran 13.3 at -O2 returns INFO = 0 instead
of the column index for all ten cases with N >= 2, and overwrites
IPIV( 0 ) with -1 for UPLO = 'U' and IPIV( N+1 ) with -N for
UPLO = 'L'; both checks fire.  With the routines as they are, INFO is
the column index and both sentinels survive in all 24 cases of each
path.

The full test suite passes: 5442045 LAPACK and 315872 BLAS tests, 0
errors, 215 of 215 ctest cases, against 5441901 LAPACK tests on the
parent -- the 144 new tests are 24 in each of SSP, DSP, CSP, ZSP, CHP
and ZHP.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rmlarsen
rmlarsen force-pushed the packed-bunch-kaufman-nan-test branch from 31af60d to dfcff8d 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