C API reference
CableDyn ships a C ABI in the shared library cabledyn. It is the embedding interface for
a host that owns time integration: a CFD solver (Star-CCM+, OpenFOAM), a vessel or platform
simulator, or a scripting layer. The host prescribes the motion of the coupled points each step,
and CableDyn returns the loads the cables exert on them, following the kinematics-in / loads-out
contract of Coupling boundary. The Python package is a ctypes layer over
the same library. OpenFAST, maintained by NLR (National Laboratory of the Rockies, formerly NREL),
uses its own in-tree module instead (OpenFAST with CompMooring = 5).
Every declaration below is in the installed header CableDyn_CAPI.h. All functions return
void except six queries: CableDyn_IsInitialized returns bool, and
CableDyn_NCoupledDOF, CableDyn_NPoints, CableDyn_NLines, CableDyn_GetAbiMinor and
CableDyn_NObjects return int. Results come back through caller-owned buffers and a status
code in *err_stat.
Overview
A model lives behind an opaque handle. The lifecycle is:
CableDyn_Create
-> CableDyn_InitDeck | CableDyn_InitDeckAt (initialize; may repeat)
| CableDyn_InitLine | CableDyn_InitLines
-> CableDyn_GetCoupledMotion (read the initial state)
-> loop: [CableDyn_SaveState | CableDyn_RestoreState] (host outer correctors)
[CableDyn_UpdatePointFluidFields]
CableDyn_Step(dt, motion at t+dt)
CableDyn_CalcOutput (loads at t+dt)
-> CableDyn_Close
A host solver that drives the Coupled points of a deck, a CFD code in particular, should
read Coupling a host (CFD) solver first: it gives the call sequence and how it differs from
MoorDyn’s C API.
Function |
Purpose |
|---|---|
|
library version and C ABI version |
|
allocate and release a handle |
|
build a model from a |
|
build a structural-only model from caller-meshed arrays |
|
set the coupled-point motion without advancing time |
|
set fluid kinematics at the deck points (point hydrodynamics) |
|
advance one implicit step to \(t+\Delta t\) |
|
loads at the coupled DOFs from the current state |
|
loads and the analytic load Jacobians at a supplied state |
|
read the current coupled-point motion |
|
diagnostic text for the last failure on a handle |
|
state and size queries |
|
object queries (ABI 1.1): the objects of the model, their committed state, and any
|
|
host coupling (ABI 1.2): initialization with the statics at the host’s positions, a one-slot state save and restore for outer correctors, and the owner of each block of the coupled vector (Host coupling calls) |
Building and linking
Build and install
The library is the CMake target cabledyn_shared. It is part of the default build.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target cabledyn_shared
cmake --install build --prefix /opt/cabledyn
The install step places the library and the header under the standard GNU install directories. It does not install a CMake package configuration file or a pkg-config file. Locate the two files directly, as shown below.
Platform |
Library files |
Header |
|---|---|---|
Linux |
|
|
macOS |
|
|
Windows, GNU toolchain |
|
|
Windows, Intel Fortran with MSVC |
|
|
The library links the Fortran runtime, LAPACK/BLAS, and, when the compiler supports it, the
OpenMP runtime. These must be loadable at run time: through the rpath or LD_LIBRARY_PATH on
Linux, and on PATH or next to the executable on Windows. When OpenMP is enabled, the lines of
one model are processed in parallel. OMP_NUM_THREADS controls the thread count.
CableDyn’s linear systems are small and banded, so it runs OpenBLAS with one thread. A threaded
OpenBLAS starts one worker per logical CPU when its DLL loads, and on Windows each worker commits
a work buffer of about 128 MB at once (about 4 GB on a 32-CPU machine). The Windows GNU build
therefore does not import openblas.dll: the first CableDyn_Create (or, in an executable,
the first solve) loads the openblas.dll next to the CableDyn library or executable with a
one-thread pool, falling back to the standard DLL search order. If it cannot be loaded,
InitDeck, InitDeckAt, InitLine and InitLines return CD_C_ALLOC_FAIL and
CableDyn_GetLastError names the library, each location tried and the Windows load error;
the standalone driver prints the same diagnostic and exits with status 2. Other builds
that link OpenBLAS call openblas_set_num_threads(1) at the first Create; the Intel
release links the single-threaded reference LAPACK/BLAS. The environment variable
CABLEDYN_BLAS_THREADS changes this: a positive integer sets that many BLAS threads, and
default (or 0) leaves OpenBLAS to its
own OPENBLAS_NUM_THREADS or CPU-count setting. An explicit OPENBLAS_NUM_THREADS is always
respected when the DLL is loaded. If another component of the process loaded the same
openblas.dll first, its pool already exists and CableDyn only limits how many workers its
calls use. Each OpenMP thread that calls into OpenBLAS still commits one work buffer.
The standalone Windows release executables do not contain this library. A process that embeds
CableDyn needs a cabledyn_shared build.
The library exports exactly the functions declared in CableDyn_CAPI.h. It uses a module
definition file on Windows, an exported-symbols list on macOS, and a version script on Linux. No
Fortran module symbol is exported, so none can collide with host symbols.
CMake consumer
cmake_minimum_required(VERSION 3.20)
project(coupler LANGUAGES C)
# CableDyn installs a header and a shared library but no CMake package,
# so locate both files directly (set CMAKE_PREFIX_PATH to the install prefix).
find_path(CABLEDYN_INCLUDE_DIR CableDyn_CAPI.h REQUIRED)
find_library(CABLEDYN_LIBRARY NAMES cabledyn REQUIRED)
add_executable(coupler coupler.c)
target_include_directories(coupler PRIVATE "${CABLEDYN_INCLUDE_DIR}")
target_link_libraries(coupler PRIVATE "${CABLEDYN_LIBRARY}")
if(NOT WIN32)
target_link_libraries(coupler PRIVATE m) # only for the example's sin/cos
endif()
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/cabledyn
cmake --build build
find_library(... NAMES cabledyn) finds libcabledyn.so or libcabledyn.dylib,
libcabledyn.dll.a with a GNU toolchain on Windows, and cabledyn.lib with MSVC.
Command lines
GCC or Clang on Linux and macOS:
PREFIX=/opt/cabledyn
gcc -std=c11 -Wall -Wextra coupler.c -I"$PREFIX/include" \
-L"$PREFIX/lib" -Wl,-rpath,"$PREFIX/lib" -lcabledyn -lm -o coupler
MinGW-w64 GCC on Windows. The DLL is found at run time through PATH:
gcc -std=c11 -Wall -Wextra coupler.c -I"$PREFIX/include" -L"$PREFIX/lib" -lcabledyn -o coupler.exe
PATH="$PREFIX/bin:$PATH" ./coupler.exe
MSVC (cl) from a Developer Command Prompt. An Intel Fortran with MSVC build already installs
cabledyn.lib:
cl /nologo /W4 coupler.c /I "%PREFIX%\include" /link /LIBPATH:"%PREFIX%\lib" cabledyn.lib
A GNU-built libcabledyn.dll can also be linked from MSVC. Generate an MSVC import library
from the module definition file in the source tree, and name the DLL it binds to:
lib /nologo /def:src\cabledyn.def /name:libcabledyn.dll /machine:x64 /out:cabledyn.lib
cl /nologo /W4 coupler.c /I "%PREFIX%\include" /link cabledyn.lib
Mixing C runtimes this way is safe. No memory allocated on one side is freed on the other,
because every buffer the API touches is owned by the caller. The header needs C99
<stdbool.h> and provides extern "C" guards for C++.
ABI and versioning
Identifier |
Value |
Meaning |
|---|---|---|
|
0 / 1 / 0 |
the CableDyn release the header belongs to |
|
1 |
the C ABI revision the header describes |
|
2 |
additive extensions of ABI 1: 1 adds the Object queries, 2 the Host coupling calls |
|
runtime |
the same four numbers, reported by the loaded library |
|
runtime |
|
shared-library |
1 |
soname |
The ABI version identifies the set of exported prototypes together with their semantics: the
argument order and types, the array layouts, and the meaning of the status codes. A change that
would break an existing caller raises CABLEDYN_CAPI_ABI_VERSION and SOVERSION together.
Examples are removing or renaming a function, changing a prototype, or changing a layout or a
status meaning. The release numbers change independently of the ABI. CableDyn 0.1.x is an early
public series, so the ABI may still change before 1.0.
At load time, check the library’s ABI against the header you compiled with:
int major, minor, patch, abi;
CableDyn_GetVersion(&major, &minor, &patch, &abi);
if (abi != CABLEDYN_CAPI_ABI_VERSION) { /* refuse to run: header and library disagree */ }
The Python package does this check at import and rejects a library that speaks another ABI.
A minor extension only adds functions: every ABI 1 prototype and its semantics are unchanged,
so a caller built against an older header keeps working. A caller that uses the additions checks
CableDyn_GetAbiMinor() >= 1 before calling the object queries, and
CableDyn_GetAbiMinor() >= 2 before calling the host-coupling functions.
Calling conventions
These rules apply to every entry point. The Python package uses the same array order.
Scalars by value, results by pointer. Counts,
dt,rho_inf,eps_fd, andfluid_densityare passed by value. Everyint *err_statis always written.Pointers that are not checked. Scalar out-parameters must point to valid storage:
err_stat,converged,stalled,n_iter, the fourCableDyn_GetVersionoutputs, thevalueofCableDyn_EvalChannel, and thevoid **ofCreateandClose. They are not checked forNULL; passingNULLis undefined behaviour.Pointers that are checked. Handles, arrays, and strings are checked for
NULL. An array may beNULLonly where its declared length is zero. Each function’s table states the exact rule. ANULLwhere data is required returnsCD_C_BAD_INPUT.Array lengths. Arrays are caller-owned, contiguous, and
doubleorint. The library reads or writes exactly the length given in each table. It cannot detect a shorter buffer: a short buffer is undefined behaviour. The sizes passed to a call must match the initialised model (n_coupled_dofmust equalCableDyn_NCoupledDOF,n_pointmust equalCableDyn_NPoints). Otherwise the call returnsCD_C_BAD_INPUTand reads nothing.Vectors. Nodal positions, velocities, and fluid fields are interleaved
x, y, ztriples (3 * nvalues). The coupled vectorsq,v,a, andloadsare flat vectors ofn_coupled_dofentries. Their layout depends on how the model was initialised; see Coupled DOF layout.Indices are 1-based. This applies to element connectivity (
elem_conn, two node indices per element), fixed-DOF indices (fixed_dofs), andcoupled_mapentries. DOF \(d\) belongs to node \(\lfloor (d-1)/3 \rfloor + 1\), component \((d-1) \bmod 3\) (0 = x, 1 = y, 2 = z). Node \(k\) therefore owns DOFs \(3k-2, 3k-1, 3k\).Matrices are column-major. The
n-by-nblocks written byCableDyn_CalcOutputDerivativesstoreJ[i + j*n]as \(\partial L_i/\partial x_j\) (0-basedi,j). This is the Fortran and LAPACK order. A row-majorJ[i][j]view of the same memory is the transpose.Strings. A deck path is
deck_path_lenbytes. An embeddedNULends it early, and trailing blanks are ignored. The Fortran runtime opens the path, using the active ANSI code page on Windows. A relative path resolves against the process working directory. Files the deck references resolve against the deck’s directory. Output strings are alwaysNUL-terminated and truncated tolen - 1characters.Units and frame. SI throughout, in the global right-handed frame with z up and still water at \(z = 0\) (Conventions). See State, units, and load sign.
Threads.
CreateandCloseare thread-safe; a process-wide mutex guards the handle registry, independently of OpenMP. Separate handles may run concurrently on separate threads.InitDeck,InitDeckAt,InitLine, andInitLinesare serialised by a second process-wide lock: they open and parse files through the Fortran runtime, which cannot open one file on two units at once (two threads reading the same deck, or decks sharing a Syrope OWC table) and whose formatted I/O is not safe to run concurrently. Concurrent initialisations therefore succeed but run one after another; a thread blocks while another thread initialises.Step,UpdateStates,CalcOutput, and the other per-handle calls do no file or formatted I/O when they succeed and run concurrently on separate handles. A single handle must not be used from two threads at once, or while another thread closes it. The GNU Fortran runtime switches the process-wide numeric locale to"C"for the duration of each formatted I/O statement; a host thread that formats numbers with the C library at that moment can observe it.Closealso releases the calling thread’s OpenMP worker pool, so no worker thread is still shutting down when the host process exits. Close each handle on the thread that stepped it, before that thread ends; a pool left behind by a thread that ends without closing can deadlock the MinGW OpenMP runtime at process exit on Windows.Handles are raw pointers checked against a registry of live handles before any use.
CloseonNULLis a no-op. A closed, double-closed, or foreign pointer is rejected withCD_C_BAD_HANDLEand is never dereferenced. One limitation is inherent to a pointer ABI: if a stale copy’s address has been reused by a newCreate, the copy cannot be told apart from the new handle.Re-initialisation is atomic for decks. Calling
CableDyn_InitDeckorCableDyn_InitDeckAton an initialised handle replaces the model only on success. If the new initialisation fails, the previous model is kept unchanged, and so is the state saved byCableDyn_SaveState; a successful one discards that save.CableDyn_InitLineandCableDyn_InitLinesbuild the point model in place: after a failure the handle must be initialised again, and the saved state is discarded. The one exception is a handle whose deck model runs on the coupled aggregate (finite-EI lines, bodies or rods): a failedInitLine/InitLinesleaves that model live, and its saved state with it.Time. The library keeps no clock. Only
dtentersCableDyn_Step, and the host owns the time line.
Status codes
Code |
Value |
Meaning |
|---|---|---|
|
0 |
success |
|
1 |
the handle is |
|
2 |
a memory allocation failed |
|
3 |
an argument was rejected. Causes include a |
|
4 |
a numerical failure: the static solve, an initial-acceleration solve, a hard failure in a dynamic step, or a failed load or derivative evaluation |
|
5 |
the call needs a model, and no |
A step that finishes without meeting the Newton tolerance still returns CD_C_OK. The
converged flag reports that case; see the step outcomes.
Diagnostics
Each handle keeps the text of its last failure, up to 4096 characters. The message is set by a
failing call on a valid handle and cleared by a successful one. CableDyn_GetLastError does
not change it, and neither does CableDyn_IsInitialized. A call rejected with
CD_C_BAD_HANDLE has no handle to store a message in. CableDyn_GetLastError on such a
pointer returns "CableDyn C API: bad handle". Read the message right after the failing call.
Any later call on the same handle can replace it.
Coupled DOF layout
q_coupled, v_coupled, a_coupled, and coupled_loads all use the same layout, and
so do the rows and columns of every Jacobian. The layout is fixed when the handle is
initialised.
CableDyn_InitDeckorCableDyn_InitDeckAt, point route (EI = 0lines only)One
x, y, zblock for every deck point that is attached to at least one line end, inPOINTStable order. This includes points of every type:Fixedanchors,Coupled/Vesselpoints, andFree/Connectpoints.n_coupled_dofis three times the number of attached points. A point referenced by no line gets no block, but it is still counted byCableDyn_NPoints.CableDyn_InitDeckorCableDyn_InitDeckAt, aggregate route (finite-EI lines, bodies, rods)One
x, y, zblock for every host-driven point and nothing else: first theCoupled/Vesselpoints attached to anEI = 0line, inPOINTStable order, then the fairlead (End A, which must beCoupled/Vessel) of each finite-EI line, inLINEStable order.Fixedanchors,Free/Connectpoints, bodies and rods have no block.CableDyn_InitLinen_fixedentries. Entrykis the DOFfixed_dofs[k], in the order the caller listed them.CableDyn_InitLineswithout acoupled_mapThe concatenation, in line order, of each line’s
fixed_dofslist.n_coupled_dofis the sum ofn_fixed.CableDyn_InitLineswith acoupled_mapLine
i’sj-th fixed DOF maps to system DOFcoupled_map[6*i + j](0-basediandj, 1-based value). Lines that share a system DOF share that end point: its motion is applied to every such line, and their loads and Jacobian entries are summed.n_coupled_dofis the largest map entry.
Every block is prescribed by the caller on each Step and UpdateStates, including the
anchor blocks of a deck. Fill the vectors once from CableDyn_GetCoupledMotion, then
overwrite only the blocks the host drives. Deck Free and Connect points are the
exception, described under CableDyn_Step.
On a deck model, CableDyn_GetCoupledLayout (ABI 1.2) returns the deck point id and type
of every block, so a host does not have to derive the layout from the deck: the host drives
the blocks of type CD_C_POINT_COUPLED. Coupling a host (CFD) solver works through an
example.
State, units, and load sign
Quantity |
Unit |
Definition |
|---|---|---|
|
m |
global coordinates of the coupled DOFs, not displacements |
|
m/s |
time derivative of |
|
m/s² |
time derivative of |
|
N |
force exerted by the cables on the host at each coupled DOF, in global axes |
|
N/m |
\(\partial L/\partial q\) |
|
N·s/m |
\(\partial L/\partial v\) |
|
kg |
\(\partial L/\partial a\) and \(M_a = -\partial L/\partial a\) |
Load sign. Each line satisfies \(M\ddot{q} + f_{\text{int}} - f_{\text{ext}} = R\), where \(R\) is the support reaction the host must supply at the prescribed DOFs. The returned load is \(L = -R\), the equal and opposite force the cable applies to the host. This is the MoorDyn coupled-load convention. It includes the cable’s end inertia, and for deck models also the weight, buoyancy, seabed, and hydrodynamic shares at the end node. A cable under tension pulls each attached point toward the cable. For example, a line running along \(+x\) from end A to end B returns \(L_x > 0\) at A and \(L_x < 0\) at B. When lines share a point, their loads at that point are summed.
Jacobians. For the tight Newton loop of a monolithic host, the linear load model is \(L(q+\delta q, v+\delta v, a+\delta a) \approx L + J_q\,\delta q + J_v\,\delta v - M_a\,\delta a\).
Coupling a host (CFD) solver
This section is for a host that moves a body with its own equations of motion, typically a CFD
solver with a rigid-body solver, and drives the deck’s Coupled points as fairleads on that
body. It goes through the coupling step by step and compares each step with MoorDyn’s C API
(MoorDyn_Create, MoorDyn_Init, MoorDyn_Step, MoorDyn_Serialize), because a host
written for MoorDyn needs the differences below, not only renamed calls. The functions are
those of ABI 1.2: check CableDyn_GetAbiMinor() >= 2.
The example is the floating box of foamMooring’s EsfLOWC tutorial,
tests/data/foam_esflowc_box_points.dat: four slack EI = 0 lines from Fixed anchors
1 to 4 to Coupled fairleads 5 to 8. The deck places the fairleads at
\(z = -0.062\) m; the CFD box starts with them at \(z = -0.077\) m.
Step 1: initialise at the host’s positions
MoorDyn. MoorDyn_Create parses the input file; MoorDyn_Init(X, XD) then moves the
coupled points to the host’s X and computes the initial condition there.
CableDyn. CableDyn_InitDeckAt(h, deck, deck_len, q0, n_q0, &err) does both in one call: it
parses the deck and solves the statics with every line-bound Coupled/Vessel point held at
its entry of q0. CableDyn_InitDeck solves them at the deck coordinates instead, and a
host whose body starts elsewhere then gives the lines a start-up transient on its first steps.
For the box, the deck statics followed by fairleads held 15 mm lower move line 4’s fairlead load
by up to 0.38 N, 74 % of its 0.52 N static value, and it is still 0.04 N off after 1 s
(\(\Delta t = 10^{-3}\) s). After CableDyn_InitDeckAt the held fairleads keep their
load to round-off (\(2 \times 10^{-16}\) N over the same second), and line 4’s fairlead load
is the standalone value of the deck moved to the CFD positions, (0.3026, -0.0761, -0.4114) N
(MoorDyn: (0.3034, -0.0763, -0.4136) N).
q0 is one of two vectors; its length selects which:
the
Coupled/Vesselblocks alone, in coupled-vector order: 3 values per host point. This is the layout of MoorDyn’sXfor a deck whose coupled objects are points that are all attached to lines (MoorDyn’sXalso holds aCoupledpoint attached to no line, which has no block here), and the one a host that knows only its fairleads can build before any handle exists;the full coupled vector,
CableDyn_NCoupledDOFvalues. ItsFixed,FreeandConnectblocks are not read.
The statics start at rest: there is no counterpart of MoorDyn’s XD. A host whose body is
already moving imposes the velocities on the coupled blocks with CableDyn_UpdateStates
after the initialisation; the line interiors stay at rest.
Step 2: build the coupled vector from the layout
MoorDyn. The coupled vector holds the coupled objects and nothing else: 6 values per coupled body or cantilevered rod, 3 per pinned rod or coupled point.
CableDyn. On the point route (decks of EI = 0 lines) the vector has a block for every
line-bound point, Fixed anchors included, in POINTS order. On the aggregate route
(finite-EI lines, bodies or rods) only the host-driven points have blocks (Coupled DOF
layout). Ask the handle rather than deriving the layout from the deck:
int n = CableDyn_NCoupledDOF(h, &err); /* 24 for the box */
int ids[8], types[8]; /* n / 3 blocks */
CableDyn_GetCoupledLayout(h, ids, types, n / 3, &err);
Block |
DOFs |
|
|
|---|---|---|---|
0 to 3 |
0 to 11 |
1 to 4 |
|
4 to 7 |
12 to 23 |
5 to 8 |
|
Seed q, v and a once from CableDyn_GetCoupledMotion after the initialisation,
and before each step overwrite only the CD_C_POINT_COUPLED blocks. Every block is
prescribed on every step, so the anchor blocks must keep the values GetCoupledMotion
returned: zeros there would move the anchors to the origin. CableDyn_CalcOutput returns a
load for every block, and the anchor blocks carry the anchor reactions, which are not loads on
the host.
Step 3: step with the motion at the end of the step
MoorDyn. MoorDyn_Step(X, XD, F, &t, &dt) takes the coupled state at the start of the
step, \(t\), extrapolates it over the step with XD, and returns the forces at
\(t + \Delta t\).
CableDyn. CableDyn_Step(h, dt, q, v, a, ...) takes the prescribed position, velocity and
acceleration at the end of the step, \(t + \Delta t\), and CableDyn_CalcOutput then
returns the loads at \(t + \Delta t\). Pass the host’s motion for the new time level (in a
CFD time step, the body’s current iterate for it) with a consistent acceleration: the
integrator uses v and a, not only q. A host without body accelerations can use the
backward difference \(a = (v(t + \Delta t) - v(t)) / \Delta t\).
Step 4: outer correctors save and restore
A host that iterates its time step (PIMPLE outer correctors, predictor-corrector schemes) advances the mooring once per pass and must rewind it between passes, or the mooring advances by several steps per host step.
MoorDyn. The host serialises the system at the start of the time step
(MoorDyn_Serialize) and deserialises it before every repeated pass.
CableDyn. CableDyn_SaveState copies the whole model into the handle’s single save slot
(every line, point, body and rod state with its solver history); CableDyn_RestoreState
returns to it. A restore keeps the slot, so one save at the first pass of a time step serves
every later pass. A restore followed by a step is bit-identical to taking that step from the
saved state directly, and any number of steps may lie between the save and the restore (the
sub-steps of Step 6). A successful Init* call discards the save.
Key the save on the start time of the pass, \(t_{prev} = t - \Delta t\), not on its end
time \(t\). Save when a pass starts later than the saved state, which happens once the
previous step has been accepted and the model stands at \(t_{prev}\); restore before
every other pass, which starts from the same \(t_{prev}\) whatever its \(\Delta t\).
A host that rejects a step and retries it with a smaller \(\Delta t\) thus restores: keyed
on \(t\), the retry would look like a new step and save a model that the rejected pass has
already advanced. Compare the start times with a tolerance, as foamMooring’s
sixDoFMooringRestraints moorDynR2 does (a new step when
\(t_{prev} - t_{saved} \ge 10^{-3}\,\Delta t\)), since \(t - \Delta t\) is subject to
round-off.
The names differ from MoorDyn’s file calls: MoorDyn_SaveState writes the state to a file,
CableDyn_SaveState keeps it in memory. Serialising the state to a caller buffer or a
restart file (MoorDyn_Serialize, MoorDyn_Save/MoorDyn_Load) is not supported. A
restarted host re-initialises with CableDyn_InitDeckAt at its restart positions, which
gives the statics there rather than the dynamic state.
Step 5: sum the loads into a rigid-body force and moment
The sign convention is MoorDyn’s: each block of coupled_loads is the force the lines exert
on that point, in newtons, in global axes (State, units, and load sign). For a rigid body
with reference point \(r\), sum the CD_C_POINT_COUPLED blocks only:
where \(x_k\) is the fairlead position the step was taken with. The reference point
depends on the host’s restraint: foamMooring’s moorDynR2 of rigidBodyMooringRestraints
in point mode accumulates \(x_k \times f_k\), the moment about the global origin
(\(r = 0\)), while its moorDynR2 of sixDoFMooringRestraints accumulates
\((x_k - x_{CoR}) \times f_k\) about the body’s centre of rotation
(\(r = x_{CoR}\)). Do not add the anchor blocks.
Step 6: fixed step on the aggregate route
On the point route each CableDyn_Step takes any \(\Delta t > 0\). On the aggregate route
every step must use the deck’s dtM: a host step of \(m\) times dtM is taken as
\(m\) calls of CableDyn_Step, with the host-driven motion interpolated linearly between
the start and the end of the host step. One CableDyn_SaveState before the first sub-step
and one CableDyn_RestoreState undo all of them. MoorDyn sub-steps internally with its own
dtM.
Step 7: units and frame
SI units in the global right-handed frame with z up and still water at \(z = 0\) (Conventions), as in MoorDyn. A host in another frame or unit system converts the motion it passes and the loads it receives.
Call sequence
The calls a restraint of a CFD rigid-body solver makes, with n_fair fairleads on one body
whose positions x_fair (3 * n_fair values in coupled-vector order: on the point route
the deck’s POINTS order of the Coupled points) the host computes from the body pose:
/* First call, the body at its initial pose. */
CableDyn_Create(&h, &err);
CableDyn_InitDeckAt(h, deck, (int)strlen(deck), x_fair, 3 * n_fair, &err);
n = CableDyn_NCoupledDOF(h, &err);
CableDyn_GetCoupledLayout(h, ids, types, n / 3, &err); /* host blocks: CD_C_POINT_COUPLED */
CableDyn_GetCoupledMotion(h, q, v, a, n, &err); /* seed every block */
t_saved = -HUGE_VAL; /* nothing saved yet */
/* Every call: a pass of the host time step from t_prev = t - dt to t. */
if (t_prev - t_saved >= 1.0e-3 * dt) { /* a new step: the model is at t_prev */
CableDyn_SaveState(h, &err);
t_saved = t_prev;
} else { /* a repeated pass from t_saved, any dt */
CableDyn_RestoreState(h, &err);
}
/* the fairlead motion at t (the END of the step) into the host blocks, in layout order */
for (j = 0, k = 0; k < n / 3; ++k) {
if (types[k] != CD_C_POINT_COUPLED) continue;
for (c = 0; c < 3; ++c) {
q[3 * k + c] = x_fair[3 * j + c];
v[3 * k + c] = v_fair[3 * j + c];
a[3 * k + c] = a_fair[3 * j + c];
}
++j;
}
CableDyn_Step(h, dt, q, v, a, n, &converged, &stalled, &n_iter, &err);
CableDyn_CalcOutput(h, f, n, &err);
/* F = sum of f over the host blocks; M = sum of (x - r) x f over the same blocks */
/* End of the run. */
CableDyn_Close(&h, &err);
Check err after every call. On the aggregate route, replace the single CableDyn_Step
by the sub-steps of Step 6.
Function reference
Every table uses these columns:
Dir:
in,out, orinout.Length: the element count read or written. A dash means a scalar.
NULL: whether the pointer may be
NULL. A dash means a value argument.
Version queries
void CableDyn_GetVersion(int *major, int *minor, int *patch, int *abi_version);
void CableDyn_GetVersionString(char *version, int version_len);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
out |
|
1 each |
no (not checked) |
library release and C ABI version |
|
out |
|
|
yes: no-op |
receives |
|
in |
|
– |
– |
buffer capacity in bytes. A value below 1 makes the call a no-op. |
Neither function needs a handle. There is no status argument.
CableDyn_Create
void CableDyn_Create(void **handle, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
out |
|
1 |
no (not checked) |
receives a new, uninitialised handle, or |
|
out |
|
1 |
no (not checked) |
|
The handle stays uninitialised until an Init* call succeeds.
CableDyn_Close
void CableDyn_Close(void **handle, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
inout |
|
1 |
pointer: no (not checked). |
handle to release. Set to |
|
out |
|
1 |
no (not checked) |
|
*handle == NULL is a no-op that returns CD_C_OK. A pointer that is not a live handle is
left untouched: it is not freed, it is not set to NULL, and CD_C_BAD_HANDLE is
returned. If two threads close the same handle, or copies of it, exactly one of them frees it.
That thread gets CD_C_OK and the others get CD_C_BAD_HANDLE.
CableDyn_InitDeck
void CableDyn_InitDeck(void *handle, const char *deck_path, int deck_path_len, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
handle from |
|
in |
|
|
no: |
path of a |
|
in |
|
– |
– |
number of bytes to read. It must be at least 1, and the first byte must not be |
|
out |
|
1 |
no (not checked) |
status |
The call parses and validates the deck and chooses one of two routes. A deck of EI = 0 lines
without BODIES or RODS runs on the point route: the call builds every line as an
EI = 0 model, solves each line’s static equilibrium with its end points at their deck
coordinates, and connects the lines through the deck points. A deck with finite-EI lines,
BODIES or RODS runs on the aggregate route described next. On both routes gravity,
water density, seabed, currents, line hydrodynamic coefficients, rhoInf, and the dynamic
Newton settings are taken from the deck. The deck’s OUTPUTS section is parsed and validated,
but the C ABI writes no output files.
Finite-EI lines, bodies and rods. A deck with finite-EI (EI > 0) lines or with a
BODIES or RODS table (Point3 buoys, free Rigid6 bodies, or free, fixed and
Pinned rods, with their lines; a Pinned rod turns about its fixed End A) runs on the same
coupled aggregate as the OpenFAST module, with these differences from the point-system decks:
the coupled DOFs are the
x, y, zof the deck’sCoupled/Vesselpoints only, in the order of Coupled DOF layout (anchors are not part of the vector); the bodies and rods are integrated internally;the deck must declare
dtM, and everyCableDyn_Stepmust use thatdt;the run is in still water: deck waves or currents are rejected, and
CableDyn_UpdatePointFluidFieldsandCableDyn_CalcOutputDerivativesreturnCD_C_BAD_INPUT;CableDyn_NPointsandCableDyn_NLinesreport the deck’s point and line counts.
The deck is validated under the standalone-deck rules:
dtMandTMaxmust both be present or both be absent.A deck with
FreeorConnectpoints must declaredtM, and thereforeTMax.On the point route the time-step values themselves are not used: the step size is the
dtpassed to eachCableDyn_Step. On the aggregate routedtMis required and is the fixed step of everyCableDyn_Step.
CableDyn_InitDeck rejects the following with CD_C_BAD_INPUT:
Deck content |
Where it is supported |
|---|---|
|
the OpenFAST module; the standalone driver with a |
|
the OpenFAST module |
a |
the standalone driver (prescribe motion through |
a |
the standalone driver; the OpenFAST module |
a |
the OpenFAST module |
|
the OpenFAST module |
a |
the standalone driver; supply time-varying fluid kinematics through
|
FAST.Farm |
the OpenFAST module in FAST.Farm |
|
the point-system solver does not combine seabed friction with |
Important
The C ABI has no simulation clock and no per-node fluid input. A deck’s current is applied
as a steady field. A deck with a waves option is rejected with CD_C_BAD_INPUT,
because its waves could not be evaluated. Time-varying fluid kinematics reach the Free
and Connect points through CableDyn_UpdatePointFluidFields.
Parse and validation failures, including a file that cannot be opened, return
CD_C_BAD_INPUT. A static-solve or system-assembly failure returns CD_C_SOLVE_FAIL. See
Coupled DOF layout for the resulting vectors.
CableDyn_InitLine
void CableDyn_InitLine(void *handle, int n_nodes, int n_elem, const double *q0,
const double *v0, const int *elem_conn, const double *l0,
const double *ea, const double *rho_a,
const int *fixed_dofs, int n_fixed, int tension_only,
double rho_inf, int *err_stat);
Builds one line from caller-meshed arrays. The model is structural only: two-node EI = 0
axial elements with a consistent mass matrix. It has no gravity or buoyancy (the external load
is zero), no seabed, no hydrodynamics, and no structural damping. There is no static solve. The
state starts at q0, v0 exactly, and the initial acceleration is computed from that
state. To get gravity, seabed, and hydrodynamics, use CableDyn_InitDeck.
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
handle from |
|
in |
|
– |
– |
node count, at least 2 |
|
in |
|
– |
– |
element count, at least 1 |
|
in |
|
|
no |
initial nodal positions [m], global, interleaved |
|
in |
|
|
no |
initial nodal velocities [m/s] |
|
in |
|
|
no |
1-based node pair of each element, |
|
in |
|
|
no |
unstretched element length [m], greater than 0 |
|
in |
|
|
no |
axial stiffness EA [N], greater than 0 |
|
in |
|
|
no |
mass per unit unstretched length [kg/m], greater than 0 |
|
in |
|
|
only if |
1-based DOF indices in |
|
in |
|
– |
– |
number of prescribed DOFs, at least 0. This becomes |
|
in |
|
– |
– |
nonzero: an element carries no compression (it goes slack). Zero: the element is linear in tension and compression. |
|
in |
|
– |
– |
generalised-α high-frequency spectral radius, in |
|
out |
|
1 |
no (not checked) |
status |
Invalid counts, NULL arrays, non-finite values, non-positive l0, ea, or rho_a,
out-of-range connectivity, out-of-range or duplicate fixed_dofs, and a rho_inf outside
[0, 1] (including NaN) all return CD_C_BAD_INPUT. A failure of the initial acceleration
solve returns CD_C_SOLVE_FAIL.
The Newton settings are fixed at their
defaults: relative residual tolerance \(10^{-8}\), at most 30 iterations per step, and at
most 12 line-search backtracks. So is the generalised-α scheme: these lines always take the
force-blended step (alpha_force_blend True, with the release cap on a tension-only line,
Theory), with the rotation- and snap-resolved sub-steps of ei0_substeps auto at
their default settings (OPTIONS reference and defaults). They cannot select the configuration blend of 0.1.1
and earlier (False alpha_force_blend) or off ei0_substeps, which only a deck sets,
through CableDyn_InitDeck.
CableDyn_InitLines
void CableDyn_InitLines(void *handle, int n_lines, const int *n_nodes,
const int *n_elem, const int *n_fixed, const double *q0,
const double *v0, const int *elem_conn,
const double *l0, const double *ea,
const double *rho_a, const int *fixed_dofs,
const int *coupled_map, int n_coupled_map,
int tension_only, double rho_inf, int *err_stat);
Builds several structural-only lines, with the same model as CableDyn_InitLine, in one
system. Every per-line array is the concatenation, in line order, of that line’s
CableDyn_InitLine array. Connectivity and fixed_dofs stay local to each line: node
1 is the first node of that line. tension_only and rho_inf apply to every line.
In the table below, \(N = \sum n\_nodes\), \(E = \sum n\_elem\), and \(F = \sum n\_fixed\).
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
handle from |
|
in |
|
– |
– |
line count, at least 1 |
|
in |
|
|
no |
per-line counts: at least 2, at least 1, and at least 0 |
|
in |
|
|
no |
initial positions [m] and velocities [m/s] |
|
in |
|
|
no |
line-local 1-based node pairs |
|
in |
|
|
no |
unstretched length [m], EA [N], mass per unit length [kg/m] |
|
in |
|
|
only if |
line-local 1-based prescribed DOFs |
|
in |
|
|
only if |
optional map from line fixed DOFs to shared system DOFs (below) |
|
in |
|
– |
– |
either 0 (no map) or exactly |
|
in |
|
– |
– |
as in |
|
out |
|
1 |
no (not checked) |
status |
The coupled map. Supply a map to connect lines at shared points. The map has these requirements:
Every line must have exactly six fixed DOFs (
n_fixed[i] == 6), normally thex, y, zof its two end nodes.The map holds six entries per line. Entry
6*i + j(0-based) is the 1-based system DOF that receives linei’sj-th fixed DOF, in that line’sfixed_dofsorder.Entries must be positive and must use every index from 1 to their maximum with no gaps. That maximum becomes
CableDyn_NCoupledDOF.The map is checked per DOF only. Keep shared DOFs grouped as whole
x, y, ztriples so that a shared point stays one point.
Lines that share a DOF should start with the same coordinate there. CableDyn_GetCoupledMotion
rejects shared DOFs whose line states differ by more than \(10^{-10}\) in q, v, or
a until the first Step or UpdateStates makes them equal.
Without a map, lines are independent, and their fixed DOFs are concatenated into the coupled vector.
Raw-array handles have no deck points: CableDyn_NPoints is 0, and every coupled DOF is
prescribed by the caller.
CableDyn_UpdateStates
void CableDyn_UpdateStates(void *handle, const double *q_coupled, const double *v_coupled,
const double *a_coupled, int n_coupled_dof, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
initialised handle |
|
in |
|
|
only if |
coupled positions [m], velocities [m/s], and accelerations [m/s²] |
|
in |
|
– |
– |
must equal |
|
out |
|
1 |
no (not checked) |
status |
Sets the coupled DOFs of every line to the supplied motion without advancing time. Interior node
positions and velocities are kept. Interior accelerations are recomputed from the equations of
motion at the new boundary state, so CableDyn_CalcOutput afterwards returns the loads for
that state. Use this call for a predictor–corrector host, or to evaluate loads at a trial
motion. Every block is written, including Free and Connect point blocks of a deck model.
The call is atomic. A non-finite value returns CD_C_BAD_INPUT, and any failure leaves the
model unchanged.
CableDyn_UpdatePointFluidFields
void CableDyn_UpdatePointFluidFields(void *handle, const double *fluid_velocity,
const double *fluid_acceleration,
const double *waterline_z, int n_point,
double fluid_density, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
handle initialised by |
|
in |
|
|
only if |
ambient fluid velocity at each point [m/s], global, interleaved |
|
in |
|
|
only if |
ambient fluid acceleration at each point [m/s²] |
|
in |
|
|
only if |
global z of the free surface above each point [m]. A point is wetted when its z is at or below this value. |
|
in |
|
– |
– |
must equal |
|
in |
|
– |
– |
fluid density [kg/m³], finite and at least 0. A value of 0 disables point hydrodynamics. |
|
out |
|
1 |
no (not checked) |
status |
Stores the fields used by the lumped hydrodynamic load on deck Free and Connect points,
which is built from their CdA, Ca, and Vol columns:
The load applies while the point is wetted. Fields for points of other types are stored but
unused. The values persist until the next call. Before the first call the density is 0, so no
point hydrodynamic load is applied. The constant net weight
\((\rho_w V - m)\,g\), taken from the deck’s rhoW and g, is always applied,
independently of this call.
A handle initialised from raw arrays has no deck points, so on such a handle the call always
returns CD_C_BAD_INPUT (a non-zero n_point fails the count check, and n_point = 0
fails because the model has no point system). The call is atomic: every input is validated before
any value
is stored. A non-finite field returns CD_C_BAD_INPUT.
CableDyn_Step
void CableDyn_Step(void *handle, double dt, const double *q_coupled,
const double *v_coupled, const double *a_coupled,
int n_coupled_dof, bool *converged, bool *stalled,
int *n_iter, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
initialised handle |
|
in |
|
– |
– |
step size [s], finite and greater than 0 |
|
in |
|
|
only if |
prescribed coupled motion at the end of the step, \(t+\Delta t\) |
|
in |
|
– |
– |
must equal |
|
out |
|
1 |
no (not checked) |
true if every line met the Newton tolerance over the full step |
|
out |
|
1 |
no (not checked) |
true if a line’s line search stalled (backtracking exhausted) |
|
out |
|
1 |
no (not checked) |
largest Newton iteration count over the lines |
|
out |
|
1 |
no (not checked) |
status |
converged, stalled, and n_iter are always written. They are cleared on entry, even
when the call is rejected. The step advances every line over \([t, t+\Delta t]\) with the
Chung–Hulbert generalised-α method. The prescribed DOFs are held at the supplied end-of-step
q, v, and a. Pass a consistent v and a: the integrator uses them, not only
q.
Step outcomes.
Outcome |
Returned |
Committed state |
|---|---|---|
converged |
|
the solution at \(t+\Delta t\) |
soft non-convergence: the Newton iteration limit was reached, or the line search stalled |
|
the best iterate at \(t+\Delta t\), with the prescribed DOFs at their supplied values. The model has advanced; do not repeat the step. |
hard failure, for example a singular tangent or an assembly or constitutive failure |
|
rolled back to the state at \(t\), including any viscoelastic or Syrope history and
deck point states; on the aggregate route the whole model returns to the step start,
bodies and rods included. The step can be retried, for example with a smaller |
rejected input: a size mismatch, a |
|
no step is taken |
Before it reports a soft non-convergence, the step retries internally. It rolls back to \(t\) and re-solves the interval as two half steps. The prescribed motion at the midpoint is the linear interpolation of the motion at the start and end of the step. A half step that also fails to converge is split again, down to \(\Delta t/64\).
If the subdivided solution covers the whole interval with convergence, that solution is committed, and
converged = true.Otherwise the model is restored to \(t\) and the single full-
dtstep is taken again. Its best iterate and flags are returned, so a partial subdivided state is never committed.
The retry costs time only on steps that would otherwise fail to converge.
Deck models with Free or Connect points. These points are integrated by CableDyn, not prescribed. Each step:
Advances the points from the summed end loads of the attached lines, the constant net weight, and any point hydrodynamic load.
Solves the lines implicitly with the new point positions as end conditions.
The point update treats the attached line ends’ own mass (structural and added), end-segment
stiffness, and axial damping linearly implicitly, so a massless Connect junction stays
stable and the end segments set no explicit dt limit. dt still has to resolve the
response of interest. A step whose point motion becomes non-finite fails with an error that
names the point. The host’s
entries for Free and Connect blocks are not used as prescribed motion. Pass the values
last returned by CableDyn_GetCoupledMotion for those blocks. After the step, read them back
to obtain the integrated point motion.
CableDyn_CalcOutput
void CableDyn_CalcOutput(void *handle, double *coupled_loads, int n_coupled_dof, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
initialised handle |
|
out |
|
|
only if |
loads by the cables on the host [N], global axes |
|
in |
|
– |
– |
must equal |
|
out |
|
1 |
no (not checked) |
status |
Evaluates the loads from the current state:
after
Init*, the initial or static state,after
CableDyn_Step, the committed state at \(t+\Delta t\),after
CableDyn_UpdateStates, the updated state.
The model state is not changed. Once the size and pointer checks pass, the buffer is set to zero before evaluation, so a failed evaluation leaves it all zero.
CableDyn_CalcOutputDerivatives
void CableDyn_CalcOutputDerivatives(void *handle, const double *q_coupled,
const double *v_coupled, const double *a_coupled,
int n_coupled_dof, double eps_fd,
double *coupled_loads, double *dload_dq,
double *dload_dv, double *dload_da,
double *added_mass, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
initialised handle |
|
in |
|
|
only if |
the operating point: coupled positions, velocities, and accelerations |
|
in |
|
– |
– |
|
|
in |
|
– |
– |
kept for ABI compatibility. It must be at least 0 ( |
|
out |
|
|
only if |
loads at the operating point [N] |
|
out |
|
|
only if |
\(\partial L/\partial q\) [N/m], column-major |
|
out |
|
|
only if |
\(\partial L/\partial v\) [N·s/m], column-major |
|
out |
|
|
only if |
\(\partial L/\partial a\) [kg], column-major |
|
out |
|
|
only if |
\(M_a = -\partial L/\partial a\) [kg], column-major |
|
out |
|
1 |
no (not checked) |
status |
The derivative blocks are analytic, not finite differences. They are assembled from the exact
element, contact, and hydrodynamic tangents of each line and summed through the coupled layout.
Each line’s interior DOFs are condensed out at the operating point: interior positions and
velocities are held, and interior accelerations are eliminated through the consistent mass
matrix, including added mass. The blocks therefore give the instantaneous response of the end
loads to the end motion, the linearisation a tightly coupled host needs. The acceleration block
is \(-(M_{cc} - M_{cf} M_{ff}^{-1} M_{fc})\). Every coupled DOF is differentiated as a
prescribed DOF, including Free and Connect blocks.
Warning
This call sets the model state; it is not a pure query. On return, the model is at the
supplied operating point: the coupled DOFs hold the supplied q, v, and a, and the
interior accelerations are recomputed, as after CableDyn_UpdateStates. This holds on
success, and also when a later stage of the call fails. The previous state is not restored.
A host that needs its previous state back must call CableDyn_UpdateStates with it
afterwards.
Rejected arguments leave the model unchanged: a bad size, a NULL array, eps_fd < 0,
or an operating point that UpdateStates rejects. If n*n exceeds the int range,
the call returns CD_C_BAD_INPUT.
After the pointer checks pass, all five output buffers are set to zero, so a failed call leaves them all zero.
CableDyn_GetCoupledMotion
void CableDyn_GetCoupledMotion(void *handle, double *q_coupled, double *v_coupled,
double *a_coupled, int n_coupled_dof, int *err_stat);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
no: |
initialised handle |
|
out |
|
|
only if |
current coupled positions [m], velocities [m/s], and accelerations [m/s²] |
|
in |
|
– |
– |
must equal |
|
out |
|
1 |
no (not checked) |
status |
Copies the current coupled motion. Right after CableDyn_InitDeck this is the static
solution: the deck coordinates of every attached point, with zero velocity and acceleration.
After CableDyn_InitDeckAt the host-driven blocks hold their entries of q0 instead.
Use it to seed the host’s coupled vectors before the first step. The buffers are set to zero
before the copy and stay zero if the call fails. The model is not changed.
CableDyn_GetLastError
void CableDyn_GetLastError(void *handle, char *message, int message_len);
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
– |
yes |
the handle to query. For an invalid handle the text is |
|
out |
|
|
yes: no-op |
receives the message, truncated to |
|
in |
|
– |
– |
buffer capacity in bytes. A value below 1 makes the call a no-op. A message is at most
4096 characters, so a buffer of 4097 bytes always holds the full message and its
terminating |
State and size queries
bool CableDyn_IsInitialized(void *handle);
int CableDyn_NCoupledDOF(void *handle, int *err_stat);
int CableDyn_NPoints(void *handle, int *err_stat);
int CableDyn_NLines(void *handle, int *err_stat);
Function |
Returns |
|---|---|
|
|
|
length of the coupled vectors (Coupled DOF layout) |
|
number of deck points, the |
|
number of lines in the model |
The three counting queries return 0 whenever *err_stat is not CD_C_OK:
CD_C_BAD_HANDLE for an invalid pointer, CD_C_NOT_INITIALIZED before a successful
Init*. err_stat must be valid storage.
Object queries
ABI 1 minor extension 1. These read the committed state of an initialised handle at any step:
the objects of the model and any output channel, through the evaluator behind the deck
OUTPUTS channels and the per-line .Line<L>.p.out/.t.out files, so a query returns the
value the standalone driver writes for the same state.
int CableDyn_GetAbiMinor(void);
int CableDyn_NObjects(void *handle, int kind, int *err_stat);
void CableDyn_GetObjectInfo(void *handle, int kind, int index, int *id,
int *n_nodes, int *subtype, int *err_stat);
void CableDyn_GetLineValues(void *handle, int index, int quantity, double *out,
int n_out, int *err_stat);
void CableDyn_GetPointState(void *handle, int index, double *pos, double *vel,
double *force, int *err_stat);
void CableDyn_GetBodyState(void *handle, int index, double *pose, double *vel,
double *acc, double *wrench, int *err_stat);
void CableDyn_GetRodState(void *handle, int index, double *nodes, int n_nodes,
double *pose, double *vel, double *wrench, int *err_stat);
void CableDyn_EvalChannel(void *handle, const char *token, int token_len,
double *value, int *err_stat);
kind is CD_C_OBJ_LINE (1), CD_C_OBJ_POINT (2), CD_C_OBJ_BODY (3) or
CD_C_OBJ_ROD (4). index is 0-based in the handle’s inventory: lines in ascending deck
id, points in system order, bodies and rods in solver order. id is the deck id (a raw-line
handle numbers its lines 1..n).
Function |
Result |
|---|---|
|
number of objects of |
|
|
|
one quantity at every node of the line’s final mesh, End A first:
|
|
position, velocity and the resultant force of the attached lines ( |
|
6 doubles each: pose |
|
node positions End A (node 0) to End B, 3 doubles per node; pose |
|
the value of one |
On a finite-EI line whose mesh the static solve refined, n_nodes and the
CableDyn_GetLineValues arrays cover every node of the refined mesh, while a node token of
CableDyn_EvalChannel (Ten1N5, L1N5px) names deck node 5 at its node of that mesh
(Node channels on a refined line); CD_C_LINE_DECK_NODE locates it: the array row whose value is
5.
A NULL output pointer skips that output, except for the out array of
CableDyn_GetLineValues and the value of CableDyn_EvalChannel; the latter is not
checked and must point to valid storage. An index out of range, a wrongly sized buffer, an
unknown quantity or a malformed token returns CD_C_BAD_INPUT, as does a channel the handle
cannot evaluate (for example TDP<L> on a point-route deck or an unknown object id); a failed
state query or a non-finite value returns CD_C_SOLVE_FAIL. CableDyn_GetLastError gives
the reason. The queries do no file I/O and follow the threading rules of the other per-handle
calls.
CableDyn_EvalChannel reads token_len bytes from token, which need not be
NUL-terminated; an embedded NUL ends the token early, so token_len may be the
capacity of a larger buffer, as for the other string arguments. token_len must be at least 1,
and a token longer than 64 characters (the longest channel name) is rejected with
CD_C_BAD_INPUT.
Host coupling calls
ABI 1 minor extension 2: check CableDyn_GetAbiMinor() >= 2 before calling these.
Coupling a host (CFD) solver shows how they fit together.
#define CD_C_POINT_FIXED 1
#define CD_C_POINT_COUPLED 2 /* Coupled or Vessel */
#define CD_C_POINT_FREE 3
#define CD_C_POINT_CONNECT 4
void CableDyn_InitDeckAt(void *handle, const char *deck_path, int deck_path_len,
const double *q0, int n_q0, int *err_stat);
void CableDyn_SaveState(void *handle, int *err_stat);
void CableDyn_RestoreState(void *handle, int *err_stat);
void CableDyn_GetCoupledLayout(void *handle, int *point_id, int *point_type,
int n_block, int *err_stat);
The CD_C_POINT_* codes are also the point subtype of CableDyn_GetObjectInfo.
CableDyn_InitDeckAt. CableDyn_InitDeck with the static solve at the host’s positions of
the coupled points. handle, deck_path and deck_path_len are as for
CableDyn_InitDeck, and the call takes the same routes, rejects the same decks, and is
serialised and transactional in the same way.
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
in |
|
|
only if |
host positions [m], interleaved |
|
in |
|
– |
– |
3 times the number of line-bound |
Every line-bound Coupled/Vessel point is placed at its entry of q0 before the static
solve, and the lines, Free/Connect points, bodies and rods are solved in equilibrium with
those points held there. The result equals CableDyn_InitDeck of the deck with those points
moved to q0. The statics start at rest: every coupled velocity and acceleration is zero,
and CableDyn_GetCoupledMotion returns q0 in the host-driven blocks. A
Coupled/Vessel point referenced by no line has no block and keeps its deck coordinates.
On the aggregate route the two forms of q0 coincide; a point that has two blocks there (a
fairlead shared by an EI = 0 and a finite-EI line) needs the same position in both.
An n_q0 that is negative, not a multiple of 3, or fits neither form, a NULL or
non-finite q0, a line’s End A moved below its Fixed End B anchor (on a deck without
Free or Connect points), and the deck rejections of CableDyn_InitDeck return
CD_C_BAD_INPUT; the diagnostics of a q0 that does not fit the deck call it
coupled_positions. A static-solve failure returns CD_C_SOLVE_FAIL.
CableDyn_SaveState, CableDyn_RestoreState. One save slot per handle, for a host’s outer correctors.
Function |
Effect |
|---|---|
|
copies the whole model into the slot, replacing any earlier save: every line, point,
body and rod state with its solver history (viscoelastic, Syrope and seabed-friction
states), the point fluid fields of |
|
returns the model to the slot and keeps the slot, so one save serves any number of restores |
A restore followed by a step is bit-identical to taking that step from the saved state
directly, whatever ran between the save and the restore: the slot is a copy of the model
object, independent of the rollback buffers the steps use internally. The slot follows the
model it was taken from: a successful Init* call discards it; a failed CableDyn_InitDeck
or CableDyn_InitDeckAt keeps it with the model; a failed CableDyn_InitLine or
CableDyn_InitLines discards it, except on a handle whose model runs on the coupled
aggregate, which stays live with its save (Calling conventions). Both calls return
CD_C_NOT_INITIALIZED before an initialisation, and RestoreState returns
CD_C_BAD_INPUT when nothing has been saved since the model was initialised. Each call copies
the model, so it allocates memory of the size of the model and costs about as much as copying
it; it does no file I/O and follows the threading rules of the other per-handle calls. On the
aggregate route SaveState returns CD_C_ALLOC_FAIL when the slot itself cannot be
allocated, and the handle then holds no save; running out of memory during the copy of the
model, on either route and in either call, aborts the process, as any Fortran assignment of
allocatable data does. There is no serialisation of the state to a caller buffer.
CableDyn_GetCoupledLayout. The owner of each 3-DOF block of the coupled vector.
Argument |
Dir |
C type |
Length |
NULL |
Meaning |
|---|---|---|---|---|---|
|
out |
|
|
yes: skipped |
deck point id of block |
|
out |
|
|
yes: skipped |
|
|
in |
|
– |
– |
must equal |
The host drives the CD_C_POINT_COUPLED blocks. On the point route the blocks are the
line-bound points of every type in POINTS order; on the aggregate route they are the
Coupled/Vessel points only (Coupled DOF layout). A handle initialised from raw arrays
has no deck points, and the call returns CD_C_BAD_INPUT: its coupled vector is the caller’s
own fixed_dofs list. A wrong n_block returns CD_C_BAD_INPUT.
Examples
Both programs compile without warnings under gcc -std=c11 -Wall -Wextra -pedantic and
cl /W4.
One line from arrays
A pre-tensioned 100 m line along \(+x\). The x, y, z of both end nodes are prescribed. End B surges ±0.05 m with a 10 s period. The program needs no input file.
/* One pre-tensioned EI=0 line between two driven end points. */
#include "CableDyn_CAPI.h"
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#define N_NODES 11
#define N_ELEM (N_NODES - 1)
static int report(void *h, const char *where, int err)
{
char msg[4097]; /* a message is at most 4096 characters, plus the NUL */
CableDyn_GetLastError(h, msg, (int)sizeof msg);
fprintf(stderr, "%s failed (status %d): %s\n", where, err, msg);
return 1;
}
int main(void)
{
double q0[3 * N_NODES], v0[3 * N_NODES] = {0.0};
int elem_conn[2 * N_ELEM];
double l0[N_ELEM], ea[N_ELEM], rho_a[N_ELEM];
/* Coupled DOFs: x, y, z of node 1, then x, y, z of node N_NODES (1-based). */
const int fixed_dofs[6] = {1, 2, 3, 3 * N_NODES - 2, 3 * N_NODES - 1, 3 * N_NODES};
const double span = 100.0, dt = 0.05;
void *h = NULL;
int err = CD_C_OK;
for (int i = 0; i < N_NODES; ++i) { /* straight line along +x */
q0[3 * i + 0] = span * i / N_ELEM;
q0[3 * i + 1] = 0.0;
q0[3 * i + 2] = -50.0;
}
for (int e = 0; e < N_ELEM; ++e) {
elem_conn[2 * e + 0] = e + 1; /* 1-based node indices */
elem_conn[2 * e + 1] = e + 2;
l0[e] = 0.999 * span / N_ELEM; /* 0.1 % pre-strain, m */
ea[e] = 1.0e8; /* N */
rho_a[e] = 100.0; /* kg/m */
}
CableDyn_Create(&h, &err);
if (err != CD_C_OK) return 1;
CableDyn_InitLine(h, N_NODES, N_ELEM, q0, v0, elem_conn, l0, ea, rho_a,
fixed_dofs, 6, /*tension_only=*/1, /*rho_inf=*/0.8, &err);
if (err != CD_C_OK) { report(h, "CableDyn_InitLine", err); CableDyn_Close(&h, &err); return 1; }
const int n = CableDyn_NCoupledDOF(h, &err); /* 6 */
double q[6], v[6], a[6], loads[6];
CableDyn_GetCoupledMotion(h, q, v, a, n, &err); /* start from the initial state */
int status = 0;
for (int k = 1; k <= 200 && status == 0; ++k) {
const double t = k * dt, w = 2.0 * acos(-1.0) / 10.0, amp = 0.05;
q[3] = span + amp * sin(w * t); /* surge end B, prescribed at t */
v[3] = amp * w * cos(w * t);
a[3] = -amp * w * w * sin(w * t);
bool converged = false, stalled = false;
int n_iter = 0;
CableDyn_Step(h, dt, q, v, a, n, &converged, &stalled, &n_iter, &err);
if (err != CD_C_OK) { status = report(h, "CableDyn_Step", err); break; }
if (!converged) fprintf(stderr, "t=%.2f: step not converged (stalled=%d)\n", t, stalled);
CableDyn_CalcOutput(h, loads, n, &err);
if (err != CD_C_OK) { status = report(h, "CableDyn_CalcOutput", err); break; }
if (k % 50 == 0)
printf("t=%6.2f s Fx(A)=%12.1f N Fx(B)=%12.1f N iters=%d\n", t, loads[0], loads[3], n_iter);
}
CableDyn_Close(&h, &err);
return status;
}
The tension pulls end A toward \(+x\) and end B toward \(-x\), so Fx(A) is positive
and Fx(B) is negative. Both oscillate about the 0.1 % pre-tension of about 100 kN.
A mooring deck
Initialise from a deck, seed the coupled vectors from the static solution, and march. A real host writes its own motion into the blocks of the points it drives before each step.
/* Couple a mooring deck to a host that drives its Coupled points. */
#include "CableDyn_CAPI.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
const char *deck = argc > 1 ? argv[1] : "mooring.dat";
int major, minor, patch, abi, err = CD_C_OK, status = 1;
void *h = NULL;
double *q = NULL, *v = NULL, *a = NULL, *loads = NULL;
char msg[4097]; /* a message is at most 4096 characters, plus the NUL */
CableDyn_GetVersion(&major, &minor, &patch, &abi);
if (abi != CABLEDYN_CAPI_ABI_VERSION) {
fprintf(stderr, "library speaks C ABI %d, header expects %d\n", abi, CABLEDYN_CAPI_ABI_VERSION);
return 1;
}
CableDyn_Create(&h, &err);
if (err != CD_C_OK) return 1;
CableDyn_InitDeck(h, deck, (int)strlen(deck), &err); /* parse + static solve */
if (err != CD_C_OK) goto fail;
const int n = CableDyn_NCoupledDOF(h, &err);
if (err != CD_C_OK) goto fail;
printf("%d lines, %d points, %d coupled DOFs\n", CableDyn_NLines(h, &err), CableDyn_NPoints(h, &err), n);
q = calloc((size_t)n, sizeof *q);
v = calloc((size_t)n, sizeof *v);
a = calloc((size_t)n, sizeof *a);
loads = calloc((size_t)n, sizeof *loads);
if (!q || !v || !a || !loads) goto fail;
/* Start from the static solution: every block (anchors too) must hold valid data. */
CableDyn_GetCoupledMotion(h, q, v, a, n, &err);
if (err != CD_C_OK) goto fail;
const double dt = 0.05;
for (int step = 1; step <= 100; ++step) {
/* ... overwrite the blocks of the points the host drives with its motion at t+dt ... */
bool converged, stalled;
int n_iter;
CableDyn_Step(h, dt, q, v, a, n, &converged, &stalled, &n_iter, &err);
if (err != CD_C_OK) goto fail;
if (!converged) fprintf(stderr, "step %d: not converged after %d iterations\n", step, n_iter);
CableDyn_CalcOutput(h, loads, n, &err); /* N, global axes, cable on host */
if (err != CD_C_OK) goto fail;
}
printf("block 1 load: %.1f %.1f %.1f N\n", loads[0], loads[1], loads[2]);
status = 0;
fail:
if (status != 0 && h != NULL) {
CableDyn_GetLastError(h, msg, (int)sizeof msg);
fprintf(stderr, "CableDyn error %d: %s\n", err, msg);
}
free(q); free(v); free(a); free(loads);
CableDyn_Close(&h, &err);
return status;
}
Deck features that CableDyn_InitDeck rejects, such as a Coupled rod, are reported
through CableDyn_GetLastError with status CD_C_BAD_INPUT. For the matching Python calls,
see Python package. For solver diagnostics, see Troubleshooting.