Tighten instability detection exception flow and zero-load singular checks#323
Merged
JWock82 merged 5 commits intoJun 6, 2026
Merged
Conversation
A globally unstable model -- a mechanism (for example a beam with an internal hinge and no support there) or a structure with insufficient supports -- has a singular global stiffness matrix even though every diagonal term is non-zero. The existing nodal stability check only inspects the diagonal for unsupported zero-stiffness degrees of freedom, so it cannot see this kind of global rank deficiency. The underlying solvers do not reliably report it either: scipy's spsolve returns NaN (with only a MatrixRankWarning) or even finite-but-meaningless displacements, and numpy.linalg.solve can miss the singularity due to round-off. The model then silently returns erroneous reactions and displacements. This adds Analysis._solve_unknown_disp, which solves for the unknown displacements and, when check_stability is enabled, verifies the solution against equilibrium using the relative residual ||K11 @ x - rhs|| / ||rhs||. A stable structure -- even a numerically ill-conditioned one -- solves to within round-off because the direct solvers are backward stable, whereas a singular system produces a non-finite or large-residual result. Unlike a pivot-magnitude/rank threshold, the residual test does not misclassify legitimately ill-conditioned but stable models (verified against the existing suite, which includes stiff mat-foundation and plate meshes whose smallest LU pivots are already near machine epsilon while their solves remain accurate). The first-order elastic/TC path (Analysis._first_order) and the linear path (FEModel3D.analyze_linear) now route through this helper. The check is gated on the existing check_stability flag, so the historical fast path is preserved. Added Testing/test_instability.py covering the hinge mechanism and the unsupported-model scenarios (sparse and dense), that a stable propped cantilever still solves to the correct reaction, and that check_stability=False bypasses the check. Fixes #255 Fixes #275
Copilot
AI
changed the title
[WIP] Update instability detection implementation based on review feedback
Tighten instability detection exception flow and zero-load singular checks
Jun 6, 2026
JWock82
approved these changes
Jun 6, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #323 +/- ##
==========================================
- Coverage 71.46% 71.45% -0.01%
==========================================
Files 23 23
Lines 9895 9911 +16
==========================================
+ Hits 7071 7082 +11
- Misses 2824 2829 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR refines the instability-detection changes in the current analysis path to address review concerns: broad exception masking, over-broad sparse warning suppression, and under-specified handling of homogeneous (
rhs == 0) solves. The update keeps scope limited to solver stability detection/error handling and related coverage.Analysis-path error handling
except:wrappers around the new_solve_unknown_dispcall sites in modified first-order/linear paths.Sparse warning scope
spsolvewith targeted handling ofscipy.sparse.linalg.MatrixRankWarningonly.rhs == 0(homogeneous) behavior_solve_unknown_disp: whenrhsis zero, treat non-zero solved displacement norm as unstable (xmust be ~0 for invertible systems).rhs.Targeted coverage update
Original prompt
Please update PR #320 in repository
JWock82/Pyniteby addressing the review concerns in the current changes for the instability-detection implementation.Context:
_solve_unknown_dispto detect global instability by checking finiteness and the solution residual after solvingK11 @ x = rhs.except:handlers around the call sites and suppresses all warnings aroundspsolve.rhs == 0edge case.Required changes:
except:handlers in the modified analysis paths with narrower, intentional exception handling that preserves unexpected errors instead of always converting them into the generic singular-matrix message.rhs == 0/ zero-load edge case in_solve_unknown_dispso the instability detection is more principled and does not silently skip an important singular case.Please make the minimal, reviewable change set needed to address these points and open a pull request with the fixes.
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Explain these changes.
Assistant: This PR makes PyNite detect a broader class of unstable models by checking the solution residual after solving for displacements, instead of relying only on obvious singular-matrix failures.
In a nutshell
Before this change, analysis could miss some global instability cases: the stiffness matrix might be rank-deficient even though its diagonal entries look fine, and the solver could return
NaNor even plausible-looking but wrong numbers. This PR adds a shared helper,_solve_unknown_disp, that solves the system and then verifies the result by checking whetherK11 @ xactually matches the right-hand side. If not, it raises the usual “structure is unstable” exception.What changed
1. New
_solve_unknown_disp(...)helperA new function centralizes solving:
scipy.sparse.linalg.spsolve) and dense (numpy.linalg.solve) paths_SINGULAR_MSGIts key idea is:
K11 @ x = rhsxare finite||K11 @ x - rhs|| <= tol * ||rhs||If either fails, the matrix is treated as singular/unstable.
2. Global instability is now detected more reliably
The docstring explains the motivation well:
_check_stability(...)only catches cases with zero diagonal stiffnessThis PR addresses those cases using the residual test.
3. Sparse solver warnings are suppressed and replaced with a clearer error path
For sparse solves:
spsolvemay emit aMatrixRankWarningwarnings.catch_warnings()That gives users a more consistent API-level failure.
4.
_first_order(...)now uses the helperPreviously
_first_ordersolved directly:spsolve(...)solve(...)Now it:
rhsfirst_solve_unknown_disp(K11, rhs, sparse, check_stability)So the first-order analysis path gets the new global-instability detection automatically.
5. Imports updated
New imports support the helper:
warningsasarray,isfinitefrom NumPynormfromnumpy.linalgWhy this matters
This is mainly a correctness and safety fix.
Without it, users could get:
NaNdisplacement results, or worseWith this change, PyNite is more likely to fail fast with an explicit instability error instead of silently continuing with invalid analysis results.
Behavior impact
Practically:
One subtle detail
The residual check only triggers when:
check_stability=True, and||rhs||So this adds protection without changing the bare-solver path when stability checking ...
This pull request was created from Copilot chat.