Skip to content

LTM: PREVIOUS-in-subscript arg not reduced to a bare var during helper rewriting -> LTM synthetic link/loop score silently drops to 0 #587

Description

@bpowers

Summary

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

  1. 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.
  2. 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).")
  3. 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.
  4. 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).

Why it matters

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).

Linkage

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingengineIssues with the rust-based simulation engineltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions