Discretisation methods

Discretisation formulas

When calling solve, the option disc_method=... can be used to set the discretisation scheme. In addition to the default implicit :trapeze method (aka Crank-Nicolson), other choices are available, namely implicit :midpoint and the Gauss-Legendre collocations with 2 and stages, :gauss_legendre_2 and :gauss_legendre_3, of order 4 and 6 respectively. Note that higher order methods will typically lead to larger NLP problems for the same number of time steps, and that accuracy will also depend on the smoothness of the problem.

As an example we will use the Goddard problem

using OptimalControl  # to define the optimal control problem and more
using NLPModelsIpopt  # to solve the problem via a direct method
using Plots           # to plot the solution

t0 = 0      # initial time
r0 = 1      # initial altitude
v0 = 0      # initial speed
m0 = 1      # initial mass
vmax = 0.1  # maximal authorized speed
mf = 0.6    # final mass to target

ocp = @def begin # definition of the optimal control problem

    tf ∈ R, variable
    t ∈ [t0, tf], time
    x = (r, v, m) ∈ R³, state
    u ∈ R, control

    x(t0) == [ r0, v0, m0 ]
    m(tf) == mf,         (1)
    0 ≤ u(t) ≤ 1
    r(t) ≥ r0
    0 ≤ v(t) ≤ vmax

    ẋ(t) == F0(x(t)) + u(t) * F1(x(t))

    r(tf) → max

end;

# Dynamics
const Cd = 310
const Tmax = 3.5
const β = 500
const b = 2

F0(x) = begin
    r, v, m = x
    D = Cd * v^2 * exp(-β*(r - 1)) # Drag force
    return [ v, -D/m - 1/r^2, 0 ]
end

F1(x) = begin
    r, v, m = x
    return [ 0, Tmax/m, -b*Tmax ]
end

Now let us compare different discretisations

sol_trapeze = solve(ocp; tol=1e-8)
plot(sol_trapeze)

sol_midpoint = solve(ocp, disc_method=:midpoint; tol=1e-8)
plot!(sol_midpoint)

sol_euler = solve(ocp, disc_method=:euler; tol=1e-8)
plot!(sol_euler)

sol_euler_imp = solve(ocp, disc_method=:euler_implicit; tol=1e-8)
plot!(sol_euler_imp)

sol_gl2 = solve(ocp, disc_method=:gauss_legendre_2; tol=1e-8)
plot!(sol_gl2)

sol_gl3 = solve(ocp, disc_method=:gauss_legendre_3; tol=1e-8)
plot!(sol_gl3)
Example block output

Large problems and AD backend

For some large problems, you may notice that solving spends a long time before the iterations actually begin. This is due to the computing of the sparse derivatives, namely the Jacobian of the constraints and the Hessian of the Lagrangian, that can become quite costly. A possible alternative is to set the option adnlp_backend=:manual, which will use more basic sparsity patterns. The resulting matrices are faster to compute but are also less sparse, so this is a trade-off bewteen the AD preparation and the optimization itself.

solve(ocp, disc_method=:gauss_legendre_3, grid_size=1000, adnlp_backend=:manual)
This is Ipopt version 3.14.17, running with linear solver MUMPS 5.7.3.

Number of nonzeros in equality constraint Jacobian...:   174028
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:   104022

Total number of variables............................:    13004
                     variables with only lower bounds:     1001
                variables with lower and upper bounds:     2001
                     variables with only upper bounds:        0
Total number of equality constraints.................:    12004
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.0100000e+00 2.22e+00 2.00e+00   0.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1 -1.0359714e+00 2.59e+00 2.26e+04  -1.0 6.29e+00    -  1.26e-01 9.81e-01f  1
   2 -1.0421591e+00 2.58e+00 2.25e+04   0.5 1.61e+02    -  1.00e+00 5.62e-03f  1
   3 -1.0581309e+00 2.37e+00 1.98e+05   1.6 2.29e+01    -  1.00e+00 7.89e-02f  1
   4 -1.0297827e+00 1.37e+00 5.30e+05   2.3 1.01e+01    -  9.95e-01 6.06e-01h  1
   5 -1.0168610e+00 7.22e-01 1.25e+06   2.3 4.82e+00    -  5.95e-01 9.84e-01h  1
   6 -1.0101725e+00 8.73e-02 2.61e+07   2.3 2.06e+00    -  1.00e+00 8.67e-01h  1
   7 -1.0088127e+00 5.28e-03 7.50e+05   1.6 6.07e-01    -  1.00e+00 9.97e-01h  1
   8 -1.0088898e+00 7.84e-05 1.14e+03   0.2 9.19e-02    -  1.00e+00 1.00e+00f  1
   9 -1.0088804e+00 2.32e-06 1.10e+01  -1.5 2.05e-02    -  1.00e+00 1.00e+00f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10 -1.0088776e+00 2.71e-07 9.58e-02  -3.6 7.53e-03    -  1.00e+00 1.00e+00h  1
  11 -1.0089150e+00 1.08e-05 1.50e+01  -5.5 1.88e-02    -  9.99e-01 1.00e+00h  1
  12 -1.0109999e+00 4.39e-02 1.70e-03  -6.1 8.30e-01    -  1.00e+00 1.00e+00f  1
  13 -1.0121802e+00 3.92e-02 1.06e+01  -6.6 9.51e-01    -  1.00e+00 7.90e-01h  1
  14 -1.0124904e+00 9.56e-03 4.00e-05  -7.0 7.89e-01    -  1.00e+00 1.00e+00h  1
  15 -1.0125500e+00 3.91e-03 3.80e-01  -7.5 6.77e-01    -  1.00e+00 9.43e-01h  1
  16 -1.0125686e+00 2.11e-03 4.64e-06  -8.0 6.09e-01    -  1.00e+00 1.00e+00h  1
  17 -1.0125744e+00 9.51e-04 8.30e-03  -8.6 5.47e-01    -  1.00e+00 9.89e-01h  1
  18 -1.0125756e+00 4.32e-04 2.82e-02  -9.2 6.93e-01    -  1.00e+00 8.38e-01h  1
  19 -1.0125759e+00 1.83e-04 6.00e-05  -9.3 6.45e-01    -  9.94e-01 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20 -1.0125762e+00 1.07e-04 3.43e-08 -10.0 3.84e-01    -  1.00e+00 1.00e+00h  1
  21 -1.0125763e+00 9.04e-05 1.61e-08 -11.0 6.51e-01    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 21

                                   (scaled)                 (unscaled)
Objective...............:  -1.0125763121283586e+00   -1.0125763121283586e+00
Dual infeasibility......:   1.6147371975487365e-08    1.6147371975487365e-08
Constraint violation....:   3.8848892991438788e-09    9.0393234664976063e-05
Variable bound violation:   2.7626403077445802e-31    2.7626403077445802e-31
Complementarity.........:   6.3724399828030864e-11    6.3724399828030864e-11
Overall NLP error.......:   1.6147371975487365e-08    9.0393234664976063e-05


Number of objective function evaluations             = 22
Number of objective gradient evaluations             = 22
Number of equality constraint evaluations            = 22
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 22
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 21
Total seconds in IPOPT                               = 8.506

EXIT: Optimal Solution Found.

Explicit time grid

The option time_grid=... allows to pass the complete time grid vector t0, t1, ..., tf, which is typically useful if one wants a non uniform grid. In the case of a free initial and/or final time, provide a normalised grid between 0 and 1. Note that time_grid will override grid_size if both are present.

sol = solve(ocp, time_grid=[0, 0.1, 0.5, 0.9, 1], display=false)
println(time_grid(sol))
[0.0, 0.016495556123488363, 0.08247778061744182, 0.1484600051113953, 0.16495556123488364]