Skip to content

Commit edbf158

Browse files
committed
ExplicitMPC seems fully functional
1 parent 76ee83c commit edbf158

File tree

6 files changed

+145
-16
lines changed

6 files changed

+145
-16
lines changed

docs/src/public/predictive_control.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ PredictiveController
6666
LinMPC
6767
```
6868

69+
## ExplicitMPC
70+
71+
```@docs
72+
ExplicitMPC
73+
```
74+
6975
## NonLinMPC
7076

7177
```@docs

src/controller/explicitmpc.jl

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
1010
M_Hp::Diagonal{Float64, Vector{Float64}}
1111
Ñ_Hc::Diagonal{Float64, Vector{Float64}}
1212
L_Hp::Diagonal{Float64, Vector{Float64}}
13+
C::Float64
14+
E::Float64
1315
R̂u::Vector{Float64}
1416
R̂y::Vector{Float64}
1517
S̃_Hp::Matrix{Bool}
@@ -34,16 +36,19 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
3436
model = estim.model
3537
nu, nxd, nxs, ny, nd = model.nu, model.nx, estim.nxs, model.ny, model.nd
3638
x̂d, x̂s, ŷ, Ŷs = zeros(nxd), zeros(nxs), zeros(ny), zeros(ny*Hp)
37-
validate_weights(model, Hp, Hc, Mwt, Nwt, Lwt, Inf, ru)
39+
Cwt = Inf # no slack variable ϵ for ExplicitMPC
40+
Ewt = 0 # economic costs not supported for ExplicitMPC
41+
validate_weights(model, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru)
3842
M_Hp = Diagonal{Float64}(repeat(Mwt, Hp))
3943
N_Hc = Diagonal{Float64}(repeat(Nwt, Hc))
4044
L_Hp = Diagonal{Float64}(repeat(Lwt, Hp))
45+
C = Cwt
4146
# manipulated input setpoint predictions are constant over Hp :
4247
R̂u = ~iszero(Lwt) ? repeat(ru, Hp) : R̂u = Float64[]
4348
R̂y = zeros(ny* Hp) # dummy R̂y (updated just before optimization)
4449
S_Hp, T_Hp, S_Hc, T_Hc = init_ΔUtoU(nu, Hp, Hc)
4550
E, F, G, J, Kd, Q = init_deterpred(model, Hp, Hc)
46-
_ , S̃_Hp, Ñ_Hc, Ẽ = init_defaultcon(model, Hp, Hc, Inf, S_Hp, S_Hc, N_Hc, E)
51+
_ , S̃_Hp, Ñ_Hc, Ẽ = init_defaultcon(model, Hp, Hc, C, S_Hp, S_Hc, N_Hc, E)
4752
P̃, q̃, p = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
4853
Ks, Ps = init_stochpred(estim, Hp)
4954
d, D̂ = zeros(nd), zeros(nd*Hp)
@@ -54,7 +59,7 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
5459
estim,
5560
ΔŨ, x̂d, x̂s, ŷ, Ŷs,
5661
Hp, Hc,
57-
M_Hp, Ñ_Hc, L_Hp, R̂u, R̂y,
62+
M_Hp, Ñ_Hc, L_Hp, Cwt, Ewt, R̂u, R̂y,
5863
S̃_Hp, T_Hp, T_Hc,
5964
Ẽ, F, G, J, Kd, Q, P̃, q̃, p,
6065
Ks, Ps,
@@ -87,15 +92,14 @@ arguments.
8792
- `Mwt=fill(1.0,model.ny)` : main diagonal of ``\mathbf{M}`` weight matrix (vector).
8893
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector).
8994
- `Lwt=fill(0.0,model.nu)` : main diagonal of ``\mathbf{L}`` weight matrix (vector).
90-
- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only.
9195
- `ru=model.uop` : manipulated input setpoints ``\mathbf{r_u}`` (vector).
9296
9397
# Examples
9498
```jldoctest
9599
julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4);
96100
97101
julia> mpc = ExplicitMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
98-
ExplicitMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFilter estimator and:
102+
ExplicitMPC controller with a sample time Ts = 4.0 s, SteadyKalmanFilter estimator and:
99103
30 prediction steps Hp
100104
1 control steps Hc
101105
1 manipulated inputs u
@@ -120,7 +124,7 @@ Use custom state estimator `estim` to construct `ExplicitMPC`.
120124
julia> estim = KalmanFilter(LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4), i_ym=[2]);
121125
122126
julia> mpc = ExplicitMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
123-
ExplicitMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, KalmanFilter estimator and:
127+
ExplicitMPC controller with a sample time Ts = 4.0 s, KalmanFilter estimator and:
124128
30 prediction steps Hp
125129
1 control steps Hc
126130
1 manipulated inputs u
@@ -152,6 +156,8 @@ function ExplicitMPC(
152156
return ExplicitMPC{S}(estim, Hp, Hc, Mwt, Nwt, Lwt, ru)
153157
end
154158

159+
setconstraint!(::ExplicitMPC,kwargs...) = error("ExplicitMPC does not support constraints.")
160+
155161
function Base.show(io::IO, mpc::ExplicitMPC)
156162
Hp, Hc = mpc.Hp, mpc.Hc
157163
nu, nd = mpc.estim.model.nu, mpc.estim.model.nd
@@ -173,4 +179,7 @@ Analytically solve the optimization problem for [`ExplicitMPC`](@ref).
173179
function optim_objective!(mpc::ExplicitMPC)
174180
mpc.ΔŨ[:] = -mpc.\mpc.
175181
return mpc.ΔŨ
176-
end
182+
end
183+
184+
"For [`ExplicitMPC`](@ref), return an empty summary."
185+
get_summary(::ExplicitMPC) = solution_summary(JuMP.Model(), verbose=true)

src/controller/linmpc.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
1313
Ñ_Hc::Diagonal{Float64, Vector{Float64}}
1414
L_Hp::Diagonal{Float64, Vector{Float64}}
1515
C::Float64
16+
E::Float64
1617
R̂u::Vector{Float64}
1718
R̂y::Vector{Float64}
1819
S̃_Hp::Matrix{Bool}
@@ -37,6 +38,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
3738
model = estim.model
3839
nu, nxd, nxs, ny, nd = model.nu, model.nx, estim.nxs, model.ny, model.nd
3940
x̂d, x̂s, ŷ, Ŷs = zeros(nxd), zeros(nxs), zeros(ny), zeros(ny*Hp)
41+
Ewt = 0 # economic costs not supported for LinMPC
4042
validate_weights(model, Hp, Hc, Mwt, Nwt, Lwt, Cwt, ru)
4143
M_Hp = Diagonal{Float64}(repeat(Mwt, Hp))
4244
N_Hc = Diagonal{Float64}(repeat(Nwt, Hc))
@@ -58,7 +60,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
5860
estim, optim, con,
5961
ΔŨ, x̂d, x̂s, ŷ, Ŷs,
6062
Hp, Hc,
61-
M_Hp, Ñ_Hc, L_Hp, Cwt, R̂u, R̂y,
63+
M_Hp, Ñ_Hc, L_Hp, Cwt, Ewt, R̂u, R̂y,
6264
S̃_Hp, T_Hp, T_Hc,
6365
Ẽ, F, G, J, Kd, Q, P̃, q̃, p,
6466
Ks, Ps,

src/controller/nonlinmpc.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,14 @@ function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
321321
return nothing
322322
end
323323

324+
# TODO: déplacer les 2 prochaines méthodes dans predictive_control.jl
325+
324326
"""
325-
obj_nonlinprog(mpc::NonLinMPC, model::LinModel, ΔŨ::Vector{Real})
327+
obj_nonlinprog(mpc::PredictiveController, model::LinModel, ΔŨ::Vector{Real})
326328
327329
Objective function for [`NonLinMPC`](@ref) when `model` is a [`LinModel`](@ref).
328330
"""
329-
function obj_nonlinprog(mpc::NonLinMPC, model::LinModel, Ŷ, ΔŨ::Vector{T}) where {T<:Real}
331+
function obj_nonlinprog(mpc::PredictiveController, model::LinModel, Ŷ, ΔŨ::Vector{T}) where {T<:Real}
330332
J = obj_quadprog(ΔŨ, mpc.P̃, mpc.q̃)
331333
if !iszero(mpc.E)
332334
U = mpc.S̃_Hp*ΔŨ + mpc.T_Hp*(mpc.estim.lastu0 + model.uop)
@@ -339,11 +341,11 @@ function obj_nonlinprog(mpc::NonLinMPC, model::LinModel, Ŷ, ΔŨ::Vector{T})
339341
end
340342

341343
"""
342-
obj_nonlinprog(mpc::NonLinMPC, model::SimModel, ΔŨ::Vector{Real})
344+
obj_nonlinprog(mpc::PredictiveController, model::SimModel, ΔŨ::Vector{Real})
343345
344346
Objective function for [`NonLinMPC`](@ref) when `model` is not a [`LinModel`](@ref).
345347
"""
346-
function obj_nonlinprog(mpc::NonLinMPC, model::SimModel, Ŷ, ΔŨ::Vector{T}) where {T<:Real}
348+
function obj_nonlinprog(mpc::PredictiveController, model::SimModel, Ŷ, ΔŨ::Vector{T}) where {T<:Real}
347349
# --- output setpoint tracking term ---
348350
êy = mpc.R̂y -
349351
JR̂y = êy'*mpc.M_Hp*êy

src/predictive_control.jl

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,23 +303,30 @@ julia> sol_summary, info = getinfo(mpc); round.(info[:Ŷ], digits=3)
303303
```
304304
"""
305305
function getinfo(mpc::PredictiveController)
306-
sol_summary = solution_summary(mpc.optim)
306+
sol_summary = get_summary(mpc)
307+
= predict(mpc, mpc.estim.model, mpc.ΔŨ)
307308
info = Dict{Symbol, InfoDictType}()
308309
info[:ΔU] = mpc.ΔŨ[1:mpc.Hc*mpc.estim.model.nu]
309310
info[] = isinf(mpc.C) ? NaN : mpc.ΔŨ[end]
310-
info[:J] = objective_value(mpc.optim) + mpc.p[begin]
311+
info[:J] = obj_nonlinprog(mpc, mpc.estim.model, Ŷ, mpc.ΔŨ) + mpc.p[begin]
311312
info[:U] = mpc.S̃_Hp*mpc.ΔŨ + mpc.T_Hp*(mpc.estim.lastu0 + mpc.estim.model.uop)
312313
info[:u] = info[:U][1:mpc.estim.model.nu]
313314
info[:d] = mpc.d
314315
info[:D̂] = mpc.
315316
info[:ŷ] = mpc.
316-
info[:Ŷ] = predict(mpc, mpc.estim.model, mpc.ΔŨ)
317+
info[:Ŷ] =
317318
info[:Ŷs] = mpc.Ŷs
318319
info[:Ŷd] = info[:Ŷ] - info[:Ŷs]
319320
info[:R̂y] = mpc.R̂y
320321
info[:R̂u] = mpc.R̂u
321322
return sol_summary, info
322323
end
324+
"""
325+
get_summary(mpc::PredictiveController)
326+
327+
Get optimizer solution summary `sol_summary`.
328+
"""
329+
get_summary(mpc::PredictiveController) = solution_summary(mpc.optim, verbose=true)
323330

324331
"""
325332
setstate!(mpc::PredictiveController, x̂)
@@ -333,7 +340,7 @@ setstate!(mpc::PredictiveController, x̂) = (setstate!(mpc.estim, x̂); return m
333340
334341
Init `mpc.ΔŨ` for warm-starting and the states of `mpc.estim` [`StateEstimator`](@ref).
335342
336-
Before calling [`initstate!(::StateEstimator, _ , _ )`](@ref), it warm-starts ``\mathbf{ΔŨ}``:
343+
Before calling [`initstate!(::StateEstimator,_,_)`](@ref), it warm-starts ``\mathbf{ΔŨ}``:
337344
- If `model` is a [`LinModel`], the vector is filled with the analytical minimum ``J`` of
338345
the unconstrained problem.
339346
- Else, the vector is filled with zeros.

test/test_predictive_control.jl

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,109 @@ end
8585
@test mpc1.estim. [0,0,0,0]
8686
@test_throws ArgumentError updatestate!(mpc1, [0,0])
8787
end
88+
89+
90+
91+
92+
93+
94+
95+
96+
97+
98+
99+
100+
101+
102+
@testset "ExplicitMPC construction" begin
103+
model = LinModel(sys, Ts, i_d=[3])
104+
mpc1 = ExplicitMPC(model, Hp=15)
105+
@test isa(mpc1.estim, SteadyKalmanFilter)
106+
@test size(mpc1.Ẽ,1) == 15*mpc1.estim.model.ny
107+
mpc4 = ExplicitMPC(model, Mwt=[1,2], Hp=15)
108+
@test mpc4.M_Hp Diagonal(diagm(repeat(Float64[1, 2], 15)))
109+
mpc5 = ExplicitMPC(model, Nwt=[3,4], Hc=5)
110+
@test mpc5.Ñ_Hc Diagonal(diagm(repeat(Float64[3, 4], 5)))
111+
mpc6 = ExplicitMPC(model, Lwt=[0,1], ru=[0,50], Hp=15)
112+
@test mpc6.L_Hp Diagonal(diagm(repeat(Float64[0, 1], 15)))
113+
@test mpc6.R̂u repeat([0,50], 15)
114+
kf = KalmanFilter(model)
115+
mpc8 = ExplicitMPC(kf)
116+
@test isa(mpc8.estim, KalmanFilter)
117+
118+
@test_throws ErrorException ExplicitMPC(model, Hp=0)
119+
@test_throws ErrorException ExplicitMPC(model, Hc=0)
120+
@test_throws ErrorException ExplicitMPC(model, Hp=1, Hc=2)
121+
@test_throws ErrorException ExplicitMPC(model, Mwt=[1])
122+
@test_throws ErrorException ExplicitMPC(model, Mwt=[1])
123+
@test_throws ErrorException ExplicitMPC(model, Lwt=[1])
124+
@test_throws ErrorException ExplicitMPC(model, ru=[1])
125+
@test_throws ErrorException ExplicitMPC(model, Mwt=[-1,1])
126+
@test_throws ErrorException ExplicitMPC(model, Nwt=[-1,1])
127+
@test_throws ErrorException ExplicitMPC(model, Lwt=[-1,1])
128+
end
129+
130+
@testset "ExplicitMPC constraints" begin
131+
model = LinModel(sys, Ts, i_d=[3])
132+
mpc = ExplicitMPC(model, Hp=1, Hc=1)
133+
@test_throws ErrorException setconstraint!(mpc, umin=[0.0, 0.0])
134+
end
135+
136+
@testset "ExplicitMPC moves and getinfo" begin
137+
mpc1 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1)
138+
r = [5]
139+
u = moveinput!(mpc1, r)
140+
@test u [1] atol=1e-2
141+
u = mpc1(r)
142+
@test u [1] atol=1e-2
143+
_ , info = getinfo(mpc1)
144+
@test info[:u] u
145+
@test info[:Ŷ][end] r[1] atol=1e-2
146+
mpc2 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1)
147+
u = moveinput!(mpc2, [5])
148+
@test u [1] atol=1e-2
149+
mpc3 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Mwt=[0], Nwt=[0], Lwt=[1], ru=[12])
150+
u = moveinput!(mpc3, [0])
151+
@test u [12] atol=1e-2
152+
end
153+
154+
@testset "ExplicitMPC other methods" begin
155+
linmodel1 = setop!(LinModel(sys,Ts,i_u=[1,2]), uop=[10,50], yop=[50,30])
156+
mpc1 = ExplicitMPC(linmodel1)
157+
@test initstate!(mpc1, [10, 50], [50, 30+1]) [zeros(3); [1]]
158+
setstate!(mpc1, [1,2,3,4])
159+
@test mpc1.estim. [1,2,3,4]
160+
setstate!(mpc1, [0,0,0,0])
161+
updatestate!(mpc1, mpc1.estim.model.uop, mpc1.estim())
162+
@test mpc1.estim. [0,0,0,0]
163+
@test_throws ArgumentError updatestate!(mpc1, [0,0])
164+
end
165+
166+
167+
168+
169+
170+
171+
172+
173+
174+
175+
176+
177+
178+
179+
180+
181+
182+
183+
184+
185+
186+
187+
188+
189+
190+
88191

89192
@testset "NonLinMPC construction" begin
90193
linmodel1 = LinModel(sys,Ts,i_d=[3])

0 commit comments

Comments
 (0)