Let a caller ask solve! to throw on a failed solve - #282
Merged
Conversation
`solve!(...; throw_on_fail=true)` raises SolveFailure when the circulation loop missed the solver's tolerances or the coefficients it assembled are not finite, ForwardDiff.Dual partials included. The default is off, so a post-stall solve still returns its FAILURE solution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MnfrHUTR4uvX1uY7b9m7gE
Member
|
Did you check if solver.lr.converged and sol.solver_status are actually updated and relevant and that it actually throws on a failing solve, is there a test? |
1-Bart-1
approved these changes
Sep 9, 2026
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.
TL;DR
solve!gainsthrow_on_fail, which turns a solve that missed the solver's tolerances or came back non-finite into aSolveFailureexception instead of aVSMSolutionthe caller has to inspect. The default is off, so nothing that solves today changes; SymbolicAWEModels.jl asks for the throw and is where this comes from (OpenSourceAWE/SymbolicAWEModels.jl#291).Why the failure belongs here
A caller that cannot use a failed solve has to know that
solver.lr.convergedandsol.solver_statusexist, and has to re-derive "did this come back finite" for itself — including overForwardDiff.Dualpartials, which is not obvious. SymbolicAWEModels.jl carried exactly that: its ownVSMSolveFailureexception, its own Dual-awarefinite_full, and three call sites that re-checked the solver's own flag and threw. Review on that PR said the error should exist and be thrown here, which is right: whether a solve is usable is this package's question, not a caller's.So
SolveFailure, thefinite_fullcheck and thethroware here now, and the caller says once that it wants them.Why the default is off, and not on
Making
solve!always throw was the first thing I tried, and two testsets in this repo say no.NONLIN solve! re-runs across callssolves atva = [10, 0, 5]— 26.6° — andsolve! artificial viscosity: attached no-op, post-stall finitesolves at 20° on a flat-plate LEI wing; both then assert on the result. Neither converges. The artificial-viscosity testset is explicit about what it wants there: a finite post-stall answer, not a converged one.That is a real regime for this package, so the default keeps it: a post-stall solve still returns its
solver_status == FAILUREsolution and the caller decides. Flip the default if you would rather every caller be told — the two testsets above are what would have to move with it.What I found while measuring that
On the wing
test/solver/solver_test_wing.yamlbuilds, atva = [10, 0, 5],NONLINmisses the tolerances in all 1500 iterations whileLOOPconverges, and the two answers differ by 3.6% in peak circulation (max|gamma|8.781 against 9.106).NONLIN solve! re-runs across callshas been asserting on that non-converged result. Nothing in this PR depends on it and I have not touched the test; filed as #283.What the coverage check caught
codecov/patchwas the one red check on the first push, and it was right: the lines it named are the ones only the calling package exercised. The throw fires on non-convergence beforefinite_fullis ever reached, so both of its methods,showerrorand the whole converged branch ofthrow_on_failhad no test here at all. They do now — including aDualwhose partial is not finite, which is the case the method exists for.Verification
test/solver/test_solver.jlred before (UndefVarError: SolveFailure not defined in Main), green after: 27 assertions, 9 of them new (juliaserver, Julia 1.12.7)test/runtests.jl, every testset green, one pre-existing@test_broken. The coverage commit that followed it only adds assertions totest/solver/test_solver.jl, which was re-run on its own: 27/27DOCSBUILD OKthrow_on_fail || return solbefore the existingreturnScope
+94 / −18 across 7 files:
src/solver.jlisSolveFailure,finite_fulland the kwarg; the rest is two testsets, two docs entries and the changelog. Not stacked on #273 — it shares no source file with this, onlyCHANGELOG.md.