You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
engine: MDL writer exports 1-arg INITIAL as invalid ACTIVE INITIAL -- name-only inverse of many-to-one function renames (roundtrip harness is blind to the class) #852
The MDL writer exports XMILE init(x) as ACTIVE INITIAL(x) unconditionally, producing invalid Vensim syntax: ACTIVE INITIAL requires 2 arguments, and a 1-arg init should export as INITIAL.
Repro: an MDL model containing x = INITIAL(y) (e.g. test/sdeverywhere/models/initial/initial.mdl, which is in the corpus) round-trips through import + export to x = ACTIVE INITIAL(y) -- wrong function name AND wrong arity for Vensim.
Root cause: many-to-one import mapping with a name-only inverse
Import direction (src/simlin-engine/src/mdl/xmile_compat.rs, format_function_name, ~line 558) maps three Vensim functions to one XMILE builtin:
Export direction (src/simlin-engine/src/mdl/writer.rs:203, xmile_to_mdl_function_name) inverts by name only -- the function does not receive the argument list, so it cannot dispatch on arity:
"init" => "ACTIVE INITIAL".to_owned(),
Why the roundtrip test is blind to this
mdl_to_mdl_roundtrip re-parses the exported MDL and compares datamodels. Since INITIAL, ACTIVE INITIAL, and REINITIAL all collapse back to init on re-parse, the corrupted export compares equal to the original. The harness structurally cannot catch any many-to-one rename collapse -- it validates simlin-internal fixpoint, not Vensim-syntax validity.
Same bug class elsewhere in the inverse table
Every many-to-one entry in format_function_name needs an arity/shape-aware inverse. Known instances:
init (this issue): 1-arg -> INITIAL, 2-arg -> ACTIVE INITIAL.
normal -> RANDOM NORMAL: XMILE normal(mean, sd) (2-arg, optional seed as 3rd) exports as RANDOM NORMAL(mean, sd), but Vensim's RANDOM NORMAL requires 5 args (min, max, mean, sd, seed). reorder_args in writer.rs only reorders when args.len() >= 5, so the 2-arg form passes through as invalid Vensim. Needs synthesized min/max (or NORMAL where appropriate).
zidz/xidz -> safediv(correction: already handled) -- the writer special-cases arity for this pair (writer.rs:663: safediv with 3+ args emits XIDZ, otherwise ZIDZ), and a CLI round-trip of XIDZ(1, 2, 3) confirms it survives unchanged. Listed here as the model for the fix: init and normal need the same call-site arity dispatch.
Audit the remaining collapsed pairs for the same shape: smooth/smoothi -> SMTH1, smooth3/smooth3i -> SMTH3, delay1/delay1i -> DELAY1, delay3/delay3i -> DELAY3 (the i variants differ by arity -- verify the writer's output arity is valid for the non-i Vensim name it picks), lookup extrapolate -> LOOKUP (extrapolation semantics silently dropped), vmax/vmin -> MAX/MIN, reinitial -> init (re-exported as ACTIVE INITIAL).
Why it matters
Correctness: exported .mdl files fail to parse or simulate in Vensim (high severity -- MDL export is a headline interop feature).
Test gap: the only roundtrip harness cannot detect this entire bug class, so regressions land silently.
Suggested approach
Plumb argument count into xmile_to_mdl_function_name (or dispatch at the App call site in the writer) and pick the Vensim name by arity: init 1-arg -> INITIAL, 2-arg -> ACTIVE INITIAL; safediv 2-arg -> ZIDZ, 3-arg -> XIDZ; handle normal's arity forms explicitly.
Add a Vensim-syntax-validity check to the roundtrip harness (per-function arity table for emitted MDL builtins) and/or golden-file tests for exported MDL text, so name collapses cannot silently round-trip.
Identified during review of the MDL writer's function-name inverse table (writer.rs xmile_to_mdl_function_name vs xmile_compat.rs format_function_name).
Activity