Skip to content

Commit 2e0db48

Browse files
committed
added: MHE update cov with KF if LinModel
1 parent 05d439d commit 2e0db48

File tree

3 files changed

+100
-53
lines changed

3 files changed

+100
-53
lines changed

src/estimator/kalman.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ control period ``k-1``. See [^2] for details.
333333
Linear Dynamical Systems*, https://web.stanford.edu/class/ee363/lectures/kf.pdf.
334334
"""
335335
function update_estimate!(estim::KalmanFilter, u, ym, d)
336-
return update_estimate_kf!(estim, estim.Â, estim.Ĉm, u, ym, d)
336+
return update_estimate_kf!(estim, u, ym, d, estim.Â, estim.Ĉm, estim.P̂, estim.)
337337
end
338338

339339
struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
@@ -736,7 +736,7 @@ function update_estimate!(estim::ExtendedKalmanFilter, u, ym, d=empty(estim.x̂)
736736
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, u, d), estim.x̂)
737737
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, d), estim.x̂)
738738
Ĥm = Ĥ[estim.i_ym, :]
739-
return update_estimate_kf!(estim, F̂, Ĥm, u, ym, d)
739+
return update_estimate_kf!(estim, u, ym, d, F̂, Ĥm, estim.P̂, estim.)
740740
end
741741

742742
"Set `estim.P̂` to `estim.P̂0` for the time-varying Kalman Filters."
@@ -766,25 +766,25 @@ function validate_kfcov(nym, nx̂, Q̂, R̂, P̂0=nothing)
766766
end
767767

768768
"""
769-
update_estimate_kf!(estim, Â, Ĉm, u, ym, d; updatestate=true)
769+
update_estimate_kf!(estim, u, ym, d, Â, Ĉm, P̂, x̂=nothing)
770770
771771
Update time-varying/extended Kalman Filter estimates with augmented `Â` and `Ĉm` matrices.
772772
773773
Allows code reuse for [`KalmanFilter`](@ref) and [`ExtendedKalmanFilterKalmanFilter`](@ref).
774774
They update the state `x̂` and covariance `P̂` with the same equations. The extended filter
775775
substitutes the augmented model matrices with its Jacobians (`Â = F̂` and `Ĉm = Ĥm`).
776776
The implementation uses in-place operations and explicit factorization to reduce
777-
allocations. See e.g. [`KalmanFilter`](@ref) docstring for the equations. If `updatestate`
778-
is `false`, only the covariance `P̂` is updated.
777+
allocations. See e.g. [`KalmanFilter`](@ref) docstring for the equations. If `isnothing(x̂)`,
778+
only the covariance `P̂` is updated.
779779
"""
780-
function update_estimate_kf!(estim, Â, Ĉm, u, ym, d; updatestate=true)
781-
x̂, P̂, Q̂, R̂, K̂, = estim.x̂, estim.P̂, estim.Q̂, estim., estim., estim.
780+
function update_estimate_kf!(estim, u, ym, d, Â, Ĉm, P̂, x̂=nothing)
781+
Q̂, R̂, M̂ = estim.Q̂, estim.R̂, estim.
782782
mul!(M̂, P̂, Ĉm')
783783
rdiv!(M̂, cholesky!(Hermitian(Ĉm ** Ĉm' + R̂)))
784-
if updatestate
785-
mul!(K̂, Â, M̂)
784+
if !isnothing(x̂)
785+
mul!(estim.K̂, Â, M̂)
786786
ŷm = @views (estim, estim.model, x̂, d)[estim.i_ym]
787-
x̂[:] = (estim, estim.model, x̂, u, d) +* (ym - ŷm)
787+
x̂[:] = (estim, estim.model, x̂, u, d) + estim.* (ym - ŷm)
788788
end
789789
.data[:] =* (P̂ -* Ĉm * P̂) *' +# .data is necessary for Hermitians
790790
return nothing

src/estimator/mhe.jl

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ struct MovingHorizonEstimator{
1212
::Vector{NT}
1313
lastu0::Vector{NT}
1414
::Vector{NT}
15-
::Hermitian{NT, Matrix{NT}}
1615
He::Int
1716
i_ym::Vector{Int}
1817
nx̂ ::Int
@@ -24,18 +23,17 @@ struct MovingHorizonEstimator{
2423
Cs_y::Matrix{NT}
2524
nint_u ::Vector{Int}
2625
nint_ym::Vector{Int}
27-
::Matrix{NT}
28-
B̂u::Matrix{NT}
29-
::Matrix{NT}
30-
B̂d::Matrix{NT}
31-
D̂d::Matrix{NT}
26+
::Matrix{NT}
27+
B̂u ::Matrix{NT}
28+
::Matrix{NT}
29+
B̂d ::Matrix{NT}
30+
D̂d ::Matrix{NT}
3231
P̂0::Hermitian{NT, Matrix{NT}}
3332
::Hermitian{NT, Matrix{NT}}
3433
::Hermitian{NT, Matrix{NT}}
3534
invP̄::Hermitian{NT, Matrix{NT}}
3635
invQ̂_He::Hermitian{NT, Matrix{NT}}
3736
invR̂_He::Hermitian{NT, Matrix{NT}}
38-
::Matrix{NT}
3937
::Matrix{NT}
4038
X̂min::Vector{NT}
4139
X̂max::Vector{NT}
@@ -46,6 +44,7 @@ struct MovingHorizonEstimator{
4644
D ::Union{Vector{NT}, Missing}
4745
::Union{Vector{NT}, Missing}
4846
x̂0_past::Vector{NT}
47+
P̂0_past::Hermitian{NT, Matrix{NT}}
4948
Nk::Vector{Int}
5049
function MovingHorizonEstimator{NT, SM, JM}(
5150
model::SM, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, optim::JM
@@ -65,8 +64,7 @@ struct MovingHorizonEstimator{
6564
invP̄ = Hermitian(inv(P̂0), :L)
6665
invQ̂_He = Hermitian(repeatdiag(inv(Q̂), He), :L)
6766
invR̂_He = Hermitian(repeatdiag(inv(R̂), He), :L)
68-
= copy(P̂0)
69-
K̂, M̂ = zeros(NT, nx̂, nym), zeros(NT, nx̂, nym)
67+
= zeros(NT, nx̂, nym)
7068
nvar, nX̂ = nx̂*(He + 1), nx̂*(He + 1)
7169
X̂min , X̂max = fill(-Inf, nX̂), fill(+Inf, nX̂)
7270
i_X̂min, i_X̂max = .!isinf.(X̂min) , .!isinf.(X̂max)
@@ -75,18 +73,19 @@ struct MovingHorizonEstimator{
7573
X̂, Ym = zeros(NT, nx̂*He), zeros(NT, nym*He)
7674
U, D, Ŵ = zeros(NT, nu*He), zeros(NT, nd*He), zeros(NT, nx̂*He)
7775
x̂0_past = zeros(NT, nx̂)
76+
P̂0_past = copy(P̂0)
7877
Nk = [0]
7978
estim = new{NT, SM, JM}(
8079
model, optim, W̃,
81-
lastu0, x̂, P̂, He,
80+
lastu0, x̂, He,
8281
i_ym, nx̂, nym, nyu, nxs,
8382
As, Cs_u, Cs_y, nint_u, nint_ym,
8483
Â, B̂u, Ĉ, B̂d, D̂d,
8584
P̂0, Q̂, R̂, invP̄, invQ̂_He, invR̂_He,
86-
K̂, M̂,
85+
M̂,
8786
X̂min, X̂max, i_g,
8887
X̂, Ym, U, D, Ŵ,
89-
x̂0_past, Nk
88+
x̂0_past, P̂0_past, Nk
9089
)
9190
init_optimization!(estim, optim)
9291
return estim
@@ -123,18 +122,13 @@ and the covariances are repeated ``N_k`` times:
123122
\mathbf{R̂}_{N_k} &= \text{diag}\mathbf{(R̂,R̂,...,R̂)}
124123
\end{aligned}
125124
```
126-
The vectors ``\mathbf{Ŵ}`` and ``\mathbf{V̂}`` incorporate the estimated process noise
125+
The estimation horizon ``H_e`` limits the window length ``N_k = \min(k+1, H_e)``. The
126+
vectors ``\mathbf{Ŵ}`` and ``\mathbf{V̂}`` encompass the estimated process noise
127127
``\mathbf{ŵ}(k-j)`` and sensor noise ``\mathbf{v̂}(k-j)`` from ``j=0`` to ``N_k-1``. The
128-
estimation horizon ``H_e`` limits the window length:
129-
```math
130-
N_k = \begin{cases}
131-
k + 1 & k < H_e \\
132-
H_e & k ≥ H_e \end{cases}
133-
```
134-
See [`SteadyKalmanFilter`](@ref) for details on ``\mathbf{R̂}, \mathbf{Q̂}`` covariances and
135-
model augmentation. The process model is identical to the one in [`UnscentedKalmanFilter`](@ref)
136-
documentation. Note that the estimation error covariance ``\mathbf{P̂}_{k-N_k}(k-N_k+1)`` is
137-
approximated with an [`ExtendedKalmanFilter`](@ref).
128+
latter is generated by the augmented model ``\mathbf{f̂, ĥ}``. See [`SteadyKalmanFilter`](@ref)
129+
for details on ``\mathbf{R̂}, \mathbf{Q̂}`` covariances and model augmentation. The process
130+
model is identical to the one in [`UnscentedKalmanFilter`](@ref) documentation. The matrix
131+
``\mathbf{P̂}_{k-N_k}(k-N_k+1)`` is estimated with an [`ExtendedKalmanFilter`](@ref).
138132
139133
!!! warning
140134
See the Extended Help of [`NonLinMPC`](@ref) function if you get an error like:
@@ -394,16 +388,16 @@ end
394388

395389
"Reset `estim.P̂`, `estim.invP̄` and the time windows for the moving horizon estimator."
396390
function init_estimate_cov!(estim::MovingHorizonEstimator, _ , _ , _ )
397-
estim.invP̄.data[:] = Hermitian(inv(estim.P̂0), :L)
398-
estim..data[:] = estim.P̂0
399-
estim.x̂0_past .= 0
400-
estim.W̃ .= 0
401-
estim.X̂ .= 0
402-
estim.Ym .= 0
403-
estim.U .= 0
404-
estim.D .= 0
405-
estim.Ŵ .= 0
406-
estim.Nk .= 0
391+
estim.invP̄.data[:] = Hermitian(inv(estim.P̂0), :L)
392+
estim.P̂0_past.data[:] = estim.P̂0
393+
estim.x̂0_past .= 0
394+
estim. .= 0
395+
estim. .= 0
396+
estim.Ym .= 0
397+
estim.U .= 0
398+
estim.D .= 0
399+
estim. .= 0
400+
estim.Nk .= 0
407401
return nothing
408402
end
409403

@@ -412,7 +406,51 @@ end
412406
413407
Update [`MovingHorizonEstimator`](@ref) state `estim.x̂`.
414408
415-
TBW
409+
Denoting the estimated process noises augmented with the state at arrival ``\mathbf{W̃} =
410+
[\begin{smallmatrix} \mathbf{x̂}_k(k-N_k+1) \\ \mathbf{Ŵ} \end{smallmatrix}]``, the following
411+
optimization problem is solved at each discrete time ``k``:
412+
```math
413+
\min_{\mathbf{W̃}} \mathbf{x̄}' \mathbf{P̄}^{-1} \mathbf{x̄}
414+
+ \mathbf{Ŵ}' \mathbf{Q̂}_{N_k}^{-1} \mathbf{Ŵ}
415+
+ \mathbf{V̂}' \mathbf{R̂}_{N_k}^{-1} \mathbf{V̂}
416+
```
417+
in which:
418+
```math
419+
\begin{aligned}
420+
N_k &= \min(k+1, H_e) \\
421+
\mathbf{x̄} &= \mathbf{x̂}_k(k-N_k+1) - \mathbf{x̂}_{k-N_k}(k-N_k+1) \\
422+
\mathbf{P̄} &= \mathbf{P̂}_{k-N_k}(k-N_k+1) \\
423+
\mathbf{Q̂}_{N_k} &= \text{diag}\mathbf{(Q̂,Q̂,...,Q̂)} \\
424+
\mathbf{R̂}_{N_k} &= \text{diag}\mathbf{(R̂,R̂,...,R̂)} \\
425+
\end{aligned}
426+
```
427+
and also:
428+
```math
429+
\mathbf{Ŵ} =
430+
\begin{bmatrix}
431+
\mathbf{ŵ}(k-N_k+1) \\[6px]
432+
\mathbf{ŵ}(k-N_k+2) \\[6px]
433+
\vdots \\[6px]
434+
\mathbf{ŵ}(k)
435+
\end{bmatrix} , \quad
436+
\mathbf{V̂} =
437+
\begin{bmatrix}
438+
\mathbf{y^m} - \mathbf{ĥ^m}\Big(\mathbf{x̂}_k(k-N_k+1), \mathbf{d}(k-N_k+1)\Big) \\
439+
\mathbf{y^m} - \mathbf{ĥ^m}\Big(\mathbf{x̂}_k(k-N_k+2), \mathbf{d}(k-N_k+2)\Big) \\
440+
\vdots \\
441+
\mathbf{y^m} - \mathbf{ĥ^m}\Big(\mathbf{x̂}_k(k), \mathbf{d}(k)\Big)
442+
\end{bmatrix}
443+
```
444+
The augmented model and the additive process noise recursively generates the states:
445+
```math
446+
\mathbf{x̂}_k(k-j+1) = \mathbf{f̂}\Big(\mathbf{x̂}_k(k-j), \mathbf{u}(k-j), \mathbf{d}(k-j)\Big)
447+
+ \mathbf{ŵ}(k-j)
448+
```
449+
Once the problem solved, the next estimate ``\mathbf{x̂}_k(k+1)`` is computed by inserting
450+
the optimal values of ``\mathbf{x̂}_k(k-N_k+1)`` and ``\mathbf{Ŵ}`` in the last equation from
451+
``j=N_k-1`` to ``0``, inclusively. Afterward, if ``k ≥ H_e``, the arrival covariance for the
452+
next time step ``\mathbf{P̂}_{k-N_k+1}(k-N_k+2)`` is estimated with the equations of
453+
[`update_estimate!(::ExtendedKalmanFilter)`](@ref) (or `KalmanFilter`, for [`LinModel`](@ref)).
416454
"""
417455
function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<:Real
418456
model, optim, x̂ = estim.model, estim.optim, estim.
@@ -477,18 +515,27 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
477515
Ŷm, X̂ = predict!(Ŷm, X̂, estim, model, estim.W̃)
478516
x̂[:] = X̂[(1 + nx̂*Nk):(nx̂*(Nk+1))]
479517
if Nk == He
480-
# update the arrival covariance with an Extended Kalman Filter:
481-
# TODO: also support UnscentedKalmanFilter, and KalmanFilter for LinModel
482-
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, u, d), estim.x̂)
483-
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, d), estim.x̂)
484-
Ĥm = Ĥ[estim.i_ym, :]
485-
update_estimate_kf!(estim, F̂, Ĥm, u, ym, d, updatestate=false)
486-
= estim.
487-
estim.invP̄.data[:] = Hermitian(inv(P̄), :L) # .data is necessary for Hermitian
518+
P̂0_past = update_cov!(estim, model, u, ym, d)
519+
estim.invP̄.data[:] = Hermitian(inv(P̂0_past), :L)
488520
end
489521
return nothing
490522
end
491523

524+
"Update the covariance `estim.P̂0_past` with the `KalmanFilter` if `model` is a `LinModel`."
525+
function update_cov!(estim::MovingHorizonEstimator, ::LinModel, u, ym, d)
526+
update_estimate_kf!(estim, u, ym, d, estim.Â, estim.Ĉ[estim.i_ym, :], estim.P̂0_past)
527+
return estim.P̂0_past
528+
end
529+
"Update it with the `ExtendedKalmanFilter` if model is not a `LinModel`."
530+
function update_cov!(estim::MovingHorizonEstimator, ::SimModel, u, ym, d)
531+
# TODO: also support UnscentedKalmanFilter
532+
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, u, d), estim.x̂)
533+
= ForwardDiff.jacobian(x̂ -> (estim, estim.model, x̂, d), estim.x̂)
534+
Ĥm = Ĥ[estim.i_ym, :]
535+
update_estimate_kf!(estim, u, ym, d, F̂, Ĥm, estim.P̂0_past)
536+
return estim.P̂0_past
537+
end
538+
492539
"""
493540
obj_nonlinprog(estim::MovingHorizonEstimator, model::SimModel, W̃)
494541

src/state_estim.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,8 @@ end
442442

443443
include("estimator/kalman.jl")
444444
include("estimator/luenberger.jl")
445-
include("estimator/internal_model.jl")
446445
include("estimator/mhe.jl")
446+
include("estimator/internal_model.jl")
447447

448448
"""
449449
evalŷ(estim::StateEstimator, _ , d) -> ŷ

0 commit comments

Comments
 (0)