Flow(h̃vf, law) compatibility
This page is a living compatibility reference for Flow(h̃vf, law), built from a Data.PseudoHamiltonianVectorField h̃vf(t,x,p,u,v) = (ẋ,ṗ) — the derivatives of a pseudo-Hamiltonian already differentiated by hand, with an explicit control argument u — and a control law that closes the loop. No AD, no OCP involved. Every ✓ / ⚠ in the table below is demonstrated by a code block re-run on every documentation build.
Scope: CPU only. The table is generated from probe/cpu/probe_cpu.jl (run it locally with julia --project=probe/cpu 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 vector field needs the costate p that those laws do not provide.
Flow(h̃vf, law) is the vector-field analogue of Flow(h̃, law): that constructor differentiates a scalar H̃ by AD (:total/:partial); this one takes the derivatives directly, so there is no hamiltonian_type option — only one mode. Instead, the axis that matters here is the same one as Flow(HamiltonianVectorField): whether h̃vf is out-of-place or in-place.
Same reference dynamics as Flow(h̃, law)'s page, pre-differentiated by hand instead of via AD:
so h̃vf(x,p,u) = (\dot x, \dot p) = (u, 0) is the already-differentiated vector field supplied directly — no AD anywhere in this constructor.
Last probed: 2026-07-24
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.
Two flows are built once — one from an out-of-place h̃vf, one from an in-place one — sharing the same DynClosedLoop law, and reused by every example below:
h̃vf = Data.PseudoHamiltonianVectorField((x, p, u) -> (u, zero(p))) # out-of-place
law = Data.DynClosedLoop((x, p) -> p)
hflow = Flows.Flow(h̃vf, law; reltol=1e-8)Flow
├─ system: PseudoHamiltonianVectorFieldSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ ├─ PseudoHamiltonianVectorField: autonomous, fixed (no variable), out-of-place
│ │ natural call: f(x, p, u)
│ │ uniform call: f(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)
└─ 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.h̃vf_ip = Data.PseudoHamiltonianVectorField(
(dx, dp, x, p, u) -> (dx .= u; dp .= 0); is_inplace=true,
) # in-place
hflow_ip = Flows.Flow(h̃vf_ip, law; reltol=1e-8)Flow
├─ system: PseudoHamiltonianVectorFieldSystem
│ ├─ time_dependence: Autonomous
│ ├─ variable_dependence: Fixed
│ ├─ PseudoHamiltonianVectorField: autonomous, fixed (no variable), in-place
│ │ natural call: f(dx, dp, x, p, u)
│ │ uniform call: f(dx, dp, 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)
└─ 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
The two call styles are the point call hflow(t0, x0, p0, tf) → final (xf, pf), and the trajectory call hflow((t0, tf), x0, p0) → CTFlows.Trajectories.HamiltonianVectorFieldTrajectory. The OOP / IP columns are the pseudo-Hamiltonian-vector-field kind — independent of the state/costate container, exactly as for Flow(HamiltonianVectorField).
| State/costate type | OOP point | OOP traj | IP point | IP traj |
|---|---|---|---|---|
Scalar Real | ✓ | ✓ | ✓ | ✓ |
Vector Real | ✓ | ✓ | ✓ | ✓ |
MVector Real | ✓ | ✓ | ✓ | ✓ |
SVector Real | ✓ | ✓ | ⚠ | ⚠ |
Matrix Real (batch) | ✓ | ✓ | ✓ | ✓ |
MMatrix Real | ✓ | ✓ | ✓ | ✓ |
SMatrix Real | ✓ | ✓ | ⚠ | ⚠ |
Scalar Complex | ✓ | ✓ | ✓ | ✓ |
Vector Complex | ✓ | ✓ | ✓ | ✓ |
MVector Complex | ✓ | ✓ | ✓ | ✓ |
SVector Complex | ✓ | ✓ | ⚠ | ⚠ |
Matrix Complex | ✓ | ✓ | ✓ | ✓ |
SMatrix Complex | ✓ | ✓ | ⚠ | ⚠ |
ForwardDiff.Dual scalar | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual Vector | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual MVector | ✓ | ✓ | ✓ | ✓ |
ForwardDiff.Dual SVector | ✓ | ✓ | ⚠ | ⚠ |
Legend
✓ — works; the result matches the analytic
and is executed on this page.⚠ — works, but emits a performance warning; see note (a).
Every state/costate type is supported on CPU — there is no unsupported (✗) combination, including Complex and ForwardDiff.Dual: since Flow(h̃vf, law) has no internal AD backend (unlike Flow(h̃, law), which fails on Complex and a hand-built Dual), this constructor's profile matches Flow(HamiltonianVectorField) exactly — measured, not assumed.
(a) In-place pseudo-Hamiltonian vector field + immutable initial condition
An in-place h̃vf with an immutable (x0, p0) (SVector, SMatrix) emits a performance @warn and falls back to an out-of-place finalize RHS. The result is correct but the path is slower — same fallback as Flow(HamiltonianVectorField). Prefer an out-of-place h̃vf, or mutable MVector / MMatrix, for static states.
Real states
julia> x0, p0 = 1.0, 0.5;
julia> xf, pf = hflow(0.0, x0, p0, 1.0);
julia> xf ≈ x0 + p0, pf ≈ p0
(true, true)julia> hflow(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((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])In-place pseudo-Hamiltonian vector fields
An in-place h̃vf produces the same results for mutable containers:
julia> hflow_ip(0.0, [1.0, 2.0], [0.5, 0.3], 1.0)
([1.5, 2.3], [0.5, 0.3])
julia> hflow_ip(0.0, MVector{2}(1.0, 2.0), MVector{2}(0.5, 0.3), 1.0)
([1.5, 2.3], [0.5, 0.3])An immutable static array (SVector/SMatrix) falls back to an out-of-place finalize RHS and warns (note (a)); the result is still correct:
julia> hflow_ip(0.0, SA[1.0, 2.0], SA[0.5, 0.3], 1.0) # warns, then returns the correct value
┌ Warning: InPlace PseudoHamiltonianVectorField with immutable u0 (e.g. SVector): consider using an out-of-place function for better performance.
└ @ CTFlows.Systems ~/work/CTFlows.jl/CTFlows.jl/src/Systems/pseudo_hamiltonian_vector_field_system.jl:148
([1.5, 2.3], [0.5, 0.3])OpenLoop/ClosedLoop — rejected
A pseudo-Hamiltonian vector field OpenLoop/ClosedLoop control laws do not provide:
julia> try
Flows.Flow(h̃vf, Data.ClosedLoop(x -> -x); reltol=1e-8)
catch e
showerror(stdout, e)
end
PreconditionError → #_flow_from_pseudo_hamiltonian_vf#45, building.jl:447
│
│ Flow(h̃vf, law) requires a DynClosedLoop control law
│
│ Reason a pseudo-Hamiltonian vector field h̃vf(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̃vf::PseudoHamiltonianVectorField, 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,Flow(h̃vf, law)vsFlow(h̃, law)).Flow(HamiltonianVectorField)compatibility — the no-AD sibling on the control-free side of the grid, and the shared in-place/immutable-u0warning.Flow(h̃, law)compatibility — the AD-based sibling (same dynamics, differentiated instead of supplied directly), whoseComplex/Dualrestrictions do not apply here.Compatibility overview — the per-constructor feature matrix and the other flow types.
CTFlows.Flows.Flow,CTFlows.Flows.HamiltonianFlow— the flow types.CTBase.Data.PseudoHamiltonianVectorField,CTBase.Data.DynClosedLoop— the data wrappers.