Skip to content

Commit f60fcc9

Browse files
committed
Merge branch 'debug_test_minreal' into debug_KF
2 parents 0bae784 + 910952f commit f60fcc9

File tree

7 files changed

+60
-41
lines changed

7 files changed

+60
-41
lines changed

Project.toml

+1-1
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.5.2"
4+
version = "1.5.3"
55

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

src/estimator/execute.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ julia> estim = SteadyKalmanFilter(LinModel(tf(3, [10, 1]), 0.5), nint_ym=[2], di
125125
126126
julia> u = [1]; y = [3 - 0.1]; x̂ = round.(initstate!(estim, u, y), digits=3)
127127
3-element Vector{Float64}:
128-
5.0
128+
10.0
129129
0.0
130130
-0.1
131131

src/model/linmodel.jl

+13-12
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ with the state ``\mathbf{x}`` and output ``\mathbf{y}`` vectors. The ``\mathbf{z
8282
comprises the manipulated inputs ``\mathbf{u}`` and measured disturbances ``\mathbf{d}``,
8383
in any order. `i_u` provides the indices of ``\mathbf{z}`` that are manipulated, and `i_d`,
8484
the measured disturbances. The constructor automatically discretizes continuous systems,
85-
resamples discrete ones if `Ts ≠ sys.Ts`, computes a new realization if not minimal, and
86-
separates the ``\mathbf{z}`` terms in two parts (details in Extended Help). The rest of the
87-
documentation assumes discrete dynamics since all systems end up in this form.
85+
resamples discrete ones if `Ts ≠ sys.Ts`, computes a new balancing and minimal state-space
86+
realization, and separates the ``\mathbf{z}`` terms in two parts (details in Extended Help).
87+
The rest of the documentation assumes discrete models since all systems end up in this form.
8888
8989
See also [`ss`](@extref ControlSystemsBase.ss)
9090
@@ -119,10 +119,11 @@ LinModel with a sample time Ts = 0.1 s and:
119119
`sys` is discrete and the provided argument `Ts ≠ sys.Ts`, the system is resampled by
120120
using the aforementioned discretization methods.
121121
122-
Note that the constructor transforms the system to its minimal realization using [`minreal`](@extref ControlSystemsBase.minreal)
123-
for controllability/observability. As a consequence, the final state-space
124-
representation may be different from the one provided in `sys`. It is also converted
125-
into a more practical form (``\mathbf{D_u=0}`` because of the zero-order hold):
122+
Note that the constructor transforms the system to its minimal and balancing realization
123+
using [`minreal`](@extref ControlSystemsBase.minreal) for controllability/observability.
124+
As a consequence, the final state-space representation will be presumably different from
125+
the one provided in `sys`. It is also converted into a more practical form
126+
(``\mathbf{D_u=0}`` because of the zero-order hold):
126127
```math
127128
\begin{aligned}
128129
\mathbf{x}(k+1) &= \mathbf{A x}(k) + \mathbf{B_u u}(k) + \mathbf{B_d d}(k) \\
@@ -152,8 +153,8 @@ function LinModel(
152153
sysu = sminreal(sys[:,i_u]) # remove states associated to measured disturbances d
153154
sysd = sminreal(sys[:,i_d]) # remove states associated to manipulates inputs u
154155
if !iszero(sysu.D)
155-
error("LinModel only supports strictly proper systems (state matrix D must be 0 "*
156-
"for columns associated to manipulated inputs u)")
156+
error("LinModel only supports strictly proper systems (state-space matrix D must "*
157+
"be 0 for columns associated to manipulated inputs u)")
157158
end
158159
if iscontinuous(sys)
159160
isnothing(Ts) && error("Sample time Ts must be specified if sys is continuous")
@@ -175,7 +176,7 @@ function LinModel(
175176
end
176177
end
177178
# minreal to merge common poles if possible and ensure controllability/observability:
178-
sys_dis = minreal([sysu_dis sysd_dis]) # same realization if already minimal
179+
sys_dis = minreal([sysu_dis sysd_dis])
179180
nu = length(i_u)
180181
A = sys_dis.A
181182
Bu = sys_dis.B[:,1:nu]
@@ -207,7 +208,7 @@ LinModel with a sample time Ts = 0.5 s and:
207208
```
208209
"""
209210
function LinModel(sys::TransferFunction, Ts::Union{Real,Nothing} = nothing; kwargs...)
210-
sys_min = minreal(ss(sys)) # remove useless states with pole-zero cancellation
211+
sys_min = ss(sys) # minreal is automatically called during conversion
211212
return LinModel(sys_min, Ts; kwargs...)
212213
end
213214

@@ -220,7 +221,7 @@ Discretize with zero-order hold when `sys` is a continuous system with delays.
220221
The delays must be multiples of the sample time `Ts`.
221222
"""
222223
function LinModel(sys::DelayLtiSystem, Ts::Real; kwargs...)
223-
sys_dis = minreal(c2d(sys, Ts, :zoh)) # c2d only supports :zoh for DelayLtiSystem
224+
sys_dis = c2d(sys, Ts, :zoh) # c2d only supports :zoh for DelayLtiSystem
224225
return LinModel(sys_dis, Ts; kwargs...)
225226
end
226227

test/0_test_module.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Ts = 400.0
44
sys = [ tf(1.90,[1800.0,1]) tf(1.90,[1800.0,1]) tf(1.90,[1800.0,1]);
55
tf(-0.74,[800.0,1]) tf(0.74,[800.0,1]) tf(-0.74,[800.0,1]) ]
6-
sys_ss = minreal(ss(sys))
7-
Gss = c2d(sys_ss[:,1:2], Ts, :zoh)
8-
Gss2 = c2d(sys_ss[:,1:2], 0.5Ts, :zoh)
6+
sys_ss = ss(sys)
7+
sys_ss_u = sminreal(sys_ss[:,1:2])
8+
Gss = minreal(c2d(sys_ss_u, Ts, :zoh))
9+
Gss2 = minreal(c2d(sys_ss_u, 0.5Ts, :zoh))
910
export Ts, sys, sys_ss, Gss, Gss2
1011
end

test/1_test_sim_model.jl

+16-11
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
@test linmodel5.nu == 2
3838
@test linmodel5.nd == 1
3939
@test linmodel5.ny == 2
40-
sysu_ss = sminreal(c2d(minreal(ss(sys))[:,1:2], Ts, :zoh))
41-
sysd_ss = sminreal(c2d(minreal(ss(sys))[:,3], Ts, :tustin))
42-
sys_ss = [sysu_ss sysd_ss]
40+
sysu_ss = c2d(sminreal(ss(sys)[:,1:2]), Ts, :zoh)
41+
sysd_ss = c2d(sminreal(ss(sys)[:,3]), Ts, :tustin)
42+
sys_ss = minreal([sysu_ss sysd_ss])
4343
@test linmodel5.A sys_ss.A
4444
@test linmodel5.Bu sys_ss.B[:,1:2]
4545
@test linmodel5.Bd sys_ss.B[:,3]
@@ -51,14 +51,19 @@
5151

5252
linmodel6 = LinModel([delay(Ts) delay(Ts)]*sys,Ts,i_d=[3])
5353
@test linmodel6.nx == 3
54-
@test sum(eigvals(linmodel6.A) .≈ 0) == 1
55-
56-
linmodel7 = LinModel(
57-
ss(diagm( .1: .1: .3), I(3), diagm( .4: .1: .6), 0, 1.0),
58-
i_u=[1, 2],
59-
i_d=[3])
60-
@test linmodel7.A diagm( .1: .1: .3)
61-
@test linmodel7.C diagm( .4: .1: .6)
54+
@test sum(isapprox.(eigvals(linmodel6.A), 0, atol=1e-15)) == 1
55+
56+
A = diagm( .1: .1: .3)
57+
Bu = [I(2); zeros(1,2)]
58+
C = diagm( .4: .1: .6)
59+
Bd = [zeros(2,1); I(1)]
60+
Dd = 0;
61+
linmodel7 = LinModel(A, Bu, C, Bd, Dd, 1.0)
62+
@test linmodel7.A A
63+
@test linmodel7.Bu Bu
64+
@test linmodel7.Bd Bd
65+
@test linmodel7.C C
66+
@test linmodel7.Dd zeros(3,1)
6267

6368
linmodel8 = LinModel(Gss.A, Gss.B, Gss.C, zeros(Float32, 2, 0), zeros(Float32, 2, 0), Ts)
6469
@test isa(linmodel8, LinModel{Float64})

test/2_test_state_estim.jl

+23-11
Original file line numberDiff line numberDiff line change
@@ -1334,8 +1334,8 @@ end
13341334
@testitem "MovingHorizonEstimator v.s. Kalman filters" setup=[SetupMPCtests] begin
13351335
using .SetupMPCtests, ControlSystemsBase, LinearAlgebra
13361336
linmodel1 = setop!(LinModel(sys,Ts,i_d=[3]), uop=[10,50], yop=[50,30], dop=[20])
1337-
mhe = MovingHorizonEstimator(linmodel1, He=3, nint_ym=0, direct=false)
13381337
kf = KalmanFilter(linmodel1, nint_ym=0, direct=false)
1338+
mhe = MovingHorizonEstimator(linmodel1, He=3, nint_ym=0, direct=false)
13391339
X̂_mhe = zeros(4, 6)
13401340
X̂_kf = zeros(4, 6)
13411341
for i in 1:6
@@ -1347,28 +1347,33 @@ end
13471347
updatestate!(mhe, [11, 50], y, [25])
13481348
updatestate!(kf, [11, 50], y, [25])
13491349
end
1350-
@test X̂_mhe X̂_kf atol=1e-3 rtol=1e-3
1351-
mhe = MovingHorizonEstimator(linmodel1, He=3, nint_ym=0, direct=true)
1350+
@test X̂_mhe X̂_kf atol=1e-6 rtol=1e-6
13521351
kf = KalmanFilter(linmodel1, nint_ym=0, direct=true)
1352+
# recuperate P̂(-1|-1) exact value using the Kalman filter:
1353+
preparestate!(kf, [50, 30], [20])
1354+
σP̂ = sqrt.(diag(kf.P̂))
1355+
mhe = MovingHorizonEstimator(linmodel1, He=3, nint_ym=0, direct=true, σP_0=σP̂)
1356+
updatestate!(kf, [10, 50], [50, 30], [20])
13531357
X̂_mhe = zeros(4, 6)
13541358
X̂_kf = zeros(4, 6)
13551359
for i in 1:6
1356-
y = [50,31] + randn(2)
1360+
y = [50,31] #+ randn(2)
13571361
x̂_mhe = preparestate!(mhe, y, [25])
13581362
x̂_kf = preparestate!(kf, y, [25])
13591363
X̂_mhe[:,i] = x̂_mhe
13601364
X̂_kf[:,i] = x̂_kf
13611365
updatestate!(mhe, [11, 50], y, [25])
13621366
updatestate!(kf, [11, 50], y, [25])
13631367
end
1364-
@test X̂_mhe X̂_kf atol=1e-3 rtol=1e-3
1368+
@test X̂_mhe X̂_kf atol=1e-6 rtol=1e-6
1369+
13651370
f = (x,u,d,model) -> model.A*x + model.Bu*u + model.Bd*d
13661371
h = (x,d,model) -> model.C*x + model.Dd*d
13671372
nonlinmodel = NonLinModel(f, h, Ts, 2, 4, 2, 1, p=linmodel1, solver=nothing)
13681373
nonlinmodel = setop!(nonlinmodel, uop=[10,50], yop=[50,30], dop=[20])
1369-
mhe = MovingHorizonEstimator(nonlinmodel, He=5, nint_ym=0, direct=false)
13701374
ukf = UnscentedKalmanFilter(nonlinmodel, nint_ym=0, direct=false)
13711375
ekf = ExtendedKalmanFilter(nonlinmodel, nint_ym=0, direct=false)
1376+
mhe = MovingHorizonEstimator(nonlinmodel, He=5, nint_ym=0, direct=false)
13721377
X̂_mhe = zeros(4, 6)
13731378
X̂_ukf = zeros(4, 6)
13741379
X̂_ekf = zeros(4, 6)
@@ -1384,11 +1389,18 @@ end
13841389
updatestate!(ukf, [11, 50], y, [25])
13851390
updatestate!(ekf, [11, 50], y, [25])
13861391
end
1387-
@test X̂_mhe X̂_ukf atol=1e-3 rtol=1e-3
1388-
@test X̂_mhe X̂_ekf atol=1e-3 rtol=1e-3
1389-
mhe = MovingHorizonEstimator(nonlinmodel, He=5, nint_ym=0, direct=true)
1392+
@test X̂_mhe X̂_ukf atol=1e-6 rtol=1e-6
1393+
@test X̂_mhe X̂_ekf atol=1e-6 rtol=1e-6
1394+
13901395
ukf = UnscentedKalmanFilter(nonlinmodel, nint_ym=0, direct=true)
13911396
ekf = ExtendedKalmanFilter(nonlinmodel, nint_ym=0, direct=true)
1397+
# recuperate P̂(-1|-1) exact value using the Unscented Kalman filter:
1398+
preparestate!(ukf, [50, 30], [20])
1399+
preparestate!(ekf, [50, 30], [20])
1400+
σP̂ = sqrt.(diag(ukf.P̂))
1401+
mhe = MovingHorizonEstimator(nonlinmodel, He=5, nint_ym=0, direct=true, σP_0=σP̂)
1402+
updatestate!(ukf, [10, 50], [50, 30], [20])
1403+
updatestate!(ekf, [10, 50], [50, 30], [20])
13921404
X̂_mhe = zeros(4, 6)
13931405
X̂_ukf = zeros(4, 6)
13941406
X̂_ekf = zeros(4, 6)
@@ -1404,8 +1416,8 @@ end
14041416
updatestate!(ukf, [11, 50], y, [25])
14051417
updatestate!(ekf, [11, 50], y, [25])
14061418
end
1407-
@test X̂_mhe X̂_ukf atol=1e-3 rtol=1e-3
1408-
@test X̂_mhe X̂_ekf atol=1e-3 rtol=1e-3
1419+
@test X̂_mhe X̂_ukf atol=1e-6 rtol=1e-6
1420+
@test X̂_mhe X̂_ekf atol=1e-6 rtol=1e-6
14091421
end
14101422

14111423
@testitem "MovingHorizonEstimator LinModel v.s. NonLinModel" setup=[SetupMPCtests] begin

test/3_test_predictive_control.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -705,13 +705,13 @@ end
705705
@test_nowarn geq_end(5.0, 4.0, 3.0, 2.0)
706706
nmpc6 = NonLinMPC(linmodel3, Hp=10)
707707
preparestate!(nmpc6, [0])
708-
@test moveinput!(nmpc6, [0]) [0.0]
708+
@test moveinput!(nmpc6, [0]) [0.0] atol=5e-2
709709
nonlinmodel2 = NonLinModel{Float32}(f, h, 3000.0, 1, 2, 1, 1, solver=nothing, p=linmodel2)
710710
nmpc7 = NonLinMPC(nonlinmodel2, Hp=10)
711711
y = similar(nonlinmodel2.yop)
712712
nonlinmodel2.solver_h!(y, Float32[0,0], Float32[0], nonlinmodel2.p)
713713
preparestate!(nmpc7, [0], [0])
714-
@test moveinput!(nmpc7, [0], [0]) [0.0]
714+
@test moveinput!(nmpc7, [0], [0]) [0.0] atol=5e-2
715715
nmpc8 = NonLinMPC(nonlinmodel, Nwt=[0], Hp=100, Hc=1, transcription=MultipleShooting())
716716
preparestate!(nmpc8, [0], [0])
717717
u = moveinput!(nmpc8, [10], [0])

0 commit comments

Comments
 (0)