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
{{ message }}
Repository navigation
ltm: mapped-dimension sliced reducers are not hoisted into aggregate nodes (compute_read_slice declines a remapped iterated axis) #534
compute_read_slice in src/simlin-engine/src/ltm_agg.rs (added in Phase 4 / #514) decides whether an inlined array-reducer subexpression is hoistable into a synthetic $⁚ltm⁚agg⁚{n} node by computing, per source axis, an AxisRead ∈ {Pinned(elem), Iterated(dim), Reduced}. For the Iterated case -- a subscript index that names one of the enclosing (apply-to-all) equation's iterated dimensions in the position matching the source's declared dimension order -- the index dimension must match the source's row-axis dimension. As resolved by code review (commit 7f837928), the iterated-index branch now returns None (not hoistable) when the index matches the source's row axis only via a dimension mapping rather than literally being the same dimension -- e.g. SUM(matrix[State,*]) inside an apply-to-all-over-State body, where matrix is declared matrix[Region,D2] and there is a State→Region mapping.
So a mapped-dimension sliced reducer falls back to the conservative full-cross-product element-graph path (DirectWildcard reference → emit_edges_for_reference's cross-product arm, a …→{from} per-shape link score) instead of being hoisted into a synthetic agg with read-slice-driven element edges and per-read-row link scores. (Whole-extent reducers SUM(matrix[*,*]), literally-same-dim sliced reducers SUM(matrix[D1,*]) over matrix[D1,D2], and pinned-slice reducers SUM(matrix[NYC,*]) are all still hoisted -- this is specifically the mapped-iterated case that is declined.)
The reason: the Iterated-axis-driven agg-routing machinery -- emit_agg_routed_edges (db_analysis.rs), emit_source_to_agg_link_scores / read_slice_rows, emit_agg_to_target_link_scores (db_ltm.rs) -- assumes the agg's result axis (recorded in AggNode::result_dims) and the arrayed source's row axis are literally the same dimension. Honoring a mapping would require the IteratedAxisRead (and result_dims) to carry both the target-equation dim and the source-row dim, and to remap between them in all three emitters (the element-graph fan-out, the source→agg link-score row enumeration, and the agg→target link-score fan-out). Rather than thread a (target dim, source dim) pair through everywhere for a case no current model exercises, Phase 4 declined the mapped axis. Removing the mapping handling also left compute_read_slice's DimensionsContext parameter dead, so it was dropped (see commit 7f837928).
Why it matters
Low. It's a conservatism, never a crash: a mapped-dimension sliced reducer gets less-precise LTM scoring (the conservative full-cross-product element graph + a lumped per-shape link score) instead of the fine-grained per-read-row aggregate edges + per-row reducer link scores. The conservative path is still correct (more edges than necessary, never fewer; the spurious ones get effectively-zero runtime scores) -- just imprecise, the same flavor of over-approximation as the pre-Phase-4 wildcard-link-score path for all sliced reducers (#514).
src/simlin-engine/src/db_analysis.rs -- emit_agg_routed_edges (assumes result_dims ⊆ the source's row dims by name; would need to remap an Iterated axis from the source's declared dim to the target's iterated dim when fanning source[<...iterated...>] → agg[<iterated>] and agg[<iterated>] → to[e])
src/simlin-engine/src/db_ltm.rs -- emit_source_to_agg_link_scores / read_slice_rows / ReadSliceRow (the source→agg per-read-row link-score emitter; the row enumeration would need the mapping to know which source elements map to which agg result slot), emit_agg_to_target_link_scores (the agg→target half)
Possible approaches
Thread the (target dim, source dim) pair through the IteratedAxisRead (and correspondingly through AggNode::result_dims or a parallel structure), and remap between them in the three emitters via DimensionsContext's element-level mapping (SubdimensionRelation / has_mapping_to). The element-graph side (emit_agg_routed_edges) already does a result_dims-onto-to projection for the broadcast case, and expand_same_element's Bare arm is in the process of getting mapping-awareness (ltm: expand_same_element is over-conservative for mapped dimensions (full cross-product instead of the positional-mapping diagonal) #527) -- the same correspondence data is needed here.
If the mapped-dimension-sliced-reducer topology is judged not worth the extra plumbing, the conservative path is at least correct and the carve-out is documented (commit 7f837928, src/simlin-engine/CLAUDE.md under "LTM arrays hardening Phase 4", and enumerate_agg_nodes's carve-out list in ltm_agg.rs); this issue records the gap.
Discovery context
Identified during the Phase 4 code review of the LTM arrays hardening epic (#488; commit 7f837928 on branch ltm-arrays-hardening; design plan and per-phase tasks under docs/implementation-plans/2026-05-11-ltm-arrays-hardening/, see phase_04.md -- the sliced-reducer-hoisting work). The mapped-axis carve-out was the deliberate resolution of a code-review IMPORTANT issue: the Iterated-axis machinery assumes the agg result axis and the source row axis are the same dimension, and Phase 4 chose to decline the mapped axis rather than thread a (target dim, source dim) pair through all three emitters for an unexercised case. Not a regression -- no sliced reducer was hoisted at all before Phase 4.
Tracking
Part of LTM tracking epic #488. Related to: #514 (sliced-reducer hoisting -- the Phase-4 work; distinct: #514 was about whether sliced reducers are hoisted at all in the un-mapped case, this is the residual mapped-dimension carve-out within that work), #527 (expand_same_element over-conservative for mapped dims in direct references -- same "honor the mapping in the element correspondence" requirement, different code path: db_analysis.rs element-edge count vs ltm_agg.rs whether-to-hoist; could be folded together), #528 (agg→target link-score over-subscripts an arrayed synthetic agg -- another arrayed-synthetic-agg edge case from the same Phase-4 work), #520 (unify the reference-site walkers), and the LTM array-support umbrella #273.
Summary
compute_read_sliceinsrc/simlin-engine/src/ltm_agg.rs(added in Phase 4 / #514) decides whether an inlined array-reducer subexpression is hoistable into a synthetic$⁚ltm⁚agg⁚{n}node by computing, per source axis, anAxisRead ∈ {Pinned(elem), Iterated(dim), Reduced}. For theIteratedcase -- a subscript index that names one of the enclosing (apply-to-all) equation's iterated dimensions in the position matching the source's declared dimension order -- the index dimension must match the source's row-axis dimension. As resolved by code review (commit7f837928), the iterated-index branch now returnsNone(not hoistable) when the index matches the source's row axis only via a dimension mapping rather than literally being the same dimension -- e.g.SUM(matrix[State,*])inside an apply-to-all-over-Statebody, wherematrixis declaredmatrix[Region,D2]and there is aState→Regionmapping.So a mapped-dimension sliced reducer falls back to the conservative full-cross-product element-graph path (
DirectWildcardreference →emit_edges_for_reference's cross-product arm, a…→{from}per-shape link score) instead of being hoisted into a synthetic agg with read-slice-driven element edges and per-read-row link scores. (Whole-extent reducersSUM(matrix[*,*]), literally-same-dim sliced reducersSUM(matrix[D1,*])overmatrix[D1,D2], and pinned-slice reducersSUM(matrix[NYC,*])are all still hoisted -- this is specifically the mapped-iterated case that is declined.)The reason: the
Iterated-axis-driven agg-routing machinery --emit_agg_routed_edges(db_analysis.rs),emit_source_to_agg_link_scores/read_slice_rows,emit_agg_to_target_link_scores(db_ltm.rs) -- assumes the agg's result axis (recorded inAggNode::result_dims) and the arrayed source's row axis are literally the same dimension. Honoring a mapping would require theIteratedAxisRead(andresult_dims) to carry both the target-equation dim and the source-row dim, and to remap between them in all three emitters (the element-graph fan-out, the source→agg link-score row enumeration, and the agg→target link-score fan-out). Rather than thread a(target dim, source dim)pair through everywhere for a case no current model exercises, Phase 4 declined the mapped axis. Removing the mapping handling also leftcompute_read_slice'sDimensionsContextparameter dead, so it was dropped (see commit7f837928).Why it matters
Low. It's a conservatism, never a crash: a mapped-dimension sliced reducer gets less-precise LTM scoring (the conservative full-cross-product element graph + a lumped per-shape link score) instead of the fine-grained per-read-row aggregate edges + per-row reducer link scores. The conservative path is still correct (more edges than necessary, never fewer; the spurious ones get effectively-zero runtime scores) -- just imprecise, the same flavor of over-approximation as the pre-Phase-4 wildcard-link-score path for all sliced reducers (#514).
Components affected
src/simlin-engine/src/ltm_agg.rs--compute_read_slice(the mapped-iterated-axis branch that returnsNone; ~theIteratedrecognition logic, post-ltm: sliced reducer sub-expressions are not hoisted into aggregate nodes (stay on the wildcard-link-score path) #514),AggNode::read_slice/AxisRead/result_dims(the descriptor that would need theIteratedvariant to carry a(target dim, source dim)pair)src/simlin-engine/src/db_analysis.rs--emit_agg_routed_edges(assumesresult_dims⊆ the source's row dims by name; would need to remap anIteratedaxis from the source's declared dim to the target's iterated dim when fanningsource[<...iterated...>] → agg[<iterated>]andagg[<iterated>] → to[e])src/simlin-engine/src/db_ltm.rs--emit_source_to_agg_link_scores/read_slice_rows/ReadSliceRow(the source→agg per-read-row link-score emitter; the row enumeration would need the mapping to know which source elements map to which agg result slot),emit_agg_to_target_link_scores(the agg→target half)Possible approaches
(target dim, source dim)pair through theIteratedAxisRead(and correspondingly throughAggNode::result_dimsor a parallel structure), and remap between them in the three emitters viaDimensionsContext's element-level mapping (SubdimensionRelation/has_mapping_to). The element-graph side (emit_agg_routed_edges) already does aresult_dims-onto-toprojection for the broadcast case, andexpand_same_element'sBarearm is in the process of getting mapping-awareness (ltm: expand_same_element is over-conservative for mapped dimensions (full cross-product instead of the positional-mapping diagonal) #527) -- the same correspondence data is needed here.expand_same_elementmapping-awareness): both want "make the projection mapping-aware instead of name-only". ltm: expand_same_element is over-conservative for mapped dimensions (full cross-product instead of the positional-mapping diagonal) #527 is the direct-reference, element-edge-count side (db_analysis.rs); this is the sliced-reducer-hoisting side (ltm_agg.rs, whether the reducer gets hoisted at all). They share the underlying "honor the dimension mapping in the element correspondence" requirement but touch different code paths -- file as a sibling or extend ltm: expand_same_element is over-conservative for mapped dimensions (full cross-product instead of the positional-mapping diagonal) #527, whichever is cleaner at implementation time.7f837928,src/simlin-engine/CLAUDE.mdunder "LTM arrays hardening Phase 4", andenumerate_agg_nodes's carve-out list inltm_agg.rs); this issue records the gap.Discovery context
Identified during the Phase 4 code review of the LTM arrays hardening epic (#488; commit
7f837928on branchltm-arrays-hardening; design plan and per-phase tasks underdocs/implementation-plans/2026-05-11-ltm-arrays-hardening/, seephase_04.md-- the sliced-reducer-hoisting work). The mapped-axis carve-out was the deliberate resolution of a code-review IMPORTANT issue: theIterated-axis machinery assumes the agg result axis and the source row axis are the same dimension, and Phase 4 chose to decline the mapped axis rather than thread a(target dim, source dim)pair through all three emitters for an unexercised case. Not a regression -- no sliced reducer was hoisted at all before Phase 4.Tracking
Part of LTM tracking epic #488. Related to: #514 (sliced-reducer hoisting -- the Phase-4 work; distinct: #514 was about whether sliced reducers are hoisted at all in the un-mapped case, this is the residual mapped-dimension carve-out within that work), #527 (
expand_same_elementover-conservative for mapped dims in direct references -- same "honor the mapping in the element correspondence" requirement, different code path:db_analysis.rselement-edge count vsltm_agg.rswhether-to-hoist; could be folded together), #528 (agg→target link-score over-subscripts an arrayed synthetic agg -- another arrayed-synthetic-agg edge case from the same Phase-4 work), #520 (unify the reference-site walkers), and the LTM array-support umbrella #273.