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:
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 theinitial_valuessnapshot and served forever byLoadInitial. 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:(The MDL importer renames the Vensim
INITIALbuiltin ->init; this macro then shadows the built-ininitby 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 intoinitial_values, andLoadInitial(vm.rs:~1360) serves it for the rest of the run.Result:
volumetric_heat_capacity = inf(Simlin) vs0.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:A module/macro primary-output variable whose equation compiles to
LoadInitial(i.e. it isINITIAL(...)) 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 consumerunlist_initials--src/simlin-engine/src/db.rs:~3828and:~4084(if dep_graph.runlist_initials.contains(&var_ident_str) { ... } else { None }) -- then never emit initial bytecode for it.Why it matters
inf/NaN in ~177 C-LEARN climate variables; anINITIAL()-backed module/macro output is silently uninitialized at t=0 whenever a parent reads it during initials.Components affected
src/simlin-engine/src/db_dep_graph.rs--runlist_initialsneeded-set criteria (~2182-2193)src/simlin-engine/src/db.rs-- the two initials compile gates (~3828, ~4084)src/simlin-engine/src/vm.rs--run_initialssnapshot (copy_from_slice, ~1154-1155) andLoadInitial(~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(isINITIAL(...)) and is read during a parent's initials phase is included in the initials runlist (and consequently has initial bytecode emitted at the twodb.rsgates). This must be a general fix keyed on the structural property (module/macro primary output backed byINITIAL, 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, HEADb0910842). It is being fixed now as Phase 6 Task 8; filing for traceability.This is the Cluster A spurious-
infbug. It is distinct from:INIT = INITIAL(x)false self-recursion inMacroRegistry::build): a compile-time falseCircularDependency, now fixed. Same macro, different subsystem (macro registry) and symptom (compile error vs runtimeinf).NotSimulatable/CircularDependency/MismatchedDimensions/UnknownDependency) "before the VM is even constructed". This bug is post-compile -- the model now runs to FINAL TIME.GraphicalFunctionId/TempIdu8 overflow). This is runtime-numeric.