Skip to content

Feature/curved bound vortex regularisation - #165

Merged
ocayon merged 20 commits into
developfrom
feature/curved-bound-vortex-regularisation
Oct 13, 2026
Merged

ocayon merged 20 commits into
developfrom
feature/curved-bound-vortex-regularisation

Conversation

@ocayon

@ocayon ocayon commented Oct 13, 2026

Copy link
Copy Markdown
Member

Curved bound vortex at the quarter chord: chord-regularised correction, leg direction option, validation

Problem

Since the September consistency commits the sectional force direction is taken from the velocity at the quarter chord (is_aoa_corrected=True). On a straight wing that velocity is Prandtl's downwash and the on-blade induced drag equals the Trefftz plane exactly. On a curved wing the neighbouring bound vortex is no longer on the line: two line vortices meeting at a small angle induce an unbounded velocity on each other, so the quarter-chord velocity grows by a constant at every mesh doubling and the induced drag drifts. On the V3 the on-blade over Trefftz ratio went 0.93 → 0.85 between 20 and 160 sections. Leaving the bound segments out is worse: the trailed legs carry a second logarithm wherever they are not perpendicular to the line.

Solution

Seen from closer than a chord a section is a sheet, not a line, and two sheets at an angle induce a finite velocity on each other. assemble_curved_bound_correction_AIC (utils.py) builds, for each panel, the wing straightened onto that panel's tangent and evaluates

V(1/4c) = Prandtl downwash of the straightened wing (filaments)
        + [ real vortex system − straightened vortex system ]   (both as chordwise sheets)

The bound vorticity and the leg starts in the bracket are spread with the flat-plate thin-airfoil distribution (24 strips, strip legs collinear with the actual legs). The bracket is identically zero on a straight wing, so Prandtl is recovered exactly; it is finite on curved wings with a cut-off of 0.15 c fixed by the distribution, no parameter; and it differences legs as well as bound segments, so any leg direction converges. The section's own field never enters. Only the quarter-chord velocity is touched; the circulation solve at the control points is unchanged. The correction is one influence matrix per shape (Numba, ~1 s at 80 panels), cached and reused under rigid motion of the body, rebuilt on deformation.

Leg direction

BodyAerodynamics(leg_direction="rib" | "streamwise" | "perpendicular"), also on instantiate. Rib stays the default. Judged against an AeroSandbox vortex lattice for lift and the Trefftz plane for drag: rib and streamwise legs reproduce the lattice lift within 1 %; the paper's perpendicular legs lose 8–9 % of the lift on kinked and kite-like wings and are kept only as an option.

Results (on-blade / Trefftz induced drag, inviscid polars, rib legs)

case 40 80 160
straight rectangular AR 8 1.000 1.000
circular arc, R = 4c 0.999 0.999 0.999
elliptic, 10 % sweep, 25 % anhedral (Gaunaa et al.) 1.014 1.015 1.017
V3 kite (was 0.91 → 0.85) 1.007 1.008 1.010

V3 induced drag rises by about 10 % at 50 panels (CD +0.005 at 10°) and lands on the RANS values up to 10°. Lift is unchanged.

Documentation

  • docs/validation/curved_bound_vortex_report.pdf (16 pages): Prandtl baseline, the curvature logarithm, what Li and Gaunaa do, the sheet correction, results; appendices with the alternatives tested (Li 2020 literal, curved-minus-straight at the control point, Reid–Hunsaker, vortex core, sheets as baseline), strip-loading sensitivity, the leg study and the implementation status. All tables reproduced by curved_bound_vortex_validation.py, leg_direction_study.py, vlm_referee.py, strip_weight_sensitivity.py.
  • docs/Aerodynamic_model.pdf rebuilt from a new LaTeX source (docs/aerodynamic_model/): formulation, conventions, validation cases.
  • docs/Solver.md, docs/figures/README.md (figures 08–16).

Tests

tests/BodyAerodynamics/test_curved_bound_vortex.py (zero on straight wings, mesh convergence on the arc, cache reuse under rotation, rebuild under deformation) and tests/BodyAerodynamics/test_leg_direction.py. Full suite: 183 passed, 5 skipped.

Also in this branch

  • fix(solver): restores the reject condition broken by 1105122 (SyntaxError on develop), same commit as fix/solver-syntax-error.

Open items (not in this PR)

ocayon and others added 20 commits September 17, 2026 17:25
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>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 18:42

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 reached their quota limit.

@ocayon
ocayon merged commit 1201f6f into develop Oct 13, 2026
1 check failed
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.

2 participants