Skip to content

feat: add torsion to the Hermite finite-EI cable - #2

Merged
SMI-Lab-Inha merged 26 commits into
devfrom
feat/torsion
Oct 1, 2026
Merged

SMI-Lab-Inha merged 26 commits into
devfrom
feat/torsion

Conversation

@SMI-Lab-Inha

Copy link
Copy Markdown
Owner

Summary

Adds torsion to the cubic-Hermite finite-EI cable. The twist of a line is the parallel-transport twist of its centreline, condensed into one torque unknown per line, so the element keeps its 12 degrees of freedom and the banded solver its bandwidth. Torsion is active only on lines whose two ends both declare a torsional restraint; every other deck runs exactly as before.

What is included

  • Input: optional END CONNECTIONS columns TorsStiffness NxX NxY NxZ [Pretwist] (Rigid, Inf/Infinity or a finite stiffness). A torsional line needs an explicit GJ in its line type.
  • Statics: bordered (Sherman–Morrison) Newton step with a dense fallback for small lines, continuation of the imposed twist, continuous tracking of full turns, and a stability check that leaves the unstable straight state above the Greenhill onset.
  • Dynamics: torsion in the generalised-alpha step, turning parent bodies and vessels, a roll column in motionFile, torque returned to Rigid6 bodies in statics and dynamics, restart and snapshot state, no added allocation per step.
  • Outputs: Torq<L>N<J>, Twist<L>N<J>, Twist<L> and range-graph torque/twist columns; Python deck tools accept the new columns and channels.
  • Documentation: input format, outputs, conventions, theory, capabilities, troubleshooting, a worked example (examples/torsion_lazy_wave_hangoff_twist.dat), VALIDATION.md torsion section and a changelog entry.

Limits in this version (refused by name or documented)

No torsional inertia, no seabed friction resisting twist, no torque–tension coupling and no self-contact. Coupled OpenFAST/FAST.Farm runs, rod ends, Point3 bodies, lines with ATTACHMENTS, modal analysis and the configuration force blend refuse torsional decks with a named error. A bending-pinned end with twist restrained behaves as a semi-tangential torque end (onset 4.9112877 EI/L, not the hinged 2π EI/L); the documentation says so.

Validation

  • Twist kernel against an independent automatic-differentiation reference: ≤ 4e-15.
  • Pure torsion: torque exactly GJ·Φ/L.
  • Clamped Greenhill onset against van der Heijden, Neukirch, Goss & Thompson (2003): 9e-6 at T = 0, ≤ 4e-5 with tension; fourth-order mesh convergence.
  • Semi-tangential onset: 6e-7.
  • OrcaFlex 11.6d (summary values only, regeneration script included): pure torsion to output precision; onsets ≤ 3e-5; lazy-wave line with 1–5 turns of hang-off twist: torque ≤ 0.019 %, peak curvature ≤ 0.056 %, out-of-plane offset ≤ 27 mm.
  • Dynamics: torque step response exact, body torsional pendulum to 6e-5, stable at Ω·dt = 2 and 5 (body roll matches the closed form to 1e-7 deg), restart bit-identical after more than one turn.

Checks

  • Release and Debug: 226/226 CTest; Debug with -fcheck=all, warning-free.
  • With torsion off, all 51 example decks produce byte-identical output to dev.
  • Python: 1926 tests, 99.42 % coverage; ruff and mypy clean.
  • Sphinx -W -n clean; pre-commit clean.
  • An independent three-part adversarial review was applied before opening this pull request.

New module CableDyn_HermiteTorsion for condensed isotropic torsion: for one
line it returns the parallel-transport holonomy Theta between the two end
frames, its gradient and its Hessian in the existing general-band layout
(half-bandwidth 11), plus the derivatives with respect to incremental end
frame rotations. Element holonomies and smallest-rotation chain terms are
differentiated in closed form; the chain derivatives use per-link gauges
frozen at the current configuration. Also provides the 2 pi unwrap rule with
a pi/2 step limit and a fold guard (1 + t1.t >= 0.5) that fail closed with
named status codes.

The module is not called by any solver yet, so existing results are
unchanged.
tests/test_hermite_torsion_kernel.f90 checks Theta, its gradient and every
second derivative against reference values from
validation/scripts/hermite_torsion_oracle.py (JAX, four random
large-deformation lines with clamped and articulated ends), and against
finite differences. It also checks the band edge, rigid-rotation invariance,
planar curves, the helix closed form, unwrapping through more than three
turns, the step limit, the fold guard and malformed input.
CD_HermiteCable_Static_Solve takes an optional torsion description
(CD_HermiteTorsionType: GJ per element, end frames, end compliances,
imposed twist Phi, the accepted Theta). Without it nothing changes. With it,
the imposed twist is the last load stage: Phi is ramped from the twist of
the untwisted equilibrium (zero torque) in stages of at most pi/4, halved on
a failed stage. Each Newton step adds E_t = (Phi - Theta)^2/(2C) and solves
the bordered system by Sherman-Morrison on the band factor, with pivot,
denominator and backward-error guards and a shifted, refined fallback.
Theta is unwrapped against the last accepted iterate and may change by at
most pi/2 per accepted step.

Every converged stage is tested for stability by the inertia of
B + g g^T/C (banded L D L^T and the determinant lemma); an unstable state
above a buckling onset is left by descent along the lowest mode and energy
minimisation, and one exact zero mode can be allowed for coaxial clamped
ends. The energy step handles the rank-one term through the same update.

tests/test_hermite_torsion_static.f90 gates pure torsion (exact torque),
the clamped Greenhill onsets of van der Heijden et al. (2003) eq. (33) at
N = 32 within 1e-5 with fourth-order convergence, the semi-tangential
pinned onsets, post-buckling descent, multi-turn twist across the +-pi
branch of the holonomy and fail-closed inputs.
CD_HermiteCable_Dyn_Set_Torsion (and CD_HFMF_Set_Torsion) installs the
torsion of a converged static solve: end frames, GJ, the imposed twist and
the accepted Theta, which now passes through snapshot, restore and end. The
residual, the support reaction, the end force, the energy and the
connection moment include it. The moment adds M_t dTheta/dw of the end
frame, so a torsionally restrained end returns its torque to the support,
also at a bending-pinned end; at a rigid end the slaved tangent adds its
share through the constraint reaction. CD_HermiteCable_Dyn_Torsion_State
reports Theta and the torque without advancing the stored state. The time
step refuses a model with torsion (statics only in this build).
END CONNECTIONS rows take optional columns TorsStiffness NxX NxY NxZ
[Pretwist]: Free (default), Rigid or a torsional stiffness, the zero-twist
reference normal in the frame of Ez, and a roll of the end frame about Ez in
degrees. Six-column rows parse as before. Torsion is solved on a line only
when both ends are restrained; it needs a finite-EI line with a Fixed End B
and an explicit GJ on every section, and is refused on rod ends, non-Rigid6
bodies and with ATTACHMENTS. Malformed rows fail with named errors.

On the standalone cubic-Hermite route (TMax 0) a torsional line is solved
in 3D, then the imposed twist Phi = Pretwist(B) - Pretwist(A) is ramped on
the final mesh with stability checks and buckling descent; the torque is
returned to a Rigid6 body at End A, in the output loads and in the body
static equilibrium. New channels Torq<L>N<J> (N m), Twist<L>N<J> (cable
twist from End A, deg) and Twist<L> (Phi - Theta, deg) are accepted only
on torsional lines. Dynamic decks (TMax > 0), modal analysis, the coupled
OpenFAST and FAST.Farm entries and the mixed aggregate route refuse
torsion by name.

tests/test_torsion_deck.f90 gates the torque law and twist channels,
byte-identical output with Free or one-end torsion columns, a three-turn
pretwist sweep, the body torque return, the moored-body statics and every
named refusal.
CD_HermiteTorsion_Line takes an optional workspace (CD_HermiteTorsion_Work_Init) for its chain vertices, gauges and Hessian band, so a dynamic step can evaluate the twist without heap allocation. With keep_hessian the unscaled Hessian stays in the workspace and CD_HermiteTorsion_Work_Add_Hessian adds it with a factor that depends on Theta (the torque). The kernel test checks that the workspace path is bit-identical to the allocating one and that a workspace of the wrong size is refused.
The generalised-alpha step now carries condensed torsion instead of
refusing it. The residual adds -M_t dTheta/dq and the tangent
-M_t d2Theta/dq2; the rank-one (1 - alpha_f) g g^T / C joins the solve by
Sherman-Morrison on the band factor, with g, K^-1 g and the denominator
kept with the factor so that a reused factor stays consistent. Theta is
unwrapped against the committed value; a step that would move it by more
than pi/2 fails its evaluation, so the line search or the recovering step
cuts it. Torsion is quasi-static (no torsional inertia) and needs the
force-blended generalised-alpha.

CD_HermiteCable_Dyn_Set_Torsion_Drive sets the end frames and Phi at the
end of the next step (the recovering step interpolates the frames on SO(3)
and Phi linearly over its substeps); the commit advances them with Theta.
CD_HermiteCable_Dyn_Set_Torsion_Frames moves the committed frames between
steps. Snapshot and restore carry Theta, Phi and the frames. The kernel
evaluates in a model-owned, explicit-shape workspace, so a step allocates
nothing.

CD_HFMF_Set_Torsion can attach the coupled end's torsion frame to its
parent: UpdateStates and SetCoupledKinematics turn it with the parent
orientation (also at a bending-pinned end), and UpdateStates takes the
imposed twist u_twist. The checkpoint mirror gains Theta, Phi and the
frames when torsion is active (unchanged otherwise).

tests/test_hermite_torsion_dynamic.f90 gates the step response against
the quasi-static torque, a parent turning 1.5 turns, energy conservation
at rho_inf = 1, bit-identical snapshot and mirror restarts past one turn,
and the bordered step; test_step_allocations gains a torsion mode.
A deck with torsional END CONNECTIONS at both ends of a line now runs
dynamically on the standalone cubic-Hermite route and the multibody march
(it needs the force-blended generalised-alpha; modal analysis and the
coupled OpenFAST and FAST.Farm entries still refuse torsion by name).
End A's torsion frame (-Ez, Nx) is attached to its parent, so a turning
body or vessel turns it, also at a bending-pinned end.

motionFile point rows take an optional 12th column on a deck with a
torsional line: the roll of the line end frames at that point about Ez in
degrees, added to the End A pretwist (Phi = Pretwist(B) - Pretwist(A) -
roll), given on every row of a point or on none, starting at 0 and only at
the End A point of a torsional line (named errors otherwise). Torsion has
no inertia here, so the torque follows the roll at once.

Range graphs of a torsional line add Torque and Twist (from End A)
envelopes; other lines' range files are unchanged.

tests/test_torsion_deck.f90 gains a roll-column step and a vessel rolling
about the line axis (Twist = -roll, quasi-static torque at every step), the
range envelopes, a body swinging as a torsional pendulum through two turns
at sqrt(GJ / (L I)) with the connection moment along the axis, and the
refusals of the configuration blend and of roll-column misuse.
The static body solve returned a line's torque as a fixed load each pass,
which converged only when the body's own restoring stiffness was far above
GJ/L. Each pass now also gives the body solve the torque's moment
stiffness (1/C) a a^T about the previous pass's pose (a = dTheta/dw of the
cable with its shape held: the End A frame turns, a clamped end's tangent
with it). The own-load Jacobian carries it into the Newton step and the
stability test, so the passes converge whatever the ratio of the body's
restoring stiffness to GJ/L, to 1e-10 of the force scale.

Gate 9 of tests/test_torsion_deck.f90: a buoyant body held by a vertical
twisted cable with no yaw restoring at all yaws until the cable is
untwisted, and with two legs whose yaw stiffness is of the order of GJ/L
the two share the twist; Twist = 90 deg + yaw, and the static net moment
on the body is below 1e-11 of the torque, in three passes.
DeckFile validates the optional END CONNECTIONS torsion columns
(TorsStiffness NxX NxY NxZ [Pretwist]) with the native rules: 6, 10 or 11
columns, Free/Rigid or a non-negative stiffness, a non-zero reference
normal not parallel to Ez, and the line rules of a torsional line (finite
EI, Fixed End B, explicit GJ, no attachments, no rod end, a Rigid6 body),
the force blend for a dynamic run, no modal analysis and no coupled route.
The Torq<L>N<J>, Twist<L>N<J> and Twist<L> channels are recognised (one
identity per quantity) and accepted only on a line restrained at both ends.

DeckModel's EndConnection carries the optional torsion columns and writes
them back, and add_end_connection takes them as keyword arguments.
The parity tests pin each rule.
The Unreleased torsion entry now covers dynamics (quasi-static torsion with turning end frames and a motionFile roll column), the body torque in statics and dynamics, the range envelopes and the Python deck tools.
…h and the bordered step

A new validation test checks zero-twist parity on a three-dimensional sagging line, a clamped four-turn Kirchhoff helix against its helical wrench, the clamped onset at T L^2/EI = 400 and 1600 against eq. (33), a bent and twisted rod against the Cosserat rod path, and the rank-one term of the bordered Newton step on post-buckled states (linear convergence at the predicted rate without it).
test_torsion_orcaflex checks pure torsion through the deck, the clamped and semi-tangential buckling onsets, and the Gulf of Mexico 80 m lazy-wave cable twisted up to five turns at the hang-off against OrcaFlex 11.6d summary values. validation/scripts/orcaflex_torsion_reference.py regenerates them (summary values and settings only).
… scope refusals

The rolling-body gate also runs with a bending-pinned End A, and a rod end, a Point3 body and ATTACHMENTS on a torsional line stop by name.
read_output attaches N-m and deg to the Torq and Twist channels, read_range_graphs returns the torque and twist envelopes, and the torsion option check takes option records.
torsion_lazy_wave_hangoff_twist.dat twists the Gulf of Mexico 80 m cable two turns at the hang-off over 60 s and holds it; it runs as a CTest example and is catalogued.
Deck columns, explicit GJ, the motionFile roll column, the Torq and Twist channels and range columns, sign conventions, the condensed-torsion theory with its references, the semi-tangential pinned end, named errors and the scope limits.
…ibody route

A body holding a line restrained in torsion had its moment rows scaled by its weight, so a heavy body with a small rotational inertia accepted moment residuals far above the line torque, and a trial turn above 180 deg aliased the line's twist by a whole turn. Its moment rows now scale with its rotational step stiffness and each trial turn stays inside the torsion step limit; the torsional pendulum follows the trapezoidal closed form at Omega dt = 2 and 5.
A static re-solve from a stored Theta stops by name when the untwisted state
is more than 90 deg of twist away, and a failed solve leaves the stored Theta
unchanged. The bordered step falls back to a dense solve on small lines and
names an ill-conditioned failure; the inertia count rejects a non-square band
and names an unreliable count; the stability test trusts a reliable count and
scales the zero-mode band by EI/(EA Le^2). Newton and descent steps bound their
linearised twist change before the trial. SetCoupledKinematics checks the
turned torsion frame before writing anything, a restored mirror checks its
frames and recovers the parent orientation, and the committed-state torque
queries no longer allocate (now gated with CalcOutput). Test maxima are
NaN-aware.
TorsStiffness takes Inf, Infinity, Rigid (also quoted), Free and Zero in the
native reader as documented, and both readers parse the END CONNECTIONS
stiffness columns as plain reals (no q exponent, finite, not subnormal). A
Free end's reference normal is not checked, and one restrained end is ignored
before the End B rule, so it no longer refuses a line with two moving ends.
Python refuses torsion on a standalone mixed EI = 0 + finite-EI deck as the
aggregate does, after the line rules; the refusal and the motionFile roll
message name the deck's own ends, and a failed torsion stage is reported
without a doubled prefix. The static retry starts from a fresh torsion state,
the body pass loop passes the torque stiffness only with torsion, and the
simple deck writer refuses torsion arguments.
A refused SetCoupledKinematics (and its preflight) leaves the cable unchanged,
a mirror with non-orthonormal torsion frames is refused before any write, and
a reload recovers a parent-attached frame's orientation. The deck gates add
the Rigid/Infinity/Inf/quoted keywords, Free ends with an unused normal, a
one-end row on a line with two moving ends (the six-column verdict) and a
failed torsion stage named once.
The roll column is described in terms of the line's moving end, the Free-end
normal and the keyword set as the reader takes them, the multibody 90-degree
step limit and the refusal of torsion on mixed decks without a body. The
validation record presents its cases without the planning labels and records
the coarse-step pendulum; the changelog entry is shortened. The oracle script
needs --out, the OrcaFlex script imports OrcFxAPI only to run OrcaFlex, and
internal wording is removed from test data and docstrings.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-01T08:57:48.586679Z e739bac PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 CableDyn | 🛠️ Build #34871351 | 📁 Comparing e739bac against latest (c0a88eb)

  🔍 Preview build  

14 files changed · ± 14 modified

± Modified

@SMI-Lab-Inha
SMI-Lab-Inha merged commit c89059f into dev Oct 1, 2026
15 checks passed
@SMI-Lab-Inha
SMI-Lab-Inha deleted the feat/torsion branch October 1, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant