From 87888ec654f8e8c7b02ce047ec33e50345dc5c0c Mon Sep 17 00:00:00 2001
From: AnHeuermann <38031952+AnHeuermann@users.noreply.github.com>
Date: Tue, 24 Mar 2026 13:46:05 +0100
Subject: [PATCH 1/2] WIP
---
.github/scripts/gen_landing_page.py | 26 ++++-
Project.toml | 1 +
src/parse_bm.jl | 172 ++++++++++++++++++++++------
src/pipeline.jl | 25 ++--
src/report.jl | 80 +++++++++----
src/summary.jl | 26 ++++-
src/types.jl | 10 +-
test/chua_circuit.jl | 10 +-
8 files changed, 268 insertions(+), 82 deletions(-)
diff --git a/.github/scripts/gen_landing_page.py b/.github/scripts/gen_landing_page.py
index 71153d2d2..ff9092d57 100644
--- a/.github/scripts/gen_landing_page.py
+++ b/.github/scripts/gen_landing_page.py
@@ -79,9 +79,15 @@ def load_runs(site_root: Path) -> list[dict]:
except Exception:
continue
- models = data.get("models", [])
+ models = data.get("models", [])
n = len(models)
n_exp = sum(1 for m in models if m.get("export", False))
+ # Sub-steps — fall back to overall "parse" flag for older summary.json files
+ # that predate the three-step split.
+ has_substeps = any("antlr" in m for m in models)
+ n_antlr = sum(1 for m in models if m.get("antlr", m.get("parse", False))) if has_substeps else None
+ n_mtk = sum(1 for m in models if m.get("mtk", m.get("parse", False))) if has_substeps else None
+ n_ode = sum(1 for m in models if m.get("ode", m.get("parse", False))) if has_substeps else None
n_par = sum(1 for m in models if m.get("parse", False))
n_sim = sum(1 for m in models if m.get("sim", False))
@@ -100,6 +106,9 @@ def load_runs(site_root: Path) -> list[dict]:
"omc_version": data.get("omc_version", "?"),
"total": n,
"n_exp": n_exp,
+ "n_antlr": n_antlr,
+ "n_mtk": n_mtk,
+ "n_ode": n_ode,
"n_par": n_par,
"n_sim": n_sim,
"n_cmp": n_cmp,
@@ -123,6 +132,11 @@ def _pct_cell(num: int, den: int) -> str:
def render(runs: list[dict]) -> str:
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
+ def _parse_sub_cell(n_sub, n_prev):
+ if n_sub is None:
+ return '
— '
+ return _pct_cell(n_sub, n_prev)
+
if runs:
rows = []
for r in runs:
@@ -140,13 +154,15 @@ def render(runs: list[dict]) -> str:
{r['date']}
{r['duration']}
{_pct_cell(r['n_exp'], r['total'])}
- {_pct_cell(r['n_par'], r['n_exp'])}
+ {_parse_sub_cell(r['n_antlr'], r['n_exp'])}
+ {_parse_sub_cell(r['n_mtk'], r['n_antlr'] if r['n_antlr'] is not None else r['n_exp'])}
+ {_parse_sub_cell(r['n_ode'], r['n_mtk'] if r['n_mtk'] is not None else r['n_exp'])}
{_pct_cell(r['n_sim'], r['n_par'])}
{cmp_cell}
""")
rows_html = "\n".join(rows)
else:
- rows_html = ' No results yet. '
+ rows_html = ' No results yet. '
return f"""\
@@ -180,7 +196,9 @@ def render(runs: list[dict]) -> str:
Date
Duration
BM Export
- BM Parse
+ ANTLR
+ BM→MTK
+ ODEProblem
MTK Sim
Ref Cmp
diff --git a/Project.toml b/Project.toml
index 21560b138..120bdab48 100644
--- a/Project.toml
+++ b/Project.toml
@@ -10,6 +10,7 @@ DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
+ModelingToolkitBase = "7771a370-6774-4173-bd38-47e70ca0b839"
OMJulia = "0f4fe800-344e-11e9-2949-fb537ad918e1"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
diff --git a/src/parse_bm.jl b/src/parse_bm.jl
index dcaa2c935..3e8309c16 100644
--- a/src/parse_bm.jl
+++ b/src/parse_bm.jl
@@ -2,53 +2,159 @@
import BaseModelica
import Logging
+import ModelingToolkit
+import ModelingToolkitBase
"""
- run_parse(bm_path, model_dir, model) → (success, time, error, ode_prob)
+ run_parse(bm_path, model_dir, model) → NamedTuple
-Parse a Base Modelica `.bmo` file with BaseModelica.jl and create an
-`ODEProblem` from the `Experiment` annotation.
-Writes a `_parsing.log` file in `model_dir`.
-Returns `nothing` as the fourth element on failure.
+Parse a Base Modelica `.bmo` file with BaseModelica.jl in three sub-steps and
+create an `ODEProblem` from the `Experiment` annotation.
+
+Sub-steps timed and reported separately, each with its own log file:
+1. **ANTLR parser** — `_antlr.log` — parses the `.bmo` file into an AST.
+2. **BM → MTK** — `_mtk.log` — converts the AST into a
+ `ModelingToolkit.System`.
+3. **ODEProblem** — `_ode.log` — builds the `ODEProblem` using the
+ `Experiment` annotation.
+
+Returns a NamedTuple with fields:
+- `success`, `time`, `error` — overall result
+- `ode_prob` — the `ODEProblem` (or `nothing` on failure)
+- `antlr_success`, `antlr_time`
+- `mtk_success`, `mtk_time`
+- `ode_success`, `ode_time`
"""
function run_parse(bm_path::String, model_dir::String,
- model::String)::Tuple{Bool,Float64,String,Any}
- parse_success = false
- parse_time = 0.0
- parse_error = ""
+ model::String)
+ antlr_success = false; antlr_time = 0.0; antlr_error = ""
+ mtk_success = false; mtk_time = 0.0; mtk_error = ""
+ ode_success = false; ode_time = 0.0; ode_error = ""
ode_prob = nothing
+ package = nothing
+ sys = nothing
isdir(model_dir) || mkpath(model_dir)
- log_file = open(joinpath(model_dir, "$(model)_parsing.log"), "w")
- stdout_pipe = Pipe()
- println(log_file, "Model: $model")
- logger = Logging.SimpleLogger(log_file, Logging.Debug)
+
+ # ── Step 1: ANTLR parser ──────────────────────────────────────────────────
+ log1 = open(joinpath(model_dir, "$(model)_antlr.log"), "w")
+ pipe1 = Pipe()
+ logger = Logging.SimpleLogger(log1, Logging.Debug)
+ println(log1, "Model: $model")
t0 = time()
try
- # create_odeproblem returns an ODEProblem using the Experiment
- # annotation for StartTime/StopTime/Tolerance/Interval.
- # Redirect Julia log output to the log file and stdout/stderr to a
- # buffer so they can be appended after the summary lines.
- ode_prob = redirect_stdout(stdout_pipe) do
- redirect_stderr(stdout_pipe) do
+ package = redirect_stdout(pipe1) do
+ redirect_stderr(pipe1) do
Logging.with_logger(logger) do
- BaseModelica.create_odeproblem(bm_path)
+ BaseModelica.parse_file_antlr(bm_path)
end
end
end
- parse_time = time() - t0
- parse_success = true
+ antlr_time = time() - t0
+ antlr_success = true
catch e
- parse_time = time() - t0
- parse_error = sprint(showerror, e, catch_backtrace())
+ antlr_time = time() - t0
+ antlr_error = sprint(showerror, e, catch_backtrace())
+ end
+ close(pipe1.in)
+ captured = read(pipe1.out, String)
+ println(log1, "Time: $(round(antlr_time; digits=3)) s")
+ println(log1, "Success: $antlr_success")
+ isempty(captured) || print(log1, "\n--- Parser output ---\n", captured)
+ isempty(antlr_error) || println(log1, "\n--- Error ---\n$antlr_error")
+ close(log1)
+
+ # ── Step 2: Base Modelica → ModelingToolkit ───────────────────────────────
+ if antlr_success
+ log2 = open(joinpath(model_dir, "$(model)_mtk.log"), "w")
+ pipe2 = Pipe()
+ logger = Logging.SimpleLogger(log2, Logging.Debug)
+ println(log2, "Model: $model")
+ t0 = time()
+ try
+ sys = redirect_stdout(pipe2) do
+ redirect_stderr(pipe2) do
+ Logging.with_logger(logger) do
+ BaseModelica.baseModelica_to_ModelingToolkit(package)
+ end
+ end
+ end
+ mtk_time = time() - t0
+ mtk_success = true
+ catch e
+ mtk_time = time() - t0
+ mtk_error = sprint(showerror, e, catch_backtrace())
+ end
+ close(pipe2.in)
+ captured = read(pipe2.out, String)
+ println(log2, "Time: $(round(mtk_time; digits=3)) s")
+ println(log2, "Success: $mtk_success")
+ isempty(captured) || print(log2, "\n--- Parser output ---\n", captured)
+ isempty(mtk_error) || println(log2, "\n--- Error ---\n$mtk_error")
+ close(log2)
end
- close(stdout_pipe.in)
- captured = read(stdout_pipe.out, String)
- println(log_file, "Time: $(round(parse_time; digits=3)) s")
- println(log_file, "Success: $parse_success")
- isempty(captured) || print(log_file, "\n--- Parser output ---\n", captured)
- isempty(parse_error) || println(log_file, "\n--- Error ---\n$parse_error")
- close(log_file)
-
- return parse_success, parse_time, parse_error, ode_prob
+
+ # ── Step 3: ODEProblem generation ─────────────────────────────────────────
+ if mtk_success
+ log3 = open(joinpath(model_dir, "$(model)_ode.log"), "w")
+ pipe3 = Pipe()
+ logger = Logging.SimpleLogger(log3, Logging.Debug)
+ println(log3, "Model: $model")
+ t0 = time()
+ try
+ # Extract experiment annotation from the parsed package
+ annotation = nothing
+ try
+ annotation = package.model.long_class_specifier.composition.annotation
+ catch; end
+ exp_params = BaseModelica.parse_experiment_annotation(annotation)
+
+ ode_prob = redirect_stdout(pipe3) do
+ redirect_stderr(pipe3) do
+ Logging.with_logger(logger) do
+ _mv = ModelingToolkitBase.MissingGuessValue.Constant(0.0)
+ if !isnothing(exp_params)
+ tspan = (exp_params.StartTime, exp_params.StopTime)
+ extra_kw = isnothing(exp_params.Interval) ?
+ (reltol = exp_params.Tolerance,) :
+ (reltol = exp_params.Tolerance,
+ saveat = exp_params.Interval)
+ ModelingToolkit.ODEProblem(sys, [], tspan;
+ missing_guess_value = _mv, extra_kw...)
+ else
+ ModelingToolkit.ODEProblem(sys, [], (0.0, 1.0);
+ missing_guess_value = _mv)
+ end
+ end
+ end
+ end
+ ode_time = time() - t0
+ ode_success = true
+ catch e
+ ode_time = time() - t0
+ ode_error = sprint(showerror, e, catch_backtrace())
+ end
+ close(pipe3.in)
+ captured = read(pipe3.out, String)
+ println(log3, "Time: $(round(ode_time; digits=3)) s")
+ println(log3, "Success: $ode_success")
+ isempty(captured) || print(log3, "\n--- Parser output ---\n", captured)
+ isempty(ode_error) || println(log3, "\n--- Error ---\n$ode_error")
+ close(log3)
+ end
+
+ first_error = !isempty(antlr_error) ? antlr_error :
+ !isempty(mtk_error) ? mtk_error : ode_error
+ return (
+ success = ode_success,
+ time = antlr_time + mtk_time + ode_time,
+ error = first_error,
+ ode_prob = ode_prob,
+ antlr_success = antlr_success,
+ antlr_time = antlr_time,
+ mtk_success = mtk_success,
+ mtk_time = mtk_time,
+ ode_success = ode_success,
+ ode_time = ode_time,
+ )
end
diff --git a/src/pipeline.jl b/src/pipeline.jl
index b253f61aa..bc32bd5f3 100644
--- a/src/pipeline.jl
+++ b/src/pipeline.jl
@@ -73,12 +73,20 @@ function test_model(omc::OMJulia.OMCSession,
# Phase 1 ──────────────────────────────────────────────────────────────────
exp_ok, exp_t, exp_err = run_export(omc, model, model_dir, bm_path)
exp_ok || return ModelResult(
- model, false, exp_t, exp_err, false, 0.0, "", false, 0.0, "", 0, 0, 0, "")
+ model, false, exp_t, exp_err,
+ false, 0.0, "",
+ false, 0.0, false, 0.0, false, 0.0,
+ false, 0.0, "", 0, 0, 0, "")
# Phase 2 ──────────────────────────────────────────────────────────────────
- par_ok, par_t, par_err, ode_prob = run_parse(bm_path, model_dir, model)
- par_ok || return ModelResult(
- model, true, exp_t, exp_err, false, par_t, par_err, false, 0.0, "", 0, 0, 0, "")
+ par = run_parse(bm_path, model_dir, model)
+ par.success || return ModelResult(
+ model, true, exp_t, exp_err,
+ false, par.time, par.error,
+ par.antlr_success, par.antlr_time,
+ par.mtk_success, par.mtk_time,
+ par.ode_success, par.ode_time,
+ false, 0.0, "", 0, 0, 0, "")
# Resolve reference CSV and comparison signals early so phase 3 can filter
# the CSV output to only the signals that will actually be verified.
@@ -96,7 +104,7 @@ function test_model(omc::OMJulia.OMCSession,
end
# Phase 3 ──────────────────────────────────────────────────────────────────
- sim_ok, sim_t, sim_err, sol = run_simulate(ode_prob, model_dir, model;
+ sim_ok, sim_t, sim_err, sol = run_simulate(par.ode_prob, model_dir, model;
settings = sim_settings,
csv_max_size_mb, cmp_signals)
@@ -114,8 +122,11 @@ function test_model(omc::OMJulia.OMCSession,
return ModelResult(
model,
- true, exp_t, exp_err,
- true, par_t, par_err,
+ true, exp_t, exp_err,
+ true, par.time, par.error,
+ par.antlr_success, par.antlr_time,
+ par.mtk_success, par.mtk_time,
+ par.ode_success, par.ode_time,
sim_ok, sim_t, sim_err,
cmp_total, cmp_pass, cmp_skip, cmp_csv)
end
diff --git a/src/report.jl b/src/report.jl
index 8ce87a243..ccef062b5 100644
--- a/src/report.jl
+++ b/src/report.jl
@@ -99,10 +99,12 @@ Write an `index.html` overview report to `results_root` and return its path.
"""
function generate_report(results::Vector{ModelResult}, results_root::String,
info::RunInfo; csv_max_size_mb::Int = CSV_MAX_SIZE_MB)
- n = length(results)
- n_exp = count(r -> r.export_success, results)
- n_par = count(r -> r.parse_success, results)
- n_sim = count(r -> r.sim_success, results)
+ n = length(results)
+ n_exp = count(r -> r.export_success, results)
+ n_antlr = count(r -> r.antlr_success, results)
+ n_mtk = count(r -> r.mtk_success, results)
+ n_ode = count(r -> r.ode_success, results)
+ n_sim = count(r -> r.sim_success, results)
# Comparison summary (only models where cmp_total > 0)
cmp_results = filter(r -> r.cmp_total > 0, results)
@@ -114,13 +116,31 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
cmp_summary_row = n_cmp_models > 0 ? """
Reference Comparison (MAP-LIB) $n_cmp_pass $n_cmp_models $(pct(n_cmp_pass,n_cmp_models)) """ : ""
- rows = join(["""
+ rows = join([begin
+ antlr_status = !r.export_success ? "na" : (r.antlr_success ? "pass" : "fail")
+ mtk_status = !r.antlr_success ? "na" : (r.mtk_success ? "pass" : "fail")
+ ode_status = !r.mtk_success ? "na" : (r.ode_success ? "pass" : "fail")
+
+ antlr_cell = antlr_status == "na" ? """— """ :
+ _status_cell(r.antlr_success, r.antlr_time,
+ rel_log_file_or_nothing(results_root, r.name, "antlr"))
+ mtk_cell = mtk_status == "na" ? """— """ :
+ _status_cell(r.mtk_success, r.mtk_time,
+ rel_log_file_or_nothing(results_root, r.name, "mtk"))
+ ode_cell = ode_status == "na" ? """— """ :
+ _status_cell(r.ode_success, r.ode_time,
+ rel_log_file_or_nothing(results_root, r.name, "ode"))
+
+ """
$(r.name).bmo
$(_status_cell(r.export_success, r.export_time, rel_log_file_or_nothing(results_root, r.name, "export")))
- $(_status_cell(r.parse_success, r.parse_time, rel_log_file_or_nothing(results_root, r.name, "parsing")))
+ $(antlr_cell)
+ $(mtk_cell)
+ $(ode_cell)
$(_status_cell(r.sim_success, r.sim_time, rel_log_file_or_nothing(results_root, r.name, "sim")))
$(_cmp_cell(r, results_root, csv_max_size_mb))
- """ for r in results], "\n")
+ """
+ end for r in results], "\n")
bm_sha_link = isempty(info.bm_sha) ? "" :
""" ($(info.bm_sha) )"""
@@ -168,9 +188,11 @@ Total run time: $(time_str)
Stage Passed Total Rate
- Base Modelica Export (OpenModelica) $n_exp $n $(pct(n_exp,n))
- Parsing (BaseModelica.jl) $n_par $n_exp $(pct(n_par,n_exp))
- Simulation (MTK.jl) $n_sim $n_par $(pct(n_sim,n_par)) $cmp_summary_row
+ Base Modelica Export (OpenModelica) $n_exp $n $(pct(n_exp,n))
+ Base Modelica Parse - ANTLR $n_antlr $n_exp $(pct(n_antlr,n_exp))
+ Base Modelica Parse - BM→MTK $n_mtk $n_antlr $(pct(n_mtk,n_antlr))
+ Base Modelica Parse - ODEProblem $n_ode $n_mtk $(pct(n_ode,n_mtk))
+ Simulation (MTK.jl) $n_sim $n_ode $(pct(n_sim,n_ode)) $cmp_summary_row
@@ -178,16 +200,20 @@ Total run time: $(time_str)
Model
BM Export
- BM Parse
- MTK Sim
- Ref Cmp
+ ANTLR
+ BM→MTK
+ ODEProblem
+ MTK Simulation
+ Result Comparison
- All Pass Fail
- All Pass Fail
- All Pass Fail
- All Pass Fail
+ All Pass Fail
+ All Pass Fail
+ All Pass Fail
+ All Pass Fail
+ All Pass Fail
+ All Pass Fail
@@ -206,17 +232,21 @@ function applyFilters() {
nameInput.classList.add('invalid');
return;
}
- var exp = document.getElementById('f-exp').value;
- var par = document.getElementById('f-par').value;
- var sim = document.getElementById('f-sim').value;
- var cmp = document.getElementById('f-cmp').value;
+ var exp = document.getElementById('f-exp').value;
+ var antlr = document.getElementById('f-antlr').value;
+ var mtk = document.getElementById('f-mtk').value;
+ var ode = document.getElementById('f-ode').value;
+ var sim = document.getElementById('f-sim').value;
+ var cmp = document.getElementById('f-cmp').value;
document.querySelectorAll('#model-rows tr').forEach(function(row) {
var name = row.cells[0] ? row.cells[0].textContent : '';
var show = (!nameRe || nameRe.test(name)) &&
- (exp === 'all' || row.dataset.exp === exp) &&
- (par === 'all' || row.dataset.par === par) &&
- (sim === 'all' || row.dataset.sim === sim) &&
- (cmp === 'all' || row.dataset.cmp === cmp);
+ (exp === 'all' || row.dataset.exp === exp) &&
+ (antlr === 'all' || row.dataset.antlr === antlr) &&
+ (mtk === 'all' || row.dataset.mtk === mtk) &&
+ (ode === 'all' || row.dataset.ode === ode) &&
+ (sim === 'all' || row.dataset.sim === sim) &&
+ (cmp === 'all' || row.dataset.cmp === cmp);
row.style.display = show ? '' : 'none';
});
}
diff --git a/src/summary.jl b/src/summary.jl
index 847a9c8e7..b7f441169 100644
--- a/src/summary.jl
+++ b/src/summary.jl
@@ -43,6 +43,12 @@ function write_summary(
"\"export_time\":$(@sprintf "%.3f" r.export_time)," *
"\"parse\":$(r.parse_success)," *
"\"parse_time\":$(@sprintf "%.3f" r.parse_time)," *
+ "\"antlr\":$(r.antlr_success)," *
+ "\"antlr_time\":$(@sprintf "%.3f" r.antlr_time)," *
+ "\"mtk\":$(r.mtk_success)," *
+ "\"mtk_time\":$(@sprintf "%.3f" r.mtk_time)," *
+ "\"ode\":$(r.ode_success)," *
+ "\"ode_time\":$(@sprintf "%.3f" r.ode_time)," *
"\"sim\":$(r.sim_success)," *
"\"sim_time\":$(@sprintf "%.3f" r.sim_time)," *
"\"cmp_total\":$(r.cmp_total)," *
@@ -77,7 +83,9 @@ Parsed contents of a single `summary.json` file.
- `total_time_s` — wall-clock duration of the full test run in seconds
- `solver` — fully-qualified solver name, e.g. `"DifferentialEquations.Rodas5P"`
- `models` — vector of per-model dicts; each has keys
- `"name"`, `"export"`, `"parse"`, `"sim"`, `"cmp_total"`, `"cmp_pass"`
+ `"name"`, `"export"`, `"parse"`,
+ `"antlr"`, `"mtk"`, `"ode"` (parse sub-steps),
+ `"sim"`, `"cmp_total"`, `"cmp_pass"`
"""
struct RunSummary
library :: String
@@ -124,7 +132,7 @@ function load_summary(results_root::String)::Union{RunSummary,Nothing}
models = Dict{String,Any}[]
for m in eachmatch(
- r"\{\"name\":\"([^\"]*)\",\"export\":(true|false),\"export_time\":([\d.]+),\"parse\":(true|false),\"parse_time\":([\d.]+),\"sim\":(true|false),\"sim_time\":([\d.]+),\"cmp_total\":(\d+),\"cmp_pass\":(\d+)\}",
+ r"\{\"name\":\"([^\"]*)\",\"export\":(true|false),\"export_time\":([\d.]+),\"parse\":(true|false),\"parse_time\":([\d.]+),\"antlr\":(true|false),\"antlr_time\":([\d.]+),\"mtk\":(true|false),\"mtk_time\":([\d.]+),\"ode\":(true|false),\"ode_time\":([\d.]+),\"sim\":(true|false),\"sim_time\":([\d.]+),\"cmp_total\":(\d+),\"cmp_pass\":(\d+)\}",
txt)
push!(models, Dict{String,Any}(
"name" => string(m.captures[1]),
@@ -132,10 +140,16 @@ function load_summary(results_root::String)::Union{RunSummary,Nothing}
"export_time" => parse(Float64, m.captures[3]),
"parse" => m.captures[4] == "true",
"parse_time" => parse(Float64, m.captures[5]),
- "sim" => m.captures[6] == "true",
- "sim_time" => parse(Float64, m.captures[7]),
- "cmp_total" => parse(Int, m.captures[8]),
- "cmp_pass" => parse(Int, m.captures[9]),
+ "antlr" => m.captures[6] == "true",
+ "antlr_time" => parse(Float64, m.captures[7]),
+ "mtk" => m.captures[8] == "true",
+ "mtk_time" => parse(Float64, m.captures[9]),
+ "ode" => m.captures[10] == "true",
+ "ode_time" => parse(Float64, m.captures[11]),
+ "sim" => m.captures[12] == "true",
+ "sim_time" => parse(Float64, m.captures[13]),
+ "cmp_total" => parse(Int, m.captures[14]),
+ "cmp_pass" => parse(Int, m.captures[15]),
))
end
return RunSummary(
diff --git a/src/types.jl b/src/types.jl
index 18704c5ae..843237033 100644
--- a/src/types.jl
+++ b/src/types.jl
@@ -106,9 +106,15 @@ struct ModelResult
export_success :: Bool
export_time :: Float64
export_error :: String
- parse_success :: Bool
- parse_time :: Float64
+ parse_success :: Bool # true iff all three parse sub-steps succeeded
+ parse_time :: Float64 # total parse time (sum of three sub-steps)
parse_error :: String
+ antlr_success :: Bool # Phase 2a: ANTLR parser
+ antlr_time :: Float64
+ mtk_success :: Bool # Phase 2b: Base Modelica → ModelingToolkit conversion
+ mtk_time :: Float64
+ ode_success :: Bool # Phase 2c: ODEProblem generation
+ ode_time :: Float64
sim_success :: Bool
sim_time :: Float64
sim_error :: String
diff --git a/test/chua_circuit.jl b/test/chua_circuit.jl
index 1f3ffcbcb..e98f95150 100644
--- a/test/chua_circuit.jl
+++ b/test/chua_circuit.jl
@@ -15,12 +15,12 @@
exp_ok || @warn "Export error: $exp_err"
if exp_ok
- par_ok, _, par_err, ode_prob = run_parse(bm_path, model_dir, TEST_MODEL_CHUA)
- @test par_ok
- par_ok || @warn "Parse error: $par_err"
+ par = run_parse(bm_path, model_dir, TEST_MODEL_CHUA)
+ @test par.success
+ par.success || @warn "Parse error: $(par.error)"
- if par_ok
- sim_ok, _, sim_err, _ = run_simulate(ode_prob, model_dir, TEST_MODEL_CHUA)
+ if par.success
+ sim_ok, _, sim_err, _ = run_simulate(par.ode_prob, model_dir, TEST_MODEL_CHUA)
@test sim_ok
sim_ok || @warn "Simulation error: $sim_err"
end
From c260322db4f25f1cb3b18e7998d54ff1d8e48fbf Mon Sep 17 00:00:00 2001
From: AnHeuermann <38031952+AnHeuermann@users.noreply.github.com>
Date: Tue, 24 Mar 2026 13:57:34 +0100
Subject: [PATCH 2/2] Finer error reports for parsing Base Modelica
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Added separate log files and timings for steps of parsing:
* Phase 2a: ANTLR parser
* Phase 2b: Base Modelica → ModelingToolkit conversion
* Phase 2c: ODEProblem generation
* Update HTML report and overview.
* Updated summary.json
Co-authored-by: Claude Sonnet 4.6
---
src/report.jl | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/report.jl b/src/report.jl
index ccef062b5..e64f7c1ab 100644
--- a/src/report.jl
+++ b/src/report.jl
@@ -120,6 +120,7 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
antlr_status = !r.export_success ? "na" : (r.antlr_success ? "pass" : "fail")
mtk_status = !r.antlr_success ? "na" : (r.mtk_success ? "pass" : "fail")
ode_status = !r.mtk_success ? "na" : (r.ode_success ? "pass" : "fail")
+ sim_status = !r.ode_success ? "na" : (r.sim_success ? "pass" : "fail")
antlr_cell = antlr_status == "na" ? """— """ :
_status_cell(r.antlr_success, r.antlr_time,
@@ -130,14 +131,17 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
ode_cell = ode_status == "na" ? """— """ :
_status_cell(r.ode_success, r.ode_time,
rel_log_file_or_nothing(results_root, r.name, "ode"))
+ sim_cell = sim_status == "na" ? """— """ :
+ _status_cell(r.sim_success, r.sim_time,
+ rel_log_file_or_nothing(results_root, r.name, "sim"))
- """
+ """
$(r.name).bmo
$(_status_cell(r.export_success, r.export_time, rel_log_file_or_nothing(results_root, r.name, "export")))
$(antlr_cell)
$(mtk_cell)
$(ode_cell)
- $(_status_cell(r.sim_success, r.sim_time, rel_log_file_or_nothing(results_root, r.name, "sim")))
+ $(sim_cell)
$(_cmp_cell(r, results_root, csv_max_size_mb))
"""
end for r in results], "\n")
@@ -164,7 +168,7 @@ function generate_report(results::Vector{ModelResult}, results_root::String,
td.ok { background: #d4edda; color: #155724; }
td.partial { background: #fff3cd; color: #856404; }
td.fail { background: #f8d7da; color: #721c24; }
- td.na { color: #888; }
+ td.na { background: #f8d7da; color: #888; }
a { color: #0366d6; text-decoration: none; }
a:hover { text-decoration: underline; }
.filter-row th { background: #f5f5f5; }