What to build
Extend the single-segment path (#96) to handle piecewise-constant controls within one shooting node. Given one coarse TrajectoryInterval (a node's tspan) and the node's active ControlParameters (#92), split it into a Vector{TrajectoryInterval} at the union of all active controls' breakpoints falling inside that tspan — purely structurally, recomputed from the controls' own time grids on every call rather than cached in st (so the layer stays reusable if the controls structure varies between calls).
Each fine segment's p is built by writing in every control active at that segment's start (if control A switches at t=2 and B at t=2.5, segment [2,2.5) needs both A's and B's current values merged in — same semantics as today's build_index_grid union-of-breakpoints logic, re-expressed as repeated named writes via remake instead of an indicator-matrix multiply).
Solve the resulting vector sequentially: each segment's ic is the previous segment's output endpoint, built via a non-mutating reduction (e.g. accumulate/mapfoldl-style, not a push! loop), since reverse-mode AD must work through this (per #96).
The breakpoint/index lookup logic (searchsortedlast-style determination of which control segment is active) has no derivative — mark it non-differentiable via ChainRulesCore so Zygote doesn't try to push gradients through it.
Acceptance criteria
Blocked by
What to build
Extend the single-segment path (#96) to handle piecewise-constant controls within one shooting node. Given one coarse
TrajectoryInterval(a node's tspan) and the node's activeControlParameters (#92), split it into aVector{TrajectoryInterval}at the union of all active controls' breakpoints falling inside that tspan — purely structurally, recomputed from the controls' own time grids on every call rather than cached inst(so the layer stays reusable if the controls structure varies between calls).Each fine segment's
pis built by writing in every control active at that segment's start (if control A switches at t=2 and B at t=2.5, segment[2,2.5)needs both A's and B's current values merged in — same semantics as today'sbuild_index_gridunion-of-breakpoints logic, re-expressed as repeated named writes viaremakeinstead of an indicator-matrix multiply).Solve the resulting vector sequentially: each segment's
icis the previous segment'soutputendpoint, built via a non-mutating reduction (e.g.accumulate/mapfoldl-style, not apush!loop), since reverse-mode AD must work through this (per #96).The breakpoint/index lookup logic (
searchsortedlast-style determination of which control segment is active) has no derivative — mark it non-differentiable viaChainRulesCoreso Zygote doesn't try to push gradients through it.Acceptance criteria
TrajectoryInterval+ a set ofControlParameters produces the correctVector{TrajectoryInterval}breakpoints (union of all active controls' grids, clipped to the node's tspan)pcorrectly merges every control active at that segment, including overlapping/non-aligned breakpoints across multiple controlssolvewith continuity)push!/in-placeVectormutation)ChainRulesCore.@non_differentiable(or wrapped inignore_derivatives)ps.controlsmatches a forward-mode/finite-difference referenceBlocked by