Skip to content

Commit c564f06

Browse files
authored
Merge pull request #202 from JuliaControl/efficient_conversions
added: be more efficient with weights and conversion matrices for `NonLinMPC`
2 parents 5c85eeb + 52fe019 commit c564f06

File tree

8 files changed

+174
-132
lines changed

8 files changed

+174
-132
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "1.6.0"
4+
version = "1.6.1"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

src/controller/construct.jl

+46-19
Original file line numberDiff line numberDiff line change
@@ -33,41 +33,64 @@ function PredictiveControllerBuffer(
3333
end
3434

3535
"Include all the objective function weights of [`PredictiveController`](@ref)"
36-
struct ControllerWeights{NT<:Real}
37-
M_Hp::Hermitian{NT, Matrix{NT}}
38-
Ñ_Hc::Hermitian{NT, Matrix{NT}}
39-
L_Hp::Hermitian{NT, Matrix{NT}}
36+
struct ControllerWeights{
37+
NT<:Real,
38+
# parameters to support both dense and Diagonal matrices (with specialization):
39+
MW<:AbstractMatrix{NT},
40+
NW<:AbstractMatrix{NT},
41+
LW<:AbstractMatrix{NT},
42+
}
43+
M_Hp::Hermitian{NT, MW}
44+
Ñ_Hc::Hermitian{NT, NW}
45+
L_Hp::Hermitian{NT, LW}
4046
E ::NT
4147
iszero_M_Hp::Vector{Bool}
4248
iszero_Ñ_Hc::Vector{Bool}
4349
iszero_L_Hp::Vector{Bool}
4450
iszero_E::Bool
51+
isinf_C ::Bool
4552
function ControllerWeights{NT}(
46-
model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt=Inf, Ewt=0
47-
) where NT<:Real
53+
model, Hp, Hc, M_Hp::MW, N_Hc::NW, L_Hp::LW, Cwt=Inf, Ewt=0
54+
) where {
55+
NT<:Real,
56+
MW<:AbstractMatrix{NT},
57+
NW<:AbstractMatrix{NT},
58+
LW<:AbstractMatrix{NT}
59+
}
4860
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
49-
# convert `Diagonal` to normal `Matrix` if required:
50-
M_Hp = Hermitian(convert(Matrix{NT}, M_Hp), :L)
51-
N_Hc = Hermitian(convert(Matrix{NT}, N_Hc), :L)
52-
L_Hp = Hermitian(convert(Matrix{NT}, L_Hp), :L)
5361
nΔU = size(N_Hc, 1)
5462
C = Cwt
55-
if !isinf(Cwt)
63+
isinf_C = isinf(C)
64+
if !isinf_C
5665
# ΔŨ = [ΔU; ϵ] (ϵ is the slack variable)
57-
Ñ_Hc = Hermitian([N_Hc zeros(NT, nΔU, 1); zeros(NT, 1, nΔU) C], :L)
66+
Ñ_Hc = [N_Hc zeros(NT, nΔU, 1); zeros(NT, 1, nΔU) C]
67+
isdiag(N_Hc) && (Ñ_Hc = Diagonal(Ñ_Hc)) # NW(Ñ_Hc) does not work on Julia 1.10
5868
else
5969
# ΔŨ = ΔU (only hard constraints)
6070
Ñ_Hc = N_Hc
61-
end
71+
end
72+
M_Hp = Hermitian(M_Hp, :L)
73+
Ñ_Hc = Hermitian(Ñ_Hc, :L)
74+
L_Hp = Hermitian(L_Hp, :L)
6275
E = Ewt
6376
iszero_M_Hp = [iszero(M_Hp)]
6477
iszero_Ñ_Hc = [iszero(Ñ_Hc)]
6578
iszero_L_Hp = [iszero(L_Hp)]
6679
iszero_E = iszero(E)
67-
return new{NT}(M_Hp, Ñ_Hc, L_Hp, E, iszero_M_Hp, iszero_Ñ_Hc, iszero_L_Hp, iszero_E)
80+
return new{NT, MW, NW, LW}(
81+
M_Hp, Ñ_Hc, L_Hp, E,
82+
iszero_M_Hp, iszero_Ñ_Hc, iszero_L_Hp, iszero_E, isinf_C
83+
)
6884
end
6985
end
7086

87+
"Outer constructor to convert weight matrix number type to `NT` if necessary."
88+
function ControllerWeights{NT}(
89+
model, Hp, Hc, M_Hp::MW, N_Hc::NW, L_Hp::LW, Cwt=Inf, Ewt=0
90+
) where {NT<:Real, MW<:AbstractMatrix, NW<:AbstractMatrix, LW<:AbstractMatrix}
91+
return ControllerWeights{NT}(model, Hp, Hc, NT.(M_Hp), NT.(N_Hc), NT.(L_Hp), Cwt, Ewt)
92+
end
93+
7194
"Include all the data for the constraints of [`PredictiveController`](@ref)"
7295
struct ControllerConstraint{NT<:Real, GCfunc<:Union{Nothing, Function}}
7396
# matrices for the terminal constraints:
@@ -478,8 +501,10 @@ end
478501

479502
"""
480503
init_defaultcon_mpc(
481-
estim::StateEstimator, transcription::TranscriptionMethod,
482-
Hp, Hc, C,
504+
estim::StateEstimator,
505+
weights::ControllerWeights
506+
transcription::TranscriptionMethod,
507+
Hp, Hc,
483508
PΔu, Pu, E,
484509
ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
485510
Eŝ, Fŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ,
@@ -491,16 +516,18 @@ Init `ControllerConstraint` struct with default parameters based on estimator `e
491516
Also return `P̃Δu`, `P̃u`, `Ẽ` and `Ẽŝ` matrices for the the augmented decision vector `Z̃`.
492517
"""
493518
function init_defaultcon_mpc(
494-
estim::StateEstimator{NT}, transcription::TranscriptionMethod,
495-
Hp, Hc, C,
519+
estim::StateEstimator{NT},
520+
weights::ControllerWeights,
521+
transcription::TranscriptionMethod,
522+
Hp, Hc,
496523
PΔu, Pu, E,
497524
ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
498525
Eŝ, Fŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ,
499526
gc!::GCfunc = nothing, nc = 0
500527
) where {NT<:Real, GCfunc<:Union{Nothing, Function}}
501528
model = estim.model
502529
nu, ny, nx̂ = model.nu, model.ny, estim.nx̂
503-
= isinf(C) ? 0 : 1
530+
= weights.isinf_C ? 0 : 1
504531
u0min, u0max = fill(convert(NT,-Inf), nu), fill(convert(NT,+Inf), nu)
505532
Δumin, Δumax = fill(convert(NT,-Inf), nu), fill(convert(NT,+Inf), nu)
506533
y0min, y0max = fill(convert(NT,-Inf), ny), fill(convert(NT,+Inf), ny)

src/controller/execute.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂
212212
mul!(F, mpc.G, mpc.d0, 1, 1) # F = F + G*d0
213213
mul!(F, mpc.J, mpc.D̂0, 1, 1) # F = F + J*D̂0
214214
end
215-
Cy, Cu, M_Hp_Ẽ, L_Hp_P̃U = mpc.buffer.Ŷ, mpc.buffer.U, mpc.buffer.Ẽ, mpc.buffer.P̃u
215+
Cy, Cu, M_Hp_Ẽ, L_Hp_P̃u = mpc.buffer.Ŷ, mpc.buffer.U, mpc.buffer.Ẽ, mpc.buffer.P̃u
216216
q̃, r = mpc.q̃, mpc.r
217217
q̃ .= 0
218218
r .= 0
@@ -226,8 +226,8 @@ function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂
226226
# --- input setpoint tracking term ---
227227
if !mpc.weights.iszero_L_Hp[]
228228
Cu .= mpc.Tu_lastu0 .+ mpc.Uop .- R̂u
229-
mul!(L_Hp_P̃U, mpc.weights.L_Hp, mpc.P̃u)
230-
mul!(q̃, L_Hp_P̃U', Cu, 1, 1) # q̃ = q̃ + L_Hp*P̃u'*Cu
229+
mul!(L_Hp_P̃u, mpc.weights.L_Hp, mpc.P̃u)
230+
mul!(q̃, L_Hp_P̃u', Cu, 1, 1) # q̃ = q̃ + L_Hp*P̃u'*Cu
231231
r .+= dot(Cu, mpc.weights.L_Hp, Cu) # r = r + Cu'*L_Hp*Cu
232232
end
233233
# --- finalize ---

src/controller/explicitmpc.jl

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
1+
struct ExplicitMPC{
2+
NT<:Real,
3+
SE<:StateEstimator,
4+
CW<:ControllerWeights
5+
} <: PredictiveController{NT}
26
estim::SE
37
transcription::SingleShooting
48
::Vector{NT}
59
::Vector{NT}
610
Hp::Int
711
Hc::Int
812
::Int
9-
weights::ControllerWeights{NT}
13+
weights::CW
1014
R̂u::Vector{NT}
1115
R̂y::Vector{NT}
1216
P̃Δu::Matrix{NT}
@@ -34,17 +38,12 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
3438
Dop::Vector{NT}
3539
buffer::PredictiveControllerBuffer{NT}
3640
function ExplicitMPC{NT}(
37-
estim::SE, Hp, Hc, M_Hp, N_Hc, L_Hp
38-
) where {NT<:Real, SE<:StateEstimator}
41+
estim::SE, Hp, Hc, weights::CW
42+
) where {NT<:Real, SE<:StateEstimator, CW<:ControllerWeights}
3943
model = estim.model
4044
nu, ny, nd, nx̂ = model.nu, model.ny, model.nd, estim.nx̂
4145
= copy(model.yop) # dummy vals (updated just before optimization)
4246
= 0 # no slack variable ϵ for ExplicitMPC
43-
weights = ControllerWeights{NT}(model, Hp, Hc, M_Hp, N_Hc, L_Hp)
44-
# convert `Diagonal` to normal `Matrix` if required:
45-
M_Hp = Hermitian(convert(Matrix{NT}, M_Hp), :L)
46-
N_Hc = Hermitian(convert(Matrix{NT}, N_Hc), :L)
47-
L_Hp = Hermitian(convert(Matrix{NT}, L_Hp), :L)
4847
# dummy vals (updated just before optimization):
4948
R̂y, R̂u, Tu_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
5049
transcription = SingleShooting() # explicit MPC only supports SingleShooting
@@ -53,7 +52,7 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
5352
E, G, J, K, V, B = init_predmat(model, estim, transcription, Hp, Hc)
5453
# dummy val (updated just before optimization):
5554
F, fx̂ = zeros(NT, ny*Hp), zeros(NT, nx̂)
56-
P̃Δu, P̃u, Ñ_Hc, = PΔu, Pu, N_Hc, E # no slack variable ϵ for ExplicitMPC
55+
P̃Δu, P̃u, Ẽ = PΔu, Pu, E # no slack variable ϵ for ExplicitMPC
5756
= init_quadprog(model, weights, Ẽ, P̃Δu, P̃u)
5857
# dummy vals (updated just before optimization):
5958
q̃, r = zeros(NT, size(H̃, 1)), zeros(NT, 1)
@@ -65,7 +64,7 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
6564
nZ̃ = get_nZ(estim, transcription, Hp, Hc) +
6665
= zeros(NT, nZ̃)
6766
buffer = PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
68-
mpc = new{NT, SE}(
67+
mpc = new{NT, SE, CW}(
6968
estim,
7069
transcription,
7170
Z̃, ŷ,
@@ -131,9 +130,9 @@ function ExplicitMPC(
131130
Mwt = fill(DEFAULT_MWT, model.ny),
132131
Nwt = fill(DEFAULT_NWT, model.nu),
133132
Lwt = fill(DEFAULT_LWT, model.nu),
134-
M_Hp = diagm(repeat(Mwt, Hp)),
135-
N_Hc = diagm(repeat(Nwt, Hc)),
136-
L_Hp = diagm(repeat(Lwt, Hp)),
133+
M_Hp = Diagonal(repeat(Mwt, Hp)),
134+
N_Hc = Diagonal(repeat(Nwt, Hc)),
135+
L_Hp = Diagonal(repeat(Lwt, Hp)),
137136
kwargs...
138137
)
139138
estim = SteadyKalmanFilter(model; kwargs...)
@@ -154,17 +153,18 @@ function ExplicitMPC(
154153
Mwt = fill(DEFAULT_MWT, estim.model.ny),
155154
Nwt = fill(DEFAULT_NWT, estim.model.nu),
156155
Lwt = fill(DEFAULT_LWT, estim.model.nu),
157-
M_Hp = diagm(repeat(Mwt, Hp)),
158-
N_Hc = diagm(repeat(Nwt, Hc)),
159-
L_Hp = diagm(repeat(Lwt, Hp)),
156+
M_Hp = Diagonal(repeat(Mwt, Hp)),
157+
N_Hc = Diagonal(repeat(Nwt, Hc)),
158+
L_Hp = Diagonal(repeat(Lwt, Hp)),
160159
) where {NT<:Real, SE<:StateEstimator{NT}}
161160
isa(estim.model, LinModel) || error("estim.model type must be a LinModel")
162161
nk = estimate_delays(estim.model)
163162
if Hp nk
164163
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
165164
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
166165
end
167-
return ExplicitMPC{NT}(estim, Hp, Hc, M_Hp, N_Hc, L_Hp)
166+
weights = ControllerWeights{NT}(estim.model, Hp, Hc, M_Hp, N_Hc, L_Hp)
167+
return ExplicitMPC{NT}(estim, Hp, Hc, weights)
168168
end
169169

170170
setconstraint!(::ExplicitMPC; kwargs...) = error("ExplicitMPC does not support constraints.")

src/controller/linmpc.jl

+25-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const DEFAULT_LINMPC_TRANSCRIPTION = SingleShooting()
44
struct LinMPC{
55
NT<:Real,
66
SE<:StateEstimator,
7+
CW<:ControllerWeights,
78
TM<:TranscriptionMethod,
89
JM<:JuMP.GenericModel
910
} <: PredictiveController{NT}
@@ -18,7 +19,7 @@ struct LinMPC{
1819
Hp::Int
1920
Hc::Int
2021
::Int
21-
weights::ControllerWeights{NT}
22+
weights::CW
2223
R̂u::Vector{NT}
2324
R̂y::Vector{NT}
2425
P̃Δu::Matrix{NT}
@@ -45,13 +46,18 @@ struct LinMPC{
4546
Dop::Vector{NT}
4647
buffer::PredictiveControllerBuffer{NT}
4748
function LinMPC{NT}(
48-
estim::SE, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt,
49+
estim::SE, Hp, Hc, weights::CW,
4950
transcription::TM, optim::JM
50-
) where {NT<:Real, SE<:StateEstimator, TM<:TranscriptionMethod, JM<:JuMP.GenericModel}
51+
) where {
52+
NT<:Real,
53+
SE<:StateEstimator,
54+
CW<:ControllerWeights,
55+
TM<:TranscriptionMethod,
56+
JM<:JuMP.GenericModel
57+
}
5158
model = estim.model
5259
nu, ny, nd, nx̂ = model.nu, model.ny, model.nd, estim.nx̂
5360
= copy(model.yop) # dummy vals (updated just before optimization)
54-
weights = ControllerWeights{NT}(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
5561
# dummy vals (updated just before optimization):
5662
R̂y, R̂u, Tu_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
5763
PΔu = init_ZtoΔU(estim, transcription, Hp, Hc)
@@ -63,8 +69,9 @@ struct LinMPC{
6369
# dummy vals (updated just before optimization):
6470
F, fx̂, Fŝ = zeros(NT, ny*Hp), zeros(NT, nx̂), zeros(NT, nx̂*Hp)
6571
con, nϵ, P̃Δu, P̃u, Ẽ, Ẽŝ = init_defaultcon_mpc(
66-
estim, transcription,
67-
Hp, Hc, Cwt, PΔu, Pu, E,
72+
estim, weights, transcription,
73+
Hp, Hc,
74+
PΔu, Pu, E,
6875
ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
6976
Eŝ, Fŝ, Gŝ, Jŝ, Kŝ, Vŝ, Bŝ
7077
)
@@ -78,7 +85,7 @@ struct LinMPC{
7885
nZ̃ = get_nZ(estim, transcription, Hp, Hc) +
7986
= zeros(NT, nZ̃)
8087
buffer = PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
81-
mpc = new{NT, SE, TM, JM}(
88+
mpc = new{NT, SE, CW, TM, JM}(
8289
estim, transcription, optim, con,
8390
Z̃, ŷ,
8491
Hp, Hc, nϵ,
@@ -140,9 +147,9 @@ arguments. This controller allocates memory at each time step for the optimizati
140147
- `Mwt=fill(1.0,model.ny)` : main diagonal of ``\mathbf{M}`` weight matrix (vector).
141148
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector).
142149
- `Lwt=fill(0.0,model.nu)` : main diagonal of ``\mathbf{L}`` weight matrix (vector).
143-
- `M_Hp=diagm(repeat(Mwt,Hp))` : positive semidefinite symmetric matrix ``\mathbf{M}_{H_p}``.
144-
- `N_Hc=diagm(repeat(Nwt,Hc))` : positive semidefinite symmetric matrix ``\mathbf{N}_{H_c}``.
145-
- `L_Hp=diagm(repeat(Lwt,Hp))` : positive semidefinite symmetric matrix ``\mathbf{L}_{H_p}``.
150+
- `M_Hp=Diagonal(repeat(Mwt,Hp))` : positive semidefinite symmetric matrix ``\mathbf{M}_{H_p}``.
151+
- `N_Hc=Diagonal(repeat(Nwt,Hc))` : positive semidefinite symmetric matrix ``\mathbf{N}_{H_c}``.
152+
- `L_Hp=Diagonal(repeat(Lwt,Hp))` : positive semidefinite symmetric matrix ``\mathbf{L}_{H_p}``.
146153
- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only.
147154
- `transcription=SingleShooting()` : a [`TranscriptionMethod`](@ref) for the optimization.
148155
- `optim=JuMP.Model(OSQP.MathOptInterfaceOSQP.Optimizer)` : quadratic optimizer used in
@@ -199,9 +206,9 @@ function LinMPC(
199206
Mwt = fill(DEFAULT_MWT, model.ny),
200207
Nwt = fill(DEFAULT_NWT, model.nu),
201208
Lwt = fill(DEFAULT_LWT, model.nu),
202-
M_Hp = diagm(repeat(Mwt, Hp)),
203-
N_Hc = diagm(repeat(Nwt, Hc)),
204-
L_Hp = diagm(repeat(Lwt, Hp)),
209+
M_Hp = Diagonal(repeat(Mwt, Hp)),
210+
N_Hc = Diagonal(repeat(Nwt, Hc)),
211+
L_Hp = Diagonal(repeat(Lwt, Hp)),
205212
Cwt = DEFAULT_CWT,
206213
transcription::TranscriptionMethod = DEFAULT_LINMPC_TRANSCRIPTION,
207214
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_LINMPC_OPTIMIZER, add_bridges=false),
@@ -242,9 +249,9 @@ function LinMPC(
242249
Mwt = fill(DEFAULT_MWT, estim.model.ny),
243250
Nwt = fill(DEFAULT_NWT, estim.model.nu),
244251
Lwt = fill(DEFAULT_LWT, estim.model.nu),
245-
M_Hp = diagm(repeat(Mwt, Hp)),
246-
N_Hc = diagm(repeat(Nwt, Hc)),
247-
L_Hp = diagm(repeat(Lwt, Hp)),
252+
M_Hp = Diagonal(repeat(Mwt, Hp)),
253+
N_Hc = Diagonal(repeat(Nwt, Hc)),
254+
L_Hp = Diagonal(repeat(Lwt, Hp)),
248255
Cwt = DEFAULT_CWT,
249256
transcription::TranscriptionMethod = DEFAULT_LINMPC_TRANSCRIPTION,
250257
optim::JM = JuMP.Model(DEFAULT_LINMPC_OPTIMIZER, add_bridges=false),
@@ -255,7 +262,8 @@ function LinMPC(
255262
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
256263
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
257264
end
258-
return LinMPC{NT}(estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, transcription, optim)
265+
weights = ControllerWeights{NT}(estim.model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
266+
return LinMPC{NT}(estim, Hp, Hc, weights, transcription, optim)
259267
end
260268

261269
"""

0 commit comments

Comments
 (0)