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