Skip to content

Commit a5e6c6a

Browse files
committed
Rewrite tutorial-nlp: use new API (discretize/nlp_model/ocp_solution) and step-by-step resolution with OptimalControl strategies
1 parent 3fdd838 commit a5e6c6a

1 file changed

Lines changed: 162 additions & 1 deletion

File tree

docs/src/tutorial-nlp.md

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,162 @@
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

Comments
 (0)