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.
using OptimalControlA model prints as a readable summary:
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
ocpAbstract 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.
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]
q̇(s) == [y(s)+w, u(s)]
0.5∫( u(s)^2 ) → min
endTimes
The time component defines the temporal domain of the optimal control problem.
Times model
Get the times model:
times(ocp) # returns the TimesModel struct containing time informationCTModels.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:
initial_time(ocp) # returns the initial time value0For the final time, if it is free (part of the variable), you need to provide the variable value:
v = [1, 2] # example variable values: w=1, tf=2
final_time(ocp, v) # returns tf value from variable2If 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:
time_name(ocp) # returns "s" (the time variable name in this OCP)"s"initial_time_name(ocp) # returns "0" (initial time is fixed at 0)"0"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:
has_fixed_initial_time(ocp) # true if t0 is fixedtruehas_free_initial_time(ocp) # true if t0 is free (part of variable)falseVariant 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:
has_fixed_final_time(ocp) # false (tf is free in this OCP)falsehas_free_final_time(ocp) # true (tf is part of variable v)trueAutonomy
Check if the dynamics and Lagrange cost are autonomous (time-independent):
is_autonomous(ocp) # false if dynamics or cost depend on timetrueFor more details on autonomy, see the Time dependence section below.
Summary table
| Method | Returns | Description |
|---|---|---|
times(ocp) | (Float64, Any) | Time interval (t0, tf) or (t0, tf_name) |
initial_time(ocp) | Float64 | Initial time t0 |
final_time(ocp) | Float64 | Final time tf (error if free) |
final_time(ocp, v) | Float64 | Final time tf from variable v |
time_name(ocp) | String | Time variable name |
initial_time_name(ocp) | String | Initial time name or value |
final_time_name(ocp) | String | Final time name or value |
has_fixed_initial_time(ocp) | Bool | True if t0 is fixed |
has_free_initial_time(ocp) | Bool | True if t0 is free |
has_fixed_final_time(ocp) | Bool | True if tf is fixed |
has_free_final_time(ocp) | Bool | True if tf is free |
is_autonomous(ocp) | Bool | True 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:
state_name(ocp) # returns "q" (the state variable name)"q"state_dimension(ocp) # returns 2 (dimension of state)2state_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):
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 boundsindices: vector of component indices (1-based)ub: vector of upper boundslabels: vector of constraint labelsaliases: vector of vectors containing all labels that declared each component
Get the dimension of state box constraints:
dim_state_constraints_box(ocp) # returns number of box constraints on state0Summary table
| Method | Returns | Description |
|---|---|---|
state_name(ocp) | String | State variable name |
state_dimension(ocp) | Int | State dimension |
state_components(ocp) | Vector{String} | State component names |
state_constraints_box(ocp) | Box constraints | State box constraints |
dim_state_constraints_box(ocp) | Int | Number 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:
control_name(ocp) # returns "u" (the control variable name)"u"control_dimension(ocp) # returns 1 (dimension of control)1control_components(ocp) # returns ["u"] (component names)1-element Vector{String}:
"u"Control box constraints
Get the box constraints on the control:
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 boundsindices: vector of component indices (1-based)ub: vector of upper boundslabels: vector of constraint labelsaliases: vector of vectors containing all labels that declared each component
Get the dimension of control box constraints:
dim_control_constraints_box(ocp) # returns number of box constraints on control1Control presence
Check whether the problem has a control input:
has_control(ocp) # true if problem has a control inputtrueVariant method
is_control_free(ocp)≡!has_control(ocp). See Problems without a control.
Summary table
| Method | Returns | Description |
|---|---|---|
control_name(ocp) | String | Control variable name |
control_dimension(ocp) | Int | Control dimension |
control_components(ocp) | Vector{String} | Control component names |
control_constraints_box(ocp) | Box constraints | Control box constraints |
dim_control_constraints_box(ocp) | Int | Number of control box constraints |
has_control(ocp) | Bool | True if problem has a control input |
is_control_free(ocp) | Bool | True 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:
variable_name(ocp) # returns "v" (the variable name)"v"variable_dimension(ocp) # returns 2 (dimension of variable)2variable_components(ocp) # returns ["w", "tf"] (component names)2-element Vector{String}:
"w"
"tf"Variable box constraints
Get the box constraints on the variable:
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 boundsindices: vector of component indices (1-based)ub: vector of upper boundslabels: vector of constraint labelsaliases: vector of vectors containing all labels that declared each component
Get the dimension of variable box constraints:
dim_variable_constraints_box(ocp) # returns number of box constraints on variable2Variable presence
Check whether the problem has optimization variables:
has_variable(ocp) # true if problem has optimization variablestrueVariant methods
is_variable(ocp)≡has_variable(ocp)is_nonvariable(ocp)≡!has_variable(ocp)
Summary table
| Method | Returns | Description |
|---|---|---|
variable_name(ocp) | String | Variable name |
variable_dimension(ocp) | Int | Variable dimension |
variable_components(ocp) | Vector{String} | Variable component names |
variable_constraints_box(ocp) | Box constraints | Variable box constraints |
dim_variable_constraints_box(ocp) | Int | Number of variable box constraints |
has_variable(ocp) | Bool | True if problem has optimization variables |
is_variable(ocp) | Bool | Alias for has_variable |
is_nonvariable(ocp) | Bool | True 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):
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.0The first argument dx is mutated upon call and contains the state derivative. The other arguments are:
t: timex: stateu: controlv: variable
Summary table
| Method | Returns | Description |
|---|---|---|
dynamics(ocp) | Function | In-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:
criterion(ocp) # returns :min or :max:minObjective 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:
has_mayer_cost(ocp) # true if Mayer cost existsfalsehas_lagrange_cost(ocp) # true if Lagrange cost existstrueVariant 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):
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 value2.0Summary table
| Method | Returns | Description |
|---|---|---|
criterion(ocp) | Symbol | :min or :max |
has_mayer_cost(ocp) | Bool | True if Mayer cost exists |
has_lagrange_cost(ocp) | Bool | True if Lagrange cost exists |
mayer(ocp) | Function | Mayer cost function g(x0, xf, v) |
lagrange(ocp) | Function | Lagrange 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):
(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.0The function signature depends on the constraint type:
For
:boundaryand:variableconstraints:f(x0, xf, v)For other constraints (
:control,:state,:mixed):f(t, x, u, v)
Examples of different constraint types:
(type, f, lb, ub) = constraint(ocp, :cons_bound)
println("type: ", type)
println("val: ", f(x0, xf, v))type: boundary
val: -3.0(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(type, f, lb, ub) = constraint(ocp, :cons_mixed)
println("type: ", type)
println("val: ", f(s, q, u, v))type: path
val: 4.0All constraints
Get all constraints as a collection:
constraints(ocp) # returns all constraintsCTModels.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:
path_constraints_nl(ocp) # returns nonlinear path constraints([-Inf], Main.var"#fun##2436#97"(), [10.0], [:cons_mixed])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 boundsf!: constraint function (in-place)ub: vector of upper boundslabels: vector of constraint labels
The constraint functions have the following signatures:
Path constraints:
f!(val, t, x, u, v)wherevalis mutatedBoundary constraints:
f!(val, x0, xf, v)wherevalis mutated
Get the dimensions of nonlinear constraints:
dim_path_constraints_nl(ocp) # number of nonlinear path constraints1dim_boundary_constraints_nl(ocp) # number of nonlinear boundary constraints4Note
To get the dual variable (or Lagrange multiplier) associated to a constraint, use the dual method on a solution — see Solution object.
Summary table
| Method | Returns | Description |
|---|---|---|
constraint(ocp, label) | (Symbol, Function, Real, Real) | Get constraint by label |
constraints(ocp) | Collection | All constraints |
path_constraints_nl(ocp) | Constraints | Nonlinear path constraints |
boundary_constraints_nl(ocp) | Constraints | Nonlinear boundary constraints |
dim_path_constraints_nl(ocp) | Int | Number of nonlinear path constraints |
dim_boundary_constraints_nl(ocp) | Int | Number of nonlinear boundary constraints |
Problem definition
Get the problem definition as a string:
definition(ocp) # returns the OCP definition as AbstractDefinitionAbstract 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) → minTo extract the expression from the definition, use:
expr = expression(ocp) # returns the Expr from the definitionNote
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:
has_abstract_definition(ocp) # true if definition is present (not EmptyDefinition)trueVariant 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.
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)trueThe following problem is non-autonomous since the dynamics depends on t.
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)falseFinally, this last problem is non-autonomous because the Lagrange part of the cost depends on t.
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)falseThe variant predicate is_nonautonomous is also available and returns the opposite of is_autonomous:
is_nonautonomous(ocp) # true if dynamics or cost depend on timetrueVariant 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
Formulation — the mathematics behind these fields.
Abstract syntax (
@def) · Functional API — building the model this page reads.Solve overview — solving it.
Solution object — the symmetric page for reading back a solution rather than a problem.