Solve a problem

In this tutorial, we consider the Beam problem. First, import the OptimalControlProblems package to access the problem:

using OptimalControlProblems

Solving from an OptimalControl model

First, import the problem:

using OptimalControl
docp = beam(OptimalControlBackend())
nlp = nlp_model(docp)

The nlp model represents the nonlinear programming problem (NLP) obtained after discretising the optimal control problem (OCP). See the Introduction page for details. The model is an ADNLPModels.ADNLPModel, which provides automatic differentiation (AD)-based models that follow the NLPModels.jl API.

We can then solve the problem using, for instance, NLPModelsIpopt.ipopt:

using NLPModelsIpopt

# Solve the model
sol = NLPModelsIpopt.ipopt(
    nlp;
    print_level=5,
    tol=1e-8,
    mu_strategy="adaptive",
    sb="yes",
)
This is Ipopt version 3.14.19, running with linear solver MUMPS 5.8.1.

Number of nonzeros in equality constraint Jacobian...:     4004
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:      501

Total number of variables............................:     1503
                     variables with only lower bounds:        0
                variables with lower and upper bounds:     1002
                     variables with only upper bounds:        0
Total number of equality constraints.................:     1004
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.0000000e-02 1.10e+00 2.62e-13   0.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  3.6382396e-01 7.50e-01 1.72e+00  -0.9 3.39e+00    -  5.94e-01 3.18e-01h  1
   2  1.1944708e+01 7.50e-03 1.82e+00  -0.8 7.62e+00    -  6.98e-01 9.90e-01f  1
   3  1.2010093e+01 7.48e-05 2.84e-01  -1.9 2.53e-01    -  9.98e-01 9.90e-01h  1
   4  1.0698046e+01 5.30e-07 1.46e+00  -2.4 1.80e+00    -  9.94e-01 9.93e-01f  1
   5  9.2863700e+00 1.11e-16 2.37e+01  -3.2 9.06e-01    -  9.99e-01 1.00e+00f  1
   6  8.9473960e+00 1.11e-16 2.67e+03  -4.2 2.48e-01    -  1.00e+00 7.69e-01f  1
   7  8.9328684e+00 1.11e-16 6.58e+03  -5.1 2.35e-01    -  1.00e+00 2.34e-01f  1
   8  8.9059261e+00 1.11e-16 2.68e+03  -4.7 2.86e-01    -  3.16e-01 6.56e-01f  1
   9  8.9052608e+00 1.11e-16 2.64e+03 -10.6 1.81e-01    -  9.64e-02 3.95e-02f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  8.8997178e+00 1.11e-16 1.53e+03  -6.7 1.67e-01    -  1.98e-01 3.58e-01f  1
  11  8.8995100e+00 2.22e-16 1.57e+03 -10.8 1.74e-01    -  1.05e-01 2.18e-02f  1
  12  8.8968682e+00 2.22e-16 1.34e+03  -6.0 9.89e-02    -  5.06e-01 2.49e-01f  1
  13  8.8965717e+00 2.22e-16 1.31e+03 -11.0 1.65e-01    -  9.30e-02 4.03e-02f  1
  14  8.8940168e+00 2.22e-16 8.08e+02  -6.4 1.13e-01    -  1.82e-01 3.42e-01f  1
  15  8.8938704e+00 2.22e-16 8.31e+02 -11.0 1.29e-01    -  1.86e-01 3.28e-02f  1
  16  8.8927053e+00 2.22e-16 7.10e+02  -5.9 6.43e-02    -  7.11e-01 2.26e-01f  1
  17  8.8900801e+00 1.11e-16 5.70e+01  -5.6 9.74e-02    -  3.96e-01 9.85e-01f  1
  18  8.8900330e+00 1.11e-16 1.10e+02 -11.0 4.06e-02    -  3.60e-01 6.68e-02f  1
  19  8.8898907e+00 2.22e-16 2.70e+01  -5.5 2.95e-02    -  6.93e-01 3.13e-01f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20  8.8896270e+00 1.11e-16 2.79e+01  -6.0 3.55e-02    -  4.27e-01 5.86e-01f  1
  21  8.8895382e+00 1.11e-16 1.02e+01  -5.7 1.09e-02    -  9.98e-01 7.56e-01f  1
  22  8.8894231e+00 1.11e-16 5.46e+01  -6.3 2.89e-02    -  9.68e-01 6.10e-01f  1
  23  8.8893916e+00 1.11e-16 7.11e-15  -6.0 6.02e-03    -  1.00e+00 1.00e+00f  1
  24  8.8893248e+00 1.11e-16 1.78e+00  -6.5 1.48e-02    -  9.95e-01 9.70e-01f  1
  25  8.8893119e+00 2.22e-16 2.19e+01  -7.7 1.43e-02    -  1.00e+00 3.06e-01f  1
  26  8.8892891e+00 1.11e-16 4.32e+00  -8.0 1.69e-02    -  1.00e+00 8.10e-01f  1
  27  8.8892829e+00 1.11e-16 1.73e-03  -8.7 8.14e-03    -  1.00e+00 1.00e+00f  1
  28  8.8892819e+00 1.11e-16 1.54e-02  -9.6 3.13e-03    -  1.00e+00 9.96e-01f  1
  29  8.8892817e+00 1.11e-16 7.11e-15 -10.8 1.42e-03    -  1.00e+00 1.00e+00f  1

Number of Iterations....: 29

                                   (scaled)                 (unscaled)
Objective...............:   8.8892816510267849e+00    8.8892816510267849e+00
Dual infeasibility......:   7.1054273576010019e-15    7.1054273576010019e-15
Constraint violation....:   1.1102230246251565e-16    1.1102230246251565e-16
Variable bound violation:   9.9998963970504562e-09    9.9998963970504562e-09
Complementarity.........:   1.1068094190397985e-09    1.1068094190397985e-09
Overall NLP error.......:   1.1068094190397985e-09    1.1068094190397985e-09


Number of objective function evaluations             = 30
Number of objective gradient evaluations             = 30
Number of equality constraint evaluations            = 30
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 30
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 29
Total seconds in IPOPT                               = 0.079

EXIT: Optimal Solution Found.

To get the number of iterations from the NLP solution:

sol.iter
29

and the objective value:

sol.objective
8.889281651026785

To recover the state, control, and costate, we recommend building an optimal control solution and using the associated getters (you can also retrieve the number of iterations and the objective value from the OCP solution):

ocp_sol = build_ocp_solution(docp, sol)

t = time_grid(ocp_sol)  # t0, ..., tN = tf
x = state(ocp_sol)      # function of time
u = control(ocp_sol)    # function of time
p = costate(ocp_sol)    # function of time
o = objective(ocp_sol)  # scalar objective value
i = iterations(ocp_sol) # number of iteration

tf = t[end]
println("tf = ", tf)
println("x(tf) = ", x(tf))
println("u(tf) = ", u(tf))
println("p(tf) = ", p(tf))
println("objective:  ", o)
println("iterations: ", i)
tf = 1.0
x(tf) = [6.983483423835114e-36, -1.0]
u(tf) = -6.644778101442487
p(tf) = [44.44721421847331, -13.37845063459404]
objective:  8.889281651026785
iterations: 29
Note

If the problem includes additional optimisation variables, such as the final time, you can retrieve them with:

v = variable(ocp_sol)

From ocp_sol you can also plot the state, control, and costate trajectories. For more details about plotting optimal control problems, see the Plot Manual.

using Plots
plt = plot(ocp_sol; color=1, size=(800, 700), control_style=(label="OptimalControl", ))
Example block output

Solving from a JuMP model

First, import the JuMP model:

using JuMP
nlp = beam(JuMPBackend())
A JuMP Model
├ solver: none
├ objective_sense: MIN_SENSE
│ └ objective_function_type: QuadExpr
├ num_variables: 1503
├ num_constraints: 3008
│ ├ AffExpr in MOI.EqualTo{Float64}: 1004
│ ├ VariableRef in MOI.GreaterThan{Float64}: 1002
│ └ VariableRef in MOI.LessThan{Float64}: 1002
└ Names registered in the model
  └ :u, :x1, :x2, :∂x1, :∂x2

We can then solve the problem using the JuMP.optimize! function:

using Ipopt

# Set the optimiser
set_optimizer(nlp, Ipopt.Optimizer)

# Set optimiser attributes
set_optimizer_attribute(nlp, "tol", 1e-8)
set_optimizer_attribute(nlp, "mu_strategy", "adaptive")
set_optimizer_attribute(nlp, "linear_solver", "mumps")
set_optimizer_attribute(nlp, "sb", "yes")

# Solve the model
optimize!(nlp)
This is Ipopt version 3.14.19, running with linear solver MUMPS 5.8.1.

Number of nonzeros in equality constraint Jacobian...:     4004
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:      501

Total number of variables............................:     1503
                     variables with only lower bounds:        0
                variables with lower and upper bounds:     1002
                     variables with only upper bounds:        0
Total number of equality constraints.................:     1004
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.0000000e-02 1.10e+00 2.54e-13   0.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  3.6382396e-01 7.50e-01 1.72e+00  -0.9 3.39e+00    -  5.94e-01 3.18e-01h  1
   2  1.1944708e+01 7.50e-03 1.82e+00  -0.8 7.62e+00    -  6.98e-01 9.90e-01f  1
   3  1.2010093e+01 7.48e-05 2.84e-01  -1.9 2.53e-01    -  9.98e-01 9.90e-01h  1
   4  1.0698046e+01 5.30e-07 1.46e+00  -2.4 1.80e+00    -  9.94e-01 9.93e-01f  1
   5  9.2863700e+00 9.45e-17 2.37e+01  -3.2 9.06e-01    -  9.99e-01 1.00e+00f  1
   6  8.9473960e+00 9.02e-17 2.67e+03  -4.2 2.48e-01    -  1.00e+00 7.69e-01f  1
   7  8.9328684e+00 1.41e-16 6.58e+03  -5.1 2.35e-01    -  1.00e+00 2.34e-01f  1
   8  8.9059261e+00 1.08e-16 2.68e+03  -4.7 2.86e-01    -  3.16e-01 6.56e-01f  1
   9  8.9052608e+00 1.48e-16 2.64e+03 -10.6 1.81e-01    -  9.64e-02 3.95e-02f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  8.8997178e+00 1.77e-16 1.53e+03  -6.7 1.67e-01    -  1.98e-01 3.58e-01f  1
  11  8.8995100e+00 2.03e-16 1.57e+03 -10.8 1.74e-01    -  1.05e-01 2.18e-02f  1
  12  8.8968682e+00 1.80e-16 1.34e+03  -6.0 9.89e-02    -  5.06e-01 2.49e-01f  1
  13  8.8965717e+00 2.28e-16 1.31e+03 -11.0 1.65e-01    -  9.30e-02 4.03e-02f  1
  14  8.8940168e+00 1.65e-16 8.08e+02  -6.4 1.13e-01    -  1.82e-01 3.42e-01f  1
  15  8.8938704e+00 2.53e-16 8.31e+02 -11.0 1.29e-01    -  1.86e-01 3.28e-02f  1
  16  8.8927053e+00 2.46e-16 7.10e+02  -5.9 6.43e-02    -  7.11e-01 2.26e-01f  1
  17  8.8900778e+00 1.04e-16 5.51e+01  -5.6 9.75e-02    -  3.97e-01 9.86e-01f  1
  18  8.8900312e+00 1.47e-16 1.08e+02 -11.0 4.05e-02    -  3.59e-01 6.63e-02f  1
  19  8.8898898e+00 1.45e-16 2.71e+01  -5.5 2.97e-02    -  6.90e-01 3.10e-01f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20  8.8896196e+00 1.28e-16 3.13e+01  -6.0 3.56e-02    -  4.26e-01 6.05e-01f  1
  21  8.8895320e+00 1.04e-16 9.66e+00  -5.7 1.08e-02    -  1.00e+00 7.59e-01f  1
  22  8.8894202e+00 1.42e-16 5.53e+01  -6.3 2.88e-02    -  9.79e-01 6.08e-01f  1
  23  8.8893851e+00 1.08e-16 1.47e-14  -6.0 6.56e-03    -  1.00e+00 1.00e+00f  1
  24  8.8893214e+00 8.93e-17 2.60e-01  -6.5 1.41e-02    -  9.96e-01 1.00e+00f  1
  25  8.8893107e+00 1.66e-16 2.21e+01  -8.4 1.51e-02    -  1.00e+00 2.72e-01f  1
  26  8.8892856e+00 1.03e-16 1.82e-02  -8.1 1.71e-02    -  1.00e+00 9.99e-01f  1
  27  8.8892828e+00 1.03e-16 1.22e+00  -9.5 5.42e-03    -  1.00e+00 8.33e-01f  1
  28  8.8892818e+00 1.03e-16 7.11e-15  -8.9 2.98e-03    -  1.00e+00 1.00e+00f  1

Number of Iterations....: 28

                                   (scaled)                 (unscaled)
Objective...............:   8.8892818464565817e+00    8.8892818464565817e+00
Dual infeasibility......:   7.1054273576010019e-15    7.1054273576010019e-15
Constraint violation....:   1.0321604682062002e-16    1.0321604682062002e-16
Variable bound violation:   9.9671177561377888e-09    9.9671177561377888e-09
Complementarity.........:   4.1493706604063998e-09    4.1493706604063998e-09
Overall NLP error.......:   4.1493706604063998e-09    4.1493706604063998e-09


Number of objective function evaluations             = 29
Number of objective gradient evaluations             = 29
Number of equality constraint evaluations            = 29
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 1
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 1
Total seconds in IPOPT                               = 0.056

EXIT: Optimal Solution Found.

To get the number of iterations:

barrier_iterations(nlp)
28

To get the objective value:

objective_value(nlp)
8.889281846456582

To get the time grid, state, control, and costate, OptimalControlProblems provides the following getters (you have also similar getters to retrieve the number of iterations and the objective value):

problem = :beam

t = time_grid(problem, nlp)    # t0, ..., tN = tf
x = state(problem, nlp)        # function of time
u = control(problem, nlp)      # function of time
p = costate(problem, nlp)      # function of time
o = objective(problem, nlp)    # scalar objective value
i = iterations(problem, nlp)   # number of iteration

tf = t[end]
println("tf = ", tf)
println("x(tf) = ", x(tf))
println("u(tf) = ", u(tf))
println("p(tf) = ", p(tf))
println("objective:  ", o)
println("iterations: ", i)
tf = 1.0
x(tf) = [-4.454407807932683e-34, -1.0]
u(tf) = -6.644786948253476
p(tf) = [-44.447394877959965, 13.378468969723418]
objective:  8.889281846456582
iterations: 28
Note

If the problem includes additional optimisation variables, such as the final time, you can retrieve them with:

v = variable(problem, nlp)

We can add the state, costate, and control to the plot:

n = length(metadata[problem][:state_name])   # dimension of the state
m = length(metadata[problem][:control_name]) # dimension of the control

for i in 1:n # state
    plot!(plt[i], t, t -> x(t)[i]; color=2, linestyle=:dash, label=:none)
end

for i in 1:n # costate
    plot!(plt[n+i], t, t -> -p(t)[i]; color=2, linestyle=:dash, label=:none)
end

for i in 1:m # control
    plot!(plt[2n+i], t, t -> u(t)[i]; color=2, linestyle=:dash, label="JuMP")
end
Example block output
Note

The costate from JuMP is the opposite of the costate from OptimalControl, that is why we plot $-p(t)$.