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 into
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. |
|
Okay, so I looked at our implementation more; we're using LDL for low-rank factorization in the dense case via Bunch-Kaufman pivoting. It should be possible to do the same in the sparse case, especially since we don't need symbolic analysis reuse or anything. Let me try some experiments and see what we can do... |
|
Here's a very messy implementation that preserves sparsity and handles low-rank |
|
Here's an easy fix: #3313. I would like to---at some point---use a Bunch-Kaufman-based sparse-LDL factorization so that we can get low-rank support, but maybe that's a different problem. |
|
#3313 fixes this better. |
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