Skip to content

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:    , law   (the stationary point,    ) gives  , hence

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:

julia
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.
julia
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 typeOOP pointOOP trajIP pointIP 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
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
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
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
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
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 depends on the costate , which OpenLoop/ClosedLoop control laws do not provide:

julia
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 ​