Archived Plan: Lowered Evaluator Implementation
Archived on 2026-04-20 after Phase 6 closed out the lowered-evaluator implementation track. Keep ../lowered_evaluator.md as the long-lived design/spec. This file preserves the phase-by-phase implementation history and the constraints/success criteria that drove it.
This file tracks implementation status and active follow-up work for the lowered evaluator.
Canonical references
Long-lived design/spec: ../lowered_evaluator.md
Supported-model ground truth: ../supported_models.md
Closed backend decision / benchmarks: numba_vs_jax.md
Final status
Phases 1-5 are complete: GraphIR, scheduling, the lowered evaluator, integration, compare mode, benchmark-driven backend validation, static caching, and Voigt lowering all landed.
Phase 6 is complete:
ENERGY_1D, 1D/2D profile lowering, 2D time-domain IRF/convolution lowering, and subcycle-aware lowering are all implemented.fit_model_girnow covers the full MCP-supported model surface for the bundled examples.
Completed implementation history
Phase 1: built the GraphIR layer, domain classification, and the initial lowering gates.
Phase 2: added the scheduler, expression compilation, and structural plan validation.
Phase 3: implemented
evaluate_2d(plan, theta)and numerical parity validation against MCP.Phase 4: integrated
fit_model_gir, compare mode, fit-time plan building, and post-fit parameter writeback.Phase 5: benchmarked the lowered path, added static component caching, added Voigt support, and closed the Numba-vs-JAX decision in favor of staying on the NumPy backend for this branch.
Phase 6.1: lowered
ENERGY_1Dfits viacan_lower_1d,schedule_1d, andevaluate_1d, then wired the fast path intofit_baseline()andfit_spectrum().Phase 6.2a: lowered profile-varying parameters for 1D fits, including profile-dependent expressions and parity/dispatch coverage.
Phase 6.2b: lowered profile-varying parameters in
fit_2d(), including profile expression programs, profiled-component evaluation, and parity coverage.Phase 6.3: lowered 2D time-domain IRF/convolution nodes for single-cycle resolved-trace cases. The graph remains the sole source of truth; unsupported convolution shapes still fall back cleanly to MCP.
Phase 6.4: lowered subcycle dynamics.
SUBCYCLE_REMAP/SUBCYCLE_MASKgraph nodes are compiled away inschedule_2dinto per-substepdyn_sub_time_axes/dyn_sub_masksarrays – no new OpKinds, no runtime graph traversal. The evaluator hot loop becomesfunc(axis[s], *pars) * mask[s]. Resolved-trace (subcycle=0) convolution coexists with subcycle-aware dynamics in the same model; onlysubcycle>0substeps carry remap/mask data. Parity verified against MCP on two-/three-subcycle fixtures (including cross-subcycle expressions) and on a mixed IRF + subcycle model; example 3 (multi_cycle) benchmarks at ~3.8x over MCP at 7e-15 parity, with no regression on non-subcycle example 2 (~3.4x, 1e-13 parity).
Non-goals for this plan
Project-level fit lowering remained a separate track.
Jacobian plumbing and optimizer changes remained a separate track.
JAX backend work remained a separate track.
Dynamic kernel-support resizing during fitting was out of scope; kernel support stayed fixed for a built model / scheduled plan.
Mixed-backend execution inside one fit was out of scope.
Constraints
Prefer flat, packed-array schedule state so the door stays open for a future JAX port if the roadmap ever justifies it.
Coverage parity comes first; widening lowering coverage should not regress the previously lowerable subset by more than about 5% per call on existing benchmarks.
Land the remaining work incrementally and keep the default GIR path numerically stable.
Success criteria
Default
fit_model_girdispatch no longer falls back to MCP for the remaining supported subcycle-aware cases in the bundled examples.Newly lowerable
SUBCYCLE_*coverage ships with direct parity tests and compare-mode coverage.Existing lowerable 1D/2D GIR paths remain numerically stable and performance-neutral within the regression budget above.