Skip to content

Commit 985fb09

Browse files
committed
added : ExplicitMPC
1 parent 581064b commit 985fb09

File tree

7 files changed

+224
-11
lines changed

7 files changed

+224
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ for more detailed examples.
8484
- [x] input setpoint tracking
8585
- [x] economic costs (economic model predictive control)
8686
- [ ] terminal cost to ensure nominal stability
87+
- [x] explicit predictive controller for problems without constraint
8788
- [x] soft and hard constraints on:
8889
- [x] output predictions
8990
- [x] manipulated inputs

example/juMPC.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ updatestate!(luenberger, [0,0], [0,0])
5757

5858
mpcluen = LinMPC(luenberger)
5959

60+
mpcexplicit = ExplicitMPC(LinModel(append(tf(3,[2, 1]), tf(2, [6, 1])), 0.1), Hp=10000, Hc=1)
61+
moveinput!(mpcexplicit, [10, 10])
6062

61-
62-
63+
#=
6364
kalmanFilter1 = KalmanFilter(linModel1)
6465
kalmanFilter2 = KalmanFilter(linModel1,nint_ym=0)
6566
@@ -211,3 +212,4 @@ display(pu)
211212
display(py)
212213
213214
=#
215+
=#

src/ModelPredictiveControl.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export SimModel, LinModel, NonLinModel, setop!, setstate!, updatestate!, evalout
1515
export StateEstimator, InternalModel, Luenberger
1616
export SteadyKalmanFilter, KalmanFilter, UnscentedKalmanFilter, ExtendedKalmanFilter
1717
export initstate!
18-
export PredictiveController, LinMPC, NonLinMPC, setconstraint!, moveinput!, getinfo, sim!
18+
export PredictiveController, ExplicitMPC, LinMPC, NonLinMPC, setconstraint!, moveinput!
19+
export getinfo, sim!
1920

2021
"Generate a block diagonal matrix repeating `n` times the matrix `A`."
2122
repeatdiag(A, n::Int) = kron(I(n), A)

src/controller/explicitmpc.jl

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
struct ExplicitMPC{S<:StateEstimator} <: PredictiveController
2+
estim::S
3+
ΔŨ::Vector{Float64}
4+
x̂d::Vector{Float64}
5+
x̂s::Vector{Float64}
6+
::Vector{Float64}
7+
Ŷs::Vector{Float64}
8+
Hp::Int
9+
Hc::Int
10+
M_Hp::Diagonal{Float64, Vector{Float64}}
11+
Ñ_Hc::Diagonal{Float64, Vector{Float64}}
12+
L_Hp::Diagonal{Float64, Vector{Float64}}
13+
R̂u::Vector{Float64}
14+
R̂y::Vector{Float64}
15+
S̃_Hp::Matrix{Bool}
16+
T_Hp::Matrix{Bool}
17+
T_Hc::Matrix{Bool}
18+
::Matrix{Float64}
19+
F ::Vector{Float64}
20+
G ::Matrix{Float64}
21+
J ::Matrix{Float64}
22+
Kd::Matrix{Float64}
23+
Q ::Matrix{Float64}
24+
::Hermitian{Float64, Matrix{Float64}}
25+
::Vector{Float64}
26+
p ::Vector{Float64}
27+
Ks::Matrix{Float64}
28+
Ps::Matrix{Float64}
29+
d::Vector{Float64}
30+
::Vector{Float64}
31+
Yop::Vector{Float64}
32+
Dop::Vector{Float64}
33+
function ExplicitMPC{S}(estim::S, Hp, Hc, Mwt, Nwt, Lwt, ru) where {S<:StateEstimator}
34+
model = estim.model
35+
nu, nxd, nxs, ny, nd = model.nu, model.nx, estim.nxs, model.ny, model.nd
36+
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)
38+
M_Hp = Diagonal{Float64}(repeat(Mwt, Hp))
39+
N_Hc = Diagonal{Float64}(repeat(Nwt, Hc))
40+
L_Hp = Diagonal{Float64}(repeat(Lwt, Hp))
41+
# manipulated input setpoint predictions are constant over Hp :
42+
R̂u = ~iszero(Lwt) ? repeat(ru, Hp) : R̂u = Float64[]
43+
R̂y = zeros(ny* Hp) # dummy R̂y (updated just before optimization)
44+
S_Hp, T_Hp, S_Hc, T_Hc = init_ΔUtoU(nu, Hp, Hc)
45+
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)
47+
P̃, q̃, p = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
48+
Ks, Ps = init_stochpred(estim, Hp)
49+
d, D̂ = zeros(nd), zeros(nd*Hp)
50+
Yop, Dop = repeat(model.yop, Hp), repeat(model.dop, Hp)
51+
nvar = size(Ẽ, 2)
52+
ΔŨ = zeros(nvar)
53+
mpc = new(
54+
estim,
55+
ΔŨ, x̂d, x̂s, ŷ, Ŷs,
56+
Hp, Hc,
57+
M_Hp, Ñ_Hc, L_Hp, R̂u, R̂y,
58+
S̃_Hp, T_Hp, T_Hc,
59+
Ẽ, F, G, J, Kd, Q, P̃, q̃, p,
60+
Ks, Ps,
61+
d, D̂,
62+
Yop, Dop,
63+
)
64+
return mpc
65+
end
66+
end
67+
68+
@doc raw"""
69+
ExplicitMPC(model::LinModel; <keyword arguments>)
70+
71+
Construct an explicit linear predictive controller based on [`LinModel`](@ref) `model`.
72+
73+
The controller minimizes the following objective function at each discrete time ``k``:
74+
```math
75+
\min_{\mathbf{ΔU}} \mathbf{(R̂_y - Ŷ)}' \mathbf{M}_{H_p} \mathbf{(R̂_y - Ŷ)}
76+
+ \mathbf{(ΔU)}' \mathbf{N}_{H_c} \mathbf{(ΔU)}
77+
+ \mathbf{(R̂_u - U)}' \mathbf{L}_{H_p} \mathbf{(R̂_u - U)}
78+
```
79+
80+
This method uses the default state estimator, a [`SteadyKalmanFilter`](@ref) with default
81+
arguments.
82+
83+
# Arguments
84+
- `model::LinModel` : model used for controller predictions and state estimations.
85+
- `Hp=10+nk`: prediction horizon ``H_p``, `nk` is the number of delays in `model`.
86+
- `Hc=2` : control horizon ``H_c``.
87+
- `Mwt=fill(1.0,model.ny)` : main diagonal of ``\mathbf{M}`` weight matrix (vector).
88+
- `Nwt=fill(0.1,model.nu)` : main diagonal of ``\mathbf{N}`` weight matrix (vector).
89+
- `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.
91+
- `ru=model.uop` : manipulated input setpoints ``\mathbf{r_u}`` (vector).
92+
93+
# Examples
94+
```jldoctest
95+
julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4);
96+
97+
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:
99+
30 prediction steps Hp
100+
1 control steps Hc
101+
1 manipulated inputs u
102+
4 states x̂
103+
2 measured outputs ym
104+
0 unmeasured outputs yu
105+
0 measured disturbances d
106+
```
107+
108+
"""
109+
ExplicitMPC(model::LinModel; kwargs...) = ExplicitMPC(SteadyKalmanFilter(model); kwargs...)
110+
111+
"""
112+
ExplicitMPC(estim::StateEstimator; <keyword arguments>)
113+
114+
Use custom state estimator `estim` to construct `ExplicitMPC`.
115+
116+
`estim.model` must be a [`LinModel`](@ref). Else, a [`NonLinMPC`](@ref) is required.
117+
118+
# Examples
119+
```jldoctest
120+
julia> estim = KalmanFilter(LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 4), i_ym=[2]);
121+
122+
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:
124+
30 prediction steps Hp
125+
1 control steps Hc
126+
1 manipulated inputs u
127+
3 states x̂
128+
1 measured outputs ym
129+
1 unmeasured outputs yu
130+
0 measured disturbances d
131+
```
132+
"""
133+
function ExplicitMPC(
134+
estim::S;
135+
Hp::Union{Int, Nothing} = nothing,
136+
Hc::Int = 2,
137+
Mwt = fill(1.0, estim.model.ny),
138+
Nwt = fill(0.1, estim.model.nu),
139+
Lwt = fill(0.0, estim.model.nu),
140+
ru = estim.model.uop,
141+
) where {S<:StateEstimator}
142+
isa(estim.model, LinModel) || error("estim.model type must be LinModel")
143+
poles = eigvals(estim.model.A)
144+
nk = sum(poles .≈ 0)
145+
if isnothing(Hp)
146+
Hp = 10 + nk
147+
end
148+
if Hp nk
149+
@warn("prediction horizon Hp ($Hp) ≤ number of delays in model "*
150+
"($nk), the closed-loop system may be zero-gain (unresponsive) or unstable")
151+
end
152+
return ExplicitMPC{S}(estim, Hp, Hc, Mwt, Nwt, Lwt, ru)
153+
end
154+
155+
function Base.show(io::IO, mpc::ExplicitMPC)
156+
Hp, Hc = mpc.Hp, mpc.Hc
157+
nu, nd = mpc.estim.model.nu, mpc.estim.model.nd
158+
nx̂, nym, nyu = mpc.estim.nx̂, mpc.estim.nym, mpc.estim.nyu
159+
n = maximum(ndigits.((Hp, Hc, nu, nx̂, nym, nyu, nd))) + 1
160+
println(io, "$(typeof(mpc).name.name) controller with a sample time Ts = "*
161+
"$(mpc.estim.model.Ts) s, "*
162+
"$(typeof(mpc.estim).name.name) estimator and:")
163+
println(io, "$(lpad(Hp, n)) prediction steps Hp")
164+
println(io, "$(lpad(Hc, n)) control steps Hc")
165+
print_estim_dim(io, mpc.estim, n)
166+
end
167+
168+
linconstraint!(::ExplicitMPC, ::LinModel) = nothing
169+
170+
"""
171+
Analytically solve the optimization problem for [`ExplicitMPC`](@ref).
172+
"""
173+
function optim_objective!(mpc::ExplicitMPC)
174+
mpc.ΔŨ[:] = -mpc.\mpc.
175+
return mpc.ΔŨ
176+
end

src/estimator/kalman.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ ones, for ``\mathbf{Ĉ^u, D̂_d^u}``).
9595
``\mathbf{Q}`` of `model`, specified as a standard deviation vector.
9696
- `σR=fill(1,length(i_ym))` : main diagonal of the sensor noise covariance ``\mathbf{R}``
9797
of `model` measured outputs, specified as a standard deviation vector.
98-
- `nint_ym=`[`default_nint`](@ref)`(model,i_ym)` : integrator quantity per measured outputs (vector)
99-
for the stochastic model, use `nint_ym=0` for no integrator at all (see Extended Help).
98+
- `nint_u=0`: integrator quantity per manipulated inputs (vector) for the
99+
stochastic model, use `nint_u=0` for no integrator.
100+
- `nint_ym=`[`default_nint`](@ref)`(model,i_ym)` : integrator quantity per measured outputs
101+
(vector) for the stochastic model, use `nint_ym=0` for no integrator (see Extended Help).
100102
- `σQ_int=fill(1,sum(nint_ym))` : same than `σQ` but for the stochastic model covariance
101103
``\mathbf{Q_{int}}`` (composed of output integrators).
102104

src/predictive_control.jl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ function linconstraint!(mpc::PredictiveController, model::SimModel)
512512
end
513513

514514
"""
515-
optim_objective!(mpc::PredictiveController, b, p)
515+
optim_objective!(mpc::PredictiveController)
516516
517517
Optimize the objective function ``J`` of `mpc` controller and return the solution `ΔŨ`.
518518
"""
@@ -1011,11 +1011,7 @@ function Base.show(io::IO, mpc::PredictiveController)
10111011
"$(typeof(mpc.estim).name.name) estimator and:")
10121012
println(io, "$(lpad(Hp, n)) prediction steps Hp")
10131013
println(io, "$(lpad(Hc, n)) control steps Hc")
1014-
println(io, "$(lpad(nu, n)) manipulated inputs u")
1015-
println(io, "$(lpad(nx̂, n)) states x̂")
1016-
println(io, "$(lpad(nym, n)) measured outputs ym")
1017-
println(io, "$(lpad(nyu, n)) unmeasured outputs yu")
1018-
print(io, "$(lpad(nd, n)) measured disturbances d")
1014+
print_estim_dim(io, mpc.estim, n)
10191015
end
10201016

10211017
"Verify that the solver termination status means 'no solution available'."
@@ -1037,5 +1033,6 @@ function (mpc::PredictiveController)(
10371033
return moveinput!(mpc, ry, d; kwargs...)
10381034
end
10391035

1036+
include("controller/explicitmpc.jl")
10401037
include("controller/linmpc.jl")
10411038
include("controller/nonlinmpc.jl")

src/state_estim.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ function Base.show(io::IO, estim::StateEstimator)
3939
n = maximum(ndigits.((nu, nx̂, nym, nyu, nd))) + 1
4040
println(io, "$(typeof(estim).name.name) estimator with a sample time "*
4141
"Ts = $(estim.model.Ts) s, $(typeof(estim.model).name.name) and:")
42+
print_estim_dim(io, estim, n)
43+
end
44+
45+
"Print the overall dimensions of the state estimator `estim` with left padding `n`."
46+
function print_estim_dim(io::IO, estim::StateEstimator, n)
47+
nu, nd = estim.model.nu, estim.model.nd
48+
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
4249
println(io, "$(lpad(nu, n)) manipulated inputs u")
4350
println(io, "$(lpad(nx̂, n)) states x̂")
4451
println(io, "$(lpad(nym, n)) measured outputs ym")
@@ -127,6 +134,33 @@ function init_estimstoch(i_ym, nint_ym)
127134
return Asm, Csm, nint_ym
128135
end
129136

137+
function init_estimstoch_u(nu, nint_u)
138+
if nint_u == 0 # alias for no output integrator at all
139+
nint_u = fill(0, length(nu))
140+
end
141+
#nym = length(i_ym);
142+
if length(nint_u) nu
143+
error("nint_u size ($(length(nint_u))) ≠ manipulated input quantity nu ($nu)")
144+
end
145+
any(nint_u .< 0) && error("nint_u values should be ≥ 0")
146+
nxs = sum(nint_u)
147+
Asm, Csm = zeros(nxs, nxs), zeros(nym, nxs)
148+
if nxs 0 # construct stochastic model state-space matrices (integrators) :
149+
i_Asm, i_Csm = 1, 1
150+
for iym = 1:nym
151+
nint = nint_u[iym]
152+
if nint 0
153+
rows_Asm = (i_Asm):(i_Asm + nint - 1)
154+
Asm[rows_Asm, rows_Asm] = Bidiagonal(ones(nint), ones(nint-1), :L)
155+
Csm[iym, i_Csm+nint-1] = 1
156+
i_Asm += nint
157+
i_Csm += nint
158+
end
159+
end
160+
end
161+
return Asm, Csm, nint_u
162+
end
163+
130164
@doc raw"""
131165
augment_model(model::LinModel, As, Cs; verify_obsv=true) -> Â, B̂u, Ĉ, B̂d, D̂d
132166

0 commit comments

Comments
 (0)