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
A PREVIOUS(...) call whose argument sits inside a Subscript index (e.g. PREVIOUS(self[COP, tNext])) is not reduced to a bare Expr::Var by the PREVIOUS helper-rewrite pass. When such an expression appears in an LTM synthetic link-score / loop-score fragment (generated only when LTM discovery is enabled), codegen rejects it and the fragment is silently dropped, so that LTM link/loop score reads a constant 0 -- degrading LTM loop-discovery accuracy for that edge.
This is not a simulation-correctness bug: core simulation is unaffected. It is an LTM-accuracy gap on the loop-analysis path. It is also not a blocker for the element-cycle-resolution work -- C-LEARN compiles, runs, and the LTM-discovery-compiles checks all pass; this surfaces (loudly, via a Warning) once that work removed the masking failures.
Failure chain
The PREVIOUS helper-rewrite (src/simlin-engine/src/builtins_visitor.rs, the scalar-helper synthesis for non-bare PREVIOUS args -- walk() / make_temp_arg) does not reduce a PREVIOUS argument that is an Expr::Subscript index to a bare per-element scalar Expr::Var. The argument survives helper rewriting still shaped as a Subscript.
When this expression is part of an LTM synthetic fragment, codegen reaches the Expr::Subscript(off, indices, ...) -> SubscriptIndex::Single(expr) arm at src/simlin-engine/src/compiler/codegen.rs (~:489-:521, the self.walk_expr(expr)? at ~:493-:502). The inner walk returns Err(NotSimulatable, "PREVIOUS requires a variable reference after helper rewriting"), which -- as of commit 59f01126 -- is propagated via ? rather than panicking. (That ? propagation is exactly the Incremental compiler panics on C-LEARN model (catch_unwind workaround) #363 / AC7.5 fix: the in-tree comment at codegen.rs:494-501 documents "a PREVIOUS whose arg survived helper rewriting as a non-variable expression (NotSimulatable) ... must flow back to the caller (db_ltm.rs gracefully drops the un-compilable LTM synthetic fragment), never escalate to a panic (Incremental compiler panics on C-LEARN model (catch_unwind workaround) #363).")
That Err flows back to compile_phase in src/simlin-engine/src/db_ltm.rs, where the Err(_) => None arm at db_ltm.rs:2083 (and the sibling Err(_) => None build_var fallbacks at ~:2093/:2099) gracefully drops the un-compilable LTM synthetic fragment.
With no bytecode emitted for that synthetic variable, it keeps its layout slot and reads a constant 0 for the entire simulation. The loop score for any loop through that edge is the product of its link scores, so a zero link score collapses that loop's score.
The drop is surfaced, not silent: the existing model_ltm_fragment_diagnostics pass emits a Warning for every LTM synthetic variable whose fragment fails to compile (subject to the #466 caveat that the diagnostic-collection FFI paths leave ltm_enabled false).
Bounded blast radius. Core simulation is unaffected (this is the LTM-augmentation path only), and the failure is no longer a panic (Incremental compiler panics on C-LEARN model (catch_unwind workaround) #363/AC7.5) -- it is a clean drop with a Warning. So this is a correctness/accuracy gap in LTM scoring, not a crash or a sim-result regression.
Repro context
Surfaced and made loud during element-cycle-resolution Phase 6 Task 11 (#363 / AC7.5), commit 59f01126, on test/xmutil_test_models/C-LEARN v77 for Vensim.mdl with LTM discovery enabled.
Components affected
src/simlin-engine/src/builtins_visitor.rs -- the PREVIOUS helper-rewrite (walk() / make_temp_arg): a subscripted/expression PREVIOUS argument is not reduced to a bare per-element scalar Expr::Var.
src/simlin-engine/src/compiler/codegen.rs -- the Expr::Subscript -> SubscriptIndex::Single arm (~:489-:521) where the inner walk_expr(expr)? returns the NotSimulatable "PREVIOUS requires a variable reference after helper rewriting" error.
src/simlin-engine/src/db_ltm.rs -- db_ltm.rs:2083 (Err(_) => None, plus the sibling build_var Err(_) => None arms), the graceful-drop site that makes the LTM synthetic fragment read constant 0.
Possible fix direction
Extend the PREVIOUS helper-rewrite in builtins_visitor.rs to reduce a subscripted (or otherwise non-bare-expression) PREVIOUS argument to a bare Expr::Var -- i.e. synthesize the per-element scalar helper aux for the subscripted slice -- so the LTM synthetic fragment compiles instead of being dropped. This is the same structural direction proposed for the related PREVIOUS-arg cases (generalize the helper synthesis so PREVIOUS(expr) desugars for any expr, not just a bare Var). The fix should be paired with a regression test asserting the LTM link/loop score for an edge through a subscripted-PREVIOUS reference is non-zero (so the dropped-fragment-reads-0 behavior cannot silently return).
Summary
A
PREVIOUS(...)call whose argument sits inside aSubscriptindex (e.g.PREVIOUS(self[COP, tNext])) is not reduced to a bareExpr::Varby the PREVIOUS helper-rewrite pass. When such an expression appears in an LTM synthetic link-score / loop-score fragment (generated only when LTM discovery is enabled), codegen rejects it and the fragment is silently dropped, so that LTM link/loop score reads a constant0-- degrading LTM loop-discovery accuracy for that edge.This is not a simulation-correctness bug: core simulation is unaffected. It is an LTM-accuracy gap on the loop-analysis path. It is also not a blocker for the element-cycle-resolution work -- C-LEARN compiles, runs, and the LTM-discovery-compiles checks all pass; this surfaces (loudly, via a
Warning) once that work removed the masking failures.Failure chain
src/simlin-engine/src/builtins_visitor.rs, the scalar-helper synthesis for non-barePREVIOUSargs --walk()/make_temp_arg) does not reduce aPREVIOUSargument that is anExpr::Subscriptindex to a bare per-element scalarExpr::Var. The argument survives helper rewriting still shaped as aSubscript.Expr::Subscript(off, indices, ...)->SubscriptIndex::Single(expr)arm atsrc/simlin-engine/src/compiler/codegen.rs(~:489-:521, theself.walk_expr(expr)?at ~:493-:502). The inner walk returnsErr(NotSimulatable, "PREVIOUS requires a variable reference after helper rewriting"), which -- as of commit59f01126-- is propagated via?rather than panicking. (That?propagation is exactly the Incremental compiler panics on C-LEARN model (catch_unwind workaround) #363 / AC7.5 fix: the in-tree comment at codegen.rs:494-501 documents "a PREVIOUS whose arg survived helper rewriting as a non-variable expression (NotSimulatable) ... must flow back to the caller (db_ltm.rs gracefully drops the un-compilable LTM synthetic fragment), never escalate to a panic (Incremental compiler panics on C-LEARN model (catch_unwind workaround) #363).")Errflows back tocompile_phaseinsrc/simlin-engine/src/db_ltm.rs, where theErr(_) => Nonearm atdb_ltm.rs:2083(and the siblingErr(_) => Nonebuild_var fallbacks at ~:2093/:2099) gracefully drops the un-compilable LTM synthetic fragment.The drop is surfaced, not silent: the existing
model_ltm_fragment_diagnosticspass emits aWarningfor every LTM synthetic variable whose fragment fails to compile (subject to the #466 caveat that the diagnostic-collection FFI paths leaveltm_enabledfalse).Why it matters
PREVIOUS(self[COP, tNext], init)(the C-LEARNSAMPLE IF TRUE-style construct) is legal and appears in real models. When an edge using it is in a detected loop and gets scored under LTM, its link/loop score silently reads0, so the loop looks inactive when it is not -- the same hazard class as ltm: synthetic-fragment compilation uses an empty tables map, so link scores into graphical-function targets stub to 0 #546 (GF-table targets stub to 0) and ltm: composite-reference link score into a stdlib-macro module fails to compile in exhaustive mode, so loops through SMOOTH/DELAY stub to 0 #548 (composite-reference into a stdlib macro stubs to 0).Warning. So this is a correctness/accuracy gap in LTM scoring, not a crash or a sim-result regression.Repro context
Surfaced and made loud during element-cycle-resolution Phase 6 Task 11 (#363 / AC7.5), commit
59f01126, ontest/xmutil_test_models/C-LEARN v77 for Vensim.mdlwith LTM discovery enabled.Components affected
src/simlin-engine/src/builtins_visitor.rs-- the PREVIOUS helper-rewrite (walk()/make_temp_arg): a subscripted/expressionPREVIOUSargument is not reduced to a bare per-element scalarExpr::Var.src/simlin-engine/src/compiler/codegen.rs-- theExpr::Subscript->SubscriptIndex::Singlearm (~:489-:521) where the innerwalk_expr(expr)?returns theNotSimulatable"PREVIOUS requires a variable reference after helper rewriting" error.src/simlin-engine/src/db_ltm.rs--db_ltm.rs:2083(Err(_) => None, plus the sibling build_varErr(_) => Nonearms), the graceful-drop site that makes the LTM synthetic fragment read constant 0.Possible fix direction
Extend the PREVIOUS helper-rewrite in
builtins_visitor.rsto reduce a subscripted (or otherwise non-bare-expression)PREVIOUSargument to a bareExpr::Var-- i.e. synthesize the per-element scalar helper aux for the subscripted slice -- so the LTM synthetic fragment compiles instead of being dropped. This is the same structural direction proposed for the related PREVIOUS-arg cases (generalize the helper synthesis soPREVIOUS(expr)desugars for anyexpr, not just a bareVar). The fix should be paired with a regression test asserting the LTM link/loop score for an edge through a subscripted-PREVIOUSreference is non-zero (so the dropped-fragment-reads-0 behavior cannot silently return).Linkage
?-propagation path -- and therefore this graceful drop -- the live behavior; commit59f01126).model_ltm_fragment_diagnosticsWarning") as ltm: synthetic-fragment compilation uses an empty tables map, so link scores into graphical-function targets stub to 0 #546 (GF-table targets) and ltm: composite-reference link score into a stdlib-macro module fails to compile in exhaustive mode, so loops through SMOOTH/DELAY stub to 0 #548 (composite-reference into a stdlib macro) -- but a distinct root cause and codegen site.pop[Region, young]) classifiedDynamicIndex, where the A2A partial generator wraps aSUM(...)(anExpr::App) inPREVIOUS, failing at theBuiltinFn::Previousarm (codegen.rs ~:701/:712). This issue's failing argument is anExpr::Subscript(not anApp) and the error fires at theExpr::Subscript->SubscriptIndex::Singlearm (codegen.rs ~:493), via the subscripted-PREVIOUShelper-rewrite path -- not the partial generator'sSUM-wrap.PREVIOUS(PREVIOUS(pop)), a non-LTM whole-project hard compile failure rooted inmake_temp_argsynthesizing a scalar helper for a bare arrayed name (no subscript to substitute). This issue's argument is a subscripted reference, and the failure is the graceful LTM-fragment drop (score -> 0), not a hard whole-project failure.assemble_simulationNotSimulatablefailure on the ~150 real/synthetic temp-arg helper model variables (the core-compile path returningErr). This issue is specifically the LTM synthetic link/loop-score fragment being gracefully dropped atdb_ltm.rs:2083(the LTM-discovery path), which leaves the model simulating fine with a degraded loop score -- not aNotSimulatablecore-compile failure.