Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions src/compare.jl
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ end

"""
compare_with_reference(sol, ref_csv_path, model_dir, model;
settings) → (total, pass, skip, diff_csv)
settings, signals) → (total, pass, skip, diff_csv)

Compare a DifferentialEquations / MTK solution against the MAP-LIB reference CSV.

Expand All @@ -354,33 +354,42 @@ is written whenever there are failures or skipped signals.

# Keyword arguments
- `settings` — a `CompareSettings` instance controlling tolerances and the
error function. Defaults to the module-level settings returned
by `compare_settings()`. Use `configure_comparison!` to change
the defaults, or pass a local `CompareSettings(...)` here.
error function.
- `signals` — explicit list of signal names to compare. When non-empty,
overrides `comparisonSignals.txt` and the full reference CSV
column list.
"""
function compare_with_reference(
sol,
ref_csv_path::String,
model_dir::String,
model::String;
settings::CompareSettings = _CMP_SETTINGS,
signals::Vector{String} = String[],
)::Tuple{Int,Int,Int,String}

times, ref_data = _read_ref_csv(ref_csv_path)
isempty(times) && return 0, 0, 0, ""

# Determine which signals to compare: prefer comparisonSignals.txt
sig_file = joinpath(dirname(ref_csv_path), "comparisonSignals.txt")
using_sig_file = isfile(sig_file)
signals = if using_sig_file
sigs = filter(s -> lowercase(s) != "time" && !isempty(s), strip.(readlines(sig_file)))
sigs_missing = filter(s -> !haskey(ref_data, s), sigs)
isempty(sigs_missing) || error("Signal(s) listed in comparisonSignals.txt not present in reference CSV: $(join(sigs_missing, ", "))")
sigs
# Determine which signals to compare.
# Prefer the caller-supplied list; fall back to comparisonSignals.txt, then
# all columns in the reference CSV.
signals = if !isempty(signals)
sigs_missing = filter(s -> !haskey(ref_data, s), signals)
isempty(sigs_missing) || error("Signal(s) not present in reference CSV: $(join(sigs_missing, ", "))")
signals
else
filter(k -> lowercase(k) != "time", collect(keys(ref_data)))
sig_file = joinpath(dirname(ref_csv_path), "comparisonSignals.txt")
if isfile(sig_file)
sigs = filter(s -> lowercase(s) != "time" && !isempty(s), strip.(readlines(sig_file)))
sigs_missing = filter(s -> !haskey(ref_data, s), sigs)
isempty(sigs_missing) || error("Signal(s) listed in comparisonSignals.txt not present in reference CSV: $(join(sigs_missing, ", "))")
sigs
else
filter(k -> lowercase(k) != "time", collect(keys(ref_data)))
end
end
n_total = length(signals)
n_total = length(signals)

# ── Build variable accessor map ──────────────────────────────────────────────
# var_access: normalized name → Int (state index) or MTK symbolic (observed).
Expand Down
34 changes: 24 additions & 10 deletions src/pipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,34 @@ function test_model(omc::OMJulia.OMCSession, model::String, results_root::String
par_ok || return ModelResult(
model, true, exp_t, exp_err, false, par_t, par_err, 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.
ref_csv = isempty(ref_root) ? nothing : _ref_csv_path(ref_root, model)
cmp_signals = if ref_csv !== nothing
sig_file = joinpath(dirname(ref_csv), "comparisonSignals.txt")
if isfile(sig_file)
String.(filter(s -> lowercase(s) != "time" && !isempty(s), strip.(readlines(sig_file))))
else
_, ref_data = _read_ref_csv(ref_csv)
filter(k -> lowercase(k) != "time", collect(keys(ref_data)))
end
else
String[]
end

# Phase 3 ──────────────────────────────────────────────────────────────────
sim_ok, sim_t, sim_err, sol = run_simulate(ode_prob, model_dir, model; csv_max_size_mb)
sim_ok, sim_t, sim_err, sol = run_simulate(ode_prob, model_dir, model;
csv_max_size_mb, cmp_signals)

# Phase 4 (optional) ───────────────────────────────────────────────────────
cmp_total, cmp_pass, cmp_skip, cmp_csv = 0, 0, 0, ""
if sim_ok && !isempty(ref_root)
ref_csv = _ref_csv_path(ref_root, model)
if ref_csv !== nothing
try
cmp_total, cmp_pass, cmp_skip, cmp_csv =
compare_with_reference(sol, ref_csv, model_dir, model)
catch e
@warn "Reference comparison failed for $model: $(sprint(showerror, e))"
end
if sim_ok && ref_csv !== nothing
try
cmp_total, cmp_pass, cmp_skip, cmp_csv =
compare_with_reference(sol, ref_csv, model_dir, model;
signals = cmp_signals)
catch e
@warn "Reference comparison failed for $model: $(sprint(showerror, e))"
end
end

Expand Down
24 changes: 18 additions & 6 deletions src/simulate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@ import ModelingToolkit
import Printf: @sprintf

"""
run_simulate(ode_prob, model_dir, model; csv_max_size_mb) → (success, time, error, sol)
run_simulate(ode_prob, model_dir, model; cmp_signals, csv_max_size_mb) → (success, time, error, sol)

Solve `ode_prob` with Rodas5P (stiff solver). On success, also writes the
full solution as a CSV file `<Short>_sim.csv` in `model_dir`.
solution as a CSV file `<Short>_sim.csv` in `model_dir`.
Writes a `<model>_sim.log` file in `model_dir`.
Returns `nothing` as the fourth element on failure.

CSV files larger than `csv_max_size_mb` MiB are deleted and replaced with a
When `cmp_signals` is non-empty, only observed variables whose names appear in
that list are written to the CSV, keeping file sizes small when only a subset
of signals will be compared.

CSV files larger than `csv_max_size_mb` MiB are replaced with a
`<Short>_sim.csv.toobig` marker so that the report can note the omission.
"""
function run_simulate(ode_prob, model_dir::String, model::String;
csv_max_size_mb::Int = CSV_MAX_SIZE_MB)::Tuple{Bool,Float64,String,Any}
function run_simulate(ode_prob, model_dir::String,
model::String;
cmp_signals ::Vector{String} = String[],
csv_max_size_mb::Int = CSV_MAX_SIZE_MB)::Tuple{Bool,Float64,String,Any}
sim_success = false
sim_time = 0.0
sim_error = ""
Expand Down Expand Up @@ -67,7 +73,13 @@ function run_simulate(ode_prob, model_dir::String, model::String;
sys = sol.prob.f.sys
vars = ModelingToolkit.unknowns(sys)
obs_eqs = ModelingToolkit.observed(sys)
obs_syms = [eq.lhs for eq in obs_eqs]
# Only save observed variables that appear in cmp_signals.
# This avoids writing thousands of algebraic variables to disk when
# only a handful are actually verified during comparison.
norm_cmp = Set(_normalize_var(s) for s in cmp_signals)
obs_eqs_filtered = isempty(norm_cmp) ? obs_eqs :
filter(eq -> _normalize_var(string(eq.lhs)) in norm_cmp, obs_eqs)
obs_syms = [eq.lhs for eq in obs_eqs_filtered]
col_names = vcat(
[_clean_var_name(string(v)) for v in vars],
[_clean_var_name(string(s)) for s in obs_syms],
Expand Down
Loading