Skip to content

Commit 7f51fb8

Browse files
committed
added : Luenberger observer with pole placement
1 parent e8f9a1b commit 7f51fb8

File tree

9 files changed

+128
-9
lines changed

9 files changed

+128
-9
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.5.2"
4+
version = "0.5.3"
55

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

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ more detailed examples.
108108
- ⬜ supported state estimators/observers:
109109
- ✅ steady-state Kalman filter
110110
- ✅ Kalman filter
111-
- Luenberger observer
111+
- Luenberger observer
112112
- ✅ internal model structure
113113
- ⬜ extended Kalman filter
114114
- ✅ unscented Kalman filter

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Pages = [
6464
- ⬜ supported state estimators/observers:
6565
- ✅ steady-state Kalman filter
6666
- ✅ Kalman filter
67-
- Luenberger observer
67+
- Luenberger observer
6868
- ✅ internal model structure
6969
- ⬜ extended Kalman filter
7070
- ✅ unscented Kalman filter

docs/src/public/state_estim.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ SteadyKalmanFilter
5050
KalmanFilter
5151
```
5252

53+
## Luenberger
54+
55+
```@docs
56+
Luenberger
57+
```
58+
5359
## UnscentedKalmanFilter
5460

5561
```@docs

example/juMPC.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ internalModel3 = InternalModel(linModel1,i_ym=[2])
5151

5252
initstate!(internalModel1,[0,0],[1,1])
5353

54+
luenberger = Luenberger(linModel1)
55+
56+
updatestate!(luenberger, [0,0], [0,0])
57+
58+
mpcluen = LinMPC(luenberger)
59+
5460
kalmanFilter1 = KalmanFilter(linModel1)
5561
kalmanFilter2 = KalmanFilter(linModel1,nint_ym=0)
5662

src/ModelPredictiveControl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import OSQP, Ipopt
1212

1313
export SimModel, LinModel, NonLinModel, setop!, setstate!, updatestate!, evaloutput
1414
export StateEstimator, InternalModel
15-
export SteadyKalmanFilter, KalmanFilter, UnscentedKalmanFilter
15+
export SteadyKalmanFilter, KalmanFilter, Luenberger, UnscentedKalmanFilter
1616
export initstate!
1717
export PredictiveController, LinMPC, NonLinMPC, setconstraint!, moveinput!, getinfo, sim!
1818

src/estimator/luenberger.jl

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ struct Luenberger <: StateEstimator
1818
Ĉm ::Matrix{Float64}
1919
D̂dm ::Matrix{Float64}
2020
K::Matrix{Float64}
21-
function Luenberger(model, i_ym, nint_ym, Asm, Csm, L)
21+
function Luenberger(model, i_ym, nint_ym, Asm, Csm, p)
2222
nu, nx, ny = model.nu, model.nx, model.ny
2323
nym, nyu = length(i_ym), ny - length(i_ym)
2424
nxs = size(Asm,1)
2525
nx̂ = nx + nxs
26-
validate_kfcov(nym, nx̂, Q̂, R̂)
2726
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
28-
f̂, ĥ, Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
27+
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
2928
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
30-
K = L
29+
K = try
30+
place(Â, Ĉ, p, :o)
31+
catch
32+
error("Cannot compute the Luenberger gain L with specified poles p.")
33+
end
3134
i_ym = collect(i_ym)
3235
lastu0 = zeros(nu)
3336
= [copy(model.x); zeros(nxs)]
@@ -38,8 +41,69 @@ struct Luenberger <: StateEstimator
3841
As, Cs, nint_ym,
3942
Â, B̂u, B̂d, Ĉ, D̂d,
4043
Ĉm, D̂dm,
41-
Q̂, R̂,
4244
K
4345
)
4446
end
47+
end
48+
49+
@doc raw"""
50+
Luenberger(
51+
model::LinModel;
52+
i_ym = 1:model.ny,
53+
nint_ym = fill(1, length(i_ym)),
54+
p̂ = 1e-3*(0:(model.nx + sum(nint_ym)-1)) .+ 0.5)
55+
)
56+
57+
Construct a Luenberger observer with the [`LinModel`](@ref) `model`.
58+
59+
`i_ym` provides the `model` output indices that are measured ``\mathbf{y^m}``, the rest are
60+
unmeasured ``\mathbf{y^u}``. `model` matrices are augmented with the stochastic model, which
61+
is specified by the numbers of output integrator `nint_ym` (see [`SteadyKalmanFilter`](@ref)
62+
Extended Help). The argument `p̂` is a vector of `model.nx + sum(nint_ym)` elements
63+
specifying the observer poles/eigenvalues (near ``z=0.5`` by default). The method computes
64+
the observer gain ``\mathbf{K}`` with [`place`](https://juliacontrol.github.io/ControlSystems.jl/stable/lib/synthesis/#ControlSystemsBase.place).
65+
66+
# Examples
67+
```jldoctest
68+
julia> model = LinModel([tf(3, [30, 1]); tf(-2, [5, 1])], 0.5);
69+
70+
julia> lo = Luenberger(model, nint_ym=[1, 1], p̂=[0.61, 0.62, 0.63, 0.64])
71+
Luenberger estimator with a sample time Ts = 0.5 s, LinModel and:
72+
1 manipulated inputs u
73+
4 states x̂
74+
2 measured outputs ym
75+
0 unmeasured outputs yu
76+
0 measured disturbances d
77+
```
78+
"""
79+
function Luenberger(
80+
model::LinModel;
81+
i_ym::IntRangeOrVector = 1:model.ny,
82+
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
83+
= 1e-3*(0:(model.nx + sum(nint_ym)-1)) .+ 0.5
84+
)
85+
if nint_ym == 0 # alias for no output integrator at all :
86+
nint_ym = fill(0, length(i_ym));
87+
end
88+
Asm, Csm = init_estimstoch(i_ym, nint_ym)
89+
nx = model.nx
90+
if length(p̂) model.nx + sum(nint_ym)
91+
error("p̂ length ($(length(p̂))) ≠ nx ($nx) + integrator quantity ($(sum(nint_ym)))")
92+
end
93+
any(abs.(p̂) .≥ 1) && error("Observer poles p̂ should be inside the unit circles.")
94+
return Luenberger(model, i_ym, nint_ym, Asm, Csm, p̂)
95+
end
96+
97+
98+
"""
99+
updatestate!(estim::Luenberger, u, ym, d=Float64[])
100+
101+
Same `SteadyKalmanFilter` but using the Luenberger observer.
102+
"""
103+
function updatestate!(estim::Luenberger, u, ym, d=Float64[])
104+
u, d, ym = remove_op!(estim, u, d, ym)
105+
Â, B̂u, B̂d, Ĉm, D̂dm = estim.Â, estim.B̂u, estim.B̂d, estim.Ĉm, estim.D̂dm
106+
x̂, K = estim.x̂, estim.K
107+
x̂[:] =*+ B̂u*u + B̂d*d + K*(ym - Ĉm*- D̂dm*d)
108+
return
45109
end

src/state_estim.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ end
265265
(estim::StateEstimator)(d=Float64[]) = evaloutput(estim, d)
266266

267267
include("estimator/kalman.jl")
268+
include("estimator/luenberger.jl")
268269
include("estimator/internal_model.jl")
269270

270271
"Get [`InternalModel`](@ref) output `ŷ` from current measured outputs `ym` and dist. `d`."

test/test_state_estim.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,48 @@ end
9797
@test kalmanfilter1. [1,2,3,4]
9898
end
9999

100+
@testset "Luenberger construction" begin
101+
linmodel1 = LinModel(sys,Ts,i_u=[1,2])
102+
lo1 = Luenberger(linmodel1)
103+
@test lo1.nym == 2
104+
@test lo1.nyu == 0
105+
@test lo1.nxs == 2
106+
@test lo1.nx̂ == 4
107+
108+
linmodel2 = LinModel(sys,Ts,i_d=[3])
109+
lo2 = Luenberger(linmodel2, i_ym=[2])
110+
@test lo2.nym == 1
111+
@test lo2.nyu == 1
112+
@test lo2.nxs == 1
113+
@test lo2.nx̂ == 5
114+
115+
lo3 = Luenberger(linmodel1, nint_ym=0)
116+
@test lo3.nxs == 0
117+
@test lo3.nx̂ == 2
118+
119+
lo4 = Luenberger(linmodel1, nint_ym=[2,2])
120+
@test lo4.nxs == 4
121+
@test lo4.nx̂ == 6
122+
123+
@test_throws ErrorException Luenberger(linmodel1, nint_ym=[1,1,1])
124+
@test_throws ErrorException Luenberger(linmodel1, nint_ym=[-1,0])
125+
@test_throws ErrorException Luenberger(linmodel1, p̂=[0.5])
126+
@test_throws ErrorException Luenberger(linmodel1, p̂=fill(1.5, lo1.nx̂))
127+
end
128+
129+
@testset "Luenberger estimator methods" begin
130+
linmodel1 = setop!(LinModel(sys,Ts,i_u=[1,2]), uop=[10,50], yop=[50,30])
131+
lo1 = Luenberger(linmodel1)
132+
@test updatestate!(lo1, [10, 50], [50, 30]) zeros(4)
133+
@test updatestate!(lo1, [10, 50], [50, 30], Float64[]) zeros(4)
134+
@test lo1. zeros(4)
135+
@test evaloutput(lo1) lo1() [50, 30]
136+
@test evaloutput(lo1, Float64[]) lo1(Float64[]) [50, 30]
137+
@test initstate!(lo1, [10, 50], [50, 30+1]) [zeros(3); [1]]
138+
setstate!(lo1, [1,2,3,4])
139+
@test lo1. [1,2,3,4]
140+
end
141+
100142
@testset "InternalModel construction" begin
101143
linmodel1 = LinModel(sys,Ts,i_u=[1,2])
102144
internalmodel1 = InternalModel(linmodel1)

0 commit comments

Comments
 (0)