Skip to content

Inspect a problem

Once a problem is built — via @def or the functional API — every part of it can be read back: dimensions, names, dynamics, costs, constraints, traits. This page is about reading a model, not solving it: for that, see Solve overview; for the indirect/PMP route, see Flows overview.

julia
using OptimalControl

A model prints as a readable summary:

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
ocp
Abstract definition:

    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

The (autonomous) optimal control problem is of the form:

    minimize  J(x, u) = ∫ f⁰(x(t), u(t)) dt, over [0, 1]

    subject to

        ẋ(t) = f(x(t), u(t)), t in [0, 1] a.e.,

        ϕ₋ ≤ ϕ(x(0), x(1)) ≤ ϕ₊, 

    where x(t) = (q(t), v(t)) ∈ R² and u(t) ∈ R.

To illustrate the accessors in the sections below, we use a richer problem: free final time, a variable, and several kinds of constraints.

julia
ocp = @def begin
    v = (w, tf)  R²,   variable
    s  [0, tf],        time
    q = (x, y)  R²,    state
    u  R,              control
    0 tf  2,         (1)
    u(s)  0,           (cons_u)
    x(s) + u(s)  10,   (cons_mixed)
    w == 0
    x(0) == -1
    y(0) - tf == 0,     (cons_bound)
    q(tf) == [0, 0]
(s) == [y(s)+w, u(s)]
    0.5∫( u(s)^2 )  min
end

Times

The time component defines the temporal domain of the optimal control problem.

Times model

Get the times model:

julia
times(ocp)  # returns the TimesModel struct containing time information
CTModels.Components.TimesModel{CTModels.Components.FixedTimeModel{Int64}, CTModels.Components.FreeTimeModel}(CTModels.Components.FixedTimeModel{Int64}(0, "0"), CTModels.Components.FreeTimeModel(2, "tf"), "s")

You can also access initial and final times separately:

julia
initial_time(ocp)  # returns the initial time value
0

For the final time, if it is free (part of the variable), you need to provide the variable value:

julia
v = [1, 2]  # example variable values: w=1, tf=2
final_time(ocp, v)  # returns tf value from variable
2

If you try to get the final time without providing the variable when it's free, an error occurs:

julia> final_time(ocp)  # error: tf is free, need variable
ERROR: PreconditionError

  Cannot get final time with this function

  Reason   This model type does not support direct final time access

  Context  final_time on AbstractModel
  Hint     Use final_time(ocp) on a Model with FixedTimeModel or use final_time(ocp, variable) for variable final time
└─

Time variable names

Get the names of the time variable and time bounds:

julia
time_name(ocp)  # returns "s" (the time variable name in this OCP)
"s"
julia
initial_time_name(ocp)  # returns "0" (initial time is fixed at 0)
"0"
julia
final_time_name(ocp)  # returns "tf" (final time is a variable)
"tf"

Time fixedness predicates

Check whether initial or final times are fixed or free:

julia
has_fixed_initial_time(ocp)  # true if t0 is fixed
true
julia
has_free_initial_time(ocp)  # true if t0 is free (part of variable)
false

Variant methods

Alternative methods with is_* prefix are also available and equivalent:

  • is_initial_time_fixed(ocp)has_fixed_initial_time(ocp)

  • is_initial_time_free(ocp)has_free_initial_time(ocp)

  • is_final_time_fixed(ocp)has_fixed_final_time(ocp)

  • is_final_time_free(ocp)has_free_final_time(ocp)

Similarly for final time:

julia
has_fixed_final_time(ocp)  # false (tf is free in this OCP)
false
julia
has_free_final_time(ocp)  # true (tf is part of variable v)
true

Autonomy

Check if the dynamics and Lagrange cost are autonomous (time-independent):

julia
is_autonomous(ocp)  # false if dynamics or cost depend on time
true

For more details on autonomy, see the Time dependence section below.

Summary table

MethodReturnsDescription
times(ocp)(Float64, Any)Time interval (t0, tf) or (t0, tf_name)
initial_time(ocp)Float64Initial time t0
final_time(ocp)Float64Final time tf (error if free)
final_time(ocp, v)Float64Final time tf from variable v
time_name(ocp)StringTime variable name
initial_time_name(ocp)StringInitial time name or value
final_time_name(ocp)StringFinal time name or value
has_fixed_initial_time(ocp)BoolTrue if t0 is fixed
has_free_initial_time(ocp)BoolTrue if t0 is free
has_fixed_final_time(ocp)BoolTrue if tf is fixed
has_free_final_time(ocp)BoolTrue if tf is free
is_autonomous(ocp)BoolTrue if time-independent

State

The state component represents the state variables of the optimal control problem.

State component information

Get the name, dimension, and component names of the state:

julia
state_name(ocp)  # returns "q" (the state variable name)
"q"
julia
state_dimension(ocp)  # returns 2 (dimension of state)
2
julia
state_components(ocp)  # returns ["x", "y"] (component names)
2-element Vector{String}:
 "x"
 "y"

Note

The component names are used when plotting the solution. See Plot.

State box constraints

Get the box constraints on the state (lower and upper bounds):

julia
state_constraints_box(ocp)  # returns box constraints if any
(Float64[], Int64[], Float64[], Symbol[], Vector{Symbol}[])

Tuple structure

The returned tuple has the structure (lb, indices, ub, labels, aliases) where:

  • lb: vector of lower bounds

  • indices: vector of component indices (1-based)

  • ub: vector of upper bounds

  • labels: vector of constraint labels

  • aliases: vector of vectors containing all labels that declared each component

Get the dimension of state box constraints:

julia
dim_state_constraints_box(ocp)  # returns number of box constraints on state
0

Summary table

MethodReturnsDescription
state_name(ocp)StringState variable name
state_dimension(ocp)IntState dimension
state_components(ocp)Vector{String}State component names
state_constraints_box(ocp)Box constraintsState box constraints
dim_state_constraints_box(ocp)IntNumber of state box constraints

Control

The control component represents the control variables of the optimal control problem.

Control component information

Get the name, dimension, and component names of the control:

julia
control_name(ocp)  # returns "u" (the control variable name)
"u"
julia
control_dimension(ocp)  # returns 1 (dimension of control)
1
julia
control_components(ocp)  # returns ["u"] (component names)
1-element Vector{String}:
 "u"

Control box constraints

Get the box constraints on the control:

julia
control_constraints_box(ocp)  # returns box constraints if any
([0.0], [1], [Inf], [:cons_u], [[:cons_u]])

Tuple structure

The returned tuple has the structure (lb, indices, ub, labels, aliases) where:

  • lb: vector of lower bounds

  • indices: vector of component indices (1-based)

  • ub: vector of upper bounds

  • labels: vector of constraint labels

  • aliases: vector of vectors containing all labels that declared each component

Get the dimension of control box constraints:

julia
dim_control_constraints_box(ocp)  # returns number of box constraints on control
1

Control presence

Check whether the problem has a control input:

julia
has_control(ocp)  # true if problem has a control input
true

Variant method

Summary table

MethodReturnsDescription
control_name(ocp)StringControl variable name
control_dimension(ocp)IntControl dimension
control_components(ocp)Vector{String}Control component names
control_constraints_box(ocp)Box constraintsControl box constraints
dim_control_constraints_box(ocp)IntNumber of control box constraints
has_control(ocp)BoolTrue if problem has a control input
is_control_free(ocp)BoolTrue if problem has no control (≡ !has_control)

Variable

The variable component represents the optimization variables (parameters) of the optimal control problem.

Variable component information

Get the name, dimension, and component names of the variable:

julia
variable_name(ocp)  # returns "v" (the variable name)
"v"
julia
variable_dimension(ocp)  # returns 2 (dimension of variable)
2
julia
variable_components(ocp)  # returns ["w", "tf"] (component names)
2-element Vector{String}:
 "w"
 "tf"

Variable box constraints

Get the box constraints on the variable:

julia
variable_constraints_box(ocp)  # returns box constraints if any
([0.0, 0.0], [1, 2], [0.0, 2.0], [Symbol("label##2440"), :eq1], [[Symbol("label##2440")], [:eq1]])

Tuple structure

The returned tuple has the structure (lb, indices, ub, labels, aliases) where:

  • lb: vector of lower bounds

  • indices: vector of component indices (1-based)

  • ub: vector of upper bounds

  • labels: vector of constraint labels

  • aliases: vector of vectors containing all labels that declared each component

Get the dimension of variable box constraints:

julia
dim_variable_constraints_box(ocp)  # returns number of box constraints on variable
2

Variable presence

Check whether the problem has optimization variables:

julia
has_variable(ocp)  # true if problem has optimization variables
true

Variant methods

  • is_variable(ocp)has_variable(ocp)

  • is_nonvariable(ocp)!has_variable(ocp)

Summary table

MethodReturnsDescription
variable_name(ocp)StringVariable name
variable_dimension(ocp)IntVariable dimension
variable_components(ocp)Vector{String}Variable component names
variable_constraints_box(ocp)Box constraintsVariable box constraints
dim_variable_constraints_box(ocp)IntNumber of variable box constraints
has_variable(ocp)BoolTrue if problem has optimization variables
is_variable(ocp)BoolAlias for has_variable
is_nonvariable(ocp)BoolTrue if problem has no variables (≡ !has_variable)

Dynamics

The dynamics component defines the differential equations governing the state evolution.

Dynamics function

The dynamics are stored as an in-place function of the form f!(dx, t, x, u, v):

julia
f! = dynamics(ocp)
s = 0.5  # time
q = [0.0, 1.0]  # state
u = 2.0  # control (scalar: control_dimension(ocp) == 1)
v = [1.0, 2.0]  # variable
dq = similar(q)
f!(dq, s, q, u, v)
dq  # returns the derivative q̇
2-element Vector{Float64}:
 2.0
 2.0

The first argument dx is mutated upon call and contains the state derivative. The other arguments are:

  • t: time

  • x: state

  • u: control

  • v: variable

Summary table

MethodReturnsDescription
dynamics(ocp)FunctionIn-place dynamics function f!(dx, t, x, u, v)

Objective

The objective component defines the cost function to minimize or maximize.

Criterion

The criterion indicates whether the problem is a minimization or maximization:

julia
criterion(ocp)  # returns :min or :max
:min

Objective form

The objective function can be in Mayer form, Lagrange form, or Bolza form (combination of both):

  • Mayer:  

  • Lagrange:   

  • Bolza:    

Check which form is present:

julia
has_mayer_cost(ocp)  # true if Mayer cost exists
false
julia
has_lagrange_cost(ocp)  # true if Lagrange cost exists
true

Variant methods

Alternative methods are also available:

  • is_mayer_cost_defined(ocp)has_mayer_cost(ocp)

  • is_lagrange_cost_defined(ocp)has_lagrange_cost(ocp)

Mayer cost

Get the Mayer cost function with signature g(x0, xf, v):

julia> g = mayer(ocp)  # error if no Mayer cost
ERROR: PreconditionError

  Cannot access Mayer cost

  Reason   This OCP has no Mayer objective defined

  Context  mayer accessor
  Hint     Define a Mayer objective using objective!(ocp, :min/:max, mayer=...) before accessing it
└─

Lagrange cost

Get the Lagrange cost function with signature f⁰(t, x, u, v):

julia
f⁰ = lagrange(ocp)
s = 0.5
q = [0.0, 1.0]
u = 2.0
v = [1.0, 2.0]
f⁰(s, q, u, v)  # returns the integrand value
2.0

Summary table

MethodReturnsDescription
criterion(ocp)Symbol:min or :max
has_mayer_cost(ocp)BoolTrue if Mayer cost exists
has_lagrange_cost(ocp)BoolTrue if Lagrange cost exists
mayer(ocp)FunctionMayer cost function g(x0, xf, v)
lagrange(ocp)FunctionLagrange cost function f⁰(t, x, u, v)

Constraints

The constraints component defines the constraints on the optimal control problem.

Individual constraints

Retrieve a specific constraint by its label using the constraint function. It returns a tuple (type, f, lb, ub):

julia
(type, f, lb, ub) = constraint(ocp, :eq1)
println("type: ", type)
x0 = [0, 1]
xf = [2, 3]
v  = [1, 4]
println("val: ", f(x0, xf, v))
println("lb: ", lb)
println("ub: ", ub)
type: variable
val: 4
lb: 0.0
ub: 2.0

The function signature depends on the constraint type:

  • For :boundary and :variable constraints: f(x0, xf, v)

  • For other constraints (:control, :state, :mixed): f(t, x, u, v)

Examples of different constraint types:

julia
(type, f, lb, ub) = constraint(ocp, :cons_bound)
println("type: ", type)
println("val: ", f(x0, xf, v))
type: boundary
val: -3.0
julia
(type, f, lb, ub) = constraint(ocp, :cons_u)
println("type: ", type)
s = 0.5
q = [1.0, 2.0]
u = 3.0
println("val: ", f(s, q, u, v))
type: control
val: 3.0
julia
(type, f, lb, ub) = constraint(ocp, :cons_mixed)
println("type: ", type)
println("val: ", f(s, q, u, v))
type: path
val: 4.0

All constraints

Get all constraints as a collection:

julia
constraints(ocp)  # returns all constraints
CTModels.Components.ConstraintsModel{Tuple{Vector{Float64}, Main.var"#fun##2436#97", Vector{Float64}, Vector{Symbol}}, Tuple{Vector{Float64}, CTModels.Building.CompositeConstraint{:boundary, Tuple{Main.var"#fun##2444#98", Main.var"#fun##2449#99", Main.var"#fun##2455#100"}}, Vector{Float64}, Vector{Symbol}}, Tuple{Vector{Float64}, Vector{Int64}, Vector{Float64}, Vector{Symbol}, Vector{Vector{Symbol}}}, Tuple{Vector{Float64}, Vector{Int64}, Vector{Float64}, Vector{Symbol}, Vector{Vector{Symbol}}}, Tuple{Vector{Float64}, Vector{Int64}, Vector{Float64}, Vector{Symbol}, Vector{Vector{Symbol}}}}(([-Inf], Main.var"#fun##2436#97"(), [10.0], [:cons_mixed]), ([-1.0, 0.0, 0.0, 0.0], CompositeConstraint{:boundary}(n=3, dims=[1, 1, 2]), [-1.0, 0.0, 0.0, 0.0], [Symbol("label##2442"), :cons_bound, Symbol("label##2453"), Symbol("label##2453")]), (Float64[], Int64[], Float64[], Symbol[], Vector{Symbol}[]), ([0.0], [1], [Inf], [:cons_u], [[:cons_u]]), ([0.0, 0.0], [1, 2], [0.0, 2.0], [Symbol("label##2440"), :eq1], [[Symbol("label##2440")], [:eq1]]))

Nonlinear constraints

Get nonlinear path and boundary constraints:

julia
path_constraints_nl(ocp)  # returns nonlinear path constraints
([-Inf], Main.var"#fun##2436#97"(), [10.0], [:cons_mixed])
julia
boundary_constraints_nl(ocp)  # returns nonlinear boundary constraints
([-1.0, 0.0, 0.0, 0.0], CompositeConstraint{:boundary}(n=3, dims=[1, 1, 2]), [-1.0, 0.0, 0.0, 0.0], [Symbol("label##2442"), :cons_bound, Symbol("label##2453"), Symbol("label##2453")])

Tuple structure

The returned tuples have the structure (lb, f!, ub, labels) where:

  • lb: vector of lower bounds

  • f!: constraint function (in-place)

  • ub: vector of upper bounds

  • labels: vector of constraint labels

The constraint functions have the following signatures:

  • Path constraints: f!(val, t, x, u, v) where val is mutated

  • Boundary constraints: f!(val, x0, xf, v) where val is mutated

Get the dimensions of nonlinear constraints:

julia
dim_path_constraints_nl(ocp)  # number of nonlinear path constraints
1
julia
dim_boundary_constraints_nl(ocp)  # number of nonlinear boundary constraints
4

Note

To get the dual variable (or Lagrange multiplier) associated to a constraint, use the dual method on a solution — see Solution object.

Summary table

MethodReturnsDescription
constraint(ocp, label)(Symbol, Function, Real, Real)Get constraint by label
constraints(ocp)CollectionAll constraints
path_constraints_nl(ocp)ConstraintsNonlinear path constraints
boundary_constraints_nl(ocp)ConstraintsNonlinear boundary constraints
dim_path_constraints_nl(ocp)IntNumber of nonlinear path constraints
dim_boundary_constraints_nl(ocp)IntNumber of nonlinear boundary constraints

Problem definition

Get the problem definition as a string:

julia
definition(ocp)  # returns the OCP definition as AbstractDefinition
Abstract definition:

    v = ((w, tf) ∈ R², variable)
    s ∈ [0, tf], time
    q = ((x, y) ∈ R², state)
    u ∈ R, control
    0 ≤ tf ≤ 2, 1
    u(s) ≥ 0, cons_u
    x(s) + u(s) ≤ 10, cons_mixed
    w == 0
    x(0) == -1
    y(0) - tf == 0, cons_bound
    q(tf) == [0, 0]
    q̇(s) == [y(s) + w, u(s)]
    0.5 * ∫(u(s) ^ 2) → min

To extract the expression from the definition, use:

julia
expr = expression(ocp)  # returns the Expr from the definition

Note

The definition is optional and can be EmptyDefinition — this is what the functional API produces. Use has_abstract_definition(ocp) to check if a definition is present.

Definition presence

Check whether the problem carries an abstract definition:

julia
has_abstract_definition(ocp)  # true if definition is present (not EmptyDefinition)
true

Variant method

  • is_abstractly_defined(ocp)has_abstract_definition(ocp)

Time dependence

Optimal control problems can be autonomous or non-autonomous. In an autonomous problem, neither the dynamics nor the Lagrange cost explicitly depends on the time variable.

The following problem is autonomous.

julia
ocp = @def begin
    t  [ 0, 1 ], time
    x  R, state
    u  R, control
(t)  == u(t)                       # no explicit dependence on t
    x(1) + 0.5∫( u(t)^2 )  min         # no explicit dependence on t
end
is_autonomous(ocp)
true

The following problem is non-autonomous since the dynamics depends on t.

julia
ocp = @def begin
    t  [ 0, 1 ], time
    x  R, state
    u  R, control
(t)  == u(t) + t                   # explicit dependence on t
    x(1) + 0.5∫( u(t)^2 )  min
end
is_autonomous(ocp)
false

Finally, this last problem is non-autonomous because the Lagrange part of the cost depends on t.

julia
ocp = @def begin
    t  [ 0, 1 ], time
    x  R, state
    u  R, control
(t)  == u(t)
    x(1) + 0.5∫( t + u(t)^2 )  min     # explicit dependence on t
end
is_autonomous(ocp)
false

The variant predicate is_nonautonomous is also available and returns the opposite of is_autonomous:

julia
is_nonautonomous(ocp)  # true if dynamics or cost depend on time
true

Variant method

  • is_nonautonomous(ocp)!is_autonomous(ocp)

API trap: time is gone

time(ocp) is not the time accessor — time is Base.time (wall-clock time), extended but not exported. The accessor is times(ocp), shown above. Calling time(ocp) throws a migration error pointing here — see Migrating to v2.1.

See also