Skip to content

fmi-ls-dae: DAE FMUs, IDA for the system, and algebraic loops as rows (#1645) - #1646

Merged
adrpo merged 7 commits into
OpenModelica:masterfrom
adrpo:fmi-ls-dae-support
Nov 1, 2026
Merged

adrpo merged 7 commits into
OpenModelica:masterfrom
adrpo:fmi-ls-dae-support

Conversation

@adrpo

@adrpo adrpo commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Towards #1645. OpenModelica/OMSimulator-3rdParty#111 (SUNDIALS' IDA, and SuiteSparse for its KLU) is merged, and master's 3rdParty already contains it, so this PR no longer changes the submodule.

Rebased on master (2026-10-31). The fmi4c update on master (#1638) passes an instance handle to the runtime calls, so the Configuration Mode switch and the residual, algebraic-variable and derivative accessors now call fmi4c with instance instead of fmu. ctest: 126 tests, 0 failed (11 skipped, all platform-gated). The two-component DAE loop (IDA, loop as rows, KLU) gives the same result as before the rebase (diffSimulationResults at 1e-8, no differing variables).

Update 2026-11-01: tests, and a second copy of the solver enum.

  • fmu.py had its own copy of the solver values, _FMU_SOLVER, which still sent 6 for 'ma'. After the renumbering 6 is oms_solver_wc_min, which SystemWC::setSolver refuses, so the command line (OMSimulator x.fmu) failed on every Co-Simulation FMU with Failed to set solver. Fixed in commit 2. simulation/fmuSimulation3.py covers it: it selects every solver by name on both kinds of Dahlquist3.fmu, and with the old map it fails.
  • New commit 7 adds the testsuite cases this PR was missing; that gap is why codecov reported most of the patch as uncovered. DaeLoopA (--daeMode) and LoopB are in tests/resources/, exported by OpenModelica with --simCodeTarget=C+Rust ([FMI] Export a --daeMode model as a Model Exchange FMU from C+Rust (#16622) OpenModelica#17185).
    • simulation/DaeMode1.py connects the two FMUs into an algebraic loop through an SSP: IDA, the loop as rows, and the coloured 5x5 Jacobian with KLU. It gets x(1) = 0.701445, which is the analytical 0.5 + 0.5·exp(−1/1.1).
    • simulation/DaeMode2.py runs DaeLoopA alone through the FMU API, once with ida selected and once with the default, which the system switches to IDA. Both get x(1) = 0.683940 = 0.5 + 0.5·exp(−1).
  • The FMUs only have x86_64-linux binaries, so these tests are enabled for linux only. They need glibc 2.38, which the 24.04 and 26.04 images have. ASan is off for them because I could not check it here.
  • ctest locally: 129 tests, 0 failed. I have not measured coverage locally; the codecov comment will show it for this push.

Ready for review. The open points at the bottom are follow-ups, not blockers. The tests use FMUs from OpenModelica/OpenModelica#17185, which is now merged, so master can regenerate them.

A Model Exchange FMU carrying extra/org.fmi-standard.fmi-ls-dae/fmi-ls-manifest.xml exposes the residuals of F(t, x, x', z) = 0 and lets the importer solve for the algebraic variables z beside the states, instead of iterating on them behind its own interface. OpenModelica exports such FMUs (OpenModelica/OpenModelica#16504 for wasm, OpenModelica/OpenModelica#16622 for the C target). This makes OMSimulator read one and integrate it.

Reading the manifest (commit 1)

  • LsDae reads it: the enable parameter, the algebraic variables, and the residuals with their dependencies — the sparsity a DAE master differences the Jacobian with. A missing manifest is not an error; it is the ordinary FMU.
  • ComponentFMU3ME loads it when the FMU is unpacked, remembers the <ContinuousStateDerivative> value references (the model description's own order, which pairs them with the states), switches the FMU into DAE mode at instantiation — that is where Configuration Mode is — and gains the residual, algebraic-variable and derivative accessors. fmi4c already binds fmi3EnterConfigurationMode, so no 3rdParty change was needed for that.

Integrating it (commit 2)

The unknowns are laid out per component, [x_i | z_i], and so are the rows: a component in DAE mode contributes the residuals it declares, an ODE component the explicit rows x'_i - f_i(x, u, t). Both blocks are square, so the system is, and the two mix — a DAE component next to an ordinary FMU needs nothing special. IDASetId says which unknowns are differential; IDACalcIC solves for the algebraic ones and for the derivatives at the start and again after every event, because the components initialize consistently with themselves rather than with the coupled system, and an event moves them the same way.

Direct (non-loop) coupling stays where it was for CVODE: updateInputs() propagates connected outputs into inputs before the rows are evaluated. Algebraic loops do not — see the next section.

DAE mode is not really a solver choice: a component that declares a DAE formulation has no explicit ODE for CVODE to integrate, so the system switches to IDA on its own and says so in the log. It still has a name (oms_solver_sc_ida, --solver ida) for the SSD and the CLI.

Algebraic loops as rows, not an inner solve (commit 4)

An algebraic loop over connections was solved by an inner KINSOL Newton, run to convergence inside every residual evaluation, on top of whatever solver each FMU hides behind its own interface. Where IDA drives the system it does not have to be. The translation is direct, because it is the same system the inner solver had: each connection of the loop contributes one unknown — the value of the input it drives — and one row, output - input = 0.

A loop is lifted only if all of its connections carry reals; one with an integer or boolean connection keeps its own solver, and so does every loop when the system is not in DAE mode, where none of this applies.

Initialization and events leave every loop to IDACalcIC rather than to the inner solver. They have to: a component in DAE mode does not solve for its own algebraic outputs — the master does — so "set the input, read the output back" has nothing behind it and KINSOL fails on a singular Jacobian. The consistent initial conditions are solved for the whole coupled system instead, which is where that belongs.

Tested with a loop across two FMUs, one of them --daeMode, against the same two components written as a single Modelica model and simulated by OpenModelica:

x(0.5) x(1.0) loop equation residual
IDA, loop as rows 0.817368712680 0.701445480172 2.7e-14
CVODE + KINSOL inner solve 0.817433688498 0.701528323696 8.7e-08
monolithic omc 0.817368180018 0.701445143664 —

The lifted rows hold the connection equation to machine precision instead of the inner solver's tolerance, and land two orders of magnitude closer to the monolithic answer. The KINSOL run of that same loop also needs --directionalDerivatives=false, because it wants a Jacobian the FMU does not provide — where the lifted rows are simply part of the one IDA differences itself.

The Jacobian: coloured, and factorized by KLU (commits 5 and 6)

IDA's own difference quotient costs one residual evaluation per unknown, and every one of those calls into every FMU of the system. The manifests say which unknowns each residual reaches, which is enough to colour the columns — two share a colour when no row reaches both — so one evaluation differences all of them at once:

unknowns structural nonzeros colours
1 pair of components 5 16 5
4 pairs 20 64 5
8 pairs 40 128 5

The cost of a Jacobian stays flat as the system grows. The same structure holds the matrix sparse (CSC) and lets KLU factorize it, reusing its symbolic factorization since the pattern never changes. Without a usable structure nothing changes: dense matrix, IDA differences it itself.

Two things about the structure are worth flagging:

  • A residual's dependency on an input is not in the manifest. The dependencies it declares are those of the FMU's own DAE Jacobian, whose columns are the states and the algebraic variables — an input is not one of them. It only becomes an unknown in a master that carries it for a lifted loop, so those columns are added to every row of the component regardless. Without them the Jacobian is wrong by exactly the -1 of the input's own row, and a coloured difference quotient then attributes one column's perturbation to another. It may be worth raising with the layered standard: a residual has no way to declare that it depends on an input.
  • The derivative attribute is a value reference in FMI 3.0, where FMI 2.0 had an index — the same change as the ModelStructure dependencies, and fmi4c hands the raw attribute through under its FMI 2.0 name. Read as an index, derivative="0" meant "no state" and the structure was discarded.

Validated against a column-at-a-time difference quotient of the same system: the coloured Jacobian agrees to the last bit. KLU and the dense factorization give the same trajectory and the same step counts.

Three bugs found on the way

  1. FMI 3.0 ModelStructure dependencies are value references, not indices. Both FMI 3.0 components read them the FMI 2.0 way, as one-based indices into the variable list, so the dependency graph was wired to whichever variables sat at those positions — and an FMU whose dependency names value reference 0 was rejected outright (bad dependency on variable with index 0). That is what two connected FMI 3.0 FMUs ran into here. Fixed in its own commit; ComponentFMUME keeps the index reading, which is correct for FMI 2.0.
  2. Causality had no structuralParameter, so Causality[causality] raised a KeyError while parsing modelDescription.xml: OMSimulator could not load any FMI 3.0 FMU with a structural parameter, nothing to do with fmi-ls-dae. Happy to split this out.
  3. SolverType in instantiated_model.py duplicates the C enum's integer values. Inserting oms_solver_sc_ida renumbered oms_ma/oms_mav/oms_mav2, setSolver() quietly began selecting the wrong solver, and 37 tests failed with Failed to set solver and no hint of why. Fixed, with a comment saying the two have to be kept in step — deriving them from the C API would be better, and I did not want to widen this PR into that. fmu.py has a second copy, _FMU_SOLVER, which I missed at first (see the update at the top).

Tested

Against a --daeMode Model Exchange FMU exported by OpenModelica (OpenModelica/OpenModelica#16622), an index-1 DAE with an algebraic loop, a nonlinear algebraic variable and a state event:

$ OMSimulator --mode me --stopTime 1.0 DaeModeMeC.fmu
info:    FMU "DaeModeMe" carries a DAE formulation (fmi-ls-dae 1.0.0-alpha.1): 3 residuals over 1 states and 2 algebraic variables
info:    system "DaeModeMe.root" contains a component in DAE mode (fmi-ls-dae); integrating with IDA instead of cvode
info:    Final Statistics for 'DaeModeMe.root':
         NumSteps = 367 NumResEvals  = 372 NumLinSolvSetups = 13
         NumNonlinSolvIters = 370 NumNonlinSolvConvFails = 0 NumErrTestFails = 0
info:    0 errors

The same model was also exported as an ordinary ODE FMU and run through this same master with CVODE. The two agree to ~1e-8, state event included:

x(0.25) event time x(1.0)
IDA, DAE FMU 1.4190675494 0.2896179339 3.4468190259
CVODE, ODE FMU 1.4190675512 0.2896179331 3.4468190377

(Both differ from OpenModelica's own run of the model by ~1e-5, but identically so — that is OMSimulator's existing behaviour against omc, not something this PR introduces, which is why the comparison above is against the CVODE path rather than against omc.)

ctest: 117/117, the same as master.

Open points

  • The DAE tests run on linux only. The test FMUs carry x86_64-linux binaries alone; win64 builds can be added the way the other resources have them.
  • A lifted loop still gets a KINSOL solver instantiated for it that is never used; harmless, but it should not be built.
  • The semi-explicit form of the layered standard is rejected with a clear message; only the fully implicit one is handled.
  • fmi-ls-dae is not a released layered standard yet (1.0.0-alpha.1, draft of 2026-09-02).

Generated by Claude Code

@adrpo adrpo self-assigned this Sep 8, 2026
@adrpo adrpo changed the title Read an FMU's fmi-ls-dae manifest and switch it into DAE mode (#1645) fmi-ls-dae: read a DAE FMU's manifest and integrate the system with IDA (#1645) Sep 8, 2026
@adrpo adrpo changed the title fmi-ls-dae: read a DAE FMU's manifest and integrate the system with IDA (#1645) fmi-ls-dae: DAE FMUs, IDA for the system, and algebraic loops as rows (#1645) Sep 8, 2026
@adrpo adrpo added the CI/Update Submodules Allow a pull request to update submodules label Sep 8, 2026
@codecov

codecov Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.91441% with 281 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.22%. Comparing base (6168c70) to head (62acdaa).

Files with missing lines Patch % Lines
src/OMSimulatorLib/SystemSC.cpp 61.68% 125 Missing and 80 partials ⚠️
src/OMSimulatorLib/ComponentFMU3ME.cpp 47.42% 29 Missing and 22 partials ⚠️
src/OMSimulatorLib/LsDae.cpp 60.00% 10 Missing and 6 partials ⚠️
src/OMSimulatorLib/ComponentFMU3CS.cpp 40.00% 7 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1646      +/-   ##
==========================================
+ Coverage   28.43%   30.22%   +1.78%     
==========================================
  Files          72       74       +2     
  Lines       13981    14661     +680     
  Branches     9023     9432     +409     
==========================================
+ Hits         3976     4431     +455     
- Misses       8771     8892     +121     
- Partials     1234     1338     +104     
Flag Coverage Δ
linux 30.22% <59.91%> (+1.78%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…delica#1645)

The first half of DAE-FMU support: recognising one and being able to put it in
DAE mode. The integration itself — an IDA path in SystemSC over the union of the
components' states and algebraic variables, with the connection equations as
residual rows — is the follow-up, and nothing here changes how a system is
simulated yet.

A Model Exchange FMU carrying
extra/org.fmi-standard.fmi-ls-dae/fmi-ls-manifest.xml exposes the residuals of
F(t, x, x', z) = 0 and lets the importer solve for the algebraic variables z
beside the states, instead of iterating on them behind its own interface. It is
an ordinary ODE FMU until the importer sets the structural parameter the
manifest names.

* LsDae: reads the manifest — the enable parameter, the algebraic variables, and
  the residuals with their dependencies (the sparsity a DAE master differences
  the Jacobian with). No manifest is not an error; it is the ordinary FMU.
* ComponentFMU3ME: loads it when the FMU is unpacked, remembers the
  <ContinuousStateDerivative> value references (the model description's own
  order, which pairs them with the states), and gains enableDaeMode() —
  Configuration Mode, the switch, and the checks that the system is square and
  fully implicit — plus the residual, algebraic-variable and derivative
  accessors a master needs.
* variable.py: FMI 3.0's structuralParameter causality. Without it
  Causality[causality] raises a KeyError and OMSimulator cannot even load an FMU
  that has one — which every fmi-ls-dae FMU does.

Tested against a --daeMode Model Exchange FMU exported by OpenModelica
(OpenModelica/OpenModelica#16622): OMSimulator reads the manifest and reports
"3 residuals over 1 states and 2 algebraic variables", and the FMU's own refusal
to run as an ODE FMU comes through as a diagnostic instead of a crash. Ordinary
ODE FMUs are unaffected.

Refs OpenModelica#1645, OpenModelica/OpenModelica#16622

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
@adrpo
adrpo force-pushed the fmi-ls-dae-support branch from 41915dd to cb87beb Compare October 31, 2026 21:41
adrpo and others added 5 commits November 1, 2026 11:15
The other half: a strongly coupled system that holds a component in DAE mode is
integrated as one implicit DAE instead of an explicit ODE.

The unknowns are laid out per component, [x_i | z_i], and so are the rows: a
component in DAE mode contributes the residuals it declares, an ODE component
the explicit rows x'_i - f_i(x, u, t). Both blocks are square, so the system is,
and the two mix — a DAE component next to an ordinary FMU needs nothing special.
IDASetId says which unknowns are differential, and IDACalcIC solves for the
algebraic ones and for the derivatives, at the start and again after every event:
the components initialize consistently with themselves, not with the coupled
system, and an event moves them the same way.

The coupling stays where it was for CVODE: updateInputs() propagates connected
outputs into inputs before the rows are evaluated, so the residuals see a
consistent coupling instead of the value left over from the last accepted step,
and a loop spanning components is still resolved by AlgLoop. Lifting the
connection equations into residual rows of their own — which is what would let
one global implicit system replace that inner solve — is the next step, not this
one.

* SystemSC: ida_res / ida_roots, the [x | z] layout, setDaePoint/getDaePoint,
  calcConsistentInitialConditions, doStepIDA, and the IDA setup and teardown
  beside the CVODE ones. Dense Jacobian for now; the manifest's residual
  dependencies are a sparsity pattern for KLU, which matters as soon as the
  system is more than small.
* oms_solver_sc_ida: DAE mode is not really a choice — a component that declares
  a DAE formulation has no explicit ODE for CVODE to integrate — so the system
  switches to IDA on its own and says so. The solver still has a name, for the
  SSD and for --solver.
* ComponentFMU3ME: DAE mode is switched on at instantiation, which is where
  Configuration Mode is. The checks that need the state count moved to
  validateDaeMode(), called from initialize(): an FMU only reports how many
  continuous states it has once it has left Initialization Mode.
* instantiated_model.py: SolverType duplicates the C enum's *values*, so
  inserting a solver renumbered oms_ma/oms_mav/oms_mav2 and setSolver() started
  choosing the wrong one — 37 testsuite failures with no hint of why. Values
  fixed and the duplication called out in a comment; deriving them from the C
  API would be better. fmu.py has a second copy, _FMU_SOLVER, which still sent
  6 for 'ma': that is oms_solver_wc_min now, SystemWC::setSolver refuses it,
  and the command line could no longer run any Co-Simulation FMU. Fixed, with
  'ida' added there too, and covered by simulation/fmuSimulation3.py, which
  selects every solver by name on both kinds of an FMU.

Requires OpenModelica/OMSimulator-3rdParty#111 (SUNDIALS_ENABLE_IDA was OFF).

Tested with a --daeMode Model Exchange FMU exported by OpenModelica
(OpenModelica/OpenModelica#16622): it simulates through IDA, and the trajectory
agrees with the same model exported as an ordinary ODE FMU and run through
CVODE by this same master to ~1e-8, state event included. ctest: 117/117, the
same as master.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
FMI 3.0 changed this from FMI 2.0: the `dependencies` attribute of an <Output>,
<InitialUnknown> and the rest holds **value references**, not one-based indices
into the variable list. Both FMI 3.0 components read them as indices, the FMI 2.0
way, so the dependency graph was wired to whichever variables happened to sit at
those positions — and an FMU whose dependency names value reference 0, a
perfectly ordinary one, was rejected outright:

  warning: Output q has bad dependency on variable with index 0 which couldn't be resolved
  error:   B: erroneous dependencies detected in modelDescription.xml

which is how this was found: two connected FMI 3.0 FMUs, the driven one with its
input at value reference 0.

Resolve them as value references. ComponentFMUME keeps the index reading, which
is right for FMI 2.0.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
…lica#1645)

An algebraic loop over connections was solved by an inner KINSOL Newton, run to
convergence inside every residual evaluation, on top of whatever solver each FMU
hides behind its own interface. Where IDA drives the system it does not have to
be: the loop's connections become unknowns and rows of the system itself, and
the coupling constraint gets the integrator's own error control.

The translation is direct, because it is the same system the inner solver had:
each connection of the loop contributes one unknown — the value of the input it
drives — and one row, output - input = 0. They are algebraic unknowns, they
follow the components' in y, and updateInputs() leaves those loops alone since
IDA has already set their inputs.

A loop is only lifted if all of its connections carry reals; one with an integer
or boolean connection keeps its own solver, and so does every loop when the
system is not in DAE mode, where nothing here applies.

Initialization and events leave every loop to IDACalcIC rather than to the inner
solver. They have to: a component in DAE mode does not solve for its own
algebraic outputs — the master does — so "set the input, read the output back"
has nothing behind it, and KINSOL fails on a singular Jacobian. The consistent
initial conditions are solved for the whole coupled system instead, which is
where that belongs.

Tested with a loop across two FMUs, one of them --daeMode, against the same two
components written as a single Modelica model and simulated by OpenModelica:

                          x(0.5)            x(1.0)         loop equation
  IDA, loop as rows    0.817368712680   0.701445480172        2.7e-14
  CVODE + KINSOL       0.817433688498   0.701528323696        8.7e-08
  monolithic omc       0.817368180018   0.701445143664           -

The lifted rows hold the connection to machine precision instead of the inner
solver's tolerance, and land two orders of magnitude closer to the monolithic
answer. (The KINSOL run of the same loop needs --directionalDerivatives=false:
it wants a Jacobian the FMU does not provide, where the lifted rows are part of
the one IDA differences itself.)

ctest: 117/117, the same as master.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
…lica#1645)

IDA's own difference quotient costs one residual evaluation per unknown, and
every one of those calls into every FMU of the system. The components' manifests
say which unknowns each residual reaches, which is enough to colour the columns:
two share a colour when no row reaches both, so one evaluation differences all of
them at once. The whole Jacobian then costs as many evaluations as there are
colours, which for a coupled system is a handful however many unknowns there are:

  1 pair of components:   5x5,  16 nonzeros, 5 colours
  4 pairs:               20x20, 64 nonzeros, 5 colours
  8 pairs:               40x40, 128 nonzeros, 5 colours

Where a manifest's <Residual> names dependencies they are used exactly; where it
names none, or names a value reference that is not one of the unknowns, the row
falls back to the whole component. An ODE component's rows and the rows of a
lifted loop fall back the same way. Every case is a superset of the true
structure, which is what a coloured difference quotient needs — omitting a
nonzero silently attributes one column's perturbation to another.

Two things the manifest does not say, and one it says in a way that had to be
read correctly:

* A residual's dependency on an *input* is not in it: the dependencies it
  declares are those of the FMU's own DAE Jacobian, whose columns are the states
  and the algebraic variables. An input only becomes an unknown here, where the
  master carries it for a lifted loop, so those columns are added to every row of
  the component regardless. Without them the Jacobian is wrong by exactly the
  -1 of the input's own row.
* The `derivative` attribute of a Float64 is a *value reference* in FMI 3.0
  where FMI 2.0 had an index — the same change as the ModelStructure
  dependencies, and fmi4c passes the raw attribute through under its FMI 2.0
  name. Read as an index, `derivative="0"` (a perfectly ordinary value
  reference) meant "no state", and the structure was discarded.

The linear solver is still dense: KLU wants SuiteSparse, which OMSimulator's
3rdParty does not carry. The structure is built and kept in a form a sparse
matrix can take directly, so that is a small step once it does — and the
evaluations, not the factorization, are what a coupled system pays for at this
size.

Checked against a column-at-a-time difference quotient of the same system: the
coloured Jacobian agrees to the last bit, and the loop model's results and step
counts are unchanged from the dense-differenced ones. ctest: 117/117.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
The structure the manifests give is what a sparse solver wants, so the Jacobian
is held as a CSC matrix and factorized by KLU instead of being a dense one. The
pattern never changes, so KLU reuses its symbolic factorization across the run,
and the matrix carries its structure in the same arrays as its values — which is
why SUNMatZero is not called on it: it would wipe the pattern, and every entry
is written each time anyway, structure and value together.

Without a usable structure nothing changes: the matrix is dense and IDA
differences it itself.

Needs OpenModelica/OMSimulator-3rdParty#111, which brings in SuiteSparse and
turns SUNDIALS_ENABLE_KLU on.

Same answers as the dense factorization, to the last digit or two of the
trajectory, and the same step counts:

              x(0.5)            x(1.0)          loop equation
  dense    0.817368712680   0.701445480172        2.7e-14
  KLU      0.817368712680   0.701445480171        2.6e-14
  omc      0.817368180018   0.701445143664           -

ctest: 117/117.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfnWV25k2u9x4CHxcnQbF7
@adrpo
adrpo force-pushed the fmi-ls-dae-support branch from cb87beb to adff1b0 Compare November 1, 2026 10:16
Nothing in the testsuite ran the DAE path, so codecov reported most of this
PR as uncovered: the fmi-ls-dae manifest, the switch into DAE mode, IDA, the
loop lifted into rows and the KLU factorization only ran in hand-made checks.

* simulation/DaeMode1.py: DaeLoopA (--daeMode) and an ordinary LoopB in an
  algebraic loop, through an SSP. The system switches to IDA by itself, lifts
  the loop's two connections into rows and colours the 5x5 Jacobian.
  x(1) = 0.701445, the closed-loop solution 0.5 + 0.5*exp(-1/1.1).
* simulation/DaeMode2.py: DaeLoopA alone through the FMU API, once with IDA
  selected by name and once with the default CVODE, which the system replaces
  with IDA. Both give x(1) = 0.683940 = 0.5 + 0.5*exp(-1).

Both FMUs were exported by OpenModelica with --simCodeTarget=C+Rust (the
ls-dae export of OpenModelica#17185), so they carry x86_64-linux binaries only
and the tests are enabled for linux only. They need glibc 2.38 and run on the
24.04 and 26.04 images; ASan is off for them, not verified here.

Refs OpenModelica#1645

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/Update Submodules Allow a pull request to update submodules project/openscaling OpenSCALING related issues and pull requests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant