Repository navigation
LOOP caps each panel's relaxation at its own stiffness, so narrow tip panels no longer diverge; unrefined section loads are area-weighted - #402
Open
1-Bort-1 wants to merge 6 commits into
Conversation
…non-finite gamma The :curved reference wing's stiffest mode needs relaxation below 0.0095; the single retry at 0.015 diverged to NaN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ficients are area-weighted Narrow tip panels set the LOOP stability limit; scaling their relaxation by median/own stiffness lets the default factor converge on the curved wing and on solver_test_wing up to 40 panels. The solver_test_wing polar gains a -20 deg row so its cl crosses zero and refined tips can unload. cl/cd/cm/alpha_unrefined_dist become area-weighted means, so coefficient times section area is the section load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…location Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iffness mean Leaves the relaxation of panels that were already stable unchanged: V3 and pyramid iteration counts match main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cs entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1-Bort-1
commented
Sep 30, 2026
1-Bort-1
left a comment
Member
Author
There was a problem hiding this comment.
Independent review (advisory)
Verdict: APPROVE WITH COMMENTS · 2 inline, 0 off the diff
Good
- Does what the card says: each panel's step is
min(relaxation_factor, 1/stiffness_i)(solver.jl:911), so a panel only takes a smaller step when its stiffness is above 1/ω. That supports the unchanged V3 and pyramid iteration counts in the card. - The residual divides each panel's step by its own
panel_relaxation(solver.jl:1129), so the convergence test still measuresF(gamma) - gamma. The fixed point and the tolerance mean the same as before. - The new buffer uses
solver.cache[9]from aLazyBufferCache, so the Dual/linearize path (solver.jl:1211-1232,relaxation_factor = TD(...)) gets a buffer of the right element type without extra wiring. - The tests now fail on the bug instead of hiding it:
solver_status == FEASIBLEafter every solve, and 5% tolerances behind NaN guards became exactrtol=1e-12checks, which area weighting makes possible. This makes the tests stricter. moment_unrefined_distis now summed likemoment_coeff_unrefined_dist, so the two unrefined moments are aggregated the same way. The test at test_unrefined_dist.jl:149 pins this.- The
area_meanhelper replaces six copies of the plain mean. The polar row and the aggregation fix are both named and justified in the card as fixes to code next to the change. panel_relaxation_factors!is in docs/src/private_functions.md, so its docstring can be reached from the built docs.
Not good
src/solver.jl:736— A LOOP solve that does not converge now retries five times (0.015 down to 0.00094) instead of once, each up tomax_iterations, so a post-stall case like the α=20° artificial-viscosity solve costs about 6× main's time for the same FAILURE. Consider capping the number of halvings, or stopping once the cap already holds every panel below the halved factor.src/solver.jl:738— The@warnis now unconditional and runs inside the halving loop, so a caller that callssolve!every simulation step, or throughlinearize's Dual solve, gets up to five warnings per failing call. Main kept this behindlog; if the card's 'as you asked' covers only the fallback, keep the warning behindlogor usemaxlog.- Local CI was still running and GitHub CI was pending when the card was written; neither result is shown.
- The card says the user asked for the halving fallback, but not for making the warning unconditional. Dropping the
log &&gate changes behaviour for callers that solve every time step. - Testsets 1 and 3 still have
isnan(expected_cl)branches and the 'Handle NaN values that can occur in INVISCID models' comment. Now that FEASIBLE is asserted and these are VSM solves, those branches look unreachable; the compaction pass (§7) should have removed them. x_airf/y_airf/z_airf/va_vec_unrefined_distare still plain count means of unit vectors and are not renormalised, next to coefficients that are now area-weighted. This predates the PR but sits in the block it rewrote.- The inline comment at solver.jl:532-533 lists what each aggregate is. That belongs in the field docstrings, which already say it, so the comment fails the deletion test.
- There is no direct unit test of
panel_relaxation_factors!(for example, a panel with stiffness below 1/ω keeps ω exactly). It is covered only indirectly through the curved-wing integration solve. - The
gamma_loop!docstring signature (solver.jl:917-919) still listsAIC_x/AIC_y/AIC_zarguments the function does not take. The PR edited the docstring right below it and left this stale.
claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.
…t-test the relaxation cap Review of #402: one warning naming the final relaxation_factor instead of one per halving, the gamma_loop! docstring signature matches the function, and the unrefined-dist testsets no longer branch on NaN now that FEASIBLE is asserted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Local full suite: PASS (10 min, Julia 1.13.0, one cell of the matrix) |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This branch has not been deployed
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
The LOOP solver now relaxes each panel by at most
1 / (1 + π c |z_airf ⋅ AIC_ii|), so cosine-spaced tip panels (1.3 cm on a 1 m chord) no longer blow the iteration up to NaN. The curved reference wing now converges at the default settings in about 300 iterations. Making those solves converge exposed thattest_unrefined_dist.jlhad never checked anything, because it solved a wing that diverged. Fixing that test meant fixing its polar and the unrefined aggregation, which were both wrong.Why LOOP diverged
The geometry is not the cause: the AIC, panel frames and starting gamma of the curved wing are all finite. NONLIN finds its fixed point (CL 0.4223, residual 1e-13). The Jacobian of the LOOP map there has an eigenvalue at −210, from the narrow tip panels' self-induction, so a uniform step is stable only for
relaxation_factor < 2/(1+210) ≈ 0.0095. Main's 0.03 and its one retry at 0.015 both diverge: max|gamma| reaches 3.8e50 by iteration 10 and NaN by 20.Halving further (your option A) reaches that limit, but the iteration count grows as 1/ω. At 40 panels
solver_test_wingneeds ω ≤ 0.004 and more than 1,500 iterations, so halving alone just moves the failure. The stiffness sits on the diagonal:1 + π c_i |z_airf_i ⋅ AIC_ii|tracks each panel's diagonal ofI − Jto within about 20% (215 against 176 at the tip, measured). So each panel's step is capped at that diagonal step:min(relaxation_factor, 1/stiffness_i). The fixed point is unchanged, and only panels stiffer than1/relaxation_factortake smaller steps. I first tried scaling against the median and then against the geometric mean of the stiffness. Both slowed V3 by 6% and 12%. The cap leaves every wing that already converged exactly as it was:As you asked, the halving stays as a fallback:
solve_base!halvesrelaxation_factorwhile it is above 1e-3, then warns once, whether or notlogis on, naming the factor it started and stopped at and whether it converged. One warning per solve rather than one per halving keeps a caller that solves every time step, orlinearize's Dual solve, from getting five per failing call. Each attempt now stops at the first non-finite residual instead of running 1,500 iterations on NaN.throw_on_fail=truestill raisesSolveFailure. On the suite, the only solves that still reach the halving are the three capped atmax_iterations1 or 2 on purpose, and the α = 20° post-stall artificial-viscosity solve, which fails on main too (FAILUREboth, CL 0.692 → 0.668).What the stricter solves exposed in
test_unrefined_dist.jlsolver_test_polar.csvhad three points (−10° → cl 0.1, 0° → 1.0, 10° → 1.8) and extrapolates flat, so cl never drops below 0.1 at any angle. A refined tip panel sits near −10° and cannot unload, so the fixed point disappears as the tips get narrower. LOOP and NONLIN both failed at 16–20 panels on main, at every angle I tried. I added a −20° row (cl −0.8, continuing the −10…0° slope) so cl crosses zero. Pinnedsolver_test_wingvalues in the suite do not move, because no pinned case has a panel below −10°. Refined unpinned cases do move, for example 10 panels at α = 0: CL 0.4305 → 0.4279.!isnanguards skipped every check. Once the solve converged, the checks failed by 17%: cl·c·w of a section was 0.355 against a panel sum of 0.430.cl/cd/cm/alpha_unrefined_distare now area-weighted means, andchord_unrefined_distis section area over width, so coefficient ×chord_unrefined_dist · width_unrefined_distis exactly the section load. The two older testsets in the same file pinned the plain mean; they now pin the area-weighted mean through one helper. No other repo reads these four fields (GitHub code search across OpenSourceAWE).moment_unrefined_dist[N·m] was divided by the panel count, whilemoment_coeff_unrefined_distwas summed. It is now the section sum, solinearize(...; aero_coeffs=false)agrees with the coefficient path. StableAWEModels reads only the coefficient, which does not change.The testsets now assert
solver_status == FEASIBLEafter every solve, and the consistency checks are exact (rtol = 1e-12, previously 5% behind NaN guards). Theisnanbranches that let a NaN coefficient pass are gone, so a diverging solve can no longer hide.x/y/z_airf_unrefined_distare still plain means of unit vectors, so they are neither unit length nor orthogonal. Nothing reads them. That is older than this PR and needs a decision on what a section frame should be, so it is #404.Verification
solve!→FAILURE, 39/39 gamma NaN at 5.71° and 10°; LLT on the same wing convergestest/solver/test_solver.jlnew unit test ofpanel_relaxation_factors!: a panel with stiffness 2 keeps ω = 0.03 exactly, one with stiffness 100 gets 1/100 (green, 94/94 in the file)test/body_aerodynamics/test_body_aerodynamics.jlnew curved LOOP testset: red before (FAILURE == FEASIBLE,NaN ≈ 0.422308), green after (4807/4807)test/solver/test_unrefined_dist.jl: red on main's solver and polar (11 failures: 5×FAILURE == FEASIBLE, 6×NaN ≈ NaN), green after (54/54)test_body_aerodynamics,test_results,test_unrefined_dist,test_solver,test_flow_curvature,test_viscous_drag_correction,test_attached_trailed_force,test_moment_units,test_forwarddiff,test_polar_vectors,test_backend_comparison,test_wing_directions,test_stability,test_verification,test_wing_constructor,test_refinement_validation,test_VortexStepMethod,bench.jlgamma_loop!allocations 6 / 208 B, same as main (bench.jlbound ≤ 10)test_solver.jl,test_unrefined_dist.jl,test_body_aerodynamics.jlgreen in the worktree sessionagent ci-local, Julia 1.13, one cell): PASS in 8 min at f7afff7, rerun at c62bd58 running at time of writing · GitHub CI: pendingsolve_base!'s log, not timings.1/relaxation_factor(≈ 33 at the default) now takes smaller steps everywhere than main did. None of V3, pyramid or the test wings is like that, but a very fine uniform mesh could need more iterations.Scope
+143 / −95 across 7 files.
src/solver.jl+73 / −36: the 17-linepanel_relaxation_factors!, the halving loop with its one warning, the area-weighted aggregation, docstrings wrapped to 92 columns. Tests +62 / −59: the curved solve, the relaxation-cap unit test,FEASIBLEchecks, and thearea_meanhelper replacing six copies of the plain mean and the NaN branches. Thefail-fast: falseinCI.ymlis its own cleanup, queued as #401.Opened by
1-Bort-1, an AI agent working for @1-Bart-1.Closes #395 · task
VortexStepMethod.jl-395