Skip to content

Commit fc2ce6a

Browse files
authored
Merge pull request #139 from JuliaControl/reduce_alloc_mpc
added: reduce allocations for `PredictiveController`
2 parents 2c9c0be + 1cac792 commit fc2ce6a

File tree

3 files changed

+46
-38
lines changed

3 files changed

+46
-38
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@ for more detailed examples.
9797
- automatic model augmentation with integrating states for offset-free tracking
9898
- support for unmeasured model outputs
9999
- feedforward action with measured disturbances that supports direct transmission
100-
- custom predictions for:
100+
- custom predictions for (or preview):
101101
- output setpoints
102102
- measured disturbances
103+
- input setpoints
103104
- easy integration with `Plots.jl`
104105
- optimization based on `JuMP.jl`:
105106
- quickly compare multiple optimizers

src/controller/execute.jl

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ results ``\mathbf{u}(k)``. Following the receding horizon principle, the algorit
1818
the optimal future manipulated inputs ``\mathbf{u}(k+1), \mathbf{u}(k+2), ...`` Note that
1919
the method mutates `mpc` internal data but it does not modifies `mpc.estim` states. Call
2020
[`preparestate!(mpc, ym, d)`](@ref) before `moveinput!`, and [`updatestate!(mpc, u, ym, d)`](@ref)
21-
after, to update `mpc` state estimates.
21+
after, to update `mpc` state estimates. Setpoint and measured disturbance previews can
22+
be implemented with the `R̂y`, `R̂u` and `D̂` keyword arguments.
2223
2324
Calling a [`PredictiveController`](@ref) object calls this method.
2425
@@ -35,8 +36,8 @@ See also [`LinMPC`](@ref), [`ExplicitMPC`](@ref), [`NonLinMPC`](@ref).
3536
in the future by default or ``\mathbf{d̂}(k+j)=\mathbf{d}(k)`` for ``j=1`` to ``H_p``.
3637
- `R̂y=repeat(ry, mpc.Hp)` or *`Rhaty`* : predicted output setpoints ``\mathbf{R̂_y}``, constant
3738
in the future by default or ``\mathbf{r̂_y}(k+j)=\mathbf{r_y}(k)`` for ``j=1`` to ``H_p``.
38-
- `R̂u=mpc.Uop` or *`Rhatu`* : predicted manipulated input setpoints, constant in the future
39-
by default or ``\mathbf{r̂_u}(k+j)=\mathbf{u_{op}}`` for ``j=0`` to ``H_p-1``.
39+
- `R̂u=mpc.Uop` or *`Rhatu`* : predicted manipulated input setpoints ``\mathbf{R̂_u}``, constant
40+
in the future by default or ``\mathbf{r̂_u}(k+j)=\mathbf{u_{op}}`` for ``j=0`` to ``H_p-1``.
4041
4142
# Examples
4243
```jldoctest
@@ -53,8 +54,8 @@ function moveinput!(
5354
mpc::PredictiveController,
5455
ry::Vector = mpc.estim.model.yop,
5556
d ::Vector = mpc.buffer.empty;
56-
Dhat ::Vector = repeat!(mpc.buffer.D̂, d, mpc.Hp),
57-
Rhaty::Vector = repeat!(mpc.buffer.R̂y, ry, mpc.Hp),
57+
Dhat ::Vector = repeat!(mpc.buffer.D̂, d, mpc.Hp),
58+
Rhaty::Vector = repeat!(mpc.buffer., ry, mpc.Hp),
5859
Rhatu::Vector = mpc.Uop,
5960
= Dhat,
6061
R̂y = Rhaty,
@@ -127,9 +128,9 @@ function getinfo(mpc::PredictiveController{NT}) where NT<:Real
127128
=
128129
Ŷ .= @views Ŷe[model.ny+1:end]
129130
oldF = copy(mpc.F)
130-
predictstoch!(mpc, mpc.estim)
131-
Ŷs .= mpc.F # predictstoch! init mpc.F with Ŷs value if estim is an InternalModel
132-
mpc.F .= oldF # restore old F value
131+
F = predictstoch!(mpc, mpc.estim)
132+
Ŷs .= F # predictstoch! init mpc.F with Ŷs value if estim is an InternalModel
133+
F .= oldF # restore old F value
133134
info[:ΔU] = mpc.ΔŨ[1:mpc.Hc*model.nu]
134135
info[] = mpc.== 1 ? mpc.ΔŨ[end] : zero(NT)
135136
info[:J] = J
@@ -189,36 +190,26 @@ They are computed with these equations using in-place operations:
189190
```
190191
"""
191192
function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂u)
192-
lastu = mpc.buffer.u
193-
lastu .= mpc.estim.lastu0 .+ model.uop
194-
mul!(mpc.T_lastu, mpc.T, lastu)
195-
ŷ, F, q̃, r = mpc.ŷ, mpc.F, mpc.q̃, mpc.r
196-
Cy, Cu, M_Hp_Ẽ, L_Hp_S̃ = mpc.buffer.Ŷ, mpc.buffer.U, mpc.buffer.Ẽ, mpc.buffer.
197-
ŷ .= evaloutput(mpc.estim, d)
198-
predictstoch!(mpc, mpc.estim) # init F with Ŷs for InternalModel
193+
F = initpred_common!(mpc, model, d, D̂, R̂y, R̂u)
199194
F .+= mpc.B # F = F + B
200195
mul!(F, mpc.K, mpc.estim.x̂0, 1, 1) # F = F + K*x̂0
201196
mul!(F, mpc.V, mpc.estim.lastu0, 1, 1) # F = F + V*lastu0
202197
if model.nd 0
203-
mpc.d0 .= d .- model.dop
204-
mpc.D̂0 .=.- mpc.Dop
205-
mpc.D̂e[1:model.nd] .= d
206-
mpc.D̂e[model.nd+1:end] .=
207198
mul!(F, mpc.G, mpc.d0, 1, 1) # F = F + G*d0
208199
mul!(F, mpc.J, mpc.D̂0, 1, 1) # F = F + J*D̂0
209200
end
201+
Cy, Cu, M_Hp_Ẽ, L_Hp_S̃ = mpc.buffer.Ŷ, mpc.buffer.U, mpc.buffer.Ẽ, mpc.buffer.
202+
q̃, r = mpc.q̃, mpc.r
210203
q̃ .= 0
211204
r .= 0
212205
# --- output setpoint tracking term ---
213-
mpc.R̂y .= R̂y
214206
if !mpc.weights.iszero_M_Hp[]
215207
Cy .= F .+ mpc.Yop .- R̂y
216208
mul!(M_Hp_Ẽ, mpc.weights.M_Hp, mpc.Ẽ)
217209
mul!(q̃, M_Hp_Ẽ', Cy, 1, 1) # q̃ = q̃ + M_Hp*Ẽ'*Cy
218210
r .+= dot(Cy, mpc.weights.M_Hp, Cy) # r = r + Cy'*M_Hp*Cy
219211
end
220212
# --- input setpoint tracking term ---
221-
mpc.R̂u .= R̂u
222213
if !mpc.weights.iszero_L_Hp[]
223214
Cu .= mpc.T_lastu .- R̂u
224215
mul!(L_Hp_S̃, mpc.weights.L_Hp, mpc.S̃)
@@ -236,11 +227,23 @@ end
236227
Init `ŷ, F, d0, D̂0, D̂e, R̂y, R̂u` vectors when model is not a [`LinModel`](@ref).
237228
"""
238229
function initpred!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u)
230+
F = initpred_common!(mpc, model, d, D̂, R̂y, R̂u)
231+
return nothing
232+
end
233+
234+
"""
235+
initpred_common!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u) -> F
236+
237+
Common computations of `initpred!` for all types of [`SimModel`](@ref).
238+
239+
Will init `mpc.F` with 0 values, or with the stochastic predictions `Ŷs` if `mpc.estim` is
240+
an [`InternalModel`](@ref). The function returns `mpc.F`.
241+
"""
242+
function initpred_common!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u)
239243
lastu = mpc.buffer.u
240244
lastu .= mpc.estim.lastu0 .+ model.uop
241245
mul!(mpc.T_lastu, mpc.T, lastu)
242246
mpc.ŷ .= evaloutput(mpc.estim, d)
243-
predictstoch!(mpc, mpc.estim) # init F with Ŷs for InternalModel
244247
if model.nd 0
245248
mpc.d0 .= d .- model.dop
246249
mpc.D̂0 .=.- mpc.Dop
@@ -249,22 +252,23 @@ function initpred!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂
249252
end
250253
mpc.R̂y .= R̂y
251254
mpc.R̂u .= R̂u
252-
return nothing
255+
F = predictstoch!(mpc, mpc.estim) # init mpc.F with Ŷs for InternalModel
256+
return F
253257
end
254258

255259
@doc raw"""
256-
predictstoch!(mpc::PredictiveController, estim::InternalModel)
260+
predictstoch!(mpc::PredictiveController, estim::InternalModel) -> F
257261
258262
Init `mpc.F` vector with ``\mathbf{F = Ŷ_s}`` when `estim` is an [`InternalModel`](@ref).
259263
"""
260-
function predictstoch!(mpc::PredictiveController{NT}, estim::InternalModel) where {NT<:Real}
264+
function predictstoch!(mpc::PredictiveController, estim::InternalModel)
261265
Ŷs = mpc.F
262266
mul!(Ŷs, mpc.Ks, estim.x̂s)
263267
mul!(Ŷs, mpc.Ps, estim.ŷs, 1, 1)
264-
return nothing
268+
return mpc.F
265269
end
266270
"Separate stochastic predictions are not needed if `estim` is not [`InternalModel`](@ref)."
267-
predictstoch!(mpc::PredictiveController, ::StateEstimator) = (mpc.F .= 0; nothing)
271+
predictstoch!(mpc::PredictiveController, ::StateEstimator) = (mpc.F .= 0)
268272

269273
@doc raw"""
270274
linconstraint!(mpc::PredictiveController, model::LinModel)
@@ -320,7 +324,7 @@ function linconstraint!(mpc::PredictiveController, ::SimModel)
320324
mpc.con.b[(n+1):(n+nΔŨ)] .= @. +mpc.con.ΔŨmax
321325
if any(mpc.con.i_b)
322326
lincon = mpc.optim[:linconstraint]
323-
JuMP.set_normalized_rhs(lincon, mpc.con.b[mpc.con.i_b])
327+
@views JuMP.set_normalized_rhs(lincon, mpc.con.b[mpc.con.i_b])
324328
end
325329
return nothing
326330
end
@@ -476,12 +480,14 @@ solution. A failed optimization prints an `@error` log in the REPL and returns t
476480
warm-start value.
477481
"""
478482
function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
479-
optim = mpc.optim
480-
model = mpc.estim.model
483+
model, optim = mpc.estim.model, mpc.optim
484+
nu, Hc = model.nu, mpc.Hc
481485
ΔŨvar::Vector{JuMP.VariableRef} = optim[:ΔŨvar]
482486
# initial ΔŨ (warm-start): [Δu_{k-1}(k); Δu_{k-1}(k+1); ... ; 0_{nu × 1}; ϵ_{k-1}]
483-
ϵ0 = (mpc.== 1) ? mpc.ΔŨ[end] : empty(mpc.ΔŨ)
484-
ΔŨ0 = [mpc.ΔŨ[(model.nu+1):(mpc.Hc*model.nu)]; zeros(NT, model.nu); ϵ0]
487+
ΔŨ0 = mpc.buffer.ΔŨ
488+
ΔŨ0[1:(Hc*nu-nu)] .= @views mpc.ΔŨ[nu+1:Hc*nu]
489+
ΔŨ0[(Hc*nu-nu+1):(Hc*nu)] .= 0
490+
mpc.== 1 && (ΔŨ0[end] = mpc.ΔŨ[end])
485491
JuMP.set_start_value.(ΔŨvar, ΔŨ0)
486492
set_objective_linear_coef!(mpc, ΔŨvar)
487493
try

src/predictive_control.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ abstract type PredictiveController{NT<:Real} end
2121

2222
struct PredictiveControllerBuffer{NT<:Real}
2323
u ::Vector{NT}
24-
R̂y::Vector{NT}
24+
ΔŨ::Vector{NT}
2525
::Vector{NT}
2626
::Vector{NT}
2727
U ::Vector{NT}
@@ -40,15 +40,16 @@ The buffer is used to store intermediate results during computation without allo
4040
function PredictiveControllerBuffer{NT}(
4141
nu::Int, ny::Int, nd::Int, Hp::Int, Hc::Int, nϵ::Int
4242
) where NT <: Real
43+
nΔŨ = nu*Hc +
4344
u = Vector{NT}(undef, nu)
44-
R̂y = Vector{NT}(undef, ny*Hp)
45+
ΔŨ = Vector{NT}(undef, nΔŨ)
4546
= Vector{NT}(undef, nd*Hp)
4647
= Vector{NT}(undef, ny*Hp)
4748
U = Vector{NT}(undef, nu*Hp)
48-
= Matrix{NT}(undef, ny*Hp, nu*Hc +)
49-
= Matrix{NT}(undef, nu*Hp, nu*Hc +)
49+
= Matrix{NT}(undef, ny*Hp, nΔŨ)
50+
= Matrix{NT}(undef, nu*Hp, nΔŨ)
5051
empty = Vector{NT}(undef, 0)
51-
return PredictiveControllerBuffer{NT}(u, R̂y, D̂, Ŷ, U, Ẽ, S̃, empty)
52+
return PredictiveControllerBuffer{NT}(u, ΔŨ, D̂, Ŷ, U, Ẽ, S̃, empty)
5253
end
5354

5455
include("controller/construct.jl")

0 commit comments

Comments
 (0)