Skip to content

Release v2.3.0 - #170

Merged
ocayon merged 47 commits into
mainfrom
develop
Nov 4, 2026
Merged

ocayon merged 47 commits into
mainfrom
develop

Conversation

@ocayon

@ocayon ocayon commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Release PR for v2.3.0. Merge it after #172, the last changelog wording change on develop.

Default results change

Forces, moments and coefficients computed with the defaults differ from 2.2.0, most on swept and curved wings such as kites. The lifting-line coupling now follows the consistent formulation of Gaunaa, Li & Pirrung (TORQUE 2026):

  • circulation from the effective velocity, including induction;
  • span-perpendicular sections and chord;
  • quarter-chord force directions (is_aoa_corrected=True);
  • forces on the attached trailed vortices (is_with_attached_trailed_vortex_force=True);
  • a curved bound-vortex correction at the quarter chord, new in this release, which spreads the bound vorticity over the chord with the flat-plate thin-airfoil loading.

On-blade induced drag now matches the Trefftz plane within about 1 % on arc, swept and kite geometries, independently of the panel count. changelog.md lists each change and the option that restores the old behaviour, where one exists. The formulation and validation are in docs/aerodynamic_model and docs/validation.

Breaking changes

  • Removed six Solver options that were never read, and three Simonet gamma_loop_type values that already raised ImportError. An unknown gamma_loop_type now raises ValueError.
  • Solver.compute_aerodynamic_quantities no longer returns Umagw.
  • The Anderson loop no longer falls back to the base loop by default (anderson_fallback_to_base=True restores it).

The version is a minor bump, following the precedent of 2.1.0, which also changed results through a default.

Also in this release

  • v2.2.0 content: the Plotly plotting, the body_axis fix in stability_derivatives and the plt.show() removal in trim_angle. v2.2.0 was tagged on a side branch and never reached main.
  • CasADi:
    • the casadi_newton loop;
    • VSM.core.panel_kernels;
    • compute_quarter_chord_AIC_matrices;
    • casadi_panel_force_function;
    • polar tables as function inputs.
  • New required dependency: SurfplanAdapter.
  • Performance: a 36-panel V3 casadi_newton solve takes about 17 ms, down from about 50 ms.

Validation

  • CI on release/v2.3.0, without the git-ignored data/ml_models files: 213 passed, 11 skipped. The test_masure.py tests are among the skipped ones, so CI does not exercise them.
  • Local, with those files present: 220 passed, 4 skipped.
  • Kernel refactor: results match the reference from before the refactor to 1.5e-14 or better.

After merging

  1. Tag the merge commit: git tag v2.3.0 <merge-sha> && git push origin v2.3.0. The version comes from setuptools_scm.
  2. Create the GitHub release from the 2.3.0 changelog entry.
  3. Publish to PyPI if desired; there is no release workflow.

Written by Claude, reviewed by Oriol Cayon

jellepoland and others added 30 commits July 24, 2026 09:59
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an opt-in 'fancy' mode to interactive_plot that renders the physical
inflatable structure of a LEI kite: the leading-edge tube, the chordwise
strut tubes, and the curved single-skin canopy.

Passing surfplan_dir (a raw Surfplan export: a <name>.txt plus profiles/)
triggers a cached SurfplanAdapter conversion; the generated tube-diameter
tables and airfoil top-surfaces are then drawn on the wing. Omitting it
leaves the plain plot unchanged.

- src/VSM/plotly/surfplan_runner.py: run + cache the SurfplanAdapter pipeline
- src/VSM/plotly/tube_geometry.py: LE tube (diameter from leading_edge_tubes
  table) and profile-curved strut tubes (paired tip-to-tip with strut_tubes)
- src/VSM/plotly/canopy_geometry.py: loft airfoil upper contours into a canopy
- All geometry anchored on the drawn panels, so it is independent of the
  Surfplan export's coordinate frame
- interactive_plot gains surfplan_dir / is_with_canopy / is_with_tube_rings
- SurfplanAdapter added as a dependency; tests + tutorial step added

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Struts now start at the leading-edge tube's aft edge (buried half a tube
  depth in) instead of passing straight through it
- Leading-edge tube wraps around each wingtip and continues to the trailing
  edge of the outer panel (constant-diameter, tip-to-tip continuous)
- Twist-free parallel-transport ring framing (rings_along_centerline) so the
  swept surfaces do not pinch where the tube turns at the tips
- Solid (opaque) tubes so the leading-edge tube reads clearly over the canopy
- Split add_tube_surfaces into add_strut_surfaces / add_le_tube_surface so the
  leading-edge tube can be drawn last (on top)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- interactive_plot gains is_with_panels (default off in fancy mode); when off
  the panel outlines are hidden and drawn thin/blue when on
- Replace the per-panel force arrows over the canopy with a grid of equal,
  surface-normal vectors: one row per panel x 10 chordwise nodes placed at the
  true 5%-95% chord fractions (clear of the LE tube and trailing edge)
- Draw order: struts under the canopy, then canopy, then the vector grid, then
  the leading-edge tube last so it is never occluded
- Canopy recoloured grey; matte surface lighting
- Make the figure axis-range helper skip None/NaN gap separators

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the equal surface-normal vector grid with a physically-scaled loading:

- New VSM.plotly.cp_distributions: treat data/.../cpx_distributions as a Cp
  database keyed by AoA; select the closest available angle to the operating
  point and extract the upper (suction) surface |Cp|(x/c)
- Each panel's VSM force is distributed chordwise, weighted by |Cp| (even split
  without a Cp dir); the node forces of a panel sum back to that panel's force
- Vectors point normal to the local chord line (flat-panel up), constant per
  section, at the true 5%-95% chord fractions
- Normalise force->length so the largest panel force plots at the maximum chord
  length; modest fixed-size arrowheads
- interactive_plot gains cp_distributions_dir; add the V3 Cp/Cf data files and
  Cp-database tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ample

- Distribute each panel's VSM force over the chord weighted by the closest-AoA
  |Cp|(x/c); per-vector size anchored to a base of 10 nodes (extract the shared
  distributed_surface_vector_field into canopy_geometry)
- Cap the open tube ends (leading-edge + struts) so junctions read cleanly
- save_high_res_render: render on a square canvas (Plotly only renders a 3D scene
  undistorted when square), strip legend/axes/annotations, and auto-crop to the
  kite -> a clean, to-scale PNG
- Drop the forced cube aspectratio in the layout so the scene stays true-to-scale
- Add examples/TUDELFT_V3_KITE/fancy_interactive_plot.py running the pipeline and
  writing the high-res render; embed docs/plotly_render.png in the README

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bug plot, add changelog

- stability_derivatives._evaluate_with_angles now passes body_axis so baseline
  roll/pitch rates are not silently mapped to the z-axis
- trim_angle: remove leftover matplotlib debug plot/plt.show() in coarse sweep
- changelog: add 2.2.0 entry (rotational-inflow sign fix, body_rates/body_axis
  API, bridle 'd' key, solver/filament changes, plotly plotting)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmKZURQ7nvjtTzZXXqE6ze
The quarter-chord flow that sets the force directions (TAT3) held the other
panels' bound segments evaluated on the bound line itself. On a curved or
swept line that self-induction is logarithmically singular (Li, Gaunaa,
Pirrung, Ramos-Garcia & Horcas 2020, "LL-P&S"), and because the on-blade
induced drag is rho*Gamma*(w cos a + u sin a) with w only ~1 deg of induced
angle, the chordwise u it produces moved the V3 drag by 10% and kept moving
it with the panel count: on-blade over Trefftz 0.93 / 0.90 / 0.86 at
20 / 50 / 150 panels. Leaving the term out is worse (swept wings collapse,
because the streamwise legs carry the opposite singularity), and reusing
Li's 3/4-chord difference at the quarter chord breaks straight wings here.

Replace the term by the difference between the actual vortex system and the
same wing unrolled onto each panel's tangent line, with the bound vorticity
and the leg starting points of every ring spread along the chord with the
thin-airfoil distribution sqrt((c-x)/x). The quarter-chord velocity is then
Prandtl's downwash of the unrolled wing plus a bracket that is identically
zero on straight wings, finite on curved ones (the distribution fixes the
cut-off at 0.15 c, no tunable length) and mesh-independent. Same mechanism
as the effective-lifting-line / jointed-vortex method of Reid & Hunsaker
(2020), whose two empirical lengths it removes; discrete counterpart of the
inner solution of curved lifting-line theory (Guermond 1990).

On-blade over Trefftz induced drag, inviscid: straight 1.000 (unchanged);
arc R=4c 0.999 at 40, 80 and 160 panels; V3 1.007 / 1.008 / 1.010; swept
30 deg 0.97 -> 0.99; swept-anhedral elliptic wing of Gaunaa et al. 1.01.
V3 viscous polars: lift unchanged, CD +0.001 at 5 deg, +0.006 at 10 deg,
+0.011 at 13 deg, onto the RANS values. Cost: one numba kernel evaluated
once per solve (about 1 s at 80 panels).

Docs: Solver.md section on the consistent coupling, figures 03 (regenerated),
08 (mechanism and fix) and 09 (V3 polars) with README entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…und-vortex regularisation

docs/validation/curved_bound_vortex_validation.py reproduces every table of
the report from the committed solver: on-blade over Trefftz induced drag
against panel count for a straight wing, skewed ribs, a 30 deg swept wing,
a circular arc, the TORQUE 2026 swept-anhedral elliptic wing and the V3,
for the regularisation and for the alternatives that were tried and
rejected (trailed system only, bound segments on the line as on develop
before, the literal Li et al. 2020 recipe, the Reid & Hunsaker 2020
effective-line construction with its joint and blending parameters), the
arc mid-span self-induction (polyline vs chordwise-spread sheet), the V3
viscous polars, and figures 08 and 09. Full run 193 s; results in the
JSON next to it.

docs/validation/curved_bound_vortex_report.tex/.pdf: problem statement,
cause, literature (Li 2018/2020, Gaunaa 2026 and its online appendix,
Reid & Hunsaker, Guermond 1990, core methods), the formulation, the
verification tables and the V3 polars against RANS and the wind tunnel,
plus the two points where the code still deviates from the published
Li/Gaunaa prescription.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the regularisation in place

docs/validation/leg_direction_study.py repeats the September leg-direction
comparison with the curved bound-vortex regularisation (f43bcdb) instead
of the singular bound-on-line term: on-blade over Trefftz induced drag on
the arc, the swept-anhedral elliptic wing, the inviscid V3 and a kinked
30 deg swept wing, with the attached-trailed force on and off, and the V3
viscous polars against RANS, for rib legs and for span-perpendicular (CP1)
legs. Results in leg_direction_study.json (149 s).

Outcome: the rib legs stay. Perpendicular legs cannot be built on the
kinked planform (adjacent perpendiculars cross, NaN), lower the V3
inviscid lift by 6% and the agreement with RANS (mean |dCL| 0.031 against
0.020), and with the attached-trailed force sit 2.5-4% above the Trefftz
plane on the swept V3 and elliptic lines, the same excess the TORQUE 2026
appendix shows on the authors' swept wings. Rib legs are within 1.5% on
every curved case. No solver change.

Report: new Section 4.3 with the table, status item 2 and the open items
updated; PDF rebuilt (10 pages). Figures README entry 07 carries the new
numbers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… discrepancy

118e91d stated that the TORQUE 2026 appendix shows the same 1-6% excess
of on-blade over Trefftz induced drag that perpendicular legs give here.
The sign is the opposite: in Gaunaa, Li & Pirrung (2026) Table 2 and the
appendix the on-blade induced drag of LL-Gaunaa is 1-5% below the Trefftz
plane on the swept wings (e_Osw,Blade above e_Osw,Trefftz), and their VLM
reference is 3% below as well. The report now states this, quotes the
paper's own wording that the leg direction is a modelling choice not fixed
by theory, and notes that the paper runs the legs up to the trailing edge
while the perpendicular legs tested here have a fixed length.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ged with a vortex-lattice referee

BodyAerodynamics(..., leg_direction="rib" | "streamwise" | "perpendicular"),
also on instantiate(). "rib" (default, unchanged behaviour) keeps the legs
along the interpolated input sections. "perpendicular" is the TORQUE 2026
recipe: legs along the CP1 chord direction, perpendicular to the
quarter-chord line, up to the trailing-edge curve; the line tangent is
averaged over one leg length so the legs fan out instead of crossing at a
planform kink (no free parameter), the leg ends at the closest point of the
trailing-edge curve (extended at its ends for skewed tips), and the control
point moves between the panel's own legs (without that, kinked wings
diverge). "streamwise" projects the apparent velocity onto the surface and
rebuilds the legs whenever va is set. utils.perpendicular_leg_end_points,
Panel.set_leg_end_points. Legs that still cross raise ValueError.

Referee: docs/validation/vlm_referee.py runs the AeroSandbox VLM (flat-plate
sections) on straight, yawed, kinked-swept, arc, swept-anhedral elliptic
wings and the inviscid V3, with the Trefftz drag of the VLM circulation
(its near-field drag is low on non-planar wings). Rib and streamwise legs
reproduce the VLM lift within 1% on all planar and curved wings (streamwise
2% low on the arc); the perpendicular legs lose 9% on the kinked wing and
8% on the V3, where they also cross at the tips above 80 panels. On the V3
against RANS the streamwise legs give the closest polars (drag within 0.3%
up to 10 deg). The rib legs stay the default; the earlier fixed-length
perpendicular test (118e91d) is superseded.

Docs: report Section 4.3 rewritten with the three constructions, the
referee tables and figures 10 (geometry: legs, aerodynamic centres, control
points, VLM lattice) and 11 (referee); status item 2 and open items updated;
docs/Solver.md and figures README. Tests: tests/BodyAerodynamics/
test_leg_direction.py (11 tests: equality with ribs where ribs are
perpendicular or streamwise, skewed ribs give the straight wing, kinked wing
builds and converges, crossing raises, legs follow the inflow). Full suite
181 passed, 5 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BodyAerodynamics(leg_direction="streamwise") becomes the default (was "rib").
The legs run along the apparent velocity projected onto the surface, up to
the trailing-edge curve, and are rebuilt whenever va is set; they depend only
on the quarter-chord line, the trailing edge and the flow, not on how the
input sections were drawn, follow the inflow in sideslip, and give the
closest V3 polars to RANS (drag within 0.3% up to 10 deg, lift slope equal
to the RANS one). Known cost, from the AeroSandbox referee: 1-3% less lift
than a lifting surface on the nearly vertical tips of an arc wing at 4-15 deg
(rib legs: -0.2 to +1.6%), with the induced drag closer; up to 7% less lift
at high angle of attack on the thesis curved-wing case. The change is
physical: the vortex system differs where the ribs are not aligned with the
flow (kite tips, sideslip); identical results on wings whose ribs are drawn
along the flow.

An inflow without a mean direction (pure rotation about the centroid) has
no streamwise legs: the rib legs are used for that inflow and a warning is
logged (Panel.reset_legs_to_ribs). After a geometry rebuild (rotate,
update_from_points) the legs follow the stored inflow distribution.

Tests that verify the thesis vortex system (tests/utils.py, the
create_geometry_general oracle, the curved-wing and V3 verification cases)
and the regularisation tests (chordwise legs isolate the bound-vortex term)
now pass leg_direction="rib" explicitly; make_pr_figures.py and the
regularisation validation script likewise, so figures 01-09 and the report's
Sections 2-4 stay reproducible. New test: fallback for pure rotation.
Report (decision paragraph, status, open items), Solver.md and the figures
README state the new default and its cost. Suite: 182 passed, 5 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Reverts the default of def20bb. The code's main use is trim finding with
an inflow that varies along the span (body rates, distributed va), where
the streamwise construction can only use the mean inflow direction and is
rebuilt at every inflow; the rib legs give a fixed vortex system of the
same accuracy (better on arc-shaped tips against the VLM). The streamwise
and perpendicular constructions, the referee, the tests and the figures
are unchanged; the report, Solver.md and the figures README state the
reasoning and the mean-direction limitation. Suite: 182 passed, 5 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d validation

docs/Aerodynamic_model.pdf held chapter 4 of the 2022 MSc thesis, with no
source in the repository and sections left empty. It is replaced by an
18-page document whose source is docs/aerodynamic_model/aerodynamic_model.tex
(latexmk; the compiled file is copied to docs/Aerodynamic_model.pdf).

Contents: scope and assumptions; geometry and the CP1 panel frame; the
vortex system (filaments, Biot-Savart, cores, influence matrices, wake,
the three attached-trailed leg constructions); the inner-outer coupling
(TAT1-3, Gamma = 1/2 |V_inner| c Cl, quarter-chord directions, the curved
bound-vortex regularisation, the attached-trailed force, the spanwise
viscous correction, artificial viscosity, the LLT variant); solver,
inflow, outputs, Trefftz drag and bridle lines; validation (horseshoe and
elliptic analytical cases, Trefftz consistency, the AeroSandbox VLM
referee on planar, swept, arc, elliptic and V3 wings, the V3 against RANS
and the wind tunnel, mesh convergence, the regression cases); limits; a
table of conventions. Every figure names the script that generates it.

New figures 12 (classical wings against Prandtl and the VLM; the LLT loses
its Trefftz consistency on a curved quarter-chord line, the VSM does not)
and 13 (mesh convergence), from docs/validation/aerodynamic_model_figures.py.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ror)

1105122 (Copilot Autofix, "Refactor ratio and gate condition") replaced
five lines with one: it kept the new ratio expression but deleted the
crossed_gate assignment and the opening `if not finite(trial) or (` with its
condition, leaving a bare `):` at Solver.py:1447. Every `import VSM` on main
raised SyntaxError: unmatched ')', so every fresh install of a package that
depends on VSM@main (AWETrim) failed.

Restores the condition and keeps the autofix's intended change: a trial with
exactly zero residual now has ratio = inf (accepted) instead of 0 (rejected).

(cherry picked from commit beb0a4f)
…metry

The quarter-chord correction (assemble_curved_bound_correction_AIC) depends
on the panel geometry only: the wake terms cancel between the actual and
the unrolled systems and the inflow speed does not change the result (bit
identical on the V3 at 10, 20 and 40 m/s). It costs about 0.5 s at 50
panels and was rebuilt on every solve, which multiplied in a coupled
aerostructural trim that solves one deformed geometry hundreds of times
(231 s against 60 s for one LEI-V3 wireframe case with the old force
directions). It is now cached on a hash of the arrays the kernel reads
(evaluation and bound points, leg ends, chord directions, chords) plus the
chord fractions, so a rebuild, a rotation, a moved point or a changed leg
construction invalidates it; a solve on an unchanged geometry takes about
0.1 s at 54 panels. Results are unchanged. Test: one kernel evaluation for
two inflows on the same panels, identical results to a fresh body, and a
recomputation after a rotation. Suite: 183 passed, 5 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tion

A trim that rotates the body for every residual evaluation (AWETrim's
quasi-steady trim) rebuilds the panels each time, so the per-geometry
cache of 2644b5c never hit: profiling one LEI-V3 coupled case showed the
kernel on all 280 solves, 135 s of 222 s. The cache now stores the
evaluation, bound and leg-end points, the chord directions and chords of
the computed geometry; when the current panels are that geometry up to a
rigid rotation and translation (Kabsch fit, residual below 1e-9 of the
size), the stored matrix is rotated instead of rebuilt, since its entries
are velocity vectors that rotate with the body. A different shape
recomputes. Test: one kernel evaluation for a rotated body, the same lift
and drag as a body built in the rotated position, and a recomputation
after a trailing-edge point is moved. Suite: 183 passed, 5 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…not once per solve

Both the regularisation report and the aerodynamic model document said
the kernel is called once per solve; since 2644b5c and 0e5a67f it is
computed once per deformed shape and reused under rigid motion. PDFs
rebuilt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd the model document

docs/validation/unrolling_figure.py draws, for the arc wing, the kinked
swept wing and the V3, the straight reference wing that the curved
bound-vortex regularisation builds on each evaluation panel's own tangent
(one per panel, nodes at their arc-length distances, legs along that
panel's chord direction), and the distance between actual and unrolled
nodes against the distance from the panel: the near field coincides and
cancels in the difference, the far field is the curvature effect. Figure
14 in docs/figures, placed after eq. 6 of the regularisation report and in
Section 5.3 of the aerodynamic model document; both PDFs rebuilt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
docs/validation/chord_spread_figure.py draws the 24 thin-airfoil strips on
real and unrolled panels of the arc wing, the chordwise velocity one
neighbouring segment induces at the quarter-chord point as a filament and
as a sheet (equal beyond a chord, finite within it only for the sheet,
levelling at the 0.15 c cut-off), and the running sum of the neighbours
from far to near at 40 to 320 panels (filaments keep rising, sheets
settle). Figure 15 in docs/figures, placed after the spreading paragraph
of the regularisation report and in Section 5.3 of the aerodynamic model
document; both PDFs rebuilt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e printing

New Section 4.2 'Reading of the formulation': the quarter-chord
construction is the same handover rule as TAT1 at the three-quarter chord
(3D minus what the 2D section already contains, with the unrolled wing as
the section's straight continuation); the chordwise sheet is the vorticity
the inner model is made of, with a table showing that the three-quarter
chord sums converge and need no sheet while the quarter-chord sums do
not; the two sheet terms are the same panel in two positions. 'Other
approaches' gains the per-panel segment-minus-infinite-line subtraction,
tested and divergent like one over the panel width squared. Table 4's
perpendicular-leg rows now use the current construction (the earlier rows
were the superseded fixed-length legs). Open items gain the body-rate
direction velocity (effective torsional rate, Li, Gaunaa and Pirrung
2026 preprint). Abstract mentions the leg referee; title block and date
updated; 17 pages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…othesised LL-Gaunaa term

Report Section 4.2: derivation of the velocity two line vortices meeting at
a kink induce on each other (Gamma (1 - cos theta) / (4 pi r sin theta),
unbounded at the kink), its sum over a curved line (the logarithm of the
span over the panel width) and the same sum for chord-spread sheets (the
logarithm of the span over 0.15 c, mesh-independent), with figure 16 from
docs/validation/kink_figure.py.

Report Section 3.1: the form that reproduces the published behaviour of
LL-Gaunaa, curved-minus-straight bound segments as single filaments at the
three-quarter-chord control point reused at the quarter chord, is now
implemented in the validation script (--hypothesis) and tabulated: it is
mesh-independent and within 1-2 % of the Trefftz plane with the paper's
perpendicular legs, and fails with rib legs on the V3 (0.81 -> 0.74) and
the kinked wing, because skewed legs carry their own logarithm that a
bound-only difference cannot see. Open item and status updated accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…p loading

Replace the flat-plate thin-airfoil strip weights by the NeuralFoil loading
(gamma = ue_upper - ue_lower) of the V3 centre section at 5, 10 and 15 deg
and of a NACA 4412. The effective cut-off moves from 0.16 c to 0.13 c and
the centroid aft of the quarter chord; the arc wing changes by at most
0.3 % and the V3 induced drag by 0.5-1.5 %, within the referee's own
uncertainty. The flat-plate weights are kept (solution-independent, cached
per shape). Script docs/validation/strip_weight_sensitivity.py, report
Section 4.2 (Table tab:strips).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hysics

Narrative: Prandtl's straight wing as the exact baseline and why; curvature
makes two line vortices at an angle induce an unbounded velocity on each
other, hence the logarithm and the mesh drift; Li and Gaunaa avoid the
quarter-chord point and assume the velocity constant along the chord and
the legs perpendicular; a section is a chordwise sheet and two sheets at
an angle see a finite velocity; the correction is the real wing as sheets
minus the wing straightened at the section as sheets, added to Prandtl,
so that curvature is isolated and the section's own field never enters;
results against the Trefftz plane, a vortex lattice and RANS. Alternatives
tested, strip-loading sensitivity, the leg study in detail and the
implementation status move to appendices. 16 pages (was 19).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ocayon and others added 11 commits October 13, 2026 20:43
…ularisation

Feature/curved bound vortex regularisation
The induced-velocity kernels carried vectors as length-3 arrays; in numba
every temporary (np.cross, r1 - r0, np.zeros(3)) is a heap allocation, and
those allocations were ~95% of the cost: ~1.1 us per filament evaluation.
They now carry 3-tuples, which numba keeps on the stack (~15-19 ns).

Formulas, branch structure and summation order are unchanged, so the
matrices still match the Filament.py reference to round-off (the
equivalence tests at rtol 1e-12 pass). error_model="numpy" keeps NumPy's
inf/nan semantics for zero denominators instead of numba's scalar
ZeroDivisionError, so degenerate geometry behaves as before.

V3 kite, 36 panels: AIC 7.3 -> 0.10 ms; casadi_newton solve 44 -> 24 ms.
Affects assemble_AIC_matrices, assemble_curved_bound_correction_AIC and
induced_velocity_at_points. Numerical, not physical; outputs unchanged.

Generated with Claude Code
The section force law moves into VSM.core.panel_kernels, pure functions over
an xp namespace (numpy or casadi), so CasADi callers (AWETrim's CasADi trim)
use VSM's formula instead of restating it. compute_results calls the same
kernels for its per-panel force, so the formula lives once.

- BodyAerodynamics.compute_quarter_chord_AIC_matrices(core_radius_fraction,
  va_norm_array, va_unit_array): public quarter-chord induction, AIC_LLT +
  the cached curved bound-vortex correction. update_effective_angle_of_
  attack_if_VSM now uses it (one source).
- panel_kernels: relative_flow, quarter_chord_velocity, direction_angle,
  section_force_directions, section_force_magnitudes, section_force_vectors,
  panel_forces, panel_moment_local, attached_trailed_forces, row helpers.
  compute_results and compute_attached_trailed_vortex_forces call them.
- Solver.casadi_panel_force_function(): CasADi function (v_rel, v_qc, axes,
  chord, width, rho) -> per-panel force, local moment, alpha, alpha_dir,
  umag, cl, cd, cm, on the same polar lookups as the casadi_newton residual
  (factored into _casadi_polar_lookups; the residual function is unchanged).

Refactor plus API: no physics change. Every numeric solve output matches
VSM 1201f6f to <= 1.5e-14 relative for is_aoa_corrected True and False on a
straight, a swept, an arc wing and the LEI V3 kite (frozen reference in
tests/BodyAerodynamics/data, written with 1201f6f). CasADi kernels equal the
NumPy ones to 1e-12; a CasADi graph with the induction matrices as
parameters reproduces compute_results panel forces to 2e-16.

Generated with Claude Code
…dual calls

- compute_trefftz_plane_induced_drag: the O(n^2) per-panel Python loop with
  np.cross becomes one vectorised evaluation (1.3 -> 0.2 ms at 36 panels).
  It is a far-wake diagnostic computed on every solve; output unchanged.
- Wing.compute_projected_area: vectorised over the strips (1.3 -> 0.1 ms).
  Also stops normalising the caller's z_plane_vector in place.
- gamma_loop_casadi_newton: the inputs that are constant within a solve are
  converted to CasADi matrices once, and the loop calls a cached variant of
  the residual function with only the six outputs it reads (same graph).
  ~185 -> ~77 us per residual evaluation.

Numerical, not physical. Sums are reordered, so outputs move at round-off
only: every solve output still matches 1201f6f to <= 1.5e-14 relative.
V3, 36 panels, casadi_newton: ~24 -> ~17 ms per solve.

Generated with Claude Code
On a deforming wing each panel's blended polar changes slightly every
coupled iteration. The CasADi residual and panel-force functions embedded
the tables as constants and were cached per table set, so a caller that
re-trims deformed shapes (AWETrim's CasadiTrim) rebuilt its graph every
iteration and could not afford to SX-expand it.

- _casadi_newton_function(table_inputs=False) and
  casadi_panel_force_function(table_inputs=False): with True the functions
  take cl_table, cd_table, cm_table, each (n_panels, n_alpha), as extra
  inputs and are cached per (panel count, alpha grid, settings), not per
  table values. The lookup is one 1-D parametric linear interpolant on the
  shared grid, mapped over panels: same nodes and clamping, so the outputs
  equal the embedded-table ones to round-off. Omitted tables default to
  NaN, never to a silently zero polar.
- Solver.casadi_polar_table_inputs() and casadi_stall_angle_inputs(): the
  current tables and stall-gate angles in the input layout.
- Linear polars on one shared alpha grid only (a parametric cubic spline
  refits per call, ~0.1 s); other cases raise a clear ValueError.

VSM's own loops keep the embedded-table functions and are unchanged: all
solve outputs are bit-identical to a801b5b (4 loop types, both
is_aoa_corrected settings, viscosity on/off). Expanded to SX, the table
function evaluates as fast as the embedded one (+2% at 135 panels).

Generated with Claude Code
Quarter-chord force law as xp kernels + public API; allocation-free filament kernels
CasADi panel functions: polar tables as inputs, so one graph serves every deformed shape
v2.2.0 was tagged on the improve_plotting branch (c58337d) and never merged
into develop or main. This brings in what that release shipped: the Plotly
plotting (inflatable tubes, curved canopy, Cp force fields), the body_axis
fix in the angle-derivative evaluation of stability_derivatives, the removal
of a leftover debug plot (plt.show) in trim_angle, the 2.2.0 changelog entry,
CLAUDE.md and README updates. Merges without conflicts.

Generated with Claude Code
#167 was merged into its stacked base feature/quarter-chord-xp-kernels after
#166 had already landed that branch on develop, so 3cb7bd0 never reached
develop (PR #168 brings it in on its own; merging both is harmless).

Generated with Claude Code
Lists the default-result changes of the consistent lifting-line coupling
(each with the option that restores the old behaviour where one exists), the
removed options, the new CasADi APIs, fixes and speedups, and notes that the
release includes the 2.2.0 content that had not been merged.

Generated with Claude Code
Land #167 on develop (polar tables as CasADi inputs)
Copilot AI balanced review requested due to automatic review settings November 3, 2026 15:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

ocayon and others added 2 commits November 4, 2026 12:24
The models in data/ml_models are git-ignored, so these six tests failed on
every clean checkout, including CI. Skip the module when they are missing.

test_masure_regression_requires_model_files checked data/models, a path that
does not exist, so it was always skipped. The module-level check replaces it
and that test now runs when the files are present.

No source change. Test bug, no effect on results.

Generated with Claude Code
Release prep v2.3.0: restore the unmerged v2.2.0 content, land #167, changelog
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
5092 3943 77% 0% 🟢

New Files

File Coverage Status
tests/BodyAerodynamics/panel_kernel_cases.py 85% 🟢
tests/BodyAerodynamics/test_curved_bound_vortex.py 100% 🟢
tests/BodyAerodynamics/test_leg_direction.py 100% 🟢
tests/BodyAerodynamics/test_panel_kernels.py 100% 🟢
tests/Solver/test_casadi_polar_table_inputs.py 100% 🟢
tests/plotting/test_tube_geometry.py 98% 🟢
TOTAL 97% 🟢

Modified Files

File Coverage Status
tests/AirfoilAerodynamics/test_masure.py 27% 🟢
tests/BodyAerodynamics/test_wing_aero_object_against_create_geometry_general.py 94% 🟢
tests/utils.py 60% 🟢
TOTAL 60% 🟢

updated for commit: a0fe3aa by action🐍

Name the chordwise thin-airfoil loading of the curved bound-vortex correction,
a new modelling assumption. Add the anderson_max_iterations default, the new
Panel leg methods and the SurfplanAdapter dependency, and set the release date.

Documentation only, no effect on results.

Generated with Claude Code
ocayon and others added 3 commits November 4, 2026 12:43
…elocity

Say that the circulation uses the span-perpendicular effective velocity,
including induction, and state the old formula in the same terms.

Documentation only, no effect on results.

Generated with Claude Code
docs(changelog): describe the circulation velocity as the effective velocity
@ocayon
ocayon merged commit a2a91cb into main Nov 4, 2026
1 check passed
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.

3 participants