FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0152 — AcceptedStep and the diagnostics read the last solver iterate under SUNDIALS
ID 0152
Class BUG
Severity 2
Status blocked

Models: —

Found: 2026-10-24 review r2 (R2-07) of 25024ce7

Decision needed: how to pay for, or live with, the lag (owner ruling r2-D2, 2026-10-25: deferred because the bench is over +10 %). Options: (a) accept the extra OdeFcn per accepted step; (b) evaluate only when needed (a SimObject overrides AcceptedStep, or diagnostics need ports; needs a flag in the SimObject API); (c) keep the lag and document it.

Evidence

FhIntegratorSundials.cpp:202 (at 25024ce7) m_model->AcceptedStep(m_t, m_rawX); with no OdeFcn at the accepted state; the diagnostics run earlier, inside each solver branch (:182,190,198). The Engine order is OdeFcn, AcceptedStep, OnEngineStep (FhIntegrator.cpp:208-224, FHSIM-0066).

Effect

A SimObject that reads an input port in AcceptedStep sees the port value of the solver's last stage or iterate, not of the accepted state, and so does the diagnostics port snapshot. With the test input below the lag under CVODE BDF is 0.000179; ARKODE ERK and DIRK showed 0 (their last stage is at the accepted state).

Possible fix

After the SUNDIALS step returns, call m_model->OdeFcn(m_t, m_rawX, m_rawStateDerivative) once, then AcceptedStep, then the step diagnostics (move RunStepDiagnostics_(lastStepSize) out of the three solver branches; keep the step index it is given). It was implemented and measured on branch feature/r2-sundials (commit removed again), cost by callgrind instruction counts, with against without the extra call:

Input without with change
fhsim_gauss CableChainRuntimeBench.xml (BDF) 1,079,184,906 1,334,767,881 +23.7 %
fhsim_gauss GaussChainRuntimeBench.xml (BDF) 411,571,973 429,214,662 +4.3 %
fhsim IntegratorConfig_BDF_in.xml 4,471,462 4,478,182 +0.15 %

No existing golden of fhsim moved, and no consumer result (base, marine_elements, fishery, environment) moved.

Test that would prove it

A copy of ImplicitIntegration_RK45_AcceptedStepPortReading_in.xml with Method="BDF" (<Integrator Method="BDF" NumCores="1">, StepControl Step="0.1") and in tests/integrator/methods/ODE_test.cpp, next to AcceptedStepPortReading.PortReadInAcceptedStep_HoldsTheAcceptedState:

TEST(AcceptedStepPortReading, PortReadInAcceptedStep_HoldsTheAcceptedStateUnderBdf)
{
RunAndVerify("ImplicitIntegration_BDF_AcceptedStepPortReading", 1.0, 10.0,
[](const fhsim::test::ResultFileData& resData) {
const auto* lag = fhsim::test::FindSeries(resData, "C", "AcceptedStepPortLag_0");
ASSERT_NE(lag, nullptr);
EXPECT_EQ(lag->m_colData.second.back(), 0.0);
});
}

Red without the fix (lag 0.000179), green with it. The ERK copy of the input is green either way. Add the golden with run_golden.py --update --case ImplicitIntegration/ImplicitIntegration_BDF_AcceptedStepPortReading.

Risk

The extra call is one more right-hand-side evaluation per accepted step: about 1/7 more for a six-stage ERK method, and the benchmark numbers above for BDF. A SimObject with state in OdeFcn (counter, latch, random draw) can see its results change, as with the Engine (FHSIM-0066).