Task-oriented guide for adding new physics, models, and adapters to SEA-Stack. Each section covers one extension point: the interface to implement, where to place files, what to link, and how to test.
For the full architecture and module inventory, see TECHNICAL_OVERVIEW.md.
Extension points at a glance
- Force components — implement
IHydroForceComponent - Wave models — subclass
WaveBase - PTO models — implement
IPTOModel - Controllers — implement
IController - External force modules — out-of-process Python/MATLAB (or FMI) via
IExternalForceModel; see EXTERNAL_FORCE_MODULES.md - Solver adapters — add an adapter analogous to
adapters/chrono/
Interface: IHydroForceComponent in
libs/core/include/seastack/core/force_component.h
namespace seastack::hydro {
class IHydroForceComponent {
public:
virtual ~IHydroForceComponent() = default;
virtual HydroComponentType Type() const = 0;
virtual void Compute(const SystemState& state,
double time,
BodyForces& inout_forces) = 0;
};
} // namespace seastack::hydroSteps:
-
Create a header under
libs/hydro/include/seastack/hydro/force_components/and a source file underlibs/hydro/src/force_components/. -
Subclass
IHydroForceComponent. ImplementType()(add a value toHydroComponentTypeif needed) andCompute(). -
Add the source file to the
SEAStack::Hydrotarget inlibs/hydro/CMakeLists.txt. -
Register the component with the force model. Two options:
- Via HydroModelBuilder: call
.AddComponent(std::make_unique<YourComponent>(...))on the builder before.Build(). - Manual assembly: construct the component directly and include it in the
std::vector<std::unique_ptr<IHydroForceComponent>>passed toHydroForces.
- Via HydroModelBuilder: call
Example: see DampingComponent in
libs/hydro/include/seastack/hydro/force_components/damping_component.h for
a simple component that reads from SystemState and accumulates into
BodyForces.
Testing: add a unit test under tests/unit/. Chrono-free components can
be tested without a solver — construct a SystemState, call Compute(),
and check the output BodyForces.
Interface: IPTOModel in
libs/pto/include/seastack/pto/pto_model.h
namespace seastack::pto {
class IPTOModel {
public:
virtual ~IPTOModel() = default;
virtual double ComputeForce(double displacement,
double velocity,
double time) = 0;
};
} // namespace seastack::ptoSteps:
-
Create a header and source file under
libs/pto/include/seastack/pto/andlibs/pto/src/. -
Subclass
IPTOModeland implementComputeForce(). -
Add the source file to
SEAStack::PTOinlibs/pto/CMakeLists.txt. -
PTO has no dependencies beyond the C++ standard library. Do not introduce Eigen, HDF5, or Chrono dependencies.
Chrono integration: when running inside Chrono, PTOForceFunctor in the
adapter layer wraps any IPTOModel and connects it to a ChLinkTSDA. No
changes to the adapter are needed for new PTO models.
Example: LinearPTO in libs/pto/include/seastack/pto/linear_pto.h
(spring-damper: F = -kx - cv). For a complex example, see
RectifiedHydraulicPTO which includes sub-stepping, accumulators, and an
optional controller.
Testing: add a unit test under tests/unit/. PTO tests are chrono-free
and run in milliseconds.
Interface: IController in
libs/control/include/seastack/control/controller.h
namespace seastack::control {
class IController {
public:
virtual ~IController() = default;
virtual double Compute(double measurement, double time) = 0;
virtual void Reset() {}
};
} // namespace seastack::controlSteps:
-
Create a header under
libs/control/include/seastack/control/. Control is header-only — no source files orCMakeLists.txtchanges needed unless your controller requires a.cpp. -
Subclass
IController. ImplementCompute()and optionallyReset(). -
Control has no dependencies beyond the C++ standard library. Keep it that way.
Usage with PTO: controllers are typically connected to a PTO model (e.g.
RectifiedHydraulicPTO accepts an optional IController* for motor speed
regulation).
Example: PIController in
libs/control/include/seastack/control/pi_controller.h (proportional-integral
with anti-windup). Also see the ProportionalController in
examples/standalone_controller/main.cpp for a minimal inline example.
Testing: add a unit test under tests/unit/. Controller tests are
chrono-free.
Interface: IExternalForceModel in
libs/external/include/seastack/external/external_force_model.h
Full protocol, lifecycle, YAML keys, and language helpers are documented in EXTERNAL_FORCE_MODULES.md.
Summary:
- Implement a user script that speaks the length-prefixed JSON protocol
(use the shipped
seastack_externalPython or MATLAB helper). - Bridge via
ExternalPtoModel(1-DOFIPTOModel) orExternalForceComponent(6-DOF hydro component). - Enable with
-DSEASTACK_ENABLE_EXTERNAL=ON. - Point
run_seastackat the script withexternal_pto_file:in the setup YAML (details in*.external_pto.yaml), or wire in C++ withPTOForceFunctor.
Examples (Python, by verification property): linear_damper_pto.py
(transport), adaptive_damping_pto.py (stateful PI control) and
hydraulic_accumulator_pto.py (dynamic subsystem) under
data/demos/run_seastack/rm3/external_pto* (TSDA) and
data/demos/run_seastack/oswec/external_pto* (RSDA; same scripts, rotational
config).
Testing: a three-level verification ladder (see
examples/external_pto/README.md):
- chrono-free unit tests under
tests/unit/test_ipc_external_*andtests/unit/test_external_pto_model.cpp; - prescribed-input golden + hydraulic component checks via
examples/external_pto/verify_examples.py(ctesttest_external_pto_examples_golden); - IPC transport equivalence via
compare_ipc_replay.py(cteststest_external_pto_ipc_{linear,adaptive,hydraulic}); - a focused Chrono RM3 regression via
verify_rm3_regression.py(ctesttest_external_pto_rm3_regression) — linear exact-equivalence vs a nativeLinearPTOtwin, aggregate checks for adaptive/hydraulic.
Pattern: adapters/chrono/ — the existing Chrono adapter.
The adapter layer bridges a dynamics engine to SEA-Stack's domain libraries.
The key coupling point is ChronoHydroCoupler, which:
- Extracts body states from the solver into
SystemState - Calls
HydroForces::Evaluate(state, time)to getBodyForces - Maps forces back into the solver's representation
Chrono and infinite-frequency added mass: For N hydro bodies, let
nDoF = 6N. The coupled infinite-frequency added-mass matrix is
nDoF × nDoF (including hydrodynamic cross-coupling between bodies).
Project Chrono's ChLoadHydrodynamics takes N blocks of shape
6 × nDoF: each block is one body's six rows against all hydro DOFs (see
tests/unit/test_added_mass_determinism.cpp). HydroSystem
(adapters/chrono/src/hydro_system.cpp) registers those blocks from the per-body
inf_added_mass arrays read from BEMIO HDF5 (dimensions follow the file:
6×(6N) when the full coupled strip is stored, 6×6 for a self-only
block).
Data flow for a non-Chrono multibody solver (aligned with TECHNICAL_OVERVIEW.md Appendix F.4.1–F.4.2):
graph LR
subgraph other [Other Solver]
Solver["MBD Solver"]
Adapter["Custom Adapter"]
end
subgraph seastack [SEA-Stack Libraries]
HydroIO["HydroIO<br/>(H5 import)"]
HydroForcesLib["HydroForces<br/>(force evaluation)"]
end
Solver -->|"body states"| Adapter
Adapter -->|"SystemState"| HydroForcesLib
HydroForcesLib -->|"BodyForces"| Adapter
Adapter -->|"applied forces"| Solver
HydroIO -->|"HydroData"| HydroForcesLib
Steps for a new adapter:
-
Create a directory
adapters/<solver_name>/withinclude/andsrc/subdirectories. -
Implement a coupler analogous to
ChronoHydroCoupler:- Extract positions and velocities from your solver's body types into
SystemState - Call
HydroForces::Evaluate() - Apply the returned
BodyForcesto your solver
- Extract positions and velocities from your solver's body types into
-
Handle added mass. The full operator is
nDoF × nDoFwithnDoF = 6NforNhydro bodies (not independent6×6blocks unless cross-coupling is negligible or omitted in the data). With Chrono,HydroSystemwiresChLoadHydrodynamicsas above. With any other MBD package, SEA-Stack does not provide a generic injector: you must map the sameinf_added_massstrips (and any off-diagonal coupling your file format encodes) into that solver's mass assembly. -
Wire PTO forces. Create a functor analogous to
PTOForceFunctorthat connectsIPTOModel::ComputeForce()to your solver's joint/link mechanism. -
Add a CMake target in
adapters/<solver_name>/CMakeLists.txt. LinkSEAStack::Core,SEAStack::Hydro,SEAStack::PTO, and your solver.
Reference: the Chrono adapter in adapters/chrono/ is the canonical
example. See ChronoHydroCoupler for the SystemState / BodyForces
bridge, HydroSystem for ChLoadHydrodynamics and added mass, and
PTOForceFunctor for PTO integration.
HydroSystem lifecycle: after constructing HydroSystem and applying
configuration setters (SetRadiationMethod, AddWaves, damping, etc.), call
BuildModel() before the first Chrono DoStepDynamics. That validates
the configuration and builds the force model outside the Chrono callback.
Skipping BuildModel() leaves construction until the first force evaluation,
where exceptions cannot be caught reliably on every platform (notably macOS).
run_seastack / RunSingleCase already call BuildModel() after YAML setup.
See also TECHNICAL_OVERVIEW.md Appendix F.3
(subsections F.3.1–F.3.4) and F.4 (F.4.1–F.4.3) for Chrono diagrams, the
run_seastack lifecycle flowchart, and alternate-solver / light-target patterns.
| Suite | Label | Purpose | Reference comparison |
|---|---|---|---|
| Unit | unit |
Library-level checks, mostly chrono-free | None (assertions) |
| Regression | regression |
Simulation output vs frozen ss_ref_*.txt |
L2/L-inf norms |
| Verification | verification |
External/multi-code baselines | data/verification/ |
| Comparison | comparison |
Two SEA-Stack configs compared | Python diff |
| Benchmark | benchmark |
Wall-clock timing | None (JSON output) |
See tests/README.md for how to run suites and track
overview; for per-case CTest names and advanced runner notes, see
tests/TEST_SUITES_REFERENCE.md.
-
Create
tests/unit/test_<name>.cpp. -
Use
TEST_ASSERT/TEST_NEARmacros (see existing tests for patterns). -
Register in
tests/unit/CMakeLists.txt:add_executable(test_my_feature test_my_feature.cpp) target_link_libraries(test_my_feature PRIVATE SEAStack::Hydro) # or appropriate target add_test(NAME test_unit_my_feature COMMAND test_my_feature) set_tests_properties(test_unit_my_feature PROPERTIES LABELS "unit;chrono-free")
-
Create the simulation executable under
tests/regression/<family>/. -
Run once to produce output, review it, and commit the reference file as
ss_ref_<case>.txt. -
Register the CTest run step and a Python comparison step. Use the naming functions from
cmake/SeaStackTestNaming.cmake:include(SeaStackTestNaming) seastack_ctest_name(regression sphere my_case RUN run_name) seastack_ctest_name(regression sphere my_case REFERENCE ref_name)
-
Reference data (
ss_ref_*.txt) is never regenerated — it is the frozen ground truth. If physics change, update the reference deliberately.
YAML-driven demo cases go under data/demos/run_seastack/<model>/. Each
case directory contains a simulation YAML, model YAML, and hydro config.
See data/demos/run_seastack/README.md
for the catalog and file conventions.
- Preserve existing APIs unless explicitly extending them.
- Prefer simple, readable code over heavy abstractions or templates.
- Document units and sign conventions in new headers.
- All existing regression tests must pass after your changes.
- Follow the Google C++ Style Guide and the conventions in CONTRIBUTING.md.