Skip to content

Flow(HamiltonianVectorField) compatibility ​

This page is a living compatibility reference for the Hamiltonian flow built from a Data.HamiltonianVectorField: which state/costate types and call styles it accepts, each shown with a minimal, executable example. Every ✓ / ⚠ in the table below is demonstrated by a code block on this page and is re-run on every documentation build, so the page cannot drift from the code.

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.

All examples integrate the harmonic oscillator     (autonomous, fixed) with the default SciML integrator (OrdinaryDiffEqTsit5). The analytic solution is   ,    , so integrating from   to   maps every initial condition to   .

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.

Two flows are built once — one from an out-of-place Hamiltonian vector field, one from an in-place one — and reused by every example below, so each example isolates the one thing that varies: the initial condition (x0, p0).

julia
hvf   = Data.HamiltonianVectorField((x, p) -> (p, -x))   # out-of-place: (x,p) -> (p,-x)
hflow = Flows.Flow(hvf; reltol=1e-8)
Flow
├─ system: HamiltonianVectorFieldSystem
│  ├─ time_dependence: Autonomous
│  ├─ variable_dependence: Fixed
│  └─ HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
│       natural call: f(x, p)
│       uniform call: f(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
hvf_ip   = Data.HamiltonianVectorField(
    (dx, dp, x, p) -> (dx .= p; dp .= -x); is_autonomous=true, is_variable=false,
)   # in-place
hflow_ip = Flows.Flow(hvf_ip; reltol=1e-8)
Flow
├─ system: HamiltonianVectorFieldSystem
│  ├─ time_dependence: Autonomous
│  ├─ variable_dependence: Fixed
│  └─ HamiltonianVectorField: autonomous, fixed (no variable), in-place
│       natural call: f(dx, dp, x, p)
│       uniform call: f(dx, dp, 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) → Trajectories.HamiltonianVectorFieldTrajectory. The OOP / IP columns are the Hamiltonian-vector-field kind — an out-of-place (x, p) -> (p, -x) versus an in-place (dx, dp, x, p) -> (dx .= p; dp .= -x) — independent of the state/costate container. (Which integration path is taken is chosen automatically from the mutability of vcat(x0, p0); see In-place Hamiltonian vector fields.)

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.

(a) In-place Hamiltonian vector field + immutable initial condition

An in-place Hamiltonian vector field 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. Prefer an out-of-place Hamiltonian vector field, or mutable MVector / MMatrix, for static states.

1-D = scalar, end to end

A scalar (x0, p0) stays a scalar in both call styles: the point call returns a scalar (xf, pf) pair, and the trajectory accessors state(sol)(t) / costate(sol)(t) also return scalars — not length-1 vectors. A length-1 vector or SVector collapses the same way. This is the ecosystem's "1-D = scalar" convention, implemented here via the HamiltonianVectorFieldSystem coercion machinery — the same mechanism Flow(VectorField) now shares. See the Shape contract page for the full cross-constructor picture.


Real states ​

Scalar ​

Both call styles return scalars:

julia
julia> hflow(0.0, 1.0, 0.0, pi/2)   # (xf, pf) ≈ (0.0, -1.0)
(-1.5541996200011742e-10, -0.9999999997009266)
julia
julia> sol = hflow((0.0, pi/2), 1.0, 0.0);

julia> (Trajectories.state(sol)(pi/2), Trajectories.costate(sol)(pi/2))   # ≈ (0.0, -1.0), both scalars
(-1.5541963451763603e-10, -0.9999999997009266)

Vector ​

julia
julia> hflow(0.0, [1.0, 0.0], [0.0, 1.0], pi/2)
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

The trajectory's state/costate accessors interpolate at any time in the span:

julia
julia> sol = hflow((0.0, pi/2), [1.0, 0.0], [0.0, 1.0]);

julia> (Trajectories.state(sol)(pi/4), Trajectories.costate(sol)(pi/4))
([0.7071067807847535, 0.707106781788745], [-0.707106781788745, 0.7071067807847535])

MVector and SVector ​

Mutable and immutable static vectors both integrate out-of-place:

julia
julia> hflow(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), pi/2)
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

julia> hflow(0.0, SA[1.0, 0.0], SA[0.0, 1.0], pi/2)
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

Matrix (batch) ​

A matrix state/costate integrates every column as an independent trajectory of the harmonic oscillator:

julia
julia> hflow(0.0, [1.0 2.0; 3.0 4.0], [0.0 0.0; 1.0 1.0], pi/2)
([-9.327931901967881e-11 -1.8655863803935762e-10; 0.9999999995245173 0.9999999994312379], [-0.9999999998043553 -1.9999999996087106; -2.9999999995063455 -3.9999999993107])

julia> hflow(0.0, MMatrix{2,2}(1.0, 3.0, 2.0, 4.0), MMatrix{2,2}(0.0, 1.0, 0.0, 1.0), pi/2)   # mutable
([-9.327931901967881e-11 -1.8655863803935762e-10; 0.9999999995245173 0.9999999994312379], [-0.9999999998043553 -1.9999999996087106; -2.9999999995063455 -3.9999999993107])

julia> hflow(0.0, SMatrix{2,2}(1.0, 3.0, 2.0, 4.0), SMatrix{2,2}(0.0, 1.0, 0.0, 1.0), pi/2)   # immutable
([-9.327931901967881e-11 -1.8655863803935762e-10; 0.9999999995245173 0.9999999994312379], [-0.9999999998043553 -1.9999999996087106; -2.9999999995063455 -3.9999999993107])

Complex states ​

Complex initial conditions work with the same real Hamiltonian vector field — the scalar case:

julia
julia> hflow(0.0, 1.0 + 2.0im, 0.0 + 0.0im, pi/2)   # ≈ (0+0im, -1-2im)
(-9.676404246107521e-11 - 1.9352808492215042e-10im, -0.9999999997977443 - 1.9999999995954887im)

vectors:

julia
julia> hflow(0.0, [1.0 + 2.0im, 0.0 + 0.0im], [0.0 + 0.0im, 1.0 + 1.0im], pi/2)
(ComplexF64[-1.1098569197652709e-10 - 2.2197138395305417e-10im, 0.9999999997731949 + 0.9999999997731949im], ComplexF64[-0.9999999997731949 - 1.9999999995463897im, -1.1098569197652709e-10 - 1.1098569197652709e-10im])

and matrices:

julia
julia> hflow(0.0, [1.0+2.0im 5.0+6.0im; 3.0+4.0im 7.0+8.0im], [0.0+0.0im 1.0+1.0im; 2.0+2.0im 3.0+3.0im], pi/2)
(ComplexF64[-5.782075318723213e-11 - 1.1564150637446425e-10im 0.9999999995798117 + 0.999999999521992im; 1.999999999564368 + 1.9999999995065474im 2.9999999992019992 + 2.9999999991441815im], ComplexF64[-0.9999999998689151 - 1.9999999997378302im -4.9999999994023945 - 5.999999999271313im; -2.9999999997223874 - 3.9999999995913007im -6.999999999255864 - 7.9999999991247845im])

Automatic differentiation (ForwardDiff.Dual) ​

A ForwardDiff.Dual initial condition propagates a sensitivity through the integration — the basis for differentiating a flow with respect to its initial state/costate:

julia
julia> x0 = ForwardDiff.Dual(1.0, 1.0)    # value 1.0, seed 1.0
Dual{Nothing}(1.0,1.0)

julia> p0 = ForwardDiff.Dual(0.0, 0.0)
Dual{Nothing}(0.0,0.0)

julia> xf, pf = hflow(0.0, x0, p0, pi/2)
(Dual{Nothing}(-1.5541996200011742e-10,-1.5541996200011742e-10), Dual{Nothing}(-0.9999999997009266,-0.9999999997009266))

julia> (ForwardDiff.value(xf), ForwardDiff.value(pf))   # ≈ (0.0, -1.0)
(-1.5541996200011742e-10, -0.9999999997009266)

Vector duals work the same way:

julia
julia> x0 = [ForwardDiff.Dual(1.0, 1.0), ForwardDiff.Dual(0.0, 0.0)]
2-element Vector{ForwardDiff.Dual{Nothing, Float64, 1}}:
 Dual{Nothing}(1.0,1.0)
 Dual{Nothing}(0.0,0.0)

julia> p0 = [ForwardDiff.Dual(0.0, 0.0), ForwardDiff.Dual(1.0, 0.0)]
2-element Vector{ForwardDiff.Dual{Nothing, Float64, 1}}:
 Dual{Nothing}(0.0,0.0)
 Dual{Nothing}(1.0,0.0)

julia> hflow(0.0, x0, p0, pi/2)
(ForwardDiff.Dual{Nothing, Float64, 1}[Dual{Nothing}(-1.5541996200011742e-10,-1.5541996200011742e-10), Dual{Nothing}(0.9999999997009266,0.0)], ForwardDiff.Dual{Nothing, Float64, 1}[Dual{Nothing}(-0.9999999997009266,-0.9999999997009266), Dual{Nothing}(-1.5541996200011742e-10,0.0)])

Grid invariance

As for Flow(VectorField), the SciML integrator's internalnorm ignores dual parts, so the integration grid is identical whether the initial condition is a Real or a Dual.

Recommended: differentiate the flow, not one hand-seeded Dual ​

As for Flow(VectorField), wrap the flow call in an outer ForwardDiff.jacobian / ForwardDiff.gradient to get every partial in one call, rather than hand-seeding a Dual per component. Flow(HamiltonianVectorField) has no internal AD backend, so there is no nesting concern (unlike Flow(Hamiltonian)):

julia
julia> shoot(z) = collect(hflow(0.0, z[1], z[2], pi/2))
shoot (generic function with 1 method)

julia> ForwardDiff.jacobian(shoot, [1.0, 0.0])   # ≈ [0 1; -1 0]
2×2 Matrix{Float64}:
 -1.5542e-10   1.0
 -1.0         -1.5542e-10

This is the same Jacobian of the same dynamics computed by Flow(Hamiltonian) — a good cross-check that both constructors agree.


In-place Hamiltonian vector fields ​

An in-place Hamiltonian vector field (dx, dp, x, p) -> (dx .= p; dp .= -x) produces the same results. The state and costate are concatenated via vcat(x0, p0) into a single ODE state; the flow picks the integration path from the mutability of that concatenation. For a mutable container (Vector, MVector, Matrix, MMatrix) — and, notably, for a scalar (x0, p0) too, since vcat of two numbers always produces a mutable Vector — it is integrated truly in place:

julia
julia> hflow_ip(0.0, 1.0, 0.0, pi/2)                            # scalar: vcat(x0,p0) is mutable
(-1.5541996200011742e-10, -0.9999999997009266)

julia> hflow_ip(0.0, [1.0, 0.0], [0.0, 1.0], pi/2)
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

julia> hflow_ip(0.0, MVector{2}(1.0, 0.0), MVector{2}(0.0, 1.0), pi/2)
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

Immutable initial conditions (SVector / SMatrix) ​

An immutable static array cannot be written in place, so an in-place Hamiltonian vector field falls back to an out-of-place finalize RHS and emits a performance warning (note (a)). The result is still correct:

julia
julia> hflow_ip(0.0, SA[1.0, 0.0], SA[0.0, 1.0], pi/2)   # warns, then returns the correct value
┌ Warning: InPlace HamiltonianVectorField 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/hamiltonian_vector_field_system.jl:341
([-1.5541996200011742e-10, 0.9999999997009266], [-0.9999999997009266, -1.5541996200011742e-10])

For static states, prefer the out-of-place Hamiltonian vector field (hflow) shown above, or use mutable MVector / MMatrix.


See also ​