11# ── Phase 3: ODE simulation with DifferentialEquations / MTK ──────────────────
22
3- import DifferentialEquations: init, solve, ReturnCode
3+ import DifferentialEquations
44import OrdinaryDiffEqBDF
55import Logging
66import ModelingToolkit
77import Printf: @sprintf
88
9+ """ Module-level default simulation settings. Modify via `configure_simulate!`."""
10+ const _SIM_SETTINGS = SimulateSettings (solver = DifferentialEquations. Rodas5Pr ())
11+
12+ """
13+ configure_simulate!(; solver, saveat_n) → SimulateSettings
14+
15+ Update the module-level simulation settings in-place and return them.
16+
17+ # Keyword arguments
18+ - `solver` — any SciML ODE/DAE algorithm instance (e.g. `FBDF()`, `Rodas5P()`).
19+ - `saveat_n` — number of uniform time points for purely algebraic systems.
20+
21+ # Example
22+
23+ ```julia
24+ using OrdinaryDiffEqBDF
25+ configure_simulate!(solver = FBDF())
26+ ```
27+ """
28+ function configure_simulate! (;
29+ solver :: Union{Any,Nothing} = nothing ,
30+ saveat_n :: Union{Int,Nothing} = nothing ,
31+ )
32+ isnothing (solver) || (_SIM_SETTINGS. solver = solver)
33+ isnothing (saveat_n) || (_SIM_SETTINGS. saveat_n = saveat_n)
34+ return _SIM_SETTINGS
35+ end
36+
37+ """
38+ simulate_settings() → SimulateSettings
39+
40+ Return the current module-level simulation settings.
41+ """
42+ simulate_settings () = _SIM_SETTINGS
43+
944"""
10- run_simulate(ode_prob, model_dir, model; cmp_signals, csv_max_size_mb) → (success, time, error, sol)
45+ run_simulate(ode_prob, model_dir, model; settings, cmp_signals, csv_max_size_mb) → (success, time, error, sol)
1146
12- Solve `ode_prob` with FBDF (stiff solver) . On success, also writes the
47+ Solve `ode_prob` using the algorithm in `settings. solver` . On success, also writes the
1348solution as a CSV file `<Short>_sim.csv` in `model_dir`.
1449Writes a `<model>_sim.log` file in `model_dir`.
1550Returns `nothing` as the fourth element on failure.
@@ -21,10 +56,12 @@ of signals will be compared.
2156CSV files larger than `csv_max_size_mb` MiB are replaced with a
2257`<Short>_sim.csv.toobig` marker so that the report can note the omission.
2358"""
24- function run_simulate (ode_prob, model_dir:: String ,
59+ function run_simulate (ode_prob,
60+ model_dir:: String ,
2561 model:: String ;
26- cmp_signals :: Vector{String} = String[],
27- csv_max_size_mb:: Int = CSV_MAX_SIZE_MB):: Tuple{Bool,Float64,String,Any}
62+ settings :: SimulateSettings = _SIM_SETTINGS,
63+ cmp_signals :: Vector{String} = String[],
64+ csv_max_size_mb:: Int = CSV_MAX_SIZE_MB):: Tuple{Bool,Float64,String,Any}
2865 sim_success = false
2966 sim_time = 0.0
3067 sim_error = " "
@@ -35,8 +72,9 @@ function run_simulate(ode_prob, model_dir::String,
3572 println (log_file, " Model: $model " )
3673 logger = Logging. SimpleLogger (log_file, Logging. Debug)
3774 t0 = time ()
75+
76+ solver = settings. solver
3877 try
39- # FBDF handles stiff DAE-like systems and purely algebraic systems well.
4078 # Redirect all library log output (including Symbolics/MTK warnings)
4179 # to the log file so they don't clutter stdout.
4280 sol = Logging. with_logger (logger) do
@@ -46,27 +84,36 @@ function run_simulate(ode_prob, model_dir::String,
4684 # Supply explicit time points so observed variables can be evaluated.
4785 sys = ode_prob. f. sys
4886 saveat = isempty (ModelingToolkit. unknowns (sys)) ?
49- collect (range (ode_prob. tspan[1 ], ode_prob. tspan[end ]; length = 500 )) :
87+ collect (range (ode_prob. tspan[1 ], ode_prob. tspan[end ]; length = settings . saveat_n )) :
5088 Float64[]
5189 kwargs = (saveat = saveat, dense = true )
5290
53- # Log solver settings
54- initializedSolver = init (ode_prob, OrdinaryDiffEqBDF. FBDF (); kwargs... )
55- solver_settings_string =
56- """
57- OrdinaryDiffEqBDF.FBDF()
58- saveat: $(let sv = initializedSolver. opts. saveat; isempty (sv) ? " []" : " $(length (sv)) points in [$(first (sv)) , $(last (sv)) ]" end )
59- abstol: $(@sprintf (" %.2e" , initializedSolver. opts. abstol))
60- reltol: $(@sprintf (" %.2e" , initializedSolver. opts. reltol))
61- adaptive: $(initializedSolver. opts. adaptive)
62- dense: $(initializedSolver. opts. dense)
63- """
91+ # Log solver settings — init returns NullODEIntegrator (no .opts)
92+ # when the problem has no unknowns (u::Nothing), so only inspect
93+ # opts when a real integrator is returned.
94+ integ = DifferentialEquations. init (ode_prob, solver; kwargs... )
95+ solver_settings_string = if hasproperty (integ, :opts )
96+ sv = integ. opts. saveat
97+ """
98+ Solver $(parentmodule (typeof (solver))) .$(nameof (typeof (solver)))
99+ saveat: $(isempty (sv) ? " []" : " $(length (sv)) points in [$(first (sv)) , $(last (sv)) ]" )
100+ abstol: $(@sprintf (" %.2e" , integ. opts. abstol))
101+ reltol: $(@sprintf (" %.2e" , integ. opts. reltol))
102+ adaptive: $(integ. opts. adaptive)
103+ dense: $(integ. opts. dense)
104+ """
105+ else
106+ sv_str = isempty (saveat) ? " []" : " $(length (saveat)) points in [$(first (saveat)) , $(last (saveat)) ]"
107+ " Solver (NullODEIntegrator — no unknowns)
108+ saveat: $sv_str
109+ dense: true"
110+ end
64111
65112 # Solve
66- solve (ode_prob, OrdinaryDiffEqBDF. FBDF (); kwargs... )
113+ DifferentialEquations . solve (ode_prob, OrdinaryDiffEqBDF. FBDF (); kwargs... )
67114 end
68115 sim_time = time () - t0
69- if sol. retcode == ReturnCode. Success
116+ if sol. retcode == DifferentialEquations . ReturnCode. Success
70117 sys = sol. prob. f. sys
71118 n_vars = length (ModelingToolkit. unknowns (sys))
72119 n_obs = length (ModelingToolkit. observed (sys))
@@ -84,7 +131,7 @@ function run_simulate(ode_prob, model_dir::String,
84131 sim_time = time () - t0
85132 sim_error = sprint (showerror, e, catch_backtrace ())
86133 end
87- println (log_file, " Solver settings: $ solver_settings_string" )
134+ println (log_file, solver_settings_string)
88135 println (log_file, " Time: $(round (sim_time; digits= 3 )) s" )
89136 println (log_file, " Success: $sim_success " )
90137 isempty (sim_error) || println (log_file, " \n --- Error ---\n $sim_error " )
0 commit comments