Skip to content

F/reverse rotor - #3436

Draft
andrew-platt wants to merge 25 commits into
OpenFAST:devfrom
andrew-platt:f/reverseRotor
Draft

F/reverse rotor#3436
andrew-platt wants to merge 25 commits into
OpenFAST:devfrom
andrew-platt:f/reverseRotor

Conversation

@andrew-platt

Copy link
Copy Markdown
Collaborator

Draft

Feature or improvement description
Enabling the MirrorRotor option in OpenFAST. Complete description coming soon.

Current development status

Phase Scope Status
0 Scope, findings, definition of done ✅ Complete
1 AeroDyn + ElastoDyn, steady wind, no controller ✅ Complete
2 BeamDyn blades ✅ Complete
3 Controller in the loop (ServoDyn) ✅ Complete
4 System integration (floating, SED/ADsk, multi-rotor) ⬜ Not started
5 Documentation consolidation + implementation report 🟡 Partial
6 Remaining coverage (linearization, FAST.Farm, OLAF, …) ⬜ Not started

Phase detail

Step Description Status
1.0 Isolated AeroDyn/SED driver checks ✅ 23/23 sweep, SED canary
1.1 Guard rails and baseline hygiene
1.1a HSS brake + tight coupling guard ✅ Already upstream
1.2–1.3 CW + mirrored steady cases ✅ Registered, passing
1.4 Code changes (AD, ED, SED, glue)
1.5 Iterate to a clean mirror ✅ 9/9 condition matrix
1.6 Documentation
2.1 BeamDyn cases ✅ Registered pair
2.2 The 6×6 transform ✅ + 4 unit tests
2.3 Verification ladder ✅ 13/13 matrix, ratio 1.000
3.1 CW presentation layer
3.2 Mirrored inflow (MirrorY.bts) ✅ Verified to 8.9e-9
3.3 Cases (brake + turbulent ROSCO ×2) ✅ Registered, passing
4.1 Floating system
4.2 Simplified ElastoDyn + AeroDisk ⬜ Includes SED alias split
4.3 Two-rotor mirrored case
4.4 Existing multi-rotor cases
5.1 User documentation ✅ Written, needs final pass
5.2 Implementation report
5.3 Final home for the tooling ⬜ Still in scratch
6.1 Linearization / MBC3 ⬜ Guard rail active
6.2 FAST.Farm
6.3a OLAF rotor-level triage ⬜ Guard rail active
6.3b OLAF / FVW full work ⬜ Guard rail active
6.4 AeroAcoustics ⬜ Guard rail active
6.5 C++ / ExtLoads / ExtInflow, AeroMap, StrucCtrl
6.6 openfast_io round-trip

Verification

Check Result
Full regression (Release, double) ✅ 76/79 — 3 known-temperamental, pre-existing
ctest -L mirrorrotor ✅ 7/7
Committed mirror pairs ✅ 5/5 clean
Condition matrix ✅ 13/13
BeamDyn unit tests ✅ 4/4

Outstanding non-blockers

Item Note
OutListParameters.xlsx You apply by hand; list in DECISIONS.md
Tooling location 18 scripts still in tools
Furling Now operational with a warning; no mirror pair verifies it

Created 3 todos

Test results, if applicable

  • r-test branch merging required

andrew-platt and others added 21 commits August 18, 2026 18:25
Points at r-test commit baef6e291, which sets MirrorRotor to F in the
three multi-rotor cases that previously set "F T". The flag is
currently a parsing-only stub (FAST_Subs.f90:3080-3083), so results
are unchanged.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ment-mismatch)

Debug builds with gfortran fail to compile fftpack5.1.f with 64 hard errors:

  Error: Type mismatch in argument 'dsum' at (1); passed REAL(4) to REAL(8)
  Error: Type mismatch in argument 'c' at (1); passed REAL(4) to COMPLEX(4)

These mismatches are intentional: FFTPACK 5.1 uses legacy Fortran 77 type
punning and is deliberately compiled with -fno-default-real-8 while the rest
of the build uses -fdefault-real-8, so its callers can pass explicit SiKi/R4Ki
arrays. -fallow-argument-mismatch is already applied to downgrade them to
warnings.

However, Debug builds add -pedantic, which promotes the argument mismatches
back to errors and overrides -fallow-argument-mismatch. Appending
-Wno-pedantic to this file's compile flags restores the intended behaviour.
The suppression is scoped to this single legacy source; Release builds are
unaffected as they do not pass -pedantic.

Verified on gfortran 12.2.0 with
  cmake -DDOUBLE_PRECISION=On -DCMAKE_BUILD_TYPE=Debug -DGENERATE_TYPES=On ..
Isolation test on the exact compile line:
  -pedantic                            -> 64 errors
  -pedantic -std=legacy                -> 64 errors
  -pedantic -Wno-argument-mismatch     -> 64 errors
  -pedantic -Wno-pedantic              ->  0 errors (object builds)

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds the first working slice of counter-clockwise rotor support. A new
RotInitInputType flag MirrorRotor selects a RotParameterType RotDir of
-1, which is applied only at the AeroDyn module boundary. BEMT, unsteady
aero, DBEMT and AirfoilInfo are untouched and continue to solve the
equivalent clockwise problem, so airfoil polars are used verbatim and no
polar transformation is needed.

Applied on the way in (SetInputsForBEMT): omega, psi_s, theta, toeAngle,
Vy and omega_z. The cant angle is deliberately not flipped. Blade twist
and swept-AC offset are negated during init, after the setCantAngle loop,
because setCantAngle derives BlCrvAng from BlTwist and the cant must not
follow the mirror.

Applied on the way out: the y-component of forces and the x and z
components of moments, following v' = Sv for true vectors and w' = -Sw
for pseudovectors with S = diag(1,-1,1).

Several write-outputs mixed the two frames and are corrected here. Rotor
and blade power used the clockwise omega against a mirrored Mxh, which
produced negative power. BNFl, BNFd, BNFn and BNFt combined mirrored
forces with clockwise phi and theta. BNVIndy multiplied two clockwise
quantities. BNCy, BNCt and BNCm now carry the mirror. RtTSR keeps the
clockwise omega, which is the value that stays positive. The free-wake
copy of the blade output block is left alone; OLAF remains guard-railed.

The AeroDyn driver gains a per-turbine MirrorRotor(i) input and mirrors
the prescribed hub kinematics, standing in for ElastoDyn.

Verified against a paired clockwise/mirrored NREL 5MW driver case: every
one of the 150 output channels is exactly equal, exactly sign-flipped, a
mirrored angle, or below the numerical noise floor, with zero residual.
BEMT internals including Alpha, Phi, Vrel, Cl, Cd and the induction
factors are bit-identical between the two runs.

MirrorRotor defaults to false and the clockwise path is unchanged; the
AeroDyn driver regression cases reproduce their baselines bit-for-bit.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A condition sweep over the AeroDyn driver (pitch, wind speed, tip-speed
ratio, propeller-brake, tilt, precone, shear, yaw, both BEM models, DBEMT
and four unsteady-aero models) exposed four sign errors that the original
axisymmetric zero-pitch check could not reach.

Blade pitch was not mirrored by the driver. Blade twist is negated when
the inputs are read, so with a non-zero pitch the two ended up in opposite
frames and the local angle of attack was wrong. The driver stands in for
ElastoDyn here, so it now applies RotDir to the prescribed pitch and its
rate, matching p%BlPitch = p%RotDir * BlPitch.

The skew-aligned disk frame used to measure the azimuth fed to the skewed
wake correction is built from cross(V_diskAvg, x_hat_disk). That is a
pseudovector, so under the mirror the frame changed handedness and the
azimuth came out as pi minus azimuth rather than negated. The correction
was then applied with the wrong sign, moving the axial induction the wrong
way around the disk. RotDir is now applied to that vector, which leaves
the azimuth already in the clockwise-equivalent frame, so psi_s no longer
needs its own factor. This was invisible without Skew_Mod = 1 because
psi_s is not used elsewhere.

The polar BEM path builds psiSkewOffset from a second cross product and
needed the same treatment; applying RotDir to z_vec makes that triad
mirror as S R S, after which theta(1) simply negates.

Finally the reported skew angle is placed in the rotor's own convention.
Its sign is cosmetic, since chi is formed from abs(chi0), and this makes
the two BEM models agree with each other under the mirror.

All 23 sweep variants now pass: every channel is identical, exactly
sign-flipped, or below the numerical noise floor. The clockwise path is
unchanged and the AeroDyn driver regression cases stay bit-identical.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds MirrorRotor support to SimplifiedElastoDyn, the drivetrain half of
the counter-clockwise rotor work. As in AeroDyn the mirror lives at the
module boundary: the azimuth and rotor speed states are physical, while
inputs and outputs stay in the rotor's own convention so a mirrored rotor
turning its design direction still reads positive.

RotDir is applied to the initial azimuth and rotor speed, to the
generator torque on its way in, to the commanded blade pitch where it is
turned into blade root geometry, and to the reported azimuth, speed,
acceleration, torque and power. The HSS brake torque is deliberately left
alone: it is already signed by the direction the shaft is turning, so a
blanket sign flip would make the brake drive the rotor instead of
stopping it.

That last point exposed a latent bug. SED_AB4 and SED_ABM4 both sign the
brake with SIGN(HSSBrTrqC, qdt), but SED_RK4 assigned the commanded
torque directly, so the brake always acted in one fixed direction
regardless of which way the shaft was turning. A clockwise rotor spinning
forwards never notices, but ABM4 uses RK4 for startup, so a mirrored
rotor was accelerated by its own brake for the first two steps before the
multistep scheme took over and corrected the sign. RK4 now matches the
other two integrators.

Verified with a brake canary: the HSS brake case run clockwise and
mirrored now produces bit-identical azimuth, rotor speed, rotor
acceleration, generator speed and generator acceleration, and both runs
decelerate 12 rpm to rest.

The clockwise path is unchanged. sed_test_freewheel is bit-identical.
sed_test_HSSbrk is identical while the rotor is turning and differs only
after it has stopped, where the brake sign is now taken from a rotor
speed of order 1e-18; that shifts the far digits of the existing
stopped-state chatter by a relative 1e-10.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
MirrorRotor already existed in the OpenFAST primary input file as a stub:
it was read into p_FAST%MirrorRotor but never used. This connects it.

The glue code now passes the per-rotor flag into the ElastoDyn and
AeroDyn initialization inputs, and rejects the combinations that have not
been worked through yet: linearization, the steady-state aero map solver,
SimplifiedElastoDyn, BeamDyn, AeroDisk, ExtLoads and ServoDyn. AeroDyn
adds two more of its own, for the OLAF free wake and the acoustics model.
Each of these guards is removed as the corresponding module is done.

ElastoDyn gets the same boundary treatment as AeroDyn and SED. The
azimuth and rotor speed states are physical, so the initial azimuth,
rotor speed and blade pitch are mirrored as they are read, and the
structural twist is negated to match the aerodynamic twist that AeroDyn
already negates. Blade one sits at QT(GeAz) + AzimB1Up + pi/2, so the
azimuth initial condition keeps those offsets outside the mirror.

On the drivetrain, the generator torque is converted from the ServoDyn
convention onto the physical shaft and the high-speed shaft speed is
reported in the rotor's own convention. As in SED the brake torque is
left alone, since it is already signed by the direction the shaft is
turning and mirroring the whole bracket would make it drive the rotor.

MirrorRotor still defaults to false everywhere. 5MW_Land_DLL_WTurb and
5MW_Land_DLL_WTurb_SED remain bit-identical to their baselines.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The blade coordinate system is built from the commanded pitch, not from
the pitch degree of freedom, so mirroring only the pitch DOF state left
the blade geometry unmirrored whenever the pitch DOF is off, which is the
usual case. The rotor then ran with the twist mirrored but the pitch not,
which put every blade at the wrong angle of attack.

With a rigid rotor the mirrored machine produced 12 percent more thrust
than its clockwise twin. The blade root twist angle reported by AeroDyn
made it obvious: 14.308 degrees clockwise against 12.308 mirrored, where
13.308 is the root twist and 1.0 the pitch, so the two were arriving with
opposite relative signs instead of both negated.

SetCoordSy now mirrors the commanded pitch, matching the pitch DOF state
which is already physical. The AeroDyn summary confirmed the blade twist
and swept-axis offset were already being negated correctly, so only the
pitch needed changing.

With a rigid rotor the two runs now agree exactly: thrust identical,
torque exactly opposite. With the flexible blade and tower degrees of
freedom enabled every one of the eighty output channels resolves to
identical, exactly sign-flipped, a mirrored angle, or the numerical noise
floor. The signs follow the mirror operator throughout, with tower base
side-to-side force and fore-aft moment flipping while fore-aft force and
side-to-side moment do not, and out-of-plane blade deflection matching
while in-plane deflection flips.

5MW_Land_DLL_WTurb, 5MW_Land_DLL_WTurb_SED and 5MW_Land_BD_DLL_WTurb are
all still bit-identical to their baselines.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The gearbox efficiency factor is selected from the sign of the low-speed
shaft torque about the shaft axis. A mirrored rotor generates with a
negative shaft torque, so that test classified generation as motoring and
raised the gearbox factor to 1/GBoxEff instead of GBoxEff, turning the
loss into a gain. The test now uses the torque in the rotor's own
convention, which is the direction power actually flows.

The high-speed shaft torque and power are reported in the rotor's own
convention as well, so they are unchanged by the mirror. The low-speed
shaft torque keeps its axis suffix and stays physical, so it flips.

Verified with a free drivetrain, loose coupling and a 95 percent gearbox.
Those all matter: the baseline gearbox is 100 percent efficient, which
makes both branches of the factor identical, and SignLSSTrq is only
reached from the loose-coupling integrators, so a tight-coupled run with a
lossless gearbox cannot see this at all. With the fix reverted under those
settings, 74 channels fail; with it in place every channel resolves.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Several ElastoDyn channel names share one AllOuts slot even though they
mean different things once a rotor can be mirrored. RotSpeed and
LSSTipVxa were the same number, as were Azimuth and LSSTipPxa, RotAccel
and LSSTipAxa, and RotTorq, LSShftTq and LSShftMxa. A name carrying an
explicit axis suffix should report the physical component, while a name
describing a rotor or drivetrain quantity should report it as the rotor
experiences it.

Four AllOuts slots are added for the rotor-convention channels and the
five affected names are pointed at them. The names, units and ordering of
the parameter tables are untouched, so the three parallel arrays keep
their length and alphabetical ordering; only the index array changes.
Generator speed and acceleration move to the rotor convention too, since
the generator side stays positive whichever way the rotor was built.

For a clockwise rotor RotDir is +1 and every one of these is numerically
identical to what it was, so no existing deck or baseline changes. The
split only becomes visible when MirrorRotor is set.

The three parameter tables are indexed in parallel with nothing in the
build checking they agree, so a misalignment would silently report the
wrong channel. tools/check_ed_outparams.py parses all three out of the
source and verifies the lengths match, the names stay sorted and unique,
and every index symbol is declared and within MaxOutPts.

Mirrored against the clockwise pair, RotSpeed, RotAccel, Azimuth,
RotTorq, LSShftTq, HSShftTq, GenSpeed, GenAccel, RotPwr and RotThrust now
all read identically, while LSShftMxa, LSSTipVxa, LSSTipAxa and LSSGagMxa
flip and LSSTipPxa and LSSGagPxa mirror as angles. Rigid, flexible and
free-drivetrain runs all resolve completely. Four regression cases
including an offshore one stay bit-identical.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds docs/source/user/glue-code/mirror_rotor.rst covering what MirrorRotor
does, the two output conventions and why they differ, the measured sign
table, the current limitations, and how the mirror is verified.

The sign table is measured rather than asserted: it comes from running the
same model clockwise and mirrored and comparing every output channel. The
note that mirroring reverses the blade sweep order, so blade 2 of one
rotor corresponds to blade 3 of the other under non-axisymmetric inflow,
is there because that is an easy way to misread a comparison.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds 5MW_Land_noDLL_Steady_CW and 5MW_Land_noDLL_Steady_MirrorRotor to the
regression suite under a new mirrorrotor label, so the pair can be run on
its own while a mirrored rotor is still being worked through.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
BeamDyn describes a blade by its key-point geometry and a 6x6 sectional
mass and stiffness matrix. Mirroring the blade about its local x-z plane
reflects y, which negates the key-point y offset and the twist, and
transforms the matrices by T*M*T^T with T = diag(1,-1,1,-1,1,-1). The
theta entries pick up a sign because rotations are pseudovectors. In
practice every matrix entry flips exactly when one of its indices is in
{2,4,6}.

The transform is applied between reading the input and validating it, so
the polar-inertia check runs against the mirrored blade. That check
compares mass0(6,6) against mass0(4,4) + mass0(5,5), all diagonal
entries, which the transform leaves alone.

Note the twist stored on read is already the negative of the value in the
file, so negating it here leaves it equal to the file value rather than
doubly negated.

A unit test covers the transform directly: that applying it twice is the
identity, that entries flip exactly when one index is in {2,4,6}, that
the polar-inertia constraint survives, and that key points reflect in y
and reverse twist while the x and z offsets are untouched.

Coupled, a mirrored BeamDyn rotor now resolves cleanly against its
clockwise twin, including the BeamDyn root and tip channels: the x and z
forces, the y moment and the x and z deflections match, while the y
force, the x and z moments and the y deflection flip. This holds with
steady inflow, shear, yaw and a free drivetrain.

BeamDyn's guard rail is removed. The clockwise path is unchanged and
5MW_Land_BD_DLL_WTurb and 5MW_Land_BD_DLL_WTurb_StC stay bit-identical.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds 5MW_Land_BD_noDLL_Steady_CW and 5MW_Land_BD_noDLL_Steady_MirrorRotor
under the mirrorrotor label alongside the ElastoDyn pair.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
y%BlPitch was inconsistent: it carried the physical pitch angle when the pitch
DOF was enabled, but the command it was handed when it was not. Apply RotDir to
the DOF branch so the output is always in the command's convention, which is
what ServoDyn is given.

HSSBrTq belongs to the same generator-side family as HSShftTq and HSShftPwr,
so it reads in the rotor's own convention rather than about the +x shaft axis.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A furling machine is chiral by design: the tail boom, tail fin and the rotor-
and tail-furl axes are all offset to one side, and the nacelle mass centre is
laterally offset with them. Reversing only the rotor leaves that geometry
untouched and produces a turbine that is not the mirror of anything.

Mirroring it properly means transforming the furl input file as well, which is
out of scope, so fail rather than answer quietly.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ServoDyn is left entirely alone. Instead the signals crossing its boundary are
converted, so an unmodified controller -- including a Bladed-style DLL such as
ROSCO -- sees exactly what it would see on a clockwise rotor and needs no
knowledge that the rotor has been reversed.

In-plane and about-axis quantities flip; out-of-plane ones do not. HSS_Spd and
BlPitch already arrive in that convention from ElastoDyn, RotPwr is a product of
two flipped quantities, and ElastoDyn signs the generator and brake torques
itself, so none of those are converted here. The shaft azimuth is negated and
re-wrapped so it still increases clockwise.

Yaw is deliberately untouched. It acts about the vertical axis in the inertial
frame, so it is not a rotor-convention quantity: the yaw error stays physically
correct and a yaw controller still points the nacelle into the real wind.
ServoDyn also receives the yaw angle and rate through the ordinary variable
mapping rather than here, so converting the yaw moment on its own would inverse
the sign of its yaw spring and drive the nacelle unstable.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds AWT_WSt_StartUp_HighSpShutDown_MirrorRotor under the mirrorrotor label.
This is the first mirrored regression case with ServoDyn in the loop, and it
covers the high-speed-shaft brake through zero rotor speed.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds two sections. The first records that ServoDyn is left unmodified and is
handed everything in the clockwise convention, and why yaw is deliberately
excluded from that.

The second is the one that costs time if it is not written down: mirroring the
turbine does not mirror the environment or the control setpoints. Initial
nacelle yaw, wind direction, horizontal shear, a turbulence box, a prescribed
structural-control load and lateral geometry are all left as written, and
leaving one of them unmirrored looks exactly like a sign error in the code.

Also corrects the limitations table, which still listed BeamDyn and ServoDyn as
unsupported, and adds the furling restriction.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds 5MW_Land_DLL_WTurb_MirrorRotor and 5MW_Land_BD_DLL_WTurb_MirrorRotor under
the mirrorrotor label. These are the cases that drive a mirrored rotor with an
unmodified Bladed-style controller through a y-reflected turbulence box, and
they complete the mirrored-rotor coverage for ServoDyn.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Downgrades the furling guard from fatal to a warning and lets the run proceed.

The tail is modelled as a drag force applying a moment about the yaw axis, and
that calculation does not depend on which way the rotor turns, since the tail
does not interact with the wake. With the aerodynamic and structural mirror
already verified, there is reasonable confidence the combination behaves as
intended once the whole structure is mirrored.

What the flag does not do is mirror the furl geometry itself. That puts furling
in the same category as an initial nacelle yaw or a wind direction: an
asymmetric input the user must mirror as well if the intent is to reproduce the
mirror image of the clockwise turbine. The documentation moves it accordingly,
out of the unsupported table and into the list of inputs the mirror leaves
alone.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@andrew-platt
andrew-platt marked this pull request as draft August 19, 2026 14:11
@andrew-platt

Copy link
Copy Markdown
Collaborator Author

Where the mirror is applied

Module Source changed Where the transformation happens
Glue code (FAST_Subs, FAST_Mapping) Reads MirrorRotor and distributes it to ED, AD and BD; holds the guard rails; and carries the entire ED ↔ ServoDyn presentation layer
ServoDyn none Nothing internal. Signals are converted in the glue code, and the generator/brake torque signs are applied inside ElastoDyn. A Bladed DLL is likewise untouched
AeroDyn Four places, all at the edges — see below
BeamDyn Input data, not the boundary — the exception to the rule
ElastoDyn Initial states, blade-pitch geometry, gearbox, outputs
SimplifiedElastoDyn Same pattern as ElastoDyn
InflowWind none The user supplies a y-reflected box; the mirror does not touch the environment
BEMT / UnsteadyAero / DBEMT / AirfoilInfo none Physics kernels solve the equivalent clockwise problem
AeroDyn driver Per-turbine flag; mirrors the prescribed hub kinematics and pitch
SED driver Flag pass-through only

AeroDyn, in a little more detail

What Where Direction
Blade twist and sweep On read, after cant is derived In
Rotor speed, pitch, toe, inflow $V_y$, blade angular rate SetInputsForBEMT In
Skew-aligned disk frame DiskAvgValues$\hat{z}$ is a pseudovector, so it needs an explicit flip In
Hub and airfoil loads Load conversion out of BEMT Out
Output channels AeroDyn_IO — power, tangential force, $C_y$, $C_t$, $C_m$, lateral induction Out

Airfoil polars are used verbatim; there is no polar transformation.

The one exception

ElastoDyn, SimplifiedElastoDyn and AeroDyn all convert signals crossing a boundary. BeamDyn instead transforms the blade description itself as it is read — key-point $y$, structural twist, and the $6\times6$ stiffness and mass matrices via $T M T$ with $T = \mathrm{diag}(1,-1,1,-1,1,-1)$.

That is necessary because those matrices carry bend–twist and shear–extension couplings with a handedness of their own; presenting mirrored motion to an unmirrored blade would not give the mirrored answer. The blade input file still describes the clockwise blade, and the finite-element solver is still never told anything.

andrew-platt and others added 4 commits August 19, 2026 08:34
Three gaps found while auditing what phases 1 to 3 actually left behind.

BeamDyn was never documented. It gained support two phases ago and the only
change made here at the time was to delete the row saying it was unsupported,
which left the page implying it did not work. Worse, the concept section stated
the reflection is applied at module boundaries only, and BeamDyn is precisely
where that is untrue: its blade description is transformed on read, because the
6x6 stiffness and mass matrices carry couplings with a handedness of their own.
That claim is now qualified and the transform is written out.

Adds a module map giving, for each module, whether it changes at all and where.
ServoDyn, InflowWind and the aerodynamic and structural kernels are listed with
the others precisely because the answer for them is "nothing".

The AeroDyn driver gained a required MirrorRotor input per turbine, which the
driver documentation never mentioned and its example input blocks did not show,
so a reader copying one would have written a file the driver rejects.

Also brings the verification section up to date: it described only the first
phase, and now names the regression pairs and the controller result.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The two example driver files shipped with the documentation never gained the
MirrorRotor input the driver now requires, so anyone copying one would have
written a file the driver rejects. ad_driver_multiple.dvr needs it for both of
its turbines.

Also rewords the BasicHAWTFormat comment here and in the driver documentation to
match the regression inputs: the basic inputs are named as a range rather than
counted, since the count was wrong and MirrorRotor now sits between the flag and
them.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant