A live polar is a polar, not a model of its own - #268
Conversation
SAMPLED was a second linear interpolation over a second set of knots, doing what POLAR_VECTORS already does with Interpolations, and diverging from it: drag extrapolated linearly under one and was held flat under the other, and delta needed a rule of its own. What a live source actually needs is not a model, it is the ability to rewrite a table it already has. set_polar! rewrites a POLAR_VECTORS panel's knots and values in its own storage and rebuilds the interpolations, since Interpolations copies the values it is handed. The panel records the range its table covers, and window_alpha holds alpha inside it, so a table sampled over a window around one angle stops at its ends instead of extrapolating out of them. That is a property of a bounded table rather than of a live one, so the stall scan can key off it and full-range polars, whose window is zero, are untouched. init_aero! now clears the window and the live shape, so a mesh rebuild that re-seeds a panel from its section cannot leave a stale window clamping the section's own table. SAMPLED never shipped, so nothing here is breaking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Interpolations finds a gridded knot with searchsortedfirst, which dispatches on the knot vector, so the search is ours to choose without changing the library. A binary search over nine angles costs more than the whole lookup around it — its branches are unpredictable where a scan over contiguous memory is not — and the live polars a solve rewrites every step are exactly that short. ScanKnots wraps the angles and scans them, and polar_knots hands it to a table under SCAN_KNOT_MAX and the plain vector to anything longer, where the scan would lose badly: 71 ns against 13 at 361 knots. On the 44 panel wing the solve goes from 215 to 176 us, against 206 for the hand-rolled polar this replaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added
Measured on the 44-panel wing (identical converged force,
For scale, a live-polar wing runs
Follow-up upstream: two things worth proposing to Interpolations.jl — a search-strategy option on |
SAMPLEDwas a second linear interpolation over a second set of knots, doing whatPOLAR_VECTORSalready does with Interpolations.jl — and quietly diverging from it. Samecalculate_cd(panel, alpha)call gave linear extrapolation past the table under one model and a flat hold under the other, anddeltaneeded a rule of its own ("ignoresdelta"). What a live polar source actually needs is not a model of its own, it is the ability to rewrite a table the panel already has.What changes
SAMPLEDis gone fromAeroModel. A live polar is aPOLAR_VECTORSpanel whose table gets rewritten.set_sampled_polar!→set_polar!, which rewrites the knots and values in the panel's own storage and rebuilds the three interpolations. The rebuild is needed becauseInterpolationscopies the values it is handed — it holds the knots by reference but not the coefficients. That is the only thing standing between this and a true in-place update; see below.window_alphaholdsalphainside the range the table covers, so a table sampled over a window around one angle stops at its ends rather than extrapolating out of them. This is a property of a bounded table, not of a live one, so the stall-angle scan keys off it instead of off an enum member, and full-range polars — window0— are bit-identical to before.init_aero!clears the window and the live shape, so a mesh rebuild that re-seeds a panel from its section cannot leave a stale window clamping the section's own table.sampled_valueis deleted.Not breaking
SAMPLEDhas never been in a release — it was added and removed inside this cycle, likeTAYLORbefore it. Relative to v4.2.0 there is no model to remove and no behaviour to change. ExistingPOLAR_VECTORSandPOLAR_MATRICESwings evaluate exactly as before.Cost
A live polar now requires panels that carry interpolation fields, i.e. a wing built from
POLAR_VECTORSsections. Panels built without them (Panel{Float64}(), INVISCID or POLY sections) get aset_polar!method that says so rather than failing on a field type. Two test fixtures were updated accordingly.Follow-up
The interpolation rebuild per refresh is temporary. An upstream PR to Interpolations.jl for an in-place value update (or a non-copying constructor) would reduce
set_polar!to writing into the arrays the interpolant already holds, which is a one-line change here.Verification
test/airfoil_aero/test_live_polar.jl— 83 assertions passtest/panel/,test/body_aerodynamics/,test/solver/test_solver.jl— passtest/plotting/test_plotting.jl— 77 pass🤖 Generated with Claude Code