stability_derivatives gives roll, pitch and yaw rate derivatives about solver.reference_point - #356
Conversation
set_va!(body_aero, va, omega; reference_point) gives each panel va - omega × (control_point - reference_point). The point is stored on BodyAerodynamics so reinit!, the omega setter and the ForwardDiff shadow in linearize keep it; it starts at the origin. set_va!(body_aero, settings) passes condition.yaw_rate as omega about body z instead of dropping it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y-about-the-origin' into agent/345-stability-derivatives-gives-roll-pitch-a # Conflicts: # CHANGELOG.md # src/body_aerodynamics.jl
The omega columns of linearize, scaled to p̂ = pb/2V, q̂ = q c_ref/2V and r̂ = rb/2V, with the body turning about solver.reference_point. trim_angle's bracketing solves turn about the same point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1-Bort-1
left a comment
There was a problem hiding this comment.
Independent review (advisory)
Verdict: APPROVE WITH COMMENTS · 2 inline, 0 off the diff
Good
- The rate columns follow the plan in the card:
linearizeruns over[va; omega]withomega_idxs=4:6, and columns 4..6 are scaled by 2V/b, 2V/c_ref and 2V/b (src/stability.jl:18-30). - The pivot reaches both AD paths: FiniteDiff calls
set_va!onbody_aeroitself, which keeps its storedreference_point, andmake_dual_shadowcopiesreference_pointinto the dual body (checked inlinearizeat src/solver.jl:1297-1320). - The rate test checks against central differences of
solve!about the same pivot, with a guard that no column is zero, so a missing or mis-scaled column fails it. - The trim regression test solves again at the returned alpha about the reference point and checks CMy against the slope, which pins the pivot bug the card describes.
set_va!now has one inflow formula,va - omega × (control_point - reference_point), and areference_pointsetter; the oldall(iszero, omega)branch and the per-wing index loop are gone.- The card says why c_ref and not S/b is used for q̂, and names the difference from Python.
- The changed src and test files have no line over 92 characters (grep for
.{93,}).
Not good
src/stability.jl:17—stability_derivativespermanently overwritesbody_aero.reference_pointwithsolver.reference_point, and only the test says so; the docstring says 'leavesbody_aeroat this inflow'. A caller who set their own pivot will see laterset_va!/reinit!calls turn about a different point with no warning.test/solver/test_stability.jl:64—coeffs_at_rateis a nested closure with logic that nearly repeats the file's top-levelcoeffs_athelper (set_va!, solve!, stack coefficients). Extendingcoeffs_atwithomega/reference_pointkeywords gives one helper and follows §2 and §6.- The card does not say what was searched for before adding the rate scaling and the new test helper (§2).
- The
stability_derivativesdocstring sentence 'dp, dq, dr with respect to the body rates about x, y and z as p̂ = …' is hard to parse; 'with respect to p̂ = pb/2V, q̂ = …, r̂ = …' would say it directly. - V is
wind_speedin the p̂/q̂/r̂ scaling, but the coefficients are normalised by the reference speed built from the panel inflow. The card flags this as a risk, but neither the docstring nor the CHANGELOG mentions it. pitch_moment_coeffhas the same pivot-writing side effect asstability_derivatives, so everythingtrim_angletouches changesbody_aero.reference_pointtoo.- The ForwardDiff testset builds a new
BodyAerodynamics([wing])on every iteration whilebody_aeroalready exists. This is cheap, but it shareswingwithbody_aerowithout saying so. - In the ForwardDiff test,
turnsreuses the outeromegafor its first entry and then names the loop variableomega_op, so two omegas are in scope.
claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.
| va = apparent_wind(alpha, beta, wind_speed) | ||
| jac, results, converged = linearize(solver, body_aero, va; | ||
| theta_idxs=nothing, va_idxs=1:3, aero_coeffs=true, kwargs...) | ||
| set_va!(body_aero, va, body_aero.omega; reference_point=solver.reference_point) |
There was a problem hiding this comment.
MINOR: stability_derivatives permanently overwrites body_aero.reference_point with solver.reference_point, and only the test says so; the docstring says 'leaves body_aero at this inflow'. A caller who set their own pivot will see later set_va!/reinit! calls turn about a different point with no warning.
| @test derivatives.converged | ||
| @test body_aero.reference_point == reference_point | ||
|
|
||
| function coeffs_at_rate(rate) |
There was a problem hiding this comment.
MINOR: coeffs_at_rate is a nested closure with logic that nearly repeats the file's top-level coeffs_at helper (set_va!, solve!, stack coefficients). Extending coeffs_at with omega/reference_point keywords gives one helper and follows §2 and §6.
|
Local full suite: PASS (7 min, Julia 1.12.7, one cell of the matrix) |
…igin Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…agent/345-stability-derivatives-gives-roll-pitch-a Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y-about-the-origin' into agent/345-stability-derivatives-gives-roll-pitch-a # Conflicts: # CHANGELOG.md # src/body_aerodynamics.jl
…ty-derivatives-and' into agent/345-stability-derivatives-gives-roll-pitch-a # Conflicts: # src/body_aerodynamics.jl # src/stability.jl # test/solver/test_stability.jl
TL;DR
stability_derivativesnow also returnsdp,dqanddr: the derivatives of CFx..CMz with respect to p̂ = pb/2V, q̂ = q c_ref/2V and r̂ = rb/2V, taken fromlinearize's omega columns with the body turning aboutsolver.reference_point. Rate derivatives are only meaningful when the body turns about the same point the moments are taken about, which #353 makes possible.What changed
stability_derivativesstoressolver.reference_pointas the pivot onbody_aero(set_va!(...; reference_point)from set_va! turns the body about a stored reference_point, and set_va!(body_aero, settings) applies yaw_rate #353), linearises in[va; omega]atbody_aero.omega, and scales the omega columns by 2V/b, 2V/c_ref and 2V/b, with b the wing span and Vwind_speed.coeffs_at_angles, whichtrim_anglebrackets with, turns aboutsolver.reference_pointtoo. Before, a pitching body was trimmed about whatever pivotbody_aeroheld: withomega = [0, 0.5, 0]andreference_point = [1, 0, 0], the returned trim at α = −1.12° had CMy 3.9e-6 turning about the origin and −0.083 turning about the reference point. It is now 1.3e-5, inside the bisection tolerance.Choice of chord for q̂
The Python
compute_rigid_body_stability_derivativesnormalises q̂ with c = S/b while its moment coefficients use the max chord. Here q̂ usesbody_aero.c_ref(max panel chord), the same length the moment coefficients are divided by, so one chord runs through every pitch term. For a tapered or curved kitedqtherefore differs from Python's by the factor c_ref/(S/b).Pivot matters
Same wing, α = 4°, β = 3°, reference point [0.25, 0.5, 0.1]: dCFz/dq (raw, per rad/s) is 0.1087 turning about the reference point against 0.1654 turning about the origin, and dCMx/dp is −0.510 against −0.456.
Stack
Base is #346. The branch also merges #353's head, because the pivot lives there; #353's commits show in this diff until it merges. Where the two meet,
set_va!(body_aero, settings)takes #346'sapparent_windform with #353'syaw_rateomega, and #346'scoeffs_at_angles(which now feeds both the trim bracketing and the tests) turns aboutsolver.reference_point.Depends-On: #353
Verification
trim_anglepivot bug was reproduced first: CMy −0.083 at the returned trimtest/solver/test_stability.jlred before, green after (juliaserver): new "rate derivatives match central differences of solve! about reference_point" errored on missingdp/dq/drand failed onbody_aero.reference_point == [0,0,0], now 6/6. Central differences are over omega = [0.1, −0.05, 0.08] ± 1e-4 at rtol 1e-4. New "pitching body: trim turning about the reference point" failed at0.0835 < 1e-5·|slope|, now 8/8. The existing α/β testset passes 5/51a38c0b(merged with Add stability_derivatives (angle of attack, sideslip) and trim_angle, built on linearize #346b50a2b5and set_va! turns the body about a stored reference_point, and set_va!(body_aero, settings) applies yaw_rate #3536ecd77a), green in a fresh juliaserver session:test_stability.jl22/22 (incl. Add stability_derivatives (angle of attack, sideslip) and trim_angle, built on linearize #346's NONLIN and SolveFailure trim testsets) ·test_body_aerodynamics.jl4888/4888 ·test_forwarddiff.jl10/101a38c0b(makedocs without deploy)1a38c0b: PASS (8 min, Julia 1.12.7, one matrix cell) · GitHub CI: Documentation PASS; the test matrix is skipped on this pull request because its base is Add stability_derivatives (angle of attack, sideslip) and trim_angle, built on linearize #346's branch, notmaindp/dq/drusewind_speedfor V while the solver'sq_refuses a reference speed built from the panel inflow distribution, so at nonzeroomegathe p̂ scaling and the coefficients' dynamic pressure use slightly different speeds.Scope
Against a plain merge of #346 and #353: +72 / −55 in
src/stability.jl,test/solver/test_stability.jl,CHANGELOG.md, andsrc/body_aerodynamics.jl, where the last is only theset_va!(body_aero, settings)resolution. The rest of the diff against #346 is #353. Stack: 2/2 on #346.Closes #345 · task
VortexStepMethod.jl-345