Skip to content

Accessors ​

A flow keeps what it was built from: its Hamiltonian, its vector field, the pseudo-Hamiltonian and the control law, and its integrator. This page shows how to read them back, and on which flows.

julia
using OptimalControl
using OrdinaryDiffEqTsit5

We take the flow of the energy-minimal double integrator, built from the problem and the control law   (see From an OCP), and the point   ,  : the start of the optimal extremal.

julia
ocp = @def begin
    t ∈ [0, 1], time
    x = (q, v) ∈ R², state
    u ∈ R, control
    x(0) == [-1, 0]
    x(1) == [0, 0]
    ẋ(t) == [v(t), u(t)]
    0.5∫(u(t)^2) → min
end

f = Flow(ocp, (x, p) -> p[2])
x, p = [-1, 0], [12, 6]

The Hamiltonian ​

hamiltonian(f) returns the maximised Hamiltonian    :

julia
H = hamiltonian(f)
H(x, p)   # 12 × 0 + 6²/2
18.0

The Hamiltonian vector field ​

hamiltonian_vector_field(f) returns   , as the pair   :

julia
Hv = hamiltonian_vector_field(f)
Hv(x, p)
([0.0, 6.0], [-0.0, -12.0])

The pseudo-Hamiltonian and the control law ​

pseudo_hamiltonian(f) returns    , and control_law(f) the law you passed:

julia
Hp = pseudo_hamiltonian(f)
u = control_law(f)
Hp(x, p, u(x, p)), u(x, p)
(18.0, 6)

These functions take the short form of their arguments, as above, or the full form with the time and the variable: H(t, x, p, v), Hv(t, x, p, v), Hp(t, x, p, u, v), u(t, x, p, v), with v = Float64[] for a problem without a variable.

Gradients ​

get_hamiltonian_gradient(f) returns the gradient of with respect to and , and get_variable_gradient(f) its gradient with respect to the variable. get_pseudo_hamiltonian_gradient and get_pseudo_variable_gradient do the same for , before the law is substituted. They only take the full form (t, x, p, v) (CTFlows#438):

julia
∇H = get_hamiltonian_gradient(f)
∇H(0.0, x, p, Float64[])   # (∇ₓH, ∇ₚH) = ((0, 12), (0, 6))
([0.0, 12.0], [0.0, 6.0])

Without a flow ​

hamiltonian_vector_field also applies to a Hamiltonian, without building a flow:

julia
Hv2 = hamiltonian_vector_field(Hamiltonian((x, p) -> p[1] * x[2] + p[2]^2 / 2))
Hv2(x, p)
([0.0, 6.0], [-0.0, -12.0])

On which flow ​

What a flow can give back depends on what it was built from:

Flow built fromhamiltonianhamiltonian_vector_fieldpseudo_hamiltonian, control_lawvector_field
ocp, law with a law u(x, p), or PseudoHamiltonian(Hp), law✓✓✓✓
Hamiltonian(H)✓✓✗✓
HamiltonianVectorField(Hv)✗✓✗✓
VectorField(f), ControlledVectorField(f), law, ocp, OpenLoop(…)✗✗✗✓

On a flow of the state, vector_field returns the vector field that is integrated:

julia
Fx = vector_field(Flow(VectorField(x -> -x)))
Fx(2.0)
-2.0

An accessor that does not apply raises an error. For a Hamiltonian flow, the error says what the flow holds and what to use instead:

julia
julia> hamiltonian(Flow(HamiltonianVectorField((x, p) -> ([x[2], p[2]], [0, -p[1]]))))
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
└─

On a flow of the state, it is a MethodError for now (CTFlows#438).

The system and the integrator ​

A flow is made of a system, the mathematical object it integrates, and an integrator. Both are reached with qualified names, since they are not exported:

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.

The options of the integrator are those of Flows overview, given when the flow is built.

See also ​