Flow(h̃, law) compatibility
This page is a living compatibility reference for Flow(h̃, law), built directly from a Data.PseudoHamiltonian
Scope: CPU, default AD backend. The table is generated from probe/cpu/probe_cpu.jl. GPU compatibility is a separate effort — see probe/gpu.
As documented in Control laws, only a DynClosedLoop law is accepted here — OpenLoop and ClosedLoop are rejected, since a pseudo-Hamiltonian needs the costate p that those laws do not provide. Unlike Flow(ocp, law), there is no OCP involved: Systems.PseudoHamiltonianSystem/Data.ComposedHamiltonian wrap the user's H̃ function directly, with no OCP-derived fixed-size buffer — so, as measured below, this constructor's compatibility profile matches Flow(Hamiltonian), not the more restricted Flow(ocp, law).
The dynamics used on this page generalize Control laws's scalar example (
Last probed: 2026-07-23
This page's table shape — which containers and axes are tested — reflects probe/cpu/probe_cpu.jl as of this date. Every ✓/✗ cell below is still re-executed on every documentation build regardless (see Compatibility overview) — only the scope of what's tested can go stale, not the results shown.
The flow is built once, in both hamiltonian_type modes:
h̃ = Data.PseudoHamiltonian((x, p, u) -> sum(p .* u) - 0.5 * sum(abs2, u))
law = Data.DynClosedLoop((x, p) -> p)
hflow_total = Flows.Flow(h̃, law; reltol=1e-8) # :total (default)
hflow_partial = Flows.Flow(h̃, law; hamiltonian_type=:partial, reltol=1e-8)Flow
├─ system: PseudoHamiltonianSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ ├─ PseudoHamiltonian: autonomous, fixed (no variable)
│ │ natural call: h̃(x, p, u)
│ │ uniform call: h̃(t, x, p, u, v)
│ ├─ ControlLaw: dyn-closed-loop, autonomous, fixed (no variable)
│ │ natural call: u(x, p)
│ │ uniform call: u(t, x, p, v)
│ └─ backend: DifferentiationInterface{CPU}(ad_backend=AutoForwardDiff)
└─ integrator: SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm [default]
├─ alg = Tsit5 [default]
├─ reltol = 1.0e-8 [user]
├─ save_everystep = auto [default]
├─ abstol = 1.0e-8 [default]
├─ save_start = auto [default]
└─ dense = auto [default]
Tip: use describe(SciML{CPU}) to see all available options.Compatibility table
Because the feedback is stationary, :total (AD through the law) and :partial (AD at fixed u) agree exactly — the two pairs of columns are a genuine consistency cross-check, not two independent measurements, exactly as on Flow(ocp, law)'s table:
| State/costate type | :total point | :total traj | :partial point | :partial traj |
|---|---|---|---|---|
Scalar Real | ✓ | ✓ | ✓ | ✓ |
Vector/MVector/SVector Real | ✓ | ✓ | ✓ | ✓ |
Matrix/MMatrix/SMatrix Real (batch) | ✓ | ✓ | ✓ | ✓ |
Complex (any container) | ✗ (a) | ✗ (a) | ✗ (a) | ✗ (a) |
ForwardDiff.Dual (any container) | ✗ (b) | ✗ (b) | ✗ (b) | ✗ (b) |
The full Matrix family works here — measured, not assumed: confirms the "wraps H̃ directly" hypothesis above. Flow(ocp, law) now matches this (fixed as of 2026-07-24, #358) — before that fix, its OCP-derived fixed-size buffer rejected Matrix-family states, unlike the buffer-free wrapping used here.
(a) Complex is not supported
Same cause as Flow(Hamiltonian)'s note (a): the default AutoForwardDiff backend cannot differentiate through a Complex input.
(b) A hand-built ForwardDiff.Dual collides with the flow's own AD
Same cause as Flow(Hamiltonian)'s nested-AD section: Flow(h̃, law) is itself AutoForwardDiff-backed, so passing a hand-built Dual as x0/p0 raises DualMismatchError rather than working. To differentiate the flow itself (e.g. for a shooting method), wrap the flow call in an outer ForwardDiff.jacobian/gradient instead — the same pattern documented there.
Real states
julia> x0, p0 = 1.0, 0.5;
julia> xf, pf = hflow_total(0.0, x0, p0, 1.0);
julia> xf ≈ x0 + p0, pf ≈ p0
(true, true)julia> hflow_partial(0.0, x0, p0, 1.0) # matches hflow_total exactly — stationary feedback
(1.5, 0.5)julia> hflow_total(0.0, SA[1.0, 2.0], SA[0.5, 0.3], 1.0)
([1.5, 2.3], [0.5, 0.3])julia> sol = hflow_total((0.0, 1.0), [1.0, 2.0], [0.5, 0.3]);
julia> Trajectories.state(sol)(1.0), Trajectories.costate(sol)(1.0)
([1.4999999999999976, 2.2999999999999985], [0.5, 0.3])julia> hflow_total(0.0, MMatrix{2,2}(1.0, 3.0, 2.0, 4.0), MMatrix{2,2}(0.0, 1.0, 0.0, 1.0), 1.0) # mutable
([1.0 2.0; 4.0 5.0], [0.0 0.0; 1.0 1.0])
julia> hflow_total(0.0, SMatrix{2,2}(1.0, 3.0, 2.0, 4.0), SMatrix{2,2}(0.0, 1.0, 0.0, 1.0), 1.0) # immutable
([1.0 2.0; 4.0 5.0], [0.0 0.0; 1.0 1.0])Complex and Dual states — not supported
julia> try
hflow_total(0.0, 1.0 + 2.0im, 0.0 + 0.0im, 1.0)
catch e
showerror(stdout, e)
end
ArgumentError: Cannot create a dual over scalar type ComplexF64. If the type behaves as a scalar, define ForwardDiff.can_dual(::Type{ComplexF64}) = true.julia> try
hflow_total(0.0, ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0), 1.0)
catch e
showerror(stdout, e)
end
Cannot determine ordering of Dual tags Nothing and ForwardDiff.Tag{CTBaseDifferentiationInterface.var"#h_x#h_x##0"{CTBase.Data.ComposedHamiltonian{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, CTBase.Data.PseudoHamiltonian{Main.var"#2#3", CTBase.Traits.Autonomous, CTBase.Traits.Fixed}, CTBase.Data.ControlLaw{Main.var"#5#6", CTBase.Traits.DynClosedLoopFeedback, CTBase.Traits.Autonomous, CTBase.Traits.Fixed}}, Float64, ForwardDiff.Dual{Nothing, Float64, 1}, Nothing}, ForwardDiff.Dual{Nothing, Float64, 1}}See notes (a)/(b) above — for Complex, rewrite the system on real/imaginary components; for differentiating the flow, wrap the call in an outer ForwardDiff.jacobian/gradient as shown on Flow(Hamiltonian)'s page.
OpenLoop/ClosedLoop — rejected
A pseudo-Hamiltonian OpenLoop/ClosedLoop control laws do not provide:
julia> try
Flows.Flow(h̃, Data.ClosedLoop(x -> -x); reltol=1e-8)
catch e
showerror(stdout, e)
end
PreconditionError → #_flow_from_pseudo_hamiltonian#42, building.jl:315
│
│ Flow(h̃, law) requires a DynClosedLoop control law
│
│ Reason a pseudo-Hamiltonian H̃(t,x,p,u,v) depends on the costate p, but OpenLoop u(t,v) and ClosedLoop u(t,x,v) control laws do not take p
│
│ Context Flow(h̃::PseudoHamiltonian, law::ControlLaw) — feedback dispatch
│ Hint use DynClosedLoop(u) to construct a control law u(t,x,p,v)
└─See also
Control laws — the guide page this reference expands on (
DynClosedLoop,hamiltonian_type,Flow(h̃, law)vsFlow(ocp, law)).Flow(Hamiltonian)compatibility — the sharedComplex/nested-Dualroot cause, and how to actually differentiate an AD-backed flow.Flow(h̃vf, law)compatibility — the vector-field sibling (derivatives supplied directly, no AD), whoseComplex/Dualrestrictions do not apply.Flow(ocp, law)compatibility — the OCP-based counterpart, whose fixed-size-bufferMatrix-family restriction (#358) never applied here and is now fixed there too.Compatibility overview — the per-constructor feature matrix and the other flow types.
CTFlows.Flows.Flow,CTFlows.Flows.HamiltonianFlow— the flow types.CTBase.Data.PseudoHamiltonian,CTBase.Data.DynClosedLoop— the data wrappers.