Skip to content

Commit 1cc1a5c

Browse files
committed
seperate source into construct.jl and execute.jl files
1 parent 170892a commit 1cc1a5c

File tree

14 files changed

+2662
-2615
lines changed

14 files changed

+2662
-2615
lines changed

src/ModelPredictiveControl.jl

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,7 @@ export default_nint, initstate!
2020
export PredictiveController, ExplicitMPC, LinMPC, NonLinMPC, setconstraint!, moveinput!
2121
export SimResult, getinfo, sim!
2222

23-
"Termination status that means 'no solution available'."
24-
const FATAL_STATUSES = [
25-
INFEASIBLE, DUAL_INFEASIBLE, LOCALLY_INFEASIBLE, INFEASIBLE_OR_UNBOUNDED,
26-
NUMERICAL_ERROR, INVALID_MODEL, INVALID_OPTION, INTERRUPTED,
27-
OTHER_ERROR
28-
]
29-
30-
"Evaluate the quadratic programming objective function `0.5x'*H*x + q'*x` at `x`."
31-
obj_quadprog(x, H, q) = 0.5*dot(x, H, x) + q'*x # dot(x, H, x) is faster than x'*H*x
32-
33-
"Generate a block diagonal matrix repeating `n` times the matrix `A`."
34-
repeatdiag(A, n::Int) = kron(I(n), A)
35-
23+
include("general.jl")
3624
include("sim_model.jl")
3725
include("state_estim.jl")
3826
include("predictive_control.jl")
@@ -44,7 +32,7 @@ include("plot_sim.jl")
4432
@compile_workload begin
4533
# all calls in this block will be precompiled, regardless of whether
4634
# they belong to your package or not (on Julia 1.8 and higher)
47-
include("precompile_calls.jl")
35+
include("precompile.jl")
4836
end
4937
end
5038

src/controller/construct.jl

Lines changed: 843 additions & 0 deletions
Large diffs are not rendered by default.

src/controller/execute.jl

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
@doc raw"""
2+
initstate!(mpc::PredictiveController, u, ym, d=[]) -> x̂
3+
4+
Init the states of `mpc.estim` [`StateEstimator`](@ref) and warm start `mpc.ΔŨ` at zero.
5+
"""
6+
function initstate!(mpc::PredictiveController, u, ym, d=empty(mpc.estim.x̂))
7+
mpc.ΔŨ .= 0
8+
return initstate!(mpc.estim, u, ym, d)
9+
end
10+
11+
@doc raw"""
12+
moveinput!(mpc::PredictiveController, ry=mpc.estim.model.yop, d=[]; <keyword args>) -> u
13+
14+
Compute the optimal manipulated input value `u` for the current control period.
15+
16+
Solve the optimization problem of `mpc` [`PredictiveController`](@ref) and return the
17+
results ``\mathbf{u}(k)``. Following the receding horizon principle, the algorithm discards
18+
the optimal future manipulated inputs ``\mathbf{u}(k+1), \mathbf{u}(k+2), ...`` Note that
19+
the method mutates `mpc` internal data but it does not modifies `mpc.estim` states. Call
20+
[`updatestate!(mpc, u, ym, d)`](@ref) to update `mpc` state estimates.
21+
22+
Calling a [`PredictiveController`](@ref) object calls this method.
23+
24+
See also [`LinMPC`](@ref), [`ExplicitMPC`](@ref), [`NonLinMPC`](@ref).
25+
26+
# Arguments
27+
- `mpc::PredictiveController` : solve optimization problem of `mpc`.
28+
- `ry=mpc.estim.model.yop` : current output setpoints ``\mathbf{r_y}(k)``.
29+
- `d=[]` : current measured disturbances ``\mathbf{d}(k)``.
30+
- `D̂=repeat(d, mpc.Hp)` : predicted measured disturbances ``\mathbf{D̂}``, constant in the
31+
future by default or ``\mathbf{d̂}(k+j)=\mathbf{d}(k)`` for ``j=1`` to ``H_p``.
32+
- `R̂y=repeat(ry, mpc.Hp)` : predicted output setpoints ``\mathbf{R̂_y}``, constant in the
33+
future by default or ``\mathbf{r̂_y}(k+j)=\mathbf{r_y}(k)`` for ``j=1`` to ``H_p``.
34+
- `R̂u=repeat(mpc.estim.model.uop, mpc.Hp)` : predicted manipulated input setpoints, constant
35+
in the future by default or ``\mathbf{r̂_u}(k+j)=\mathbf{u_{op}}`` for ``j=0`` to ``H_p-1``.
36+
- `ym=nothing` : current measured outputs ``\mathbf{y^m}(k)``, only required if `mpc.estim`
37+
is an [`InternalModel`](@ref).
38+
39+
# Examples
40+
```jldoctest
41+
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1);
42+
43+
julia> ry = [5]; u = moveinput!(mpc, ry); round.(u, digits=3)
44+
1-element Vector{Float64}:
45+
1.0
46+
```
47+
"""
48+
function moveinput!(
49+
mpc::PredictiveController,
50+
ry::Vector = mpc.estim.model.yop,
51+
d ::Vector = empty(mpc.estim.x̂);
52+
::Vector = repeat(d, mpc.Hp),
53+
R̂y::Vector = repeat(ry, mpc.Hp),
54+
R̂u::Vector = mpc.noR̂u ? empty(mpc.estim.x̂) : repeat(mpc.estim.model.uop, mpc.Hp),
55+
ym::Union{Vector, Nothing} = nothing
56+
)
57+
validate_args(mpc, ry, d, D̂, R̂y, R̂u)
58+
initpred!(mpc, mpc.estim.model, d, ym, D̂, R̂y, R̂u)
59+
linconstraint!(mpc, mpc.estim.model)
60+
ΔŨ = optim_objective!(mpc)
61+
Δu = ΔŨ[1:mpc.estim.model.nu] # receding horizon principle: only Δu(k) is used (1st one)
62+
u = mpc.estim.lastu0 + mpc.estim.model.uop + Δu
63+
return u
64+
end
65+
66+
67+
68+
@doc raw"""
69+
getinfo(mpc::PredictiveController) -> info
70+
71+
Get additional information about `mpc` controller optimum to ease troubleshooting.
72+
73+
The function should be called after calling [`moveinput!`](@ref). It returns the dictionary
74+
`info` with the following fields:
75+
76+
- `:ΔU` : optimal manipulated input increments over `Hc`, ``\mathbf{ΔU}``.
77+
- `:ϵ` : optimal slack variable, ``ϵ``.
78+
- `:J` : objective value optimum, ``J``.
79+
- `:U` : optimal manipulated inputs over `Hp`, ``\mathbf{U}``.
80+
- `:u` : current optimal manipulated input, ``\mathbf{u}(k)``.
81+
- `:d` : current measured disturbance, ``\mathbf{d}(k)``.
82+
- `:D̂` : predicted measured disturbances over `Hp`, ``\mathbf{D̂}``.
83+
- `:ŷ` : current estimated output, ``\mathbf{ŷ}(k)``.
84+
- `:Ŷ` : optimal predicted outputs over `Hp`, ``\mathbf{Ŷ}``.
85+
- `:x̂end`: optimal terminal states, ``\mathbf{x̂}_{k-1}(k+H_p)``.
86+
- `:Ŷs` : predicted stochastic output over `Hp` of [`InternalModel`](@ref), ``\mathbf{Ŷ_s}``.
87+
- `:R̂y` : predicted output setpoint over `Hp`, ``\mathbf{R̂_y}``.
88+
- `:R̂u` : predicted manipulated input setpoint over `Hp`, ``\mathbf{R̂_u}``.
89+
90+
For [`LinMPC`](@ref) and [`NonLinMPC`](@ref), the field `:sol` also contains the optimizer
91+
solution summary that can be printed. Lastly, the optimal economic cost `:JE` is also
92+
available for [`NonLinMPC`](@ref).
93+
94+
# Examples
95+
```jldoctest
96+
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1, Hc=1);
97+
98+
julia> u = moveinput!(mpc, [10]);
99+
100+
julia> round.(getinfo(mpc)[:Ŷ], digits=3)
101+
1-element Vector{Float64}:
102+
10.0
103+
```
104+
"""
105+
function getinfo(mpc::PredictiveController{NT}) where NT<:Real
106+
info = Dict{Symbol, Union{JuMP._SolutionSummary, Vector{NT}, NT}}()
107+
Ŷ, x̂ = similar(mpc.Ŷop), similar(mpc.estim.x̂)
108+
Ŷ, x̂end = predict!(Ŷ, x̂, mpc, mpc.estim.model, mpc.ΔŨ)
109+
info[:ΔU] = mpc.ΔŨ[1:mpc.Hc*mpc.estim.model.nu]
110+
info[] = isinf(mpc.C) ? NaN : mpc.ΔŨ[end]
111+
info[:J] = obj_nonlinprog(mpc, mpc.estim.model, Ŷ, mpc.ΔŨ)
112+
info[:U] = mpc.*mpc.ΔŨ + mpc.T*(mpc.estim.lastu0 + mpc.estim.model.uop)
113+
info[:u] = info[:U][1:mpc.estim.model.nu]
114+
info[:d] = mpc.d0 + mpc.estim.model.dop
115+
info[:D̂] = mpc.D̂0 + mpc.Dop
116+
info[:ŷ] = mpc.
117+
info[:Ŷ] =
118+
info[:x̂end] =end
119+
info[:Ŷs] = mpc.Ŷop - repeat(mpc.estim.model.yop, mpc.Hp) # Ŷop = Ŷs + Yop
120+
info[:R̂y] = mpc.R̂y
121+
info[:R̂u] = mpc.R̂u
122+
info = addinfo!(info, mpc)
123+
return info
124+
end
125+
126+
"""
127+
addinfo!(info, mpc::PredictiveController) -> info
128+
129+
By default, add the solution summary `:sol` that can be printed to `info`.
130+
"""
131+
function addinfo!(info, mpc::PredictiveController)
132+
info[:sol] = solution_summary(mpc.optim, verbose=true)
133+
return info
134+
end
135+
136+
137+
@doc raw"""
138+
initpred!(mpc, model::LinModel, d, ym, D̂, R̂y, R̂u)
139+
140+
Init linear model prediction matrices `F, q̃, p` and current estimated output `ŷ`.
141+
142+
See [`init_predmat`](@ref) and [`init_quadprog`](@ref) for the definition of the matrices.
143+
"""
144+
function initpred!(mpc::PredictiveController, model::LinModel, d, ym, D̂, R̂y, R̂u)
145+
mpc.ŷ[:] = evalŷ(mpc.estim, ym, d)
146+
predictstoch!(mpc, mpc.estim, d, ym) # init mpc.Ŷop for InternalModel
147+
mpc.F[:] = mpc.K * mpc.estim.+ mpc.V * mpc.estim.lastu0 + mpc.Ŷop
148+
if model.nd 0
149+
mpc.d0[:], mpc.D̂0[:] = d - model.dop, D̂ - mpc.Dop
150+
mpc.D̂E[:] = [d; D̂]
151+
mpc.F[:] = mpc.F + mpc.G * mpc.d0 + mpc.J * mpc.D̂0
152+
end
153+
mpc.R̂y[:] = R̂y
154+
= mpc.F - mpc.R̂y
155+
mpc.q̃[:] = 2(mpc.M_Hp*mpc.Ẽ)'*
156+
mpc.p[] = dot(Ẑ, mpc.M_Hp, Ẑ)
157+
if ~mpc.noR̂u
158+
mpc.R̂u[:] = R̂u
159+
lastu = mpc.estim.lastu0 + model.uop
160+
= mpc.T*lastu - mpc.R̂u
161+
mpc.q̃[:] = mpc.+ 2(mpc.L_Hp*mpc.S̃)'*
162+
mpc.p[] = mpc.p[] + dot(Ẑ, mpc.L_Hp, Ẑ)
163+
end
164+
return nothing
165+
end
166+
167+
@doc raw"""
168+
initpred!(mpc::PredictiveController, model::SimModel, d, ym, D̂, R̂y, R̂u)
169+
170+
Init `ŷ, Ŷop, d0, D̂0` matrices when model is not a [`LinModel`](@ref).
171+
172+
`d0` and `D̂0` are the measured disturbances and its predictions without the operating points
173+
``\mathbf{d_{op}}``. The vector `Ŷop` is kept unchanged if `mpc.estim` is not an
174+
[`InternalModel`](@ref).
175+
"""
176+
function initpred!(mpc::PredictiveController, model::SimModel, d, ym, D̂, R̂y, R̂u)
177+
mpc.ŷ[:] = evalŷ(mpc.estim, ym, d)
178+
predictstoch!(mpc, mpc.estim, d, ym) # init mpc.Ŷop for InternalModel
179+
if model.nd 0
180+
mpc.d0[:], mpc.D̂0[:] = d - model.dop, D̂ - mpc.Dop
181+
mpc.D̂E[:] = [d; D̂]
182+
end
183+
mpc.R̂y[:] = R̂y
184+
if ~mpc.noR̂u
185+
mpc.R̂u[:] = R̂u
186+
end
187+
return nothing
188+
end
189+
190+
@doc raw"""
191+
predictstoch!(mpc::PredictiveController, estim::InternalModel, x̂s, d, ym)
192+
193+
Init `Ŷop` vector when if `estim` is an [`InternalModel`](@ref).
194+
195+
The vector combines the output operating points and the stochastic predictions:
196+
``\mathbf{Ŷ_{op} = Ŷ_{s} + Y_{op}}`` (both values are constant between the nonlinear
197+
programming iterations).
198+
"""
199+
function predictstoch!(
200+
mpc::PredictiveController{NT}, estim::InternalModel, d, ym
201+
) where {NT<:Real}
202+
isnothing(ym) && error("Predictive controllers with InternalModel need the measured "*
203+
"outputs ym in keyword argument to compute control actions u")
204+
ŷd = h(estim.model, estim.x̂d, d - estim.model.dop) + estim.model.yop
205+
ŷs = zeros(NT, estim.model.ny)
206+
ŷs[estim.i_ym] = ym - ŷd[estim.i_ym] # ŷs=0 for unmeasured outputs
207+
Ŷs = mpc.Ks*mpc.estim.x̂s + mpc.Ps*ŷs
208+
mpc.Ŷop[:] = Ŷs + repeat(estim.model.yop, mpc.Hp)
209+
return nothing
210+
end
211+
"Separate stochastic predictions are not needed if `estim` is not [`InternalModel`](@ref)."
212+
predictstoch!(::PredictiveController, ::StateEstimator, _ , _ ) = nothing
213+
214+
@doc raw"""
215+
predict!(Ŷ, x̂, mpc::PredictiveController, model::LinModel, ΔŨ) -> Ŷ, x̂end
216+
217+
Compute the predictions `Ŷ` and terminal states `x̂end` if model is a [`LinModel`](@ref).
218+
219+
The method mutates `Ŷ` and `x̂` vector arguments. The `x̂end` vector is used for
220+
the terminal constraints applied on ``\mathbf{x̂}_{k-1}(k+H_p)``.
221+
"""
222+
function predict!(Ŷ, x̂, mpc::PredictiveController, ::LinModel, ΔŨ::Vector{NT}) where {NT<:Real}
223+
# in-place operations to reduce allocations :
224+
Ŷ[:] = mul!(Ŷ, mpc.Ẽ, ΔŨ) + mpc.F
225+
x̂[:] = mul!(x̂, mpc.con.ẽx̂, ΔŨ) + mpc.con.fx̂
226+
end =
227+
return Ŷ, x̂end
228+
end
229+
230+
@doc raw"""
231+
predict!(Ŷ, x̂, mpc::PredictiveController, model::SimModel, ΔŨ) -> Ŷ, x̂end
232+
233+
Compute both vectors if `model` is not a [`LinModel`](@ref).
234+
"""
235+
function predict!(Ŷ, x̂, mpc::PredictiveController, model::SimModel, ΔŨ::Vector{NT}) where {NT<:Real}
236+
nu, ny, nd, Hp, Hc = model.nu, model.ny, model.nd, mpc.Hp, mpc.Hc
237+
x̂[:] = mpc.estim.
238+
u0::Vector{NT} = copy(mpc.estim.lastu0)
239+
d0 = @views mpc.d0[1:end]
240+
for j=1:Hp
241+
if j Hc
242+
u0[:] = @views u0 + ΔŨ[(1 + nu*(j-1)):(nu*j)]
243+
end
244+
x̂[:] = (mpc.estim, model, x̂, u0, d0)
245+
d0 = @views mpc.D̂0[(1 + nd*(j-1)):(nd*j)]
246+
Ŷ[(1 + ny*(j-1)):(ny*j)] = (mpc.estim, model, x̂, d0)
247+
end
248+
Ŷ[:] =+ mpc.Ŷop # Ŷop = Ŷs + Yop, and Ŷs=0 if mpc.estim is not an InternalModel
249+
end =
250+
return Ŷ, x̂end
251+
end
252+
253+
@doc raw"""
254+
linconstraint!(mpc::PredictiveController, model::LinModel)
255+
256+
Set `b` vector for the linear model inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
257+
258+
Also init ``\mathbf{f_x̂}`` vector for the terminal constraints, see [`init_predmat`](@ref).
259+
"""
260+
function linconstraint!(mpc::PredictiveController, model::LinModel)
261+
mpc.con.fx̂[:] = mpc.con.kx̂ * mpc.estim.+ mpc.con.vx̂ * mpc.estim.lastu0
262+
if model.nd 0
263+
mpc.con.fx̂[:] = mpc.con.fx̂ + mpc.con.gx̂ * mpc.d0 + mpc.con.jx̂ * mpc.D̂0
264+
end
265+
lastu = mpc.estim.lastu0 + model.uop
266+
mpc.con.b[:] = [
267+
-mpc.con.Umin + mpc.T*lastu
268+
+mpc.con.Umax - mpc.T*lastu
269+
-mpc.con.ΔŨmin
270+
+mpc.con.ΔŨmax
271+
-mpc.con.Ymin + mpc.F
272+
+mpc.con.Ymax - mpc.F
273+
-mpc.con.x̂min + mpc.con.fx̂
274+
+mpc.con.x̂max - mpc.con.fx̂
275+
]
276+
lincon = mpc.optim[:linconstraint]
277+
set_normalized_rhs.(lincon, mpc.con.b[mpc.con.i_b])
278+
end
279+
280+
"Set `b` excluding predicted output constraints when `model` is not a [`LinModel`](@ref)."
281+
function linconstraint!(mpc::PredictiveController, model::SimModel)
282+
lastu = mpc.estim.lastu0 + model.uop
283+
mpc.con.b[:] = [
284+
-mpc.con.Umin + mpc.T*lastu
285+
+mpc.con.Umax - mpc.T*lastu
286+
-mpc.con.ΔŨmin
287+
+mpc.con.ΔŨmax
288+
]
289+
lincon = mpc.optim[:linconstraint]
290+
set_normalized_rhs.(lincon, mpc.con.b[mpc.con.i_b])
291+
end
292+
293+
"""
294+
optim_objective!(mpc::PredictiveController)
295+
296+
Optimize the objective function ``J`` of `mpc` controller and return the solution `ΔŨ`.
297+
"""
298+
function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
299+
optim = mpc.optim
300+
model = mpc.estim.model
301+
ΔŨvar::Vector{VariableRef} = optim[:ΔŨvar]
302+
lastΔŨ = mpc.ΔŨ
303+
# initial ΔŨ (warm-start): [Δu_{k-1}(k); Δu_{k-1}(k+1); ... ; 0_{nu × 1}; ϵ_{k-1}]
304+
ϵ0 = !isinf(mpc.C) ? [lastΔŨ[end]] : empty(mpc.ΔŨ)
305+
ΔŨ0 = [lastΔŨ[(model.nu+1):(mpc.Hc*model.nu)]; zeros(NT, model.nu); ϵ0]
306+
set_start_value.(ΔŨvar, ΔŨ0)
307+
set_objective_linear_coef!(mpc, ΔŨvar)
308+
try
309+
optimize!(optim)
310+
catch err
311+
if isa(err, MOI.UnsupportedAttribute{MOI.VariablePrimalStart})
312+
# reset_optimizer to unset warm-start, set_start_value.(nothing) seems buggy
313+
MOIU.reset_optimizer(optim)
314+
optimize!(optim)
315+
else
316+
rethrow(err)
317+
end
318+
end
319+
status = termination_status(optim)
320+
ΔŨcurr, ΔŨlast = value.(ΔŨvar), ΔŨ0
321+
if !(status == OPTIMAL || status == LOCALLY_SOLVED)
322+
if isfatal(status)
323+
@error("MPC terminated without solution: returning last solution shifted",
324+
status, ΔŨcurr, ΔŨlast)
325+
else
326+
@warn("MPC termination status not OPTIMAL or LOCALLY_SOLVED: keeping "*
327+
"solution anyway", status, ΔŨcurr, ΔŨlast)
328+
end
329+
@debug solution_summary(optim, verbose=true)
330+
end
331+
mpc.ΔŨ[:] = isfatal(status) ? ΔŨlast : ΔŨcurr
332+
return mpc.ΔŨ
333+
end
334+
335+
"By default, no need to modify the objective function."
336+
set_objective_linear_coef!(::PredictiveController, _ ) = nothing
337+
338+
"""
339+
updatestate!(mpc::PredictiveController, u, ym, d=[]) -> x̂
340+
341+
Call [`updatestate!`](@ref) on `mpc.estim` [`StateEstimator`](@ref).
342+
"""
343+
updatestate!(mpc::PredictiveController, u, ym, d=empty(mpc.estim.x̂)) = updatestate!(mpc.estim,u,ym,d)
344+
updatestate!(::PredictiveController, _ ) = throw(ArgumentError("missing measured outputs ym"))

src/controller/linmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ function init_optimization!(mpc::LinMPC, optim::JuMP.GenericModel)
235235
con = mpc.con
236236
nΔŨ = length(mpc.ΔŨ)
237237
set_silent(optim)
238-
limit_solve_time(mpc)
238+
limit_solve_time(mpc.optim, mpc.estim.model.Ts)
239239
@variable(optim, ΔŨvar[1:nΔŨ])
240240
A = con.A[con.i_b, :]
241241
b = con.b[con.i_b]

src/controller/nonlinmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where
276276
con = mpc.con
277277
nΔŨ = length(mpc.ΔŨ)
278278
set_silent(optim)
279-
limit_solve_time(mpc)
279+
limit_solve_time(mpc.optim, mpc.estim.model.Ts)
280280
@variable(optim, ΔŨvar[1:nΔŨ])
281281
A = con.A[con.i_b, :]
282282
b = con.b[con.i_b]

0 commit comments

Comments
 (0)