Repository navigation
Blend POLY cl and cd into the flat-plate post-stall form over 20–25° and hold cm at its ±20° value - #386
Closed
1-Bort-1 wants to merge 3 commits into
Closed
Blend POLY cl and cd into the flat-plate post-stall form over 20–25° and hold cm at its ±20° value#3861-Bort-1 wants to merge 3 commits into
1-Bort-1 wants to merge 3 commits into
Conversation
Past |alpha| = 20 deg a POLY section swapped its polynomial for the flat-plate form without blending, so cl dropped by 0.85 and cd by 0.10 across one step, and the post-stall forms had the wrong parity (cl > 0 and cd < 0 for alpha < -20 deg). cl and cd now blend over 20-25 deg with a cubic smoothstep into 2 sin|sin| cos and 2 |sin|^3, and cm holds its +-20 deg value. Inside +-20 deg nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1-Bort-1
commented
Sep 23, 2026
1-Bort-1
left a comment
Member
Author
There was a problem hiding this comment.
Independent review (advisory)
Verdict: APPROVE WITH COMMENTS · 0 inline, 0 off the diff
Good
- The fix matches the card:
blend_post_stallis the single place the polynomial and flat-plate forms are joined, used by both cl and cd; cm is clamped, as described. - The weight is exactly 0 for |α| ≤ 20°, so results inside the fit range are unchanged. The test at test_panel.jl:235 checks this with
==againstevalpoly, and that works becauselei_poly_coeffsreturns a tuple (src/airfoil_aero/poly.jl:56). - The sign bug on the negative side is fixed properly:
sin α |sin α| cos αis odd and|sin α|³is even and non-negative, both match the old forms for α > 0, and the tests at lines 243–249 check this. - Only one implementation exists: grepping
cl_coeffs,cd_coeffs,cm_coeffsandπ/9across src/ finds the POLY evaluation only in panel.jl'scalculate_cl/cd/cm.calculate_cd_cmgoes through these, so there is no second path. - The smoothstep in |α| is C¹. Inside ±20° the
absandclampadd nothing, and at α = 0 theabs(sin α)sits inside a product that is 0 there, so ForwardDiff sees no new discontinuity. - Housekeeping is in order: the stale 'Core only evaluates the polynomial' docstring is corrected and named in the card; the new private symbols are on private_*.md; the CHANGELOG entry is compact and user-facing; the constants carry their units [deg].
- Scope matches the card (+77/−11, 6 files) with no unrelated edits.
Not good
- The blend makes cd fall from 0.183 to 0.145 over 20–24° (a negative dcd/dα). The card says so, but a follow-up issue on whether the Breukels cd should be joined to the flat plate at all would put the question to Bart instead of leaving it in PR prose.
- The test builds a Wing, runs refine! and constructs BodyAerodynamics just to get one panel, while the same file already has a
create_panelhelper (test_panel.jl:10). Check whether it can take POLY aero data before adding a heavier setup. - The jump threshold of 0.01 per 0.01° only guards against gross jumps. A C⁰-but-steep blend would pass it too, so the C¹ claim in the card is not tested.
blend_post_stallcomputes the flat-plate value on every call, including inside ±20° where its weight is 0. The cost is negligible, but the benchmark is marked n/a on a hot path used by every solver iteration.
claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.
… is C1 blend_post_stall now returns the polynomial early inside +-20 deg, so the flat-plate form is only evaluated past it; calculate_cl at 5 deg is back to main's time. The panel test uses create_panel instead of a refined Wing, and checks the second difference of cl and cd over -60..60 deg so a C0 blend with a slope kink fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Local full suite: PASS (6 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! |
Member
Author
…and- Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
A
POLY(Breukels) section's cl and cd now blend smoothly from the α-polynomial into the flat-plate post-stall form between |α| = 20° and 25°, and cm stops at its ±20° value. Before, both coefficients jumped at ±20°, and on the negative side the post-stall form had the wrong sign: cl came out positive and cd negative.What was wrong
Past |α| > π/9,
calculate_clswapped the polynomial for2 cos α sin²αandcalculate_cdswapped it for2 sin³α, with no blend.calculate_cmhad no branch and ran its quadratic unbounded (−5.5 at 30°, below −8 by 36°). The negative side was worse than the issue says.2 cos α sin²αis even in α, so at −20.05° cl flipped from −1.33 to +0.22, a jump of 1.55.2 sin³αis odd, so the drag was negative beyond −20° (−0.25 at −30°).With
lei_poly_coeffs(0.1, 0.08), the largest change between neighbouring samples 0.05° apart over −60°…60° was 1.54 in cl and 0.26 in cd before. After, it is 0.019 in cl and 0.002 in cd.What changed
(1 − w)·poly + w·flat_plate, wherewis a cubic smoothstep in |α| from 20° to 25°. That makes cl and cd C¹ in α, so Newton-type solvers see a derivative everywhere. Inside ±20°blend_post_stallreturns the polynomial before the flat-plate form is evaluated, so every result there is bit-identical to main (the test checks this with==) and costs the same.2 sin α |sin α| cos α(odd) and2 |sin α|³(even, ≥ 0). These are the same curves as before for α > 0.POLAR_*models already hold cm flat outside their table.AeroModeldocstring said "Core only evaluates the polynomial", which was already untrue. It now describes the post-stall behaviour.POLY_FIT_LIMIT,POLY_BLEND_WIDTH,blend_post_stall,flat_plate_clandflat_plate_cdare private and listed on the private docs pages.Where I would push back
The post-stall model itself is the inherited one (it matches the Python VSM), and I kept it. The two models disagree a lot at 20°: cd from the polynomial is 0.183 against 0.080 from the flat-plate form, and cl is 1.07 against 0.22. Any blend therefore gives a falling cd over 20–24° (lowest 0.145 at 24.3°). cl also dips slightly below the flat-plate curve (0.224 at 23.5°, against 0.324 at 25°). The 5° width is my choice of "a few degrees". A wider blend softens the negative lift slope, but it would carry the polynomial further past its fit range. Which post-stall model the polynomial should hand over to is your call, so it is in #388 with three options; this PR keeps the model.
Holding cm flat leaves a kink at ±20°. cm does not enter the circulation solve, so no solver sees it. The ±20° value itself (−2.42 here) is what the Breukels quadratic gives; I have not judged whether it is physical.
Verification
(19.99°, cl 1.0706, cd 0.1832, cm −2.4181),(20.01°, 0.2200, 0.0801, −2.4231),(−30°, cl +0.4330, cd −0.2500, cm −5.5312)test/panel/test_panel.jltestset "POLY polar is continuous past its ±20° fit range": red on main (57 of 153 failing: jump, slope kink, parity, cm unbounded; the inside-±20° guard passed), green after (153/153), juliaservertest/runtests.jlin thetestenv with patterns panel, solver/test_solver, test_verification, airfoil_aero, wing_geometry, yaml_geometry, body_aerodynamics, test_forwarddiff, test_stability: 6299/6299 pass (2m33s). This includes the artificial-viscosity tests on the POLYflat_plate_wing.mainafter Document the body frame as KiteUtils' KA and drop the KB definition #390 in 28b28f7: the only conflict was two independent Fixed entries inCHANGELOG.md, both kept; Document the body frame as KiteUtils' KA and drop the KB definition #390'ssrc/panel.jlchange is thePaneldocstring alone.test/panel/test_panel.jlon the merged tree: 187/187 pass (juliaserver)AutoForwardDiff matches AutoFiniteDiff (LOOP, POLAR_MATRICES)at 0.0570, the flake Keep calc_forces!' per-panel loads out of line so Julia 1.12 gets linearize's ForwardDiff Jacobian right on AVX-512 Zen #378 addresses. Its finite-difference Jacobian norm (3.078044297716207) is bit-identical to whatmain's passing cells use, and computing this test's forward and finite-difference Jacobians withmain'ssrcand with this branch's gives==results for both, so the diff cannot move that test. The rerun passed.calculate_clat 5° on a POLY panel is 5.76 ns (main) → 5.81 ns (branch) → 6.05 ns (main again),calculate_cd5.45 → 5.80 → 5.88 ns, all 0 allocations. Before the early return the branch took 15.1 ns for cl.Box: steady, 6–8 of 20 cores busy, 3.1 GHz,
agent bench-end: conditions heldScope
+87 / −11 across 6 files. The logic is about 35 lines in
src/panel.jl, the test is 41 lines and reuses the file'screate_panelhelper, and the rest is the docstring, CHANGELOG and two private-docs entries. Searchedπ/9,20°,post-stall,POLYinsrc/,test/anddocs/:src/panel.jlis the only implementation of the POLY post-stall branch.Closes #359 · task
VortexStepMethod.jl-359