Skip to content

Commit 994ba6f

Browse files
sjoelundclaude
andauthored
wasm-jit: implement the missing runtime flags (#16206)
OMEdit sends `-startTime`/`-stopTime`/`-tolerance`/`-outputFormat` on every run, none of which the wasm-jit runtime implemented, so every simulation started from it was refused outright. `SimMeta::apply_flags` is C's `read_experiment` (plus the step-size checks `solver_main.c` makes right after it): the run scalars the model was translated with, overridden by the command line. `n_intervals` is C's `numSteps`, so a moved step size lands on the output grid. Whichever entry point owns the driver applies it once per run to its own copy — the host through `SimModel::run_meta`, `rt_sim_start` in-wasm, `_start` in the standalone. Implemented at the same time: - `-outputPath` and `-r`: result-file resolution. The standalone honoured neither. - `-noemit`: C's `sim_noemit`, i.e. `-outputFormat=empty`. - `-iit`: the time `-iif`'s result file is read at. - `-mei` / `-mbi`: the event-iteration and bisection caps, the latter in both `fixedstep` and gbode. - `-newtonFTol` / `-newtonXTol` / `-newtonMaxStepFactor`: a new `rt_set_newton_tuning` export, since the homotopy Newton and KINSOL both read them as C's globals do. - `-steadyState` / `-steadyStateTol`: reported through `terminated`, so every driver's stop path serves it. - `-w`: rides the `-lv` mask as `SHOW_ALL_WARNINGS`, so the one value pushed into the wasm runtime carries it. - `-logFormat=text`, `-daeMode` and `-jacobianThreads`: accepted with C's warnings. `-outputFormat=csv|plt|ia` stays refused: no writer here yet. The rejection an unimplemented flag gets no longer explains itself with "so the run would silently ignore it" — that rationale is about the design, not about the flag, and it belongs on `C_FLAGS`. Verified against the C runtime with `diffSimulationResults` over dassl, euler and gbode with the moved scalars: no differing variables. The startup log matches C line for line, in C's order. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent dbd56e4 commit 994ba6f

17 files changed

Lines changed: 741 additions & 130 deletions

File tree

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit/src/CodegenWasmJit.rs

Lines changed: 101 additions & 57 deletions
Large diffs are not rendered by default.

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit_runtime/src/nls.rs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,17 @@ const SQRT_EPS: f64 = 1.4901161193847656e-08;
183183
const FD_DELTA: f64 = 6.664001874625056e-08;
184184
/// Newton/LM convergence tolerance: stop once a residual / step measure drops below.
185185
const NEWTON_EPS: f64 = 1.0e-6;
186-
/// C's `newtonFTol`/`newtonXTol` (nonlinearSolverHomotopy.c). `newton_solve`
187-
/// mirrors C's residual-gated convergence: a step-stall counts as success only
188-
/// when the residual is also small (`< NEWTON_FTOL*1e3`), else it fails so the
189-
/// homotopy globaliser engages instead of accepting a non-root.
190-
const NEWTON_FTOL: f64 = 1.0e-12;
191-
const NEWTON_XTOL: f64 = 1.0e-12;
186+
/// C's `newtonFTol`/`newtonXTol` (nonlinearSolverHomotopy.c), which `-newtonFTol` /
187+
/// `-newtonXTol` move. `newton_solve` mirrors C's residual-gated convergence: a
188+
/// step-stall counts as success only when the residual is also small
189+
/// (`< ftol*1e3`), else it fails so the homotopy globaliser engages instead of
190+
/// accepting a non-root.
191+
fn newton_ftol() -> f64 {
192+
crate::solvers::newton_ftol()
193+
}
194+
fn newton_xtol() -> f64 {
195+
crate::solvers::newton_xtol()
196+
}
192197
const MAX_ITER: i32 = 100;
193198
/// Line-search damping floor (2^-10): below this, keep the small step and let the
194199
/// outer iteration retry (or hit the iteration limit → recoverable failure).
@@ -558,7 +563,7 @@ fn homotopy_solve(
558563

559564
// ---- Corrector: Newton with coordinate `pos` fixed ----
560565
let last_step = y1[n] >= 1.0;
561-
let h_eps = if last_step { NEWTON_FTOL } else { H_EPS };
566+
let h_eps = if last_step { newton_ftol() } else { H_EPS };
562567
let mut pos = if last_step { n as i32 } else { tangent_pos };
563568
let mut step_accept = false;
564569
let mut corrector_ok = true;
@@ -672,7 +677,7 @@ pub(crate) fn newton_solve(
672677

673678
eval(x, &mut fvec);
674679
let mut error_f = enorm(&fvec);
675-
if error_f < NEWTON_FTOL {
680+
if error_f < newton_ftol() {
676681
return true;
677682
}
678683
f_old.copy_from_slice(&fvec);
@@ -758,15 +763,16 @@ pub(crate) fn newton_solve(
758763
if neg_steps > 20 {
759764
return false;
760765
}
761-
let f_small = error_f < NEWTON_FTOL || scaled_error_f < NEWTON_FTOL;
762-
let x_small = delta_x < NEWTON_XTOL || delta_x_scaled < NEWTON_XTOL;
766+
let (ftol, xtol) = (newton_ftol(), newton_xtol());
767+
let f_small = error_f < ftol || scaled_error_f < ftol;
768+
let x_small = delta_x < xtol || delta_x_scaled < xtol;
763769
if f_small && x_small {
764770
return true;
765771
}
766-
small_steps += (delta_x < NEWTON_XTOL * 100.0 || delta_x_scaled < NEWTON_XTOL * 100.0) as i32;
772+
small_steps += (delta_x < xtol * 100.0 || delta_x_scaled < xtol * 100.0) as i32;
767773
if x_small || small_steps > 20 {
768774
// Stalled step: accept only with a small residual (C's ftol*1e3), else fail.
769-
return error_f < NEWTON_FTOL * 1.0e3 || scaled_error_f < NEWTON_FTOL * 1.0e3;
775+
return error_f < ftol * 1.0e3 || scaled_error_f < ftol * 1.0e3;
770776
}
771777
iter += 1;
772778
if iter > MAX_ITER {
@@ -1566,8 +1572,8 @@ fn newton_c(
15661572
) -> (bool, bool) {
15671573
const ALPHA: f64 = 1.0e-1;
15681574
const LAMBDA_MIN_C: f64 = 1.0e-4;
1569-
let ftol_sq = NEWTON_FTOL * NEWTON_FTOL;
1570-
let xtol_sq = NEWTON_XTOL * NEWTON_XTOL;
1575+
let ftol_sq = newton_ftol() * newton_ftol();
1576+
let xtol_sq = newton_xtol() * newton_xtol();
15711577
let nsq = |v: &[f64]| -> f64 {
15721578
let e = enorm(v);
15731579
e * e
@@ -2147,11 +2153,9 @@ fn solve_newton_c(
21472153
}
21482154
}
21492155

2150-
/// KINSOL function-norm / scaled-step stopping tolerances (C's `newtonFTol` /
2151-
/// `newtonXTol`, `model_help.c`) and the norm below which C accepts a less
2152-
/// accurate solution rather than failing (`FTOL_WITH_LESS_ACCURACY`).
2153-
const KIN_FNORMTOL: f64 = 1.0e-12;
2154-
const KIN_SCSTEPTOL: f64 = 1.0e-12;
2156+
/// The norm below which C accepts a less accurate solution rather than failing
2157+
/// (`FTOL_WITH_LESS_ACCURACY`). KINSOL's own stopping tolerances are C's
2158+
/// `newtonFTol`/`newtonXTol`, i.e. [`newton_ftol`]/[`newton_xtol`].
21552159
const KIN_FTOL_LESS_ACCURACY: f64 = 1.0e-6;
21562160

21572161
/// `‖diag(scale)·v‖∞`, the norm KINSOL's stopping tests use.
@@ -2289,7 +2293,7 @@ fn newton_sparse_solve(
22892293
if !fnorm.is_finite() {
22902294
break;
22912295
}
2292-
if fnorm <= KIN_FNORMTOL {
2296+
if fnorm <= newton_ftol() {
22932297
solved = true;
22942298
break;
22952299
}
@@ -2332,7 +2336,7 @@ fn newton_sparse_solve(
23322336
}
23332337
x.copy_from_slice(&xnew);
23342338
fnorm = fnew;
2335-
if step <= KIN_SCSTEPTOL {
2339+
if step <= newton_xtol() {
23362340
solved = fnorm < KIN_FTOL_LESS_ACCURACY;
23372341
break;
23382342
}

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit_runtime/src/session.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,15 @@ pub extern "C" fn rt_sim_start(meta_ptr: u32, meta_len: u32, fn_base: u32, prese
252252
crate::sundials::reset_caches();
253253

254254
let bytes = unsafe { core::slice::from_raw_parts(meta_ptr as *const u8, meta_len as usize) };
255-
let model = match openmodelica_sim_meta::decode(bytes) {
255+
let mut model = match openmodelica_sim_meta::decode(bytes) {
256256
Ok(m) => m,
257257
Err(_) => return -1,
258258
};
259+
// A session always has a host, which renders `read_experiment`'s notices from
260+
// the same flags; saying it again here would double every line.
261+
driver::set_log_sink(|_| {});
262+
simflags::with_flags(|f| model.apply_flags(f));
263+
driver::set_log_sink(crate::omclog::sink);
259264

260265
crate::nls::rt_set_step_size(model.step_size());
261266
// `-lv=LOG_NLS` names the iteration variables; only the metadata has them.
@@ -269,7 +274,6 @@ pub extern "C" fn rt_sim_start(meta_ptr: u32, meta_len: u32, fn_base: u32, prese
269274
driver::set_cancel_hook(cancel_hook);
270275
driver::set_init_done_hook(init_done_hook);
271276
driver::set_no_throw_hook(|v| unsafe { rt_host_set_no_throw(v as i32) });
272-
driver::set_log_sink(crate::omclog::sink);
273277

274278
let mut engine = InWasmEngine { fn_base, present_mask };
275279
let sim_data = crate::rt_alloc(model.layout.total);

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit_runtime/src/solvers.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,33 @@ static NLSS_MIN_SIZE: AtomicU32 = AtomicU32::new(1000);
6565
static NLSS_MAX_DENSITY: core::sync::atomic::AtomicU64 =
6666
core::sync::atomic::AtomicU64::new(0x3FB999999999999A); // 0.1
6767

68+
/// C's `newtonFTol` / `newtonXTol` / `maxStepFactor` (`model_help.c`), which
69+
/// `-newtonFTol` / `-newtonXTol` / `-newtonMaxStepFactor` move. The homotopy Newton
70+
/// and KINSOL both read them, so they live here rather than in either solver.
71+
static NEWTON_FTOL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0x3D719799812DEA11); // 1e-12
72+
static NEWTON_XTOL: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0x3D719799812DEA11);
73+
static MAX_STEP_FACTOR: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0x426D1A94A2000000); // 1e12
74+
75+
#[unsafe(no_mangle)]
76+
pub extern "C" fn rt_set_newton_tuning(ftol: f64, xtol: f64, max_step_factor: f64) {
77+
NEWTON_FTOL.store(ftol.to_bits(), Ordering::Relaxed);
78+
NEWTON_XTOL.store(xtol.to_bits(), Ordering::Relaxed);
79+
MAX_STEP_FACTOR.store(max_step_factor.to_bits(), Ordering::Relaxed);
80+
}
81+
82+
pub(crate) fn newton_ftol() -> f64 {
83+
f64::from_bits(NEWTON_FTOL.load(Ordering::Relaxed))
84+
}
85+
86+
pub(crate) fn newton_xtol() -> f64 {
87+
f64::from_bits(NEWTON_XTOL.load(Ordering::Relaxed))
88+
}
89+
90+
#[cfg(sundials)]
91+
pub(crate) fn max_step_factor() -> f64 {
92+
f64::from_bits(MAX_STEP_FACTOR.load(Ordering::Relaxed))
93+
}
94+
6895
/// Set the four selectors for the next run. Host-driven builds call this through
6996
/// the export; the in-wasm session calls [`apply_flags`] instead.
7097
#[unsafe(no_mangle)]
@@ -95,6 +122,8 @@ pub(crate) fn apply_flags(f: &openmodelica_sim_meta::simflags::SimFlags) {
95122
rt_set_solvers(nls, nls_ls, ls, lss);
96123
let (min_size, max_density) = openmodelica_sim_meta::simflags::nlss_thresholds(f);
97124
rt_set_nlss_thresholds(min_size, max_density);
125+
let (ftol, xtol, msf) = openmodelica_sim_meta::simflags::newton_tuning(f);
126+
rt_set_newton_tuning(ftol, xtol, msf);
98127
}
99128

100129
pub(crate) fn nls() -> Nls {

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit_runtime/src/standalone.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ impl SimEngine for StandaloneEngine {
140140
/// Run the prepared model with the shared driver and write its result file.
141141
/// A failure traps (the command then exits nonzero).
142142
fn run() {
143-
let m = read_meta();
143+
let mut m = read_meta();
144+
driver::set_log_sink(crate::omclog::sink);
145+
simflags::with_flags(|f| {
146+
simflags::print_notices(f);
147+
m.apply_flags(f);
148+
});
144149
let sim_data = crate::rt_alloc(m.layout.total);
145150
let mut engine = StandaloneEngine;
146151
crate::nls::rt_set_step_size(m.step_size());
@@ -195,7 +200,17 @@ fn run() {
195200
result.n_reals,
196201
&params,
197202
);
198-
std::fs::write(format!("{}_res.mat", m.prefix), bytes).expect("wasm-jit standalone: cannot write result file");
203+
std::fs::write(result_file(&m.prefix), bytes).expect("wasm-jit standalone: cannot write result file");
204+
}
205+
206+
/// C's result-file resolution (`simulation_runtime.cpp`): `-r` outright, else
207+
/// `<prefix>_res.mat` under `-outputPath`.
208+
fn result_file(prefix: &str) -> String {
209+
simflags::with_flags(|f| match (&f.result_file, &f.output_path) {
210+
(Some(r), _) => r.clone(),
211+
(None, Some(dir)) => format!("{dir}/{prefix}_res.mat"),
212+
(None, None) => format!("{prefix}_res.mat"),
213+
})
199214
}
200215

201216
/// Wall clock for the driver, in ms since the first reading.

OMCompiler/Compiler/OpenModelica.rs/openmodelica_codegen_wasm_jit_runtime/src/sundials.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,9 @@ pub(crate) mod kinsol {
311311
const KIN_LSOLVE_FAIL: c_int = -12;
312312
const KIN_REPTD_SYSFUNC_ERR: c_int = -15;
313313

314-
/// C's `newtonFTol`/`newtonXTol`, `maxStepFactor` and `FTOL_WITH_LESS_ACCURACY`
315-
/// defaults, and `RETRY_MAX`.
316-
const FNORMTOL: f64 = 1.0e-12;
317-
const SCSTEPTOL: f64 = 1.0e-12;
318-
const MAXSTEPFACTOR: f64 = 1.0e12;
314+
/// C's `FTOL_WITH_LESS_ACCURACY` and `RETRY_MAX`; the stopping tolerances and
315+
/// the step factor are C's `newtonFTol`/`newtonXTol`/`maxStepFactor`, which
316+
/// `crate::solvers` holds because `-newtonFTol` and friends move them.
319317
const FTOL_LESS_ACCURACY: f64 = 1.0e-6;
320318
const RETRY_MAX: i32 = 5;
321319

@@ -425,7 +423,7 @@ pub(crate) mod kinsol {
425423
n,
426424
nnz,
427425
strategy: KIN_LINESEARCH,
428-
maxstepfactor: MAXSTEPFACTOR,
426+
maxstepfactor: crate::solvers::max_step_factor(),
429427
numeric_jac: false,
430428
};
431429
if s.kin.is_null()
@@ -446,8 +444,8 @@ pub(crate) mod kinsol {
446444
{
447445
return None;
448446
}
449-
KINSetFuncNormTol(s.kin, FNORMTOL);
450-
KINSetScaledStepTol(s.kin, SCSTEPTOL);
447+
KINSetFuncNormTol(s.kin, crate::solvers::newton_ftol());
448+
KINSetScaledStepTol(s.kin, crate::solvers::newton_xtol());
451449
KINSetNumMaxIters(s.kin, 100 * n as c_long);
452450
KINSetNoInitSetup(s.kin, 0);
453451
}
@@ -583,8 +581,8 @@ pub(crate) mod kinsol {
583581
}
584582
if reset_tol {
585583
unsafe {
586-
KINSetFuncNormTol(self.kin, FNORMTOL);
587-
KINSetScaledStepTol(self.kin, SCSTEPTOL);
584+
KINSetFuncNormTol(self.kin, crate::solvers::newton_ftol());
585+
KINSetScaledStepTol(self.kin, crate::solvers::newton_xtol());
588586
}
589587
}
590588
if success {

0 commit comments

Comments
 (0)