diff --git a/src/compare.jl b/src/compare.jl index eca5c7c0..5cf987b6 100644 --- a/src/compare.jl +++ b/src/compare.jl @@ -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. @@ -354,9 +354,10 @@ 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, @@ -364,23 +365,31 @@ function compare_with_reference( 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). diff --git a/src/pipeline.jl b/src/pipeline.jl index 7be80824..4a1815b5 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -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 diff --git a/src/simulate.jl b/src/simulate.jl index 18c40923..d2d561b6 100644 --- a/src/simulate.jl +++ b/src/simulate.jl @@ -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 `_sim.csv` in `model_dir`. +solution as a CSV file `_sim.csv` in `model_dir`. Writes a `_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 `_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 = "" @@ -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],