Skip to content

Accessors

Building a flow doesn't throw away what it was built from — a flow remembers its Hamiltonian, its vector field, the control law you passed in, and the underlying integrator. This page is the map of what you can pull back out, and from which kind of flow.

julia
using OptimalControl
using OrdinaryDiffEqTsit5
using NLPModelsIpopt

What a flow remembers

Every flow wraps a system (the mathematical object — a Hamiltonian, a vector field, a pseudo-Hamiltonian plus a law...) and an integrator. The four accessors below read off the system; the last section reaches the integrator directly.

The Hamiltonian

julia
h(x, p) = 0.5 * (x^2 + p^2)
f = Flow(Hamiltonian(h))
H = hamiltonian(f)
H(0.0, 1.0, 0.0, 1.0)   # H(t, x, p, v)
0.5

The Hamiltonian vector field

julia
hamiltonian_vector_field(f)
HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
  natural call: f(x, p)
  uniform call: f(t, x, p, v)

Returns a HamiltonianVectorField  — whether or not the flow was built with AD.

The pseudo-Hamiltonian and the control law

Only available on flows built from a pseudo-Hamiltonian (or an OCP, which is one under the hood) plus a law:

julia
htilde(x, p, u) = p * u - 0.5 * u^2
law_fun(x, p) = p
f_ph = Flow(PseudoHamiltonian(htilde), DynClosedLoop(law_fun))

= pseudo_hamiltonian(f_ph)
(0.0, 1.0, 0.0, 1.0, 1.0)   # H̃(t, x, p, u, v)
-0.5
julia
control_law(f_ph)(1.0, 0.5)   # the law you passed in, u(x, p)
0.5

Gradients

Four functions, each returning a struct-wrapped callable (not a bare function or a vector) — expect (t, x, p, v)-shaped arguments when calling what they return:

julia
get_hamiltonian_gradient(f_ph)
CTFlows.Systems.HamiltonianGradient{ComposedHamiltonian{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, PseudoHamiltonian{typeof(Main.htilde), CTBase.Traits.Autonomous, CTBase.Traits.Fixed}, ControlLaw{typeof(Main.law_fun), CTBase.Traits.DynClosedLoopFeedback, CTBase.Traits.Autonomous, CTBase.Traits.Fixed}}, CTBase.Differentiation.DifferentiationInterface{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{ad_backend::CTBase.Options.OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}(ComposedHamiltonian: autonomous, fixed (no variable)
  natural call: h(x, p)
  uniform call: h(t, x, p, v), DifferentiationInterface{CPU}(ad_backend=AutoForwardDiff))
julia
get_variable_gradient(f_ph)
CTFlows.Systems.HamiltonianVariableGradient{ComposedHamiltonian{CTBase.Traits.Autonomous, CTBase.Traits.Fixed, PseudoHamiltonian{typeof(Main.htilde), CTBase.Traits.Autonomous, CTBase.Traits.Fixed}, ControlLaw{typeof(Main.law_fun), CTBase.Traits.DynClosedLoopFeedback, CTBase.Traits.Autonomous, CTBase.Traits.Fixed}}, CTBase.Differentiation.DifferentiationInterface{CPU, CTBase.Strategies.StrategyOptions{@NamedTuple{ad_backend::CTBase.Options.OptionValue{AutoForwardDiff{nothing, Nothing}}}}}}(ComposedHamiltonian: autonomous, fixed (no variable)
  natural call: h(x, p)
  uniform call: h(t, x, p, v), DifferentiationInterface{CPU}(ad_backend=AutoForwardDiff))

get_pseudo_hamiltonian_gradient(f_ph) and get_pseudo_variable_gradient(f_ph) mirror the two above, taken on before the law is substituted in rather than on the composed .

Building the Hamiltonian vector field from a Hamiltonian, without a flow

hamiltonian_vector_field also works directly on an AbstractHamiltonian, no Flow needed:

julia
hamiltonian_vector_field(Hamiltonian(h))
HamiltonianVectorField: autonomous, fixed (no variable), out-of-place
  natural call: f(x, p)
  uniform call: f(t, x, p, v)

What is available on which flow

Not every accessor makes sense on every flow — and the failure mode differs by why it doesn't apply, confirmed live rather than assumed uniform:

Built fromhamiltonianhamiltonian_vector_fieldpseudo_hamiltoniancontrol_law
Hamiltonian(h)IncorrectArgumentIncorrectArgument
HamiltonianVectorField(hvf)IncorrectArgumentMethodErrorMethodError
PseudoHamiltonian(h̃), law
ocp, law
VectorField(f)MethodErrorMethodError (use vector_field)MethodErrorMethodError

IncorrectArgument shows up where the flow could answer in principle but deliberately doesn't have enough information (a HamiltonianVectorField-built flow has no scalar Hamiltonian to hand back, only its vector field — the error says so and suggests hamiltonian_vector_field instead). MethodError shows up where the accessor simply has no method for that system type at all — a VectorField-built flow was never given anything Hamiltonian-shaped, so none of the four Hamiltonian-side accessors apply; use vector_field instead:

julia
vector_field(Flow(VectorField(x -> -x)))
VectorField: autonomous, fixed (no variable), out-of-place
  natural call: f(x)
  uniform call: f(t, x, v)
julia
julia> hamiltonian(Flow(HamiltonianVectorField((x, p) -> (p, -x))))
IncorrectArgument  top-level scope, REPL[1]:2

│  no scalar Hamiltonian available for a HamiltonianVectorFieldSystem

│  Got       a HamiltonianVectorFieldSystem (stores the vector field directly, no AD)
│  Expected  a HamiltonianSystem or PseudoHamiltonianSystem (built from a scalar Hamiltonian with AD)

│  Context   hamiltonian(sys::HamiltonianVectorFieldSystem)
│  Hint      use hamiltonian_vector_field(flow) to retrieve X_H, or build the flow from a scalar Hamiltonian
└─

The underlying system and integrator

Escape hatch, for when nothing above is specific enough — system/integrator stay deliberately unexported (too generic a name for a DSL surface), reach them qualified:

julia
CTFlows.Flows.system(f)
HamiltonianSystem
├─ time_dependence: Autonomous
├─ variable_dependence: Fixed
├─ Hamiltonian: autonomous, fixed (no variable)
    natural call: h(x, p)
    uniform call: h(t, x, p, v)
└─ backend: DifferentiationInterface{CPU}(ad_backend=AutoForwardDiff)
julia
CTFlows.Flows.integrator(f)
SciML{CPU} (instance, id=:sciml)
├─ internalnorm = real_norm  [default]
├─ alg = Tsit5  [default]
├─ reltol = 1.0e-8  [default]
├─ 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.

See also