@@ -31,7 +31,7 @@ plant model/constraints.
31
31
struct SingleShooting <: ShootingMethod end
32
32
33
33
@doc raw """
34
- MultipleShooting()
34
+ MultipleShooting(; f_threads=false, h_threads=false )
35
35
36
36
Construct a direct multiple shooting [`TranscriptionMethod`](@ref).
37
37
@@ -56,24 +56,33 @@ for [`NonLinModel`](@ref).
56
56
This transcription computes the predictions by calling the augmented discrete-time model
57
57
in the equality constraint function recursively over ``H_p``, or by updating the linear
58
58
equality constraint vector for [`LinModel`](@ref). It is generally more efficient for large
59
- control horizon ``H_c``, unstable or highly nonlinear plant models/constraints.
59
+ control horizon ``H_c``, unstable or highly nonlinear models/constraints. Multithreading
60
+ with `threads_f` or `threads_h` keyword arguments can be advantageous if ``\m athbf{f}`` or
61
+ ``\m athbf{h}`` in the [`NonLinModel`](@ref) is expensive to evaluate, respectively.
60
62
61
63
Sparse optimizers like `OSQP` or `Ipopt` and sparse Jacobian computations are recommended
62
64
for this transcription method.
63
65
"""
64
- struct MultipleShooting <: ShootingMethod end
66
+ struct MultipleShooting <: ShootingMethod
67
+ f_threads:: Bool
68
+ h_threads:: Bool
69
+ function MultipleShooting (; f_threads= false , h_threads= false )
70
+ return new (f_threads, h_threads)
71
+ end
72
+ end
65
73
66
74
@doc raw """
67
- TrapezoidalCollocation(h::Int=0)
75
+ TrapezoidalCollocation(h::Int=0; f_threads=false, h_threads=false )
68
76
69
77
Construct an implicit trapezoidal [`TranscriptionMethod`](@ref) with `h`th order hold.
70
78
71
79
This is the simplest collocation method. It supports continuous-time [`NonLinModel`](@ref)s
72
80
only. The decision variables are the same as for [`MultipleShooting`](@ref), hence similar
73
- computational costs. The `h` argument is `0` or `1`, for piecewise constant or linear
74
- manipulated inputs ``\m athbf{u}`` (`h=1` is slightly less expensive). Note that the various
75
- [`DiffSolver`](@ref) here assume zero-order hold, so `h=1` will induce a plant-model
76
- mismatch if the plant is simulated with these solvers.
81
+ computational costs. See the same docstring for descriptions of `f_threads` and `h_threads`
82
+ keywords. The `h` argument is `0` or `1`, for piecewise constant or linear manipulated
83
+ inputs ``\m athbf{u}`` (`h=1` is slightly less expensive). Note that the various [`DiffSolver`](@ref)
84
+ here assume zero-order hold, so `h=1` will induce a plant-model mismatch if the plant is
85
+ simulated with these solvers.
77
86
78
87
This transcription computes the predictions by calling the continuous-time model in the
79
88
equality constraint function and by using the implicit trapezoidal rule. It can handle
@@ -96,12 +105,14 @@ transcription method.
96
105
struct TrapezoidalCollocation <: CollocationMethod
97
106
h:: Int
98
107
nc:: Int
99
- function TrapezoidalCollocation (h:: Int = 0 )
108
+ f_threads:: Bool
109
+ h_threads:: Bool
110
+ function TrapezoidalCollocation (h:: Int = 0 ; f_threads= false , h_threads= false )
100
111
if ! (h == 0 || h == 1 )
101
112
throw (ArgumentError (" h argument must be 0 or 1 for TrapezoidalCollocation." ))
102
113
end
103
114
nc = 2 # 2 collocation points per interval for trapezoidal rule
104
- return new (h, nc)
115
+ return new (h, nc, f_threads, h_threads )
105
116
end
106
117
end
107
118
@@ -1171,17 +1182,17 @@ function predict!(
1171
1182
nu, nx̂, ny, nd, nk, Hp = model. nu, mpc. estim. nx̂, model. ny, model. nd, model. nk, mpc. Hp
1172
1183
D̂0 = mpc. D̂0
1173
1184
x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1174
- d0 = @views mpc. d0[1 : nd]
1185
+ d̂0 = @views mpc. d0[1 : nd]
1175
1186
for j= 1 : Hp
1176
1187
u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
1177
1188
û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
1178
1189
k0 = @views K0[(1 + nk* (j- 1 )): (nk* j)]
1179
1190
x̂0next = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
1180
- f̂! (x̂0next, û0, k0, mpc. estim, model, x̂0, u0, d0 )
1191
+ f̂! (x̂0next, û0, k0, mpc. estim, model, x̂0, u0, d̂0 )
1181
1192
x̂0 = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
1182
- d0 = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1193
+ d̂0 = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1183
1194
ŷ0 = @views Ŷ0[(1 + ny* (j- 1 )): (ny* j)]
1184
- ĥ! (ŷ0, mpc. estim, model, x̂0, d0 )
1195
+ ĥ! (ŷ0, mpc. estim, model, x̂0, d̂0 )
1185
1196
end
1186
1197
Ŷ0 .+ = mpc. F # F = Ŷs if mpc.estim is an InternalModel, else F = 0.
1187
1198
x̂0end .= x̂0
@@ -1206,21 +1217,21 @@ in which ``\mathbf{x̂_0}`` is the augmented state extracted from the decision v
1206
1217
"""
1207
1218
function predict! (
1208
1219
Ŷ0, x̂0end, _, _, _,
1209
- mpc:: PredictiveController , model:: NonLinModel , :: TranscriptionMethod ,
1220
+ mpc:: PredictiveController , model:: NonLinModel , transcription :: TranscriptionMethod ,
1210
1221
_ , Z̃
1211
1222
)
1212
1223
nu, ny, nd, nx̂, Hp, Hc = model. nu, model. ny, model. nd, mpc. estim. nx̂, mpc. Hp, mpc. Hc
1224
+ h_threads = transcription. h_threads
1213
1225
X̂0 = @views Z̃[(nu* Hc+ 1 ): (nu* Hc+ nx̂* Hp)] # Z̃ = [ΔU; X̂0; ϵ]
1214
1226
D̂0 = mpc. D̂0
1215
- local x̂0
1216
- for j= 1 : Hp
1227
+ @threadsif h_threads for j= 1 : Hp
1217
1228
x̂0 = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
1218
- d0 = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1229
+ d̂0 = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1219
1230
ŷ0 = @views Ŷ0[(1 + ny* (j- 1 )): (ny* j)]
1220
- ĥ! (ŷ0, mpc. estim, model, x̂0, d0 )
1231
+ ĥ! (ŷ0, mpc. estim, model, x̂0, d̂0 )
1221
1232
end
1222
1233
Ŷ0 .+ = mpc. F # F = Ŷs if mpc.estim is an InternalModel, else F = 0.
1223
- x̂0end .= x̂0
1234
+ x̂0end .= @views X̂0[( 1 + nx̂ * (Hp - 1 )) : (nx̂ * Hp)]
1224
1235
return Ŷ0, x̂0end
1225
1236
end
1226
1237
@@ -1329,28 +1340,30 @@ in which the augmented state ``\mathbf{x̂_0}`` are extracted from the decision
1329
1340
"""
1330
1341
function con_nonlinprogeq! (
1331
1342
geq, X̂0, Û0, K0,
1332
- mpc:: PredictiveController , model:: NonLinModel , :: MultipleShooting , U0, Z̃
1343
+ mpc:: PredictiveController , model:: NonLinModel , transcription :: MultipleShooting , U0, Z̃
1333
1344
)
1334
1345
nu, nx̂, nd, nk = model. nu, mpc. estim. nx̂, model. nd, model. nk
1335
1346
Hp, Hc = mpc. Hp, mpc. Hc
1336
1347
nΔU, nX̂ = nu* Hc, nx̂* Hp
1348
+ f_threads = transcription. f_threads
1337
1349
D̂0 = mpc. D̂0
1338
- X̂0_Z̃ = @views Z̃[(nΔU+ 1 ): (nΔU+ nX̂)]
1339
- x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1340
- d0 = @views mpc. d0[1 : nd]
1341
- # TODO : allow parallel for loop or threads?
1342
- for j= 1 : Hp
1350
+ X̂0_Z̃ = @views Z̃[(nΔU+ 1 ): (nΔU+ nX̂)]
1351
+ @threadsif f_threads for j= 1 : Hp
1352
+ if j < 2
1353
+ x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1354
+ d̂0 = @views mpc. d0[1 : nd]
1355
+ else
1356
+ x̂0 = @views X̂0_Z̃[(1 + nx̂* (j- 2 )): (nx̂* (j- 1 ))]
1357
+ d̂0 = @views D̂0[(1 + nd* (j- 2 )): (nd* (j- 1 ))]
1358
+ end
1343
1359
u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
1344
1360
û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
1345
1361
k0 = @views K0[(1 + nk* (j- 1 )): (nk* j)]
1346
- d0next = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1347
1362
x̂0next = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
1348
1363
x̂0next_Z̃ = @views X̂0_Z̃[(1 + nx̂* (j- 1 )): (nx̂* j)]
1349
1364
ŝnext = @views geq[(1 + nx̂* (j- 1 )): (nx̂* j)]
1350
- f̂! (x̂0next, û0, k0, mpc. estim, model, x̂0, u0, d0 )
1365
+ f̂! (x̂0next, û0, k0, mpc. estim, model, x̂0, u0, d̂0 )
1351
1366
ŝnext .= x̂0next .- x̂0next_Z̃
1352
- x̂0 = x̂0next_Z̃ # using states in Z̃ for next iteration (allow parallel for)
1353
- d0 = d0next
1354
1367
end
1355
1368
return geq
1356
1369
end
@@ -1400,25 +1413,23 @@ function con_nonlinprogeq!(
1400
1413
nu, nx̂, nd, nx, h = model. nu, mpc. estim. nx̂, model. nd, model. nx, transcription. h
1401
1414
Hp, Hc = mpc. Hp, mpc. Hc
1402
1415
nΔU, nX̂ = nu* Hc, nx̂* Hp
1416
+ f_threads = transcription. f_threads
1403
1417
Ts, p = model. Ts, model. p
1404
1418
As, Cs_u = mpc. estim. As, mpc. estim. Cs_u
1405
1419
nk = get_nk (model, transcription)
1406
1420
D̂0 = mpc. D̂0
1407
1421
X̂0_Z̃ = @views Z̃[(nΔU+ 1 ): (nΔU+ nX̂)]
1408
- x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1409
- d0 = @views mpc. d0[1 : nd]
1410
- if ! iszero (h)
1411
- k1, u0, û0 = @views K0[1 : nx], U0[1 : nu], Û0[1 : nu]
1412
- x0, xs = @views x̂0[1 : nx], x̂0[nx+ 1 : end ]
1413
- mul! (û0, Cs_u, xs)
1414
- û0 .+ = u0
1415
- model. f! (k1, x0, û0, d0, p)
1416
- lastk2 = k1
1417
- end
1418
1422
# TODO : allow parallel for loop or threads?
1419
- for j= 1 : Hp
1423
+ @threadsif f_threads for j= 1 : Hp
1424
+ if j < 2
1425
+ x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1426
+ d̂0 = @views mpc. d0[1 : nd]
1427
+ else
1428
+ x̂0 = @views X̂0_Z̃[(1 + nx̂* (j- 2 )): (nx̂* (j- 1 ))]
1429
+ d̂0 = @views D̂0[(1 + nd* (j- 2 )): (nd* (j- 1 ))]
1430
+ end
1420
1431
k0 = @views K0[(1 + nk* (j- 1 )): (nk* j)]
1421
- d0next = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1432
+ d̂0next = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
1422
1433
x̂0next = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
1423
1434
x̂0next_Z̃ = @views X̂0_Z̃[(1 + nx̂* (j- 1 )): (nx̂* j)]
1424
1435
ŝnext = @views geq[(1 + nx̂* (j- 1 )): (nx̂* j)]
@@ -1431,39 +1442,28 @@ function con_nonlinprogeq!(
1431
1442
mul! (xsnext, As, xs)
1432
1443
ssnext .= @. xsnext - xsnext_Z̃
1433
1444
# ----------------- deterministic defects --------------------------------------
1434
- if iszero (h) # piecewise constant manipulated inputs u:
1435
- u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
1436
- û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
1437
- mul! (û0, Cs_u, xs) # ys_u(k) = Cs_u*xs(k)
1438
- û0 .+ = u0 # û0(k) = u0(k) + ys_u(k)
1439
- model. f! (k1, x0, û0, d0, p)
1440
- model. f! (k2, x0next_Z̃, û0, d0next, p)
1441
- else # piecewise linear manipulated inputs u:
1442
- k1 .= lastk2
1443
- j == Hp && break # special case, treated after the loop
1445
+ u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
1446
+ û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
1447
+ mul! (û0, Cs_u, xs) # ys_u(k) = Cs_u*xs(k)
1448
+ û0 .+ = u0 # û0(k) = u0(k) + ys_u(k)
1449
+ if f_threads || h < 1 || j < 2
1450
+ # we need to recompute k1 with multi-threading, even with h==1, since the
1451
+ # last iteration (j-1) may not be executed (iterations are re-orderable)
1452
+ model. f! (k1, x0, û0, d̂0, p)
1453
+ else
1454
+ k1 .= @views K0[(1 + nk* (j- 1 )- nx): (nk* (j- 1 ))] # k2 of of the last iter. j-1
1455
+ end
1456
+ if h < 1 || j ≥ Hp
1457
+ # j = Hp special case: u(k+Hp-1) = u(k+Hp) since Hc ≤ Hp implies Δu(k+Hp) = 0
1458
+ û0next = û0
1459
+ else
1444
1460
u0next = @views U0[(1 + nu* j): (nu* (j+ 1 ))]
1445
1461
û0next = @views Û0[(1 + nu* j): (nu* (j+ 1 ))]
1446
1462
mul! (û0next, Cs_u, xsnext_Z̃) # ys_u(k+1) = Cs_u*xs(k+1)
1447
1463
û0next .+ = u0next # û0(k+1) = u0(k+1) + ys_u(k+1)
1448
- model. f! (k2, x0next_Z̃, û0next, d0next, p)
1449
- lastk2 = k2
1450
1464
end
1465
+ model. f! (k2, x0next_Z̃, û0next, d̂0next, p)
1451
1466
sdnext .= @. x0 - x0next_Z̃ + 0.5 * Ts* (k1 + k2)
1452
- x̂0 = x̂0next_Z̃ # using states in Z̃ for next iteration (allow parallel for)
1453
- d0 = d0next
1454
- end
1455
- if ! iszero (h)
1456
- # j = Hp special case: u(k+Hp-1) = u(k+Hp) since Hc ≤ Hp implies Δu(k+Hp)=0
1457
- x̂0, x̂0next_Z̃ = @views X̂0_Z̃[end - 2 nx̂+ 1 : end - nx̂], X̂0_Z̃[end - nx̂+ 1 : end ]
1458
- k1, k2 = @views K0[end - 2 nx+ 1 : end - nx], K0[end - nx+ 1 : end ] # k1 already filled
1459
- d0next = @views D̂0[end - nd+ 1 : end ]
1460
- û0next, u0next = @views Û0[end - nu+ 1 : end ], U0[end - nu+ 1 : end ] # correspond to u(k+Hp-1)
1461
- x0, x0next_Z̃, xsnext_Z̃ = @views x̂0[1 : nx], x̂0next_Z̃[1 : nx], x̂0next_Z̃[nx+ 1 : end ]
1462
- sdnext = @views geq[end - nx̂+ 1 : end - nx̂+ nx] # ssnext already filled
1463
- mul! (û0next, Cs_u, xsnext_Z̃)
1464
- û0next .+ = u0next
1465
- model. f! (k2, x0next_Z̃, û0next, d0next, p)
1466
- sdnext .= @. x0 - x0next_Z̃ + (Ts/ 2 )* (k1 + k2)
1467
1467
end
1468
1468
return geq
1469
1469
end
0 commit comments