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 OptimalControl
<< @example-block not executed in draft mode >>

Hamiltonian lift

Given a vector field $X: \mathbb{R}^n \to \mathbb{R}^n$, its Hamiltonian lift is the function $H_X: \mathbb{R}^n \times (\mathbb{R}^n)^* \to \mathbb{R}$ defined by

\[H_X(x, p) = \langle p, X(x) \rangle = \sum_{i=1}^n p_i X_i(x).\]

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)
<< @example-block not executed in draft mode >>

The result is $H(x, p) = p_1 x_2 + p_2 (-x_1) = 3 \times 2 + 4 \times (-1) = 2$.

From VectorField type

You can also use the OptimalControl.VectorField type, which allows more control over the function's properties:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Non-autonomous case

For time-dependent vector fields, use autonomous=false:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Variable case

For vector fields depending on an additional parameter $v$, use variable=true:

using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

Lie derivative

The Lie derivative of a function $f: \mathbb{R}^n \to \mathbb{R}$ along a vector field $X$ is defined by

\[(X \cdot f)(x) = f'(x) \cdot X(x) = \sum_{i=1}^n \frac{\partial f}{\partial x_i}(x) X_i(x).\]

This represents the directional derivative of $f$ along $X$.

From plain Julia functions

When using plain Julia functions, they are treated as autonomous and non-variable:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

For the harmonic oscillator with $X(x) = (x_2, -x_1)$ and energy $f(x) = x_1^2 + x_2^2$:

\[(X \cdot f)(x) = 2x_1 x_2 + 2x_2(-x_1) = 0,\]

which confirms that energy is conserved along trajectories.

From VectorField type

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Alternative 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])
<< @example-block not executed in draft mode >>

With keyword arguments

For non-autonomous or variable cases, use the Lie function with keyword arguments:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>
using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

With VectorField type

You can also create the VectorField explicitly with the keywords, then use it without keywords in the Lie function:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>
using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

Poisson bracket

For two functions $f, g: \mathbb{R}^n \times (\mathbb{R}^n)^* \to \mathbb{R}$, the Poisson bracket is defined by

\[\{f, g\}(x, p) = \sum_{i=1}^n \left( \frac{\partial f}{\partial p_i} \frac{\partial g}{\partial x_i} - \frac{\partial f}{\partial x_i} \frac{\partial g}{\partial p_i} \right).\]

Properties

The Poisson bracket satisfies:

  • Bilinearity: $\{af + bg, h\} = a\{f, h\} + b\{g, h\}$ for scalars $a, b$
  • Antisymmetry: $\{f, g\} = -\{g, f\}$
  • Leibniz rule: $\{fg, h\} = f\{g, h\} + g\{f, h\}$
  • Jacobi identity: $\{\{f, g\}, h\} + \{\{h, f\}, g\} + \{\{g, h\}, f\} = 0$

From plain Julia functions

using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

Verify 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))
<< @example-block not executed in draft mode >>

From Hamiltonian type

# Wrap in Hamiltonian type
F = OptimalControl.Hamiltonian(f)
G = OptimalControl.Hamiltonian(g)

H = Poisson(F, G)
H(x, p)
<< @example-block not executed in draft mode >>

With keyword arguments

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

With 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:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>
using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

Relation to Hamiltonian vector fields

The Poisson bracket is closely related to the Lie derivative. If $\vec{H} = (\nabla_p H, -\nabla_x H)$ denotes the Hamiltonian vector field associated to $H$, then

\[\{H, G\} = \vec{H} \cdot G.\]

This means the Poisson bracket of $H$ and $G$ equals the Lie derivative of $G$ along the Hamiltonian vector field of $H$.

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:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

This satisfies: $\{H_X, H_Y\} = H_{[X,Y]}$ where $[X,Y]$ is the Lie bracket of vector fields (see next section).

Lie bracket of vector fields

For two vector fields $X, Y: \mathbb{R}^n \to \mathbb{R}^n$, the Lie bracket is the vector field $[X, Y]$ defined by

\[[X, Y](x) = Y'(x) \cdot X(x) - X'(x) \cdot Y(x),\]

where $X'(x)$ denotes the Jacobian matrix of $X$ at $x$.

From VectorField type

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Relation to Poisson brackets

If $H_X = \text{Lift}(X)$ and $H_Y = \text{Lift}(Y)$ are the Hamiltonian lifts, then:

\[\{H_X, H_Y\} = H_{[X,Y]}.\]

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))
<< @example-block not executed in draft mode >>

The @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:
    • VectorField objects
    • Plain Julia functions (automatically wrapped as VectorField)
  • Curly braces {...} denote Poisson brackets and work with:
    • Plain Julia functions (automatically wrapped as Hamiltonian)
    • Hamiltonian objects

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

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Nested Lie brackets

F3 = OptimalControl.VectorField(x -> [x[1], x[2], x[3]])
F123 = @Lie [[F1, F2], F3]
F123([1, 2, 3])
<< @example-block not executed in draft mode >>

Lie 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:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

With keyword arguments for plain functions

For non-autonomous or variable cases, specify the keywords:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>
using OptimalControl # hide
# 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)
<< @example-block not executed in draft mode >>

Nested brackets with plain functions

using OptimalControl # hide
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])
<< @example-block not executed in draft mode >>
Plain 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=false

Poisson brackets from plain functions

For Hamiltonian functions (plain Julia functions), use curly braces {_, _}:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Iterated 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))
<< @example-block not executed in draft mode >>

These iterated brackets are used to compute singular controls. For a pseudo-Hamiltonian of the form $H = H_0 + u H_1$, if the switching function $H_1$ vanishes on an interval (singular arc), the control is given by

\[u_s = -\frac{H_{001}}{H_{101}},\]

provided $H_{101} \neq 0$. See the singular control example for a complete application.

With keyword arguments

For non-autonomous functions, specify autonomous=false:

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Poisson brackets from Hamiltonian type

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Partial time derivative

For non-autonomous functions $f(t, x, \ldots)$, the partial derivative with respect to time is computed using the ∂ₜ operator:

\[(\partial_t f)(t, x, \ldots) = \frac{\partial f}{\partial t}(t, x, \ldots).\]

Basic usage

using OptimalControl # hide
# Define a time-dependent function
f(t, x) = t * x

# Compute partial time derivative
df = ∂ₜ(f)

# Evaluate
df(0, 8)
<< @example-block not executed in draft mode >>
df(2, 3)
<< @example-block not executed in draft mode >>

More complex example

using OptimalControl # hide
# 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])
<< @example-block not executed in draft mode >>

Relation to total time derivative

For a non-autonomous Hamiltonian $H(t, x, p)$ and a function $G(t, x, p)$, the total time derivative along the Hamiltonian flow is:

\[\frac{\mathrm{d}}{\mathrm{d}t} G(t, x(t), p(t)) = \partial_t G + \{H, G\}.\]

This is the sum of:

  • The partial time derivative $\partial_t G$ (explicit time dependence)
  • The Poisson bracket $\{H, G\}$ (evolution along the flow)

This relation is fundamental in non-autonomous optimal control theory.

Summary

Function/OperatorMathematical notationJulia syntax
Hamiltonian lift$H_X(x,p) = \langle p, X(x) \rangle$H = Lift(X)
Lie derivative$(X \cdot f)(x) = f'(x) \cdot X(x)$X ⋅ f or Lie(X, f)
Poisson bracket$\{f,g\}(x,p) = \langle \nabla_p f, \nabla_x g \rangle - \langle \nabla_x f, \nabla_p g \rangle$Poisson(f, g) or @Lie {f, g}
Lie bracket$[X,Y](x) = Y'(x) X(x) - X'(x) Y(x)$Lie(X, Y) or @Lie [X, Y]
Partial time derivative$\partial_t f(t, x, \ldots)$∂ₜ(f)

See also