Skip to content

Commit a934f8e

Browse files
authored
Merge pull request #130 from JuliaControl/fix_type_instability
Fix type instability in `LinMPC` and `NonLinMPC` (introduced in 1.1.0)
2 parents f37e9bb + 887bcb2 commit a934f8e

File tree

8 files changed

+104
-80
lines changed

8 files changed

+104
-80
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
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.1.0"
4+
version = "1.1.1"
55

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

src/controller/construct.jl

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct ControllerWeights{NT<:Real}
2727
end
2828

2929
"Include all the data for the constraints of [`PredictiveController`](@ref)"
30-
struct ControllerConstraint{NT<:Real, GCfunc<:Function}
30+
struct ControllerConstraint{NT<:Real, GCfunc<:Union{Nothing, Function}}
3131
ẽx̂ ::Matrix{NT}
3232
fx̂ ::Vector{NT}
3333
gx̂ ::Matrix{NT}
@@ -434,9 +434,7 @@ function validate_args(mpc::PredictiveController, ry, d, D̂, R̂y, R̂u)
434434
size(d) (nd,) && throw(DimensionMismatch("d size $(size(d)) ≠ measured dist. size ($nd,)"))
435435
size(D̂) (nd*Hp,) && throw(DimensionMismatch("D̂ size $(size(D̂)) ≠ measured dist. size × Hp ($(nd*Hp),)"))
436436
size(R̂y) (ny*Hp,) && throw(DimensionMismatch("R̂y size $(size(R̂y)) ≠ output size × Hp ($(ny*Hp),)"))
437-
if ~mpc.noR̂u
438-
size(R̂u) (nu*Hp,) && throw(DimensionMismatch("R̂u size $(size(R̂u)) ≠ manip. input size × Hp ($(nu*Hp),)"))
439-
end
437+
size(R̂u) (nu*Hp,) && throw(DimensionMismatch("R̂u size $(size(R̂u)) ≠ manip. input size × Hp ($(nu*Hp),)"))
440438
end
441439

442440

@@ -668,18 +666,17 @@ end
668666
"""
669667
init_defaultcon_mpc(
670668
estim, C, S, E, ex̂, fx̂, gx̂, jx̂, kx̂, vx̂,
671-
gc!=(_,_,_,_,_,_)->nothing, nc=0
669+
gc!=nothing, nc=0
672670
) -> con, S̃, Ẽ
673671
674672
Init `ControllerConstraint` struct with default parameters based on estimator `estim`.
675673
676674
Also return `S̃` and `Ẽ` matrices for the the augmented decision vector `ΔŨ`.
677675
"""
678676
function init_defaultcon_mpc(
679-
estim::StateEstimator{NT},
680-
Hp, Hc, C, S, E, ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
681-
gc!::GCfunc=(_,_,_,_,_,_)->nothing, nc=0
682-
) where {NT<:Real, GCfunc<:Function}
677+
estim::StateEstimator{NT}, Hp, Hc, C, S, E, ex̂, fx̂, gx̂, jx̂, kx̂, vx̂, bx̂,
678+
gc!::GCfunc=nothing, nc=0
679+
) where {NT<:Real, GCfunc<:Union{Nothing, Function}}
683680
model = estim.model
684681
nu, ny, nx̂ = model.nu, model.ny, estim.nx̂
685682
= isinf(C) ? 0 : 1

src/controller/execute.jl

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -191,32 +191,34 @@ They are computed with these equations using in-place operations:
191191
function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂u)
192192
mul!(mpc.T_lastu0, mpc.T, mpc.estim.lastu0)
193193
ŷ, F, q̃, r = mpc.ŷ, mpc.F, mpc.q̃, mpc.r
194+
Cy, Cu, M_Hp_Ẽ, L_Hp_S̃ = mpc.buffer.Ŷ, mpc.buffer.U, mpc.buffer.Ẽ, mpc.buffer.
194195
ŷ .= evaloutput(mpc.estim, d)
195-
predictstoch!(mpc, mpc.estim) # init mpc.F with Ŷs for InternalModel
196-
F .+= mpc.B
197-
mul!(F, mpc.K, mpc.estim.x̂0, 1, 1)
198-
mul!(F, mpc.V, mpc.estim.lastu0, 1, 1)
196+
predictstoch!(mpc, mpc.estim) # init F with Ŷs for InternalModel
197+
F .+= mpc.B # F = F + B
198+
mul!(F, mpc.K, mpc.estim.x̂0, 1, 1) # F = F + K*x̂0
199+
mul!(F, mpc.V, mpc.estim.lastu0, 1, 1) # F = F + V*lastu0
199200
if model.nd 0
200201
mpc.d0 .= d .- model.dop
201202
mpc.D̂0 .=.- mpc.Dop
202203
mpc.D̂e[1:model.nd] .= d
203204
mpc.D̂e[model.nd+1:end] .=
204-
mul!(F, mpc.G, mpc.d0, 1, 1)
205-
mul!(F, mpc.J, mpc.D̂0, 1, 1)
205+
mul!(F, mpc.G, mpc.d0, 1, 1) # F = F + G*d0
206+
mul!(F, mpc.J, mpc.D̂0, 1, 1) # F = F + J*D̂0
206207
end
208+
# --- output setpoint tracking term ---
207209
mpc.R̂y .= R̂y
208-
Cy = F .- (R̂y .- mpc.Yop)
209-
M_Hp_Ẽ = mpc.weights.M_Hp*mpc.
210-
mul!(q̃, M_Hp_Ẽ', Cy)
210+
Cy .= F .- (R̂y .- mpc.Yop)
211+
mul!(M_Hp_Ẽ, mpc.weights.M_Hp, mpc.)
212+
mul!(q̃, M_Hp_Ẽ', Cy) # q̃ = M_Hp*Ẽ'*Cy
211213
r .= dot(Cy, mpc.weights.M_Hp, Cy)
212-
if ~mpc.noR̂u
213-
mpc.R̂u .= R̂u
214-
Cu = mpc.T_lastu0 .- (R̂u .- mpc.Uop)
215-
L_Hp_S̃ = mpc.weights.L_Hp*mpc.
216-
mul!(q̃, L_Hp_S̃', Cu, 1, 1)
217-
r .+= dot(Cu, mpc.weights.L_Hp, Cu)
218-
end
219-
lmul!(2, q̃)
214+
# --- input setpoint tracking term ---
215+
mpc.R̂u .= R̂u
216+
Cu .= mpc.T_lastu0 .- (R̂u .- mpc.Uop)
217+
mul!(L_Hp_S̃, mpc.weights.L_Hp, mpc.)
218+
mul!(q̃, L_Hp_S̃', Cu, 1, 1) # q̃ = q̃ + L_Hp*S̃'*Cu
219+
r .+= dot(Cu, mpc.weights.L_Hp, Cu)
220+
# --- finalize ---
221+
lmul!(2, q̃) # q̃ = 2*q̃
220222
return nothing
221223
end
222224

@@ -228,17 +230,15 @@ Init `ŷ, F, d0, D̂0, D̂e, R̂y, R̂u` vectors when model is not a [`LinModel
228230
function initpred!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u)
229231
mul!(mpc.T_lastu0, mpc.T, mpc.estim.lastu0)
230232
mpc.ŷ .= evaloutput(mpc.estim, d)
231-
predictstoch!(mpc, mpc.estim) # init mpc.F with Ŷs for InternalModel
233+
predictstoch!(mpc, mpc.estim) # init F with Ŷs for InternalModel
232234
if model.nd 0
233235
mpc.d0 .= d .- model.dop
234236
mpc.D̂0 .=.- mpc.Dop
235237
mpc.D̂e[1:model.nd] .= d
236238
mpc.D̂e[model.nd+1:end] .=
237239
end
238240
mpc.R̂y .= R̂y
239-
if ~mpc.noR̂u
240-
mpc.R̂u .= R̂u
241-
end
241+
mpc.R̂u .= R̂u
242242
return nothing
243243
end
244244

@@ -371,15 +371,15 @@ The function mutates `Ue`, `Ŷe` and `Ū` in arguments, without assuming any i
371371
function extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
372372
ny, nu = model.ny, model.nu
373373
# --- extended manipulated inputs Ue = [U; u(k+Hp-1)] ---
374-
U0 =
375-
U0 .= mul!(U0, mpc.S̃, ΔŨ) .+ mpc.T_lastu0
376-
Ue[1:end-nu] .= U0 .+ mpc.Uop
374+
U =
375+
U .= mul!(U, mpc.S̃, ΔŨ) .+ mpc.T_lastu0 .+ mpc.Uop
376+
Ue[1:end-nu] .= U
377377
# u(k + Hp) = u(k + Hp - 1) since Δu(k+Hp) = 0 (because Hc ≤ Hp):
378-
Ue[end-nu+1:end] .= @views Ue[end-2nu+1:end-nu]
378+
Ue[end-nu+1:end] .= @views U[end-nu+1:end]
379379
# --- extended output predictions Ŷe = [ŷ(k); Ŷ] ---
380380
Ŷe[1:ny] .= mpc.
381381
Ŷe[ny+1:end] .= Ŷ0 .+ mpc.Yop
382-
return Ue, Ŷe
382+
return Ue, Ŷe
383383
end
384384

385385
"""
@@ -418,13 +418,9 @@ function obj_nonlinprog!(
418418
# --- move suppression and slack variable term ---
419419
JΔŨ = dot(ΔŨ, mpc.weights.Ñ_Hc, ΔŨ)
420420
# --- input setpoint tracking term ---
421-
if !mpc.noR̂u
422-
Ū .= @views Ue[1:end-nu]
423-
Ū .= mpc.R̂u .-
424-
JR̂u = dot(Ū, mpc.weights.L_Hp, Ū)
425-
else
426-
JR̂u = 0.0
427-
end
421+
Ū .= @views Ue[1:end-nu]
422+
Ū .= mpc.R̂u .-
423+
JR̂u = dot(Ū, mpc.weights.L_Hp, Ū)
428424
# --- economic term ---
429425
E_JE = obj_econ(mpc, model, Ue, Ŷe)
430426
return JR̂y + JΔŨ + JR̂u + E_JE

src/controller/explicitmpc.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
88
weights::ControllerWeights{NT}
99
R̂u::Vector{NT}
1010
R̂y::Vector{NT}
11-
noR̂u::Bool
1211
::Matrix{NT}
1312
T::Matrix{NT}
1413
T_lastu0::Vector{NT}
@@ -46,7 +45,6 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
4645
L_Hp = Hermitian(convert(Matrix{NT}, L_Hp), :L)
4746
# dummy vals (updated just before optimization):
4847
R̂y, R̂u, T_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
49-
noR̂u = iszero(L_Hp)
5048
S, T = init_ΔUtoU(model, Hp, Hc)
5149
E, G, J, K, V, B = init_predmat(estim, model, Hp, Hc)
5250
# dummy val (updated just before optimization):
@@ -62,13 +60,13 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
6260
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
6361
nΔŨ = size(Ẽ, 2)
6462
ΔŨ = zeros(NT, nΔŨ)
65-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp)
63+
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
6664
mpc = new{NT, SE}(
6765
estim,
6866
ΔŨ, ŷ,
6967
Hp, Hc, nϵ,
7068
weights,
71-
R̂u, R̂y, noR̂u,
69+
R̂u, R̂y,
7270
S̃, T, T_lastu0,
7371
Ẽ, F, G, J, K, V, B,
7472
H̃, q̃, r,

src/controller/linmpc.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct LinMPC{
99
# note: `NT` and the number type `JNT` in `JuMP.GenericModel{JNT}` can be
1010
# different since solvers that support non-Float64 are scarce.
1111
optim::JM
12-
con::ControllerConstraint{NT}
12+
con::ControllerConstraint{NT, Nothing}
1313
ΔŨ::Vector{NT}
1414
::Vector{NT}
1515
Hp::Int
@@ -18,7 +18,6 @@ struct LinMPC{
1818
weights::ControllerWeights{NT}
1919
R̂u::Vector{NT}
2020
R̂y::Vector{NT}
21-
noR̂u::Bool
2221
::Matrix{NT}
2322
T::Matrix{NT}
2423
T_lastu0::Vector{NT}
@@ -50,7 +49,6 @@ struct LinMPC{
5049
weights = ControllerWeights{NT}(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
5150
# dummy vals (updated just before optimization):
5251
R̂y, R̂u, T_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
53-
noR̂u = iszero(L_Hp)
5452
S, T = init_ΔUtoU(model, Hp, Hc)
5553
E, G, J, K, V, B, ex̂, gx̂, jx̂, kx̂, vx̂, bx̂ = init_predmat(estim, model, Hp, Hc)
5654
# dummy vals (updated just before optimization):
@@ -67,13 +65,13 @@ struct LinMPC{
6765
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
6866
nΔŨ = size(Ẽ, 2)
6967
ΔŨ = zeros(NT, nΔŨ)
70-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp)
68+
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
7169
mpc = new{NT, SE, JM}(
7270
estim, optim, con,
7371
ΔŨ, ŷ,
7472
Hp, Hc, nϵ,
7573
weights,
76-
R̂u, R̂y, noR̂u,
74+
R̂u, R̂y,
7775
S̃, T, T_lastu0,
7876
Ẽ, F, G, J, K, V, B,
7977
H̃, q̃, r,

src/controller/nonlinmpc.jl

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ const DEFAULT_NONLINMPC_OPTIMIZER = optimizer_with_attributes(Ipopt.Optimizer,"s
22

33
struct NonLinMPC{
44
NT<:Real,
5-
SE<:StateEstimator,
5+
SE<:StateEstimator,
66
JM<:JuMP.GenericModel,
77
JEfunc<:Function,
8+
GCfunc<:Function,
89
P<:Any
910
} <: PredictiveController{NT}
1011
estim::SE
1112
# note: `NT` and the number type `JNT` in `JuMP.GenericModel{JNT}` can be
1213
# different since solvers that support non-Float64 are scarce.
1314
optim::JM
14-
con::ControllerConstraint{NT}
15+
con::ControllerConstraint{NT, GCfunc}
1516
ΔŨ::Vector{NT}
1617
::Vector{NT}
1718
Hp::Int
@@ -22,7 +23,6 @@ struct NonLinMPC{
2223
p::P
2324
R̂u::Vector{NT}
2425
R̂y::Vector{NT}
25-
noR̂u::Bool
2626
::Matrix{NT}
2727
T::Matrix{NT}
2828
T_lastu0::Vector{NT}
@@ -45,18 +45,23 @@ struct NonLinMPC{
4545
Yop::Vector{NT}
4646
Dop::Vector{NT}
4747
buffer::PredictiveControllerBuffer{NT}
48-
function NonLinMPC{NT, SE, JM, JEfunc, P}(
49-
estim::SE, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE::JEfunc, gc, nc, p::P, optim::JM
50-
) where {NT<:Real, SE<:StateEstimator, JM<:JuMP.GenericModel, JEfunc<:Function, P<:Any}
48+
function NonLinMPC{NT, SE, JM, JEfunc, GCfunc, P}(
49+
estim::SE,
50+
Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE::JEfunc, gc!::GCfunc, nc, p::P, optim::JM
51+
) where {
52+
NT<:Real,
53+
SE<:StateEstimator,
54+
JM<:JuMP.GenericModel,
55+
JEfunc<:Function,
56+
GCfunc<:Function,
57+
P<:Any
58+
}
5159
model = estim.model
5260
nu, ny, nd, nx̂ = model.nu, model.ny, model.nd, estim.nx̂
5361
= copy(model.yop) # dummy vals (updated just before optimization)
54-
validate_JE(NT, JE)
55-
gc! = get_mutating_gc(NT, gc)
5662
weights = ControllerWeights{NT}(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
5763
# dummy vals (updated just before optimization):
5864
R̂y, R̂u, T_lastu0 = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
59-
noR̂u = iszero(L_Hp)
6065
S, T = init_ΔUtoU(model, Hp, Hc)
6166
E, G, J, K, V, B, ex̂, gx̂, jx̂, kx̂, vx̂, bx̂ = init_predmat(estim, model, Hp, Hc)
6267
# dummy vals (updated just before optimization):
@@ -74,14 +79,14 @@ struct NonLinMPC{
7479
test_custom_functions(NT, model, JE, gc!, nc, Uop, Yop, Dop, p)
7580
nΔŨ = size(Ẽ, 2)
7681
ΔŨ = zeros(NT, nΔŨ)
77-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp)
78-
mpc = new{NT, SE, JM, JEfunc, P}(
82+
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
83+
mpc = new{NT, SE, JM, JEfunc, GCfunc, P}(
7984
estim, optim, con,
8085
ΔŨ, ŷ,
8186
Hp, Hc, nϵ,
8287
weights,
8388
JE, p,
84-
R̂u, R̂y, noR̂u,
89+
R̂u, R̂y,
8590
S̃, T, T_lastu0,
8691
Ẽ, F, G, J, K, V, B,
8792
H̃, q̃, r,
@@ -234,7 +239,7 @@ function NonLinMPC(
234239
L_Hp = diagm(repeat(Lwt, Hp)),
235240
Cwt = DEFAULT_CWT,
236241
Ewt = DEFAULT_EWT,
237-
JE::Function = (_,_,_,_) -> 0.0,
242+
JE ::Function = (_,_,_,_) -> 0.0,
238243
gc!::Function = (_,_,_,_,_,_) -> nothing,
239244
gc ::Function = gc!,
240245
nc::Int = 0,
@@ -312,7 +317,7 @@ function NonLinMPC(
312317
L_Hp = diagm(repeat(Lwt, Hp)),
313318
Cwt = DEFAULT_CWT,
314319
Ewt = DEFAULT_EWT,
315-
JE ::JEfunc = (_,_,_,_) -> 0.0,
320+
JE ::JEfunc = (_,_,_,_) -> 0.0,
316321
gc!::Function = (_,_,_,_,_,_) -> nothing,
317322
gc ::Function = gc!,
318323
nc = 0,
@@ -330,8 +335,11 @@ function NonLinMPC(
330335
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
331336
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
332337
end
333-
return NonLinMPC{NT, SE, JM, JEfunc, P}(
334-
estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, gc, nc, p, optim
338+
validate_JE(NT, JE)
339+
gc! = get_mutating_gc(NT, gc)
340+
GCfunc = get_type_mutating_gc(gc!)
341+
return NonLinMPC{NT, SE, JM, JEfunc, GCfunc, P}(
342+
estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, gc!, nc, p, optim
335343
)
336344
end
337345

@@ -388,6 +396,9 @@ function get_mutating_gc(NT, gc)
388396
return gc!
389397
end
390398

399+
"Get the type of the mutating version of the custom constrain function `gc!`."
400+
get_type_mutating_gc(::GCfunc) where {GCfunc<:Function} = GCfunc
401+
391402
"""
392403
test_custom_functions(NT, model::SimModel, JE, gc!, nc, Uop, Yop, Dop, p)
393404
@@ -514,7 +525,7 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
514525
gc = get_tmp(gc_cache, ΔŨ1)
515526
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
516527
Ue, Ŷe = extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
517-
ϵ = (nϵ 0) ? ΔŨ[end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
528+
ϵ = (nϵ 0) ? ΔŨ[end] : 0 # ϵ = 0 if nϵ == 0 (meaning no relaxation)
518529
mpc.con.gc!(gc, Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
519530
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
520531
return obj_nonlinprog!(Ȳ, Ū, mpc, model, Ue, Ŷe, ΔŨ)::T
@@ -533,7 +544,7 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
533544
gc = get_tmp(gc_cache, ΔŨ1)
534545
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
535546
Ue, Ŷe = extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
536-
ϵ = (nϵ 0) ? ΔŨ[end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
547+
ϵ = (nϵ 0) ? ΔŨ[end] : 0 # ϵ = 0 if nϵ == 0 (meaning no relaxation)
537548
mpc.con.gc!(gc, Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
538549
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
539550
end

0 commit comments

Comments
 (0)