Skip to content

Commit d19acc4

Browse files
authored
Merge pull request #41 from JuliaControl/mhe_covestim
Added: MHE supports custom estimator for the arrival covariance
2 parents 7e64174 + 75aba0a commit d19acc4

File tree

5 files changed

+82
-93
lines changed

5 files changed

+82
-93
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.19.2"
4+
version = "0.20.0"
55

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

docs/src/manual/nonlinmpc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ savefig(ans, "plot1_NonLinMPC.svg"); nothing # hide
7979
An [`UnscentedKalmanFilter`](@ref) estimates the plant state :
8080

8181
```@example 1
82-
α=0.1; σQ=[0.1, 0.5]; σR=[0.5]; nint_u=[1]; σQint_u=[0.1]
82+
α=0.01; σQ=[0.1, 0.5]; σR=[0.5]; nint_u=[1]; σQint_u=[0.1]
8383
estim = UnscentedKalmanFilter(model; α, σQ, σR, nint_u, σQint_u)
8484
```
8585

src/estimator/kalman.jl

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ This syntax allows nonzero off-diagonal elements in ``\mathbf{Q̂, R̂}``.
163163
"""
164164
function SteadyKalmanFilter(model::SM, i_ym, nint_u, nint_ym, Q̂, R̂) where {NT<:Real, SM<:LinModel{NT}}
165165
Q̂, R̂ = to_mat(Q̂), to_mat(R̂)
166-
return SteadyKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, Q̂ , R̂)
166+
return SteadyKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, Q̂, R̂)
167167
end
168168

169169

@@ -315,7 +315,7 @@ This syntax allows nonzero off-diagonal elements in ``\mathbf{P̂}_{-1}(0), \mat
315315
"""
316316
function KalmanFilter(model::SM, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂) where {NT<:Real, SM<:LinModel{NT}}
317317
P̂0, Q̂, R̂ = to_mat(P̂0), to_mat(Q̂), to_mat(R̂)
318-
return KalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, P̂0, Q̂ , R̂)
318+
return KalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
319319
end
320320

321321
@doc raw"""
@@ -428,7 +428,7 @@ represents the measured outputs of ``\mathbf{ĥ}`` function (and unmeasured one
428428
429429
# Arguments
430430
- `model::SimModel` : (deterministic) model for the estimations.
431-
- `α=1e-3` : alpha parameter, spread of the state distribution ``(0 α ≤ 1)``.
431+
- `α=1e-3` : alpha parameter, spread of the state distribution ``(0 < α ≤ 1)``.
432432
- `β=2` : beta parameter, skewness and kurtosis of the states distribution ``(β ≥ 0)``.
433433
- `κ=0` : kappa parameter, another spread parameter ``(0 ≤ κ ≤ 3)``.
434434
- `<keyword arguments>` of [`SteadyKalmanFilter`](@ref) constructor.
@@ -478,14 +478,14 @@ function UnscentedKalmanFilter(
478478
end
479479

480480
@doc raw"""
481-
UnscentedKalmanFilter(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, α, β, κ)
481+
UnscentedKalmanFilter(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, α=1e-3, β=2, κ=0)
482482
483483
Construct the estimator from the augmented covariance matrices `P̂0`, `Q̂` and `R̂`.
484484
485485
This syntax allows nonzero off-diagonal elements in ``\mathbf{P̂}_{-1}(0), \mathbf{Q̂, R̂}``.
486486
"""
487487
function UnscentedKalmanFilter(
488-
model::SM, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, α, β, κ
488+
model::SM, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, α=1e-3, β=2, κ=0
489489
) where {NT<:Real, SM<:SimModel{NT}}
490490
P̂0, Q̂, R̂ = to_mat(P̂0), to_mat(Q̂), to_mat(R̂)
491491
return UnscentedKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, P̂0, Q̂ , R̂, α, β, κ)
@@ -565,22 +565,7 @@ noise, respectively.
565565
ISBN9780470045343.
566566
"""
567567
function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:Real
568-
return update_estimate_ukf!(estim, u, ym, d, estim.P̂, estim.x̂)
569-
end
570-
571-
"""
572-
update_estimate_ukf!(estim::StateEstimator, u, ym, d, P̂, x̂=nothing)
573-
574-
Update Unscented Kalman Filter estimates and covariance matrices.
575-
576-
Allows code reuse for [`UnscentedKalmanFilter`](@ref) and [`MovingHorizonEstimator`](@ref).
577-
See [`update_estimate!(::UnscentedKalmanFilter, ::Any, ::Any, ::Any)`(@ref) docstring
578-
for the equations. If `isnothing(x̂)`, only the covariance `P̂` is updated.
579-
"""
580-
function update_estimate_ukf!(
581-
estim::StateEstimator{NT}, u, ym, d, P̂, x̂=nothing
582-
) where NT<:Real
583-
Q̂, R̂, K̂ = estim.Q̂, estim.R̂, estim.
568+
x̂, P̂, Q̂, R̂, K̂ = estim.x̂, estim.P̂, estim.Q̂, estim.R̂, estim.
584569
nym, nx̂, nσ = estim.nym, estim.nx̂, estim.
585570
γ, m̂, Ŝ = estim.γ, estim.m̂, estim.
586571
# --- initialize matrices ---
@@ -742,7 +727,7 @@ This syntax allows nonzero off-diagonal elements in ``\mathbf{P̂}_{-1}(0), \mat
742727
"""
743728
function ExtendedKalmanFilter(model::SM, i_ym, nint_u, nint_ym,P̂0, Q̂, R̂) where {NT<:Real, SM<:SimModel{NT}}
744729
P̂0, Q̂, R̂ = to_mat(P̂0), to_mat(Q̂), to_mat(R̂)
745-
return ExtendedKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, P̂0, Q̂ , R̂)
730+
return ExtendedKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
746731
end
747732

748733

@@ -759,7 +744,8 @@ substitutions ``\mathbf{Â = F̂}(k)`` and ``\mathbf{Ĉ^m = Ĥ^m}(k)``:
759744
[\mathbf{Ĥ^m}(k)\mathbf{P̂}_{k-1}(k)\mathbf{Ĥ^m}'(k) + \mathbf{R̂}]^{-1} \\
760745
\mathbf{K̂}(k) &= \mathbf{F̂}(k) \mathbf{M̂}(k) \\
761746
\mathbf{ŷ^m}(k) &= \mathbf{ĥ^m}\Big( \mathbf{x̂}_{k-1}(k), \mathbf{d}(k) \Big) \\
762-
\mathbf{x̂}_{k}(k+1) &= \mathbf{f̂}\Big( \mathbf{x̂}_{k-1}(k), \mathbf{u}(k), \mathbf{d}(k) \Big)
747+
\mathbf{x̂}_{k}(k+1) &= \math, covestim::CE,
748+
) bf{f̂}\Big( \mathbf{x̂}_{k-1}(k), \mathbf{u}(k), \mathbf{d}(k) \Big)
763749
+ \mathbf{K̂}(k)[\mathbf{y^m}(k) - \mathbf{ŷ^m}(k)] \\
764750
\mathbf{P̂}_{k}(k+1) &= \mathbf{F̂}(k)[\mathbf{P̂}_{k-1}(k)
765751
- \mathbf{M̂}(k)\mathbf{Ĥ^m}(k)\mathbf{P̂}_{k-1}(k)]\mathbf{F̂}'(k)
@@ -770,7 +756,8 @@ substitutions ``\mathbf{Â = F̂}(k)`` and ``\mathbf{Ĉ^m = Ĥ^m}(k)``:
770756
automatically computes the Jacobians:
771757
```math
772758
\begin{aligned}
773-
\mathbf{F̂}(k) &= \left. \frac{∂\mathbf{f̂}(\mathbf{x̂}, \mathbf{u}, \mathbf{d})}{∂\mathbf{x̂}} \right|_{\mathbf{x̂ = x̂}_{k-1}(k),\, \mathbf{u = u}(k),\, \mathbf{d = d}(k)} \\
759+
\mathbf{F̂}(k) &= \left. \fra, covestim::CE,
760+
) c{∂\mathbf{f̂}(\mathbf{x̂}, \mathbf{u}, \mathbf{d})}{∂\mathbf{x̂}} \right|_{\mathbf{x̂ = x̂}_{k-1}(k),\, \mathbf{u = u}(k),\, \mathbf{d = d}(k)} \\
774761
\mathbf{Ĥ}(k) &= \left. \frac{∂\mathbf{ĥ}(\mathbf{x̂}, \mathbf{d})}{∂\mathbf{x̂}} \right|_{\mathbf{x̂ = x̂}_{k-1}(k),\, \mathbf{d = d}(k)}
775762
\end{aligned}
776763
```
@@ -813,34 +800,29 @@ function validate_kfcov(nym, nx̂, Q̂, R̂, P̂0=nothing)
813800
end
814801

815802
"""
816-
update_estimate_kf!(estim::StateEstimator, u, ym, d, Â, Ĉm, P̂, x̂=nothing)
803+
update_estimate_kf!(estim::StateEstimator, u, ym, d, Â, Ĉm, P̂, x̂)
817804
818805
Update time-varying/extended Kalman Filter estimates with augmented `Â` and `Ĉm` matrices.
819806
820807
Allows code reuse for [`KalmanFilter`](@ref), [`ExtendedKalmanFilterKalmanFilter`](@ref).
821808
They update the state `x̂` and covariance `P̂` with the same equations. The extended filter
822809
substitutes the augmented model matrices with its Jacobians (`Â = F̂` and `Ĉm = Ĥm`).
823810
The implementation uses in-place operations and explicit factorization to reduce
824-
allocations. See e.g. [`KalmanFilter`](@ref) docstring for the equations. If `isnothing(x̂)`,
825-
only the covariance `P̂` is updated.
811+
allocations. See e.g. [`KalmanFilter`](@ref) docstring for the equations.
826812
"""
827-
function update_estimate_kf!(
828-
estim::StateEstimator{NT}, u, ym, d, Â, Ĉm, P̂, x̂=nothing
829-
) where NT<:Real
830-
Q̂, R̂, M̂ = estim.Q̂, estim.R̂, estim.
813+
function update_estimate_kf!(estim::StateEstimator{NT}, u, ym, d, Â, Ĉm, P̂, x̂) where NT<:Real
814+
Q̂, R̂, M̂, K̂ = estim.Q̂, estim.R̂, estim.M̂, estim.
831815
mul!(M̂, P̂, Ĉm')
832816
rdiv!(M̂, cholesky!(Hermitian(Ĉm ** Ĉm' .+ R̂)))
833-
if !isnothing(x̂)
834-
mul!(estim.K̂, Â, M̂)
835-
x̂next, ŷ = Vector{NT}(undef, estim.nx̂), Vector{NT}(undef, estim.model.ny)
836-
ĥ!(ŷ, estim, estim.model, x̂, d)
837-
ŷm = @views ŷ[estim.i_ym]
838-
= ŷm
839-
v̂ .= ym .- ŷm
840-
f̂!(x̂next, estim, estim.model, x̂, u, d)
841-
mul!(x̂next, estim.K̂, v̂, 1, 1)
842-
estim.x̂ .= x̂next
843-
end
817+
mul!(K̂, Â, M̂)
818+
x̂next, ŷ = Vector{NT}(undef, estim.nx̂), Vector{NT}(undef, estim.model.ny)
819+
ĥ!(ŷ, estim, estim.model, x̂, d)
820+
ŷm = @views ŷ[estim.i_ym]
821+
= ŷm
822+
v̂ .= ym .- ŷm
823+
f̂!(x̂next, estim, estim.model, x̂, u, d)
824+
mul!(x̂next, K̂, v̂, 1, 1)
825+
estim.x̂ .= x̂next
844826
.data .=* (P̂ .-* Ĉm * P̂) *' .+# .data is necessary for Hermitians
845827
return nothing
846828
end

src/estimator/mhe/construct.jl

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@ end
4343

4444
struct MovingHorizonEstimator{
4545
NT<:Real,
46-
SM<:SimModel,
47-
JM<:JuMP.GenericModel
46+
SM<:SimModel,
47+
JM<:JuMP.GenericModel,
48+
CE<:StateEstimator,
4849
} <: StateEstimator{NT}
4950
model::SM
5051
# note: `NT` and the number type `JNT` in `JuMP.GenericModel{JNT}` can be
5152
# different since solvers that support non-Float64 are scarce.
5253
optim::JM
5354
con::EstimatorConstraint{NT}
55+
covestim::CE
5456
::Vector{NT}
5557
lastu0::Vector{NT}
5658
::Vector{NT}
@@ -95,17 +97,16 @@ struct MovingHorizonEstimator{
9597
x̂arr_old::Vector{NT}
9698
P̂arr_old::Hermitian{NT, Matrix{NT}}
9799
Nk::Vector{Int}
98-
function MovingHorizonEstimator{NT, SM, JM}(
99-
model::SM, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim::JM
100-
) where {NT<:Real, SM<:SimModel{NT}, JM<:JuMP.GenericModel}
100+
function MovingHorizonEstimator{NT, SM, JM, CE}(
101+
model::SM, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim::JM, covestim::CE
102+
) where {NT<:Real, SM<:SimModel{NT}, JM<:JuMP.GenericModel, CE<:StateEstimator{NT}}
101103
nu, nd = model.nu, model.nd
102104
He < 1 && throw(ArgumentError("Estimation horizon He should be ≥ 1"))
103105
Cwt < 0 && throw(ArgumentError("Cwt weight should be ≥ 0"))
104106
nym, nyu = validate_ym(model, i_ym)
105107
As, Cs_u, Cs_y, nint_u, nint_ym = init_estimstoch(model, i_ym, nint_u, nint_ym)
106108
nxs = size(As, 1)
107109
nx̂ = model.nx + nxs
108-
nŵ = nx̂
109110
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs_u, Cs_y)
110111
validate_kfcov(nym, nx̂, Q̂, R̂, P̂0)
111112
lastu0 = zeros(NT, model.nu)
@@ -129,8 +130,8 @@ struct MovingHorizonEstimator{
129130
x̂arr_old = zeros(NT, nx̂)
130131
P̂arr_old = copy(P̂0)
131132
Nk = [0]
132-
estim = new{NT, SM, JM}(
133-
model, optim, con,
133+
estim = new{NT, SM, JM, CE}(
134+
model, optim, con, covestim,
134135
Z̃, lastu0, x̂,
135136
He,
136137
i_ym, nx̂, nym, nyu, nxs,
@@ -186,9 +187,9 @@ N_k = \begin{cases}
186187
```
187188
The vectors ``\mathbf{Ŵ}`` and ``\mathbf{V̂}`` encompass the estimated process noise
188189
``\mathbf{ŵ}(k-j)`` and sensor noise ``\mathbf{v̂}(k-j)`` from ``j=N_k-1`` to ``0``. The
189-
Extended Help defines the two vectors and the scalar ``ϵ``. See [`UnscentedKalmanFilter`](@ref)
190-
for details on the augmented process model and ``\mathbf{R̂}, \mathbf{Q̂}`` covariances. The
191-
matrix ``\mathbf{P̂}_{k-N_k}(k-N_k+1)`` is estimated with an [`ExtendedKalmanFilter`](@ref).
190+
Extended Help defines the two vectors, the slack variable ``ϵ``, and the estimation of the
191+
covariance at arrival ``\mathbf{P̂}_{k-N_k}(k-N_k+1)``. See [`UnscentedKalmanFilter`](@ref)
192+
for details on the augmented process model and ``\mathbf{R̂}, \mathbf{Q̂}`` covariances.
192193
193194
!!! warning
194195
See the Extended Help if you get an error like:
@@ -247,22 +248,27 @@ MovingHorizonEstimator estimator with a sample time Ts = 10.0 s, Ipopt optimizer
247248
The slack variable ``ϵ`` relaxes the constraints if enabled, see [`setconstraint!`](@ref).
248249
It is disabled by default for the MHE (from `Cwt=Inf`) but it should be activated for
249250
problems with two or more types of bounds, to ensure feasibility (e.g. on the estimated
250-
state and sensor noise).
251-
252-
For [`LinModel`](@ref), the optimization is treated as a quadratic program with a
253-
time-varying Hessian, which is generally cheaper than nonlinear programming.
251+
state and sensor noise).
254252
255-
For [`NonLinModel`](@ref), the optimization relies on automatic differentiation (AD).
256-
Optimizers generally benefit from exact derivatives like AD. However, the `f` and `h`
257-
functions must be compatible with this feature. See [Automatic differentiation](https://jump.dev/JuMP.jl/stable/manual/nlp/#Automatic-differentiation)
258-
for common mistakes when writing these functions.
253+
The optimization and the estimation of the covariance at arrival
254+
``\mathbf{P̂}_{k-N_k}(k-N_k+1)`` depend on `model`:
255+
256+
- If `model` is a [`LinModel`](@ref), the optimization is treated as a quadratic program
257+
with a time-varying Hessian, which is generally cheaper than nonlinear programming. By
258+
default, a [`KalmanFilter`](@ref) estimates the arrival covariance (customizable).
259+
- Else, a nonlinear program with automatic differentiation (AD) solves the optimization.
260+
Optimizers generally benefit from exact derivatives like AD. However, the `f` and `h`
261+
functions must be compatible with this feature. See [Automatic differentiation](https://jump.dev/JuMP.jl/stable/manual/nlp/#Automatic-differentiation)
262+
for common mistakes when writing these functions. An [`UnscentedKalmanFilter`](@ref)
263+
estimates the arrival covariance by default.
259264
260265
Note that if `Cwt≠Inf`, the attribute `nlp_scaling_max_gradient` of `Ipopt` is set to
261-
`10/Cwt` (if not already set), to scale the small values of ``ϵ``.
266+
`10/Cwt` (if not already set), to scale the small values of ``ϵ``. Use the second
267+
constructor to specify the covariance estimation method.
262268
"""
263269
function MovingHorizonEstimator(
264270
model::SM;
265-
He::Union{Int, Nothing}=nothing,
271+
He::Union{Int, Nothing} = nothing,
266272
i_ym::IntRangeOrVector = 1:model.ny,
267273
σP0::Vector = fill(1/model.nx, model.nx),
268274
σQ ::Vector = fill(1/model.nx, model.nx),
@@ -280,33 +286,40 @@ function MovingHorizonEstimator(
280286
P̂0 = Hermitian(diagm(NT[σP0; σP0int_u; σP0int_ym].^2), :L)
281287
= Hermitian(diagm(NT[σQ; σQint_u; σQint_ym ].^2), :L)
282288
= Hermitian(diagm(NT[σR;].^2), :L)
283-
isnothing(He) && throw(ArgumentError("Estimation horizon He must be explicitly specified"))
284-
return MovingHorizonEstimator{NT, SM, JM}(
285-
model, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim
286-
)
289+
isnothing(He) && throw(ArgumentError("Estimation horizon He must be explicitly specified"))
290+
return MovingHorizonEstimator(model, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim)
287291
end
288292

289-
"Return a `JuMP.Model` with OSQP optimizer if `model` is a [`LinModel`](@ref)."
290293
default_optim_mhe(::LinModel) = JuMP.Model(DEFAULT_LINMHE_OPTIMIZER, add_bridges=false)
291-
"Else, return it with Ipopt optimizer."
292294
default_optim_mhe(::SimModel) = JuMP.Model(DEFAULT_NONLINMHE_OPTIMIZER, add_bridges=false)
293295

294296
@doc raw"""
295-
MovingHorizonEstimator(model, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim)
297+
MovingHorizonEstimator(model, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim[, covestim])
296298
297299
Construct the estimator from the augmented covariance matrices `P̂0`, `Q̂` and `R̂`.
298300
299301
This syntax allows nonzero off-diagonal elements in ``\mathbf{P̂}_{-1}(0), \mathbf{Q̂, R̂}``.
302+
The final argument `covestim` also allows specifying a custom [`StateEstimator`](@ref)
303+
object for the estimation of covariance at the arrival ``\mathbf{P̂}_{k-N_k}(k-N_k+1)``. The
304+
supported types are [`KalmanFilter`](@ref), [`UnscentedKalmanFilter`](@ref) and
305+
[`ExtendedKalmanFilter`](@ref).
300306
"""
301307
function MovingHorizonEstimator(
302-
model::SM, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim::JM
303-
) where {NT<:Real, SM<:SimModel{NT}, JM<:JuMP.GenericModel}
308+
model::SM, He, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂, Cwt, optim::JM,
309+
covestim::CE = default_covestim_mhe(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
310+
) where {NT<:Real, SM<:SimModel{NT}, JM<:JuMP.GenericModel, CE<:StateEstimator{NT}}
304311
P̂0, Q̂, R̂ = to_mat(P̂0), to_mat(Q̂), to_mat(R̂)
305-
return MovingHorizonEstimator{NT, SM, JM}(
306-
model, He, i_ym, nint_u, nint_ym, P̂0, Q̂ , R̂, Cwt, optim
312+
return MovingHorizonEstimator{NT, SM, JM, CE}(
313+
model, He, i_ym, nint_u, nint_ym, P̂0, Q̂ , R̂, Cwt, optim, covestim
307314
)
308315
end
309316

317+
function default_covestim_mhe(model::LinModel, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
318+
return KalmanFilter(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
319+
end
320+
function default_covestim_mhe(model::SimModel, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
321+
return UnscentedKalmanFilter(model, i_ym, nint_u, nint_ym, P̂0, Q̂, R̂)
322+
end
310323

311324
@doc raw"""
312325
setconstraint!(estim::MovingHorizonEstimator; <keyword arguments>) -> estim

src/estimator/mhe/execute.jl

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,7 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
7878
estim.Ŵ[1:nŵ*Nk] .= @views estim.Z̃[nx̃+1:nx̃+nŵ*Nk] # update Ŵ with optimum for warm-start
7979
V̂, X̂ = predict!(V̂, X̂, ŷ, estim, model, estim.Z̃)
8080
x̂ .= X̂[end-nx̂+1:end]
81-
if Nk == estim.He
82-
uarr, ymarr, darr = estim.U[1:model.nu], estim.Ym[1:nym], estim.D[1:model.nd]
83-
update_cov!(estim, model, estim.x̂arr_old, estim.P̂arr_old, uarr, ymarr, darr)
84-
estim.invP̄.data .= Hermitian(inv(estim.P̂arr_old), :L)
85-
end
81+
Nk == estim.He && update_cov!(estim::MovingHorizonEstimator)
8682
return nothing
8783
end
8884

@@ -323,20 +319,18 @@ function trunc_bounds(estim::MovingHorizonEstimator{NT}, Bmin, Bmax, n) where NT
323319
return Bmin_t, Bmax_t
324320
end
325321

326-
"Update the covariance `P̂` with the `KalmanFilter` if `model` is a `LinModel`."
327-
function update_cov!(estim::MovingHorizonEstimator, ::LinModel, _ , P̂, u, ym, d)
328-
return update_estimate_kf!(estim, u, ym, d, estim.Â, estim.Ĉ[estim.i_ym, :], P̂)
329-
end
330-
"Update it with the `ExtendedKalmanFilter` if model is not a `LinModel`."
331-
function update_cov!(estim::MovingHorizonEstimator, model::SimModel, x̂, P̂, u, ym, d)
332-
# TODO: also support UnscentedKalmanFilter
333-
x̂next, ŷ = similar(estim.x̂), similar(model.yop)
334-
= ForwardDiff.jacobian((x̂next, x̂) -> f̂!(x̂next, estim, estim.model, x̂, u, d), x̂next, x̂)
335-
= ForwardDiff.jacobian((ŷ, x̂) -> ĥ!(ŷ, estim, estim.model, x̂, d), ŷ, x̂)
336-
return update_estimate_kf!(estim, u, ym, d, F̂, Ĥ[estim.i_ym, :], P̂)
322+
"Update the covariance estimate at arrival using `covestim` [`StateEstimator`](@ref)."
323+
function update_cov!(estim::MovingHorizonEstimator)
324+
nu, nd, nym = estim.model.nu, estim.model.nd, estim.nym
325+
uarr, ymarr, darr = @views estim.U[1:nu], estim.Ym[1:nym], estim.D[1:nd]
326+
estim.covestim.x̂ .= estim.x̂arr_old
327+
estim.covestim..data .= estim.P̂arr_old # .data is necessary for Hermitian
328+
update_estimate!(estim.covestim, uarr, ymarr, darr)
329+
estim.P̂arr_old.data .= estim.covestim.
330+
estim.invP̄.data .= Hermitian(inv(estim.P̂arr_old), :L)
331+
return nothing
337332
end
338333

339-
340334
"""
341335
obj_nonlinprog!( _ , estim::MovingHorizonEstimator, ::LinModel, _ , Z̃)
342336

0 commit comments

Comments
 (0)