Skip to content

engine: INITIAL()-backed module/macro primary output omitted from the initials runlist (spurious inf) #584

@bpowers

Description

@bpowers

Summary

A module/macro variable whose primary output is INITIAL(...) is omitted from that module's compiled INITIALS runlist, so during a parent model's initials phase the parent reads the module-output slot that was never written -> uninitialized garbage (inf), which is then frozen into the initial_values snapshot and served forever by LoadInitial. This is a runtime-numeric bug (the model compiles and runs to FINAL TIME), distinct from the compile-path C-LEARN issues.

Concrete C-LEARN manifestation

C-LEARN (test/xmutil_test_models/C-LEARN v77 for Vensim.mdl) defines a user macro:

:MACRO: INIT(x)
INIT = INITIAL(x)
:END OF MACRO:

(The MDL importer renames the Vensim INITIAL builtin -> init; this macro then shadows the built-in init by macro precedence -- see #554, which is the separate compile-path bug that has since been fixed.) Roughly 177 call sites -- e.g. volumetric_heat_capacity = INITIAL(mass heat cap*...) at C-LEARN line ~12698 -- compile to invoke this one shared macro module.

The macro module's compiled INITIALS runlist OMITS its own INITIAL() primary output (init = INITIAL(x), data offset 0). The output variable is compiled only into the flows phase. During the PARENT's initials phase the parent reads the module-output slot, which is never written during initials, so it reads uninitialized garbage (inf). Vm::run_initials (src/simlin-engine/src/vm.rs:1154-1155, self.initial_values.copy_from_slice(&data[...])) snapshots that garbage into initial_values, and LoadInitial (vm.rs:~1360) serves it for the rest of the run.

Result: volumetric_heat_capacity = inf (Simlin) vs 0.1327 (Ref.vdf), and ~177 climate variables then propagate NaN/inf. This is one of the two root causes of C-LEARN's core series being entirely NaN at AC7.3 (the model now compiles and runs to FINAL TIME, so these are runtime-numeric, not compile/assembly failures).

Root cause

The initials-runlist inclusion predicate in src/simlin-engine/src/db_dep_graph.rs (runlist_initials, needed-set criteria ~lines 2182-2193) seeds the needed set with only:

i.is_stock || i.is_module   // (per var_info)
|| all_init_referenced.contains(n.as_str())

A module/macro primary-output variable whose equation compiles to LoadInitial (i.e. it is INITIAL(...)) and which is read during a parent's initials phase does not satisfy any of these, so it is excluded from the initials runlist. The two compile gates that consume runlist_initials -- src/simlin-engine/src/db.rs:~3828 and :~4084 (if dep_graph.runlist_initials.contains(&var_ident_str) { ... } else { None }) -- then never emit initial bytecode for it.

Why it matters

  • Correctness: spurious inf/NaN in ~177 C-LEARN climate variables; an INITIAL()-backed module/macro output is silently uninitialized at t=0 whenever a parent reads it during initials.
  • It is a general defect in the initials dependency analysis for module/macro outputs, not specific to C-LEARN.

Components affected

  • src/simlin-engine/src/db_dep_graph.rs -- runlist_initials needed-set criteria (~2182-2193)
  • src/simlin-engine/src/db.rs -- the two initials compile gates (~3828, ~4084)
  • src/simlin-engine/src/vm.rs -- run_initials snapshot (copy_from_slice, ~1154-1155) and LoadInitial (~1360); these are where the uninitialized slot is frozen and served (not where the fix goes, but where the symptom is realized)

Possible approach for resolution

Extend the initials-runlist inclusion predicate so that a module/macro primary-output variable whose equation compiles to LoadInitial (is INITIAL(...)) and is read during a parent's initials phase is included in the initials runlist (and consequently has initial bytecode emitted at the two db.rs gates). This must be a general fix keyed on the structural property (module/macro primary output backed by INITIAL, read during a parent's initials phase), not a model-specific hack.

Context / lineage

Freshly root-caused during Phase 6 of the element-level cycle resolution work (branch clearn-hero-model, HEAD b0910842). It is being fixed now as Phase 6 Task 8; filing for traceability.

This is the Cluster A spurious-inf bug. It is distinct from:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingengineIssues with the rust-based simulation engine

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions