Differential geometry tools
Optimal control theory relies on differential geometry tools to analyze Hamiltonian systems, compute singular controls, study controllability, and more. This page introduces the main operators available in OptimalControl.jl: Hamiltonian lift, Lie derivatives, Poisson brackets, Lie brackets, and partial time derivatives.
Type qualification
Types like Hamiltonian, HamiltonianLift, VectorField, and HamiltonianVectorField are not exported by OptimalControl.jl. You must qualify them with OptimalControl. when using them (e.g., OptimalControl.VectorField). Functions and operators (Lift, ⋅, Lie, Poisson, @Lie, ∂ₜ) are exported and can be used directly.
First, import the package:
using OptimalControlHamiltonian lift
Given a vector field
From plain Julia functions
The simplest way to compute a Hamiltonian lift is from a plain Julia function. By default, the function is treated as autonomous (time-independent) and non-variable (no extra parameter):
# Define a vector field as a Julia function
X(x) = [x[2], -x[1]]
# Compute its Hamiltonian lift
H = Lift(X)
# Evaluate at a point (x, p)
x = [1, 2]
p = [3, 4]
H(x, p)2The result is
From VectorField type
You can also use the OptimalControl.VectorField type, which allows more control over the function's properties:
# Wrap in VectorField (autonomous, non-variable by default)
X = OptimalControl.VectorField(x -> [x[2], -x[1]])
H = Lift(X)
# This returns a HamiltonianLift object
H([1, 2], [3, 4])2Non-autonomous case
For time-dependent vector fields, use autonomous=false:
# Non-autonomous vector field: X(t, x) = [t*x[2], -x[1]]
X(t, x) = [t * x[2], -x[1]]
H = Lift(X; autonomous=false)
# Signature is now H(t, x, p)
H(2, [1, 2], [3, 4])8Variable case
For vector fields depending on an additional parameter variable=true:
# Variable vector field: X(x, v) = [x[2] + v, -x[1]]
X(x, v) = [x[2] + v, -x[1]]
H = Lift(X; variable=true)
# Signature is now H(x, p, v)
H([1, 2], [3, 4], 1)5Lie derivative
The Lie derivative of a function
This represents the directional derivative of
From plain Julia functions
When using plain Julia functions, they are treated as autonomous and non-variable:
# Vector field and scalar function
X(x) = [x[2], -x[1]]
f(x) = x[1]^2 + x[2]^2
# Lie derivative (using dot operator)
Xf = X ⋅ f
# Evaluate at a point
Xf([1, 2])0For the harmonic oscillator with
which confirms that energy is conserved along trajectories.
From VectorField type
# Using VectorField type
X = OptimalControl.VectorField(x -> [x[2], -x[1]])
g(x) = x[1]^2 + x[2]^2
# Lie derivative
Xg = X ⋅ g
Xg([1, 2])0Alternative syntax
The Lie function is equivalent to the ⋅ operator:
# These are equivalent
Xg1 = X ⋅ g
Xg2 = Lie(X, g)
Xg1([1, 2]) == Xg2([1, 2])trueWith keyword arguments
For non-autonomous or variable cases, use the Lie function with keyword arguments:
# Non-autonomous case
X(t, x) = [t + x[2], -x[1]]
f(t, x) = t + x[1]^2 + x[2]^2
Xf = Lie(X, f; autonomous=false)
Xf(1, [1, 2])2# Variable case
X(x, v) = [x[2] + v, -x[1]]
f(x, v) = x[1]^2 + x[2]^2 + v
Xf = Lie(X, f; variable=true)
Xf([1, 2], 1)2With VectorField type
You can also create the VectorField explicitly with the keywords, then use it without keywords in the Lie function:
# Non-autonomous VectorField created with keywords
X = OptimalControl.VectorField((t, x) -> [t + x[2], -x[1]]; autonomous=false)
f(t, x) = t + x[1]^2 + x[2]^2
# No keywords needed here - the VectorField already knows its properties
Xf = Lie(X, f)
Xf(1, [1, 2])2# Variable VectorField created with keywords
X = OptimalControl.VectorField((x, v) -> [x[2] + v, -x[1]]; variable=true)
f(x, v) = x[1]^2 + x[2]^2 + v
# No keywords needed here
Xf = Lie(X, f)
Xf([1, 2], 1)2Poisson bracket
For two functions
Properties
The Poisson bracket satisfies:
Bilinearity:
for scalarsAntisymmetry:
Leibniz rule:
Jacobi identity:
From plain Julia functions
# Define two Hamiltonian functions
f(x, p) = p[1] * x[2] + p[2] * x[1]
g(x, p) = x[1]^2 + p[2]^2
# Compute the Poisson bracket
H = Poisson(f, g)
# Evaluate at a point
x = [1, 2]
p = [3, 4]
H(x, p)-20Verify antisymmetry
Hfg = Poisson(f, g)
Hgf = Poisson(g, f)
println("Poisson(f, g) = ", Hfg(x, p))
println("Poisson(g, f) = ", Hgf(x, p))
println("Sum = ", Hfg(x, p) + Hgf(x, p))Poisson(f, g) = -20
Poisson(g, f) = 20
Sum = 0From Hamiltonian type
# Wrap in Hamiltonian type
F = OptimalControl.Hamiltonian(f)
G = OptimalControl.Hamiltonian(g)
H = Poisson(F, G)
H(x, p)-20With keyword arguments
# Non-autonomous case
f(t, x, p) = t + p[1] * x[2] + p[2] * x[1]
g(t, x, p) = t^2 + x[1]^2 + p[2]^2
H = Poisson(f, g; autonomous=false)
H(1, [1, 2], [3, 4])-20With Hamiltonian type and keywords
You can also create Hamiltonian objects explicitly with keywords, then use them without keywords in the Poisson function. Important: both Hamiltonians must have the same time and variable dependencies:
# Non-autonomous Hamiltonians created with keywords
f(t, x, p) = t + p[1] * x[2] + p[2] * x[1]
g(t, x, p) = t^2 + x[1]^2 + p[2]^2
F = OptimalControl.Hamiltonian(f; autonomous=false)
G = OptimalControl.Hamiltonian(g; autonomous=false)
# No keywords needed here - both Hamiltonians are already non-autonomous
H = Poisson(F, G)
H(1, [1, 2], [3, 4])-20# Variable Hamiltonians created with keywords
f(x, p, v) = x[1]^2 + p[2]^2 + v
g(x, p, v) = x[2]^2 + p[1]^2 + 2*v
F = OptimalControl.Hamiltonian(f; variable=true)
G = OptimalControl.Hamiltonian(g; variable=true)
# Both are variable, so the Poisson bracket is also variable
H = Poisson(F, G)
H([1, 2], [3, 4], 1)20Relation to Hamiltonian vector fields
The Poisson bracket is closely related to the Lie derivative. If
This means the Poisson bracket of
Poisson bracket of Hamiltonian lifts
When computing the Poisson bracket of two Hamiltonian lifts, the result is the Hamiltonian lift of the Lie bracket of the underlying vector fields:
# Two vector fields
X(x) = [x[1]^2, x[2]^2]
Y(x) = [x[2], -x[1]]
# Their Hamiltonian lifts
HX = Lift(X)
HY = Lift(Y)
# Poisson bracket of lifts
H = Poisson(HX, HY)
H([1, 2], [3, 4])12This satisfies:
Lie bracket of vector fields
For two vector fields
where
From VectorField type
# Define two vector fields
X = OptimalControl.VectorField(x -> [x[2], -x[1]])
Y = OptimalControl.VectorField(x -> [x[1], x[2]])
# Compute the Lie bracket
Z = Lie(X, Y)
# Evaluate at a point
Z([1, 2])2-element Vector{Int64}:
0
0Relation to Poisson brackets
If
Let's verify this numerically:
# Hamiltonian lifts
HX = Lift(x -> X(x))
HY = Lift(x -> Y(x))
# Poisson bracket of the lifts
HXY = Poisson(HX, HY)
# Lift of the Lie bracket
HZ = Lift(x -> Z(x))
# Compare at a point
x = [1, 2]
p = [3, 4]
println("Poisson bracket: ", HXY(x, p))
println("Lift of Lie bracket: ", HZ(x, p))Poisson bracket: 0
Lift of Lie bracket: 0The @Lie macro
The @Lie macro provides a convenient syntax for computing Lie brackets (for vector fields) and Poisson brackets (for Hamiltonians).
Important distinction
Square brackets
[...]denote Lie brackets and work with:VectorFieldobjectsPlain Julia functions (automatically wrapped as
VectorField)
Curly braces
{...}denote Poisson brackets and work with:Plain Julia functions (automatically wrapped as
Hamiltonian)Hamiltonianobjects
When using only plain functions (no VectorField or Hamiltonian objects), specify autonomous and/or variable keywords as needed to match your function signature. Keywords are optional - if not specified, they use the default values (autonomous=true, variable=false). If you mix plain functions with VectorField or Hamiltonian objects, the keywords are inferred from the VectorField or Hamiltonian objects.
Lie brackets with VectorField
# Define vector fields
F1 = OptimalControl.VectorField(x -> [0, -x[3], x[2]])
F2 = OptimalControl.VectorField(x -> [x[3], 0, -x[1]])
# Compute Lie bracket using macro
F12 = @Lie [F1, F2]
# Evaluate
F12([1, 2, 3])3-element Vector{Int64}:
2
-1
0Nested Lie brackets
F3 = OptimalControl.VectorField(x -> [x[1], x[2], x[3]])
F123 = @Lie [[F1, F2], F3]
F123([1, 2, 3])3-element Vector{Int64}:
0
0
0Lie brackets with plain Julia functions
You can also use plain Julia functions directly with the @Lie macro. The functions will be automatically wrapped in VectorField objects:
# Define plain Julia functions
X(x) = [x[2], -x[1]]
Y(x) = [x[1], x[2]]
# Compute Lie bracket using macro with plain functions
Z = @Lie [X, Y]
# Evaluate
Z([1, 2])2-element Vector{Int64}:
0
0With keyword arguments for plain functions
For non-autonomous or variable cases, specify the keywords:
# Non-autonomous plain functions
X(t, x) = [t + x[2], -x[1]]
Y(t, x) = [x[1], t*x[2]]
# Use autonomous=false keyword
Z = @Lie [X, Y] autonomous=false
Z(1, [1, 2])2-element Vector{Int64}:
1
0# Variable plain functions
X(x, v) = [x[2] + v, -x[1]]
Y(x, v) = [x[1], x[2] + v]
# Use variable=true keyword
Z = @Lie [X, Y] variable=true
Z([1, 2], 1)2-element Vector{Int64}:
0
0Nested brackets with plain functions
X(x) = [0, -x[3], x[2]]
Y(x) = [x[3], 0, -x[1]]
Z(x) = [x[1], x[2], x[3]]
# Nested Lie brackets
nested = @Lie [[X, Y], Z]
nested([1, 2, 3])3-element Vector{Int64}:
0
0
0Plain functions vs VectorField
Using plain functions with @Lie [X, Y] is convenient for quick computations. However, if you need to reuse the same vector field multiple times or want explicit control over the autonomy/variability, consider creating VectorField objects explicitly:
# Explicit VectorField (keywords at creation)
X = OptimalControl.VectorField((t, x) -> [t + x[2], -x[1]]; autonomous=false)
Y = OptimalControl.VectorField((t, x) -> [x[1], t*x[2]]; autonomous=false)
Z = @Lie [X, Y] # No keywords needed
# Plain functions (keywords at macro call)
X(t, x) = [t + x[2], -x[1]]
Y(t, x) = [x[1], t*x[2]]
Z = @Lie [X, Y] autonomous=falsePoisson brackets from plain functions
For Hamiltonian functions (plain Julia functions), use curly braces {_, _}:
# Define Hamiltonian functions
H0(x, p) = p[1] * x[2] + p[2] * (-x[1])
H1(x, p) = p[2]
# Compute Poisson bracket
H01 = @Lie {H0, H1}
# Evaluate
H01([1, 2], [3, 4])-3Iterated Poisson brackets
The macro is particularly useful for computing iterated brackets, which appear in singular control analysis:
# First-order bracket
H01 = @Lie {H0, H1}
# Second-order brackets
H001 = @Lie {H0, H01}
H101 = @Lie {H1, H01}
# Evaluate
x = [1, 2]
p = [3, 4]
println("H01(x, p) = ", H01(x, p))
println("H001(x, p) = ", H001(x, p))
println("H101(x, p) = ", H101(x, p))H01(x, p) = -3
H001(x, p) = -4
H101(x, p) = 0These iterated brackets are used to compute singular controls. For a pseudo-Hamiltonian of the form
provided
With keyword arguments
For non-autonomous functions, specify autonomous=false:
# Non-autonomous Hamiltonians
H0(t, x, p) = t + p[1] * x[2] + p[2] * (-x[1])
H1(t, x, p) = p[2]
# Poisson bracket with keyword
H01 = @Lie {H0, H1} autonomous=false
# Evaluate
H01(1, [1, 2], [3, 4])-3Poisson brackets from Hamiltonian type
# Using Hamiltonian type
H1 = OptimalControl.Hamiltonian((x, p) -> x[1]^2 + p[2]^2)
H2 = OptimalControl.Hamiltonian((x, p) -> x[2]^2 + p[1]^2)
# Macro works with Hamiltonian objects too
H12 = @Lie {H1, H2}
H12([1, 1], [3, 2])-4Partial time derivative
For non-autonomous functions ∂ₜ operator:
Basic usage
# Define a time-dependent function
f(t, x) = t * x
# Compute partial time derivative
df = ∂ₜ(f)
# Evaluate
df(0, 8)8df(2, 3)3More complex example
# Function with multiple arguments
g(t, x, p) = t^2 + x[1] * p[1] + x[2] * p[2]
# Partial derivative
dg = ∂ₜ(g)
# At t=3, ∂g/∂t = 2t = 6
dg(3, [1, 2], [4, 5])6Relation to total time derivative
For a non-autonomous Hamiltonian
This is the sum of:
The partial time derivative
(explicit time dependence) The Poisson bracket
(evolution along the flow)
This relation is fundamental in non-autonomous optimal control theory.
Summary
| Function/Operator | Mathematical notation | Julia syntax |
|---|---|---|
| Hamiltonian lift | H = Lift(X) | |
| Lie derivative | X ⋅ f or Lie(X, f) | |
| Poisson bracket | Poisson(f, g) or @Lie {f, g} | |
| Lie bracket | Lie(X, Y) or @Lie [X, Y] | |
| Partial time derivative | ∂ₜ(f) |
See also
Compute flows from Hamiltonians and others — Using flows with Hamiltonian vector fields
Singular control example — Application to computing singular controls
Goddard tutorial — Complex example with bang, singular, and boundary arcs