Skip to content

Commit 7348403

Browse files
authored
Merge pull request #326 from JuliaControl/epsilon_economic_function
changed: added slack `ϵ` in the `JE` function signature of `NonLinMPC`
2 parents 40b72ba + 0c22c06 commit 7348403

6 files changed

Lines changed: 37 additions & 31 deletions

File tree

benchmark/3_bench_predictive_control.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ model2, p = pendulum_model2, pendulum_p2
421421
plant2 = deepcopy(model2)
422422
plant2.p[3] = 1.25*p[3] # plant-model mismatch
423423
estim2 = UnscentedKalmanFilter(model2; σQ, σR, nint_u, σQint_u, i_ym=[1])
424-
function JE(UE, ŶE, _ , p)
424+
function JE(UE, ŶE, _ , p, _)
425425
Ts = p
426426
τ, ω = @views UE[1:end-1], ŶE[2:2:end-1]
427427
return Ts*dot(τ, ω)

docs/src/manual/nonlinmpc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ output vector of `plant` argument corresponds to the model output vector in `mpc
191191
We can now define the ``J_E`` function and the `empc` controller:
192192

193193
```@example man_nonlin
194-
function JE(Ue, Ŷe, _ , p)
194+
function JE(Ue, Ŷe, _ , p, _ )
195195
Ts = p
196196
τ, ω = Ue[1:end-1], Ŷe[2:2:end-1]
197197
return Ts*sum(τ.*ω)

src/controller/execute.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,16 @@ function getinfo(mpc::PredictiveController{NT}) where NT<:Real
185185
return info
186186
end
187187

188-
"""
189-
getϵ(mpc::PredictiveController, Z̃) -> ϵ
188+
@doc raw"""
189+
getϵ(mpc::PredictiveController, Z̃orΔŨ) -> ϵ
190+
191+
Get the slack `ϵ` from `Z̃orΔŨ` if present, otherwise return 0.
190192
191-
Get the slack `ϵ` from the decision vector `Z̃` if present, otherwise return 0.
193+
The argument `Z̃orΔŨ` can be the augmented decision vector ``\mathbf{Z̃}`` or the augmented
194+
input increment vector ``\mathbf{ΔŨ}``, it works with both.
192195
"""
193-
function getϵ(mpc::PredictiveController, ::AbstractVector{NT}) where NT<:Real
194-
return mpc. 0 ? [end] : zero(NT)
196+
function getϵ(mpc::PredictiveController, Z̃orΔŨ::AbstractVector{NT}) where NT<:Real
197+
return mpc. 0 ? Z̃orΔŨ[end] : zero(NT)
195198
end
196199

197200
"""
@@ -425,15 +428,16 @@ function obj_nonlinprog!(
425428
JR̂u = dot(Ū, mpc.weights.L_Hp, Ū)
426429
end
427430
# --- economic term ---
428-
E_JE = obj_econ(mpc, model, Ue, Ŷe)
431+
ϵ = getϵ(mpc, ΔŨ)
432+
E_JE = obj_econ(mpc, model, Ue, Ŷe, ϵ)
429433
return JR̂y + JΔŨ + JR̂u + E_JE
430434
end
431435

432436
"No custom nonlinear constraints `gc` by default, return `gc` unchanged."
433437
con_custom!(gc, ::PredictiveController, _ , _, _ ) = gc
434438

435439
"By default, the economic term is zero."
436-
function obj_econ(::PredictiveController, ::SimModel, _ , ::AbstractVector{NT}) where NT
440+
function obj_econ(::PredictiveController, ::SimModel, _ , ::AbstractVector{NT}, _ ) where NT
437441
return zero(NT)
438442
end
439443

src/controller/nonlinmpc.jl

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ controller minimizes the following objective function at each discrete time ``k`
162162
+ \mathbf{(ΔU)}' \mathbf{N}_{H_c} \mathbf{(ΔU)} \\&
163163
+ \mathbf{(R̂_u - U)}' \mathbf{L}_{H_p} \mathbf{(R̂_u - U)}
164164
+ C ϵ^2
165-
+ E J_E(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p})
165+
+ E J_E(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p}, ϵ)
166166
\end{aligned}
167167
```
168168
subject to [`setconstraint!`](@ref) bounds, and the custom inequality constraints:
@@ -219,8 +219,8 @@ This controller allocates memory at each time step for the optimization.
219219
- `Wd=nothing` : custom linear constraint matrix for meas. disturbance (see Extended Help).
220220
- `Wr=nothing` : custom linear constraint matrix for output setpoint (see Extended Help).
221221
- `Ewt=0.0` : economic costs weight ``E`` (scalar).
222-
- `JE=(_,_,_,_)->0.0` : economic or custom cost function ``J_E(\mathbf{U_e}, \mathbf{Ŷ_e},
223-
\mathbf{D̂_e}, \mathbf{p})``.
222+
- `JE=(_,_,_,_,_)->0.0` : economic or custom cost function ``J_E(\mathbf{U_e}, \mathbf{Ŷ_e},
223+
\mathbf{D̂_e}, \mathbf{p}, ϵ)``.
224224
- `gc=(_,_,_,_,_,_)->nothing` or `gc!` : custom nonlinear inequality constraint function
225225
``\mathbf{g_c}(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p}, ϵ)``, mutating or
226226
not (details in Extended Help).
@@ -275,8 +275,8 @@ NonLinMPC controller with a sample time Ts = 10.0 s:
275275
The economic cost ``J_E`` and custom constraint ``\mathbf{g_c}`` functions receive the
276276
extended vectors ``\mathbf{U_e}`` (`nu*Hp+nu` elements), ``\mathbf{Ŷ_e}`` (`ny+ny*Hp`
277277
elements) and ``\mathbf{D̂_e}`` (`nd+nd*Hp` elements) as arguments. They all include the
278-
values from ``k`` to ``k + H_p`` (inclusively). The custom constraint also receives the
279-
slack ``ϵ`` (scalar), which is always zero if `Cwt=Inf`.
278+
values from ``k`` to ``k + H_p`` (inclusively). They also receives the slack ``ϵ``
279+
(scalar), which is always zero if `Cwt=Inf`.
280280
281281
More precisely, the last two time steps in ``\mathbf{U_e}`` are forced to be equal, i.e.
282282
``\mathbf{u}(k+H_p) = \mathbf{u}(k+H_p-1)``, since ``H_c ≤ H_p`` implies that
@@ -343,7 +343,7 @@ function NonLinMPC(
343343
Wr = nothing,
344344
Cwt = DEFAULT_CWT,
345345
Ewt = DEFAULT_EWT,
346-
JE ::Function = (_,_,_,_) -> 0.0,
346+
JE ::Function = (_,_,_,_,_) -> 0.0,
347347
gc!::Function = (_,_,_,_,_,_) -> nothing,
348348
gc ::Function = gc!,
349349
nc::Int = 0,
@@ -414,7 +414,7 @@ function NonLinMPC(
414414
Wr = nothing,
415415
Cwt = DEFAULT_CWT,
416416
Ewt = DEFAULT_EWT,
417-
JE ::Function = (_,_,_,_) -> 0.0,
417+
JE ::Function = (_,_,_,_,_) -> 0.0,
418418
gc!::Function = (_,_,_,_,_,_) -> nothing,
419419
gc ::Function = gc!,
420420
nc = 0,
@@ -454,11 +454,11 @@ default_jacobian(::TranscriptionMethod) = DEFAULT_NONLINMPC_JACSPARSE
454454
Validate `JE` function argument signature.
455455
"""
456456
function validate_JE(NT, JE)
457-
# Ue, Ŷe, D̂e, p
458-
if !hasmethod(JE, Tuple{Vector{NT}, Vector{NT}, Vector{NT}, Any})
457+
# Ue, Ŷe, D̂e, p, ϵ
458+
if !hasmethod(JE, Tuple{Vector{NT}, Vector{NT}, Vector{NT}, Any, NT})
459459
error(
460460
"the economic cost function has no method with type signature "*
461-
"JE(Ue::Vector{$(NT)}, Ŷe::Vector{$(NT)}, D̂e::Vector{$(NT)}, p::Any)"
461+
"JE(Ue::Vector{$(NT)}, Ŷe::Vector{$(NT)}, D̂e::Vector{$(NT)}, p::Any, ϵ::$(NT))"
462462
)
463463
end
464464
return nothing
@@ -513,19 +513,20 @@ should ease troubleshooting of simple bugs e.g.: the user forgets to set the `nc
513513
function test_custom_functions(NT, model::SimModel, JE, gc!, nc, Uop, Yop, Dop, p)
514514
uop, dop, yop = model.uop, model.dop, model.yop
515515
Ue, Ŷe, D̂e = [Uop; uop], [yop; Yop], [dop; Dop]
516+
ϵ = zero(NT)
516517
try
517-
val::NT = JE(Ue, Ŷe, D̂e, p)
518+
val::NT = JE(Ue, Ŷe, D̂e, p, ϵ)
518519
catch err
519520
@warn(
520521
"""
521-
Calling the JE function with Ue, Ŷe, D̂e arguments fixed at uop=$uop,
522-
yop=$yop, dop=$dop failed with the following stacktrace. Did you forget
522+
Calling the JE function with Ue, Ŷe, D̂e, ϵ arguments fixed at uop=$uop,
523+
yop=$yop, dop=$dop, ϵ=0 failed with the following stacktrace. Did you forget
523524
to set the keyword argument p?
524525
""",
525526
exception=(err, catch_backtrace())
526527
)
527528
end
528-
ϵ, gc = zero(NT), Vector{NT}(undef, nc)
529+
gc = Vector{NT}(undef, nc)
529530
try
530531
gc!(gc, Ue, Ŷe, D̂e, p, ϵ)
531532
catch err
@@ -553,7 +554,7 @@ function addinfo!(info, mpc::NonLinMPC{NT}) where NT<:Real
553554
Ue = [U; U[(end - mpc.estim.model.nu + 1):end]]
554555
Ŷe = [ŷ; Ŷ]
555556
D̂e = [d; D̂]
556-
JE = mpc.JE(Ue, Ŷe, D̂e, mpc.p)
557+
JE = mpc.JE(Ue, Ŷe, D̂e, mpc.p, ϵ)
557558
LHS = Vector{NT}(undef, mpc.con.nc)
558559
mpc.con.gc!(LHS, Ue, Ŷe, D̂e, mpc.p, ϵ)
559560
info[:JE] = JE
@@ -1092,9 +1093,9 @@ end
10921093

10931094
"Evaluate the economic term `E*JE` of the objective function for [`NonLinMPC`](@ref)."
10941095
function obj_econ(
1095-
mpc::NonLinMPC, ::SimModel, Ue, Ŷe::AbstractVector{NT}
1096+
mpc::NonLinMPC, ::SimModel, Ue, Ŷe::AbstractVector{NT}, ϵ
10961097
) where NT<:Real
1097-
E_JE = mpc.weights.iszero_E ? zero(NT) : mpc.weights.E*mpc.JE(Ue, Ŷe, mpc.D̂e, mpc.p)
1098+
E_JE = mpc.weights.iszero_E ? zero(NT) : mpc.weights.E*mpc.JE(Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
10981099
return E_JE
10991100
end
11001101

src/precompile.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function h!(y, x, _ , p)
2121
end
2222
p = (sys2.A, sys2.B, sys2.C)
2323

24-
function JE( _ , Ŷe, _ , R̂y)
24+
function JE( _ , Ŷe, _ , R̂y , _ )
2525
= @views Ŷe[3:end]
2626
= R̂y -
2727
return dot(Ȳ, Ȳ)

test/3_test_predictive_control.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -782,9 +782,9 @@ end
782782
nmpc6 = NonLinMPC(linmodel1, Hp=15, Lwt=[0,1])
783783
@test nmpc6.weights.L_Hp Diagonal(diagm(repeat(Float64[0, 1], 15)))
784784
@test nmpc6.weights.L_Hp isa Hermitian{Float64, Diagonal{Float64, Vector{Float64}}}
785-
nmpc7 = NonLinMPC(linmodel1, Hp=15, Ewt=1e-3, JE=(Ue,Ŷe,D̂e,p) -> p*dot(Ue,Ŷe)+sum(D̂e), p=10)
785+
nmpc7 = NonLinMPC(linmodel1, Hp=15, Ewt=1e-3, JE=(Ue,Ŷe,D̂e,p,_) -> p*dot(Ue,Ŷe)+sum(D̂e), p=10)
786786
@test nmpc7.weights.E == 1e-3
787-
@test nmpc7.JE([1,2],[3,4],[4,6],10) == 10*dot([1,2],[3,4])+sum([4,6])
787+
@test nmpc7.JE([1,2],[3,4],[4,6],10,0) == 10*dot([1,2],[3,4])+sum([4,6])
788788
nmpc10 = NonLinMPC(linmodel1, nint_u=[1, 1], nint_ym=[0, 0])
789789
@test nmpc10.estim.nint_u == [1, 1]
790790
@test nmpc10.estim.nint_ym == [0, 0]
@@ -808,11 +808,12 @@ end
808808

809809
@test_throws DimensionMismatch NonLinMPC(linmodel1, Hp=15, Ewt=[1, 1])
810810
@test_throws ErrorException NonLinMPC(linmodel1, Hp=15, JE = (_,_,_)->0.0)
811+
@test_throws ErrorException NonLinMPC(linmodel1, Hp=15, JE = (_,_,_,_)->0.0)
811812
@test_throws ErrorException NonLinMPC(linmodel1, Hp=15, gc = (_,_,_,_)->[0.0], nc=1)
812813
@test_throws ErrorException NonLinMPC(linmodel1, Hp=15, gc! = (_,_,_,_)->[0.0], nc=1)
813814
@test_throws ArgumentError NonLinMPC(linmodel1, transcription=TrapezoidalCollocation())
814815

815-
@test_logs (:warn, Regex(".*")) NonLinMPC(linmodel1, Hp=15, JE=(Ue,_,_,_)->Ue)
816+
@test_logs (:warn, Regex(".*")) NonLinMPC(linmodel1, Hp=15, JE=(Ue,_,_,_,_)->Ue)
816817
@test_logs (:warn, Regex(".*")) NonLinMPC(linmodel1, Hp=15, gc=(Ue,_,_,_,_)->Ue, nc=0)
817818
end
818819

@@ -903,7 +904,7 @@ end
903904
setmodel!(nmpc_lin; Mwt=[0], Lwt=[1])
904905
u = moveinput!(nmpc_lin; R̂u=fill(ru[1], Hp))
905906
@test u [4] atol=5e-2
906-
function JE(Ue, Ŷe, _ , p)
907+
function JE(Ue, Ŷe, _ , p, _ )
907908
Wy, R̂y, Wu, R̂u = p
908909
return Wy*sum((R̂y-Ŷe[2:end]).^2) + Wu*sum((R̂u-Ue[1:end-1]).^2)
909910
end

0 commit comments

Comments
 (0)