Skip to content

Commit db94d8b

Browse files
committed
added: support for parameter p in economic cost JE of NonLinMPC
They are identical to the plant model parameter by default.
1 parent e16b9e5 commit db94d8b

File tree

5 files changed

+73
-58
lines changed

5 files changed

+73
-58
lines changed

docs/src/manual/nonlinmpc.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,11 @@ output vector of `plant` argument corresponds to the model output vector in `mpc
194194
We can now define the ``J_E`` function and the `empc` controller:
195195

196196
```@example 1
197-
function JE(UE, ŶE, _ )
197+
function JE(UE, ŶE, _ , Ts)
198198
τ, ω = UE[1:end-1], ŶE[2:2:end-1]
199199
return Ts*sum(τ.*ω)
200200
end
201-
empc = NonLinMPC(estim2; Hp, Hc, Nwt, Mwt=[0.5, 0], Cwt=Inf, Ewt=3.5e3, JE=JE)
201+
empc = NonLinMPC(estim2; Hp, Hc, Nwt, Mwt=[0.5, 0], Cwt=Inf, Ewt=3.5e3, JE=JE, p=Ts)
202202
empc = setconstraint!(empc; umin, umax)
203203
```
204204

src/controller/execute.jl

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -369,19 +369,7 @@ function obj_nonlinprog!(
369369
U0, Ȳ, _ , mpc::PredictiveController, model::LinModel, Ŷ0, ΔŨ::AbstractVector{NT}
370370
) where NT <: Real
371371
J = obj_quadprog(ΔŨ, mpc.H̃, mpc.q̃) + mpc.r[]
372-
if !iszero(mpc.E)
373-
ŷ, D̂E = mpc.ŷ, mpc.D̂E
374-
U = U0
375-
U .+= mpc.Uop
376-
uend = @views U[(end-model.nu+1):end]
377-
=
378-
Ŷ .= Ŷ0 .+ mpc.Yop
379-
UE = [U; uend]
380-
ŶE = [ŷ; Ŷ]
381-
E_JE = mpc.E*mpc.JE(UE, ŶE, D̂E)
382-
else
383-
E_JE = 0.0
384-
end
372+
E_JE = obj_econ!(U0, Ȳ, mpc, model, Ŷ0, ΔŨ)
385373
return J + E_JE
386374
end
387375

@@ -413,22 +401,13 @@ function obj_nonlinprog!(
413401
JR̂u = 0.0
414402
end
415403
# --- economic term ---
416-
if !iszero(mpc.E)
417-
ny, Hp, ŷ, D̂E = model.ny, mpc.Hp, mpc.ŷ, mpc.D̂E
418-
U = U0
419-
U .+= mpc.Uop
420-
uend = @views U[(end-model.nu+1):end]
421-
=
422-
Ŷ .= Ŷ0 .+ mpc.Yop
423-
UE = [U; uend]
424-
ŶE = [ŷ; Ŷ]
425-
E_JE = mpc.E*mpc.JE(UE, ŶE, D̂E)
426-
else
427-
E_JE = 0.0
428-
end
404+
E_JE = obj_econ!(U0, Ȳ, mpc, model, Ŷ0, ΔŨ)
429405
return JR̂y + JΔŨ + JR̂u + E_JE
430406
end
431407

408+
"By default, the economic term is zero."
409+
obj_econ!( _ , _ , ::PredictiveController, ::SimModel, _ , _ ) = 0.0
410+
432411
@doc raw"""
433412
optim_objective!(mpc::PredictiveController) -> ΔŨ
434413

src/controller/nonlinmpc.jl

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ struct NonLinMPC{
44
NT<:Real,
55
SE<:StateEstimator,
66
JM<:JuMP.GenericModel,
7-
JEfunc<:Function
7+
JEfunc<:Function,
8+
P<:Any
89
} <: PredictiveController{NT}
910
estim::SE
1011
# note: `NT` and the number type `JNT` in `JuMP.GenericModel{JNT}` can be
@@ -21,6 +22,7 @@ struct NonLinMPC{
2122
L_Hp::Hermitian{NT, Matrix{NT}}
2223
E::NT
2324
JE::JEfunc
25+
p::P
2426
R̂u0::Vector{NT}
2527
R̂y0::Vector{NT}
2628
noR̂u::Bool
@@ -46,9 +48,9 @@ struct NonLinMPC{
4648
Yop::Vector{NT}
4749
Dop::Vector{NT}
4850
buffer::PredictiveControllerBuffer{NT}
49-
function NonLinMPC{NT, SE, JM, JEFunc}(
50-
estim::SE, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE::JEFunc, optim::JM
51-
) where {NT<:Real, SE<:StateEstimator, JM<:JuMP.GenericModel, JEFunc<:Function}
51+
function NonLinMPC{NT, SE, JM, JEFunc, P}(
52+
estim::SE, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE::JEFunc, p::P, optim::JM
53+
) where {NT<:Real, SE<:StateEstimator, JM<:JuMP.GenericModel, JEFunc<:Function, P<:Any}
5254
model = estim.model
5355
nu, ny, nd, nx̂ = model.nu, model.ny, model.nd, estim.nx̂
5456
= copy(model.yop) # dummy vals (updated just before optimization)
@@ -77,11 +79,11 @@ struct NonLinMPC{
7779
nΔŨ = size(Ẽ, 2)
7880
ΔŨ = zeros(NT, nΔŨ)
7981
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp)
80-
mpc = new{NT, SE, JM, JEFunc}(
82+
mpc = new{NT, SE, JM, JEFunc, P}(
8183
estim, optim, con,
8284
ΔŨ, ŷ,
8385
Hp, Hc, nϵ,
84-
M_Hp, Ñ_Hc, L_Hp, Ewt, JE,
86+
M_Hp, Ñ_Hc, L_Hp, Ewt, JE, p,
8587
R̂u0, R̂y0, noR̂u,
8688
S̃, T, T_lastu0,
8789
Ẽ, F, G, J, K, V, B,
@@ -109,12 +111,12 @@ controller minimizes the following objective function at each discrete time ``k`
109111
+ \mathbf{(ΔU)}' \mathbf{N}_{H_c} \mathbf{(ΔU)} \\&
110112
+ \mathbf{(R̂_u - U)}' \mathbf{L}_{H_p} \mathbf{(R̂_u - U)}
111113
+ C ϵ^2
112-
+ E J_E(\mathbf{U}_E, \mathbf{Ŷ}_E, \mathbf{D̂}_E)
114+
+ E J_E(\mathbf{U}_E, \mathbf{Ŷ}_E, \mathbf{D̂}_E, \mathbf{p})
113115
\end{aligned}
114116
```
115117
See [`LinMPC`](@ref) for the variable definitions. The custom economic function ``J_E`` can
116118
penalizes solutions with high economic costs. Setting all the weights to 0 except ``E``
117-
creates a pure economic model predictive controller (EMPC). The arguments of ``J_E`` are
119+
creates a pure economic model predictive controller (EMPC). The arguments of ``J_E`` include
118120
the manipulated inputs, the predicted outputs and measured disturbances from ``k`` to
119121
``k+H_p`` inclusively:
120122
```math
@@ -124,10 +126,10 @@ the manipulated inputs, the predicted outputs and measured disturbances from ``k
124126
```
125127
since ``H_c ≤ H_p`` implies that ``\mathbf{Δu}(k+H_p) = \mathbf{0}`` or ``\mathbf{u}(k+H_p)=
126128
\mathbf{u}(k+H_p-1)``. The vector ``\mathbf{D̂}`` includes the predicted measured disturbance
127-
over ``H_p``.
129+
over ``H_p``. The argument ``\mathbf{p}`` is a custom parameter object of any type.
128130
129131
!!! tip
130-
Replace any of the 3 arguments with `_` if not needed (see `JE` default value below).
132+
Replace any of the 4 arguments with `_` if not needed (see `JE` default value below).
131133
132134
This method uses the default state estimator :
133135
@@ -150,7 +152,8 @@ This method uses the default state estimator :
150152
- `L_Hp=diagm(repeat(Lwt,Hp))` : positive semidefinite symmetric matrix ``\mathbf{L}_{H_p}``.
151153
- `Cwt=1e5` : slack variable weight ``C`` (scalar), use `Cwt=Inf` for hard constraints only.
152154
- `Ewt=0.0` : economic costs weight ``E`` (scalar).
153-
- `JE=(_,_,_)->0.0` : economic function ``J_E(\mathbf{U}_E, \mathbf{Ŷ}_E, \mathbf{D̂}_E)``.
155+
- `JE=(_,_,_,_)->0.0` : economic function ``J_E(\mathbf{U}_E, \mathbf{Ŷ}_E, \mathbf{D̂}_E, \mathbf{p})``.
156+
- `p=model.p` : ``J_E`` function parameter ``\mathbf{p}`` (any type).
154157
- `optim=JuMP.Model(Ipopt.Optimizer)` : nonlinear optimizer used in the predictive
155158
controller, provided as a [`JuMP.Model`](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.Model)
156159
(default to [`Ipopt`](https://github.com/jump-dev/Ipopt.jl) optimizer).
@@ -201,11 +204,12 @@ function NonLinMPC(
201204
Cwt = DEFAULT_CWT,
202205
Ewt = DEFAULT_EWT,
203206
JE::Function = (_,_,_) -> 0.0,
207+
p = model.p,
204208
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NONLINMPC_OPTIMIZER, add_bridges=false),
205209
kwargs...
206210
)
207211
estim = UnscentedKalmanFilter(model; kwargs...)
208-
NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, M_Hp, N_Hc, L_Hp, optim)
212+
NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, p, M_Hp, N_Hc, L_Hp, optim)
209213
end
210214

211215
function NonLinMPC(
@@ -220,12 +224,13 @@ function NonLinMPC(
220224
L_Hp = diagm(repeat(Lwt, Hp)),
221225
Cwt = DEFAULT_CWT,
222226
Ewt = DEFAULT_EWT,
223-
JE::Function = (_,_,_) -> 0.0,
227+
JE::Function = (_,_,_,_) -> 0.0,
228+
p = model.p,
224229
optim::JuMP.GenericModel = JuMP.Model(DEFAULT_NONLINMPC_OPTIMIZER, add_bridges=false),
225230
kwargs...
226231
)
227232
estim = SteadyKalmanFilter(model; kwargs...)
228-
NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, M_Hp, N_Hc, L_Hp, optim)
233+
NonLinMPC(estim; Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, p, M_Hp, N_Hc, L_Hp, optim)
229234
end
230235

231236

@@ -265,14 +270,17 @@ function NonLinMPC(
265270
Cwt = DEFAULT_CWT,
266271
Ewt = DEFAULT_EWT,
267272
JE::JEFunc = (_,_,_) -> 0.0,
273+
p::P = estim.model.p,
268274
optim::JM = JuMP.Model(DEFAULT_NONLINMPC_OPTIMIZER, add_bridges=false),
269-
) where {NT<:Real, SE<:StateEstimator{NT}, JM<:JuMP.GenericModel, JEFunc<:Function}
275+
) where {NT<:Real, SE<:StateEstimator{NT}, JM<:JuMP.GenericModel, JEFunc<:Function, P<:Any}
270276
nk = estimate_delays(estim.model)
271277
if Hp nk
272278
@warn("prediction horizon Hp ($Hp) ≤ estimated number of delays in model "*
273279
"($nk), the closed-loop system may be unstable or zero-gain (unresponsive)")
274280
end
275-
return NonLinMPC{NT, SE, JM, JEFunc}(estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, optim)
281+
return NonLinMPC{NT, SE, JM, JEFunc, P}(
282+
estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, p, optim
283+
)
276284
end
277285

278286
"""
@@ -285,7 +293,7 @@ function addinfo!(info, mpc::NonLinMPC)
285293
UE = [U; U[(end - mpc.estim.model.nu + 1):end]]
286294
ŶE = [ŷ; Ŷ]
287295
D̂E = [d; D̂]
288-
info[:JE] = mpc.JE(UE, ŶE, D̂E)
296+
info[:JE] = mpc.JE(UE, ŶE, D̂E, mpc.p)
289297
info[:sol] = JuMP.solution_summary(mpc.optim, verbose=true)
290298
return info
291299
end
@@ -457,4 +465,22 @@ function con_nonlinprog!(g, mpc::NonLinMPC, ::SimModel, x̂0end, Ŷ0, ΔŨ)
457465
end
458466

459467
"No nonlinear constraints if `model` is a [`LinModel`](@ref), return `g` unchanged."
460-
con_nonlinprog!(g, ::NonLinMPC, ::LinModel, _ , _ , _ ) = g
468+
con_nonlinprog!(g, ::NonLinMPC, ::LinModel, _ , _ , _ ) = g
469+
470+
"Evaluate the economic term of the objective function for [`NonLinMPC`](@ref)."
471+
function obj_econ!(U0, Ȳ, mpc::NonLinMPC, model::SimModel, Ŷ0, ΔŨ)
472+
if !iszero(mpc.E)
473+
ny, Hp, ŷ, D̂E = model.ny, mpc.Hp, mpc.ŷ, mpc.D̂E
474+
U = U0
475+
U .+= mpc.Uop
476+
uend = @views U[(end-model.nu+1):end]
477+
=
478+
Ŷ .= Ŷ0 .+ mpc.Yop
479+
UE = [U; uend]
480+
ŶE = [ŷ; Ŷ]
481+
E_JE = mpc.E*mpc.JE(UE, ŶE, D̂E, mpc.p)
482+
else
483+
E_JE = 0.0
484+
end
485+
return E_JE
486+
end

src/precompile.jl

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ exmpc.estim()
7474
u = exmpc([55, 30])
7575
sim!(exmpc, 3, [55, 30])
7676

77-
f(x,u,_,_) = model.A*x + model.Bu*u
78-
h(x,_,_) = model.C*x
77+
f(x,u,_,model) = model.A*x + model.Bu*u
78+
h(x,_,model) = model.C*x
7979

80-
nlmodel = setop!(NonLinModel(f, h, Ts, 2, 2, 2, solver=nothing), uop=[10, 10], yop=[50, 30])
80+
nlmodel = setop!(
81+
NonLinModel(f, h, Ts, 2, 2, 2, solver=nothing, p=model),
82+
uop=[10, 10], yop=[50, 30]
83+
)
8184
y = nlmodel()
8285
nmpc_im = setconstraint!(NonLinMPC(InternalModel(nlmodel), Hp=10, Cwt=Inf), ymin=[45, -Inf])
8386
initstate!(nmpc_im, nlmodel.uop, y)
@@ -86,7 +89,9 @@ nmpc_im.estim()
8689
u = nmpc_im([55, 30])
8790
sim!(nmpc_im, 3, [55, 30])
8891

89-
nmpc_ukf = setconstraint!(NonLinMPC(UnscentedKalmanFilter(nlmodel), Hp=10, Cwt=1e3), ymin=[45, -Inf])
92+
nmpc_ukf = setconstraint!(
93+
NonLinMPC(UnscentedKalmanFilter(nlmodel), Hp=10, Cwt=1e3), ymin=[45, -Inf]
94+
)
9095
initstate!(nmpc_ukf, nlmodel.uop, y)
9196
preparestate!(nmpc_ukf, [55, 30])
9297
u = nmpc_ukf([55, 30])
@@ -98,19 +103,24 @@ preparestate!(nmpc_ekf, [55, 30])
98103
u = nmpc_ekf([55, 30])
99104
sim!(nmpc_ekf, 3, [55, 30])
100105

101-
nmpc_mhe = setconstraint!(NonLinMPC(MovingHorizonEstimator(nlmodel, He=2), Hp=10, Cwt=Inf), ymin=[45, -Inf])
106+
nmpc_mhe = setconstraint!(
107+
NonLinMPC(MovingHorizonEstimator(nlmodel, He=2), Hp=10, Cwt=Inf), ymin=[45, -Inf]
108+
)
102109
setconstraint!(nmpc_mhe.estim, x̂min=[-50,-50,-50,-50], x̂max=[50,50,50,50])
103110
initstate!(nmpc_mhe, nlmodel.uop, y)
104111
preparestate!(nmpc_mhe, [55, 30])
105112
u = nmpc_mhe([55, 30])
106113
sim!(nmpc_mhe, 3, [55, 30])
107114

108-
function JE( _ , ŶE, _ )
109-
= ŶE[3:end]
110-
Eŷ = repeat([55; 30], 10) -
111-
return dot(Eŷ, I, Eŷ)
115+
function JE( _ , ŶE, _ , R̂y)
116+
= ŶE[3:end]
117+
= R̂y -
118+
return dot(Ȳ, Ȳ)
112119
end
113-
empc = setconstraint!(NonLinMPC(nlmodel, Mwt=[0, 0], Hp=10, Cwt=Inf, Ewt=1, JE=JE), ymin=[45, -Inf])
120+
R̂y = repeat([55; 30], 10)
121+
empc = setconstraint!(
122+
NonLinMPC(nlmodel, Mwt=[0, 0], Hp=10, Cwt=Inf, Ewt=1, JE=JE, p=R̂y), ymin=[45, -Inf]
123+
)
114124
preparestate!(empc, [55, 30])
115125
u = empc()
116126
sim!(empc, 3)

test/test_predictive_control.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,9 @@ end
491491
@test nmpc5.Ñ_Hc Diagonal(diagm([repeat(Float64[3, 4], 5); [1e3]]))
492492
nmpc6 = NonLinMPC(nonlinmodel, Hp=15, Lwt=[0,1])
493493
@test nmpc6.L_Hp Diagonal(diagm(repeat(Float64[0, 1], 15)))
494-
nmpc7 = NonLinMPC(nonlinmodel, Hp=15, Ewt=1e-3, JE=(UE,ŶE,D̂E) -> UE.*ŶE.*D̂E)
494+
nmpc7 = NonLinMPC(nonlinmodel, Hp=15, Ewt=1e-3, JE=(UE,ŶE,D̂E,p) -> p*UE.*ŶE.*D̂E, p=2)
495495
@test nmpc7.E == 1e-3
496-
@test nmpc7.JE([1,2],[3,4],[4,6]) == [12, 48]
496+
@test nmpc7.JE([1,2],[3,4],[4,6],2) == 2*[1,2].*[3.4].*[4,6]
497497
optim = JuMP.Model(optimizer_with_attributes(Ipopt.Optimizer, "nlp_scaling_max_gradient"=>1.0))
498498
nmpc8 = NonLinMPC(nonlinmodel, Hp=15, optim=optim)
499499
@test solver_name(nmpc8.optim) == "Ipopt"

0 commit comments

Comments
 (0)