Skip to content

Commit dabd036

Browse files
committed
added : getinfo function for additional information about optimum
1 parent 0e38ae0 commit dabd036

File tree

6 files changed

+104
-27
lines changed

6 files changed

+104
-27
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ using Pkg; Pkg.add("ModelPredictiveControl")
5555
- ✅ optimization based on `JuMP.jl`:
5656
- ✅ quickly compare multiple optimizers
5757
- ✅ nonlinear solvers relying on automatic differentiation (exact derivative)
58-
- ⬜ additional information about the optimum to ease troubleshooting:
59-
- ⬜ optimal input increments over control horizon
60-
- ⬜ slack variable optimum
61-
- ⬜ objective function optimum
62-
- ⬜ output predictions at optimum
63-
- ⬜ current stochastic output predictions
64-
- ⬜ optimal economic costs
58+
- ✅ additional information about the optimum to ease troubleshooting:
59+
- ✅ optimal input increments over control horizon
60+
- ✅ slack variable optimum
61+
- ✅ objective function optimum
62+
- ✅ output predictions at optimum
63+
- ✅ current stochastic output predictions
64+
- ✅ current deterministic output predictions
65+
- ✅ optimal economic costs
66+
- ✅ optimizer solution summary
6567

6668
### State Estimation Features
6769

example/juMPC.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ setconstraint!(mpc, umin=[5, 9.9], umax=[Inf,Inf])
9595
setconstraint!(mpc, ŷmin=[-Inf,-Inf], ŷmax=[55, 35])
9696
setconstraint!(mpc, Δumin=[-Inf,-Inf],Δumax=[+Inf,+Inf])
9797

98+
moveinput!(mpc, mpc.estim.model.yop + [0, 2], mpc.estim.model.dop)
99+
display(getinfo(mpc)[1])
100+
98101
nmpc = NonLinMPC(kf, Hp=15, Hc=1, Mwt=[1, 1] , Nwt=[0.1, 0.1], Cwt=1e5, Ewt=0.0)
99102

100103
setconstraint!(nmpc, c_umin=[0,0], c_umax=[0,0])
@@ -103,6 +106,8 @@ setconstraint!(nmpc, umin=[5, 9.9], umax=[Inf,Inf])
103106
setconstraint!(nmpc, ŷmin=[-Inf,-Inf], ŷmax=[55, 35])
104107
setconstraint!(nmpc, Δumin=[-Inf,-Inf],Δumax=[+Inf,+Inf])
105108

109+
moveinput!(nmpc, nmpc.estim.model.yop + [0, 2], nmpc.estim.model.dop)
110+
display(getinfo(nmpc)[1])
106111

107112
function test_mpc(model, mpc)
108113
N = 200

src/ModelPredictiveControl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export SimModel, LinModel, NonLinModel, setop!, setstate!, updatestate!, evalout
1111
export StateEstimator, InternalModel
1212
export SteadyKalmanFilter, KalmanFilter, UnscentedKalmanFilter
1313
export initstate!
14-
export PredictiveController, LinMPC, NonLinMPC, setconstraint!, moveinput!
14+
export PredictiveController, LinMPC, NonLinMPC, setconstraint!, moveinput!, getinfo
1515

1616
include("sim_model.jl")
1717
include("state_estim.jl")

src/controller/linmpc.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
2626
Q ::Matrix{Float64}
2727
::Hermitian{Float64, Matrix{Float64}}
2828
::Vector{Float64}
29+
p ::Vector{Float64}
2930
Ks::Matrix{Float64}
3031
Ps::Matrix{Float64}
3132
d::Vector{Float64}
@@ -47,7 +48,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
4748
S_Hp, T_Hp, S_Hc, T_Hc = init_ΔUtoU(nu, Hp, Hc)
4849
E, F, G, J, Kd, Q = init_deterpred(model, Hp, Hc)
4950
con, S̃_Hp, Ñ_Hc, Ẽ = init_defaultcon(model, Hp, Hc, C, S_Hp, S_Hc, N_Hc, E)
50-
P̃, q̃ = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
51+
P̃, q̃, p = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
5152
Ks, Ps = init_stochpred(estim, Hp)
5253
d, D̂ = zeros(nd), zeros(nd*Hp)
5354
Yop, Dop = repeat(model.yop, Hp), repeat(model.dop, Hp)
@@ -59,7 +60,7 @@ struct LinMPC{S<:StateEstimator} <: PredictiveController
5960
Hp, Hc,
6061
M_Hp, Ñ_Hc, L_Hp, Cwt, R̂u, R̂y,
6162
S̃_Hp, T_Hp, T_Hc,
62-
Ẽ, F, G, J, Kd, Q, P̃, q̃,
63+
Ẽ, F, G, J, Kd, Q, P̃, q̃, p,
6364
Ks, Ps,
6465
d, D̂,
6566
Yop, Dop,

src/controller/nonlinmpc.jl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ struct NonLinMPC{S<:StateEstimator, JEFunc<:Function} <: PredictiveController
2828
Q ::Matrix{Float64}
2929
::Hermitian{Float64, Matrix{Float64}}
3030
::Vector{Float64}
31+
p ::Vector{Float64}
3132
Ks::Matrix{Float64}
3233
Ps::Matrix{Float64}
3334
d::Vector{Float64}
@@ -51,7 +52,7 @@ struct NonLinMPC{S<:StateEstimator, JEFunc<:Function} <: PredictiveController
5152
S_Hp, T_Hp, S_Hc, T_Hc = init_ΔUtoU(nu, Hp, Hc)
5253
E, F, G, J, Kd, Q = init_deterpred(model, Hp, Hc)
5354
con, S̃_Hp, Ñ_Hc, Ẽ = init_defaultcon(model, Hp, Hc, C, S_Hp, S_Hc, N_Hc, E)
54-
P̃, q̃ = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
55+
P̃, q̃, p = init_quadprog(model, Ẽ, S̃_Hp, M_Hp, Ñ_Hc, L_Hp)
5556
Ks, Ps = init_stochpred(estim, Hp)
5657
d, D̂ = zeros(nd), zeros(nd*Hp)
5758
Yop, Dop = repeat(model.yop, Hp), repeat(model.dop, Hp)
@@ -63,7 +64,7 @@ struct NonLinMPC{S<:StateEstimator, JEFunc<:Function} <: PredictiveController
6364
Hp, Hc,
6465
M_Hp, Ñ_Hc, L_Hp, Cwt, Ewt, JE, R̂u, R̂y,
6566
S̃_Hp, T_Hp, T_Hc,
66-
Ẽ, F, G, J, Kd, Q, P̃, q̃,
67+
Ẽ, F, G, J, Kd, Q, P̃, q̃, p,
6768
Ks, Ps,
6869
d, D̂,
6970
Yop, Dop,
@@ -187,6 +188,15 @@ function NonLinMPC(
187188
return NonLinMPC{S, JEFunc}(estim, Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, ru, optim)
188189
end
189190

191+
function getinfo(mpc::NonLinMPC)
192+
info, sol_summary = invoke(getinfo, Tuple{PredictiveController}, mpc)
193+
U, Ŷ, D̂ = info[:U], info[:Ŷ], info[:D̂]
194+
UE = [U; U[(end - mpc.estim.model.nu + 1):end]]
195+
ŶE = [mpc.ŷ; Ŷ]
196+
D̂E = [mpc.d; D̂]
197+
info[:JE] = mpc.JE(UE, ŶE, D̂E)
198+
return info, sol_summary
199+
end
190200

191201
"""
192202
init_optimization!(mpc::NonLinMPC)

src/predictive_control.jl

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const LinConVector = Vector{ConstraintRef{
2626
ScalarShape
2727
}}
2828

29+
const InfoDictType = Union{Vector{Float64}, Float64}
30+
2931
"Include all the data for the constraints of [`PredictiveController`](@ref)"
3032
struct ControllerConstraint
3133
Umin ::Vector{Float64}
@@ -249,17 +251,67 @@ function moveinput!(
249251
R̂y::Vector{<:Real} = repeat(ry, mpc.Hp),
250252
::Vector{<:Real} = repeat(d, mpc.Hp),
251253
ym::Union{Vector{<:Real}, Nothing} = nothing
252-
)
254+
)
255+
validate_setpointdist(mpc, ry, d, R̂y, D̂)
253256
getestimates!(mpc, mpc.estim, ym, d)
254257
predictstoch!(mpc, mpc.estim, d, ym)
255-
p = initpred!(mpc, mpc.estim.model, d, D̂, R̂y)
258+
initpred!(mpc, mpc.estim.model, d, D̂, R̂y)
256259
linconstraint!(mpc, mpc.estim.model)
257-
ΔŨ, _ = optim_objective!(mpc, p)
260+
ΔŨ = optim_objective!(mpc)
258261
Δu = ΔŨ[1:mpc.estim.model.nu] # receding horizon principle: only Δu(k) is used (1st one)
259262
u = mpc.estim.lastu0 + mpc.estim.model.uop + Δu
260263
return u
261264
end
262265

266+
#=
267+
268+
269+
"Include the additional information about the optimum to ease troubleshooting."
270+
mutable struct OptimInfo
271+
ΔŨ::Vector{Float64}
272+
ϵ ::Float64
273+
J ::Float64
274+
u ::Vector{Float64}
275+
U ::Vector{Float64}
276+
ŷ ::Vector{Float64}
277+
Ŷ ::Vector{Float64}
278+
ŷs::Vector{Float64}
279+
Ŷs::Vector{Float64}
280+
end
281+
=#
282+
283+
#=
284+
function write_info!(mpc::LinMPC, ΔŨ, J, ŷs, Ŷs)
285+
mpc.info.ΔŨ = ΔŨ
286+
mpc.info.ϵ = isinf(mpc.C) ? NaN : ΔŨ[end]
287+
mpc.info.J = J
288+
mpc.info.U = mpc.S̃_Hp*ΔŨ + mpc.T_Hp*(mpc.estim.lastu0 + mpc.estim.model.uop)
289+
mpc.info.u = mpc.info.U[1:mpc.estim.model.nu]
290+
mpc.info.ŷ = mpc.ŷ
291+
mpc.info.Ŷ = mpc.Ẽ*ΔŨ + mpc.F
292+
mpc.info.ŷs, mpc.info.Ŷs = ŷs, Ŷs
293+
end
294+
=#
295+
296+
function getinfo(mpc::PredictiveController)
297+
sol_summary = solution_summary(mpc.optim)
298+
info = Dict{Symbol, InfoDictType}()
299+
info[:ΔU] = mpc.ΔŨ[1:mpc.Hc*mpc.estim.model.nu]
300+
info[] = isinf(mpc.C) ? NaN : mpc.ΔŨ[end]
301+
info[:J] = objective_value(mpc.optim) + mpc.p[begin]
302+
info[:U] = mpc.S̃_Hp*mpc.ΔŨ + mpc.T_Hp*(mpc.estim.lastu0 + mpc.estim.model.uop)
303+
info[:u] = info[:U][1:mpc.estim.model.nu]
304+
info[:d] = mpc.d
305+
info[:D̂] = mpc.
306+
info[:ŷ] = mpc.
307+
info[:Ŷ] = predict(mpc, mpc.estim.model, mpc.ΔŨ)
308+
info[:Ŷs] = mpc.Ŷs
309+
info[:Ŷd] = info[:Ŷ] - info[:Ŷs]
310+
info[:R̂y] = mpc.R̂y
311+
info[:R̂u] = mpc.R̂u
312+
return info, sol_summary
313+
end
314+
263315
"""
264316
setstate!(mpc::PredictiveController, x̂)
265317
@@ -285,6 +337,13 @@ Call [`updatestate!`](@ref) on `mpc.estim` [`StateEstimator`](@ref).
285337
"""
286338
updatestate!(mpc::PredictiveController, u, ym, d=Float64[]) = updatestate!(mpc.estim,u,ym,d)
287339

340+
function validate_setpointdist(mpc::PredictiveController, ry, d, R̂y, D̂)
341+
ny, nd, Hp = mpc.estim.model.ny, mpc.estim.model.nd, mpc.Hp
342+
size(ry) (ny,) && error("ry size $(size(ry)) ≠ output size ($ny,)")
343+
size(d) (nd,) && error("d size $(size(d)) ≠ measured dist. size ($nd,)")
344+
size(R̂y) (ny*Hp,) && error("R̂y size $(size(R̂y)) ≠ output size × Hp ($(ny*Hp),)")
345+
size(D̂) (nd*Hp,) && error("D̂ size $(size(D̂)) ≠ measured dist. size × Hp ($(nd*Hp),)")
346+
end
288347

289348
"""
290349
getestimates!(mpc::PredictiveController, estim::StateEstimator)
@@ -355,14 +414,14 @@ function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y)
355414
mpc.R̂y[:] = R̂y
356415
= mpc.F - R̂y
357416
mpc.q̃[:] = 2(mpc.M_Hp*mpc.Ẽ)'*
358-
p ='*mpc.M_Hp*
417+
mpc.p[:] = ['*mpc.M_Hp*]
359418
if ~isempty(mpc.R̂u)
360419
lastu = mpc.estim.lastu0 + model.uop
361420
= mpc.T_Hp*lastu - mpc.R̂u
362-
mpc.q̃[:] = mpc.+ 2(mpc.L_Hp*mpc.T_Hp)'*
363-
p +='*mpc.L_Hp*
421+
mpc.q̃[:] += 2(mpc.L_Hp*mpc.T_Hp)'*
422+
mpc.p[:] += ['*mpc.L_Hp*]
364423
end
365-
return p
424+
return nothing
366425
end
367426

368427
@doc raw"""
@@ -378,8 +437,7 @@ function initpred!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y)
378437
mpc.d[:], mpc.D̂[:] = d, D̂
379438
end
380439
mpc.R̂y[:] = R̂y
381-
p = 0.0 # only used for LinModel objects
382-
return p
440+
return nothing
383441
end
384442

385443
"""
@@ -448,7 +506,7 @@ end
448506
449507
Optimize the objective function ``J`` of `mpc` controller.
450508
"""
451-
function optim_objective!(mpc::PredictiveController, p)
509+
function optim_objective!(mpc::PredictiveController)
452510
optim = mpc.optim
453511
model = mpc.estim.model
454512
ΔŨvar::Vector{VariableRef} = optim[:ΔŨvar]
@@ -476,8 +534,7 @@ function optim_objective!(mpc::PredictiveController, p)
476534
@debug solution_summary(optim)
477535
end
478536
mpc.ΔŨ[:] = isfatal(status) ? ΔŨ0 : value.(ΔŨvar) # fatal status : use last value
479-
J_val = objective_value(optim) + p # add LinModel p constant (p=0 for NonLinModel)
480-
return mpc.ΔŨ, J_val
537+
return mpc.ΔŨ
481538
end
482539

483540
"By default, no change to the objective function."
@@ -653,14 +710,16 @@ useless at optimization but required to evaluate the minimal ``J`` value.
653710
"""
654711
function init_quadprog(::LinModel, Ẽ, S_Hp, M_Hp, N_Hc, L_Hp)
655712
= 2*Hermitian(Ẽ'*M_Hp*+ N_Hc + S_Hp'*L_Hp*S_Hp)
656-
= zeros(size(P̃, 1)) # dummy value (updated just before optimization)
657-
return P̃, q̃
713+
= zeros(size(P̃, 1)) # dummy value (updated just before optimization)
714+
p = zeros(1) # dummy value (updated just before optimization)
715+
return P̃, q̃, p
658716
end
659717
"Return empty matrices if `model` is not a [`LinModel`](@ref)."
660718
function init_quadprog(::SimModel, Ẽ, S_Hp, M_Hp, N_Hc, L_Hp)
661719
= Hermitian(zeros(0, 0))
662720
= zeros(0)
663-
return P̃, q̃
721+
p = zeros(1) # dummy value (updated just before optimization)
722+
return P̃, q̃, p
664723
end
665724

666725
"Return the quadratic programming objective function, see [`init_quadprog`](@ref)."

0 commit comments

Comments
 (0)