Replies: 2 comments
Ensemble flows — multiple shooting, initial-guess sweeps, and ensemble optimal controlExpanding on §5 (GPU support, issue #249) — the local GPU design work (see PR #326 and the 1. Multiple shooting (time-domain splitting)Split 2. Multiple shooting over many initial guesses (globalization)No time splitting — instead, many independent initial guesses ( 3. Ensemble optimal control (coupled via control and/or criterion)A different problem class entirely — not independent trajectory solves, but one optimal control problem whose state is the concatenation of N small, structurally-identical subsystems, coupled through a shared control Concrete example: MRI saturation for a single spin (saturation.html) generalizes to multi-epsilon-saturation — N spins/isochromats with different resonance offsets εᵢ, all driven by the same control, optimized jointly (e.g. average or worst-case saturation error over the ensemble). Note the different GPU fit here compared to #1/#2: because the RHS is block-diagonal (each spin's dynamics depend only on its own state, the shared control, and the pooled criterion — not on each other), this is naturally a matrix-batch RHS call ( Summary: #1 and #2 are ensembles of independent trajectories (DiffEqGPU Tracking: Captured as issue #341 — Ensemble flows — batch API for multiple shooting, globalization, and ensemble optimal control. Related: #249 (GPU support), #111 (GPU for ODEs). |
Roadmap v4 — remaining work (renumbered)Status update: §5 (GPU support & testing — array-level GPU model validated end-to-end on H200 hardware, PR #326; full device support, tests, and docs shipped through v0.15.0-beta) and the last open case of §8 (variable-costate for the with-control case, issue #231) are now done, on top of the §2/§3/§6/§7/§9 items already marked done above. Re-listing what's left, renumbered:
The "Plus" items from the previous update are now closed: #340 (missing integration tests) and #343 (compatibility table page) are both done. #315 remains the general roadmap tracking issue. Release: Bubbling this up into a new Original item numbering (§1–§10) in the opening post stays the reference for full context on each item; this comment is just a shorter pointer to what remains. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Roadmap v4
Last updated: July 7, 2026 Supersedes: roadmap-v3.md
Context
Between the v3 roadmap (July 3) and now, a burst of work (PRs #303, #304, #307, #310 and the "1-D = scalar" refactor) landed most of v3 §1, §2, §6 and the §7 tests. This v4 records what actually shipped — often in a different shape than v3 predicted — and re-scopes the remaining work with firmer design decisions.
Key differences from what v3 anticipated:
OpenLoop,ClosedLoop,DynClosedLoop) live in CTBase.Data (a single parametricControlLaw{F,FB,TD,VD}with feedback traits), not in CTFlows as three separate structs. Dispatch is on theCTBase.Traits.feedbacktrait.Flow(ocp, u::Function)always builds aDynClosedLoop;OpenLoop/ClosedLoopmust be constructed explicitly.PseudoHamiltonianSystem,ComposedHamiltonian,ComposedVectorField,ControlledVectorField(all in CTBase.Data), with ahamiltonian_type = :total | :partialaction option.variable_costatefree-time semantics were decided: the flow always integrates the naive augmented adjointṗv = -∂H/∂vwithpv(t0) = 0; the free-time transversality condition is a mitigated residual written in the shooting method, not computed by the flow.Completed since v3 (for the record)
Flow(ocp, law),Flow(h̃, law),Flow(fc, law);:total/:partial. PRs #304, #307Flow(ocp)hamiltonian,pseudo_hamiltonian,control_law,*_gradientfunctors. PR #310_asvecdropped;_coerce_state/_dim_coercecentralise coercion at the CTModels boundaryv = tf; semantics decided (see §7 below).v = t0andv = (t0, tf)to confirmThe remaining sections below are the v4 work items.
1. Implicit control (IFT / DAE) —
∂H̃/∂u = 0Issue: #46. Status: open, not started. Reference implementation exists in the old gallery.
Goal: When no control law is provided, compute the control automatically from the pseudo-Hamiltonian stationarity condition
∂H̃/∂u = 0, and integrate the resulting flow. This is the missing branch of the with-control OCP story: we now haveFlow(ocp, law)(control eliminated by an explicit feedback), but notFlow(ocp)with control implicit.Two strategies
∂H̃/∂u = 0along the flow to get an explicit ODE foru:u̇ = -(∂²H̃/∂u²)⁻¹ · (∂²H̃/∂z∂u)ᵀ · ẋ_H, withz = (x, p),ẋ_H = (∂H̃/∂p, -∂H̃/∂x).The augmented state
(x, p, u)is integrated as an ODE. Requires an initial control guessu(t0). The old-gallery RHS computes∂h/∂z,∂h/∂u,∂²h/∂u²,∂²h/∂z∂uby AD, setsdw[1:2n] = hvanddw[2n+1:end] = -(∂²h/∂u²)⁻¹(∂²h/∂z∂u)ᵀ·hv.∂H̃/∂u = 0as an algebraic constraint and integrate(x, p, u)as a DAE (DAEProblem+IDA/Sundials or a mass-matrix /DFBDFformulation).Design decision to make: option vs. strategy
The choice of implicit-control method (and IFT-vs-DAE within it) can be surfaced either as:
control = :implicit,implicit_method = :ift | :dae), routed through the existing action-option machinery (likehamiltonian_type), orCTBase.Strategiesstrategy — a first-class strategy family the same way the backend (:di) and integrator (:sciml) strategies are, so the implicit-control solver is a pluggable, dispatchable component.➡️ Decision pending. Lean toward a strategy if the IFT/DAE solver needs its own options and extension boundary (e.g. DAE pulls in Sundials as a weak dep); lean toward a simple option if it is just a branch in the RHS builder. Prototype IFT first (pure ODE, no new heavy dep), then decide whether DAE justifies the strategy machinery.
Interaction with the pseudo-Hamiltonian infrastructure
We already have
PseudoHamiltonianSystem(AD at fixedu) and thepseudo_*_gradientgetters. The IFT RHS needs the Hessian∂²H̃/∂u²and the mixed Jacobian∂²H̃/∂z∂u— new AD primitives to add toCTBase.Differentiation, alongside the existingpseudo_hamiltonian_gradient/pseudo_variable_gradient.Control passed as a keyword (not positional)
For implicit-control flows the initial control
u0is a keyword argument, consistent withvariable/variable_costate:controlis required for implicit-control flows and rejected otherwise (trait-based validation, same pattern asvariableforNonFixed/Fixed).Open questions
(x,p,u)state interact with the existingvariable_costateaugmentation (i.e.(x,p,u,pv))? Order and coercion of the augmented block.2. Flow on OCP + control + constraints
Status: OCP + control ✅ done; + constraints not started (blocked on §3 design).
Goal: Extend
Flow(ocp, law)(and the implicit-control flow of §1) to OCPs that carry path constraints — stateg(t,x) ≤ 0, controlg(t,u) ≤ 0, mixedg(t,x,u) ≤ 0. The flow must integrate the constrained Hamiltonian/state system.This is deliberately split from §3 (dual variables): here the concern is only that the constrained dynamics are integrated correctly. Whether/how multipliers are exposed is the subject of §3 and must be settled first, because it dictates the augmented-state layout.
Concretely this needs:
g+ its kind + multiplier dimension) — likely in CTBase.Data next toControlLaw, for symmetry;➡️ Do not start implementation until §3's storage question is decided.
3. Constraints & multipliers — design/reflection task (do not return duals)
Issue: #103 (closed as "captured in roadmap"). Status: reopen as a design/reflection item. This reverses the v3 §3 plan.
Revised position (v4): returning the dual variables from the flow is a bad idea and is dropped as the default deliverable. The real problem is not "how to return duals" but how the
CTModels.Solutions.Solutiontype should represent the absence/presence of multipliers. That needs thought before any code.Why returning duals cleanly is hard
dual(sol, ...)therefore cannot return something well-defined in the general (multi-constraint, partially-active) case.Solutionmust degrade gracefully when duals are absent.The actual questions to resolve
Solution: trait or type?Solution(HasDuals/NoDuals) keeps one type but branches every accessor/printer on the trait.ConstrainedSolution <: AbstractSolution) keeps the dual-freeSolutionclean and pushes dual handling to a subtype.Solutionlives there.dual(sol, ...)cannot be well-defined, what is the supported query? Per-named-constraint lookup? Returnnothing/ throw for inactive?Solutionprints with / without duals, with / without an active set.Deliverable for v4
A written design note answering (1)–(4), agreed with CTModels, before any constraint-multiplier code. Implementation (if any) is scoped afterward. The constrained dynamics (§2) can proceed independently of duals once the storage question is closed.
4. Flow derivative via dual numbers (variational equations / IND)
Issue: #93 (refs CTBase #25). Status: open, not started. Confirmed worth doing.
Goal: Differentiate through the flow correctly. When the flow is evaluated on
ForwardDiff.Dualinputs, integrate the variational equations (Internal Numerical Differentiation, IND) instead of naively pushing duals through the black-box integrator, so that∂φ/∂x₀,∂φ/∂p₀,∂φ/∂t₀,∂φ/∂t_fare obtained from the sensitivity ODE.Design
(x, p)with(δx, δp)propagated by the Jacobian of the vector field:δż = J·δz. For Hamiltonian flows this is the Hessian ofH(Jacobian ofX_H). This is the IND part.Dual-typed inputs and use a dedicated tag so we can recognise "our" duals and route to the variational path (rather than colliding with an outer AD pass). The tag disambiguates nested/foreign differentiation.variational = true/derivative = true) or aCTBase.Strategiesstrategy that selects the derivative-propagation method (IND/variational vs. plain dual push-forward vs. future adjoint/reverse). ➡️ Decision pending, ideally made together with §1 so implicit-control and derivative both use the same option-or-strategy convention.Implementation steps
J·δz(Hessian ofHvia AD).Dualdispatch on the flow call → variational path.5. GPU support & testing
Issues: #249, #111. Status: open, not started. Testing on GPU is called out as important.
Goal: Flows run on GPU arrays (
CuArray, and ideally AMDGPU/Metal) with GPU-compatible integrators, and we have actual GPU tests in CI or a guarded test env.Steps
Systems,Configs,Trajectories, and the RHS functors — infer the array type from the input instead of hard-codingVector{Float64}.Integrators.build_*preserves the input array type throughODEProblem.CPU/GPUstrategy (singleton types) — this dovetails with the option-vs-strategy discussions in §1/§4; GPU is a natural strategy candidate.6. Non-Hamiltonian flow without a costate (
Flow(ocp)for direct shooting)Issue: #230. Status: open; the Hamiltonian branch exists, the basic branch does not.
Goal: For an OCP without control but with variables, support a basic (non-Hamiltonian) state flow where the user does not pass a costate
p0:in addition to the existing Hamiltonian flow
This is the direct-shooting use case (parameter estimation / optimal design): integrate
ẋ = f(t, x, ∅, v)as a plain ODE with variables, no adjoint.Note: the v3-era
augment=truekeyword mentioned in issue #230 no longer exists — it has been replaced byvariable_costate. So issue #230's item 1 (Hamiltonian +augment) maps to today'svariable_costate; only item 2 (basic non-Hamiltonian flow with variables, nop0) is the actual remaining work here.Steps
p0, control-free OCP" call shape and route to aVectorFieldSystembuilt from the OCP dynamicsf(t,x,∅,v)(not a Hamiltonian system).variablekeyword forNonFixed(same rule as elsewhere).tspancall) — no costate, noSolution-with-costate. Decide the trajectory return type (bare state trajectory vs. a trimmedSolution).7. Getter naming & vector-field getter
Issue: #185. Status: partially addressed by PR #310; naming reconciliation pending.
We shipped
hamiltonian,pseudo_hamiltonian,control_law, and the*_gradientfunctors (§2 above). Issue #185 asks specifically for, from a flow:Work item
vector_field(flow)/hamiltonian_vector_field(flow)— expose the (symplectic) vector fieldX_Hof a Hamiltonian flow directly (we havehamiltonian_vector_fieldon systems; make sure it is reachable from the flow and named intuitively).symplectic_gradient(h)— decide whether to offer this as an alias/name forX_Hof a Hamiltonian.8. Variable costate for variable times — confirm remaining cases
Issue: #231. Status: mechanism ✅;
v = tf✅ tested;v = t0andv = (t0, tf)to confirm.The semantics are settled (this closes the main v3 §7 open question): the flow always integrates the naive
ṗv = -∂H/∂vwithpv(t0) = 0, uniformly, for any variable — time or not. When the variable is a free time, the boundary term is a mitigated transversality condition written in the shooting method, not by the flow:Remaining work
v = t0(free initial time) andv = (t0, tf)(both free) — thetest_variable_costate_free_time.jlsuite coversv = tfand a 2-D variable; make thet0-only and combined(t0, tf)cases explicit with analytic references.DynClosedLoopand implicit-control flows.9. Cache trajectory projections at construction (quality / perf)
Status: open, small, low-risk cleanup. Suggested from controlled_trajectory.jl.
Observation: the trajectory accessors rebuild an immutable projection functor on every call instead of returning a stored one. For example
control(sol::ControlledTrajectory)constructs a freshControlProjection(sol.traj, sol.law, sol.variable, sol.state_coerce)each time, although all four fields are known at construction. Same forstate(sol::ControlledTrajectory)(ControlledStateProjection).Idea: build the projections once at trajectory construction and store them as fields; the accessors then just return the stored functor. This trades a little more memory in the struct (a couple of small immutable functors) for zero reallocation on repeated
state(sol)/control(sol)calls (which happen a lot — plotting, objective quadrature, shooting).Applicability — two cases, one subtlety
ControlledTrajectory(ControlProjection,ControlledStateProjection) — the projections wrapsol.traj(the inner trajectory) +law+variable+coerce, all available before theControlledTrajectoryexists. ✅ Directly precomputable and storable.HamiltonianVectorFieldTrajectory(StateProjection,CostateProjection) — these wrapsolitself (self-reference), so they cannot be built beforesolexists. Options: (a) re-target them to wrap the inner integration result instead ofsol, then precompute; (b) a mutable/Reffield set right after construction; (c) leave as-is — they wrap a single field and are the cheapest case.Steps
ControlledTrajectory,HamiltonianVectorFieldTrajectory,VectorFieldTrajectory, and theCTModels.Solutionbuilder inOptimalControlFlowif it rebuilds closures).ControlledTrajectorycase), add fields and store them at construction; accessors return the stored functor.state,control,costatestill return callables).10. Internal ODE solver (optional / strategic)
Issue: #81. Status: open, exploratory. Not committed for v4 — recorded here because it interacts with the integrator/strategy architecture (§1, §4, §5).
Idea: a standalone in-house RKF 5(4) adaptive solver to reduce the OrdinaryDiffEq.jl dependency (install/precompile weight), keep things simple, and get easier access to tailored AD (which helps §4's variational integration). Strategic; revisit if precompile cost or AD-through-the-integrator becomes a real pain point.
Priority Summary
High priority:
vector_field(flow)(§7) — small, closes [Dev] Add getter for hamiltonian vector field #185, unblocks downstream.Flow(ocp)withoutp0(§6) — direct-shooting use case, small.Medium priority:
t0/(t0,tf)tests (§8) — finish [Dev] Augmented adjoints for variable times #231.Lower priority:
Cross-cutting decision: option vs.
CTBase.StrategiesstrategyThree features (implicit control §1, flow derivative §4, GPU §5) each face the same architectural choice: expose the behaviour as a keyword option (routed through the action-option machinery, like
hamiltonian_type) or as a first-classCTBase.Strategiesstrategy (like the:dibackend and:scimlintegrator strategies).➡️ Make this decision once, up front, and apply it consistently. A shared convention avoids three divergent mechanisms. Rule of thumb: strategy when the feature needs its own options and/or an extension boundary (weak deps: Sundials for DAE, CUDA for GPU); plain option when it is a local RHS branch with no new dependency.
Dependency Graph
Carried Over / still relevant
ODEProblem,ODEFunction) — phase 4 of the umbrella issue Complete Flow Construction features #247; partially present via the SciMLBase extension, audit completeness.All reactions