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_InitLine | CableDyn_InitLines (initialize; may repeat)
-> CableDyn_GetCoupledMotion (read the initial state)
-> loop: [CableDyn_UpdatePointFluidFields]
CableDyn_Step(dt, motion at t+dt)
CableDyn_CalcOutput (loads at t+dt)
-> CableDyn_Close
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
|
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, 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 |
|
1 |
additive extensions of ABI 1: 1 adds the Object queries |
|
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 them.
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,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. Calling any
Init*function on an initialised handle replaces the model only on success. If the new initialisation fails, the previous model is kept unchanged.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 1024 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_InitDeckOne
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_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.
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\).
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, free, fixed or pinned
rods, with their lines) 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 deck order (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 |
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.
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.
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
1024 characters, so a buffer of 1025 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 along the line, 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 |
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.
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[1024];
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[1024];
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.