Skip to content

Commit 3d98d6d

Browse files
committed
added: test calling JE and gc functions in NonLinMPC constructor
to guide the user with simple bugs
1 parent c88e90c commit 3d98d6d

File tree

5 files changed

+77
-61
lines changed

5 files changed

+77
-61
lines changed

src/controller/execute.jl

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ function getinfo(mpc::PredictiveController{NT}) where NT<:Real
120120
Ȳ, Ū = similar(mpc.Yop), similar(mpc.Uop)
121121
Ŷe, Ue = Vector{NT}(undef, nŶe), Vector{NT}(undef, nUe)
122122
Ŷ0, x̂0end = predict!(Ŷ0, x̂0, x̂0next, u0, û0, mpc, model, mpc.ΔŨ)
123-
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, mpc.ΔŨ)
124-
J = obj_nonlinprog!(Ȳ, Ū, mpc, model, Ŷe, Ue, mpc.ΔŨ)
123+
Ue, Ŷe = extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, mpc.ΔŨ)
124+
J = obj_nonlinprog!(Ȳ, Ū, mpc, model, Ue, Ŷe, mpc.ΔŨ)
125125
U =
126126
U .= @views Ue[1:end-model.nu]
127127
=
@@ -362,28 +362,28 @@ function predict!(Ŷ0, x̂0, x̂0next, u0, û0, mpc::PredictiveController, mod
362362
end
363363

364364
"""
365-
extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ) -> Ŷe, Ue
365+
extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ) -> Ŷe, Ue
366366
367-
Compute the extended predictions `Ŷe` and `Ue` for the nonlinear optimization.
367+
Compute the extended vectors `Ue` and `Ŷe` and for the nonlinear optimization.
368368
369-
The function mutates `Ŷe`, `Ue` and `Ū` in arguments, without assuming any initial values.
369+
The function mutates `Ue`, `Ŷe` and `Ū` in arguments, without assuming any initial values.
370370
"""
371-
function extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ)
371+
function extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
372372
ny, nu = model.ny, model.nu
373-
# --- extended output predictions Ŷe = [ŷ(k); Ŷ] ---
374-
Ŷe[1:ny] .= mpc.
375-
Ŷe[ny+1:end] .= Ŷ0 .+ mpc.Yop
376373
# --- extended manipulated inputs Ue = [U; u(k+Hp-1)] ---
377374
U0 =
378375
U0 .= mul!(U0, mpc.S̃, ΔŨ) .+ mpc.T_lastu0
379376
Ue[1:end-nu] .= U0 .+ mpc.Uop
380377
# u(k + Hp) = u(k + Hp - 1) since Δu(k+Hp) = 0 (because Hc ≤ Hp):
381378
Ue[end-nu+1:end] .= @views Ue[end-2nu+1:end-nu]
382-
return Ŷe, Ue
379+
# --- extended output predictions Ŷe = [ŷ(k); Ŷ] ---
380+
Ŷe[1:ny] .= mpc.
381+
Ŷe[ny+1:end] .= Ŷ0 .+ mpc.Yop
382+
return Ue, Ŷe
383383
end
384384

385385
"""
386-
obj_nonlinprog!( _ , _ , mpc::PredictiveController, model::LinModel, Ŷe, Ue, ΔŨ)
386+
obj_nonlinprog!( _ , _ , mpc::PredictiveController, model::LinModel, Ue, Ŷe, ΔŨ)
387387
388388
Nonlinear programming objective function when `model` is a [`LinModel`](@ref).
389389
@@ -392,23 +392,23 @@ also be called on any [`PredictiveController`](@ref)s to evaluate the objective
392392
at specific `Ue`, `Ŷe` and `ΔŨ`, values. It does not mutate any argument.
393393
"""
394394
function obj_nonlinprog!(
395-
_, _, mpc::PredictiveController, model::LinModel, Ŷe, Ue, ΔŨ::AbstractVector{NT}
395+
_, _, mpc::PredictiveController, model::LinModel, Ue, Ŷe, ΔŨ::AbstractVector{NT}
396396
) where NT <: Real
397397
JQP = obj_quadprog(ΔŨ, mpc.H̃, mpc.q̃) + mpc.r[]
398-
E_JE = obj_econ!(Ue, Ŷe, mpc, model)
398+
E_JE = obj_econ(mpc, model, Ue, Ŷe)
399399
return JQP + E_JE
400400
end
401401

402402
"""
403-
obj_nonlinprog!(Ȳ, Ū, mpc::PredictiveController, model::SimModel, Ŷe, Ue, ΔŨ)
403+
obj_nonlinprog!(Ȳ, Ū, mpc::PredictiveController, model::SimModel, Ue, Ŷe, ΔŨ)
404404
405405
Nonlinear programming objective method when `model` is not a [`LinModel`](@ref). The
406406
function `dot(x, A, x)` is a performant way of calculating `x'*A*x`. This method mutates
407407
`Ȳ` and `Ū` arguments, without assuming any initial values (it recuperates the values in
408408
`Ŷe` and `Ue` arguments).
409409
"""
410410
function obj_nonlinprog!(
411-
Ȳ, Ū, mpc::PredictiveController, model::SimModel, Ŷe, Ue, ΔŨ::AbstractVector{NT}
411+
Ȳ, Ū, mpc::PredictiveController, model::SimModel, Ue, Ŷe, ΔŨ::AbstractVector{NT}
412412
) where NT<:Real
413413
nu, ny = model.nu, model.ny
414414
# --- output setpoint tracking term ---
@@ -426,12 +426,12 @@ function obj_nonlinprog!(
426426
JR̂u = 0.0
427427
end
428428
# --- economic term ---
429-
E_JE = obj_econ!(Ue, Ŷe, mpc, model)
429+
E_JE = obj_econ(mpc, model, Ue, Ŷe)
430430
return JR̂y + JΔŨ + JR̂u + E_JE
431431
end
432432

433433
"By default, the economic term is zero."
434-
obj_econ!( _ , _ , ::PredictiveController, ::SimModel) = 0.0
434+
obj_econ(::PredictiveController, ::SimModel, _ , _ ) = 0.0
435435

436436
@doc raw"""
437437
optim_objective!(mpc::PredictiveController) -> ΔŨ

src/controller/linmpc.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFil
175175
| ``H_p`` | prediction horizon (integer) | `()` |
176176
| ``H_c`` | control horizon (integer) | `()` |
177177
| ``\mathbf{ΔU}`` | manipulated input increments over ``H_c`` | `(nu*Hc,)` |
178+
| ``\mathbf{D̂}`` | predicted measured disturbances over ``H_p`` | `(nd*Hp,)` |
178179
| ``\mathbf{Ŷ}`` | predicted outputs over ``H_p`` | `(ny*Hp,)` |
179180
| ``\mathbf{U}`` | manipulated inputs over ``H_p`` | `(nu*Hp,)` |
180181
| ``\mathbf{R̂_y}`` | predicted output setpoints over ``H_p`` | `(ny*Hp,)` |

src/controller/nonlinmpc.jl

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ struct NonLinMPC{
7878
# dummy vals (updated just before optimization):
7979
d0, D̂0, D̂e = zeros(NT, nd), zeros(NT, nd*Hp), zeros(NT, nd + nd*Hp)
8080
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
81+
test_custom_functions(NT, model, JE, gc!, nc, Uop, Yop, Dop, p)
8182
nΔŨ = size(Ẽ, 2)
8283
ΔŨ = zeros(NT, nΔŨ)
8384
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp)
@@ -131,16 +132,15 @@ include the manipulated inputs, predicted outputs and measured disturbances, ext
131132
\mathbf{Ŷ_e} = \begin{bmatrix} \mathbf{ŷ}(k) \\ \mathbf{Ŷ} \end{bmatrix} , \quad
132133
\mathbf{D̂_e} = \begin{bmatrix} \mathbf{d}(k) \\ \mathbf{D̂} \end{bmatrix}
133134
```
134-
The vector ``\mathbf{}`` comprises the measured disturbance predictions over ``H_p``. The
135-
argument ``\mathbf{p}`` is a custom parameter object of any type, but use a mutable one if
136-
you want to modify it later e.g.: a vector.
135+
The argument ``\mathbf{p}`` is a custom parameter object of any type, but use a mutable one
136+
if you want to modify it later e.g.: a vector. See [`LinMPC`](@ref) Extended Help for the
137+
definition of the other variables.
137138
138139
!!! tip
139140
Replace any of the arguments of ``J_E`` and ``\mathbf{g_c}`` functions with `_` if not
140141
needed (see e.g. the default value of `JE` below).
141142
142-
See [`LinMPC`](@ref) Extended Help for the definition of the other variables. This method
143-
uses the default state estimator :
143+
This method uses the default state estimator:
144144
145145
- if `model` is a [`LinModel`](@ref), a [`SteadyKalmanFilter`](@ref) with default arguments;
146146
- else, an [`UnscentedKalmanFilter`](@ref) with default arguments.
@@ -198,11 +198,17 @@ NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedK
198198
199199
The economic cost ``J_E`` and custom constraint ``\mathbf{g_c}`` functions receive the
200200
extended vectors ``\mathbf{U_e}`` (`nu*Hp+nu` elements), ``\mathbf{Ŷ_e}`` (`ny+ny*Hp`
201-
elements) and ``\mathbf{D̂_e}`` (`nd+nd*Hp` elements) as arguments. The last two time
202-
steps in ``\mathbf{U_e}`` are forced to be equal, that is ``\mathbf{u}(k+H_p) =
203-
\mathbf{u}(k+H_p-1)``, since ``H_c ≤ H_p`` implies that ``\mathbf{Δu}(k+H_p) =
204-
\mathbf{0}``. If `LHS` represents the result of the left-hand side in the inequality
205-
``\mathbf{g_c}(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p}, ϵ) ≤ \mathbf{0}``,
201+
elements) and ``\mathbf{D̂_e}`` (`nd+nd*Hp` elements) as arguments. They all include the
202+
values from ``k`` to ``k + H_p`` (inclusively). The custom constraint also receives the
203+
slack ``ϵ`` (scalar), which is always zero if `Cwt=Inf`.
204+
205+
More precisely, the last two time steps in ``\mathbf{U_e}`` are forced to be equal, i.e.
206+
``\mathbf{u}(k+H_p) = \mathbf{u}(k+H_p-1)``, since ``H_c ≤ H_p`` implies that
207+
``\mathbf{Δu}(k+H_p) = \mathbf{0}``. The vectors ``\mathbf{ŷ}(k)`` and ``\mathbf{d}(k)``
208+
are the current state estimator output and measured disturbance, respectively, and
209+
``\mathbf{Ŷ}`` and ``\mathbf{D̂}``, their respective predictions from ``k+1`` to ``k+H_p``.
210+
If `LHS` represents the result of the left-hand side in the inequality
211+
``\mathbf{g_c}(\mathbf{U_e}, \mathbf{Ŷ_e}, \mathbf{D̂_e}, \mathbf{p}, ϵ) ≤ \mathbf{0}``,
206212
the function `gc` can be implemented in two possible ways:
207213
208214
1. **Non-mutating function** (out-of-place): define it as `gc(Ue, Ŷe, D̂e, p, ϵ) -> LHS`.
@@ -388,25 +394,34 @@ function get_mutating_gc(NT, gc)
388394
return gc!
389395
end
390396

391-
function test_custom_functions(JE, gc!, uop; Uop, dop, Dop, ΔŨ, p)
392-
# TODO: contunue here (important to guide the user, sim! can be used on NonLinModel,
393-
# but there is no similar function for the custom functions of NonLinMPC)
394-
Ue = [Uop; uop]
395-
D̂e = [dop; Dop]
396-
397-
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
398-
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ)
399-
ϵ = (nϵ == 1) ? ΔŨ[end] : zero(JNT) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
400-
mpc.con.gc!(gc, Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
401-
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
402-
J = obj_nonlinprog!(Ȳ, Ū, mpc, model, Ŷe, Ue, ΔŨ)
403-
404-
405-
406-
407-
Ŷ0, x̂0next =
408-
Ŷ0, x̂0end = predict!(Ŷ0, x̂0, x̂0next, u0, û0, mpc, model, mpc.ΔŨ)
409-
JE = JE(Uop, Uop, Dop, p)
397+
function test_custom_functions(NT, model::SimModel, JE, gc!, nc, Uop, Yop, Dop, p)
398+
uop, dop, yop = model.uop, model.dop, model.yop
399+
Ue, Ŷe, D̂e = [Uop; uop], [yop; Yop], [dop; Dop]
400+
try
401+
JE(Ue, Ŷe, D̂e, p)
402+
catch err
403+
@warn(
404+
"""
405+
Calling the JE function with Ue, Ŷe, D̂e arguments fixed at uop=$uop,
406+
yop=$yop, dop=$dop failed with the following stacktrace.
407+
""",
408+
exception=(err, catch_backtrace())
409+
)
410+
end
411+
ϵ, gc = 0, Vector{NT}(undef, nc)
412+
try
413+
gc!(gc, Ue, Ŷe, D̂e, p, ϵ)
414+
catch err
415+
@warn(
416+
"""
417+
Calling the gc function with Ue, Ŷe, D̂e, ϵ arguments fixed at uop=$uop,
418+
yop=$yop, dop=$dop, ϵ=0 failed with the following stacktrace. Did you
419+
forget to set the keyword argument nc?
420+
""",
421+
exception=(err, catch_backtrace())
422+
)
423+
end
424+
return nothing
410425
end
411426

412427
"""
@@ -492,13 +507,13 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
492507
Ȳ, Ū = get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
493508
x̂0, x̂0next = get_tmp(x̂0_cache, ΔŨ1), get_tmp(x̂0next_cache, ΔŨ1)
494509
u0, û0 = get_tmp(u0_cache, ΔŨ1), get_tmp(û0_cache, ΔŨ1)
495-
g, gc = get_tmp(g_cache, ΔŨ1), get_tmp(gc_cache, ΔŨ1)
510+
gc = get_tmp(gc_cache, ΔŨ1)
496511
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
497-
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ)
498-
ϵ = (nϵ == 1) ? ΔŨ[end] : zero(JNT) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
512+
Ue, Ŷe = extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
513+
ϵ = (nϵ 0) ? ΔŨ[end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
499514
mpc.con.gc!(gc, Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
500515
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
501-
return obj_nonlinprog!(Ȳ, Ū, mpc, model, Ŷe, Ue, ΔŨ)::T
516+
return obj_nonlinprog!(Ȳ, Ū, mpc, model, Ue, Ŷe, ΔŨ)::T
502517
end
503518
function gfunc_i(i, ΔŨtup::NTuple{N, T}) where {N, T<:Real}
504519
ΔŨ1 = ΔŨtup[begin]
@@ -511,10 +526,10 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
511526
Ȳ, Ū = get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
512527
x̂0, x̂0next = get_tmp(x̂0_cache, ΔŨ1), get_tmp(x̂0next_cache, ΔŨ1)
513528
u0, û0 = get_tmp(u0_cache, ΔŨ1), get_tmp(û0_cache, ΔŨ1)
514-
g, gc = get_tmp(g_cache, ΔŨ1), get_tmp(gc_cache, ΔŨ1)
529+
gc = get_tmp(gc_cache, ΔŨ1)
515530
Ŷ0, x̂0end = predict!(Ȳ, x̂0, x̂0next, u0, û0, mpc, model, ΔŨ)
516-
Ŷe, Ue = extended_predictions!(Ŷe, Ue, Ū, mpc, model, Ŷ0, ΔŨ)
517-
ϵ = (nϵ == 1) ? ΔŨ[end] : zero(JNT) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
531+
Ue, Ŷe = extended_predictions!(Ue, Ŷe, Ū, mpc, model, Ŷ0, ΔŨ)
532+
ϵ = (nϵ 0) ? ΔŨ[end] : zero(T) # ϵ = 0 if nϵ == 0 (meaning no relaxation)
518533
mpc.con.gc!(gc, Ue, Ŷe, mpc.D̂e, mpc.p, ϵ)
519534
g = con_nonlinprog!(g, mpc, model, x̂0end, Ŷ0, gc, ϵ)
520535
end
@@ -672,7 +687,7 @@ function con_nonlinprog!(g, mpc::NonLinMPC, ::SimModel, x̂0end, Ŷ0, gc, ϵ)
672687
end
673688

674689
"Evaluate the economic term `E*JE` of the objective function for [`NonLinMPC`](@ref)."
675-
function obj_econ!(Ue, Ŷe, mpc::NonLinMPC, model::SimModel)
690+
function obj_econ(mpc::NonLinMPC, model::SimModel, Ue, Ŷe)
676691
E_JE = iszero(mpc.E) ? 0.0 : mpc.E*mpc.JE(Ue, Ŷe, mpc.D̂e, mpc.p)
677692
return E_JE
678693
end

src/estimator/mhe/construct.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ function get_optim_functions(
12981298
estim::MovingHorizonEstimator, ::JuMP.GenericModel{JNT}
12991299
) where {JNT <: Real}
13001300
model, con = estim.model, estim.con
1301-
nx̂, nym, nŷ, nu, He = estim.nx̂, estim.nym, model.ny, model.nu, estim.He
1301+
nx̂, nym, nŷ, nu, nϵ, He = estim.nx̂, estim.nym, model.ny, model.nu, estim., estim.He
13021302
nV̂, nX̂, ng, nZ̃ = He*nym, He*nx̂, length(con.i_g), length(estim.Z̃)
13031303
Nc = nZ̃ + 3
13041304
Z̃_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nZ̃), Nc)
@@ -1317,8 +1317,8 @@ function get_optim_functions(
13171317
V̂, X̂0 = get_tmp(V̂_cache, Z̃1), get_tmp(X̂0_cache, Z̃1)
13181318
û0, ŷ0 = get_tmp(û0_cache, Z̃1), get_tmp(ŷ0_cache, Z̃1)
13191319
V̂, X̂0 = predict!(V̂, X̂0, û0, ŷ0, estim, model, Z̃)
1320-
g = get_tmp(g_cache, Z̃1)
1321-
g = con_nonlinprog!(g, estim, model, X̂0, V̂, )
1320+
ϵ = (nϵ 0) ? Z̃[begin] : zero(T) # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
1321+
g = con_nonlinprog!(g, estim, model, X̂0, V̂, ϵ)
13221322
= get_tmp(x̄_cache, Z̃1)
13231323
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)::T
13241324
end
@@ -1332,7 +1332,8 @@ function get_optim_functions(
13321332
Z̃[i] = Z̃tup[i] # Z̃ .= Z̃tup seems to produce a type instability
13331333
end
13341334
V̂, X̂0 = predict!(V̂, X̂0, û0, ŷ0, estim, model, Z̃)
1335-
g = con_nonlinprog!(g, estim, model, X̂0, V̂, Z̃)
1335+
ϵ = (nϵ 0) ? Z̃[begin] : zero(T) # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
1336+
g = con_nonlinprog!(g, estim, model, X̂0, V̂, ϵ)
13361337
end
13371338
return g[i]
13381339
end

src/estimator/mhe/execute.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,13 @@ function predict!(V̂, X̂0, û0, ŷ0, estim::MovingHorizonEstimator, model::S
541541
end
542542

543543
"""
544-
con_nonlinprog!(g, estim::MovingHorizonEstimator, model::SimModel, X̂0, V̂, )
544+
con_nonlinprog!(g, estim::MovingHorizonEstimator, model::SimModel, X̂0, V̂, ϵ)
545545
546546
Nonlinear constrains for [`MovingHorizonEstimator`](@ref).
547547
"""
548-
function con_nonlinprog!(g, estim::MovingHorizonEstimator, ::SimModel, X̂0, V̂, )
548+
function con_nonlinprog!(g, estim::MovingHorizonEstimator, ::SimModel, X̂0, V̂, ϵ)
549549
nX̂con, nX̂ = length(estim.con.X̂0min), estim.nx̂ *estim.Nk[]
550550
nV̂con, nV̂ = length(estim.con.V̂min), estim.nym*estim.Nk[]
551-
ϵ = estim. 0 ? Z̃[begin] : 0 # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
552551
for i in eachindex(g)
553552
estim.con.i_g[i] || continue
554553
if i nX̂con

0 commit comments

Comments
 (0)