Skip to content

Commit c9a59e1

Browse files
committed
debug : sim! call on ExplicitMPC now works
1 parent ff559db commit c9a59e1

File tree

14 files changed

+194
-159
lines changed

14 files changed

+194
-159
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Our goal is controlling the first output $y_1$, but the second one $y_2$ should
4949

5050
```julia
5151
mpc = LinMPC(model, Mwt=[1, 0], Nwt=[0.1])
52-
mpc = setconstraint!(mpc, ŷmax=[Inf, 35])
52+
mpc = setconstraint!(mpc, ymax=[Inf, 35])
5353
```
5454

5555
The keyword arguments `Mwt` and `Nwt` are the output setpoint tracking and move suppression
@@ -60,7 +60,7 @@ The result is displayed with [`Plots.jl`](https://github.com/JuliaPlots/Plots.jl
6060
using Plots
6161
ry = [5, 0]
6262
res = sim!(mpc, 40, ry)
63-
plot(res, plotry=true, plotŷmax=true)
63+
plot(res, plotry=true, plotymax=true)
6464
```
6565

6666
![StepChangeResponse](/docs/src/assets/readme_result.svg)

docs/src/internals/predictive_control.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Functions: PredictiveController Internals
22

3+
```@contents
4+
Pages = ["predictive_control.md"]
5+
```
6+
37
The prediction methodology of this module is mainly based on Maciejowski textbook [^1].
48

59
[^1]: Maciejowski, J. 2000, "Predictive control : with constraints", 1st ed., Prentice Hall,

docs/src/internals/sim_model.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Functions: SimModel Internals
22

3+
```@contents
4+
Pages = ["sim_model.md"]
5+
```
6+
37
```@docs
48
ModelPredictiveControl.steadystate
59
ModelPredictiveControl.f

docs/src/internals/state_estim.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Functions: StateEstimator Internals
22

3+
```@contents
4+
Pages = ["state_estim.md"]
5+
```
6+
37
## Estimator Initialization
48

59
```@docs

docs/src/manual/installation.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ To install the `ModelPredictiveControl` package, run this command in the Julia R
66
using Pkg; Pkg.add("ModelPredictiveControl")
77
```
88

9+
Doing so will install the package to default Julia environnement, that is, accessible
10+
anywhere. To facilitate sharing of code and reproducibility of results, it is recommended to
11+
install packages in a project environnement. To generate a new project named `MyProject`
12+
with this package in the current working directory, write this in the REPL:
13+
14+
```julia
15+
using Pkg; Pkg.generate("MyProject"); Pkg.activate("."); Pkg.add("ModelPredictiveControl")
16+
```
17+
918
Note that that the construction of linear models typically requires `ss` or `tf` functions,
1019
it is thus recommended to load the package with:
1120

docs/src/manual/linmpc.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,17 @@ We design our [`LinMPC`](@ref) controllers by including the linear level constra
6666
[`setconstraint!`](@ref) (`±Inf` values should be used when there is no bound):
6767

6868
```@example 1
69-
mpc = setconstraint!(LinMPC(model, Hp=15, Hc=2), ŷmin=[45, -Inf])
69+
mpc = setconstraint!(LinMPC(model, Hp=15, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1]), ymin=[45, -Inf])
7070
```
7171

72-
in which `Hp` and `Hc` keyword arguments are the predictive and control horizons
73-
respectively. By default, [`LinMPC`](@ref) controllers use [`OSQP`](https://osqp.org/) to
74-
solve the problem, soft constraints on output predictions ``\mathbf{ŷ}`` to ensure
75-
feasibility, and a [`SteadyKalmanFilter`](@ref) to estimate the plant states. An attentive
76-
reader will also notice that the Kalman filter estimates two additional states compared to
77-
the plant model. These are the integrating states for the unmeasured plant disturbances, and
78-
they are automatically added the model outputs if feasible (see [`SteadyKalmanFilter`](@ref)
72+
in which `Hp`, `Hc` keyword arguments are respectively the predictive and control horizons,
73+
and `Mwt` and `Nwt`, the output setpoint tracking and move suppression weights. By default,
74+
[`LinMPC`](@ref) controllers use [`OSQP`](https://osqp.org/) to solve the problem, soft
75+
constraints on output predictions ``\mathbf{ŷ}`` to ensure feasibility, and a
76+
[`SteadyKalmanFilter`](@ref) to estimate the plant states. An attentive reader will also
77+
notice that the Kalman filter estimates two additional states compared to the plant model.
78+
These are the integrating states for the unmeasured plant disturbances, and they are
79+
automatically added to the model outputs by default if feasible (see [`SteadyKalmanFilter`](@ref)
7980
for details).
8081

8182
Before closing the loop, we call [`initstate!`](@ref) with the actual plant inputs and
@@ -157,7 +158,7 @@ real-life control problems. Constructing a [`LinMPC`](@ref) with `DAQP` and inpu
157158
using JuMP, DAQP
158159
daqp = Model(DAQP.Optimizer)
159160
estim = SteadyKalmanFilter(model, nint_u=[1, 1], nint_ym=[0, 0])
160-
mpc2 = setconstraint!(LinMPC(estim, Hp=15, Hc=2, optim=daqp), ŷmin=[45, -Inf])
161+
mpc2 = setconstraint!(LinMPC(estim, Hp=15, Hc=2, optim=daqp), ymin=[45, -Inf])
161162
```
162163

163164
leads to similar computational times, but it does accelerate the rejection of the load
@@ -169,4 +170,3 @@ initstate!(mpc2, model.uop, model())
169170
u_data2, y_data2, ry_data2 = test_mpc(mpc2, model)
170171
plot_data(t_data, u_data2, y_data2, ry_data2)
171172
```
172-

src/controller/explicitmpc.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
2424
::Hermitian{Float64, Matrix{Float64}}
2525
::Vector{Float64}
2626
p::Vector{Float64}
27+
P̃_chol::Cholesky{Float64, Matrix{Float64}}
2728
Ks::Matrix{Float64}
2829
Ps::Matrix{Float64}
2930
d::Vector{Float64}
@@ -48,6 +49,7 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
4849
E, F, G, J, K, Q = init_predmat(estim, model, Hp, Hc)
4950
_ , S̃_Hp, Ñ_Hc, Ẽ = init_defaultcon(model, Hp, Hc, C, S_Hp, S_Hc, N_Hc, E)
5051
P̃, q̃, p = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
52+
P̃_chol = cholesky(P̃)
5153
Ks, Ps = init_stochpred(estim, Hp)
5254
d, D̂ = zeros(nd), zeros(nd*Hp)
5355
Yop, Dop = repeat(model.yop, Hp), repeat(model.dop, Hp)
@@ -60,6 +62,7 @@ struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
6062
M_Hp, Ñ_Hc, L_Hp, Cwt, Ewt, R̂u, R̂y,
6163
S̃_Hp, T_Hp, T_Hc,
6264
Ẽ, F, G, J, K, Q, P̃, q̃, p,
65+
P̃_chol,
6366
Ks, Ps,
6467
d, D̂,
6568
Yop, Dop,
@@ -177,8 +180,7 @@ linconstraint!(::ExplicitMPC, ::LinModel) = nothing
177180
Analytically solve the optimization problem for [`ExplicitMPC`](@ref).
178181
"""
179182
function optim_objective!(mpc::ExplicitMPC)
180-
mpc.ΔŨ[:] = -mpc.\mpc.
181-
return mpc.ΔŨ
183+
return ldiv!(mpc.ΔŨ, mpc.P̃_chol, -mpc.q̃)
182184
end
183185

184186
"For [`ExplicitMPC`](@ref), return an empty summary."

src/controller/nonlinmpc.jl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,13 @@ function init_optimization!(mpc::NonLinMPC)
285285
@NLobjective(optim, Min, Jfunc(ΔŨvar...))
286286
if nC 0
287287
n = 0
288-
for i in eachindex(con.Ŷmin)
289-
sym = Symbol("C_Ŷmin_$i")
288+
for i in eachindex(con.Ymin)
289+
sym = Symbol("C_Ymin_$i")
290290
register(optim, sym, nvar, Cfunc[n + i], autodiff=true)
291291
end
292-
n = lastindex(con.Ŷmin)
293-
for i in eachindex(con.Ŷmax)
294-
sym = Symbol("C_Ŷmax_$i")
292+
n = lastindex(con.Ymin)
293+
for i in eachindex(con.Ymax)
294+
sym = Symbol("C_Ymax_$i")
295295
register(optim, sym, nvar, Cfunc[n + i], autodiff=true)
296296
end
297297
end
@@ -308,12 +308,12 @@ function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
308308
ΔŨvar = mpc.optim[:ΔŨvar]
309309
con = mpc.con
310310
map(con -> delete(optim, con), all_nonlinear_constraints(optim))
311-
for i in findall(.!isinf.(con.Ŷmin))
312-
f_sym = Symbol("C_Ŷmin_$(i)")
311+
for i in findall(.!isinf.(con.Ymin))
312+
f_sym = Symbol("C_Ymin_$(i)")
313313
add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
314314
end
315-
for i in findall(.!isinf.(con.Ŷmax))
316-
f_sym = Symbol("C_Ŷmax_$(i)")
315+
for i in findall(.!isinf.(con.Ymax))
316+
f_sym = Symbol("C_Ymax_$(i)")
317317
add_nonlinear_constraint(optim, :($(f_sym)($(ΔŨvar...)) <= 0))
318318
end
319319
return nothing
@@ -389,15 +389,15 @@ Nonlinear constrains for [`NonLinMPC`](@ref) when `model` is not a [`LinModel`](
389389
function con_nonlinprog(mpc::NonLinMPC, ::SimModel, Ŷ, ΔŨ::Vector{T}) where {T<:Real}
390390
if !isinf(mpc.C) # constraint softening activated :
391391
ϵ = ΔŨ[end]
392-
C_Ŷmin = (mpc.con.Ŷmin - Ŷ) - ϵ*mpc.con.c_Ŷmin
393-
C_Ŷmax = (Ŷ - mpc.con.Ŷmax) - ϵ*mpc.con.c_Ŷmax
392+
C_Ymin = (mpc.con.Ymin - Ŷ) - ϵ*mpc.con.c_Ymin
393+
C_Ymax = (Ŷ - mpc.con.Ymax) - ϵ*mpc.con.c_Ymax
394394
else # no constraint softening :
395-
C_Ŷmin = (mpc.con.Ŷmin - Ŷ)
396-
C_Ŷmax = (Ŷ - mpc.con.Ŷmax)
395+
C_Ymin = (mpc.con.Ymin - Ŷ)
396+
C_Ymax = (Ŷ - mpc.con.Ymax)
397397
end
398398
# replace -Inf with 0 to avoid INVALID_MODEL error :
399-
C_Ŷmin[isinf.(C_Ŷmin)] .= 0
400-
C_Ŷmax[isinf.(C_Ŷmax)] .= 0
401-
C = [C_Ŷmin; C_Ŷmax]
399+
C_Ymin[isinf.(C_Ymin)] .= 0
400+
C_Ymax[isinf.(C_Ymax)] .= 0
401+
C = [C_Ymin; C_Ymax]
402402
return C
403403
end

src/plot_sim.jl

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ identical to [`sim!(::StateEstimator, ::Int)`](@ref).
131131
```julia-repl
132132
julia> model = LinModel([tf(3, [30, 1]); tf(2, [5, 1])], 4);
133133
134-
julia> mpc = setconstraint!(LinMPC(model, Mwt=[0, 1], Nwt=[0.01], Hp=30), ŷmin=[0, -Inf]);
134+
julia> mpc = setconstraint!(LinMPC(model, Mwt=[0, 1], Nwt=[0.01], Hp=30), ymin=[0, -Inf]);
135135
136136
julia> res = sim!(mpc, 25, [0, 0], y_noise=[0.1], y_step=[-10, 0]);
137137
138-
julia> using Plots; plot(res, plotry=true, plotŷ=true, plotŷmin=true, plotu=true)
138+
julia> using Plots; plot(res, plotry=true, plotŷ=true, plotymin=true, plotu=true)
139139
140140
```
141141
"""
@@ -407,8 +407,8 @@ end
407407
@recipe function plot(
408408
res::SimResult{<:PredictiveController};
409409
plotry = true,
410-
plotŷmin = true,
411-
plotŷmax = true,
410+
plotymin = true,
411+
plotymax = true,
412412
plotŷ = false,
413413
plotu = true,
414414
plotru = true,
@@ -433,6 +433,9 @@ end
433433
(plotx̂ || plotxwithx̂) && (layout_mat = [layout_mat (nx̂, 1)])
434434
layout := layout_mat
435435
xguide --> "Time (s)"
436+
# --- constraints ---
437+
Umin, Umax = getUcon(mpc, nu)
438+
Ymin, Ymax = getYcon(mpc, ny)
436439
# --- outputs y ---
437440
subplot_base = 0
438441
for i in 1:ny
@@ -468,7 +471,7 @@ end
468471
t, res.Ry_data[i, :]
469472
end
470473
end
471-
if plotŷmin && !isinf(mpc.con.Ŷmin[i])
474+
if plotymin && !isinf(Ymin[i])
472475
@series begin
473476
yguide --> "\$y_$i\$"
474477
color --> 4
@@ -477,10 +480,10 @@ end
477480
linewidth --> 1.5
478481
label --> "\$\\mathbf{\\hat{y}_{min}}\$"
479482
legend --> true
480-
t, fill(mpc.con.Ŷmin[i], length(t))
483+
t, fill(Ymin[i], length(t))
481484
end
482485
end
483-
if plotŷmax && !isinf(mpc.con.Ŷmax[i])
486+
if plotymax && !isinf(Ymax[i])
484487
@series begin
485488
yguide --> "\$y_$i\$"
486489
color --> 5
@@ -489,7 +492,7 @@ end
489492
linewidth --> 1.5
490493
label --> "\$\\mathbf{\\hat{y}_{max}}\$"
491494
legend --> true
492-
t, fill(mpc.con.Ŷmax[i], length(t))
495+
t, fill(Ymax[i], length(t))
493496
end
494497
end
495498
end
@@ -518,7 +521,7 @@ end
518521
t, res.Ry_data[i, :]
519522
end
520523
end
521-
if plotumin && !isinf(mpc.con.Umin[i])
524+
if plotumin && !isinf(Umin[i])
522525
@series begin
523526
yguide --> "\$u_$i\$"
524527
color --> 4
@@ -527,10 +530,10 @@ end
527530
linewidth --> 1.5
528531
label --> "\$\\mathbf{u_{min}}\$"
529532
legend --> true
530-
t, fill(mpc.con.Umin[i], length(t))
533+
t, fill(Umin[i], length(t))
531534
end
532535
end
533-
if plotumax && !isinf(mpc.con.Umax[i])
536+
if plotumax && !isinf(Umax[i])
534537
@series begin
535538
yguide --> "\$u_$i\$"
536539
color --> 5
@@ -539,7 +542,7 @@ end
539542
linewidth --> 1.5
540543
label --> "\$\\mathbf{u_{max}}\$"
541544
legend --> true
542-
t, fill(mpc.con.Umax[i], length(t))
545+
t, fill(Umax[i], length(t))
543546
end
544547
end
545548
end
@@ -590,4 +593,10 @@ end
590593
end
591594
end
592595
end
593-
end
596+
end
597+
598+
getUcon(mpc::PredictiveController, _ ) = mpc.con.Umin, mpc.con.Umax
599+
getUcon(mpc::ExplicitMPC, nu) = fill(-Inf, mpc.Hp*nu), fill(+Inf, mpc.Hp*nu)
600+
601+
getYcon(mpc::PredictiveController, _ ) = mpc.con.Ymin, mpc.con.Ymax
602+
getYcon(mpc::ExplicitMPC, ny) = fill(-Inf, mpc.Hp*ny), fill(+Inf, mpc.Hp*ny)

src/precompile_calls.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,43 @@ Ts = 4.0
44
model = setop!(LinModel(sys, Ts), uop=[10, 10], yop=[50, 30])
55
y = model()
66

7-
mpc_im = setconstraint!(LinMPC(InternalModel(model)), ŷmin=[45, -Inf])
7+
mpc_im = setconstraint!(LinMPC(InternalModel(model)), ymin=[45, -Inf])
88
initstate!(mpc_im, model.uop, y)
99
mpc_im.estim([50, 30])
1010
u = mpc_im([55, 30], ym=y)
1111
sim!(mpc_im, 3)
1212

13-
mpc_kf = setconstraint!(LinMPC(KalmanFilter(model)), ŷmin=[45, -Inf])
13+
mpc_kf = setconstraint!(LinMPC(KalmanFilter(model)), ymin=[45, -Inf])
1414
initstate!(mpc_kf, model.uop, model())
1515
mpc_kf.estim()
1616
u = mpc_kf([55, 30])
1717
sim!(mpc_kf, 3, [55, 30])
1818

19-
mpc_lo = setconstraint!(LinMPC(Luenberger(model)), ŷmin=[45, -Inf])
19+
mpc_lo = setconstraint!(LinMPC(Luenberger(model)), ymin=[45, -Inf])
2020
initstate!(mpc_lo, model.uop, model())
2121
mpc_lo.estim()
2222
u = mpc_lo([55, 30])
2323
sim!(mpc_lo, 3, [55, 30])
2424

25-
mpc_ukf = setconstraint!(LinMPC(UnscentedKalmanFilter(model)), ŷmin=[45, -Inf])
25+
mpc_ukf = setconstraint!(LinMPC(UnscentedKalmanFilter(model)), ymin=[45, -Inf])
2626
initstate!(mpc_ukf, model.uop, model())
2727
mpc_ukf.estim()
2828
u = mpc_ukf([55, 3])
2929
sim!(mpc_ukf, 3, [55, 30])
3030

31-
mpc_ekf = setconstraint!(LinMPC(ExtendedKalmanFilter(model)), ŷmin=[45, -Inf])
31+
mpc_ekf = setconstraint!(LinMPC(ExtendedKalmanFilter(model)), ymin=[45, -Inf])
3232
initstate!(mpc_ekf, model.uop, model())
3333
mpc_ekf.estim()
3434
u = mpc_ekf([55, 30])
3535
sim!(mpc_ekf, 3, [55, 30])
3636

37-
mpc_skf = setconstraint!(LinMPC(SteadyKalmanFilter(model)), ŷmin=[45, -Inf])
37+
mpc_skf = setconstraint!(LinMPC(SteadyKalmanFilter(model)), ymin=[45, -Inf])
3838
initstate!(mpc_skf, model.uop, model())
3939
mpc_skf.estim()
4040
u = mpc_skf([55, 30])
4141
sim!(mpc_skf, 3, [55, 30])
4242

43-
nmpc_skf = setconstraint!(NonLinMPC(SteadyKalmanFilter(model), Cwt=Inf), ŷmin=[45, -Inf])
43+
nmpc_skf = setconstraint!(NonLinMPC(SteadyKalmanFilter(model), Cwt=Inf), ymin=[45, -Inf])
4444
initstate!(nmpc_skf, model.uop, model())
4545
nmpc_skf.estim()
4646
u = nmpc_skf([55, 30])
@@ -59,17 +59,17 @@ h(x,_) = model.C*x
5959

6060
nlmodel = setop!(NonLinModel(f, h, Ts, 2, 2, 2), uop=[10, 10], yop=[50, 30])
6161
y = nlmodel()
62-
nmpc_im = setconstraint!(NonLinMPC(InternalModel(nlmodel), Cwt=Inf), ŷmin=[45, -Inf])
62+
nmpc_im = setconstraint!(NonLinMPC(InternalModel(nlmodel), Cwt=Inf), ymin=[45, -Inf])
6363
initstate!(nmpc_im, nlmodel.uop, y)
6464
u = nmpc_im([55, 30], ym=y)
6565
sim!(nmpc_im, 3, [55, 30])
6666

67-
nmpc_ukf = setconstraint!(NonLinMPC(UnscentedKalmanFilter(nlmodel), Cwt=Inf), ŷmin=[45, -Inf])
67+
nmpc_ukf = setconstraint!(NonLinMPC(UnscentedKalmanFilter(nlmodel), Cwt=Inf), ymin=[45, -Inf])
6868
initstate!(nmpc_ukf, nlmodel.uop, y)
6969
u = nmpc_ukf([55, 30])
7070
sim!(nmpc_ukf, 3, [55, 30])
7171

72-
nmpc_ekf = setconstraint!(NonLinMPC(ExtendedKalmanFilter(model), Cwt=Inf), ŷmin=[45, -Inf])
72+
nmpc_ekf = setconstraint!(NonLinMPC(ExtendedKalmanFilter(model), Cwt=Inf), ymin=[45, -Inf])
7373
initstate!(nmpc_ekf, model.uop, model())
7474
u = nmpc_ekf([55, 30])
7575
sim!(nmpc_ekf, 3, [55, 30])

0 commit comments

Comments
 (0)