Skip to content

From Hamiltonians and vector fields

Every constructor below builds a flow without an OCP — the building blocks From an OCP is itself assembled from. One section per constructor.

julia
using OptimalControl
using OrdinaryDiffEqTsit5

From a vector field

The plain ODE case,   (or f(t, x) if non-autonomous — see below):

julia
f = Flow(VectorField(x -> -x))
f(0.0, 1.0, 1.0)   # (t0, x0, tf) → x(tf), exp(-1)
0.36787944125650673

From a Hamiltonian

is obtained by automatic differentiation:

julia
h(x, p) = 0.5 * (x^2 + p^2)
fh = Flow(Hamiltonian(h))
fh(0.0, 1.0, 0.0, 1.0)   # (t0, x0, p0, tf) → (x(tf), p(tf))
(0.5403023057535162, -0.8414709847374632)

Hamiltonian defaults to the natural 2-arg autonomous form h(x, p). A 3-arg h(t, x, p) silently mis-dispatches unless declared explicitly non-autonomous — see below.

From a Hamiltonian vector field

Same system, but given directly — no AD:

julia
hvf(x, p) = (p, -x)
fhvf = Flow(HamiltonianVectorField(hvf))
fhvf(0.0, 1.0, 0.0, 1.0)
(0.5403023057535162, -0.8414709847374632)

From a pseudo-Hamiltonian and a control law

plus a law Flow substitutes and differentiates through it (AD), same as From an OCP's default hamiltonian_type=:total:

julia
htilde(x, p, u) = p * u - 0.5 * u^2
law(x, p) = p   # the maximiser of h̃ above
fph = Flow(PseudoHamiltonian(htilde), DynClosedLoop(law))
fph(0.0, 1.0, 0.0, 1.0)
(1.0, 0.0)

From a pseudo-Hamiltonian vector field and a control law

Same idea, vector-field form:    given directly. No hamiltonian_type= here — there's no pseudo-Hamiltonian scalar to differentiate two different ways, so passing it is rejected:

julia
htildevf(x, p, u) = (u, 0.0)
fphvf = Flow(PseudoHamiltonianVectorField(htildevf), DynClosedLoop(law))
fphvf(0.0, 1.0, 0.0, 1.0)
(1.0, 0.0)
julia
julia> Flow(
           PseudoHamiltonianVectorField(htildevf),
           DynClosedLoop(law);
           hamiltonian_type=:partial,
       )
IncorrectArgument  top-level scope, REPL[1]:2

│  Unknown option provided

│  Got       option :hamiltonian_type in method (:sciml, :cpu)
│  Expected  valid option name for one of the strategies

│  Context   route_options - unknown option validation
│  Hint      Did you mean?
- :alg (aliases: algorithm, solver) [distance: 11]
- :adaptive (aliases: adaptive_step, adaptive_stepping) [distance: 11]
- :d_discontinuities [distance: 12]
│            If you're confident this option exists for a specific strategy, use bypass() to skip validation:
│              custom_opt = route_to(<strategy_id>=bypass(<value>))
└─

From a SciML problem

An ODEFunction/ODEProblem from the SciML ecosystem works directly — reachable through OrdinaryDiffEqTsit5 alone, nothing extra to load:

julia
rhs!(dx, x, p, t) = (dx[1] = -x[1]; nothing)
f_fun = Flow(ODEFunction(rhs!))

SciML-backed flows are always NonFixed — even with no real free variable, a call needs variable=nothing:

julia
julia> f_fun(0.0, [1.0], 1.0)
PreconditionError  top-level scope, REPL[1]:2

│  variable not provided for a NonFixed flow

│  Reason   flow depends on an extra variable parameter but none was given

│  Context  call — NonFixed flow with missing variable
│  Hint     Pass `variable=v` when calling the flow
└─
julia
f_fun(0.0, [1.0], 1.0; variable=nothing)
0.36787944125650673
julia
prob = ODEProblem(rhs!, [1.0], (0.0, 1.0))
f_prob = Flow(prob)
typeof(f_prob)
CTFlowsSciMLFlows.SciMLProblemFlow{ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, SciMLBase.NullParameters, ODEFunction{true, SciMLBase.AutoSpecialize, typeof(Main.rhs!), LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, typeof(SciMLBase.DEFAULT_OBSERVED), Nothing, Nothing, Nothing, Nothing}, Base.Pairs{Symbol, Union{}, Nothing, @NamedTuple{}}, SciMLBase.StandardODEProblem}, SciML{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{internalnorm::CTBase.Options.OptionValue{typeof(CTSolvers.Integrators.real_norm)}, alg::CTBase.Options.OptionValue{Tsit5{typeof(OrdinaryDiffEqCore.trivial_limiter!), typeof(OrdinaryDiffEqCore.trivial_limiter!), FastBroadcast.Serial}}, reltol::CTBase.Options.OptionValue{Float64}, save_everystep::CTBase.Options.OptionValue{Symbol}, abstol::CTBase.Options.OptionValue{Float64}, save_start::CTBase.Options.OptionValue{Symbol}, dense::CTBase.Options.OptionValue{Symbol}}}, Dict{Symbol, Any}, Dict{Symbol, Any}}}

Non-autonomous and variable-dependent forms

is_autonomous= and is_variable= (not autonomous=, an old spelling that no longer exists) declare the extra arguments a function needs:

julia
g(t, x) = -t * x
fg = Flow(VectorField(g; is_autonomous=false))
fg(0.0, 1.0, 1.0)
0.6065306595385606

The stale form fails two ways at once — there is no Flow(::Function) at all, and even fixed to the right type, the keyword itself changed name:

julia
julia> VectorField(g; autonomous=false)
MethodError: no method matching VectorField(::typeof(Main.g); autonomous::Bool)
This method does not support all of the given keyword arguments (and may not support any).

Closest candidates are:
  VectorField(::Any; is_autonomous, is_variable, is_inplace) got unsupported keyword argument "autonomous"
   @ CTBase ~/.julia/packages/CTBase/clz4y/src/Data/vector_field.jl:183
  VectorField(::Any, !Matched::Type{TD}, !Matched::Type{VD}, !Matched::Type{MD}) where {TD<:CTBase.Traits.TimeDependence, VD<:CTBase.Traits.VariableDependence, MD<:CTBase.Traits.AbstractMutabilityTrait} got unsupported keyword argument "autonomous"
   @ CTBase ~/.julia/packages/CTBase/clz4y/src/Data/vector_field.jl:203

is_inplace= similarly declares an in-place (mutating, f!(dx, x)) vs out-of-place (f(x) -> dx) function — inferred automatically in most cases, settable explicitly when it isn't.

Summary table

ConstructorUses AD?Returns
Flow(VectorField(f))noStateFlow
Flow(Hamiltonian(h))yesHamiltonianFlow
Flow(HamiltonianVectorField(hvf))noHamiltonianFlow
Flow(PseudoHamiltonian(h̃), law)yesHamiltonianFlow
Flow(PseudoHamiltonianVectorField(h̃vf), law)noHamiltonianFlow
Flow(ODEFunction(...)) / Flow(ODEProblem(...))noStateFlow / SciMLProblemFlow

See also