BeamDyn: rotating-frame-consistent linearization Jacobians when RotStates=True#3396
Closed
mayankchetan wants to merge 6 commits into
Closed
BeamDyn: rotating-frame-consistent linearization Jacobians when RotStates=True#3396mayankchetan wants to merge 6 commits into
mayankchetan wants to merge 6 commits into
Conversation
…RotStates=T BD's linearized states are a frozen root-aligned inertial snapshot but were flagged RotatingFrame=T, so MBC3's Omega-corrections injected spurious spin-softening (tower-mode 'dip', w^2 = w0^2 - alpha*Omega^2, alpha ~ blade mass fraction). Re-express dXdx/dXdu/dYdx in the true rotating frame (A_r = S A S^-1 + blkdiag(-wt), B_r = S B, C_r = C S^-1, with S = [[I,0],[-wt,I]] per paired (q, dqdt) state triplet and wt built from the root angular velocity in the frozen BD basis) so the exported metadata is honest. Identity at standstill (.lin numerics bit-identical). Replaces the rotation-only transform removed in 5fbd380 (which was ~identity at the OP and lacked the velocity-mixing and transport terms). The transform applies only on the glue linearization path: a new optional IsLin flag threads from ModGlue_Linearize through FAST_JacobianPInput / FAST_JacobianPContState into BD. Solver (BuildJacobianTC) and AeroMap calls are unflagged and keep the raw frozen-frame Jacobians their iterations require (the linearization-only invariant that root orientation matches the frozen reference frame does not hold mid-step, and the integrator's Newton matrix must stay consistent with its residual). Guards: fatal on state-layout or re-anchoring-invariant violation; warnings for nonzero root angular acceleration (omega-dot transport term omitted) and large WM rotation states (first-order transform). Acceptance (NREL 5-MW BD 0-12 rpm, wide-OutFmt baseline): unpatched SS branch droops 2.29%; patched flat to 0.015%, matches python frame-fix expectation within 0.02% and ElastoDyn within 0.6% at every speed.
The root-orientation-vs-GlbRot invariant check was fatal at >1e-8, which trips on healthy linearization snapshots (deviation between the per-step re-anchoring input and the lin-time input is solver-convergence noise; observed >1e-8 on IEA-15 at 4 rpm while NREL 5-MW stays below). The transform basis remains exact regardless (omega is mapped through GlbRot, the frame the states are actually anchored to); the deviation only bounds the S-basis error. Now: warn above 1e-6 (reporting the measured value), fatal only above 1e-3 (gross misalignment = states not root-aligned at all, e.g. a caller outside the linearization snapshot path). 5-MW acceptance checker re-run after the change: PASS, unchanged (SS flatness 0.015%).
…ion snapshot Gate the RootDev warning behind DoTransport (the dXdx call) so it fires once per snapshot per BD instance instead of on all three transformed blocks; the fatal gross-misalignment check remains unconditional. Also fill the IEA-15 fleet-verdict row (patched SS flatness 0.111%, max dev vs python reference 0.022%). 5-MW acceptance checker re-run: PASS unchanged (0.015%).
…till no-op, no silent return under root acceleration Two edge-case orderings found by an adversarial review of the branch: - The re-anchoring invariant guard ran before the standstill check, so a parked linearization with a grossly mis-anchored root would abort fatally even though every transform operation is an exact identity at omega = 0. The standstill early-return now precedes the guard, making the advertised standstill no-op unconditional. - The standstill early-return could skip the omega-dot warning, so a snapshot taken at exactly zero speed while the root is accelerating (e.g. LinTimes during startup) returned silently despite the omitted -skew(alpha) transport term being nonzero there. The zero-speed path now warns before returning. No change to the transform itself; the 7-speed 5-MW acceptance checker passes with identical numbers (SS flatness 0.015%). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CyaECKUrqvcsDCu78FAXi1
Contributor
Author
|
Follow-up commit
Re-verified after the change: the 7-speed 5-MW acceptance checker passes with identical numbers (tower-SS flatness 0.015 %). |
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.
Feature or improvement description
Fixes #3394: with
RotStates = True(the default), BeamDyn's linearization Jacobians are afrozen root-aligned inertial snapshot, yet the
.linrows/columns are flaggedRotatingFrame = T— so MBC3-based postprocessors inject spurious spin-softening(
f² = f₀² − αΩ²) into speed-independent branches (e.g. the 5-MW tower side-side droops2.29 % at 12 rpm; the IEA-22 droops ~8 % at rated).
This PR makes the returned Jacobians frame-consistent with the flag. A new
BD_JacRotFrameinmodules/beamdyn/src/BeamDyn.f90applies, per paired(q, dq/dt)statetriplet (pairing offset Nx/2):
to
dXdx/dXdu/dYdx, where ω is the full rootRotationVel3-vector mapped into thefrozen
GlbRotbasis (a scalar Ω is insufficient — precone alone puts −0.0548 rad/s in z at12 rpm on the 5-MW). The transform is exactly identity at standstill. This is not a revert
of the PR #2063 code removed in
5fbd380f8: that code was ≈identity at the operating pointand lacked the transport (Coriolis) term, so it never produced rotating-frame Jacobians.
Design choice — where the transform lives: it is gated by a new optional
IsLinflagthreaded
ModGlue_Linearize → FAST_JacobianPInput / FAST_JacobianPContState (FAST_Funcs.f90) → BD, so it applies only at linearization snapshots. The tight-coupling solver(
BuildJacobianTC) and AeroMap calls are unflagged and keep the raw frozen-frame Jacobianstheir Newton iterations require. All existing callers use keyword arguments; no C-binding,
Simulink, or module-driver interfaces are affected, and no registry regeneration is needed.
An alternative placement — doing the transform glue-side in
FAST_ModGlue.f90and leaving themodule API flag-free — is equally workable; happy to rework to that if maintainers prefer.
Runtime guards: fatal on state-layout-invariant violation or gross re-anchoring deviation
(>1e-3), warning (once per snapshot) on measurable re-anchoring deviation (>1e-6), ω̇ ≠ 0, or
|WM parameters| > 0.2 (first-order rotation-composition limit).
Known limitation (documented in #3394): floating platforms — platform/yaw rates fold into the
root ω, where MBC3's scalar-Ω assumption independently breaks down anyway.
Related issue, if one exists
Fixes #3394. Companion defect (independent, also corrupts BD Campbell work): #3395, fixed by
PR #3393 (
.linfull-precision writer) — the two PRs are independent but were isolated in thesame investigation, and verification decks here neutralize #3395 via
OutFmt "ES18.9E3".Impacted areas of the software
modules/beamdyn/src/BeamDyn.f90— newBD_JacRotFrame, applied inBD_JacobianPInput/BD_JacobianPContStatewhenp%RotStates .and. IsLin; deadRotateStatesdeclarations removed.modules/openfast-library/src/FAST_Funcs.f90,FAST_ModGlue.f90— thread the optionalIsLinflag fromModGlue_Linearizeonly.Additional supporting information
Verification (double precision, dev @
0160d29a0, wide-format decks):matches an independent python-side transform of the unpatched matrices to ≤0.02 % and the
ElastoDyn reference to ≤0.6 %; all damping ratios positive.
reference ≤0.022 % per point; SS flatness 0.111 % / 0.029 % / 0.026 % / 0.379 % — the
IEA-22 residual equals the python reference's own 0.361 % (second-order WM/prebend effect,
not introduced here; raw unpatched droop is ~8 %).
.linnumerics bit-identical patched vs unpatched (timestamp-only diff).precone z-component (instrumentation removed).
Generative AI usage
Developed with Anthropic Claude (Claude Code) under human direction; independently
whole-branch reviewed before submission.
Co-authored-by: Anthropic Claude claude@anthropic.com
Test results, if applicable
r-test impact: baselines change for
5MW_Land_BD_Linear,5MW_Land_BD_Linear_Aero, andDamped_Beam_Rotating(the fix working — rotating-case Jacobians change by design);Damped_Beam_Rotatingruns clean on the patched binary; standstill beam cases are identicaland all
CompElast = 1cases are unaffected. Opened as draft until the companion r-testbaseline-regeneration branch is up; can regenerate on request or follow maintainer process.