Repository navigation
FMIODE2.jac: an FMU failure while forming the Jacobian is recoverable - #426
Draft
hubertus65 wants to merge 4 commits into
Draft
hubertus65 wants to merge 4 commits into
hubertus65 wants to merge 4 commits into
Conversation
…Jacobian When 'with_jacobian' is in effect and the FMU provides directional derivatives, PyFMI evaluates the Jacobian with one fmi2GetDirectionalDerivative call per colour group of the sparsity pattern. On FMUs where one such call costs more than an rhs evaluation (measured 1.7x on OCT exports of 140-320 state models), coloured forward differences give the same Jacobian for a third of the cost, and because the colouring is bounded by the densest Jacobian row, Jacobian evaluation was 55-85 % of the solver time on those models. Measured with CVode: absorptionplant (226 states) 164 -> 89 s; Radau5ODE 529 -> 206 s; same step counts, events and accuracy (deviation <= 4e-4 at rtol 1e-6). 'jacobian_mode' = "auto" (default) times one rhs and one directional-derivative call at initialization and picks "fd" when the derivative is the more expensive one, except for RodasODE (a Rosenbrock method needs the exact Jacobian), the sparse linear solver, and rtol < 1e-7 (forward-difference truncation error ~sqrt(eps) then stalls Newton, measured on a 318-state plant at rtol 1e-8). "dd" and "fd" force either. Without directional derivatives the mode is always "fd". The setting is scoped to the simulation: the model's force_finite_differences attribute is restored afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A forward difference taken across a kink of the model -- a limiter, a region boundary of a property function -- returns jump/h (1e8 and more) where the derivative on this side is small. Newton-based integrators converge on the corrupted iteration matrix and reuse it for many steps. Measured on a 318- state combined-cycle plant at rtol 1e-6: two such entries in 200 Jacobian evaluations (d der(pump.s)/d s = -3.2e8 vs 0 at the end of the steam-table failure zone; d der(drum.p)/d hl = -1.3e8 vs -0.18 at t = 0) moved 91 of 308 plant states more than 1e-3 off the rtol 1e-8 reference. The guard (FMUModelME2.fd_kink_guard, default True) works per colour group of _estimate_directional_derivative: - flag: an entry that grew more than 1e3x relative to the previous Jacobian and is not negligible relative to it marks the group as suspect; the first evaluation checks every group. Kinks are transient, so this is nearly free. - confirm: a suspect group is re-differenced with a 10x smaller step; entries whose quotient changes by more than 10 % are kinks (a jump scales with 1/h, a smooth function does not). - fix: kink entries take the FMU's directional derivative for the group when it has one, else the smaller in magnitude of the forward and the backward difference (the side without the jump). Model variables are restored after each check; statistics in _fd_guard_stats. With the guard, CVode with the FD Jacobian on the plant matches the DD run's accuracy (plant median 5.0e-6 vs 9.8e-6, 4 vs 1 states beyond 1e-3, 23 events) at 5458 instead of 14353 steps and 320 instead of 511 Jacobians, each half the cost: 229 s instead of 340 s. On a 226-state plant and a 141-state vehicle model the guard changes nothing (accuracy and time within noise). FMI3's separate finite-difference routine is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PyrxGeWuwCmAH9LishMW6p
…tion's help The default stays "auto" (as introduced); this records what the kink guard protects against and asserts the default in the option test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PyrxGeWuwCmAH9LishMW6p
…; the FD kink guard's probe steps aside when the model refuses it
rhs() already turns an FMU failure at a trial point into AssimuloRecoverableError; jac()
let the raw FMUException through, which ended the run under any solver that asks for the
Jacobian (CVode with_jacobian, TRBDF2, Radau5). Now the same mapping: CVode answers with
CVDLS_JACFUNC_RECVR, TRBDF2 falls back to its own differences (Assimulo trbdf2 branch).
The one path that actually raised: _fd_kink_guard_group()'s probe at a smaller step
called get_real() unguarded, so a nonlinear block that does not converge at the probe
point ('Failed to get the Real values.') killed a Jacobian whose columns were already
computed. A refused probe is not a kink: the guard returns None and the column stands;
counted in _fd_guard_stats['probe_failures'].
Measured 2026-09-21: DataCenter.Examples.Systems.StandardChillerSystem and
ChillerWithEconomizerSystem (30 days, no directional derivatives) now complete under
TRBDF2 with CVode's event counts (133 / 132 state events); each had exactly one refused
probe, at t = 0 for the second.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PyrxGeWuwCmAH9LishMW6p
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One commit. Two related robustness fixes on the Jacobian path for ME FMUs, both found by running
a single-step stiff solver over a model whose property functions refuse to evaluate in part of
the state space:
FMIODE2.jac: an FMU failure while forming the Jacobian is recoverable. It raised a rawFMUExceptionout of the Assimulo callback, which ends the simulation. An rhs failure at atrial point is already treated as recoverable — a solver responds by shrinking the step — and
a Jacobian failure at a trial point is the same kind of event. It now signals a recoverable
failure, so the solver can retry with a smaller step instead of the run dying at t = 3110 s.
re-differences a flagged colour group at
h/10; if the model cannot be evaluated at thatprobe point, the probe is abandoned and the ordinary difference is kept, rather than the
probe's failure propagating.
Review notes
src/pyfmi/fmi2.pyxandsrc/pyfmi/simulation/assimulo_interface_fmi2.pyx.jacobian_mode/ kink-guard PR (the second fix is inside the guard).after an FMU has already returned an error status.
giving up) is in Assimulo Feed data into the model #138 for Radau5 and in the TRBDF2 branch, Added possibility to read very large result files #140.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PyrxGeWuwCmAH9LishMW6p