Skip to content

Write the panel aerodynamics once, as traceable functions - #265

Merged
1-Bart-1 merged 1 commit into
mainfrom
feat/shared-panel-kernel
Aug 25, 2026
Merged

Write the panel aerodynamics once, as traceable functions#265
1-Bart-1 merged 1 commit into
mainfrom
feat/shared-panel-kernel

Conversation

@1-Bart-1

@1-Bart-1 1-Bart-1 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Why

SymbolicAWEModels re-expressed this package's per-panel force symbolically so it could carry it in an ODE right-hand side — the airfoil axes, the chord blend, the angle of attack, the lift and drag directions, the force and the pitching moment, all transcribed by hand into ModelingToolkit equations, plus twins of flow_curvature_cm and section_pitch_rate. Two spellings of one derivation, kept in step by hand. The recent billow-axis fix (#264) is exactly the kind of edit that changes one and not the other.

There were three copies inside this package too: update_panel_properties! built the axes, calc_forces! built the force directions, and calculate_results built them again.

What

src/panel_aerodynamics.jl holds that algebra once, as pure functions of the section geometry and the flow:

panel_axes(le_1, te_1, le_2, te_2, chord_weight, orient)
panel_inflow(axes, va_1, va_2, v_ind, dva_1, dva_2, deficiency)
panel_force_directions(axes, alpha_dir, spanwise)
panel_loads(axes, dirs, q_dyn, cl, cd, cm, scale)

plus panel_span_vector, panel_chord, panel_chord_weight, dynamic_pressure, panel_moment, panel_couple_force, effective_alpha, smooth_norm, and the relocated flow_curvature_cm / section_pitch_rate.

They are generic in the number type, free of in-place buffers, and branch-free apart from two ifelse guards, so one definition serves a Float64 solver, a ForwardDiff.Dual linearization, and a symbolic trace. update_panel_properties!, init_pos!, calc_forces! and calculate_results all call them.

The companion SymbolicAWEModels PR (OpenSourceAWE/SymbolicAWEModels.jl#284) deletes its transcription and calls these instead.

Numerical equivalence

Measured against main on a 12-panel POLAR_VECTORS wing over three inflow directions — 55 stored distributions plus the panel geometry, compared element by element:

fields compared 55
bitwise identical 47
differing 8
max relative difference 2.1e-16 (≈ 1 ulp)

Panel geometry (x_airf/y_airf/z_airf/chord/width/aero_center/control_point) is bitwise identical, which is the check that panel_axes and its offset chord blend reproduce update_panel_properties! exactly.

The eight that differ are lift_dist, drag_dist, panel_moment_dist, moment_dist, moment_coeff_dist, f_body_3D, m_body_3D and moment. The cause is reassociation, not a behaviour change: moment_dist was (f·ẑ·arm + m)·width and is now (f·width)·ẑ·arm + m·width, and the dynamic pressure is now factored out of the lift/drag products instead of being multiplied through. Out-of-polar-range inflow produces NaN in exactly the same places as before.

Allocations

main this branch
calc_forces! 0 B 0 B
solve_base! / solve! 1472 B 1472 B
calculate_results (via solve) 12928 B 12928 B
update_panel_properties! 64 B 0 B
reinit!(body_aero) 4208 B 4144 B

The two improvements come from the chord no longer subtracting MVectors.

Notes

  • section_pitch_rate gains a three-argument form taking the trailing minus leading edge apparent wind directly, which is the shape a symbolic consumer has. The four-argument form is unchanged.
  • effective_alpha and panel_inflow's deficiency argument carry an unsteady lag into the angle the polars are read at. Nothing in the solver has unsteady state to feed them; they are here so a symbolic consumer that does reads the same definition rather than growing its own.
  • Norms inside the shared functions are floored (smooth_norm, 1e-12 m) instead of branch-guarded. flow_curvature_cm and section_pitch_rate keep an ifelse guard: a zero v_rel or zero chord must still report zero, and dropping that guard made a zero-chord section return -1e12 — caught by test_flow_curvature.
  • update_panel_properties! previously indexed out of bounds for a single-panel wing; panel_chord_weight returns 0.5 there instead.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.11765% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/panel_aerodynamics.jl 94.23% 3 Missing ⚠️
src/panel.jl 71.42% 2 Missing ⚠️
src/wing_geometry.jl 95.65% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@1-Bart-1
1-Bart-1 force-pushed the feat/shared-panel-kernel branch from 5ce22f4 to c7906ac Compare August 24, 2026 13:28
SymbolicAWEModels re-expressed this package's per-panel force symbolically so
it could carry it in an ODE right-hand side: the airfoil axes, the chord blend,
the angle of attack, the lift and drag directions, the force and the pitching
moment, all transcribed by hand into ModelingToolkit equations. Two spellings of
one derivation, and the recent billow-axis fix is exactly the kind of edit that
silently changes only one of them.

Pull that algebra into src/panel_aerodynamics.jl as pure functions of the
section geometry and the flow: panel_axes, panel_inflow, panel_force_directions,
panel_loads, and the small helpers around them. They are generic in the number
type, free of in-place buffers, and branch-free apart from two ifelse guards, so
the same definitions serve a Float64 solver, a ForwardDiff.Dual linearization
and a symbolic trace. update_panel_properties!, init_pos!, calc_forces! and
calculate_results now call them instead of spelling the algebra out three times,
which is one copy fewer inside this package too.

Panel geometry is unchanged bit for bit. Forces, moments and coefficients agree
to within 1 ulp, the products having been reassociated: on a POLAR_VECTORS wing
47 of 55 reported quantities are bitwise identical and the largest relative
difference is 2.1e-16. calc_forces! stays zero-allocation, and the geometry pass
got slightly cheaper (update_panel_properties! 64 -> 0 bytes, reinit! 4208 ->
4144) because the chord no longer subtracts MVectors.

section_pitch_rate gains a three-argument form taking the trailing minus leading
edge apparent wind directly, which is the shape a symbolic consumer has; the
four-argument form is unchanged. effective_alpha and panel_inflow's deficiency
argument carry an unsteady lag into the angle the polars are read at. Nothing in
the solver has unsteady state to feed them, but they belong beside the rest of
the panel physics rather than in whoever grows it first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@1-Bart-1
1-Bart-1 force-pushed the feat/shared-panel-kernel branch from c7906ac to 8cfb9ee Compare August 25, 2026 09:41
@1-Bart-1 1-Bart-1 changed the title Write the panel aerodynamics once, as a traceable kernel Write the panel aerodynamics once, as traceable functions Aug 25, 2026
@1-Bart-1
1-Bart-1 merged commit eee5982 into main Aug 25, 2026
9 checks passed
@1-Bart-1
1-Bart-1 deleted the feat/shared-panel-kernel branch August 25, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant