Live polars: regenerate a panel's polar from its deformed shape - #267
Live polars: regenerate a panel's polar from its deformed shape#2671-Bart-1 wants to merge 8 commits into
Conversation
A trailing edge carried by a chordwise beam bends into a smooth camber shape. Projecting that onto one hinge angle throws most of it away, and which angle you get depends on which node pair you measure between. Add the pieces to skip the projection. `TAYLOR` is a per-panel polynomial of arbitrary order in `α - α_ref`, the local expansion a live source refits each solve; it is valid only inside its window, so it ignores δ and is skipped by the stall-angle scan. `KulfanBasis` and `deform_kulfan` deform a fixed Kulfan fit by a matvec against a constant CST basis — never by refitting, which is non-unique enough that the same shape fitted twice moves Cl by more than the deformation does. `control_point_deflection` resamples a deflection given at arbitrary chord fractions onto that basis, so beam nodes today and membrane nodes later feed the same path. `LivePolars` then evaluates NeuralFoil a few angles either side of each panel's own α, in one batched forward pass, and least-squares fits the coefficients. Order and window belong together: order 2 over ±4° tracks a full-polar solve to a few tenths of a percent and bends with the stall knee, while higher orders diverge as soon as the solve steps outside. `polar_drift` reports how far it has, so the caller can refit before trusting it. Costs 2.8 ms for 20 panels, 7.1 ms for 80. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A quadratic fitted over +-4 deg is worse than useless ten degrees out, and that is exactly where a cold-started solve begins: with no previous angle of attack to expand about, the first fit sits at zero while the wing flies at fifteen. The arms then run away and the solve does not converge. Past the window, carry the polynomial's own value and slope at the edge on linearly instead. The fit is untouched inside its range, C1 at the edge, and the answer outside is an honest extrapolation the caller's drift guard then corrects by refitting where the solve went. Shrink-wrap a panel's contour before fitting its base Kulfan parameters, the route the offline polar generator already takes. Fitting a raw slice directly returns weights that oscillate by an order of magnitude more than any deformation will. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Exercised end to end on the SK100 (44 refined panels, chordwise beam) through SymbolicAWEModels. Two things it found, both fixed in A cold-started solve begins outside every window. With no previous solve to expand about, the first fit sat at α = 0 while the wing flew at 5–11°, and an order-2 fit's arms three windows out do not converge. Past A raw slice must be shrink-wrapped before fitting. With both, the SK100 solve converges: α 4.93–10.84°, drift 0.46 of the window, and against its own tabulated
— about 5 % apart, which is the Kulfan refit's own non-uniqueness rather than a difference in physics. The durable fix for that gap is for the offline generator to write the weight vector it used, so the live mode starts from the same one instead of re-deriving it; out of scope here. Worth knowing for LEI sections: NeuralFoil reports analysis confidence around 1e-5 to 3e-3 on the SK100's mid and outboard airfoils. That is not introduced here — the route that generated the SK100's existing tables ( |
It is not "nothing beyond the network's own forward pass": the deformation allocates a weight vector a panel and the deflection path allocates its resampled curve. Measured on a 44-panel wing, the deformation is 0.57 us and 608 B a panel and the forward pass is 3.6 ms and 6.2 MB, so the claim was wrong about the small part and right about which part matters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Measured breakdown on the SK100 (44 refined panels), warm, best of five, since the earlier 0.166 s figure quoted downstream was still JIT:
The analytic deformation lands where the plan predicted (0.84 µs), and the refresh is the forward pass and nothing else: 3.59 of the 5 ms live polars add.
|
The lofted skin drew section_surface at the panel's delta, which under live polars is the undeformed tabulated contour: the picture showed the shape the solve was not flying. Deformation bugs could hide behind it. A panel now carries the deformed KulfanParameters its polar was generated from, written by the same call that installs the polar so the two cannot drift apart, and the skin lofts that. It is the object the airfoil solver was handed, not a second derivation of the same deformation, which is the point: if the deformation is wrong the drawing is wrong with it. KulfanParameters moves from AirfoilAero into the core module so a Panel can hold one; the fitting and coordinate routines stay where they were and both modules still export the type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new docstring landed between airfoil_skin_geometry's docstring and its function, so the first was documenting a string literal and the whole Makie extension failed to precompile: not just airfoil skins, every plot VortexStepMethod draws. It only surfaced on loading the extension for real, which is the argument for the test this adds. The test puts a cambered live_shape on every panel and checks the rib node count switches from the tabulated contour's to the Kulfan one's and back when it is cleared, asserting first that the two counts differ so it cannot pass by drawing the wrong thing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…drag Two things the SK100 found. C(x) vanishes at both chord ends, so the CST basis can put neither edge off the chord line: a deflection that does not end at zero is asking for a chord rotation and translation. Projecting it anyway is not merely inexact, it is unstable, and pinv answered a 2.5% chord deflection with weights three times larger than the camber they were meant to carry. chord_residual removes the line through the deflection's own endpoints; chord_line hands it back, since it is a chord rotation the caller may still owe its angle of attack. A drag fit has its minimum inside the window, so the slope at the lower edge points down and the linear continuation carried it through zero. A negative drag coefficient is an energy source, not an extrapolation; calculate_cd floors it. Neither of these was what broke the SK100 settle, which was a bring-up transient slewing alpha twenty degrees out of the window in ten milliseconds. Both are wrong on their own terms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VortexStepMethod writes .arrow node tables itself, so generating polars inside the repo leaves them stageable. Nothing has been committed, and the two packages downstream already ignore them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A local polynomial cannot hold a stall. Measured on the SK100 over a causal sweep, no order/window combination the fit offered ever turned CL over: the true polar peaks at 24.0 deg with a lift slope collapsing to 0.0044/deg, every fit reported 8x to 13x that, and narrowing the window made it worse because the fit is centred on the previous step's alpha and never reaches the knee the solve is walking into. On a saved failing state that mechanism killed the run — one tip panel's window swallowed the post-peak collapse, its slope flipped to -0.0230/deg where the local slope was +0.0503/deg, and the solve went non-finite. SAMPLED replaces TAYLOR: cl/cd/cm at ascending angles per panel, linear between them and the end value held past either end. The samples are the polar, so a knee between two of them is represented rather than averaged into a slope, and past the ends the answer is stale rather than wrong-signed. The knots move with the panel and are written in place, so a refresh costs the network pass and nothing else. TAYLOR is removed rather than kept: it never shipped, and two local-polar models where one now works is one too many. Live surface pressure follows the same shapes. refresh_live_pressure! takes Cp from one batched pass at the converged alpha, contour_pressure resamples it onto the panel's own contour nodes, and live_surface_friction! gives skin friction from the flat-plate closure at the panel's live Reynolds. contour_shape_matrix and live_shape_offset! then move the contour itself by the deformation's own camber increment — a traction is -Cp*n with n a finite difference of neighbouring node positions, so an undeformed contour points the whole load the wrong way wherever the section has moved. The chord fractions never move, only the offsets, so a surface-to-point map built on that contour stays valid. decode_surface_pressure and contour_pressure also back the NeuralFoilSolver contour assembly, so the live path and the offline table generator are one implementation; a test pins them equal at rtol 1e-8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why
A trailing edge carried by a chordwise Timoshenko beam bends into a smooth camber shape. Projecting that onto a single flap angle δ throws most of it away — and which angle you get depends on which node pair you measure between, which is not a property of the aerodynamics.
This adds the machinery to skip the projection: deform the airfoil, re-generate the polar, and hand the solve a local expansion.
What
TAYLORaero model (core). A per-panel polynomial of arbitrary order inα - α_ref[rad] — the local expansion a live source refits each solve. Sections carryaero_data = (alpha_ref, cl_coeffs, cd_coeffs, cm_coeffs)and blend spanwise like any other model;set_taylor_polar!rewrites a panel's fit in place (no allocation at fixed order). It is valid only inside its fit window, so it ignoresdeltaand is skipped by the stall-angle scan — a local fit's curvature says nothing about stall 10° away.Analytic shape deformation (
AirfoilAero).KulfanBasis+deform_kulfandeform a fixed Kulfan fit by a matvec against a constant CST basis. Never by refitting: the fit is non-unique enough that the same shape fitted twice movesClby ~0.08, which reaches the polars as frame-to-frame jitter indistinguishable from flutter.control_point_deflectionresamples a deflection given at arbitrary chord fractions onto that basis, so beam nodes today and membrane nodes later (upper and lower surfaces deformed independently) feed the same path.Live polar source.
LivePolars+refresh_live_polars!deform each panel's base airfoil, evaluate NeuralFoil at a few angles either side of the panel's own α — every panel and sample in one batched forward pass — and least-squares fit the coefficients.polar_driftreports how far the solve has left the window so the caller can refit before trusting the answer.prepare_inputsnow accepts one Kulfan shape per case, which is what makes the batching possible; the network's input layout moved into a singlefill_case_input!so the scalar and batch paths cannot drift.Order and window belong together
Order 2 over ±4° is the operating point. Measured here at Re 3e6:
Higher order is better inside the window and diverges faster outside it, which is why the drift guard is a correctness requirement rather than an accuracy knob.
Cost
Under 3 % of a 10 Hz budget at 20 panels.
Tests
test/airfoil_aero/test_live_polar.jl(29 assertions): TAYLOR evaluation and its δ-blindness, in-place refit, camber projection to 0.024 % of the deflection, control-point resampling, a live fit tracking a direct NeuralFoil sweep across its own window, the drift guard, and the TAYLOR spanwise blend.test/airfoil_aeroandtest/panelpass unchanged.Nothing existing changes behaviour: no current aero model touches the new branches.