Skip to content

Commit b5bc3f7

Browse files
authored
Merge pull request #136 from JuliaControl/getinfo_gc
added: `gc` in `getinfo`
2 parents 27648cc + 6ca2912 commit b5bc3f7

File tree

9 files changed

+67
-38
lines changed

9 files changed

+67
-38
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "1.1.2"
4+
version = "1.1.3"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

docs/src/manual/nonlinmpc.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ argument ("left-hand side" of the inequality above) for the in-place version:
265265

266266
```@example man_nonlin
267267
function gc!(LHS, Ue, Ŷe, _, p, ϵ)
268-
Pmax, Hp = p
268+
Pmax = p
269269
i_τ, i_ω = 1, 2
270270
for i in eachindex(LHS)
271271
τ, ω = Ue[i_τ], Ŷe[i_ω]
@@ -287,8 +287,7 @@ specifying the number of custom inequality constraints `nc`:
287287

288288
```@example man_nonlin
289289
Cwt, Pmax, nc = 1e5, 3, Hp+1
290-
p_nmpc2 = [Pmax, Hp]
291-
nmpc2 = NonLinMPC(estim2; Hp, Hc, Nwt=Nwt, Mwt=[0.5, 0], Cwt, gc!, nc, p=p_nmpc2)
290+
nmpc2 = NonLinMPC(estim2; Hp, Hc, Nwt=Nwt, Mwt=[0.5, 0], Cwt, gc!, nc, p=Pmax)
292291
using JuMP; unset_time_limit_sec(nmpc2.optim) # hide
293292
nmpc2 # hide
294293
```

src/controller/execute.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ The function should be called after calling [`moveinput!`](@ref). It returns the
9696
- `:d` : current measured disturbance, ``\mathbf{d}(k)``
9797
9898
For [`LinMPC`](@ref) and [`NonLinMPC`](@ref), the field `:sol` also contains the optimizer
99-
solution summary that can be printed. Lastly, the optimal economic cost `:JE` is also
100-
available for [`NonLinMPC`](@ref).
99+
solution summary that can be printed. Lastly, the economical cost `:JE` and the custom
100+
nonlinear constraints `:gc` values at the optimum are also available for [`NonLinMPC`](@ref).
101101
102102
# Examples
103103
```jldoctest
@@ -131,14 +131,14 @@ function getinfo(mpc::PredictiveController{NT}) where NT<:Real
131131
Ŷs .= mpc.F # predictstoch! init mpc.F with Ŷs value if estim is an InternalModel
132132
mpc.F .= oldF # restore old F value
133133
info[:ΔU] = mpc.ΔŨ[1:mpc.Hc*model.nu]
134-
info[] = mpc.== 1 ? mpc.ΔŨ[end] : NaN
134+
info[] = mpc.== 1 ? mpc.ΔŨ[end] : zero(NT)
135135
info[:J] = J
136136
info[:U] = U
137137
info[:u] = info[:U][1:model.nu]
138138
info[:d] = mpc.d0 + model.dop
139139
info[:D̂] = mpc.D̂0 + mpc.Dop
140140
info[:ŷ] = mpc.
141-
info[:Ŷ] = Ŷ0 + mpc.Yop
141+
info[:Ŷ] =
142142
info[:x̂end] = x̂0end + mpc.estim.x̂op
143143
info[:Ŷs] = Ŷs
144144
info[:R̂y] = mpc.R̂y

src/controller/nonlinmpc.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ function test_custom_functions(NT, model::SimModel, JE, gc!, nc, Uop, Yop, Dop,
423423
exception=(err, catch_backtrace())
424424
)
425425
end
426-
ϵ, gc = 0, Vector{NT}(undef, nc)
426+
ϵ, gc = zero(NT), Vector{NT}(undef, nc)
427427
try
428428
gc!(gc, Ue, Ŷe, D̂e, p, ϵ)
429429
catch err
@@ -444,12 +444,16 @@ end
444444
445445
For [`NonLinMPC`](@ref), add `:sol` and the optimal economic cost `:JE`.
446446
"""
447-
function addinfo!(info, mpc::NonLinMPC)
448-
U, Ŷ, D̂, ŷ, d = info[:U], info[:Ŷ], info[:D̂], info[:ŷ], info[:d]
447+
function addinfo!(info, mpc::NonLinMPC{NT}) where NT<:Real
448+
U, Ŷ, D̂, ŷ, d, ϵ = info[:U], info[:Ŷ], info[:D̂], info[:ŷ], info[:d], info[]
449449
Ue = [U; U[(end - mpc.estim.model.nu + 1):end]]
450450
Ŷe = [ŷ; Ŷ]
451-
D̂e = [d; D̂]
452-
info[:JE] = mpc.JE(Ue, Ŷe, D̂e, mpc.p)
451+
D̂e = [d; D̂]
452+
JE = mpc.JE(Ue, Ŷe, D̂e, mpc.p)
453+
LHS = Vector{NT}(undef, mpc.con.nc)
454+
mpc.con.gc!(LHS, Ue, Ŷe, D̂e, mpc.p, ϵ)
455+
info[:JE] = JE
456+
info[:gc] = LHS
453457
info[:sol] = JuMP.solution_summary(mpc.optim, verbose=true)
454458
return info
455459
end

src/estimator/kalman.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,12 @@ function SteadyKalmanFilter(
199199
return SteadyKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, Q̂, R̂; direct)
200200
end
201201

202-
"Throw an error if `setmodel!` is called on a SteadyKalmanFilter"
203-
function setmodel_estimator!(::SteadyKalmanFilter, args...)
204-
error("SteadyKalmanFilter does not support setmodel! (use KalmanFilter instead)")
202+
"Throw an error if `setmodel!` is called on a SteadyKalmanFilter w/o the default values."
203+
function setmodel_estimator!(estim::SteadyKalmanFilter, model, _ , _ , _ , Q̂, R̂)
204+
if estim.model !== model || !isnothing(Q̂) || !isnothing(R̂)
205+
error("SteadyKalmanFilter does not support setmodel! (use KalmanFilter instead)")
206+
end
207+
return nothing
205208
end
206209

207210
@doc raw"""

src/estimator/luenberger.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,10 @@ function update_estimate!(estim::Luenberger, y0m, d0, u0)
135135
return predict_estimate_obsv!(estim, y0m, d0, u0)
136136
end
137137

138-
"Throw an error if `setmodel!` is called on `Luenberger` observer."
139-
setmodel_estimator!(::Luenberger, args...) = error("Luenberger does not support setmodel!")
138+
"Throw an error if `setmodel!` is called on `Luenberger` observer w/o the default values."
139+
function setmodel!(estim::Luenberger, model, args...)
140+
if estim.model !== model
141+
error("Luenberger does not support setmodel!")
142+
end
143+
return nothing
144+
end

src/estimator/mhe/execute.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function getinfo(estim::MovingHorizonEstimator{NT}) where NT<:Real
134134
end
135135
info[:Ŵ] = estim.Ŵ[1:Nk*nŵ]
136136
info[:x̂arr] = x̂0arr + estim.x̂op
137-
info[] = 0 ? estim.Z̃[begin] : NaN
137+
info[] = 0 ? estim.Z̃[begin] : zero(NT)
138138
info[:J] = obj_nonlinprog!(x̄, estim, estim.model, V̂, estim.Z̃)
139139
info[:X̂] = X̂0 .+ @views [estim.x̂op; estim.X̂op[1:nx̂*Nk]]
140140
info[:x̂] = estim.x̂0 .+ estim.x̂op

test/test_predictive_control.jl

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -566,32 +566,42 @@ end
566566

567567
@testset "NonLinMPC moves and getinfo" begin
568568
linmodel = setop!(LinModel(tf(5, [2000, 1]), 3000.0), yop=[10])
569-
nmpc_lin = NonLinMPC(linmodel, Nwt=[0], Hp=1000, Hc=1)
570-
r = [15]
569+
Hp = 1000
570+
nmpc_lin = NonLinMPC(linmodel, Nwt=[0], Hp=Hp, Hc=1)
571+
ry, ru = [15], [4]
571572
preparestate!(nmpc_lin, [10])
572-
u = moveinput!(nmpc_lin, r)
573+
u = moveinput!(nmpc_lin, ry)
573574
@test u [1] atol=5e-2
574-
u = nmpc_lin(r)
575+
u = nmpc_lin(ry)
575576
@test u [1] atol=5e-2
576577
info = getinfo(nmpc_lin)
577578
@test info[:u] u
578-
@test info[:Ŷ][end] r[1] atol=5e-2
579-
Hp = 1000
580-
R̂y = fill(r[1], Hp)
581-
JE = (_ , Ŷe, _ , R̂y) -> sum((Ŷe[2:end] - R̂y).^2)
582-
nmpc = NonLinMPC(linmodel, Mwt=[0], Nwt=[0], Cwt=Inf, Ewt=1, JE=JE, p=R̂y, Hp=Hp, Hc=1)
579+
@test info[:Ŷ][end] ry[1] atol=5e-2
580+
setmodel!(nmpc_lin; Mwt=[0], Lwt=[1])
581+
u = moveinput!(nmpc_lin; R̂u=fill(ru[1], Hp))
582+
@test u [4] atol=5e-2
583+
function JE(Ue, Ŷe, _ , p)
584+
Wy, R̂y, Wu, R̂u = p
585+
return Wy*sum((R̂y-Ŷe[2:end]).^2) + Wu*sum((R̂u-Ue[1:end-1]).^2)
586+
end
587+
R̂y, R̂u = fill(ry[1], Hp), fill(ru[1], Hp)
588+
p = [1, R̂y, 0, R̂u]
589+
nmpc = NonLinMPC(linmodel, Mwt=[0], Nwt=[0], Cwt=Inf, Ewt=1, JE=JE, p=p, Hp=Hp, Hc=1)
583590
preparestate!(nmpc, [10])
584591
u = moveinput!(nmpc)
585592
@test u [1] atol=5e-2
586593
# ensure that the current estimated output is updated for correct JE values:
587594
@test nmpc. evaloutput(nmpc.estim, Float64[])
595+
nmpc.p .= [0, R̂y, 1, R̂u]
596+
u = moveinput!(nmpc)
597+
@test u [4] atol=5e-2
588598
linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2])
589599
f = (x,u,d,_) -> linmodel2.A*x + linmodel2.Bu*u + linmodel2.Bd*d
590600
h = (x,d,_) -> linmodel2.C*x + linmodel2.Dd*d
591601
nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing)
592602
nmpc2 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=1000, Hc=1)
593603
d = [0.1]
594-
preparestate!(nmpc2, [0], [0])
604+
preparestate!(nmpc2, [0], d)
595605
u = moveinput!(nmpc2, 7d, d)
596606
@test u [0] atol=5e-2
597607
u = nmpc2(7d, d)
@@ -613,7 +623,7 @@ end
613623
@test g_Y0min_end(20.0, 10.0) 0.0
614624
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}) :
615625
@test ForwardDiff.gradient(vec->g_Y0min_end(vec...), [20.0, 10.0]) [-5, -5] atol=1e-3
616-
linmodel3 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0)
626+
linmodel3 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), 0, 0, 3000.0)
617627
nmpc6 = NonLinMPC(linmodel3, Hp=10)
618628
preparestate!(nmpc6, [0])
619629
@test moveinput!(nmpc6, [0]) [0.0]
@@ -720,10 +730,11 @@ end
720730
end
721731

722732
@testset "NonLinMPC constraint violation" begin
723-
gc(Ue, Ŷe, _ ,p , ϵ) = [p[1]*(Ue .- 4.2 .- ϵ); p[2]*(Ŷe .- 3.14 .- ϵ)]
733+
gc(Ue, Ŷe, _ ,p , ϵ) = [p[1]*(Ue[1:end-1] .- 4.2 .- ϵ); p[2]*(Ŷe[2:end] .- 3.14 .- ϵ)]
734+
Hp=50
724735

725736
linmodel = LinModel(tf([2], [10000, 1]), 3000.0)
726-
nmpc_lin = NonLinMPC(linmodel, Hp=50, Hc=5, gc=gc, nc=2*(50+1), p=[0; 0])
737+
nmpc_lin = NonLinMPC(linmodel, Hp=Hp, Hc=5, gc=gc, nc=2Hp, p=[0; 0])
727738

728739
setconstraint!(nmpc_lin, x̂min=[-1e6,-Inf], x̂max=[1e6,+Inf])
729740
setconstraint!(nmpc_lin, umin=[-10], umax=[10])
@@ -758,7 +769,7 @@ end
758769
@test all(isapprox.(info[:Ŷ], 0.9; atol=1e-1))
759770
setconstraint!(nmpc_lin, ymin=[-100], ymax=[100])
760771

761-
setconstraint!(nmpc_lin, Ymin=[-0.5; fill(-100, 49)], Ymax=[0.9; fill(+100, 49)])
772+
setconstraint!(nmpc_lin, Ymin=[-0.5; fill(-100, Hp-1)], Ymax=[0.9; fill(+100, Hp-1)])
762773
moveinput!(nmpc_lin, [-10])
763774
info = getinfo(nmpc_lin)
764775
@test info[:Ŷ][end] -10 atol=1e-1
@@ -782,17 +793,18 @@ end
782793
moveinput!(nmpc_lin, [100])
783794
info = getinfo(nmpc_lin)
784795
@test all(isapprox.(info[:U], 4.2; atol=1e-1))
796+
@test all(isapprox.(info[:gc][1:Hp], 0.0; atol=1e-1))
785797

786798
nmpc_lin.p .= [0; 1]
787799
moveinput!(nmpc_lin, [100])
788800
info = getinfo(nmpc_lin)
789801
@test all(isapprox.(info[:Ŷ], 3.14; atol=1e-1))
790-
802+
@test all(isapprox.(info[:gc][Hp+1:end], 0.0; atol=1e-1))
791803

792804
f = (x,u,_,p) -> p.A*x + p.Bu*u
793805
h = (x,_,p) -> p.C*x
794806
nonlinmodel = NonLinModel(f, h, linmodel.Ts, 1, 1, 1, solver=nothing, p=linmodel)
795-
nmpc = NonLinMPC(nonlinmodel, Hp=50, Hc=5, gc=gc, nc=2*(50+1), p=[0; 0])
807+
nmpc = NonLinMPC(nonlinmodel, Hp=50, Hc=5, gc=gc, nc=2Hp, p=[0; 0])
796808

797809
setconstraint!(nmpc, x̂min=[-1e6,-Inf], x̂max=[1e6,+Inf])
798810
setconstraint!(nmpc, umin=[-10], umax=[10])
@@ -827,7 +839,7 @@ end
827839
@test all(isapprox.(info[:Ŷ], 0.9; atol=1e-1))
828840
setconstraint!(nmpc, ymin=[-100], ymax=[100])
829841

830-
setconstraint!(nmpc, Ymin=[-0.5; fill(-100, 49)], Ymax=[0.9; fill(+100, 49)])
842+
setconstraint!(nmpc, Ymin=[-0.5; fill(-100, Hp-1)], Ymax=[0.9; fill(+100, Hp-1)])
831843
moveinput!(nmpc, [-10])
832844
info = getinfo(nmpc)
833845
@test info[:Ŷ][end] -10 atol=1e-1
@@ -851,11 +863,13 @@ end
851863
moveinput!(nmpc, [100])
852864
info = getinfo(nmpc)
853865
@test all(isapprox.(info[:U], 4.2; atol=1e-1))
866+
@test all(isapprox.(info[:gc][1:Hp], 0.0; atol=1e-1))
854867

855868
nmpc.p .= [0; 1]
856869
moveinput!(nmpc, [100])
857870
info = getinfo(nmpc)
858871
@test all(isapprox.(info[:Ŷ], 3.14; atol=1e-1))
872+
@test all(isapprox.(info[:gc][Hp+1:end], 0.0; atol=1e-1))
859873

860874
end
861875

test/test_state_estim.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ end
119119
linmodel = LinModel(ss(0.5, 0.3, 1.0, 0, 10.0))
120120
linmodel = setop!(linmodel, uop=[2.0], yop=[50.0], xop=[3.0], fop=[3.0])
121121
skalmanfilter = SteadyKalmanFilter(linmodel, nint_ym=0)
122-
@test_throws ErrorException setmodel!(skalmanfilter, linmodel)
122+
@test_nowarn setmodel!(skalmanfilter, linmodel)
123+
@test_throws ErrorException setmodel!(skalmanfilter, deepcopy(linmodel))
124+
@test_throws ErrorException setmodel!(skalmanfilter, linmodel, Q̂=[0.01])
125+
@test_throws ErrorException setmodel!(skalmanfilter, linmodel, R̂=[0.01])
123126
end
124127

125128
@testset "SteadyKalmanFilter real-time simulations" begin
@@ -349,7 +352,8 @@ end
349352
linmodel = LinModel(ss(0.5, 0.3, 1.0, 0, 10.0))
350353
linmodel = setop!(linmodel, uop=[2.0], yop=[50.0], xop=[3.0], fop=[3.0])
351354
lo = Luenberger(linmodel, nint_ym=0)
352-
@test_throws ErrorException setmodel!(lo, linmodel)
355+
@test_nowarn setmodel!(lo, linmodel)
356+
@test_throws ErrorException setmodel!(lo, deepcopy(linmodel))
353357
end
354358

355359
@testset "InternalModel construction" begin

0 commit comments

Comments
 (0)