@@ -31,7 +31,7 @@ plant model/constraints.
3131struct SingleShooting <: ShootingMethod end
3232
3333@doc raw """
34- MultipleShooting()
34+ MultipleShooting(; f_threads=false, h_threads=false )
3535
3636Construct a direct multiple shooting [`TranscriptionMethod`](@ref).
3737
@@ -56,24 +56,33 @@ for [`NonLinModel`](@ref).
5656This transcription computes the predictions by calling the augmented discrete-time model
5757in the equality constraint function recursively over ``H_p``, or by updating the linear
5858equality 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.
6062
6163Sparse optimizers like `OSQP` or `Ipopt` and sparse Jacobian computations are recommended
6264for this transcription method.
6365"""
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
6573
6674@doc raw """
67- TrapezoidalCollocation(h::Int=0)
75+ TrapezoidalCollocation(h::Int=0; f_threads=false, h_threads=false )
6876
6977Construct an implicit trapezoidal [`TranscriptionMethod`](@ref) with `h`th order hold.
7078
7179This is the simplest collocation method. It supports continuous-time [`NonLinModel`](@ref)s
7280only. 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.
7786
7887This transcription computes the predictions by calling the continuous-time model in the
7988equality constraint function and by using the implicit trapezoidal rule. It can handle
@@ -96,12 +105,14 @@ transcription method.
96105struct TrapezoidalCollocation <: CollocationMethod
97106 h:: Int
98107 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 )
100111 if ! (h == 0 || h == 1 )
101112 throw (ArgumentError (" h argument must be 0 or 1 for TrapezoidalCollocation." ))
102113 end
103114 nc = 2 # 2 collocation points per interval for trapezoidal rule
104- return new (h, nc)
115+ return new (h, nc, f_threads, h_threads )
105116 end
106117end
107118
@@ -1171,17 +1182,17 @@ function predict!(
11711182 nu, nx̂, ny, nd, nk, Hp = model. nu, mpc. estim. nx̂, model. ny, model. nd, model. nk, mpc. Hp
11721183 D̂0 = mpc. D̂0
11731184 x̂0 = @views mpc. estim. x̂0[1 : nx̂]
1174- d0 = @views mpc. d0[1 : nd]
1185+ d̂0 = @views mpc. d0[1 : nd]
11751186 for j= 1 : Hp
11761187 u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
11771188 û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
11781189 k0 = @views K0[(1 + nk* (j- 1 )): (nk* j)]
11791190 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 )
11811192 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)]
11831194 ŷ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 )
11851196 end
11861197 Ŷ0 .+ = mpc. F # F = Ŷs if mpc.estim is an InternalModel, else F = 0.
11871198 x̂0end .= x̂0
@@ -1206,21 +1217,21 @@ in which ``\mathbf{x̂_0}`` is the augmented state extracted from the decision v
12061217"""
12071218function predict! (
12081219 Ŷ0, x̂0end, _, _, _,
1209- mpc:: PredictiveController , model:: NonLinModel , :: TranscriptionMethod ,
1220+ mpc:: PredictiveController , model:: NonLinModel , transcription :: TranscriptionMethod ,
12101221 _ , Z̃
12111222)
12121223 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
12131225 X̂0 = @views Z̃[(nu* Hc+ 1 ): (nu* Hc+ nx̂* Hp)] # Z̃ = [ΔU; X̂0; ϵ]
12141226 D̂0 = mpc. D̂0
1215- local x̂0
1216- for j= 1 : Hp
1227+ @threadsif h_threads for j= 1 : Hp
12171228 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)]
12191230 ŷ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 )
12211232 end
12221233 Ŷ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)]
12241235 return Ŷ0, x̂0end
12251236end
12261237
@@ -1329,28 +1340,30 @@ in which the augmented state ``\mathbf{x̂_0}`` are extracted from the decision
13291340"""
13301341function con_nonlinprogeq! (
13311342 geq, X̂0, Û0, K0,
1332- mpc:: PredictiveController , model:: NonLinModel , :: MultipleShooting , U0, Z̃
1343+ mpc:: PredictiveController , model:: NonLinModel , transcription :: MultipleShooting , U0, Z̃
13331344)
13341345 nu, nx̂, nd, nk = model. nu, mpc. estim. nx̂, model. nd, model. nk
13351346 Hp, Hc = mpc. Hp, mpc. Hc
13361347 nΔU, nX̂ = nu* Hc, nx̂* Hp
1348+ f_threads = transcription. f_threads
13371349 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
13431359 u0 = @views U0[(1 + nu* (j- 1 )): (nu* j)]
13441360 û0 = @views Û0[(1 + nu* (j- 1 )): (nu* j)]
13451361 k0 = @views K0[(1 + nk* (j- 1 )): (nk* j)]
1346- d0next = @views D̂0[(1 + nd* (j- 1 )): (nd* j)]
13471362 x̂0next = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
13481363 x̂0next_Z̃ = @views X̂0_Z̃[(1 + nx̂* (j- 1 )): (nx̂* j)]
13491364 ŝ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 )
13511366 ŝ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
13541367 end
13551368 return geq
13561369end
@@ -1400,25 +1413,23 @@ function con_nonlinprogeq!(
14001413 nu, nx̂, nd, nx, h = model. nu, mpc. estim. nx̂, model. nd, model. nx, transcription. h
14011414 Hp, Hc = mpc. Hp, mpc. Hc
14021415 nΔU, nX̂ = nu* Hc, nx̂* Hp
1416+ f_threads = transcription. f_threads
14031417 Ts, p = model. Ts, model. p
14041418 As, Cs_u = mpc. estim. As, mpc. estim. Cs_u
14051419 nk = get_nk (model, transcription)
14061420 D̂0 = mpc. D̂0
14071421 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
14181422 # 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
14201431 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)]
14221433 x̂0next = @views X̂0[(1 + nx̂* (j- 1 )): (nx̂* j)]
14231434 x̂0next_Z̃ = @views X̂0_Z̃[(1 + nx̂* (j- 1 )): (nx̂* j)]
14241435 ŝnext = @views geq[(1 + nx̂* (j- 1 )): (nx̂* j)]
@@ -1431,39 +1442,28 @@ function con_nonlinprogeq!(
14311442 mul! (xsnext, As, xs)
14321443 ssnext .= @. xsnext - xsnext_Z̃
14331444 # ----------------- 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
14441460 u0next = @views U0[(1 + nu* j): (nu* (j+ 1 ))]
14451461 û0next = @views Û0[(1 + nu* j): (nu* (j+ 1 ))]
14461462 mul! (û0next, Cs_u, xsnext_Z̃) # ys_u(k+1) = Cs_u*xs(k+1)
14471463 û0next .+ = u0next # û0(k+1) = u0(k+1) + ys_u(k+1)
1448- model. f! (k2, x0next_Z̃, û0next, d0next, p)
1449- lastk2 = k2
14501464 end
1465+ model. f! (k2, x0next_Z̃, û0next, d̂0next, p)
14511466 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)
14671467 end
14681468 return geq
14691469end
0 commit comments