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.
Signatures and is_* aliases
Every accessor on this page is listed with its full signature and return type in the Problem API reference. Many predicates also have an equivalent is_* alias — has_control / is_control_free, has_variable / is_variable / is_nonvariable, is_autonomous / is_nonautonomous, has_fixed_final_time / is_final_time_fixed (and the three other time variants), has_mayer_cost / is_mayer_cost_defined, has_lagrange_cost / is_lagrange_cost_defined, has_abstract_definition / is_abstractly_defined. Only the has_* / is_autonomous forms are shown below.
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 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
Times model
times(ocp) # the TimesModel structTimesModel
├─ initial: FixedTimeModel(name=0, time=0)
├─ final: FreeTimeModel(name=tf, index=2)
└─ time_name: sInitial and final times separately. The final time needs the variable value when it is free:
julia> initial_time(ocp)
0
julia> final_time(ocp, [1, 2]) # w = 1, tf = 2
2Asking for a free final time without the variable errors:
julia> final_time(ocp)
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
julia> time_name(ocp) # "s"
"s"
julia> initial_time_name(ocp) # "0" — fixed
"0"
julia> final_time_name(ocp) # "tf" — a variable
"tf"Time fixedness predicates
julia> has_fixed_initial_time(ocp)
true
julia> has_free_initial_time(ocp)
false
julia> has_fixed_final_time(ocp)
false
julia> has_free_final_time(ocp)
trueAutonomy
is_autonomous(ocp) # false if dynamics or Lagrange cost depend on ttrueSee Time dependence below.
State
State component information
julia> state_name(ocp)
"q"
julia> state_dimension(ocp)
2
julia> state_components(ocp)
2-element Vector{String}:
"x"
"y"Note
The component names are used when plotting the solution. See Plot.
State box constraints
state_constraints_box(ocp)(Float64[], Int64[], Float64[], Symbol[], Vector{Symbol}[])Tuple structures
Box-constraint accessors (state_constraints_box, control_constraints_box, variable_constraints_box) return (lb, indices, ub, labels, aliases):
lb,ub— vectors of lower / upper boundsindices— component indices (1-based)labels— constraint labelsaliases— for each component, every label that declared it
The nonlinear-constraint accessors (path_constraints_nl, boundary_constraints_nl) return (lb, f!, ub, labels) instead, with f! in-place: f!(val, t, x, u, v) for path constraints, f!(val, x0, xf, v) for boundary constraints.
dim_state_constraints_box(ocp)0Control
Control component information
julia> control_name(ocp)
"u"
julia> control_dimension(ocp)
1
julia> control_components(ocp)
1-element Vector{String}:
"u"Control box constraints
julia> control_constraints_box(ocp)
([0.0], [1], [Inf], [:cons_u], [[:cons_u]])
julia> dim_control_constraints_box(ocp)
1Control presence
has_control(ocp) # true if the problem has a control inputtrueis_control_free(ocp) ≡ !has_control(ocp) — see No control.
Variable
Variable component information
julia> variable_name(ocp)
"v"
julia> variable_dimension(ocp)
2
julia> variable_components(ocp)
2-element Vector{String}:
"w"
"tf"Variable box constraints
julia> variable_constraints_box(ocp)
([0.0, 0.0], [1, 2], [0.0, 2.0], [Symbol("label##12566"), :eq1], [[Symbol("label##12566")], [:eq1]])
julia> dim_variable_constraints_box(ocp)
2Variable presence
has_variable(ocp) # true if the problem has optimisation variablestrueDynamics
The dynamics are stored as an in-place function f!(dx, t, x, u, v) — dx is mutated, the other arguments are time, state, control, variable:
f! = dynamics(ocp)
s = 0.5; q = [0.0, 1.0]; u = 2.0; v = [1.0, 2.0]
dq = similar(q)
f!(dq, s, q, u, v)
dq # the state derivative q̇2-element Vector{Float64}:
2.0
2.0Objective
criterion(ocp) # :min or :max:minThe objective is in Mayer form
julia> has_mayer_cost(ocp)
false
julia> has_lagrange_cost(ocp)
trueGet the cost functions — mayer has signature g(x0, xf, v) and errors when there is no Mayer cost; lagrange has signature f⁰(t, x, u, v):
julia> mayer(ocp)
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
└─f⁰ = lagrange(ocp)
f⁰(0.5, [0.0, 1.0], 2.0, [1.0, 2.0]) # the integrand value2.0Constraints
Individual constraints
constraint(ocp, label) returns (type, f, lb, ub). The function signature is f(x0, xf, v) for :boundary and :variable constraints, f(t, x, u, v) for :control, :state and :mixed ones:
x0 = [0, 1]; xf = [2, 3]; v = [1, 4]
s = 0.5; q = [1.0, 2.0]; u = 3.0
(type, f, lb, ub) = constraint(ocp, :eq1)
(type, f(x0, xf, v), lb, ub)(:variable, 4, 0.0, 2.0)(type, f, lb, ub) = constraint(ocp, :cons_bound) # a boundary constraint
(type, f(x0, xf, v))(:boundary, -3.0)(type, f, lb, ub) = constraint(ocp, :cons_u) # a control constraint
(type, f(s, q, u, v))(:control, 3.0)(type, f, lb, ub) = constraint(ocp, :cons_mixed) # a mixed path constraint
(type, f(s, q, u, v))(:path, 4.0)All constraints, and nonlinear ones
constraints(ocp)ConstraintsModel
├─ path nonlinear: 4
├─ boundary nonlinear: 4
├─ state box: 5
├─ control box: 5
└─ variable box: 5julia> path_constraints_nl(ocp)
([-Inf], Main.var"#fun##12562#97"(), [10.0], [:cons_mixed])
julia> boundary_constraints_nl(ocp)
([-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##12568"), :cons_bound, Symbol("label##12579"), Symbol("label##12579")])
julia> dim_path_constraints_nl(ocp)
1
julia> dim_boundary_constraints_nl(ocp)
4Note
To get the dual variable (Lagrange multiplier) of a constraint, use dual on a solution — see Solution object.
Problem definition
definition(ocp) # the OCP definition, an 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) → minexpr = expression(ocp) # the Expr from the definitionhas_abstract_definition(ocp) # false for a functional-API modeltrueTime dependence
A problem is autonomous when neither the dynamics nor the Lagrange cost depends explicitly on the time variable, non-autonomous otherwise.
ocp = @def begin
t ∈ [ 0, 1 ], time
x ∈ R, state
u ∈ R, control
ẋ(t) == u(t)
x(1) + 0.5∫( u(t)^2 ) → min
end
is_autonomous(ocp)trueAdding an explicit t to the dynamics (or to the Lagrange integrand) makes it non-autonomous:
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)falseis_nonautonomous(ocp) # the negation of is_autonomoustrueAPI 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.