Repository navigation
Measure alpha in the plane square to the bound vortex (Gaunaa CP1) - #393
Conversation
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
left a comment
There was a problem hiding this comment.
Independent review (advisory)
Verdict: APPROVE WITH COMMENTS · 3 inline, 0 off the diff
Good
- One
inflow_anglereplaces 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 thebody_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_resultscall still matches, because the PR keeps that function's signature panel_force_directionsturns 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_rowduplicates 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 readsbody_aero.cacheslots 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 ofcalculate_results'x_airf_distandz_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_alphaandinducedare 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 duplicatecalculate_relative_alpha_*bodies, and:curvednot converging (§5 wants each fixed or filed) - The duplicate
calculate_relative_alpha_and_velocity/_relative_velocitybodies both call the changedpanel_inflow, so they are one step out and in scope under §1; deleting the unexported one costs nothing matrix_rowis a generic helper defined in solver.jl but also used in body_aerodynamics.jl; it fits better next toinflow_angleor with the other small utilities- The panel test builds
along_spanfromaxes.y_airfand not fromoriented.y_airf; this is harmless because both lie along the span, but it reads like a slip - Nothing tests the
panel_force_directionschange directly; the solver test checks only the angles, never thatdir_liftis 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.
| # 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed in 746e79b: the cache is 12 slots, renumbered, nothing unused.
| 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, |
There was a problem hiding this comment.
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.
| va = [15.0, 1.0, 1.5] | ||
| set_va!(body_aero, va) | ||
|
|
||
| function crossflow_alpha(panel, velocity) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed in 746e79b: both tests now take crossflow_alpha from test_data_utils.jl, and the induced velocity is inlined as AIC[i, :, :]' * gamma.
|
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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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>
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>
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_dynwas already built. Before, alpha used the raw chord, which on a swept panel leaves that plane, so alpha andq_dynwere 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_inflowupdate_gamma_candidate!alpha_geometricincalc_forces!alpha_geometricincalculate_resultscorrect_aoapanel_force_directionsturns the inflow about the same projected chord, so the lift direction matches the angle it was read at.SymbolicAWEModelscallspanel_inflowandpanel_force_directions, so it picks up the change without an edit.Since #273,
z_airf ⟂ y_airf, soy_airf × z_airfis 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_airfstays the raw chord. The centre-of-pressure placement andSymbolicAWEModels.compute_aerostruc_loads, which rebuilds panel corners fromx_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 putsalpha,q_dynandz_airfin 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):
:rectangularreference, α 10°:curved,:elliptical, pyramidOn #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
calculate_relative_alpha_and_velocityinsrc/panel.jlhad the same body ascalculate_relative_alpha_and_relative_velocityand no callers. It is unexported, so I deleted it.BodyAerodynamics.cachegoes from 15 slots to 12. Nothing reads the three buffers the old alpha code used.calculate_resultsstill takesx_airf_distandz_airf_distand reads neither. It is exported, so removing them is a breaking change of its own. The :curved reference wing does not converge: LOOP returns NaN gamma at 5.7° and 10° #395, the:curvedreference wing does not converge (LOOP FAILURE, NaN gamma at α 5.71° and 10°); its test never solves.Verification
-0.2496rad against the crossflow angle-0.3508rad (−20.097°)test/panel/test_panel.jl"alpha is measured in the plane square to the bound vortex" andtest/solver/test_solver.jl"alpha is the crossflow angle on a swept, tapered wing": red on unchangedsrc/(9 and 40 failures), green after. The solver test checks the loop's alpha,alpha_geometricand thecorrect_aoaangle. The panel test also checks thatdir_liftis square to the crossflow velocity. With the raw chord put back intopanel_force_directionsalone, it fails 4 times (|dir_lift · v⊥| = 0.092–0.094). The loop assertion in the solver test uses atol 1e-6 becauselr.alpha_distcomes from the iterate before the last relaxed step, a 2e-8 gap, against a 0.02 rad effect.bench.jl: not run in the session, because the root env lacks BenchmarkTools. Aqua passed in the 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 oncrossflow_alphanot defined; the committed tree does not have that failure · GitHub CI: PASS on 746e79b (all 6 jobs, Documentation included)solve!andgamma_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-startreported the box saturated.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_alphaintest/test_data_utils.jlis the test oracle both alpha tests share.Closes #317 · task
VortexStepMethod.jl-317