Skip to content

BeamDyn: C interface for driving a standalone beam from C/C++ codes - #3486

Open
hgopalan wants to merge 3 commits into
OpenFAST:devfrom
hgopalan:f/beamdyn-c-binding-dev
Open

hgopalan wants to merge 3 commits into
OpenFAST:devfrom
hgopalan:f/beamdyn-c-binding-dev

Conversation

@hgopalan

@hgopalan hgopalan commented Oct 1, 2026

Copy link
Copy Markdown

BeamDyn: C interface for driving a standalone beam from C/C++ codes

Feature or improvement description

This adds a C interface to BeamDyn in the style of the existing MoorDyn and AeroDyn-Inflow C bindings, so that an
external C/C++ code can drive a single BeamDyn beam without the OpenFAST glue code. The motivation is ERF-Fire modeling of
poles and wires: the flow solver computes the loads on each pole and wire, BeamDyn returns their dynamic response
(node motions and root reactions), and the coupling runs entirely from C/C++ without the OpenFAST glue code.

New files in modules/beamdyn/src:

  • BeamDyn_C_Binding.f90: BD_C_Init, BD_C_GetRefPositions, BD_C_SetRootMotion, BD_C_SetPointLoads,
    BD_C_SetDistrLoads, BD_C_UpdateStates, BD_C_CalcOutput, BD_C_PackStates, BD_C_UnpackStates, BD_C_End.
    Module-level storage of all BeamDyn data, linear/quadratic input history, correction-step handling, and the same
    ErrStat_C/ErrMsg_C error handling as the other bindings.
  • BeamDyn_C_Binding.h: C header declaring every entry point (installed to include/).
  • BeamDyn_C_Binding_Driver.c: C driver that runs a beam like the Fortran driver (writes the same .out format) and
    checks the interface: static tip deflection and first bending frequency of a uniform cantilever against the
    Euler-Bernoulli values, bitwise reproduction of the Fortran driver output, prescribed root motion, and a
    checkpoint/restore round trip.

CMake: new targets beamdyn_driver_subs (driver subroutines split into a library, as AeroDyn does),
beamdyn_c_binding (shared), beamdyn_c_bind_static, and beamdyn_c_binding_driver.

Checkpointing uses BeamDyn's registry pack/unpack routines and writes a <root>.chkp file (same mechanism as
FAST_CreateCheckpoint), so a pole or wire can be checkpointed and restored with the rest of the ERF-Fire state; the
restarted run reproduces the straight run bit for bit.

Design deviation from the MoorDyn and AeroDyn-Inflow bindings: those interfaces pass positions, velocities,
accelerations, and gravity as c_float. Here the root kinematics (position, orientation, velocity, acceleration)
and gravity are c_double; loads, node motions, reaction loads, and channel values remain c_float, and
orientation DCMs are c_double as in AeroDyn-Inflow. The reason is that BeamDyn imposes the root motion as a
boundary condition at every step, so single-precision rounding of a prescribed root motion acts as root
acceleration noise of order eps*|disp|/dt^2. On the rotating-root BeamDyn regression case (a prescribed root motion, the situation of a wire support moving with its pole), c_float root motion
differed from the Fortran driver by up to 5e-3 (relative) in near-root acceleration channels and 1e-5 in tip
displacement; with c_double the difference is below 1e-8 on every channel and 3e-13 in tip displacement.

Impacted areas of the software

modules/beamdyn only. BeamDyn itself is unchanged; the Fortran driver now links the new beamdyn_driver_subs
library instead of compiling Driver_Beam_Subs.f90 directly.

Test results, if applicable

Built with Release, BUILD_SHARED_LIBS=ON, DOUBLE_PRECISION=ON (gfortran 16, macOS), run with beamdyn_c_binding_driver:

Check Interface Reference Rel. diff
Uniform 10 m cantilever, EI 1e7 N-m^2, 100 kg/m, 1000 N tip force: static tip deflection (static solve) 3.33340e-2 m 3.33333e-2 m (Euler-Bernoulli) 1.9e-5
Same, settled dynamic solve 3.33290e-2 m 3.33333e-2 m 1.3e-4
First bending frequency after load release (24 cycles, damping removed) 1.76873 Hz 1.76958 Hz (Euler-Bernoulli) 4.8e-4
Prescribed root motion 0.01 m at 0.2 Hz: root reaction amplitude 15.93 N 16.00 N (rigid inertial estimate) 4.4e-3

Fortran driver vs. the same case driven through the interface (BeamDyn dynamic regression example, 1047 channels, 2502 output times, 12 printed digits): non-rotating root, identical to all digits; rotating root, max relative difference 8.7e-9 over all channels (3.5e-13 in tip displacement).

Checkpoint written mid-run, restored into a fresh instance, continued: bitwise identical to the straight run on both the cantilever and the regression example.

The third commit (BD_Init returns its time step through Interval) leaves the Fortran driver output bit-identical on the example cases (DTBeam = DEFAULT).

Regression suite: all 20 BeamDyn-labelled r-test cases (8 BeamDyn driver cases, 5 time-domain OpenFAST cases, 7 linearization cases) were run on this branch and on unmodified v5.0.0 with the same toolchain (macOS, gfortran 16, double precision). All 42 result files (.out and .lin) are identical between the two builds. Fifteen cases pass the shipped baselines on both builds; the same five fail on both (Damped_Beam_Rotated, 5MW_Land_BD_Linear_Aero, IEA22MW_ModalDamping, IEA22MW_ModalDampingLoose, 5MW_Land_BD_DLL_WTurb_StC), so those are platform mismatches between this toolchain and the shipped baselines, not effects of this branch. Every BeamDyn input in r-test uses DTBeam = "DEFAULT", so the Interval change cannot alter regression results by construction.

🤖 Generated with Claude Code

hgopalan and others added 3 commits October 1, 2026 13:32
Add a C binding for BeamDyn in the style of the MoorDyn and
AeroDyn-Inflow C bindings, so that an external C/C++ code can drive a
single BeamDyn beam without the OpenFAST glue code.

Entry points (BeamDyn_C_Binding.f90, declared in BeamDyn_C_Binding.h):
BD_C_Init, BD_C_GetRefPositions, BD_C_SetRootMotion, BD_C_SetPointLoads,
BD_C_SetDistrLoads, BD_C_UpdateStates, BD_C_CalcOutput, BD_C_PackStates,
BD_C_UnpackStates, BD_C_End.  All BeamDyn data is held in module-level
variables; the input history for linear or quadratic interpolation and
the state history needed for correction steps are handled as in the
other bindings, with the same ErrStat_C/ErrMsg_C error handling.  The
input file may be passed as a path or as its contents (written to a
temporary file, since BeamDyn reads its input from a file unit).
Checkpoint files use BeamDyn's registry pack/unpack routines.

Root kinematics and gravity cross the interface in double precision:
BeamDyn imposes the root motion as a boundary condition at every step,
and single precision rounding of a prescribed root motion appears as
root acceleration noise.  Loads and outputs are single precision as in
the other bindings; orientations are double precision.

CMake: the driver subroutines move into a beamdyn_driver_subs library
(the binding uses them to write the output file), and the new targets
beamdyn_c_binding (shared) and beamdyn_c_bind_static are installed with
the header.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add BeamDyn_C_Binding_Driver.c and the beamdyn_c_binding_driver target.
The driver runs a beam from C the way the Fortran driver does (root
rotating about the origin, constant tip load, output file in the same
format) and checks the interface:
  - run:        time marching with optional prescribed sinusoidal root
                motion; the root reaction is compared with the
                rigid-body inertial estimate
  - cantilever: static tip deflection and first bending frequency of a
                uniform cantilever against the Euler-Bernoulli values
  - checkpoint: run, write a checkpoint, restore it into a fresh
                instance, and compare with the straight run

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BD_Init took the suggested coupling interval as the default for DTBeam
but never reported the time step it actually uses back through the
Interval argument, as the other modules do.  Set Interval to p%dt after
the parameters are read so that a calling code can check or adopt the
BeamDyn time step.  The C interface uses the returned value for its
time step check.  Results are unchanged when DTBeam is DEFAULT (the
regression and example cases).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants