cmudrc fork status: This is an active parity effort, not yet a complete wave energy converter simulator. A case-driven dynamics runner now covers Sphere heave free decay, RM3 regular-wave coupled motion, and OSWEC hinged pitch. Other mechanical layouts and force models remain unsupported. See PARITY.md for verified behavior, current MATLAB reference revision, and the remaining work.
Install from a clone with Python 3.12:
python3 -m venv .venv
.venv/bin/python -m pip install -e .
.venv/bin/python -c 'from wecsim import WEC; print(WEC)'For a regular install without cloning, use
python -m pip install git+https://github.com/cmudrc/wec-sim-python.git.
Both methods install the wecsim import and the wecsim and
wecsim-reference commands.
For development, run the production-code parity checks with:
.venv/bin/python -m pip install pytest
.venv/bin/python -m compileall -q wecsim
.venv/bin/python -m pytest -q tests/test_wave_parity.py tests/test_body_io.py tests/test_oswec_standalone.py tests/test_rm3_standalone.py tests/test_reference_cli.py tests/test_general_dynamics.py tests/test_case_dynamics.py tests/test_pto_connections.py tests/test_python_api.pyWEC-Sim-Python is Sungjun Won's Python port of WEC-Sim, the MATLAB/Simulink wave energy converter simulator. This fork is developing and checking the Python code against MATLAB WEC-Sim while preserving the original author's work.
The installable code is in wecsim/, and runnable cases and bundled RM3
inputs are in examples/. Sungjun Won's early sketches remain available in
Git history.
WEC-Sim-Python aims to help researchers, start-up companies, and enthusiasts without access to MATLAB in order to use the open-source code provided by NREL and Sandia lab. Also, with growing research in the field of machine learning, WEC-Sim-Python could be more convenient for those who develop machine learning projects utilizing Python.
Wave generation, RM3 and OSWEC hydrodynamic input, force preprocessing, and
the supported device dynamics have paired MATLAB checks. The Python API is
the primary way to configure a linearized device. It constructs bodies,
named motions, attachment points, PTOs, and waves as Python objects, then
returns NumPy arrays directly. Import it as wecsim after installation:
from wecsim import NoWave, WEC, WorldPoint
wec = WEC("Heaving float")
float_body = wec.body("float", "path/to/hydro.h5")
wec.coordinate("heave", float_body.move("heave"))
wec.pto("main", float_body.at(0, 0, 0), WorldPoint(0, 0, 10),
damping=1_200_000)
result = wec.run(NoWave(), dt=0.1, end_time=20,
radiation_memory=15,
initial_coordinate={"heave": 1})
heave = result.bodies["float"].position[:, 2]
pto_force = result.ptos["main"].forceThe full Python example defines a
two-body device with a shared pitch pivot and body-local PTO points. Run it
with python -m examples.configurable_rm3_pto. Relative HDF5 paths in
the Python API resolve from the current directory unless base_dir is passed
to wec.run. Body order must match the HDF5 hydrodynamic body order. The
Python builder currently covers the linear_subspace layout with regular,
regularCIC, or no waves; its named coordinates use small-motion kinematics and fixed-axis
PTOs. The other validated reference layouts remain available through the
case runner and focused solver functions.
For a regular-wave declutching PTO, pass
control=DeclutchingControl(gain=232_020, declutch_time=0.8) to wec.pto
instead of constant damping. Import DeclutchingControl from wecsim.
The controller disengages at a velocity sign change and reengages after the
configured interval; minimum_on_time defaults to 0.2 s. The published
Sphere case is paired against MATLAB at a 0.01 s step. Other geometries and
wave settings are not yet paired.
For the published Sphere latching case, pass
control=LatchingControl(gain=49_181, latch_damping=37_308_296, latch_time=2.4).
The controller applies the larger damping for the timed interval after a
velocity reversal, then returns to its normal gain. It is a finite damping
force, not a rigid lock. minimum_normal_time defaults to 0.2 s. The control
settings serialize through WEC.to_case for reproducible saved cases.
The JSON runner remains available for saved and reproducible cases. It accepts simulation, wave, body, constraint, and PTO settings. The included RM3 example runs with the historical bundled HDF5:
python -m wecsim examples/rm3.json --output results/rm3.csvThe runner saves body position and velocity, wave elevation where applicable, PTO force or torque, and a JSON provenance record beside the CSV. HDF5 paths in the case file resolve relative to that file. For a comparison with current MATLAB WEC-Sim, point the case at the HDF5 file from the pinned reference run. The provenance record hashes the case, hydrodynamic files, and any replayed phase CSV.
Supported combinations are:
Constraint kind |
Wave type |
Bodies and PTO | Integration |
|---|---|---|---|
heave |
none |
One equilibrium-mass body, initial heave displacement, no PTO | Radiation convolution |
fixed_hinge |
pm or pm_multi |
Hydrodynamic flap, optional fixed hydrodynamic base, pitch PTO | Directional PM excitation and radiation convolution; pm_multi sums independently phased seas |
fixed_hinge |
spectrumImportFullDir |
Hydrodynamic flap, optional fixed hydrodynamic base, pitch PTO | Imported frequency-dependent directional spectrum and radiation convolution |
fixed_hinge |
regular |
One hydrodynamic flap, optional fixed nonhydrodynamic base, pitch PTO | Regular-wave excitation and constant-frequency radiation |
floating_joint |
regular or regularCIC |
Two equilibrium-mass bodies, pitch inertias, relative-heave PTO; optional body_to_body |
Constant-frequency radiation, impulse-response convolution, or sampled FIR radiation |
floating_joint |
elevationImport |
Two equilibrium-mass bodies, relative-heave PTO, optional joint surge spring | Imported MAT elevation and radiation convolution |
floating_joint |
none |
Two equilibrium-mass bodies, named initial coordinates and speeds, relative-heave PTO | Radiation convolution for paired free decay; sampled FIR is also available |
linear_subspace |
regular, regularCIC, or none |
Any number of six-DOF hydrodynamic bodies; named motions or 6-by-N maps; optional linear or rotational PTOs; selected mean-drift coefficients for regular waves | Constant-frequency radiation or convolution |
The published OSWEC passive-yaw cases use one yaw coordinate and a torsional
PTO. Set passive_yaw=True on the moving body to interpolate excitation at
the wave heading relative to its current yaw angle:
from wecsim import RegularWave, WEC, WorldPoint
wec = WEC("Passive-yaw OSWEC")
flap = wec.body("flap", "oswec.h5", mass=12_700,
inertia=(1.85e6,) * 3, passive_yaw=True)
wec.body("base", "oswec.h5", mass=999, inertia=(999,) * 3)
yaw = wec.coordinate("yaw", flap.move("yaw", pivot=WorldPoint(0, 0, -8.9)))
wec.rotational_pto("hinge", yaw, damping=120_000)
result = wec.run(RegularWave(2.5, 8, direction=10),
dt=0.01, end_time=600, ramp_time=100)
angle = result.bodies["flap"].position[:, 5]
absorbed_power = result.ptos["hinge"].absorbed_powerThe rotational PTO reports angle in radians as stroke, angular speed in
rad/s as velocity, and torque in N m as force. Passive yaw currently
supports one pure-yaw hydrodynamic body, stationary additional bodies,
regular waves, and independent radiation with full-circle BEM headings.
The Python excitation interpolates continuously; the pinned MATLAB case
holds its heading coefficients until the angle changes by 0.01 degree.
For the published Sphere Mean_Drift application, select the control-surface
coefficient in the generated HDF5 file and use convolution radiation:
from wecsim import RegularCICWave, WEC
wec = WEC("Sphere mean drift")
sphere = wec.body("sphere", "Mean_Drift/hydroData/sphere.h5",
inertia=(837.75804096,) * 3,
mean_drift="control_surface")
for axis in ("surge", "heave", "pitch"):
wec.coordinate(axis, sphere.move(axis))
result = wec.run(RegularCICWave(0.1, 2), dt=0.01, end_time=100,
ramp_time=20, radiation_memory=10)
drift_force = dict(result.raw.extra_outputs)["body1_mean_drift_force"]
excitation_force = dict(result.raw.extra_outputs)["body1_excitation_force"]mean_drift="momentum_conservation" selects that HDF5 coefficient when
present. An absent selected dataset raises an error. The regular-wave drift
force is the selected coefficient times wave amplitude squared and the wave
force ramp. The published unmoored linear Sphere run travels more than 16 m
in surge over 100 s; the paired result reproduces that source model but does
not establish physical accuracy at such a large displacement.
The runner rejects unsupported layouts and settings. For the PM case, supply
wave.height, wave.period, optional directions and spreading, and either
an integer seed or a phase_file CSV to replay a MATLAB realization.
simulation accepts dt, end_time, optional ramp_time, rho, g, and
radiation_memory for radiation-memory cases. For an RM3 regularCIC floating
joint, set simulation.radiation_method to "fir" to use the published
discrete FIR calculation; "convolution" remains the default. The same
setting is available for no-wave free decay, without a paired FIR baseline. Both use
radiation_memory (60 s by default). The general dynamics module
assembles the supported body and PTO forces; it does not parse Simscape models.
The inherited WaveClass now uses the pinned MATLAB PM and JONSWAP spectrum
definitions, including height-dependent PM energy and JONSWAP's inferred
gamma when it is unspecified. Its seeded phases use a local NumPy generator;
MATLAB's Threefry substreams produce different realizations for the same
integer seed. To replay a MATLAB realization, assign its frequency-by-direction
phase matrix before waveSetup:
import numpy as np
from wecsim.waveClass import WaveClass
wave = WaveClass("irregular")
wave.T, wave.H = 8, 2.5
wave.spectrumType = "PM"
wave.freqDisc, wave.numFreq = "Traditional", 64
wave.waveDir, wave.waveSpread = [0, 30, 90], [0.1, 0.2, 0.7]
wave.phaseData = np.loadtxt("pm_phase.csv", delimiter=",")
wave.waveSetup([0.4, 2.0], "infinite", 1, 0.1, 20, 9.81, 1000, 2)The historical BS fixture remains a compatibility check for the
original Python port; current MATLAB WEC-Sim rejects BS inputs.
For this RM3 convolution layout, the solver follows the published pitched
slider geometry and uses an implicit effective added mass by default. To
reproduce the pinned MATLAB/Simulink numerical trajectory, set
simulation.added_mass_scheme to "simulink_delay" (or pass that keyword to
solve_rm3_regular and the RM3 MCR runners). This explicitly selects the
source model's mass split and 1e-7 s acceleration delay; the delay is a
numerical setting, not a WEC property. The published Cases 5 and 6 use a
suspect fitted state-space radiation model and remain unsupported. The paired
Cases 3 and 4 tests check the ordinary implicit solver separately from this
opt-in numerical comparison; neither path uses the Cases 5 and 6 fit.
For the fixed_hinge layout, an optional second body can be fixed. The
regular-wave case accepts a nonhydrodynamic base declared with nonhydro: true, fixed: true, and a three-component center_gravity; PM cases can
instead use a hydrodynamic base with fixed: true. Its stationary motion
appears in the response. With this base, constraint.location is its ground
attachment and pto.location is the flap's hinge attachment. The base's
constraint reaction forces are not yet calculated.
The regularCIC floating-joint path has paired MATLAB checks for RM3
body-to-body Cases 3 and 4 and all eight physical settings in the published
RM3 Multiple Condition Runs Option 1 sweep. The published RM3 radiation
options case also checks constant, convolution, and FIR dynamics over 500 s.
MATLAB's fitted radiation state-space Cases 5 and 6 remain outside that
validated path.
The programmatic RM3 solver also accepts mooring_surge_stiffness for a
linear spring at its floating joint and excitation_force for sampled imported
waves. The pinned MooringMatrix case pairs these forces and the full 400 s
body, PTO, and mooring trajectories with MATLAB. Its source body block applies
the wave ramp again after the imported-elevation convolution; the paired test
reproduces that source-specific step explicitly. The same setup can be passed
to the public Python case runner without precomputing a force array:
from wecsim import run_case
case = {
"simulation": {"dt": 0.01, "end_time": 400, "ramp_time": 40,
"radiation_memory": 60},
"wave": {"type": "elevationImport", "file": "Mooring/MooringMatrix/etaData.mat",
"variable": "etaData", "reapply_force_ramp": True},
"bodies": [
{"hydro_file": "_Common_Input_Files/RM3/hydroData/rm3.h5",
"hydro_body": 1, "mass": "equilibrium", "pitch_inertia": 21_306_090.66},
{"hydro_file": "_Common_Input_Files/RM3/hydroData/rm3.h5",
"hydro_body": 2, "mass": "equilibrium", "pitch_inertia": 94_407_091.24},
],
"constraint": {"kind": "floating_joint", "location": [0, 0, 0],
"initial_coordinate": {"spar_heave": -0.21}},
"pto": {"kind": "relative_heave", "damping": 1_200_000},
"mooring": {"kind": "joint_surge_spring", "stiffness": 100_000},
}
response = run_case(case, base_dir="path/to/WEC-Sim_Applications")reapply_force_ramp=True reproduces the pinned MATLAB body block's second
force ramp; its default is False. The current mooring setting is a surge
spring at this joint. Arbitrary mooring matrices and attachment locations
remain unsupported.
The RM3 floating-joint solver accepts optional PTO hard stops in Python:
from wecsim import LinearHardStops, solve_rm3_regular
stops = LinearHardStops(
lower_bound=-0.6, upper_bound=0.6,
lower_stiffness=100_000_000, upper_stiffness=100_000_000,
)
response = solve_rm3_regular(
"rm3.h5", pto_hard_stops=stops, dt=0.025, end_time=400,
)
print(response.pto_stroke, response.pto_stop_force)The case runner also accepts these names under pto.hard_stops for a
floating_joint with regular waves. Hard stops select adaptive integration,
constant-frequency radiation, and implicit added mass. Ordinary RM3 cases
retain their existing solver. The published MATLAB End_Stops run is
time-step sensitive after contact. Paired motion, force, and energy checks
pass through 400 s against the 0.0125 s MATLAB run; a 0.025 s run gates source
time-step convergence. See
PARITY.md for the limits and the published 0.1 s discrepancy.
RM3 multiple-condition runs can be configured in Python without a JSON input:
from wecsim import mcr_grid, run_rm3_mcr
conditions = mcr_grid(
heights=[1.5, 2.5], periods=[6, 8],
damping_values=[1_200_000, 2_400_000],
)
result = run_rm3_mcr("rm3.h5", conditions)
power = result.power_matrix(damping=1_200_000)
print(power.periods, power.heights, power.absorbed_power)
first_trajectory = result.traces[0].responsemcr_wave_statistics(path, damping_values) reads the published Option 2
Excel grid; mcr_mat_file(path) reads the Option 3 MAT-file case table.
run_mcr(conditions, simulate, averaging_start_time=...) accepts a Python
callback for another configured WEC. The callback returns an MCRTrace with
time samples and signed absorbed PTO power; an optional response object keeps
the full trajectory available. The published RM3 example averages from
199.9 s through 400 s and reports positive absorbed power, the opposite of
MATLAB's signed PTO power column. A spring may return stored energy, so an
individual absorbed-power sample may be negative.
The paired MATLAB workflow executes the actual Option 1, 2, and 3
wecSimMCR drivers separately. For each option it compares all eight body
and PTO trajectories, average powers, and power matrices. The Option 1
physical conditions are also paired as scalar MATLAB runs. Phase-seed sweeps,
multiple PTOs, and other MCR postprocessing remain unverified.
For the published three imported-spectrum RM3 sea states, pass the MAT-file table to the Python runner. Paired MATLAB checks cover the incident waves, excitation, body and PTO trajectories, and absorbed power. Across the three 400 s runs, the largest position differences are 14.4 mm surge, 1.03 mm heave, and 0.000638 rad pitch; mean absorbed powers differ by at most 29 W:
from wecsim import run_rm3_spectrum_mcr
sea_states = run_rm3_spectrum_mcr(
"rm3.h5", "RM3_MCROPT3_SeaState/mcrExample.mat",
)
print(sea_states.mean_absorbed_power)
first_wave = sea_states.wave_elevation[0]
first_float = sea_states.traces[0].response.body_position[:, 0, :]The three spectrumData*.mat files must sit beside mcrExample.mat. Their
third column supplies the exact phase used for each wave component; no random
seed is needed. The runner uses the same 60 s convolution radiation model as
the validated RM3 cases 3 and 4.
For a no-wave floating joint, constraint.initial_coordinate and
constraint.initial_speed accept four values or dictionaries keyed by
surge, float_heave, spar_heave, and pitch. In the published RM3 PTO
extension examples, float_heave: 5 or spar_heave: -5 creates the same
initial +5 m relative PTO stroke while moving a different body.
For linear_subspace, the map's rows are surge, sway, heave, roll, pitch,
and yaw; its columns are independent generalized coordinates. A two-body
heave case, for example, maps the first body's heave to coordinate 1 and the
second body's heave to coordinate 2. constraint.initial_coordinate and
initial_speed set those coordinates, and pto.damping_matrix and
stiffness_matrix apply generalized linear forces. This layout assumes
small rotations and a constant coordinate map.
The matrices may be signed to represent active linear feedback. For example,
the published Sphere reactive PI controller uses a heave-coordinate PTO with
stiffness_matrix: [[-573350]] and damping_matrix: [[49181]], giving
force = -49181 * heave_speed + 573350 * heave_displacement in the runner's
force convention. This published case has no PTO stroke limit; the paired
trajectory comparison does not validate hardware feasibility.
The configurable two-body example shows a WEC definition with named bodies, independent motion coordinates, and a PTO connection. Run it with:
python -m wecsim examples/configurable_rm3_pto.json --output results/configurable-rm3.csvIn constraint.coordinates, each named coordinate lists the body motions it
drives. Assigning the same coordinate to two bodies gives them a shared motion;
separate coordinates let them move independently. A motion names one of surge,
sway, heave, roll, pitch, or yaw and may include a scale (default 1).
For a rotational motion, pivot can specify {"world": [x, y, z]} or
{"point": [x, y, z]} in that body's local center-of-gravity frame. The
rotation then moves the body center about that point. With no pivot, the
body rotates about its center of gravity. The example's shared pitch rotates
both RM3 bodies about world [0, 0, 0].
initial_coordinate and initial_speed may be vectors or dictionaries keyed
by coordinate name. The original 6-by-N coordinate_map remains available for
advanced cases. These maps describe constant, small-motion kinematics, not an
arbitrary multibody joint solver.
ptos is a list of linear actuators. Each PTO has a unique name, from and
to endpoints, and damping and/or stiffness. A body endpoint uses
{"body": "float", "point": [x, y, z]}: the point is in that body's local
frame relative to its HDF5 center of gravity. A fixed world anchor uses
{"ground": [x, y, z]}. axis gives a fixed world-space force direction;
if omitted, the axis follows the line between endpoints at the reference
pose and then stays fixed. Stroke is projected displacement along that axis
from the reference pose. Moving an attachment point changes its moment arm
when the body rotates. The CSV includes each PTO's stroke, velocity, force,
and power absorbed by its damper, as well as named coordinate motion. This
force is positive along the axis on the to endpoint and opposite on from.
The reported absorbed power is damping times stroke velocity squared for a
constant damper and zero while a declutching PTO is disengaged. This
connection model is linearized for small rotations; it does not update the
actuator's axis as its endpoints move. Use either ptos or the older pto
matrix in one case.
In the example, each body-local PTO point has a vertical coordinate that
places it at world z = 0 in the reference pose; its horizontal offset sets
the pitch moment arm.
The fixed_hinge and floating_joint layouts accept scalar pto.damping
and optional pto.stiffness. They also accept either
pto.equilibrium_position or pto.pretension. For hinge pitch, the position
is an angle in radians and forces are torques; for the floating joint, it is
relative heave in meters and forces are newtons. The implemented law is
F = -damping * velocity - stiffness * (position - equilibrium_position).
As in MATLAB WEC-Sim, pretension sets the equivalent equilibrium position
to -pretension / stiffness; nonzero pretension or equilibrium position
requires positive stiffness. These fields default to zero, preserving the
paired reference cases. For example, add
"stiffness": 100000, "equilibrium_position": 0.1 to the RM3 example's
pto object to shift its neutral relative heave by 0.1 m.
The linear_subspace layout uses pto.stiffness_matrix and
pto.damping_matrix, with optional pto.equilibrium_coordinate (N values).
Its force law is F = -K @ (q - q_eq) - C @ q_dot. A nonzero offset must
produce a spring force. The zero-offset cases have paired MATLAB checks;
nonzero offsets have analytical and case-level tests. Each actuator in ptos
can instead set damping, stiffness, and either equilibrium_position or
pretension. Connection geometry and nonzero offsets have case-level and
analytical checks. A paired MATLAB Sphere case covers nonzero stiffness,
extra damping while specifying a body-local attachment shifted 1 m in x.
Its heave-only motion cannot validate attachment-location dynamics or a
rotational moment arm.
Force limits, hard stops, controllers other than the published declutching and
reactive PI laws, and hydraulic PTO models are not implemented.
The published Sphere free-decay cases can also be calculated with the focused solver:
from wecsim.linearHeave import solve_heave_free_decay
response = solve_heave_free_decay("path/to/sphere.h5", initial_displacement=1.0)
# response.time, response.position, response.velocity, response.force_totalThis solver assumes one heave-only body, zero incident waves, and no PTO,
mooring, or nonlinear force. Generate sphere.h5 with the published
WEC-Sim_Applications Sphere bemio.m, or download the HDF5 artifact from the
MATLAB reference-model run.
The RM3 regular-wave heave subsystem can be calculated with the same module:
from wecsim.linearHeave import solve_two_body_regular_heave
response = solve_two_body_regular_heave(
"path/to/rm3.h5", wave_height=2.5, wave_period=8.0,
pto_damping=1_200_000.0,
)
# response.position and response.velocity each have two body columns;
# response.pto_force is the PTO force acting on body 1.This calculation includes only vertical translation, a linear relative-motion PTO, and frequency-dependent hydrodynamic coefficients at the incident wave frequency. The coupled RM3 reference model also predicts both body surge motions and their shared pitch:
from wecsim.rm3Regular import solve_rm3_regular
response = solve_rm3_regular("path/to/rm3.h5")
# response.body_position and response.body_velocity have shape
# (time_steps, 2 bodies, 6 DOFs); response.pto_force is the heave PTO force.This model uses the published RM3 inertias, regular wave, floating-joint geometry, and linear PTO. It covers the canonical case's active degrees of freedom; it does not supply general Simscape joint dynamics. The OSWEC reference can generate a seeded Python wave realization and pass its six-component excitation history to the hinge-pitch solver:
from wecsim.hingePitch import solve_hinged_pitch_from_excitation
from wecsim.irregularWave import (
pm_equal_energy_components, synthesize_irregular_response,
)
components = pm_equal_energy_components(
"path/to/oswec.h5", significant_height=2.5, peak_period=8,
directions=[0, 30, 90], spreading=[0.1, 0.2, 0.7], seed=7,
)
wave = synthesize_irregular_response(
"path/to/oswec.h5", components, dt=0.1, end_time=400, ramp_time=100,
)
response = solve_hinged_pitch_from_excitation(
"path/to/oswec.h5", wave.excitation_force, hinge_z=-8.9,
body_mass=127_000, pitch_inertia=1.85e6, pto_damping=12_000,
)
# response.angle is in radians; wave.elevation is the incident elevation.It models pitch about a fixed hinge and the radiation-memory force. PM equal-energy bins, directional excitation, and pitch are checked against current MATLAB WEC-Sim using the same saved random phase matrix. A Python integer seed creates a reproducible Python realization, with a different random sequence from MATLAB.
For an imported full-directional spectrum, configure the Python case with
wave.type = "spectrumImportFullDir", a MAT wave.file, and either a
wave.phase_file CSV or an integer wave.seed. The default includes each
heading-bin width in both elevation and force, preserving the spectrum's
integrated energy. The published MATLAB Full_Directional_Waves force block
omits that width even though its elevation includes it. To reproduce that
specific source trajectory, set wave.force_quadrature to
"matlab_omitted" and wave.excitation_interpolation to
"spline_frequency"; simulation.added_mass_scheme = "simulink_delay"
is a separate opt-in Simulink numerical comparison. The ordinary defaults
remain integrated forcing and implicit added mass.
The same wave calculation is available directly in Python:
from wecsim import (imported_full_directional_components,
synthesize_full_directional_response)
components = imported_full_directional_components(
"path/to/oswec.h5", "path/to/fullDirSpectrum.mat", seed=7,
)
incident = synthesize_full_directional_response(
"path/to/oswec.h5", components,
dt=0.05, end_time=400, ramp_time=100,
)
# incident.elevation and incident.excitation_force are NumPy arrays.To run a supported case without writing Python code:
python -m wecsim.reference rm3 --h5 path/to/rm3.h5 --output results/rm3.csv
python -m wecsim.reference rm3 --h5 path/to/rm3.h5 --output results/rm3-b2b.csv --b2b
python -m wecsim.reference oswec --h5 path/to/oswec.h5 --output results/oswec.csv --seed 7
python -m wecsim.reference sphere --h5 path/to/sphere.h5 --output results/sphere.csv --initial-displacement 1Each command writes a numeric CSV and an adjacent JSON file with the model
settings, HDF5 SHA-256 hash, NumPy version, and Git revision/dirty state.
For RM3, --b2b includes cross-body hydrodynamic coupling as in the
published B2B Case 2; the default matches B2B Case 1.
The original README projected completion in
August 2022; that date is no longer applicable. See PARITY.md
for the tested scope and next reference case.
The Python dynamics engine uses independent coordinates for each supported constraint layout, avoiding numerical joint drift. New layouts and force models need explicit MATLAB comparisons before they are called supported. ParaView file output and BEMIO conversion remain future work.