Fall back to dense eigh in sparse_cholesky for rank-deficient PSD inputs#3308
Fall back to dense eigh in sparse_cholesky for rank-deficient PSD inputs#3308Transurgeon wants to merge 1 commit intomasterfrom
Conversation
| # QDLDL has no pivoting and fails when a zero pivot appears | ||
| # mid-elimination on rank-deficient PSD/NSD inputs (e.g. [[1,1],[1,1]]). | ||
| # Fall back to a dense symmetric eigendecomposition. | ||
| w, V = np.linalg.eigh(A.toarray()) |
There was a problem hiding this comment.
The except Exception clause (unchanged from before) now triggers an O(n³) dense eigendecomposition + O(n²) memory allocation via A.toarray(), rather than just re-raising as a ValueError. If a large sparse matrix hits this path for an unexpected reason (not rank-deficiency), the dense fallback could OOM or hang.
Consider either:
- Narrowing the catch to the specific exception type QDLDL raises on zero-pivot (likely a C-level
RuntimeError), or - Adding a size guard before densifying, e.g.:
if n > 5000:
raise ValueError(f"{SparseCholeskyMessages.FACTORIZATION_FAILED}: matrix too large for dense fallback")This way truly unexpected failures on large matrices still surface immediately rather than silently attempting dense computation.
|
This is the same saga as jump-dev/MathOptInterface.jl#2973 ultimately "fixed" jump-dev/MathOptInterface.jl#2967. Even then I don't like it. There's no magic fix. Every option has trade-offs. We discussed doing this jump-dev/MathOptInterface.jl#2931 but decided not to merge because of the performance issues for larger models. |
Description
Please include a short summary of the change.
sparse_cholesky(added in #3070, partially patched in #3270) raisesValueError: Cholesky factorization failedon simple rank-deficient PSDinputs such as
[[1, 1], [1, 1]]. The root cause is that QDLDL has nopivoting and bails out as soon as a zero pivot appears mid-elimination —
which the existing rank-deficient handling in #3270 doesn't cover (it
only special-cases all-zero rows and zero pivots that land last in
QDLDL's elimination order).
This adds a dense
numpy.linalg.eighfallback insidesparse_cholesky's existingexcept Exceptionbranch. When QDLDL fails,we eigendecompose
A, classify it as PSD/NSD/indefinite using the sametolerance logic as the QDLDL branch, and return an
n × rank(A)sparsefactor built from the nonzero eigenpairs. The function's documented
contract (
L[p,:] @ L[p,:].T == sign * A,k = rank(A)) now holds forall PSD/NSD inputs.
Issue link (if applicable):
Type of change
Contribution checklist