Skip to content

Commit aa83cda

Browse files
Merge branch 'main' into 222-manipulation-of-the-nlp
2 parents 4b090b4 + aa7f075 commit aa83cda

33 files changed

+912
-35
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OptimalControl"
22
uuid = "5f98b655-cc9a-415a-b60e-744165666948"
33
authors = ["Olivier Cots <olivier.cots@toulouse-inp.fr>"]
4-
version = "0.9.4"
4+
version = "0.10.0"
55

66
[deps]
77
CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd"
@@ -12,7 +12,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1212

1313
[compat]
1414
CTBase = "0.11"
15-
CTDirect = "0.9"
15+
CTDirect = "0.10"
1616
CTFlows = "0.4"
1717
CommonSolve = "0.2"
1818
DocStringExtensions = "0.9"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ makedocs(
2020
prettyurls = false,
2121
size_threshold_ignore = [
2222
"api-ctbase/types.md",
23+
"tutorial-plot.md",
2324
],
2425
assets = [
2526
asset("https://control-toolbox.org/assets/css/documentation.css"),

docs/src/tutorial-iss.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Indirect simple shooting
1+
# [Indirect simple shooting](@id iss)
22

33
In this tutorial we present the indirect simple shooting method on a simple example.
44

@@ -160,7 +160,16 @@ end # hide
160160
nothing # hide
161161
```
162162

163-
We get:
163+
The solution can be plot calling first the flow.
164+
165+
```@example main
166+
sol = φ( (t0, tf), x0, p0_sol )
167+
plot(sol, size=(800, 600))
168+
```
169+
170+
In the indirect shooting method, the research of the optimal control is replaced by the computation
171+
of its associated extremal. This computation is equivalent to finding the initial covector solution
172+
to the shooting function.
164173

165174
```@example main
166175
exp(p0; saveat=[]) = φ((t0, tf), x0, p0, saveat=saveat).ode_sol # hide

docs/src/tutorial-nlp.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ We describe here some more advanced operations related to the discretized optima
88
When calling `solve(ocp)` three steps are performed internally:
99

1010
- first, the OCP is discretized into a DOCP (a nonlinear optimization problem) with [`direct_transcription`](@ref),
11-
- then, this DOCP is solved,
11+
- then, this DOCP is solved (with the internal function `solve_docp`),
1212
- finally, a functional solution of the OCP is rebuilt from the solution of the discretized problem, with [`build_solution`](@ref).
1313

1414
These steps can also be done separately, for instance if you want to use your own NLP solver.
@@ -48,16 +48,11 @@ nothing # hide
4848
We discretize the problem.
4949

5050
```@example main
51-
docp = direct_transcription(ocp)
51+
docp, nlp = direct_transcription(ocp)
5252
nothing # hide
5353
```
5454

55-
The DOCP contains a copy of the original OCP, and the resulting discretized problem, in our case an `ADNLPModel`.
56-
You can extract this raw NLP problem with the [`get_nlp`](@ref) method.
57-
58-
```@example main
59-
nlp = get_nlp(docp)
60-
```
55+
The DOCP contains information related to the transcription, including a copy of the original OCP, and the NLP is the resulting discretized problem, in our case an `ADNLPModel`.
6156

6257
We can now use the solver of our choice to solve it.
6358

@@ -67,8 +62,7 @@ For a first example we use the `ipopt` solver from [`NLPModelsIpopt.jl`](https:/
6762

6863
```@example main
6964
using NLPModelsIpopt
70-
71-
nlp_sol = ipopt(get_nlp(docp); print_level=4, mu_strategy="adaptive", tol=1e-8, sb="yes")
65+
nlp_sol = ipopt(nlp; print_level=4, mu_strategy="adaptive", tol=1e-8, sb="yes")
7266
nothing # hide
7367
```
7468

@@ -84,14 +78,14 @@ plot(sol)
8478
An initial guess, including warm start, can be passed to [`direct_transcription`](@ref) the same way as for `solve`.
8579

8680
```@example main
87-
docp = direct_transcription(ocp, init=sol)
81+
docp, nlp = direct_transcription(ocp, init=sol)
8882
nothing # hide
8983
```
9084

9185
It can also be changed after the transcription is done, with [`set_initial_guess`](@ref).
9286

9387
```@example main
94-
set_initial_guess(docp, sol)
88+
set_initial_guess(docp, nlp, sol)
9589
nothing # hide
9690
```
9791

@@ -101,7 +95,6 @@ with as initial guess the solution from the first resolution.
10195
```@example main
10296
using Percival
10397
104-
nlp = get_nlp(docp)
10598
output = percival(nlp)
10699
print(output)
107100
```

docs/src/tutorial-plot.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ plot(sol;
6666
control_style = (color=:red, linewidth=2))
6767
```
6868

69+
## From Flow
70+
71+
The previous resolution of the optimal control problem was done with the `solve` function.
72+
If you use an indirect shooting method and solve shooting equations, you may want to plot the
73+
associated solution. To do so, you need to use the `Flow` function to
74+
reconstruct the solution. See the [Indirect Simple Shooting](@ref iss) tutorial for an example.
75+
In our example, you must provide the maximising control $(x, p) \mapsto p_2$ together with the
76+
optimal control problem.
77+
78+
```@example main
79+
t0 = 0
80+
tf = 1
81+
x0 = [ -1, 0 ]
82+
p0 = sol.costate(t0)
83+
f = Flow(ocp, (x, p) -> p[2])
84+
sol_flow = f( (t0, tf), x0, p0 )
85+
plot(sol_flow)
86+
```
87+
88+
You can notice that the time grid has very few points. To have a better visualisation (the accuracy
89+
won't change), you can give a finer grid.
90+
91+
```@example main
92+
sol_flow = f( (t0, tf), x0, p0; saveat=range(t0, tf, 100) )
93+
plot(sol_flow)
94+
```
95+
6996
## Split versus group layout
7097

7198
If you prefer to get a more compact figure, you can use the `layout` optional keyword argument with `:group` value. It will group the state, costate and control trajectories in one subplot for each.

src/OptimalControl.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,13 @@ export *
4242

4343
# CTDirect
4444
export direct_transcription
45-
export get_nlp
4645
export set_initial_guess
4746
export build_solution
4847
export save
4948
export load
5049
export export_ocp_solution
5150
export import_ocp_solution
5251

53-
5452
# CTBase
5553
export Index
5654
export Autonomous, NonAutonomous

src/solve.jl

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,6 @@ julia> sol = solve(ocp, init=(state=t->[-1+t, t*(t-1)], control=t->6-12*t))
6666
```
6767
6868
"""
69-
#=
70-
function solve(ocp::OptimalControlModel, description::Symbol...;
71-
display::Bool=__display(),
72-
init=__ocp_init(),
73-
kwargs...)
74-
=#
75-
76-
7769
function solve(ocp::OptimalControlModel, description::Symbol...;
7870
init=__ocp_init(),
7971
grid_size::Integer=CTDirect.__grid_size_direct(),
@@ -88,17 +80,16 @@ function solve(ocp::OptimalControlModel, description::Symbol...;
8880

8981
# print chosen method
9082
method = getFullDescription(description, available_methods())
91-
display ? println("Method = ", method) : nothing
83+
#display ? println("Method = ", method) : nothing
9284

9385
# if no error before, then the method is correct: no need of else
9486
if :direct method
95-
#return CTDirect.solve(ocp, clean(method)...; display=display, init=init, kwargs...)
9687

9788
# build discretized OCP
98-
docp = direct_transcription(ocp, description, init=init, grid_size=grid_size, time_grid=time_grid)
89+
docp, nlp = direct_transcription(ocp, description, init=init, grid_size=grid_size, time_grid=time_grid)
9990

10091
# solve DOCP (NB. init is already embedded in docp)
101-
docp_solution = CTDirect.solve_docp(docp, display=display, print_level=print_level, mu_strategy=mu_strategy, tol=tol, max_iter=max_iter, linear_solver=linear_solver; kwargs...)
92+
docp_solution = CTDirect.solve_docp(docp, nlp, display=display, print_level=print_level, mu_strategy=mu_strategy, tol=tol, max_iter=max_iter, linear_solver=linear_solver; kwargs...)
10293

10394
# build and return OCP solution
10495
return build_solution(docp, docp_solution)

test/problems/beam.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Beam example from bocop
2+
3+
function beam()
4+
5+
@def beam begin
6+
t [ 0, 1 ], time
7+
x R², state
8+
u R, control
9+
x(0) == [0,1]
10+
x(1) == [0,-1]
11+
(t) == [x₂(t), u(t)]
12+
0 x₁(t) 0.1
13+
-10 u(t) 10
14+
(u(t)^2) min
15+
end
16+
17+
return ((ocp=beam, obj=8.898598, name="beam", init=nothing))
18+
end

test/problems/beam.png

40 KB
Loading

test/problems/bioreactor.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Bioreactor example from bocop
2+
3+
# METHANE PROBLEM
4+
# mu2 according to growth model
5+
# mu according to light model
6+
# time scale is [0,10] for 24h (day then night)
7+
8+
# growth model MONOD
9+
function growth(s, mu2m, Ks)
10+
return mu2m * s / (s+Ks)
11+
end
12+
13+
# light model: max^2 (0,sin) * mubar
14+
# DAY/NIGHT CYCLE: [0,2 halfperiod] rescaled to [0,2pi]
15+
function light(time, halfperiod)
16+
pi = 3.141592653589793
17+
days = time / (halfperiod*2)
18+
tau = (days - floor(days)) * 2*pi
19+
return max(0,sin(tau))^2
20+
end
21+
22+
# 1 day periodic problem
23+
function bioreactor_1day_periodic()
24+
@def bioreactor_1 begin
25+
# constants
26+
beta = 1
27+
c = 2
28+
gamma = 1
29+
Ks = 0.05
30+
mu2m = 0.1
31+
mubar = 1
32+
r = 0.005
33+
halfperiod = 5
34+
T = halfperiod * 2
35+
36+
# ocp
37+
t [ 0, T ], time
38+
x R³, state
39+
u R, control
40+
y = x[1]
41+
s = x[2]
42+
b = x[3]
43+
mu = light(t, halfperiod) * mubar
44+
mu2 = growth(s(t), mu2m, Ks)
45+
[0,0,0.001] x(t) [Inf, Inf, Inf]
46+
0 u(t) 1
47+
1 y(0) Inf
48+
1 b(0) Inf
49+
x(0)- x(T) == [0,0,0]
50+
(t) == [mu*y(t)/(1+y(t))-(r+u(t))*y(t),
51+
-mu2*b(t) + u(t)*beta*(gamma*y(t)-s(t)),
52+
(mu2-u(t)*beta)*b(t)]
53+
(mu2*b(t)/(beta+c)) max
54+
end
55+
56+
return ((ocp=bioreactor_1, obj=0.614134, name="bioreactor_1day_periodic", init=nothing))
57+
end
58+
59+
60+
# N days (non periodic)
61+
function bioreactor_Ndays()
62+
@def bioreactor_N begin
63+
# constants
64+
beta = 1
65+
c = 2
66+
gamma = 1
67+
halfperiod = 5
68+
Ks = 0.05
69+
mu2m = 0.1
70+
mubar = 1
71+
r = 0.005
72+
N = 30
73+
T = 10 * N
74+
75+
# ocp
76+
t [ 0, T ], time
77+
x R³, state
78+
u R, control
79+
y = x[1]
80+
s = x[2]
81+
b = x[3]
82+
mu = light(t, halfperiod) * mubar
83+
mu2 = growth(s(t), mu2m, Ks)
84+
[0,0,0.001] x(t) [Inf, Inf, Inf]
85+
0 u(t) 1
86+
0.05 y(0) 0.25
87+
0.5 s(0) 5
88+
0.5 b(0) 3
89+
(t) == [mu*y(t)/(1+y(t))-(r+u(t))*y(t),
90+
-mu2*b(t) + u(t)*beta*(gamma*y(t)-s(t)),
91+
(mu2-u(t)*beta)*b(t)]
92+
(mu2*b(t)/(beta+c)) max
93+
end
94+
95+
return ((ocp=bioreactor_N, obj=19.0745, init=(state=[50,50,50],), name="bioreactor_Ndays"))
96+
end

0 commit comments

Comments
 (0)