A Julia package for running MSL (Modelica Standard Library) coverage tests against OM.jl. It auto-discovers all experiment models in the MSL using omc, runs each through the OM.jl pipeline (flatten -> translate -> simulate -> validate), and generates HTML coverage reports.
Each model is tested through up to four pipeline phases:
| Phase | Description |
|---|---|
FRONTEND |
Flatten the Modelica model via OMFrontend.jl |
BACKEND |
Lower the flat model via OMBackend.jl |
SIMULATE |
Solve the generated ODE/DAE system |
VALIDATE |
Compare simulation output against reference data |
Results are compared against Dymola-generated reference trajectories from the MAP-LIB reference results repository.
Measured 2026-10-04 on 425 MSL example models: frontend 423 (99.5 %), backend 398 (93.6 %), simulate 324 (76.2 %), validate 299 (70.4 %). Per domain and what stops the rest: docs/COVERAGE.md.
OMLibraryTesting.jl/
├── src/
│ ├── OMLibraryTesting.jl # Module entry point
│ ├── types.jl # Phase enum, ModelSpec, ModelResult, PhaseResult
│ ├── registry.jl # TOML override loader
│ ├── discovery.jl # omc-based experiment discovery
│ ├── runner.jl # WorkerManager and run_coverage orchestration
│ ├── comparison.jl # CSV trajectory comparison
│ ├── analysis.jl # Error categorization and analysis utilities
│ ├── report.jl # HTML report generation
│ └── testsuite.jl # Fetch + run the upstream OpenModelica testsuite (rtest / OM.jl modes)
├── models/
│ └── models.toml # Per-model overrides (atol, reltol, expected phase, signal mapping)
├── reference/
│ ├── csv/ # Dymola reference CSVs (MAP-LIB, MSL 3.2.3)
│ ├── signals/ # Per-model comparisonSignals.txt files
│ ├── download_refs.sh # Script to fetch/refresh reference files from GitHub
│ └── generate_refs.mos # OpenModelica script to generate reference results
├── docs/
│ └── COVERAGE.md # The latest published coverage (scripts/publish_coverage.jl)
├── results/ # Runtime output (ignored by git)
└── reports/ # Generated coverage reports (ignored by git)
using Pkg
Pkg.develop(path=".") # or add via the parent OM.jl environment
import OMLibraryTesting
# Run full MSL coverage (auto-discovers ~425 experiment models)
results = OMLibraryTesting.run_coverage(msl_version = "MSL:3.2.3")
# Filter by domain
results = OMLibraryTesting.run_coverage(domain = "Thermal", msl_version = "MSL:3.2.3")
# Run a single model
results = OMLibraryTesting.run_coverage(model = "Modelica.Thermal.HeatTransfer.Examples.TwoMasses")
# Generate an HTML report
OMLibraryTesting.generate_report(results)
# Print a summary to stdout
OMLibraryTesting.print_summary(results)
# Analyse failures
OMLibraryTesting.print_error_analysis(results)The "Latest coverage" section above and docs/COVERAGE.md are published by the MSL coverage workflow
(.github/workflows/coverage.yml: weekly, or by hand from the Actions tab with commit set). It runs
scripts/run_full_coverage.jl with OMJL_COVERAGE_NOSKIP=1 (every model through every phase) and passes the
serialized results to the publisher. The same by hand:
OMJL_COVERAGE_NOSKIP=1 julia --project=. scripts/run_full_coverage.jl
julia --project=. scripts/publish_coverage.jl logs/partial_full_msl_<timestamp>.jls [commits.txt] ["how the run was made"]run_coverage spawns cold worker processes that re-precompile the OM.jl stack after
every source edit, which costs minutes per cycle. For iterating on a single model from
a warm Julia REPL (with using OM done and Revise active), use the hot variant:
include("scripts/warm_validate.jl")
r = warm_validate("Modelica.Mechanics.MultiBody.Examples.Loops.Engine1a";
stopTime = 0.72, atol = 0.1, reltol = 0.05)
r.npass, r.nfail # validation score
r.results # per-signal table (signal, pass, maxerr, tmax, ours, ref, tol)
r.sol # solution object for probingWhat it does on each call:
- Forces a true rebuild (clears both the
OMBackend.IMTKGen.BUILTproblem cache and theOMBackend.COMPILED_MODELS_MTKgenerated-module cache, then re-runsOM.translate), so Revise-applied edits in any layer, including codegen, take effect. Passrebuild = falseto re-validate the cached build when only sampling or tolerances changed. - Simulates in-process and samples saved steps (
dense = false) on the same 21-point grid the harness uses, comparing every reference-CSV signal with the combined tolerance|err| <= atol + reltol * |ref|.
Keyword arguments mirror the models.toml overrides: stopTime, atol, reltol,
npoints, referenceFile, msl_version, solverKwargs, quiet.
The hot loop is for iteration; run_coverage remains the authoritative cold gate
before promoting a model in models/models.toml.
In addition to MSL coverage, the harness can fetch and run the upstream
OpenModelica testsuite/
regression tests (the .mos cases the rtest Perl harness drives). The first call
sparse-clones only the testsuite/ directory (partial, cone-mode checkout) into
.testsuite_cache/; later calls reuse it.
Two run modes share one fetch + discovery front-end:
| Mode | What it tests | Returns |
|---|---|---|
:omjl |
Each test's model target through the OM.jl FRONTEND -> BACKEND -> SIMULATE pipeline (reuses run_model / the worker pool). |
Vector{ModelResult} |
:rtest |
The upstream Perl rtest harness against the system omc (tests the C compiler). |
Vector{RtestResult} |
import OMLibraryTesting
# OM.jl pipeline over a testsuite subdirectory (default mode)
results = OMLibraryTesting.run_testsuite(; mode = :omjl,
subdir = "simulation/modelica/equations",
limit = 20)
OMLibraryTesting.print_summary(results)
# Upstream rtest harness against system omc
rs = OMLibraryTesting.run_testsuite(; mode = :rtest,
subdir = "flattening/modelica/scodeinst",
status = "correct", limit = 20)
OMLibraryTesting.print_rtest_summary(rs)Selected keywords (run_testsuite):
fetch(true) sparse-clone / update the testsuite first;ref("master") git ref;destcache dir. Passfetch = falseto skip the network and reuse the cache.subdirrestrict discovery to a testsuite subdirectory;filterregex over the repo-relative.mospath;status(default"correct")// status:header filter;limitcap the number of cases.:omjlforwardsmsl_version, stopTime, from_phase, to_phase, timeout, n_workers, check_sim_code;:rtestforwardsomcflags, verbose.
Notes and limitations:
:omjlmode only runs cases that load a bundled MSL version (3.2.x or 4.0.0) and target aModelica.*class. Cases using MSL 4.1.0/trunk, third-party libraries, or inline-defined models are reported as skipped with a reason. It stops atSIMULATEby default because the testsuite ships no validation CSVs in this harness.:rtestmode needsomc-diffin the scaffolded OPENMODELICAHOME.fetch_testsuitebuilds it fromtestsuite/difftoolwhen possible; that build requiresflexplus a C compiler. Ifflexis missing, install it (e.g.sudo apt-get install flex) and re-runfetch_testsuite, otherwiserun_rtestraises with a clear message.
A convenience wrapper lives in scripts/run_testsuite.jl.
The TOML file stores per-model overrides. The model list itself is always auto-discovered from omc. Example entry:
[models."Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum"]
expected = "validate"
referenceFile = "Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum.csv"
atol = 1e-3
reltol = 1e-3
[models."Modelica.Mechanics.MultiBody.Examples.Elementary.Pendulum".signalMapping]
"rev.phi" = "revolute1_phi"Reference CSVs use Modelica dot notation (e.g., rev.phi). OM.jl uses underscore notation (e.g., revolute1_phi). The default mapping converts dots to underscores. Add a signalMapping table for models where names differ between the reference and OM.jl output.
Reference trajectories come from the MAP-LIB Dymola reference results (MSL 3.2.3 branch). Run reference/download_refs.sh to fetch or refresh them.
GPL v3 / OSMC Public License 1.2. See LICENSE.md.