FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0078 — NetStructure reports analytical port Jacobians but has no OutputPortJacobian for its state-dependent output ports
ID 0078
Class BUG
Severity 1
Status ready
Models Net/NetStructure
Found 2026-09-25, independent review of the MARE-0032 port-Jacobian tests (bf26414)

Evidence

At fd0f1f1, NetStructure::HasPortJacobians() (src/net/NetStructure.cpp:840) returns HasJacobians(), which is true for a plain net, but the class does not override OutputPortJacobian. The SimObject default writes nothing, so every link from a net output port to a consumer with analytical port Jacobians is marked analytical (ModelAssemblyWiring.cpp:497 in FhSim) and gets a zero block from the analytical pass. Of the output ports, <tag>Pos/ExternalPos<node> and <tag>Vel/ExternalVel<node> (:162-169), VelNorm (:97, :707) and StrainEnergy (:98, :718) depend on the states; the <tag>Force echoes (:91-94) and BottomContact (:173) do not.

Effect

Smaller than it looks, because FhSim's JacobianAssembler::AssembleNumericalJacobianColumns (JacobianAssembler.cpp:574) fills every entry the analytical pass left at exactly zero with a forward difference, and that pass always runs in a model with a net and another stateful object: the Environment the net requires is stateless and has no port Jacobians (NeedsNumericalJacobian, :326). So the coupling is a forward difference instead of zero. tests/NetStructure_PortJacobian_Test.cpp, which reads N2Pos, N3Vel and VelNorm into the test-only Test/SignalSink, passes with no OutputPortJacobian at all. The zero block would become real if the Environment gained port Jacobians or FhSim stopped filling zero entries.

Possible fix

Override OutputPortJacobian: identity blocks on the node's position or velocity states for the node ports, sign(v) for VelNorm. StrainEnergy needs the cable elements' length and stiffness, which the stiffness reduction and length adaptation change inside NetCableElement::AddEndForces.

Test that would prove it

SimObject.NetStructure_OutputPortJacobian and _OutputPortJacobianNodeNumber check the three ports through the system Jacobian. They cannot fail for the missing override because of the fill-in above; they fail for a wrong one.

Risk

The system Jacobian changes only from a forward difference to the exact value in the coupling blocks, so no simulated result changes beyond implicit-solver round-off.


Progress (2026-09-25)

Fixed in d28d8cf for the ports that matter for coupling: OutputPortJacobian returns identity blocks for <tag>Pos/ExternalPos<node> and <tag>Vel/ExternalVel<node>, and sign(v) for VelNorm. The port names come from PosPortName/VelPortName, which the port registration uses too. New test-only Test/SignalSink (dy/dt = u - y, analytical InputPortJacobian) and SimObject.NetStructure_OutputPortJacobian/_OutputPortJacobianNodeNumber (sinks on N2Pos, N3Vel, VelNorm, a sink ahead of the net so it is not at state offset 0).

RED: because of the fill-in the tests pass without the override, so they cannot fail for the missing one. They do fail for a wrong one: a doubled position block fails with relErr 0.5, a negated VelNorm sign with absErr 2. The fixtures give the nodes non-zero velocities: at v = 0 the fill-in's forward difference of |v| is 1 where the checker's central difference is 0.

Left open for StrainEnergy, whose derivative depends on the cable elements' reduced stiffness and adapted length, both changed inside NetCableElement::AddEndForces. It still gets the numerical fill-in, as before. Status stays ready.