@@ -12,7 +12,6 @@ struct MovingHorizonEstimator{
1212 W̃:: Vector{NT}
1313 lastu0:: Vector{NT}
1414 x̂:: Vector{NT}
15- P̂:: 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 Q̂:: Hermitian{NT, Matrix{NT}}
3433 R̂:: 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- K̂:: Matrix{NT}
3937 M̂:: 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- P̂ = copy (P̂0)
69- K̂, M̂ = zeros (NT, nx̂, nym), zeros (NT, nx̂, nym)
67+ M̂ = 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 \m athbf{R̂}_{N_k} &= \t ext{diag}\m athbf{(R̂,R̂,...,R̂)}
124123\e nd{aligned}
125124```
126- The vectors ``\m athbf{Ŵ}`` and ``\m athbf{V̂}`` incorporate the estimated process noise
125+ The estimation horizon ``H_e`` limits the window length ``N_k = \m in(k+1, H_e)``. The
126+ vectors ``\m athbf{Ŵ}`` and ``\m athbf{V̂}`` encompass the estimated process noise
127127``\m athbf{ŵ}(k-j)`` and sensor noise ``\m athbf{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 = \b egin{cases}
131- k + 1 & k < H_e \\
132- H_e & k ≥ H_e \e nd{cases}
133- ```
134- See [`SteadyKalmanFilter`](@ref) for details on ``\m athbf{R̂}, \m athbf{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 ``\m athbf{P̂}_{k-N_k}(k-N_k+1)`` is
137- approximated with an [`ExtendedKalmanFilter`](@ref).
128+ latter is generated by the augmented model ``\m athbf{f̂, ĥ}``. See [`SteadyKalmanFilter`](@ref)
129+ for details on ``\m athbf{R̂}, \m athbf{Q̂}`` covariances and model augmentation. The process
130+ model is identical to the one in [`UnscentedKalmanFilter`](@ref) documentation. The matrix
131+ ``\m athbf{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."
396390function init_estimate_cov! (estim:: MovingHorizonEstimator , _ , _ , _ )
397- estim. invP̄. data[:] = Hermitian (inv (estim. P̂0), :L )
398- estim. P̂ . 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. W̃ .= 0
395+ estim. X̂ .= 0
396+ estim. Ym .= 0
397+ estim. U .= 0
398+ estim. D .= 0
399+ estim. Ŵ .= 0
400+ estim. Nk .= 0
407401 return nothing
408402end
409403
412406
413407Update [`MovingHorizonEstimator`](@ref) state `estim.x̂`.
414408
415- TBW
409+ Denoting the estimated process noises augmented with the state at arrival ``\m athbf{W̃} =
410+ [\b egin{smallmatrix} \m athbf{x̂}_k(k-N_k+1) \\ \m athbf{Ŵ} \e nd{smallmatrix}]``, the following
411+ optimization problem is solved at each discrete time ``k``:
412+ ```math
413+ \m in_{\m athbf{W̃}} \m athbf{x̄}' \m athbf{P̄}^{-1} \m athbf{x̄}
414+ + \m athbf{Ŵ}' \m athbf{Q̂}_{N_k}^{-1} \m athbf{Ŵ}
415+ + \m athbf{V̂}' \m athbf{R̂}_{N_k}^{-1} \m athbf{V̂}
416+ ```
417+ in which:
418+ ```math
419+ \b egin{aligned}
420+ N_k &= \m in(k+1, H_e) \\
421+ \m athbf{x̄} &= \m athbf{x̂}_k(k-N_k+1) - \m athbf{x̂}_{k-N_k}(k-N_k+1) \\
422+ \m athbf{P̄} &= \m athbf{P̂}_{k-N_k}(k-N_k+1) \\
423+ \m athbf{Q̂}_{N_k} &= \t ext{diag}\m athbf{(Q̂,Q̂,...,Q̂)} \\
424+ \m athbf{R̂}_{N_k} &= \t ext{diag}\m athbf{(R̂,R̂,...,R̂)} \\
425+ \e nd{aligned}
426+ ```
427+ and also:
428+ ```math
429+ \m athbf{Ŵ} =
430+ \b egin{bmatrix}
431+ \m athbf{ŵ}(k-N_k+1) \\ [6px]
432+ \m athbf{ŵ}(k-N_k+2) \\ [6px]
433+ \v dots \\ [6px]
434+ \m athbf{ŵ}(k)
435+ \e nd{bmatrix} , \q uad
436+ \m athbf{V̂} =
437+ \b egin{bmatrix}
438+ \m athbf{y^m} - \m athbf{ĥ^m}\B ig(\m athbf{x̂}_k(k-N_k+1), \m athbf{d}(k-N_k+1)\B ig) \\
439+ \m athbf{y^m} - \m athbf{ĥ^m}\B ig(\m athbf{x̂}_k(k-N_k+2), \m athbf{d}(k-N_k+2)\B ig) \\
440+ \v dots \\
441+ \m athbf{y^m} - \m athbf{ĥ^m}\B ig(\m athbf{x̂}_k(k), \m athbf{d}(k)\B ig)
442+ \e nd{bmatrix}
443+ ```
444+ The augmented model and the additive process noise recursively generates the states:
445+ ```math
446+ \m athbf{x̂}_k(k-j+1) = \m athbf{f̂}\B ig(\m athbf{x̂}_k(k-j), \m athbf{u}(k-j), \m athbf{d}(k-j)\B ig)
447+ + \m athbf{ŵ}(k-j)
448+ ```
449+ Once the problem solved, the next estimate ``\m athbf{x̂}_k(k+1)`` is computed by inserting
450+ the optimal values of ``\m athbf{x̂}_k(k-N_k+1)`` and ``\m athbf{Ŵ}`` 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 ``\m athbf{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"""
417455function update_estimate! (estim:: MovingHorizonEstimator{NT} , u, ym, d) where NT<: Real
418456 model, optim, x̂ = estim. model, estim. optim, estim. x̂
@@ -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- F̂ = ForwardDiff. jacobian (x̂ -> f̂ (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- P̄ = estim. P̂
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
490522end
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+ F̂ = ForwardDiff. jacobian (x̂ -> f̂ (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
0 commit comments