Skip to content

ltm: synthetic agg node for SUM(arrayed[*] * scalar) with a scalar target fails to compile and is stubbed to constant 0 #738

@bpowers

Description

@bpowers

Summary

The synthetic aggregate node that LTM hoists for an inlined reducer of the shape SUM(arrayed[*] * scalar) with a scalar target fails fragment compilation, so it is silently stubbed to a constant 0 (with an Assembly Warning from model_ltm_fragment_diagnostics). Every link and loop score that routes through that agg is then wrong.

Repro shape (from the characterization fixture):

  • pop[region] -- an arrayed stock
  • scale = 0.001 * total + 0.01 -- a scalar aux feeding back from the scalar stock total
  • grow = 1 + SUM(pop[*] * scale) -- a scalar flow; SUM(pop[*] * scale) is a sub-expression (the 1 + keeps it from being a whole-RHS, variable-backed agg), so it is hoisted into a synthetic $⁚ltm⁚agg⁚0

With LTM enabled the model compiles and simulates, but the hoisted $⁚ltm⁚agg⁚0 node's own equation is rejected by the fragment compiler: compile_ltm_equation_fragment returns a result with flow_bytecodes: None, assemble_module drops the fragment, and the agg keeps its layout slot with no bytecode writing it -- it reads a constant 0 at runtime instead of SUM(pop[*] * scale). model_ltm_fragment_diagnostics (src/simlin-engine/src/db/ltm/compile.rs, ~line 1147) emits the "failed to compile; ... evaluates to a constant 0" Warning, so the degradation is visible in diagnostics, but the scores are still silently wrong numerically.

The arrayed-target twin of the same expression compiles fine -- this is specific to the scalar-target shape (a scalar variable whose equation embeds a reducer over an arrayed dep multiplied by a scalar feeder).

Why it matters

Medium. This is a quiet LTM correctness gap for scalar-target reducer loops -- a common, perfectly legal model shape (total_flow = base + SUM(arr[*] * coeff)). The agg's runtime value is 0, so:

  • the source→agg link scores (pop[d] → $⁚ltm⁚agg⁚0) and the agg→target link score ($⁚ltm⁚agg⁚0 → grow) are degraded, and
  • every loop score whose chain passes through the agg is wrong.

A Warning is emitted (and per GH #466 it may not even reach the FFI diagnostics surface by default), but nothing in the numeric output flags the corruption.

Components affected

  • src/simlin-engine/src/ltm_agg.rs -- enumerate_agg_nodes (~line 308) and the agg equation construction: the emitted agg equation for the scalar-target SUM(arrayed[*] * scalar) shape is what the compiler rejects
  • src/simlin-engine/src/db/ltm/compile.rs -- compile_ltm_synthetic_fragment / compile_ltm_equation_fragment (the fragment-compile path that fails) and model_ltm_fragment_diagnostics (~line 1147, the Warning that makes the stub visible)

Possible approaches

  • Root-cause why the scalar-target agg equation fails fragment lowering while the arrayed-target twin succeeds (likely a dimension/typing mismatch in how the emitted agg equation or its dependency stubs are shaped when the hoisting target is scalar), and fix the agg equation construction in ltm_agg.rs (or the fragment dep-stub shaping in db/ltm/compile.rs) so the fragment compiles.
  • Once the agg computes, note that the pure-scalar loop total → scale → grow → total still won't route through it: build_loops_from_tiered materializes a PureScalar fast-path cycle straight from the variable-level circuit, linking scale → grow directly (gap editor overlay #2 in the characterization test's doc comment) -- closing that routing gap is adjacent follow-on work.

Existing test coverage

A characterization test documenting both gaps exists: scalar_feeder_scalar_target_loop_compiles_and_is_well_formed in src/simlin-engine/tests/integration/ltm_array_agg.rs (see its doc comment, which enumerates this issue as gap #1). It pins compile/simulate well-formedness (finite scores, loop enumerated) without asserting the currently-zero values, so closing this gap will not break it -- but a fix should tighten it to assert a non-zero agg value/score.

Discovery context

Identified during work on GH #533 (branch ltm-bug-batch, commit 189e7d0). Both gaps behave identically with and without #533's element-graph fast-path fix -- the element edge that fix adds is never consumed downstream here -- so this is pre-existing, independent breakage.

Tracking

Part of LTM tracking epic #488. Related to but distinct from: #533 (element-graph both-scalar fast path bypassing ThroughAgg routing -- the element edge, not the agg node's value), #525 (link-score partial equation fails to compile for partially-iterated subscripts -- a different synthetic-equation family), #514/#534 (whether a reducer gets hoisted at all -- here the hoist succeeds, the hoisted node's equation doesn't compile), #546/#548 (closed: other causes of synthetic fragments stubbing to 0).

Metadata

Metadata

Assignees

No one assigned

    Labels

    ltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions