Skip to content

Measure alpha in the plane square to the bound vortex (Gaunaa CP1) - #393

Merged
1-Bart-1 merged 3 commits into
mainfrom
agent/317-alpha-is-built-from-the-full-relative-ve
Sep 30, 2026
Merged

1-Bart-1 merged 3 commits into
mainfrom
agent/317-alpha-is-built-from-the-full-relative-ve

Conversation

@1-Bort-1

@1-Bort-1 1-Bort-1 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

TL;DR

The solver now measures every angle of attack in the plane square to the quarter-chord line, against the chord projected onto that plane. That is Gaunaa, Li & Pirrung 2026, CP1 with Eq. (1), and it is how q_dyn was already built. Before, alpha used the raw chord, which on a swept panel leaves that plane, so alpha and q_dyn were not measured in the same plane.

What changed

One function, inflow_angle(v, y_airf, z_airf) = atan(v·z, v·(y × z)), now forms alpha in all five places that used to compute it separately:

  • panel_inflow
  • the circulation loop's open-coded version in update_gamma_candidate!
  • alpha_geometric in calc_forces!
  • alpha_geometric in calculate_results
  • the aerodynamic-centre angle behind correct_aoa

panel_force_directions turns the inflow about the same projected chord, so the lift direction matches the angle it was read at. SymbolicAWEModels calls panel_inflow and panel_force_directions, so it picks up the change without an edit.

Since #273, z_airf ⟂ y_airf, so y_airf × z_airf is exactly the chord re-normalised in the span-perpendicular plane. Over 1000 random panels at both orientations it agrees to 4.6e-15. Its dot product with the velocity equals that of the span-perpendicular velocity with the same vector, so Eq. (1) needs no separate projection step.

x_airf stays the raw chord. The centre-of-pressure placement and SymbolicAWEModels.compute_aerostruc_loads, which rebuilds panel corners from x_airf, need the physical chord line.

Which chord

The paper never defines e_c. I asked on #317 and you answered "the most physically correct option". The raw chord (option A) would divide the span-perpendicular velocity by a vector outside that plane, which gives an angle in no plane. The projected chord puts alpha, q_dyn and z_airf in one crossflow plane, which is simple sweep theory's.

How much it moves

The table is the largest |Δalpha| from this change alone on top of #273. The numbers come from converged solves on main, with the angle recomputed both ways by a probe (details on #317):

wing max Δalpha where
V3, CFD polars, α 7.4° 1.33° tip panel
ram-air, NeuralFoil fixture, α 10° 3.02° tip panel
:rectangular reference, α 10° 1.23° panel 5
LEI ribs, α 17° 0.27° tip
:curved, :elliptical, pyramid 0 unswept

On #273's 45° swept test panel, v = [1,0,0] now reads −20.097° instead of −14.299°.

No test pins alpha, cl or force on a swept panel. The Oriol Cayon oracle takes the solver's own alpha, so the suite stays green whichever chord is used. The change is unvalidated against a swept, tapered case with a known answer, and the suite has no such case.

Found on the way

Verification

  • Reproduced first: on the swept panel, alpha -0.2496 rad against the crossflow angle -0.3508 rad (−20.097°)
  • New test/panel/test_panel.jl "alpha is measured in the plane square to the bound vortex" and test/solver/test_solver.jl "alpha is the crossflow angle on a swept, tapered wing": red on unchanged src/ (9 and 40 failures), green after. The solver test checks the loop's alpha, alpha_geometric and the correct_aoa angle. The panel test also checks that dir_lift is square to the crossflow velocity. With the raw chord put back into panel_force_directions alone, it fails 4 times (|dir_lift · v⊥| = 0.092–0.094). The loop assertion in the solver test uses atol 1e-6 because lr.alpha_dist comes from the iterate before the last relaxed step, a 2e-8 gap, against a 0.02 rad effect.
  • Green locally after the review round (746e79b): panel, solver, body_aerodynamics, results, flow_curvature, unrefined_dist, forwarddiff. Green before it (8aa6c07): panel, solver, body_aerodynamics, results, unrefined_dist, flow_curvature, viscous_drag_correction, moment_units, wing_directions, stability, verification, forwarddiff, backend_comparison, kite_geometry, yaml_geometry, obj_adapter, plus refinement_validation, wing_geometry, billowing, surfplan, wake and test_VortexStepMethod on the first commit
  • bench.jl: not run in the session, because the root env lacks BenchmarkTools. Aqua passed in the local full suite.
  • Docs build: not run locally, because the docs env has no manifest. CI's Documentation job builds it.
  • Local full suite (agent ci-local, one matrix cell, includes Aqua): PASS on 746e79b (6 min). An earlier run at 09:44Z picked up a half-edited working tree, not a commit, and failed on crossflow_alpha not defined; the committed tree does not have that failure · GitHub CI: PASS on 746e79b (all 6 jobs, Documentation included)
  • Allocations: solve! and gamma_loop! allocate identically on main and branch across VSM/LLT × LOOP/NONLIN × correct_aoa (e.g. 1120 B / 208 B for VSM LOOP). Timing not run: agent bench-start reported the box saturated.
  • Risk: a swept polar lookup that was tuned against the old angle, e.g. kite polars fitted to flight data, now reads up to 3° away at the tips.

Scope

+121 / −142 across 9 files. The net shrink comes from the four open-coded alpha formations collapsing into inflow_angle, the circulation loop dropping three buffers it no longer reads (x_airf_dist, v_normal_dist, v_tangential_dist), three unused cache slots, and the duplicate function. crossflow_alpha in test/test_data_utils.jl is the test oracle both alpha tests share.

Closes #317 · task VortexStepMethod.jl-317

1-Bort-1 and others added 2 commits September 24, 2026 10:36
Every alpha the solver forms (panel_inflow, the circulation loop, the
geometric and aerodynamic-centre angles) and the inflow direction that turns
the force now go through inflow_angle, which dots the velocity with the chord
projected onto the plane square to y_airf: Gaunaa, Li & Pirrung (2026), CP1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@1-Bort-1 1-Bort-1 added agent:running Agent task state agent:ci Agent task state and removed agent:running Agent task state labels Sep 24, 2026

@1-Bort-1 1-Bort-1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Independent review (advisory)

Verdict: APPROVE WITH COMMENTS · 3 inline, 0 off the diff

Good

  • One inflow_angle replaces five separate alpha formations, as the card says; the diff covers panel_inflow, update_gamma_candidate!, both alpha_geometric sites and update_effective_angle_of_attack!
  • Removing solver cache slots 9 and 10 is safe: rg finds no other solver.cache[9] or [10], and the body_aero.cache[9]/[10] uses are a different struct
  • The signature change to update_effective_angle_of_attack! is internal: it is not exported, and the only caller outside the diff, test/bench.jl, imports it without calling it
  • bench.jl's calculate_results call still matches, because the PR keeps that function's signature
  • panel_force_directions turns the inflow about the same projected chord that alpha is read against, so lift direction and polar angle match, in line with the card's plan
  • rg for a row-to-SVector helper finds nothing before this PR, so matrix_row duplicates nothing; it is also listed in private_functions.md for the docs build
  • The tests name the behaviour they protect and pin the reproduced −20.097° case; the card says they fail on unchanged src/
  • The card is candid that the change is unvalidated and that no swept case has a known answer, and CHANGELOG records the user-visible shift

Not good

  • src/body_aerodynamics.jl:550 — After this rewrite nothing reads body_aero.cache slots 2–4, yet BodyAerodynamics still allocates 15. The solver cache was trimmed from 10 to 8; this one was left behind, contrary to §2 on removing what a change stops using.
  • src/body_aerodynamics.jl:840 — This line drops the last use of calculate_results' x_airf_dist and z_airf_dist, so the exported function now takes two dead positional arguments. The breaking removal is deferred, but no issue is filed to track it, so it will be forgotten.
  • test/solver/test_solver.jl:343 — crossflow_alpha and induced are nested functions holding logic inside the testset (§6). The chord-in-plane projection also repeats test_panel.jl:220; hoisting both into a shared test helper keeps the oracle in one place.
  • The card lists three finds it did not act on and links no issue for any of them: the dead args on calculate_results, the duplicate calculate_relative_alpha_* bodies, and :curved not converging (§5 wants each fixed or filed)
  • The duplicate calculate_relative_alpha_and_velocity / _relative_velocity bodies both call the changed panel_inflow, so they are one step out and in scope under §1; deleting the unexported one costs nothing
  • matrix_row is a generic helper defined in solver.jl but also used in body_aerodynamics.jl; it fits better next to inflow_angle or with the other small utilities
  • The panel test builds along_span from axes.y_airf and not from oriented.y_airf; this is harmless because both lie along the span, but it reads like a slip
  • Nothing tests the panel_force_directions change directly; the solver test checks only the angles, never that dir_lift is perpendicular to the projected inflow

claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.

Comment thread src/body_aerodynamics.jl
# Direct angle calculation without temporary arrays
@inbounds for i in 1:n
alpha_corrected[i] = atan(v_normal[i], v_tangential[i])
@inbounds for (i, panel) in enumerate(body_aero.panels)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

MINOR: After this rewrite nothing reads body_aero.cache slots 2–4, yet BodyAerodynamics still allocates 15. The solver cache was trimmed from 10 to 8; this one was left behind, contrary to §2 on removing what a change stops using.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 746e79b: the cache is 12 slots, renumbered, nothing unused.

Comment thread src/body_aerodynamics.jl
inv_va / z_norm
alpha_geometric[i] = atan(-v_normal, -v_tangential)
end
alpha_geometric[i] = iszero(va_dist[i]) ? NaN : inflow_angle(panel.va_vec,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

MINOR: This line drops the last use of calculate_results' x_airf_dist and z_airf_dist, so the exported function now takes two dead positional arguments. The breaking removal is deferred, but no issue is filed to track it, so it will be forgotten.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Filed #394. It's exported, so the removal is a breaking change of its own, and #94 rewrites the same function.

Comment thread test/solver/test_solver.jl Outdated
va = [15.0, 1.0, 1.5]
set_va!(body_aero, va)

function crossflow_alpha(panel, velocity)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

MINOR: crossflow_alpha and induced are nested functions holding logic inside the testset (§6). The chord-in-plane projection also repeats test_panel.jl:220; hoisting both into a shared test helper keeps the oracle in one place.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 746e79b: both tests now take crossflow_alpha from test_data_utils.jl, and the induced velocity is inlined as AIC[i, :, :]' * gamma.

@1-Bort-1 1-Bort-1 added agent:queued Agent task state agent:running Agent task state and removed agent:ci Agent task state agent:queued Agent task state labels Sep 24, 2026
@1-Bort-1

1-Bort-1 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Local full suite: PASS (5 min, Julia 1.13.0, one cell of the matrix)

…ection

BodyAerodynamics.cache drops the three slots nothing reads since alpha
went through inflow_angle, matrix_row moves next to inflow_angle, and the
unused duplicate calculate_relative_alpha_and_velocity goes. The panel
test now checks that dir_lift is square to the crossflow velocity, and
both alpha tests take crossflow_alpha from test_data_utils.jl.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.55172% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/body_aerodynamics.jl 94.11% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@1-Bart-1
1-Bart-1 merged commit 1ec0081 into main Sep 30, 2026
7 checks passed
@1-Bart-1
1-Bart-1 deleted the agent/317-alpha-is-built-from-the-full-relative-ve branch September 30, 2026 11:45
@1-Bort-1 1-Bort-1 removed the agent:review Agent task state label Sep 30, 2026
@1-Bort-1 1-Bort-1 added the agent:done Agent task state label Sep 30, 2026
1-Bort-1 added a commit that referenced this pull request Sep 30, 2026
main's #393 rewrote calculate_results' angle of attack with inflow_angle;
this branch deletes calculate_results, so the removal stands. With it gone,
BodyAerodynamics.cache keeps its one used buffer and the write-only
VSMSolution._x_airf_dist goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1-Bort-1 added a commit that referenced this pull request Sep 30, 2026
Takes #393's inflow_angle in update_effective_angle_of_attack! and keeps this
branch's own-bound-only aero-centre matrix; BodyAerodynamics.cache holds the
three buffers still read (induced velocity, panel areas, AIC inflow).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:done Agent task state

Projects

None yet

Development

Successfully merging this pull request may close these issues.

alpha is built from the full relative velocity, not its component perpendicular to the quarter-chord line

2 participants