Skip to content

Commit b996ab0

Browse files
committed
added steadystate tests for all controllers based on InternalModel, nint_u and nint_ym options.
1 parent 4d790c1 commit b996ab0

File tree

11 files changed

+155
-61
lines changed

11 files changed

+155
-61
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 = "0.8.3"
4+
version = "0.8.4"
55

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

docs/src/manual/linmpc.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ default, [`LinMPC`](@ref) controllers use [`OSQP`](https://osqp.org/) to solve t
7676
soft constraints on output predictions ``\mathbf{ŷ}`` to ensure feasibility, and a
7777
[`SteadyKalmanFilter`](@ref) to estimate the plant states[^1]. An attentive reader will also
7878
notice that the Kalman filter estimates two additional states compared to the plant model.
79-
These are the integrators for the unmeasured plant disturbances, and they are automatically
80-
added to the model outputs by default if feasible (see [`SteadyKalmanFilter`](@ref)
79+
These are the integrating states for the unmeasured plant disturbances, and they are
80+
automatically added to the model outputs by default if feasible (see [`SteadyKalmanFilter`](@ref)
8181
for details).
8282

8383
[^1]: We could have use an [`InternalModel`](@ref) structure with

src/controller/explicitmpc.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ julia> mpc = ExplicitMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
104104
ExplicitMPC controller with a sample time Ts = 4.0 s, SteadyKalmanFilter estimator and:
105105
30 prediction steps Hp
106106
1 control steps Hc
107-
1 manipulated inputs u (0 integrators)
107+
1 manipulated inputs u (0 integrating states)
108108
4 states x̂
109-
2 measured outputs ym (2 integrators)
109+
2 measured outputs ym (2 integrating states)
110110
0 unmeasured outputs yu
111111
0 measured disturbances d
112112
```
@@ -129,9 +129,9 @@ julia> mpc = ExplicitMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
129129
ExplicitMPC controller with a sample time Ts = 4.0 s, KalmanFilter estimator and:
130130
30 prediction steps Hp
131131
1 control steps Hc
132-
1 manipulated inputs u (0 integrators)
132+
1 manipulated inputs u (0 integrating states)
133133
3 states x̂
134-
1 measured outputs ym (1 integrators)
134+
1 measured outputs ym (1 integrating states)
135135
1 unmeasured outputs yu
136136
0 measured disturbances d
137137
```
@@ -179,7 +179,7 @@ linconstraint!(::ExplicitMPC, ::LinModel) = nothing
179179
Analytically solve the optimization problem for [`ExplicitMPC`](@ref).
180180
"""
181181
function optim_objective!(mpc::ExplicitMPC)
182-
return ldiv!(mpc.ΔŨ, mpc.P̃_chol, -mpc.q̃)
182+
return lmul!(-1, ldiv!(mpc.ΔŨ, mpc.P̃_chol, mpc.))
183183
end
184184

185185
"For [`ExplicitMPC`](@ref), return an empty summary."

src/controller/linmpc.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ julia> mpc = LinMPC(model, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
120120
LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFilter estimator and:
121121
30 prediction steps Hp
122122
1 control steps Hc
123-
1 manipulated inputs u (0 integrators)
123+
1 manipulated inputs u (0 integrating states)
124124
4 states x̂
125-
2 measured outputs ym (2 integrators)
125+
2 measured outputs ym (2 integrating states)
126126
0 unmeasured outputs yu
127127
0 measured disturbances d
128128
```
@@ -167,9 +167,9 @@ julia> mpc = LinMPC(estim, Mwt=[0, 1], Nwt=[0.5], Hp=30, Hc=1)
167167
LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, KalmanFilter estimator and:
168168
30 prediction steps Hp
169169
1 control steps Hc
170-
1 manipulated inputs u (0 integrators)
170+
1 manipulated inputs u (0 integrating states)
171171
3 states x̂
172-
1 measured outputs ym (1 integrators)
172+
1 measured outputs ym (1 integrating states)
173173
1 unmeasured outputs yu
174174
0 measured disturbances d
175175
```

src/controller/nonlinmpc.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ julia> mpc = NonLinMPC(model, Hp=20, Hc=1, Cwt=1e6)
134134
NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedKalmanFilter estimator and:
135135
20 prediction steps Hp
136136
1 control steps Hc
137-
1 manipulated inputs u (0 integrators)
137+
1 manipulated inputs u (0 integrating states)
138138
2 states x̂
139-
1 measured outputs ym (1 integrators)
139+
1 measured outputs ym (1 integrating states)
140140
0 unmeasured outputs yu
141141
0 measured disturbances d
142142
```
@@ -171,9 +171,9 @@ julia> mpc = NonLinMPC(estim, Hp=20, Hc=1, Cwt=1e6)
171171
NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedKalmanFilter estimator and:
172172
20 prediction steps Hp
173173
1 control steps Hc
174-
1 manipulated inputs u (0 integrators)
174+
1 manipulated inputs u (0 integrating states)
175175
2 states x̂
176-
1 measured outputs ym (1 integrators)
176+
1 measured outputs ym (1 integrating states)
177177
0 unmeasured outputs yu
178178
0 measured disturbances d
179179
```

src/estimator/kalman.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,21 +96,21 @@ unmeasured ones, for ``\mathbf{Ĉ^u, D̂_d^u}``).
9696
- `nint_u=0`: integrator quantity for the stochastic model of the unmeasured disturbances at
9797
the manipulated inputs (vector), use `nint_u=0` for no integrator (see Extended Help).
9898
- `σQint_u=fill(1,sum(nint_u))`: same than `σQ` but for the unmeasured disturbances at
99-
manipulated inputs ``\mathbf{Q_{int_u}}`` (composed of integrators).
99+
manipulated inputs ``\mathbf{Q_{int_u}}`` (composed of integrating states).
100100
- `nint_ym=default_nint(model,i_ym,nint_u)` : same than `nint_u` but for the unmeasured
101101
disturbances at the measured outputs, use `nint_ym=0` for no integrator (see Extended Help).
102102
- `σQint_ym=fill(1,sum(nint_ym))` : same than `σQ` for the unmeasured disturbances at
103-
measured outputs ``\mathbf{Q_{int_{ym}}}`` (composed of integrators).
103+
measured outputs ``\mathbf{Q_{int_{ym}}}`` (composed of integrating states).
104104
105105
# Examples
106106
```jldoctest
107107
julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 0.5);
108108
109109
julia> estim = SteadyKalmanFilter(model, i_ym=[2], σR=[1], σQint_ym=[0.01])
110110
SteadyKalmanFilter estimator with a sample time Ts = 0.5 s, LinModel and:
111-
1 manipulated inputs u (0 integrators)
111+
1 manipulated inputs u (0 integrating states)
112112
3 states x̂
113-
1 measured outputs ym (1 integrators)
113+
1 measured outputs ym (1 integrating states)
114114
1 unmeasured outputs yu
115115
0 measured disturbances d
116116
```
@@ -249,9 +249,9 @@ value with ``\mathbf{P̂}_{-1}(0) =
249249
- `σP0=fill(1/model.nx,model.nx)` : main diagonal of the initial estimate covariance
250250
``\mathbf{P}(0)``, specified as a standard deviation vector.
251251
- `σP0int_u=fill(1,sum(nint_u))` : same than `σP0` but for the unmeasured disturbances at
252-
manipulated inputs ``\mathbf{P_{int_u}}(0)`` (composed of integrators).
252+
manipulated inputs ``\mathbf{P_{int_u}}(0)`` (composed of integrating states).
253253
- `σP0int_ym=fill(1,sum(nint_ym))` : same than `σP0` but for the unmeasured disturbances at
254-
measured outputs ``\mathbf{P_{int_{ym}}}(0)`` (composed of integrators).
254+
measured outputs ``\mathbf{P_{int_{ym}}}(0)`` (composed of integrating states).
255255
- `<keyword arguments>` of [`SteadyKalmanFilter`](@ref) constructor.
256256
257257
# Examples
@@ -260,9 +260,9 @@ julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 0.5);
260260
261261
julia> estim = KalmanFilter(model, i_ym=[2], σR=[1], σP0=[100, 100], σQint_ym=[0.01])
262262
KalmanFilter estimator with a sample time Ts = 0.5 s, LinModel and:
263-
1 manipulated inputs u (0 integrators)
263+
1 manipulated inputs u (0 integrating states)
264264
3 states x̂
265-
1 measured outputs ym (1 integrators)
265+
1 measured outputs ym (1 integrating states)
266266
1 unmeasured outputs yu
267267
0 measured disturbances d
268268
```
@@ -418,9 +418,9 @@ julia> model = NonLinModel((x,u,_)->0.1x+u, (x,_)->2x, 10.0, 1, 1, 1);
418418
419419
julia> estim = UnscentedKalmanFilter(model, σR=[1], nint_ym=[2], σP0int_ym=[1, 1])
420420
UnscentedKalmanFilter estimator with a sample time Ts = 10.0 s, NonLinModel and:
421-
1 manipulated inputs u (0 integrators)
421+
1 manipulated inputs u (0 integrating states)
422422
3 states x̂
423-
1 measured outputs ym (2 integrators)
423+
1 measured outputs ym (2 integrating states)
424424
0 unmeasured outputs yu
425425
0 measured disturbances d
426426
```
@@ -646,9 +646,9 @@ julia> model = NonLinModel((x,u,_)->0.2x+u, (x,_)->-3x, 5.0, 1, 1, 1);
646646
647647
julia> estim = ExtendedKalmanFilter(model, σQ=[2], σQint_ym=[2], σP0=[0.1], σP0int_ym=[0.1])
648648
ExtendedKalmanFilter estimator with a sample time Ts = 5.0 s, NonLinModel and:
649-
1 manipulated inputs u (0 integrators)
649+
1 manipulated inputs u (0 integrating states)
650650
2 states x̂
651-
1 measured outputs ym (1 integrators)
651+
1 measured outputs ym (1 integrating states)
652652
0 unmeasured outputs yu
653653
0 measured disturbances d
654654
```

src/estimator/luenberger.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 0.5);
7070
7171
julia> estim = Luenberger(model, nint_ym=[1, 1], p̂=[0.61, 0.62, 0.63, 0.64])
7272
Luenberger estimator with a sample time Ts = 0.5 s, LinModel and:
73-
1 manipulated inputs u (0 integrators)
73+
1 manipulated inputs u (0 integrating states)
7474
4 states x̂
75-
2 measured outputs ym (2 integrators)
75+
2 measured outputs ym (2 integrating states)
7676
0 unmeasured outputs yu
7777
0 measured disturbances d
7878
```

src/predictive_control.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ julia> mpc = setconstraint!(mpc, Δumin=[-10], Δumax=[+10], c_Δumin=[1.0], c_
9696
LinMPC controller with a sample time Ts = 4.0 s, OSQP optimizer, SteadyKalmanFilter estimator and:
9797
10 prediction steps Hp
9898
2 control steps Hc
99-
1 manipulated inputs u (0 integrators)
99+
1 manipulated inputs u (0 integrating states)
100100
2 states x̂
101-
1 measured outputs ym (1 integrators)
101+
1 measured outputs ym (1 integrating states)
102102
0 unmeasured outputs yu
103103
0 measured disturbances d
104104
```
@@ -296,8 +296,9 @@ end
296296
297297
Get additional information about `mpc` controller optimum to ease troubleshooting.
298298
299-
Return the optimizer solution summary that can be printed, `sol_summary`, and the dictionary
300-
`info` with the following fields:
299+
The function should be called after calling [`moveinput!`](@ref). It returns the optimizer
300+
solution summary that can be printed, `sol_summary`, and the dictionary `info` with the
301+
following fields:
301302
302303
- `:ΔU` : optimal manipulated input increments over `Hc` ``(\mathbf{ΔU})``
303304
- `:ϵ` : optimal slack variable ``(ϵ)``
@@ -336,7 +337,7 @@ function getinfo(mpc::PredictiveController)
336337
info[:D̂] = mpc.
337338
info[:ŷ] = mpc.
338339
info[:Ŷ] =
339-
info[:Ŷs] = mpc.Ŷop - repeat(mpc.estim.model.yop, mpc.Hp)
340+
info[:Ŷs] = mpc.Ŷop - repeat(mpc.estim.model.yop, mpc.Hp) # Ŷop = Ŷs + Yop
340341
info[:R̂y] = mpc.R̂y
341342
info[:R̂u] = mpc.R̂u
342343
return sol_summary, info

src/state_estim.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ end
4646
function print_estim_dim(io::IO, estim::StateEstimator, n)
4747
nu, nd = estim.model.nu, estim.model.nd
4848
nx̂, nym, nyu = estim.nx̂, estim.nym, estim.nyu
49-
println(io, "$(lpad(nu, n)) manipulated inputs u ($(sum(estim.nint_u)) integrators)")
49+
println(io, "$(lpad(nu, n)) manipulated inputs u ($(sum(estim.nint_u)) integrating states)")
5050
println(io, "$(lpad(nx̂, n)) states x̂")
51-
println(io, "$(lpad(nym, n)) measured outputs ym ($(sum(estim.nint_ym)) integrators)")
51+
println(io, "$(lpad(nym, n)) measured outputs ym ($(sum(estim.nint_ym)) integrating states)")
5252
println(io, "$(lpad(nyu, n)) unmeasured outputs yu")
5353
print(io, "$(lpad(nd, n)) measured disturbances d")
5454
end

test/test_predictive_control.jl

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,36 @@ end
7272
mpc3 = LinMPC(LinModel(tf(5, [2, 1]), 3), Mwt=[0], Nwt=[0], Lwt=[1], ru=[12])
7373
u = moveinput!(mpc3, [0])
7474
@test u [12] atol=1e-2
75+
mpc_im = LinMPC(InternalModel(LinModel(tf(5, [2, 1]), 3)))
76+
ym, u = mpc_im.estim.model() - [5], [0.0]
77+
for i=1:25
78+
ym = mpc_im.estim.model() - [5]
79+
u = moveinput!(mpc_im, r; ym)
80+
updatestate!(mpc_im, u, ym)
81+
updatestate!(mpc_im.estim.model, u)
82+
end
83+
@test u [2] atol=1e-2
84+
@test ym [5] atol=1e-2
85+
mpc_nint_u = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1]))
86+
ym, u = mpc_nint_u.estim.model() - [5], [0.0]
87+
for i=1:25
88+
ym = mpc_nint_u.estim.model() - [5]
89+
u = moveinput!(mpc_nint_u, r; ym)
90+
updatestate!(mpc_nint_u, u, ym)
91+
updatestate!(mpc_nint_u.estim.model, u)
92+
end
93+
@test u [2] atol=1e-2
94+
@test ym [5] atol=1e-2
95+
mpc_nint_ym = LinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1]))
96+
ym, u = mpc_nint_ym.estim.model() - [5], [0.0]
97+
for i=1:25
98+
ym = mpc_nint_ym.estim.model() - [5]
99+
u = moveinput!(mpc_nint_ym, r; ym)
100+
updatestate!(mpc_nint_ym, u, ym)
101+
updatestate!(mpc_nint_ym.estim.model, u)
102+
end
103+
@test u [2] atol=1e-2
104+
@test ym [5] atol=1e-2
75105
end
76106

77107
@testset "LinMPC other methods" begin
@@ -136,6 +166,36 @@ end
136166
mpc3 = ExplicitMPC(LinModel(tf(5, [2, 1]), 3), Mwt=[0], Nwt=[0], Lwt=[1], ru=[12])
137167
u = moveinput!(mpc3, [0])
138168
@test u [12] atol=1e-2
169+
mpc_im = ExplicitMPC(InternalModel(LinModel(tf(5, [2, 1]), 3)))
170+
ym, u = mpc_im.estim.model() - [5], [0.0]
171+
for i=1:25
172+
ym = mpc_im.estim.model() - [5]
173+
u = moveinput!(mpc_im, r; ym)
174+
updatestate!(mpc_im, u, ym)
175+
updatestate!(mpc_im.estim.model, u)
176+
end
177+
@test u [2] atol=1e-2
178+
@test ym [5] atol=1e-2
179+
mpc_nint_u = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1]))
180+
ym, u = mpc_nint_u.estim.model() - [5], [0.0]
181+
for i=1:25
182+
ym = mpc_nint_u.estim.model() - [5]
183+
u = moveinput!(mpc_nint_u, r; ym)
184+
updatestate!(mpc_nint_u, u, ym)
185+
updatestate!(mpc_nint_u.estim.model, u)
186+
end
187+
@test u [2] atol=1e-2
188+
@test ym [5] atol=1e-2
189+
mpc_nint_ym = ExplicitMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1]))
190+
ym, u = mpc_nint_ym.estim.model() - [5], [0.0]
191+
for i=1:25
192+
ym = mpc_nint_ym.estim.model() - [5]
193+
u = moveinput!(mpc_nint_ym, r; ym)
194+
updatestate!(mpc_nint_ym, u, ym)
195+
updatestate!(mpc_nint_ym.estim.model, u)
196+
end
197+
@test u [2] atol=1e-2
198+
@test ym [5] atol=1e-2
139199
end
140200

141201
@testset "ExplicitMPC other methods" begin
@@ -231,16 +291,15 @@ end
231291
nonlinmodel = NonLinModel(f, h, 3.0, 1, 2, 1, 1)
232292
nmpc2 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=1000, Hc=1)
233293
d = [0.1]
234-
r = 7*d
235-
u = moveinput!(nmpc2, r, d)
294+
u = moveinput!(nmpc2, 7d, d)
236295
@test u [0] atol=5e-2
237-
u = nmpc2(r, d)
296+
u = nmpc2(7d, d)
238297
@test u [0] atol=5e-2
239298
_ , info = getinfo(nmpc2)
240299
@test info[:u] u
241-
@test info[:Ŷ][end] r[1] atol=5e-2
300+
@test info[:Ŷ][end] 7d[1] atol=5e-2
242301
nmpc3 = NonLinMPC(nonlinmodel, Nwt=[0], Cwt=Inf, Hp=1000, Hc=1)
243-
u = moveinput!(nmpc3, r, d)
302+
u = moveinput!(nmpc3, 7d, d)
244303
@test u [0] atol=5e-2
245304
nmpc4 = NonLinMPC(nonlinmodel, Mwt=[0], Nwt=[0], Lwt=[1], ru=[12])
246305
u = moveinput!(nmpc4, [0], d)
@@ -249,6 +308,37 @@ end
249308
C_Ymax_end = nmpc5.optim.nlp_model.operators.registered_multivariate_operators[end].f
250309
@test C_Ymax_end(Float64.((1.0, 1.0))) 0.0 # test con_nonlinprog_i(i,::NTuple{N, Float64})
251310
@test C_Ymax_end(Float32.((1.0, 1.0))) 0.0 # test con_nonlinprog_i(i,::NTuple{N, Real})
311+
nmpc_im = NonLinMPC(InternalModel(LinModel(tf(5, [2, 1]), 3)))
312+
ym, u = nmpc_im.estim.model() - [5], [0.0]
313+
for i=1:25
314+
ym = nmpc_im.estim.model() - [5]
315+
u = moveinput!(nmpc_im, r; ym)
316+
updatestate!(nmpc_im, u, ym)
317+
updatestate!(nmpc_im.estim.model, u)
318+
end
319+
@test u [2] atol=1e-2
320+
@test ym [5] atol=1e-2
321+
nmpc_nint_u = NonLinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_u=[1]))
322+
ym, u = nmpc_nint_u.estim.model() - [5], [0.0]
323+
for i=1:25
324+
ym = nmpc_nint_u.estim.model() - [5]
325+
u = moveinput!(nmpc_nint_u, r; ym)
326+
updatestate!(nmpc_nint_u, u, ym)
327+
updatestate!(nmpc_nint_u.estim.model, u)
328+
end
329+
@test u [2] atol=1e-2
330+
@test ym [5] atol=1e-2
331+
nmpc_nint_ym = NonLinMPC(SteadyKalmanFilter(LinModel(tf(5, [2, 1]), 3), nint_ym=[1]))
332+
ym, u = nmpc_nint_ym.estim.model() - [5], [0.0]
333+
for i=1:25
334+
ym = nmpc_nint_ym.estim.model() - [5]
335+
u = moveinput!(nmpc_nint_ym, r; ym)
336+
updatestate!(nmpc_nint_ym, u, ym)
337+
updatestate!(nmpc_nint_ym.estim.model, u)
338+
end
339+
@test u [2] atol=1e-2
340+
@test ym [5] atol=1e-2
341+
252342
end
253343

254344
@testset "NonLinMPC other methods" begin

0 commit comments

Comments
 (0)