|
FhSim
3.1.0
Marine systems simulation
|
| ID | 0057 |
| Class | BUG |
| Severity | 2 |
| Status | blocked |
Models: —
Found: 2026-10-20 F3 review of the fill paths (a406e955), re-located at df9569e8
Source plan: plans/arch-2026-10-20/f3-jacobian-assembler-split.md, finding 2 (workspace fhsim_workspace)
Decision needed: owner to rule (F3-Q2: file and leave). Unifying the paths moves results, so it is not part of the F3 refactor.
The block of a stateful object without an analytical Jacobian is differenced in five places, with two different ODE callbacks (src/engine/model/):
m_runtime.odeFcn: the dense pass (JacobianAssembler.cpp:585, :543), the CSR value pass (:669, :680) and block-tridiagonal path B (:800, :809);simObject->OdeFcn: block-tridiagonal path A (JacobianAssembler.cpp:870, :880) and SimObjectJacobianBlock::EvaluateLocalJacobian (SimObjectJacobianBlock.cpp:109, :115), which ComponentSolver and the JacobianChecker call.The whole-model ODE re-evaluates the ports before each object, so a perturbed state reaches the object's own input ports. The object's own OdeFcn reads the input port values left by the last whole-model evaluation and does not move them.
For an object that feeds itself through ports (A to S to A, here a stateless gain), the whole-model difference includes the loop and the own-OdeFcn difference leaves it out. In the test fixture the block is [[-0.04, 1.06], [0.96, 0.56]] through the dense, CSR and path B passes and [[-0.54, 0.06], [-0.04, -1.44]] through path A and SimObjectJacobianBlock. The own-OdeFcn result also depends on whatever the ports held from the previous evaluation. Which one a solver gets depends on the linear solver chosen: ComponentSolver takes the own-OdeFcn block.
Owner to rule (F3-Q2: file and leave). Options: make every copy call the whole-model ODE (the block then includes the loop, and SimObjectJacobianBlock needs a whole-model callback it does not have today); or make every copy use the object's own OdeFcn and add the loop through the self-coupling link (a non-analytical self link is a zero matrix today by the settled decision, so the loop would be lost in the dense pass). Either choice moves results of hybrid models with a self-feeding object.
JacobianFillPathDifferences.NonAnalyticalSelfFeedback_EachPathPinnedToItsOwnArithmetic pins what each path returns today (green). JacobianFillPathDifferences.DISABLED_NonAnalyticalSelfFeedback_PathsAgreeOnTheObjectsBlock asserts path A and SimObjectJacobianBlock equal CSR and is red; enable it with the fix. Both in tests/model/JacobianFillPathDifferences_test.cpp.
Moves the Jacobian, so Newton iteration counts and step sequences, of every implicit run that has a non-analytical object coupled to itself through ports. Needs a baseline ruling (doc/agents/baselines.md). The F3 refactor keeps each path's own callback as an argument so that it does not move them.