Skip to content

engine: DELAY N / SMOOTH N require a compile-time-constant order -- non-constant/parameterized order -> UnknownBuiltin (blocks full simulation of thyroid-2008-d and any dynamic-order delay/smooth) #562

Description

@bpowers

Problem

The Simlin engine's DELAY N / SMOOTH N support requires a compile-time-constant order argument. src/simlin-engine/src/builtins_visitor.rs::rewrite_alias_module_call (lines 84-127) selects the concrete stdlib model to instantiate by reading the value of the order argument via parse_module_order_arg (lines 74-82), which only accepts an Expr0::Const:

fn parse_module_order_arg(expr: &Expr0) -> Option<u32> {
    if let Expr0::Const(_, n, _) = expr {
        let rounded = n.round();
        if (*n - rounded).abs() < 1e-9 && rounded >= 0.0 {
            return Some(rounded as u32);
        }
    }
    None
}

rewrite_alias_module_call then maps the constant order to a fixed stdlib model:

let Some(order) = parse_module_order_arg(&order_expr) else {
    return eqn_err!(UnknownBuiltin, loc.start, loc.end);
};
let rewritten_name = match (func.as_str(), order) {
    ("delayn", 1) => "delay1",
    ("delayn", 3) => "delay3",
    ("smthn", 1) => "smth1",
    ("smthn", 3) => "smth3",
    _ => return eqn_err!(UnknownBuiltin, loc.start, loc.end),
};

If the order is any non-constant expression -- a variable, a macro port/parameter, MAX(1, Delay Order), etc. -- parse_module_order_arg returns None, rewrite_alias_module_call cannot resolve the call to a stdlib model, and the call surfaces as UnknownBuiltin. (Even a constant order other than 1 or 3 hits the _ => UnknownBuiltin arm, but the primary gap reported here is the non-constant case.)

Scope: this is a pre-existing, orthogonal engine limitation -- NOT a macro bug

This is not a macro defect and was not introduced by the Vensim macro support epic (docs/implementation-plans/2026-05-13-macros/). The identical failure occurs for a plain main-model equation with no macros involved:

x = DELAY N(in, dt, init, order)   // `order` a non-constant variable -> UnknownBuiltin

It only surfaced during the Phase 7 #554-follow-up fix (the delayn macro-collision fix, commit 029cf97f): test/metasd/thyroid-dynamics/thyroid-2008-d.mdl defines :MACRO: DELAYN(Input,DelayTime,Init,Order) ... DELAYN = DELAY N(Input,DelayTime,Init,Order) and invokes it as DELAYN(TSH, tau, TSH, MAX(1, Delay Order)), passing a non-constant order. The macro template body therefore cannot resolve DELAY N to a stdlib model and emits an in-body UnknownBuiltin.

The macro epic's Phase 7 metasd expansion tier correctly tolerates this: the resulting UnknownBuiltin is confined to the macro body and is non-macro-attributable, so thyroid-2008-d PASSES the expansion tier (macros.AC6.4 is satisfied). This limitation gates no macro acceptance criterion. It only blocks full simulation of thyroid (and of any model using DELAY N / SMOOTH N with a dynamic/parameterized order), and no metasd macro model is currently simulation-tier-eligible anyway (that broader gap is tracked separately under #561). This issue records the deferred engine-capability gap so it is not silently lost.

Why it matters

  • Builtin coverage / correctness: DELAY N / SMOOTH N with a variable or parameterized order is valid Vensim and appears in real-world SD models (and naturally in macros that take the order as a parameter, e.g. thyroid's DELAYN). Such models cannot simulate at all in Simlin today; the call dead-ends at UnknownBuiltin.
  • Test corpus reach: blocks full simulation of test/metasd/thyroid-dynamics/thyroid-2008-d.mdl (and any similar model), reducing end-to-end numeric validation reach.
  • Confusing signal: the importer accepts DELAY N / SMOOTH N, but a non-constant order yields a generic UnknownBuiltin on a function the engine otherwise recognizes, rather than a clear "dynamic-order delay unsupported" diagnostic.

Components affected

  • src/simlin-engine/src/builtins_visitor.rs lines 74-82 (parse_module_order_arg) and lines 84-127 (rewrite_alias_module_call) -- the compile-time constant-order-to-stdlib-model selection
  • src/simlin-engine/src/compiler/, src/simlin-engine/src/bytecode.rs, src/simlin-engine/src/vm.rs -- if remediated via a runtime ring-buffer / order-parameterized N-th order delay/smooth instead of compile-time delay1/delay3 (smth1/smth3) selection
  • stdlib (src/simlin-engine/stdlib/*.stmx) -- if remediated via an order-parameterized stdlib model

Recommended remediation (not to perform now)

Support a dynamic / order-parameterized N-th order delay and smooth rather than compile-time selection between delay1/delay3 (smth1/smth3) from a constant order argument. Two candidate approaches:

  1. A runtime ring-buffer / N-cascade: VM-level state for an order-N cascade of exponential stages where N is read at runtime (related in spirit to the ring-buffer approach proposed for engine: DELAY FIXED ring-buffer semantics blocks 4 simulation tests #346, though engine: DELAY FIXED ring-buffer semantics blocks 4 simulation tests #346 is a fixed pipeline delay -- a distinct semantic, do not conflate).
  2. An order-parameterized stdlib model that accepts the order as an input port and internally generalizes the delay1/delay3 structure.

In either case, also emit a clearer diagnostic than bare UnknownBuiltin when a dynamic-order delay/smooth cannot be lowered.

Where this is documented in-tree (so it is not silently lost)

  • src/simlin-engine/tests/metasd_macros.rs -- thyroid's SimTier skip reason annotates this blocker
  • The issue_554_followup_thyroid_shape_* test pins thyroid's shape/expansion behavior with this limitation present

Context

Identified during Phase 7 Task 2 of the Vensim macro support epic (docs/implementation-plans/2026-05-13-macros/), specifically while landing the #554-follow-up delayn macro-collision fix (commit 029cf97f). Out of scope for the macro epic; tracked here as a deferred general-engine capability gap.

Related: #561 (metasd macro simulation-tier enablement umbrella -- this is one of the per-model non-macro blockers it defers to individual tracking; cross-reference, do not duplicate), #554 (the delayn/INIT recursion false-positive follow-up that surfaced this), #560 (a sibling unrelated builtin-coverage gap: stochastic builtins), #346 (DELAY FIXED ring-buffer -- a different builtin and a different defect: fixed-pipeline-delay approximation, not dynamic order; distinct, cross-referenced only to disambiguate).

Activity

  1. added
    engineIssues with the rust-based simulation engine
    compatCompatibility (XMILE, Vensim, etc) problem or enhancement
    on Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    compatCompatibility (XMILE, Vensim, etc) problem or enhancementengineIssues with the rust-based simulation engine

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions