|
1 | | -# |
| 1 | +# [NLP and DOCP manipulations](@id tutorial-nlp) |
| 2 | + |
| 3 | +```@meta |
| 4 | +Draft = false |
| 5 | +CurrentModule = OptimalControl |
| 6 | +``` |
| 7 | + |
| 8 | +We describe here some more advanced operations related to the discretized optimal control problem. When calling `solve(ocp)`, three steps are performed internally: |
| 9 | + |
| 10 | +1. The OCP is discretized into a DOCP (discretized optimal control problem) |
| 11 | +2. An NLP (nonlinear programming) model is built from the DOCP |
| 12 | +3. The NLP is solved, and a functional solution of the OCP is rebuilt from the NLP solution |
| 13 | + |
| 14 | +These steps can also be done manually, which gives you more control over the solving process. This is useful when you want to: |
| 15 | + |
| 16 | +- Use your own NLP solver |
| 17 | +- Access and inspect the NLP model |
| 18 | +- Benchmark different components separately |
| 19 | +- Fine-tune each step of the resolution |
| 20 | + |
| 21 | +Let us load the packages. |
| 22 | + |
| 23 | +```@example main-nlp |
| 24 | +using OptimalControl |
| 25 | +using Plots |
| 26 | +``` |
| 27 | + |
| 28 | +We define a simple test problem: a double integrator with minimal control energy. |
| 29 | + |
| 30 | +```@example main-nlp |
| 31 | +ocp = @def begin |
| 32 | +
|
| 33 | + t ∈ [0, 1], time |
| 34 | + x ∈ R², state |
| 35 | + u ∈ R, control |
| 36 | +
|
| 37 | + x(0) == [ -1, 0 ] |
| 38 | + x(1) == [ 0, 0 ] |
| 39 | +
|
| 40 | + ẋ(t) == [ x₂(t), u(t) ] |
| 41 | +
|
| 42 | + ∫( 0.5u(t)^2 ) → min |
| 43 | +
|
| 44 | +end |
| 45 | +nothing # hide |
| 46 | +``` |
| 47 | + |
| 48 | +## Step-by-step resolution with OptimalControl |
| 49 | + |
| 50 | +We now perform the resolution step by step, using OptimalControl strategy instances. |
| 51 | + |
| 52 | +### Step 1: Build the initial guess |
| 53 | + |
| 54 | +First, we build an initial guess for the problem. Here we pass `nothing` to use the default initialization. |
| 55 | + |
| 56 | +```@example main-nlp |
| 57 | +init = build_initial_guess(ocp, nothing) |
| 58 | +nothing # hide |
| 59 | +``` |
| 60 | + |
| 61 | +### Step 2: Discretize the OCP |
| 62 | + |
| 63 | +We create a discretizer strategy and use it to discretize the optimal control problem into a DOCP. |
| 64 | + |
| 65 | +```@example main-nlp |
| 66 | +discretizer = OptimalControl.Collocation(grid_size=100, scheme=:trapeze) |
| 67 | +docp = discretize(ocp, discretizer) |
| 68 | +nothing # hide |
| 69 | +``` |
| 70 | + |
| 71 | +The `docp` is a `DiscretizedModel` that contains information about the discretization, including a copy of the original OCP. |
| 72 | + |
| 73 | +### Step 3: Build the NLP model |
| 74 | + |
| 75 | +Next, we create a modeler strategy and build the NLP model from the discretized problem. |
| 76 | + |
| 77 | +```@example main-nlp |
| 78 | +modeler = OptimalControl.ADNLP(backend=:optimized) |
| 79 | +nlp = nlp_model(docp, init, modeler) |
| 80 | +nothing # hide |
| 81 | +``` |
| 82 | + |
| 83 | +The `nlp` is an `ADNLPModel` (from the NLPModels ecosystem) representing the discretized nonlinear programming problem. |
| 84 | + |
| 85 | +### Step 4: Solve the NLP |
| 86 | + |
| 87 | +We have two approaches to solve the NLP problem. |
| 88 | + |
| 89 | +#### Approach A: Using OptimalControl solver wrapper |
| 90 | + |
| 91 | +We can create an OptimalControl solver strategy and use it to solve the NLP: |
| 92 | + |
| 93 | +```@example main-nlp |
| 94 | +solver = OptimalControl.Ipopt(print_level=5, tol=1e-8, mu_strategy="adaptive") |
| 95 | +nlp_sol = solve(nlp, solver; display=true) |
| 96 | +nothing # hide |
| 97 | +``` |
| 98 | + |
| 99 | +#### Approach B: Using external NLP solvers directly |
| 100 | + |
| 101 | +Alternatively, we can use NLP solvers directly from their respective packages. For instance, with [NLPModelsIpopt.jl](https://jso.dev/NLPModelsIpopt.jl): |
| 102 | + |
| 103 | +```@example main-nlp |
| 104 | +using NLPModelsIpopt |
| 105 | +nlp_sol_ipopt = ipopt(nlp; print_level=5, mu_strategy="adaptive", tol=1e-8, sb="yes") |
| 106 | +nothing # hide |
| 107 | +``` |
| 108 | + |
| 109 | +Or with [MadNLP.jl](https://madnlp.github.io/MadNLP.jl): |
| 110 | + |
| 111 | +```@example main-nlp |
| 112 | +using MadNLP |
| 113 | +nlp_sol_madnlp = madnlp(nlp; print_level=MadNLP.ERROR, tol=1e-8) |
| 114 | +nothing # hide |
| 115 | +``` |
| 116 | + |
| 117 | +### Step 5: Build the OCP solution |
| 118 | + |
| 119 | +Finally, we build the optimal control solution from the NLP solution and plot it. Note that the multipliers from the NLP solver are used to compute the costate. |
| 120 | + |
| 121 | +```@example main-nlp |
| 122 | +sol = ocp_solution(docp, nlp_sol, modeler) |
| 123 | +plot(sol) |
| 124 | +``` |
| 125 | + |
| 126 | +## All-in-one with explicit strategies |
| 127 | + |
| 128 | +All the steps above can also be performed in a single `solve` call by passing the strategy instances explicitly: |
| 129 | + |
| 130 | +```@example main-nlp |
| 131 | +sol = solve(ocp, init, discretizer, modeler, solver; display=false) |
| 132 | +plot(sol) |
| 133 | +``` |
| 134 | + |
| 135 | +This is the explicit mode of `solve`, which gives you full control over each component while keeping the interface simple. See the [explicit mode documentation](@extref OptimalControl manual-solve-explicit) for more details. |
| 136 | + |
| 137 | +## Initial guess and warm start |
| 138 | + |
| 139 | +An initial guess can be passed at different stages of the process. |
| 140 | + |
| 141 | +If you have a previous solution, you can use it to warm-start a new resolution: |
| 142 | + |
| 143 | +```@example main-nlp |
| 144 | +# Use the previous solution as initial guess |
| 145 | +init_warm = build_initial_guess(ocp, sol) |
| 146 | +
|
| 147 | +# Discretize and solve with warm start |
| 148 | +docp_warm = discretize(ocp, discretizer) |
| 149 | +nlp_warm = nlp_model(docp_warm, init_warm, modeler) |
| 150 | +nlp_sol_warm = solve(nlp_warm, solver; display=false) |
| 151 | +sol_warm = ocp_solution(docp_warm, nlp_sol_warm, modeler) |
| 152 | +
|
| 153 | +println("Iterations with warm start: ", iterations(sol_warm)) |
| 154 | +println("Iterations without warm start: ", iterations(sol)) |
| 155 | +``` |
| 156 | + |
| 157 | +You can also pass the initial guess directly to `solve` in explicit mode: |
| 158 | + |
| 159 | +```@example main-nlp |
| 160 | +sol_warm2 = solve(ocp, init_warm, discretizer, modeler, solver; display=false) |
| 161 | +println("Objective value: ", objective(sol_warm2)) |
| 162 | +``` |
0 commit comments