Skip to content

Commit de49d10

Browse files
AnHeuermannclaude
andauthored
Implement full BaseModelicaLibraryTesting pipeline (#2)
Add a complete four-phase pipeline for testing Modelica libraries via Base Modelica and Julia: 1. Export – instantiate models to .bmo via OMJulia (src/export.jl) 2. Parse – create ODEProblem via BaseModelica.jl (src/parse_bm.jl) 3. Simulate – solve with Rodas5P from DifferentialEquations.jl (src/simulate.jl) 4. Compare – validate against MAP-LIB reference CSVs with configurable rel/abs tolerances; generate interactive Dygraph diff pages (src/compare.jl) Top-level `main()` and `test_model()` orchestrate the full pipeline and produce an HTML summary report (src/pipeline.jl, src/report.jl). Also includes: - CI workflow: add OpenModelica setup, omc-version matrix, sanity check step against MSL ChuaCircuit, and artifact upload - Project.toml: add dependencies (BaseModelica, DifferentialEquations, ModelingToolkit, OMJulia, ZMQ, etc.) - assets/: bundle Dygraph JS/CSS and an HTML diff template - README: add usage docs and MSL v4.1.0 example - .gitignore: exclude generated logs, CSVs, HTMLs, and result directories Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fd4c9f0 commit de49d10

16 files changed

Lines changed: 1322 additions & 13 deletions

.github/workflows/CI.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25+
omc-version:
26+
- 'nightly'
2527
version:
2628
- '1.10'
2729
- '1.12'
@@ -31,10 +33,31 @@ jobs:
3133
- x64
3234
steps:
3335
- uses: actions/checkout@v6
34-
- uses: julia-actions/setup-julia@v2
36+
37+
- name: "Set up OpenModelica Compiler"
38+
uses: OpenModelica/setup-openmodelica@v1.0
39+
with:
40+
version: ${{ matrix.omc-version }}
41+
packages: |
42+
omc
43+
libraries: |
44+
'Modelica 4.1.0'
45+
46+
- name: "Setup Julia ${{ matrix.version }}-${{ matrix.arch }}"
47+
uses: julia-actions/setup-julia@v2
3548
with:
3649
version: ${{ matrix.version }}
3750
arch: ${{ matrix.arch }}
3851
- uses: julia-actions/cache@v2
3952
- uses: julia-actions/julia-buildpkg@v1
4053
- uses: julia-actions/julia-runtest@v1
54+
55+
- name: Sanity check
56+
run: |
57+
julia --project='.' -e 'using BaseModelicaLibraryTesting; main(library = "Modelica", version = "4.1.0", filter = "Modelica.Electrical.Analog.Examples.ChuaCircuit", results_root = "main/Modelica/4.1.0/")'
58+
59+
- name: Upload test results
60+
uses: actions/upload-artifact@v6
61+
with:
62+
name: test-results-${{ matrix.version }}-${{ matrix.os }}-${{ matrix.arch }}
63+
path: main/

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,15 @@
88

99
# Julia files
1010
/Manifest*.toml
11+
12+
# Python
13+
.CondaPkg/
14+
15+
# Generated files and example results
16+
*.log
17+
*.html
18+
!asserts/*.html
19+
*.csv
20+
*.bmo
21+
MAP-LIB_ReferenceResults/
22+
main/

Project.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
name = "BaseModelicaLibraryTesting"
22
uuid = "e216686d-a052-4e92-9543-563498248f9c"
3-
authors = ["Andreas Heuermann"]
43
version = "0.1.0-DEV"
4+
authors = ["AnHeuermann"]
5+
6+
[deps]
7+
BaseModelica = "a17d5099-185d-4ff5-b5d3-51aa4569e56d"
8+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
9+
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
10+
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
11+
OMJulia = "0f4fe800-344e-11e9-2949-fb537ad918e1"
12+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
13+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
14+
ZMQ = "c2297ded-f4af-51ae-bb23-16f91089e4e1"
515

616
[compat]
17+
OMJulia = "0.3.3"
718
julia = "1.10.10, 1.12.5"
819

920
[extras]

README.md

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# BaseModelicaLibraryTesting
1+
# BaseModelicaLibraryTesting.jl
22

3-
[![Build Status](https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml?query=branch%3Amain)
3+
[![Build Status][build-badge-svg]][build-action-url]
44

55
Experimental Base Modelica library testing based on Julia.
66

@@ -15,16 +15,64 @@ For a given Modelica library test
1515
[DifferentialEquations.jl][diffeqjl-url])
1616
4. Validating simulation results
1717

18+
## Usage
19+
20+
```julia
21+
main(
22+
library = "<Modelica library name>",
23+
version = "<Modelica library version>",
24+
filter = "<Modelica class filter>",
25+
omc_exe = "path/to/omc",
26+
results_root = "results",
27+
ref_root = "path/to/ReferenceResults"
28+
)
29+
```
30+
31+
If reference results are available provide the path via `ref_root`.
32+
33+
### Example - Testing the Modelica Standard Library v4.1.0
34+
35+
For example, for the Modelica Standard Library v4.1.0 reference results can be obtained by cloning the [MAP-LIB_ReferenceResults][map-lib-ref-results-url] repository:`
36+
37+
```bash
38+
git clone --depth 1 -b v4.1.0 https://github.com/modelica/MAP-LIB_ReferenceResults
39+
```
40+
41+
Run the library testing for the ChuaCircuit example of the Modelica Standard
42+
Library v4.1.0 with:
43+
44+
```julia
45+
using BaseModelicaLibraryTesting
46+
47+
main(
48+
library = "Modelica",
49+
version = "4.1.0",
50+
filter = "Modelica.Electrical.Analog.Examples.ChuaCircuit",
51+
omc_exe = "omc",
52+
results_root = "main/Modelica/4.1.0/",
53+
ref_root = "MAP-LIB_ReferenceResults"
54+
)
55+
```
56+
57+
Preview the generated HTML report at `main/Modelica/4.1.0/report.html`.
58+
59+
```bash
60+
python -m http.server -d main/Modelica/4.1.0/
61+
```
62+
1863
## License
1964

2065
This package is available under the [OSMC-PL License][osmc-license-file] and the
2166
[AGPL-3.0 License][agpl-file]. See the [OSMC-License.txt][osmc-license-file]
2267
file for details.
2368

69+
[build-badge-svg]: https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml/badge.svg?branch=main
70+
[build-action-url]: https://github.com/OpenModelica/BaseModelicaLibraryTesting.jl/actions/workflows/CI.yml?query=branch%3Amain
2471
[openmodelica-url]: https://openmodelica.org/
25-
[omjuliajl-url]: https://github.com/OpenModelica/OMJulia.jl
26-
[osmc-license-file]: OSMC-License.txt
27-
[agpl-file]: LICENSE
2872
[basemodelicajl-url]: https://github.com/SciML/BaseModelica.jl
2973
[modelingtoolkitjl-url]: https://github.com/SciML/ModelingToolkit.jl
3074
[diffeqjl-url]: https://github.com/SciML/DifferentialEquations.jl
75+
[omjuliajl-url]: https://github.com/OpenModelica/OMJulia.jl
76+
[map-lib-ref-results-url]: https://github.com/modelica/MAP-LIB_ReferenceResults/tree/v4.1.0
77+
[osmc-license-file]: OSMC-License.txt
78+
[agpl-file]: LICENSE

assets/diff_template.html

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8"/>
5+
<title>Diff &#x2014; {{TITLE}}</title>
6+
<link rel="stylesheet" href="../../assets/dygraph.min.css"/>
7+
<script src="../../assets/dygraph.min.js"></script>
8+
<style>
9+
body { font-family: sans-serif; margin: 2em; font-size: 14px; }
10+
h1 { font-size: 1.3em; }
11+
h3 { font-size: 1.1em; margin-top: 2.5em; border-bottom: 1px solid #ddd;
12+
padding-bottom: 4px; }
13+
.graph { width: 100%; height: 320px; margin-bottom: 0.5em; }
14+
.meta { color: #555; font-size: 12px; margin: 0.4em 0 2em; }
15+
a { color: #0366d6; text-decoration: none; }
16+
a:hover { text-decoration: underline; }
17+
.dygraph-legend { font-size: 12px !important; }
18+
</style>
19+
</head>
20+
<body>
21+
<h1>Diff &#x2014; {{MODEL}}</h1>
22+
<p class="meta">
23+
{{N_FAIL}} signal(s) outside tolerance
24+
(rel &#x2264; {{REL_TOL_PCT}}%,&nbsp; abs &#x2264; {{ABS_TOL}})
25+
&nbsp;&middot;&nbsp;
26+
<a href="{{CSV_NAME}}">Download CSV</a>
27+
</p>
28+
<div id="charts"></div>
29+
<script>
30+
var csvRaw = `{{CSV_DATA}}`;
31+
32+
(function () {
33+
var lines = csvRaw.trim().split('\n');
34+
var headers = lines[0].split(',').map(function (s) { return s.trim(); });
35+
var rows = lines.slice(1).filter(function (l) { return l.trim() !== ''; })
36+
.map(function (l) { return l.split(',').map(Number); });
37+
38+
// Collect unique signal names from columns ending with "_ref"
39+
var sigs = [], seen = {};
40+
headers.forEach(function (h) {
41+
if (h.slice(-4) === '_ref') {
42+
var sig = h.slice(0, h.length - 4);
43+
if (!seen[sig]) { sigs.push(sig); seen[sig] = true; }
44+
}
45+
});
46+
47+
var container = document.getElementById('charts');
48+
49+
sigs.forEach(function (sig, si) {
50+
var refIdx = headers.indexOf(sig + '_ref');
51+
var simIdx = headers.indexOf(sig + '_sim');
52+
var errIdx = headers.indexOf(sig + '_relerr');
53+
54+
var data = rows.map(function (r) {
55+
return [r[0], r[refIdx], r[simIdx], r[errIdx]];
56+
});
57+
58+
var h3 = document.createElement('h3');
59+
h3.textContent = sig;
60+
var div = document.createElement('div');
61+
div.id = 'g' + si;
62+
div.className = 'graph';
63+
container.appendChild(h3);
64+
container.appendChild(div);
65+
66+
new Dygraph(div, data, {
67+
labels: ['time', sig + ' (ref)', sig + ' (sim)', 'rel. err'],
68+
colors: ['#1a73e8', '#e8700a', '#b0b0b0'],
69+
series: {
70+
'rel. err': { axis: 'y2', strokeWidth: 1 }
71+
},
72+
axes: {
73+
y: { axisLabelWidth: 70 },
74+
y2: {
75+
independentTicks: true,
76+
axisLabelFormatter: function (v) {
77+
return (v * 100).toPrecision(3) + '%';
78+
},
79+
valueFormatter: function (v) {
80+
return (v * 100).toPrecision(4) + '%';
81+
}
82+
}
83+
},
84+
y2label: 'Relative Error',
85+
xlabel: 'time',
86+
legend: 'always',
87+
showRangeSelector: true,
88+
highlightCircleSize: 3,
89+
strokeWidth: 2
90+
});
91+
});
92+
}());
93+
</script>
94+
</body>
95+
</html>

assets/dygraph.min.css

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/dygraph.min.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/BaseModelicaLibraryTesting.jl

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
module BaseModelicaLibraryTesting
22

3-
# Write your package code here.
3+
import OMJulia
4+
import OMJulia: sendExpression
5+
import BaseModelica
6+
import DifferentialEquations: solve, Rodas5P, ReturnCode
7+
import ModelingToolkit
8+
import Dates: now
9+
import Printf: @sprintf
410

5-
end
11+
include("types.jl")
12+
include("compare.jl") # defines _clean_var_name used by simulate.jl
13+
include("export.jl")
14+
include("parse_bm.jl")
15+
include("simulate.jl")
16+
include("report.jl")
17+
include("pipeline.jl")
18+
19+
# ── Public API ─────────────────────────────────────────────────────────────────
20+
21+
# Shared types and constants
22+
export ModelResult, CompareSettings
23+
export LIBRARY, LIBRARY_VERSION, CMP_REL_TOL, CMP_ABS_TOL
24+
25+
# Comparison configuration
26+
export configure_comparison!, compare_settings
27+
28+
# Pipeline phases
29+
export run_export # Phase 1: Base Modelica export via OMC
30+
export run_parse # Phase 2: BaseModelica.jl → ODEProblem
31+
export run_simulate # Phase 3: DifferentialEquations solve + CSV
32+
33+
# Reference comparison
34+
export compare_with_reference, write_diff_html
35+
36+
# HTML report
37+
export generate_report
38+
39+
# Top-level orchestration
40+
export test_model, main
41+
42+
end # module BaseModelicaLibraryTesting

0 commit comments

Comments
 (0)