Skip to content

fix: take the sparse solver path only for a positive-definite K - #145

Merged
SMI-Lab-Inha merged 1 commit into
masterfrom
fix/sparse-indefinite-window
Oct 21, 2026
Merged

SMI-Lab-Inha merged 1 commit into
masterfrom
fix/sparse-indefinite-window

Conversation

@SMI-Lab-Inha

Copy link
Copy Markdown
Owner

Reported on LinkedIn against 1.19.0 (9856574): solve_modes returned a different spectral window on the sparse path than on the dense one whenever K is indefinite.

Root cause

Above 500 DOF a symmetric subset solve goes through eigsh(K, M, sigma=0, which='LM'). Shift-invert at zero returns the eigenvalues smallest in magnitude. Those are the lowest only when K is positive definite. With a strongly negative eigenvalue present, the sparse path left it out and backfilled the window with positive modes, while the dense path, which is built to keep negative eigenvalues, returned it.

  • Synthetic: K = diag(-100, 1, ..., 500), M = I, three modes gave [1, 2, 3] on the sparse path and [-100, 1, 2] on the dense path at 400 DOF.
  • Real model: the bundled NREL 5MW land tower under run(gravity=True) with 1000 times its RNA mass. The default 40-node mesh (dense) returns four negative eigenvalues, down to λ = −16.7. Refined to 120 nodes (sparse) it returned the near-zero pair plus two positive modes. At 60 and 300 times the RNA mass the two paths agreed, because the negative eigenvalues were still small in magnitude.

Fix

  • solve_modes takes the sparse path only after _is_positive_definite confirms K admits a Cholesky factorisation. Anything else, indefinite or singular, goes to the dense path, which returns the algebraically lowest modes.
  • The check is a banded Cholesky (scipy.linalg.cholesky_banded) with the band measured from K itself. The assembled stiffness has a half-bandwidth of 10 at every mesh size tried, so the factorisation is about a millisecond at 9000 DOF. A dense Cholesky there takes about 2 s. A model with wider coupling is still checked exactly, only less cheaply.
  • The symmetrised K and M for the sparse path are now built sparsely (csc + csc.T) instead of from a dense 0.5 (K + K^T) copy, and the guard shares the same sparse K. That more than pays for the check.

Performance (NREL 5MW land tower, 6 modes, best of 2 to 3 runs)

nodes ngd master this PR
120 1071 51 ms 42 ms
400 3591 702 ms 482 ms
1000 8991 8.16 s 5.31 s

Positive-definite results are unchanged within the sparse solver's own run-to-run scatter: master against this PR differs by 3e-11 relative, and two runs of the same code by 9e-11, from ARPACK's random starting vector. The bundled reference decks and samples regenerate with no content change.

Tests (tests/fem/test_sparse_solver.py)

  • The reported diagonal case, a coupled K with two negative eigenvalues and a scaled diagonal M, a K with three zero modes, and an SPD control that must still go sparse.
  • _is_positive_definite against a dense Cholesky on random symmetric matrices, a tridiagonal band, singular and empty matrices, a full-width coupling and a tiny asymmetry.
  • Model level: the buckled NREL 5MW tower gives the same lowest four frequencies on the default and the refined mesh, with the refined one routed dense.
  • VALIDATION.md rows added; scripts/audit_validation_claims.py is clean.
  • Local, Python 3.11, default + integration: 1483 passed, 3 skipped. ruff and mypy clean.

Shift-invert at zero returns the eigenvalues smallest in magnitude,
which are the lowest only when K is positive definite. With an
indefinite K, such as a tower past buckling under gravity, the sparse
path dropped the strongly negative modes that the dense path keeps, so
the result changed with mesh refinement alone. A banded Cholesky now
gates the sparse path; anything not definite goes dense.

The sparse path also builds its symmetrised matrices sparsely, which
more than pays for the check.
@SMI-Lab-Inha

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T07:36:49.660071Z fb4b298 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: fb4b2988ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@SMI-Lab-Inha
SMI-Lab-Inha merged commit 7131c2d into master Oct 21, 2026
6 checks passed
@SMI-Lab-Inha
SMI-Lab-Inha deleted the fix/sparse-indefinite-window branch October 21, 2026 07:40
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