Repository navigation
Conversation
Add gamma_loop_type="casadi_newton": the circulation fixed point solved as a root-finding problem, R(gamma) = (I - diag(mu) L) gamma - G_raw(gamma), with the Jacobian from CasADi automatic differentiation. CasADi holds only the per-panel section physics (G_raw, mu, alpha as functions of the panel's relative velocity); NumPy assembles R and J = I - diag(mu) L - sum_k diag(c_k) AIC_k, so the graph is O(n) and the induction matrices stay numeric. Same fixed point as base/anderson. Globalisation: Newton with Armijo backtracking, switching to pseudo- transient continuation (implicit Euler on the base loop's own flow, SER with a floor at 0.03) when the line search stalls -- the piecewise-linear polars make |R|^2 have kink minima that are not roots at the Cl-max corner. The Jacobian differentiates the corner-averaged polar so it does not chatter across that corner, and the step control treats the viscosity gate, the residual's one discontinuity, as such (a crossing at the floor step is accepted). Stopping rule is the UN-relaxed residual, 1/relaxation_factor tighter than base's at the same allowed_error. Falls back to base on a failed attempt (newton_fallback_to_base, default on). Measured on the AWETrim LEI-V3 (64-state alpha/sideslip/yaw-rate grid, AV on): 27 panels cold 64/64 converged without fallback in 1.7 s vs base 14.9 s and anderson 49/64 in 7.6 s; 45 panels 61/64 + 3 fallbacks in 6.9 s vs base 29 s. Quasi-steady trims ~5x faster with identical result. The CasADi panel function also exposes the polar lookups (cl, cd, cm and their corner-averaged forms) and a polar_interpolation="bspline" option, for callers that build forces from the same tables. Optional dependency: pip install Vortex-Step-Method[casadi]. Co-Authored-By: Claude <noreply@anthropic.com>
… loops is_smooth_circulation, smoothness_factor, is_artificial_damping, artificial_damping, is_with_simonet_artificial_viscosity and simonet_artificial_viscosity_fva were stored on the Solver and never read. The gamma_loop_type values simonet_stall, non_linear_simonet_stall and non_linear_simonet_stall_newton_raphson imported VSM.StallSolvers, which no longer exists, so selecting them raised ImportError. Li, Gaunaa, Pirrung & Lonbaek (TORQUE 2026) also note the Simonet coefficient must be tuned case by case, and their artificial viscosity (is_with_artificial_viscosity) replaces it. An unknown gamma_loop_type now raises a clear ValueError. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…er| c Cl The fixed-point target was Gamma = 0.5 Umag^2 / Umagw c Cl, i.e. the Kutta-Joukowski force evaluated with the undisturbed freestream speed perpendicular to the span. Gaunaa, Li & Pirrung (TORQUE 2026, Eq. 4) equate the 2D lift to the Kutta-Joukowski force on the bound vortex with the inner velocity itself, Gamma = 0.5 |V_inner| c Cl, where V_inner is the span- perpendicular relative velocity at the control point with the section's own 2D bound induction removed. On the V3 kite Umag/Umagw is 0.94-0.98, so the old map put the circulation 2-6% low. Applied in the base, Anderson, Broyden and CasADi loops; compute_aerodynamic_quantities no longer returns Umagw and the relative velocity is exposed through compute_relative_velocity(). The CasADi post-stall test that relies on Newton finding the unregularized post-stall root now uses 10 panels: with the new map that root is not reached at 20 panels, while the regularized loop converges there in a few steps. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The post-stall regularization of Li, Gaunaa, Pirrung & Lonbaek (TORQUE 2026, Eq. 16) applies wherever the lift slope is negative, but the gate only looked for the first positive-Cl peak, so a panel past negative stall was never regularized. A mirror gate (the Cl trough nearest alpha = 0 in the negative-Cl region) is added to the numpy loops and to the CasADi residual, whose panel function also drops the now unused freestream input. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…inciple) The panel frame was built from the section's raw LE-to-TE chord line and the leading-edge step, so on swept panels the chordwise axis was not perpendicular to the bound vortex and the normal was not perpendicular to the chord (on the V3 kite tips the chord-to-span dot product reached 0.77). CP1 of Gaunaa, Li & Pirrung (TORQUE 2026) defines the inner 2D section perpendicular to the local quarter-chord line: the chord direction is now projected perpendicular to the bound-vortex axis, the normal is chord x span, and the panel chord is the perpendicular one (56% and 19% shorter on the two V3 tip panels). On unswept panels nothing changes. This also makes the normal come from the bound-vortex step rather than the leading-edge step (the issue behind VortexStepMethod.jl PR #273). The thesis-geometry oracle test now projects its expectations the same way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the quarter-chord flow Two departures from TAT3 of Gaunaa, Li & Pirrung (TORQUE 2026), which takes the lift and drag directions from the undisturbed relative flow at the quarter chord: - The profile drag direction was the global wing span axis crossed with the local lift direction. Where a panel's span axis tilts away from the global one this misdirects the drag: on the V3 kite by 108, 65, 34 and 22 degrees on the four outermost panels, the tip getting an upstream component. The drag now acts along the local inner flow direction. - The quarter-chord flow used for the directions (is_aoa_corrected) came from the LLT influence matrix, which drops every bound vortex. That is exact only for a straight lifting line; the bound segments of the other panels (the curved/swept bound-vortex influence of Li et al. 2020) are now added back through assemble_bound_vortex_AIC. On a 30 deg swept inviscid wing the on-blade induced drag was a third of the Trefftz-plane value before and is within 6% after; on a straight wing both agree to four digits. The results-oracle test gains an absolute tolerance on the side force, which is zero only up to round-off now that the drag direction is local. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…w it models The spanwise-flow friction correction of Gaunaa, Sorensen & Li (2024) computed its angle beta from cos(alpha) y_airf + sin(alpha) x_airf dotted with z_airf. That vector lies in the airfoil plane, so beta only ever saw the frame skew of swept panels, never the flow: on a straight wing in 10 deg sideslip it was exactly zero, on the V3 kite it was a geometry-only number up to 49 deg at the tips at any sideslip. The drag increment was also built as z x (z x d) = -d, pointing upstream, and the reference speed and dynamic pressure were the freestream ones. The correction now takes the full relative velocity at the control point (freestream plus induction, passed from the solver): beta is the angle between it and the span-normal plane, Re_ref and the dynamic pressure use the span-perpendicular speed (Eq. 4), the drag increment of Eq. 10 acts along the local inner flow and the spanwise force of Eq. 11 along the span with the sign of the spanwise flow. The per-panel debug prints are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gments
Gaunaa, Li & Pirrung (TORQUE 2026, Sec. 3) align the bound part of each
trailed vortex with the local chord up to the trailing edge and apply the
Kutta-Joukowski force to these attached trailed (AT) segments, a
contribution absent from lifting-line codes so far. The chordwise legs
already existed in the induction here (bound point to TE point) but carried
no force.
Neighbouring panels' legs coincide on their shared section boundary with
opposite sense, so boundary j carries gamma_j - gamma_{j-1} in the
bound-to-TE direction; the force is rho * Gamma_net * (V x l) with V the
full relative velocity at the section's 3/4-chord point on the leg (the
paper's evaluation point), from a new numba kernel that evaluates the whole
horseshoe system at arbitrary points and skips a leg the point lies on.
Each boundary force is split over its two panels in F_distribution, added
to the totals and moments, and reported in F_attached_trailed_distribution.
Opt-out with is_with_attached_trailed_vortex_force=False (default True). On
straight wings the forces are spanwise and cancel; with the quarter-chord
directions the on-blade induced drag of a straight inviscid wing then equals
the Trefftz-plane value to four digits. On the V3 kite it adds about 3% lift
and 5% drag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lity limit The fixed-point loops used a fixed relaxation_factor of 0.01 (and 0.005 on retry), a guess that is far too cautious for coarse high-aspect-ratio wings and can be unsafe for fine low-aspect-ratio ones. Li, Gaunaa, Pirrung & Lonbaek (TORQUE 2026, Eqs. 10-11) bound the largest eigenvalue of the fixed-point Jacobian by 1 + (1/4)(N/AR) max Cl' for a uniform rectangular wing, giving omega_max = 2 / (1 + N/(4 AR) max Cl'). The per-panel form 1 + (1/4) max_i (c_i/dz_i) Cl'_i is used here so narrow tip panels of a cosine or kite discretization tighten the limit as they should; Cl'_i is the steepest positive slope of the panel polar. relaxation_factor now defaults to None, meaning 0.8 x that limit computed per solve (Solver.compute_relaxation_factor_limit, stored in relaxation_factor_used); an explicit value is used verbatim. The paper's post-stall limit (Eq. 18) is for the explicit viscosity scheme; this solver applies the viscosity implicitly, for which order-one relaxation stays stable, so the attached-flow limit is used throughout. On the V3 kite the factor comes out at 0.06-0.1 and the base loop converges in 3-4x fewer iterations to the same circulation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s comparisons docs/Solver.md gains the section on the Gaunaa, Li & Pirrung (TORQUE 2026) coupling rules as implemented (span-perpendicular frame, quarter-chord directions, attached-trailed force), the corrected description of the artificial viscosity and viscous drag correction, and the V3 kite polars before and after the changes against RANS and wind-tunnel data. The verification cases compare the solver against the thesis-era code, which uses Gamma = 0.5 Umag^2/Umagw c Cl, the raw section chord and no attached-trailed force. Their tolerances are widened to the measured deviations of the new formulation (V3: CL +0.015, CD +0.012 at 8 deg, half of the CD from the attached-trailed force; curved wing: CL +0.02, CD +0.004); the comparisons against the analytical, CFD and Maneia references are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Li et al. (2026) limit is derived for a lifting line evaluated on the quarter chord. Measured against the true fixed-point Jacobian it is exact for the LLT model (omega_max / bound = 1.00 on rectangular wings of AR 5 and 16-50 panels) but optimistic for VSM, whose 3/4-chord control point sees about twice the trailing-vortex induction: the ratio is 0.51-0.66 and tends to 0.5 with refinement, and at 0.8 x the raw bound a 16-panel VSM case only converged on the half-factor retry. The bound is now halved for VSM. With the 0.8 safety factor every rectangular, cosine-spaced and V3 case tried converges for both models. Three CasADi tests pin the base loop to relaxation_factor=0.01 explicitly, because their premise (a ~1000-iteration base reference, a 100x looser base residual, a post-stall root the relaxed loop does not reach) is about that fixed factor, not the adaptive default. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds BodyAerodynamics.compute_trefftz_plane_induced_drag and the results entry drag_induced_trefftz for a wing in one uniform inflow: the wake filaments projected onto the plane normal to the freestream are 2D vortices with the circulation jump they trail, and the drag is the Kutta-Joukowski force of half their velocity on the wake trace carrying the bound circulation, D = 1/2 rho sum Gamma_i (V_T,i x l_i) . e_inf. It does not depend on where forces are evaluated on the blade, so it is the check the on-blade induced drag is held against: with quarter-chord directions and the attached-trailed force an inviscid straight wing agrees to four digits, an elliptic wing to 2% of L^2/(q pi b^2), and a 30 deg swept wing with streamwise sections is 6% low. None for per-panel inflow or body rates. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…velocity The two semi-infinite wake filaments of every ring pointed along one direction for the whole wing: the area-weighted mean of the prescribed freestream, taken before the body-rate velocity is added. Under yaw or roll rates the relative inflow direction varies along the span, so the wake did not follow the local flow anywhere. Each ring now uses its panel's own apparent velocity (freestream plus rotational term, or the distributed inflow) for direction and for the viscous-core speed, in the numba AIC kernel, the pure-Python reference, the attached-trailed force evaluation and the plotting wake. In a uniform inflow every panel gets the same direction, so translating-flight results are unchanged to round-off. A single direction is still accepted and broadcast. The wake direction is a second-order effect for a translating wing (a per-panel local-flow wake moves the V3 lift by under 1%), so this matters for turning flight and stability derivatives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Seven figures with a README, generated by docs/figures/make_pr_figures.py: V3 polars before/after against RANS and wind tunnel, the panel frame and drag-direction and viscous-beta corrections, the Trefftz-plane check of the on-blade induced drag for both direction choices, the attached-trailed force on the V3, the adaptive relaxation factor and the measured stability limit, the per-ring wake direction under a yaw rate, and the investigated and rejected perpendicular chordwise legs. The pre-change polars are kept as JSON since the current code cannot reproduce them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cted=True) The lift and drag directions now come from the undisturbed flow at the quarter chord by default (TAT3 of Gaunaa, Li & Pirrung, TORQUE 2026; the paper's LL-Gaunaa), while the magnitudes keep the 3/4-chord angle (TAT2). On inviscid wings this reproduces the Trefftz-plane induced drag exactly on straight wings and within 2% on an elliptic one, where the 3/4-chord directions overshoot it by 20-40% (docs/figures/03_trefftz_check.png). On the V3 kite the drag lands on the RANS values in the attached range and in sideslip. The 3/4-chord directions were kept as default until now because their overestimated drag happened to match the V3 wind-tunnel drag better, for reasons not yet understood; is_aoa_corrected=False still gives them. The direction step is restricted to the VSM model: the LLT loop already evaluates on the quarter chord without bound-vortex influence (the paper's LL-1/4), so its loop angle is its direction angle, and adding the other panels' bound vortices there made the LLT drag inconsistent with its own circulation (verification case CD 0.153 instead of 0.120 at 8 deg). On the V3 verification case the new default VSM is close to the thesis-era VSM, which evaluated directions at the quarter chord too: CL 0.871 vs 0.860, CD 0.108 vs 0.085 at 8 deg (the remaining CD difference is the attached- trailed force and the span-perpendicular chord). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved solver correctness and input-validation issues remain, along with documentation and regression-test gaps.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Aligns the Vortex Step Method solver with updated aerodynamic formulations, adding adaptive/Newton solving, Trefftz drag, force and wake updates, and expanded validation.
Changes:
- Updates circulation, geometry, force, viscosity, wake, and drag calculations.
- Adds adaptive relaxation, optional CasADi Newton solving, and attached-trailed forces.
- Updates tests, documentation, figures, and optional dependencies.
File summaries
| File | Summary | Review notes |
|---|---|---|
tests/verification_cases/TUDELFT_V3_KITE/test_kite.py |
V3 kite verification updates | — |
tests/verification_cases/curved_wing/test_curved_wing.py |
Curved-wing verification updates | — |
tests/Solver/test_solver.py |
Relaxation and Trefftz regression tests | — |
tests/Solver/test_gamma_loop_casadi_newton.py |
CasADi Newton solver tests | — |
tests/BodyAerodynamics/test_wing_aero_object_against_create_geometry_general.py |
Geometry regression tests | — |
tests/BodyAerodynamics/test_wake_direction.py |
Wake-direction tests | — |
tests/BodyAerodynamics/test_calculate_results_against_output_results.py |
Force/result regression tests | — |
src/VSM/core/Wake.py |
Per-panel wake directions | — |
src/VSM/core/utils.py |
Aerodynamic influence and force utilities | — |
src/VSM/core/Solver.py |
Solver algorithms, relaxation, viscosity, and Newton iteration | Moderate (2 votes): accept exact zero pseudo-transient trials. Moderate (1 vote): apply artificial-viscosity regularization in the nonlinear loop or reject the combination. Nit (1 vote): add negative-stall regression coverage. |
src/VSM/core/Panel.py |
Panel frame and chord handling | — |
src/VSM/core/BodyAerodynamics.py |
Force, wake, frame, and Trefftz calculations | Moderate (1 vote): restrict broadcasting to scalar/one-row inputs and handle empty inputs. Nit (1 vote): add swept/curved attached-trailed-force regression coverage, including flag-off behavior. |
pyproject.toml |
Optional CasADi dependency | — |
docs/Solver.md |
Solver behavior documentation | Moderate (1 vote): qualify or align the documentation’s “exact Jacobian” claim. |
docs/nomenclature.md |
Solver terminology | Nit (3 votes): remove the unsupported simonet_stall choice. |
docs/figures/v3_polars_before_fixes.json |
V3 comparison data | — |
docs/figures/README.md |
Figure descriptions | — |
docs/figures/make_pr_figures.py |
Validation figure generation | — |
Review details
Suppressed comments (5)
docs/Solver.md:64
- This public description calls the CasADi Jacobian exact, but the implementation computes the residual with the exact piecewise-linear
cl_of(alpha)and computes the Jacobian from a ±0.5° averaged lookup. Near a polar table corner these are different functions, so Newton is using a surrogate/generalized Jacobian rather than the exact Jacobian of the residual. Please document that qualification here (or make the residual use the same lookup) so users do not rely on the stated exactness.
- `"casadi_newton"`: Newton on the circulation residual with an exact
CasADi Jacobian, globalised by pseudo-transient continuation. Same fixed
src/VSM/core/BodyAerodynamics.py:830
- This broadcast check accepts any shorter repeated array, not just a scalar/one-row legacy input: for example, two identical speeds or two identical direction rows are silently expanded to all panels. Empty inputs also index element 0 before reaching the intended
ValueError. Restrict broadcasting tosize == 1/shape[0] == 1so malformed panel-sized inputs are rejected deterministically.
if wake_speeds.size != self.n_panels and np.allclose(wake_speeds, wake_speeds[0]):
wake_speeds = np.full(self.n_panels, float(wake_speeds[0]))
if wake_units.shape[0] != self.n_panels and np.allclose(wake_units, wake_units[0]):
wake_units = np.ascontiguousarray(np.tile(wake_units[0], (self.n_panels, 1)))
src/VSM/core/BodyAerodynamics.py:1578
- The new attached-trailed force is a primary swept/curved-wing behavior, but the added end-to-end check only uses a straight wing where these contributions cancel. The V3/curved verification tests merely widen tolerances, so a sign or boundary-index error in the nonzero swept-wing force can pass; add a regression asserting the force distribution and global load change for a swept/curved case, including the flag-off result.
if is_with_attached_trailed_vortex_force:
f_at, p_at = self.compute_attached_trailed_vortex_forces(
gamma_new, rho, core_radius_fraction, va_array
src/VSM/core/Solver.py:1518
gamma_loop_type="non_linear"still solves the raw mapgamma - 0.5*|V_inner|*Cl*chord, sois_with_artificial_viscosity=Trueis silently ignored whenever either Broyden solve succeeds. That returns a different, potentially sawtoothed post-stall fixed point thanbase,anderson, andcasadi_newton; apply_regularize_gamma_targetin this residual (or reject this option combination).
_, Umag_array, cl_array = self.compute_aerodynamic_quantities(gamma)
gamma_new = 0.5 * Umag_array * cl_array * self.chord_array
# Residual: difference between the computed and current gamma.
src/VSM/core/Solver.py:814
- The new negative-stall gate is not covered by a negative-angle regression: the added CasADi tests exercise attached and positive post-stall cases, but no test drives
alpha_array < stall_angles_neg. A sign or threshold error in this newly added branch could therefore pass the suite; add a negative-stall case that verifies the gate and regularization are active and agree between the base and Newton paths.
if viscosity_ctx is None or not (
np.any(alpha_array > viscosity_ctx["stall_angles"])
or np.any(alpha_array < viscosity_ctx["stall_angles_neg"])
- Files reviewed: 18/26 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Refactor ratio calculation and gate crossing condition for clarity. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Aligns the solver with three papers: Gaunaa, Li & Pirrung, From thin airfoil theory to finite wings (TORQUE 2026); Li, Gaunaa, Pirrung & Lønbæk, Relaxation limits and artificial viscosity (TORQUE 2026); Gaunaa, Sørensen & Li, A correction model for the effect of spanwise flow on the viscous force (2024). Each departure was found by reading the papers against the code, and each fix is one commit with the reasoning in its message. Figures with descriptions are in
docs/figures/README.md.What changes for users
is_aoa_corrected=True). Magnitudes keep the 3/4-chord angle.Falsegives the previous behaviour.is_with_attached_trailed_vortex_force(default on). Zero net effect on straight wings.relaxation_factor=None), from the Li et al. stability limit; an explicit value is used verbatim. On the V3 kite the base loop converges in 2-4x fewer iterations.drag_induced_trefftz, the far-wake induced drag for uniform inflow.is_smooth_circulation,smoothness_factor,is_artificial_damping,artificial_damping,is_with_simonet_artificial_viscosity,simonet_artificial_viscosity_fva, and thesimonet_*loop types (never read, or imported a module that no longer exists).Fixes, in commit order
233a238Dead stall options removed.741e02dCirculation map Γ = ½ |V_inner| c Cl (paper eq. 4) instead of ½ Umag²/Umagw c Cl. 2-6% on the V3.a6e5cf7Artificial viscosity also gates on negative stall.efbf79cPanel frame and chord perpendicular to the quarter-chord line (CP1). The old frame used the rib chord and a leading-edge-step normal; on the V3 tips the chord-to-span skew reached 0.77. Also the issue behind VortexStepMethod.jl PR #273.9c43c1aProfile drag along the local inner flow (was the global span axis crossed with lift: up to 108° off at the V3 tips), and the other panels' bound vortices included in the quarter-chord flow (curved bound-vortex effect).1300ab7Gaunaa 2024 viscous correction fed the real spanwise flow. Its β never saw the flow before (exactly zero on a straight wing in sideslip, a geometry-only number on the V3) and its drag increment pointed upstream.4e979deKutta-Joukowski force on the attached-trailed legs (paper sec. 3).b4043c8+60ef3f8Adaptive relaxation; the paper's bound is exact for LLT and half for VSM (measured against the fixed-point Jacobian).a404e10Trefftz-plane induced drag.b8cec1bEach ring's frozen wake follows its own panel's apparent velocity (matters only with body rates or distributed inflow).62c53afQuarter-chord directions by default; the direction step restricted to VSM.Evidence
Trefftz check (inviscid, same circulation, on-blade / far-wake induced drag):
V3 kite (CAD geometry, CFD+NeuralFoil polars, 50 panels) against RANS and wind tunnel. The new default sits on the RANS drag in the attached range and in sideslip; the old default overshoots RANS but was closer to the wind tunnel.
Other figures: frame and drag-direction errors (
02), attached-trailed force (04), relaxation (05), wake direction (06), the perpendicular-leg variant that was tried and rejected (07).Tests
Unit suite 155 passed, 5 skipped. Verification cases 5 passed; their thesis-code comparisons were widened to the measured deviation of the new formulation (V3: CL +0.015, CD +0.012 at 8°; curved wing: CL +0.02, CD +0.004), the analytical and CFD comparisons are unchanged. New tests: relaxation limit, Trefftz drag, wake direction, negative-stall gate, geometry oracle projected to the new frame.
Known limitations and open questions