Skip to content

Commit ca55d25

Browse files
committed
debug: evaloutput for InternalModel now include current stochastic estimate
removed: `evalŷ` method (useless with new `preparestate!`) added: warning if `evaloutput` is called before `preparestate!` with current estimators added: warning if `moveinput!` is called before `preparestate!`with current estimators tests: calling `preparestate!` where needed to avoid the warnings
1 parent f8b8338 commit ca55d25

File tree

9 files changed

+108
-60
lines changed

9 files changed

+108
-60
lines changed

docs/src/internals/state_estim.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,6 @@ ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel)
4040
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
4141
```
4242

43-
## Evaluate Estimated Output
44-
45-
```@docs
46-
ModelPredictiveControl.evalŷ
47-
```
48-
4943
## Remove Operating Points
5044

5145
```@docs

src/controller/execute.jl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ See also [`LinMPC`](@ref), [`ExplicitMPC`](@ref), [`NonLinMPC`](@ref).
4242
```jldoctest
4343
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1);
4444
45-
julia> ry = [5]; u = moveinput!(mpc, ry); round.(u, digits=3)
45+
julia> preparestate!(mpc, [0]); ry = [5];
46+
47+
julia> u = moveinput!(mpc, ry); round.(u, digits=3)
4648
1-element Vector{Float64}:
4749
1.0
4850
```
@@ -58,6 +60,9 @@ function moveinput!(
5860
R̂y = Rhaty,
5961
R̂u = Rhatu
6062
)
63+
if mpc.estim.direct && !mpc.estim.corrected[]
64+
@warn "preparestate! should be called before moveinput! with current estimators"
65+
end
6166
validate_args(mpc, ry, d, D̂, R̂y, R̂u)
6267
initpred!(mpc, mpc.estim.model, d, D̂, R̂y, R̂u)
6368
linconstraint!(mpc, mpc.estim.model)
@@ -102,7 +107,7 @@ available for [`NonLinMPC`](@ref).
102107
```jldoctest
103108
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1, Hc=1);
104109
105-
julia> u = moveinput!(mpc, [10]);
110+
julia> preparestate!(mpc, [0]); u = moveinput!(mpc, [10]);
106111
107112
julia> round.(getinfo(mpc)[:Ŷ], digits=3)
108113
1-element Vector{Float64}:
@@ -184,7 +189,7 @@ They are computed with these equations using in-place operations:
184189
function initpred!(mpc::PredictiveController, model::LinModel, d, D̂, R̂y, R̂u)
185190
mul!(mpc.T_lastu0, mpc.T, mpc.estim.lastu0)
186191
ŷ, F, q̃, r = mpc.ŷ, mpc.F, mpc.q̃, mpc.r
187-
ŷ .= evalŷ(mpc.estim, d)
192+
ŷ .= evaloutput(mpc.estim, d)
188193
predictstoch!(mpc, mpc.estim) # init mpc.F with Ŷs for InternalModel
189194
F .+= mpc.B
190195
mul!(F, mpc.K, mpc.estim.x̂0, 1, 1)
@@ -220,7 +225,7 @@ Init `ŷ, F, d0, D̂0, D̂E, R̂y0, R̂u0` vectors when model is not a [`LinMod
220225
"""
221226
function initpred!(mpc::PredictiveController, model::SimModel, d, D̂, R̂y, R̂u)
222227
mul!(mpc.T_lastu0, mpc.T, mpc.estim.lastu0)
223-
mpc.ŷ .= evalŷ(mpc.estim, d)
228+
mpc.ŷ .= evaloutput(mpc.estim, d)
224229
predictstoch!(mpc, mpc.estim) # init mpc.F with Ŷs for InternalModel
225230
if model.nd 0
226231
mpc.d0 .= d .- model.dop

src/estimator/execute.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,14 @@ If applicable, it also sets the error covariance `estim.P̂` to `estim.P̂_0`.
100100
101101
# Examples
102102
```jldoctest
103-
julia> estim = SteadyKalmanFilter(LinModel(tf(3, [10, 1]), 0.5), nint_ym=[2]);
103+
julia> estim = SteadyKalmanFilter(LinModel(tf(3, [10, 1]), 0.5), nint_ym=[2], direct=false);
104104
105105
julia> u = [1]; y = [3 - 0.1]; x̂ = round.(initstate!(estim, u, y), digits=3)
106106
3-element Vector{Float64}:
107107
5.0
108108
0.0
109109
-0.1
110110
111-
julia> preparestate!(estim, y);
112-
113111
julia> x̂ ≈ updatestate!(estim, u, y)
114112
true
115113
@@ -172,19 +170,25 @@ init_estimate!(::StateEstimator, ::SimModel, _ , _ , _ ) = nothing
172170
173171
Evaluate `StateEstimator` outputs `ŷ` from `estim.x̂0` states and disturbances `d`.
174172
175-
It returns `estim` output at the current time step ``\mathbf{ŷ}(k)``. Calling a
176-
[`StateEstimator`](@ref) object calls this `evaloutput` method.
173+
It returns `estim` output at the current time step ``\mathbf{ŷ}(k)``. If `estim.direct` is
174+
`true`, the method [`preparestate!`](@ref) should be called beforehand to correct the state
175+
estimate.
176+
177+
Calling a [`StateEstimator`](@ref) object calls this `evaloutput` method.
177178
178179
# Examples
179180
```jldoctest
180-
julia> kf = SteadyKalmanFilter(setop!(LinModel(tf(2, [10, 1]), 5), yop=[20]));
181+
julia> kf = SteadyKalmanFilter(setop!(LinModel(tf(2, [10, 1]), 5), yop=[20]), direct=false);
181182
182183
julia> ŷ = evaloutput(kf)
183184
1-element Vector{Float64}:
184185
20.0
185186
```
186187
"""
187188
function evaloutput(estim::StateEstimator{NT}, d=estim.buffer.empty) where NT <: Real
189+
if estim.direct && !estim.corrected[]
190+
@warn "preparestate! should be called before evaloutput with current estimators"
191+
end
188192
validate_args(estim.model, d)
189193
ŷ0, d0 = estim.buffer.ŷ, estim.buffer.d
190194
d0 .= d .- estim.model.dop

src/estimator/internal_model.jl

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -326,23 +326,16 @@ function init_estimate!(estim::InternalModel, model::LinModel{NT}, y0m, d0, u0)
326326
return nothing
327327
end
328328

329-
@doc raw"""
330-
evalŷ(estim::InternalModel, d) -> ŷ
331-
332-
Get [`InternalModel`](@ref) estimated output `ŷ`.
333-
334-
[`InternalModel`](@ref) estimator needs current stochastic output ``\mathbf{ŷ_s}(k)`` to
335-
estimate its outputs ``\mathbf{ŷ}(k)``. The method [`preparestate!`](@ref) store this value
336-
inside `estim` object, it should be thus called before `evalŷ`.
337-
"""
338-
function evalŷ(estim::InternalModel, d)
329+
# Compute estimated output with current stochastic estimate `estim.ŷs` for `InternalModel`
330+
function evaloutput(estim::InternalModel, d)
339331
if !estim.corrected[]
340-
error("InternalModel: preparestate! must be called before evalŷ")
332+
@warn "preparestate! should be called before evaloutput with InternalModel"
341333
end
334+
validate_args(estim.model, d)
342335
ŷ0d, d0 = estim.buffer.ŷ, estim.buffer.d
343336
d0 .= d .- estim.model.dop
344-
h!(ŷ0d, estim.model, estim.x̂d, d0)
345-
= ŷ0d
337+
!(ŷ0d, estim, estim.model, estim.x̂0, d0)
338+
= ŷ0d
346339
.+= estim.model.yop .+ estim.ŷs
347340
return
348341
end

src/plot_sim.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ function sim_closedloop!(
290290
u = sim_getu!(est_mpc, u_ry, d, ru)
291291
ud = u + u_step + u_noise.*randn(plant.nu)
292292
Y_data[:, i] .= y
293-
Ŷ_data[:, i] .= evalŷ(estim, d)
293+
Ŷ_data[:, i] .= evaloutput(estim, d)
294294
U_Ry_data[:, i] .= u_ry
295295
U_data[:, i] .= u
296296
Ud_data[:, i] .= ud

src/predictive_control.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Functor allowing callable `PredictiveController` object as an alias for [`movein
99
1010
# Examples
1111
```jldoctest
12-
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1);
12+
julia> mpc = LinMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1, direct=false);
1313
1414
julia> u = mpc([5]); round.(u, digits=3)
1515
1-element Vector{Float64}:

src/state_estim.jl

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Functor allowing callable `StateEstimator` object as an alias for [`evaloutput`]
99
1010
# Examples
1111
```jldoctest
12-
julia> kf = KalmanFilter(setop!(LinModel(tf(3, [10, 1]), 2), yop=[20]));
12+
julia> kf = KalmanFilter(setop!(LinModel(tf(3, [10, 1]), 2), yop=[20]), direct=false);
1313
1414
julia> ŷ = kf()
1515
1-element Vector{Float64}:
@@ -83,12 +83,4 @@ include("estimator/execute.jl")
8383
include("estimator/kalman.jl")
8484
include("estimator/luenberger.jl")
8585
include("estimator/mhe.jl")
86-
include("estimator/internal_model.jl")
87-
88-
"""
89-
evalŷ(estim::StateEstimator, d) -> ŷ
90-
91-
Evaluate [`StateEstimator`](@ref) output `ŷ` from measured disturbance `d` and `estim.x̂0`.
92-
"""
93-
evalŷ(estim::StateEstimator, d) = evaloutput(estim, d)
94-
86+
include("estimator/internal_model.jl")

test/test_predictive_control.jl

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ end
5454
linmodel = setop!(LinModel(tf(5, [2, 1]), 3), yop=[10])
5555
mpc1 = LinMPC(linmodel, Nwt=[0], Hp=1000, Hc=1)
5656
r = [15]
57+
preparestate!(mpc1, [10])
5758
u = moveinput!(mpc1, r)
5859
@test u [1] atol=1e-2
5960
u = mpc1(r)
@@ -62,13 +63,16 @@ end
6263
@test info[:u] u
6364
@test info[:Ŷ][end] r[1] atol=1e-2
6465
mpc2 = LinMPC(linmodel, Nwt=[0], Cwt=Inf, Hp=1000, Hc=1)
66+
preparestate!(mpc2, [10])
6567
u = moveinput!(mpc2, r)
6668
@test u [1] atol=1e-2
6769
mpc3 = LinMPC(linmodel, Mwt=[0], Nwt=[0], Lwt=[1])
70+
preparestate!(mpc3, [10])
6871
u = moveinput!(mpc3, [0], R̂u=fill(12, mpc3.Hp))
6972
@test u [12] atol=1e-2
7073
model2 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0)
7174
mpc4 = LinMPC(model2)
75+
preparestate!(mpc4, [0])
7276
moveinput!(mpc4, [0]) [0.0]
7377

7478
@test_throws DimensionMismatch moveinput!(mpc1, [0,0,0])
@@ -127,10 +131,10 @@ end
127131
setstate!(mpc1, [1,2,3,4])
128132
@test mpc1.estim.x̂0 [1,2,3,4]
129133
setstate!(mpc1, [0,0,0,0])
130-
preparestate!(mpc1, mpc1.estim())
131-
updatestate!(mpc1, mpc1.estim.model.uop, mpc1.estim())
134+
preparestate!(mpc1, [50, 30])
135+
updatestate!(mpc1, mpc1.estim.model.uop, [50, 30])
132136
@test mpc1.estim.x̂0 [0,0,0,0]
133-
preparestate!(mpc1, mpc1.estim())
137+
preparestate!(mpc1, [50, 30])
134138
@test_throws ArgumentError updatestate!(mpc1, [0,0])
135139
end
136140

@@ -187,6 +191,7 @@ end
187191
@test_throws ArgumentError setconstraint!(mpc, c_ymin=[0,0,0])
188192
@test_throws ArgumentError setconstraint!(mpc, c_ymax=[0,0,0])
189193

194+
preparestate!(mpc, mpc.estim.model.yop, mpc.estim.model.dop)
190195
moveinput!(mpc, [0, 0], [0])
191196
@test_throws ErrorException setconstraint!(mpc, c_umin=[1, 1], c_umax=[1, 1])
192197
@test_throws ErrorException setconstraint!(mpc, umin=[-Inf,-Inf], umax=[+Inf,+Inf])
@@ -208,6 +213,7 @@ end
208213
setconstraint!(mpc, umin=[-3], umax=[3])
209214
setconstraint!(mpc, Δumin=[-1.5], Δumax=[1.5])
210215
setconstraint!(mpc, ymin=[-100], ymax=[100])
216+
preparestate!(mpc, [0])
211217
moveinput!(mpc, [-10])
212218
info = getinfo(mpc)
213219
@test info[:ΔU][begin] -1.5 atol=1e-1
@@ -276,6 +282,7 @@ end
276282
@test mpc.con.Y0min fill(-54.0 -10, 1000)
277283
@test mpc.con.Y0max fill(56.0 -10, 1000)
278284
r = [15]
285+
preparestate!(mpc, [10])
279286
u = moveinput!(mpc, r)
280287
@test u [2] atol=1e-2
281288
setmodel!(mpc, setop!(LinModel(tf(5, [2, 1]), 3), yop=[20], uop=[11]))
@@ -346,6 +353,7 @@ end
346353
@testset "ExplicitMPC moves and getinfo" begin
347354
mpc1 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1)
348355
r = [5]
356+
preparestate!(mpc1, [0])
349357
u = moveinput!(mpc1, r)
350358
@test u [1] atol=1e-2
351359
u = mpc1(r)
@@ -354,13 +362,16 @@ end
354362
@test info[:u] u
355363
@test info[:Ŷ][end] r[1] atol=1e-2
356364
mpc2 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Nwt=[0], Hp=1000, Hc=1)
365+
preparestate!(mpc2, [0])
357366
u = moveinput!(mpc2, [5])
358367
@test u [1] atol=1e-2
359368
mpc3 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Mwt=[0], Nwt=[0], Lwt=[1])
369+
preparestate!(mpc3, [0])
360370
u = moveinput!(mpc3, [0], R̂u=fill(12, mpc3.Hp))
361371
@test u [12] atol=1e-2
362372
model2 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0)
363373
mpc4 = ExplicitMPC(model2)
374+
preparestate!(mpc4, [0])
364375
moveinput!(mpc4, [0]) [0.0]
365376
end
366377

@@ -414,10 +425,10 @@ end
414425
setstate!(mpc1, [1,2,3,4])
415426
@test mpc1.estim.x̂0 [1,2,3,4]
416427
setstate!(mpc1, [0,0,0,0])
417-
preparestate!(mpc1, mpc1.estim())
418-
updatestate!(mpc1, mpc1.estim.model.uop, mpc1.estim())
428+
preparestate!(mpc1, [50, 30])
429+
updatestate!(mpc1, mpc1.estim.model.uop, [50, 30])
419430
@test mpc1.estim.x̂0 [0,0,0,0]
420-
preparestate!(mpc1, mpc1.estim())
431+
preparestate!(mpc1, [50, 30])
421432
@test_throws ArgumentError updatestate!(mpc1, [0,0])
422433
end
423434

@@ -433,6 +444,7 @@ end
433444
@test mpc.Yop fill(10.0, 1000)
434445
@test mpc.Uop fill(1.0, 1000)
435446
r = [15]
447+
preparestate!(mpc, [10])
436448
u = moveinput!(mpc, r)
437449
@test u [2] atol=1e-2
438450
setmodel!(mpc, setop!(LinModel(tf(5, [2, 1]), 3), yop=[20], uop=[11]))
@@ -512,6 +524,7 @@ end
512524
linmodel = setop!(LinModel(tf(5, [2000, 1]), 3000.0), yop=[10])
513525
nmpc_lin = NonLinMPC(linmodel, Nwt=[0], Hp=1000, Hc=1)
514526
r = [15]
527+
preparestate!(nmpc_lin, [10])
515528
u = moveinput!(nmpc_lin, r)
516529
@test u [1] atol=5e-2
517530
u = nmpc_lin(r)
@@ -523,16 +536,18 @@ end
523536
R̂y = fill(r[1], Hp)
524537
JE = (_ , ŶE, _ ) -> sum((ŶE[2:end] - R̂y).^2)
525538
nmpc = NonLinMPC(linmodel, Mwt=[0], Nwt=[0], Cwt=Inf, Ewt=1, JE=JE, Hp=Hp, Hc=1)
539+
preparestate!(nmpc, [10])
526540
u = moveinput!(nmpc)
527541
@test u [1] atol=5e-2
528542
# ensure that the current estimated output is updated for correct JE values:
529-
@test nmpc. ModelPredictiveControl.evalŷ(nmpc.estim, Float64[])
543+
@test nmpc. evaloutput(nmpc.estim, Float64[])
530544
linmodel2 = LinModel([tf(5, [2000, 1]) tf(7, [8000,1])], 3000.0, i_d=[2])
531545
f = (x,u,d) -> linmodel2.A*x + linmodel2.Bu*u + linmodel2.Bd*d
532546
h = (x,d) -> linmodel2.C*x + linmodel2.Dd*d
533547
nonlinmodel = NonLinModel(f, h, 3000.0, 1, 2, 1, 1, solver=nothing)
534548
nmpc2 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=1000, Hc=1)
535549
d = [0.1]
550+
preparestate!(nmpc2, [0], [0])
536551
u = moveinput!(nmpc2, 7d, d)
537552
@test u [0] atol=5e-2
538553
u = nmpc2(7d, d)
@@ -541,9 +556,11 @@ end
541556
@test info[:u] u
542557
@test info[:Ŷ][end] 7d[1] atol=5e-2
543558
nmpc3 = NonLinMPC(nonlinmodel, Nwt=[0], Cwt=Inf, Hp=1000, Hc=1)
559+
preparestate!(nmpc3, [0], [0])
544560
u = moveinput!(nmpc3, 7d, d)
545561
@test u [0] atol=5e-2
546562
nmpc4 = NonLinMPC(nonlinmodel, Hp=15, Mwt=[0], Nwt=[0], Lwt=[1])
563+
preparestate!(nmpc4, [0], [0])
547564
u = moveinput!(nmpc4, [0], d, R̂u=fill(12, nmpc4.Hp))
548565
@test u [12] atol=5e-2
549566
nmpc5 = setconstraint!(NonLinMPC(nonlinmodel, Hp=15, Cwt=Inf), ymin=[1])
@@ -554,11 +571,13 @@ end
554571
@test ForwardDiff.gradient(vec->g_Y0min_end(vec...), [20.0, 10.0]) [-5, -5] atol=1e-3
555572
linmodel3 = LinModel{Float32}(0.5*ones(1,1), ones(1,1), ones(1,1), zeros(1,0), zeros(1,0), 1.0)
556573
nmpc6 = NonLinMPC(linmodel3, Hp=10)
574+
preparestate!(nmpc6, [0])
557575
@test moveinput!(nmpc6, [0]) [0.0]
558576
nonlinmodel2 = NonLinModel{Float32}(f, h, 3000.0, 1, 2, 1, 1, solver=nothing)
559577
nmpc7 = NonLinMPC(nonlinmodel2, Hp=10)
560578
y = similar(nonlinmodel2.yop)
561579
nonlinmodel2.h!(y, Float32[0,0], Float32[0])
580+
preparestate!(nmpc7, [0], [0])
562581
@test moveinput!(nmpc7, [0], [0]) [0.0]
563582
end
564583

@@ -608,14 +627,16 @@ end
608627
linmodel = setop!(LinModel(sys,Ts,i_u=[1,2]), uop=[10,50], yop=[50,30])
609628
f = (x,u,_) -> linmodel.A*x + linmodel.Bu*u
610629
h = (x,_) -> linmodel.C*x
611-
nonlinmodel = NonLinModel(f, h, Ts, 2, 2, 2, solver=nothing)
630+
nonlinmodel = setop!(
631+
NonLinModel(f, h, Ts, 2, 2, 2, solver=nothing), uop=[10,50], yop=[50,30]
632+
)
612633
nmpc1 = NonLinMPC(nonlinmodel, Hp=15)
613634
@test initstate!(nmpc1, [10, 50], [20, 25]) zeros(4)
614635
setstate!(nmpc1, [1,2,3,4])
615636
@test nmpc1.estim.x̂0 [1,2,3,4]
616637
setstate!(nmpc1, [0,0,0,0])
617-
preparestate!(nmpc1, nmpc1.estim())
618-
updatestate!(nmpc1, nmpc1.estim.model.uop, nmpc1.estim())
638+
preparestate!(nmpc1, [50, 30])
639+
updatestate!(nmpc1, nmpc1.estim.model.uop, [50, 30])
619640
@test nmpc1.estim.x̂0 [0,0,0,0] atol=1e-6
620641
end
621642

@@ -662,6 +683,7 @@ end
662683
setconstraint!(nmpc_lin, umin=[-3], umax=[3])
663684
setconstraint!(nmpc_lin, Δumin=[-1.5], Δumax=[1.5])
664685
setconstraint!(nmpc_lin, ymin=[-100], ymax=[100])
686+
preparestate!(nmpc_lin, [0])
665687
moveinput!(nmpc_lin, [-20])
666688
info = getinfo(nmpc_lin)
667689
@test info[:ΔU][begin] -1.5 atol=1e-2
@@ -699,6 +721,7 @@ end
699721
setconstraint!(nmpc, umin=[-3], umax=[3])
700722
setconstraint!(nmpc, Δumin=[-1.5], Δumax=[1.5])
701723
setconstraint!(nmpc, ymin=[-100], ymax=[100])
724+
preparestate!(nmpc, [0])
702725
moveinput!(nmpc, [-20])
703726
info = getinfo(nmpc)
704727
@test info[:ΔU][begin] -1.5 atol=1e-2
@@ -740,6 +763,7 @@ end
740763
@test mpc.con.Y0min fill(-54.0 -10, 1000)
741764
@test mpc.con.Y0max fill(56.0 -10, 1000)
742765
r = [15]
766+
preparestate!(mpc, [10])
743767
u = moveinput!(mpc, r)
744768
@test u [2] atol=1e-2
745769
setmodel!(mpc, setop!(LinModel(tf(5, [2, 1]), 3), yop=[20], uop=[11]))

0 commit comments

Comments
 (0)