FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0036 — The InternalCable structural-damping Jacobian ignores the compression-speed clamp, so it is wrong wherever that clamp is active
ID 0036
Class BUG
Severity 2
Status blocked
Models Cable/Branched, Cable/LumpedMass
Found 2026-09-24, rolling out the trajectory-sampling Jacobian checker (fhsim jacobian-checker-coverage)
Decision needed Should the analytical Jacobian reproduce the clamp (a numeric change to every cable Jacobian that reaches it), or is the clamped region outside the Jacobian's scope, so that only the test fixture changes?

Evidence

OdeFcn clamps the element compression speed at ±100 m/s before it enters the damping force:

// src/cable/subroutines/CableElBasics.cpp:98
const double kdMaxCompressionSpeedSaturation = 1.0e2;
// src/cable/subroutines/CableElBasics.cpp:302
ma_dCompressionSpeed = sfh::math::Bound(sfh::sim::CompressionSpeed3(adVelA, adVelB, ma_adDirection), -kdMaxCompressionSpeedSaturation, kdMaxCompressionSpeedSaturation);
// src/cable/subroutines/CableElDynStiff.cpp:275
double forceAmpDamping = ma_dDampingCoeff * ma_dCompressionSpeed * sqrt(m_eFactor);

Both analytical cable Jacobians compute the compression speed c without the clamp and differentiate d_eff * c unconditionally: InternalCable::ComputeStructuralJacobian (src/cable/subroutines/InternalCable.cpp:307-322,344, used by Cable/LumpedMass and Cable/Branched) and InternalCable::AddEndpointJacobianContributions (:442-456,467, used by Cable/Branched). While the clamp is active, OdeFcn's damping force is constant in the velocities, the Jacobian still carries d_eff * e e^T in every velocity column, and its totalForce (used for the 0 < F < maxT test and the (I - e e^T) F / L position term) is built from the unclamped c.

Observed in SimObject.CableBranched_Jacobian (tests/in/CableBranched/), whose fixture stretches every section to roughly ten times its relaxed length of 0.5 m. At the three states the scenario's own integrator reaches (t = 0.0047, 0.0083, 0.0110) the system and CB checks fail with up to 40 mismatches, maxAbsErr 6493 and maxRelErr 1.67; e.g. at the first sample (3,3) analytical=-57.798 numerical=-1, (3,4) analytical=10.09 numerical=0: the numerical velocity derivative of junction node 5 is exactly the NumericalDamping="1" term and nothing else. A temporary print in CableElDynStiff::AddStructuralForces (not committed) showed ma_dCompressionSpeed = -100 in every finite-difference evaluation at these states, with the same damping force in all of them.

The same states pass in the checker's shifted-offset copy only because that copy is freshly built and its elements still have the default relaxed length, so the cable is slack there; see MARE-0037.

Cable/LumpedMass has the same omission in ComputeStructuralJacobian by code reading; SimObject.Cable_Jacobian passes because its fixture never reaches 100 m/s.

Effect

Wherever a cable element's compression speed exceeds 100 m/s, the analytical Jacobian disagrees with OdeFcn in every row and column of the element's end nodes. An implicit integrator that uses it gets a wrong Newton matrix there; whether that moves results depends on the integrator and was not examined.

Possible fix

  • Reproduce the clamp in both Jacobians: when |c| >= 100, use the clamped c in dampForce and drop the damping terms (d_eff * e e^T and the relVelPerp part of the position term). This changes the Jacobian numerics wherever the clamp is active, and nowhere else.
  • Or declare the clamped region out of the Jacobian's scope and give the CableBranched fixture relaxed lengths it does not tear through in a few milliseconds.

Test that would prove it

SimObject.CableBranched_Jacobian with trajectorySamples back at the checker default of 3 (tests/CableBranched_Jacobian_Test.cpp sets it to 0 because of this issue).

Risk

The first fix moves Jacobian numerics for any model whose cable reaches the clamp; stored references change only if an implicit integrator in a reference scenario reaches it.


Re-triage (fix pass 2026-09-24)

Re-confirmed at 8a1772d by code reading: CableElBasics.cpp:302 clamps the compression speed at +-100 m/s, and InternalCable.cpp differentiates the unclamped damping term. Unchanged reason: reproducing the clamp changes Jacobian numerics wherever it is active, and whether the clamped region is in the Jacobian's scope is the model owner's call.