11struct NonLinMHE{M<: SimModel } <: StateEstimator
22 model:: M
33 optim:: JuMP.Model
4+ W̃:: Vector{Float64}
45 lastu0:: Vector{Float64}
56 x̂:: Vector{Float64}
67 P̂:: Hermitian{Float64, Matrix{Float64}}
@@ -12,36 +13,51 @@ struct NonLinMHE{M<:SimModel} <: StateEstimator
1213 As:: Matrix{Float64}
1314 Cs:: Matrix{Float64}
1415 nint_ym:: Vector{Int}
16+ He:: Int
1517 P̂0:: Hermitian{Float64, Matrix{Float64}}
16- Q̂:: Hermitian{Float64, Matrix{Float64}}
17- R̂:: Hermitian{Float64, Matrix{Float64}}
18- He:: Int
19- X̂max:: Vector{Float64}
18+ Q̂_He:: Hermitian{Float64, Matrix{Float64}}
19+ R̂_He:: Hermitian{Float64, Matrix{Float64}}
2020 X̂min:: Vector{Float64}
21- function NonLinMHE {M} (model:: M , i_ym, nint_ym, P̂0, Q̂, R̂, He) where {M<: SimModel }
22- nu, nx, ny = model. nu, model. nx, model. ny
21+ X̂max:: Vector{Float64}
22+ X̂ :: Vector{Float64}
23+ Ym:: Vector{Float64}
24+ U :: Vector{Float64}
25+ D :: Vector{Float64}
26+ Ŵ :: Vector{Float64}
27+ x̂0_past:: Vector{Float64}
28+ function NonLinMHE {M} (model:: M , i_ym, nint_ym, He, P̂0, Q̂, R̂, optim) where {M<: SimModel }
29+ nu, nd, nx, ny = model. nu, model. nd, model. nx, model. ny
30+ He < 1 && error (" Estimation horizon He should be ≥ 1" )
2331 nym, nyu = length (i_ym), ny - length (i_ym)
2432 Asm, Csm, nint_ym = init_estimstoch (i_ym, nint_ym)
2533 nxs = size (Asm,1 )
2634 nx̂ = nx + nxs
2735 validate_kfcov (nym, nx̂, Q̂, R̂, P̂0)
2836 As, _ , Cs, _ = stoch_ym2y (model, i_ym, Asm, [], Csm, [])
29- nσ, γ, m̂, Ŝ = init_mhe (nx̂, He)
3037 i_ym = collect (i_ym)
3138 lastu0 = zeros (nu)
3239 x̂ = [zeros (model. nx); zeros (nxs)]
3340 P̂0 = Hermitian (P̂0, :L )
34- Q̂ = Hermitian (Q̂ , :L )
35- R̂ = Hermitian (R̂ , :L )
41+ Q̂_He = Hermitian (repeatdiag (Q̂, He) , :L )
42+ R̂_He = Hermitian (repeatdiag (R̂, He) , :L )
3643 P̂ = copy (P̂0)
37- return new (
38- model,
44+ X̂min, X̂max = fill (- Inf , nx̂* He), fill (+ Inf , nx̂* He)
45+ nvar = nx̂* (He + 1 )
46+ W̃ = zeros (nvar)
47+ X̂, Ym, U, D, Ŵ = zeros (nx̂* He), zeros (nym* He), zeros (nu* He), zeros (nd* He), zeros (nx̂* He)
48+ x̂0_past = zeros (nx̂)
49+ estim = new (
50+ model, optim, W̃,
3951 lastu0, x̂, P̂,
4052 i_ym, nx̂, nym, nyu, nxs,
4153 As, Cs, nint_ym,
42- P̂0, Q̂, R̂,
43- nσ, γ, m̂, Ŝ
54+ He, P̂0, Q̂_He, R̂_He,
55+ X̂min, X̂max,
56+ X̂, Ym, U, D, Ŵ,
57+ x̂0_past
4458 )
59+ init_optimization! (estim)
60+ return estim
4561 end
4662end
4763
@@ -68,19 +84,20 @@ julia> model = NonLinModel((x,u,_)->0.1x+u, (x,_)->2x, 10.0, 1, 1, 1);
6884function NonLinMHE (
6985 model:: M ;
7086 i_ym:: IntRangeOrVector = 1 : model. ny,
87+ He:: Int = 10 ,
7188 σP0:: Vector = fill (1 / model. nx, model. nx),
7289 σQ:: Vector = fill (1 / model. nx, model. nx),
7390 σR:: Vector = fill (1 , length (i_ym)),
7491 nint_ym:: IntVectorOrInt = fill (1 , length (i_ym)),
7592 σP0_int:: Vector = fill (1 , max (sum (nint_ym), 0 )),
7693 σQ_int:: Vector = fill (1 , max (sum (nint_ym), 0 )),
77- He :: Int = 10
94+ optim :: JuMP.Model = JuMP . Model ( optimizer_with_attributes (Ipopt . Optimizer, " sb " => " yes " ))
7895) where {M<: SimModel }
7996 # estimated covariances matrices (variance = σ²) :
8097 P̂0 = Diagonal {Float64} ([σP0 ; σP0_int ]. ^ 2 );
8198 Q̂ = Diagonal {Float64} ([σQ ; σQ_int ]. ^ 2 );
8299 R̂ = Diagonal {Float64} (σR.^ 2 );
83- return NonLinMHE {M} (model, i_ym, nint_ym, P̂0, Q̂ , R̂, He )
100+ return NonLinMHE {M} (model, i_ym, nint_ym, He, P̂0, Q̂, R̂, optim )
84101end
85102
86103@doc raw """
@@ -90,4 +107,143 @@ Construct the estimator from the augmented covariance matrices `P̂0`, `Q̂` and
90107
91108This syntax allows nonzero off-diagonal elements in ``\m athbf{P̂}_{-1}(0), \m athbf{Q̂, R̂}``.
92109"""
93- NonLinMHE {M} (model:: M , i_ym, nint_ym, P̂0, Q̂, R̂, He) where {M<: SimModel }
110+ NonLinMHE {M} (model:: M , i_ym, nint_ym, P̂0, Q̂, R̂, He) where {M<: SimModel }
111+
112+ @doc raw """
113+ init_stochpred(As, Cs, He)
114+
115+ Construct stochastic model prediction matrices for nonlinear observers.
116+
117+ It allows separate simulations of the stochastic model (integrators). Stochastic model
118+ states and outputs are simulated using :
119+ ```math
120+ \b egin{aligned}
121+ \m athbf{X̂_s} &= \m athbf{M_s x̂_s}(k) + \m athbf{N_s Ŵ_s}
122+ \m athbf{Ŷ_s} &= \m athbf{P_s X̂_s}
123+ \e nd{aligned}
124+ ```
125+ where ``\m athbf{X̂_s}`` and ``\m athbf{Ŷ_s}`` are integrator states and outputs from ``k + 1``
126+ to ``k + H_e`` inclusively, and ``\m athbf{Ŵ_s}`` are integrator process noises from ``k`` to
127+ ``k + H_e - 1``. Stochastic simulations are combined with ``\m athbf{f, h}`` results to
128+ simulate the augmented model:
129+ ```math
130+ \b egin{aligned}
131+ \m athbf{X̂} &= \m athbf{M_s x̂_s}(k) + \m athbf{N_s Ŵ_s}
132+ \m athbf{Ŷ_s} &= \m athbf{P_s X̂_s}
133+ \e nd{aligned}
134+ ```
135+ Xhat = [XhatD; XhatS]
136+ Yhat = YhatD + YhatS
137+ where XhatD and YhatD are SimulFunc states and outputs from ``k + 1`` to ``k + H_e``.
138+ """
139+ function init_stochpred (As, Cs, He)
140+ nxs = size (As,1 );
141+ Ms = zeros (He* nxs, nxs);
142+ for i = 1 : Ho
143+ iRow = (1 : nxs) + nxs* (i- 1 );
144+ Ms[iRow, :] = As^ i;
145+ end
146+ Ns = zeros (Ho* nxs, He* nxs);
147+ for i = 1 : Ho
148+ iCol = (1 : nxs) + nxs* (i- 1 );
149+ Ns[nxs* (i- 1 )+ 1 : end , iCol] = [eye (nxs); Ms (1 : nxs* (Ho- i),:)];
150+ end
151+ Ps = kron (eye (Ho),Cs);
152+ return (Ms, Ns, Ps)
153+ end
154+
155+
156+
157+ """
158+ init_optimization!(estim::NonLinMHE)
159+
160+ Init the nonlinear optimization for [`NonLinMHE`](@ref).
161+ """
162+ function init_optimization! (estim:: NonLinMHE )
163+ # --- variables and linear constraints ---
164+ optim = estim. optim
165+ nvar = length (estim. W̃)
166+ set_silent (optim)
167+ @variable (optim, W̃var[1 : nvar])
168+ W̃var = optim[:W̃var ]
169+ # --- nonlinear optimization init ---
170+ model = estim. model
171+ nym, nx̂, He = estim. nym, estim. nx̂, estim. He
172+ # inspired from https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-functions-with-vector-outputs
173+ Jfunc = let estim= estim, model= model, nvar= nvar , nŶm= He* nym, nX̂= He* nx̂
174+ last_W̃tup_float, last_W̃tup_dual = nothing , nothing
175+ Ŷm_cache:: DiffCacheType = DiffCache (zeros (nŶm), nvar + 3 )
176+ X̂_cache :: DiffCacheType = DiffCache (zeros (nX̂) , nvar + 3 )
177+ function Jfunc (W̃tup:: Float64... )
178+ Ŷm, X̂ = get_tmp (Ŷm_cache, W̃tup[1 ]), get_tmp (X̂_cache, W̃tup[1 ])
179+ W̃ = collect (W̃tup)
180+ if W̃tup != last_W̃tup_float
181+ Ŷm[:], X̂[:] = predict (estim, model, W̃)
182+ last_W̃tup_float = W̃tup
183+ end
184+ return obj_nonlinprog (estim, model, Ŷm, W̃)
185+ end
186+ function Jfunc (W̃tup:: Real... )
187+ Ŷm, X̂ = get_tmp (Ŷm_cache, W̃tup[1 ]), get_tmp (X̂_cache, W̃tup[1 ])
188+ W̃ = collect (W̃tup)
189+ if W̃tup != last_W̃tup_dual
190+ Ŷm[:], X̂[:] = predict (estim, model, W̃)
191+ last_W̃tup_dual = W̃tup
192+ end
193+ return obj_nonlinprog (estim, model, Ŷm, W̃)
194+ end
195+ Jfunc
196+ end
197+ register (optim, :Jfunc , nvar, Jfunc, autodiff= true )
198+ @NLobjective (optim, Min, Jfunc (W̃var... ))
199+ return nothing
200+ end
201+
202+ """
203+ obj_nonlinprog(estim::NonLinMHE, model::SimModel, ΔŨ::Vector{Real})
204+
205+ Objective function for [`NonLinMHE`] when `model` is not a [`LinModel`](@ref).
206+ """
207+ function obj_nonlinprog (estim:: NonLinMHE , :: SimModel , Ŷm, W̃:: Vector{T} ) where {T<: Real }
208+ # `@views` macro avoid copies with matrix slice operator e.g. [a:b]
209+ @views x̄0 = W̃[1 : estim. nx̂] - estim. x̂0_past
210+ V̂ = estim. win_Ym - Ŷm
211+ @views Ŵ = W̃[estim. nx̂+ 1 : end ]
212+ return x̄0' / estim. P̂0* x̄0 + Ŵ' / Q̂_He* Ŵ + V̂' / R̂_He* V̂
213+ end
214+
215+ function predict (estim:: NonLinMHE , model:: SimModel , W̃:: Vector{T} ) where {T<: Real }
216+ nu, nd, nym, nx̂, He = model. nu, model. nd, estim. nym, estim. nx̂, estim. He
217+ Ŷm:: Vector{T} = Vector {T} (undef, nym* (He+ 1 ))
218+ X̂ :: Vector{T} = Vector {T} (undef, nx̂* (He+ 1 ))
219+ Ŵ :: Vector{T} = @views W̃[nx̂+ 1 : end ]
220+ u :: Vector{T} = Vector {T} (undef, nu)
221+ d :: Vector{T} = Vector {T} (undef, nu)
222+ ŵ :: Vector{T} = Vector {T} (undef, nx̂)
223+ x̂ :: Vector{T} = W̃[1 : nx̂]
224+ for j= 1 : He
225+ u[:] = @views estim. U[(1 + nu* (j- 1 )): (nu* j)]
226+ d[:] = @views estim. D[(1 + nd* (j- 1 )): (nd* j)]
227+ ŵ[:] = @views Ŵ[(1 + nx̂* (j- 1 )): (nx̂* j)]
228+ X̂[(1 + nx̂* (j- 1 )): (nx̂* j)] = x̂
229+ Ŷm[(1 + ny* (j- 1 )): (ny* j)] = @views ĥ (estim, x̂, d)[estim. i_ym]
230+ x̂[:] = f̂ (estim, x̂, u, d) + ŵ
231+ end
232+ X̂[end - nx̂+ 1 : end ] = x̂
233+ Ŷm[end - nx̂+ 1 : end ] = @views ĥ (estim, x̂, estin. D[end - nd+ 1 : end ])[estim. i_ym]
234+ return Ŷm, X̂
235+ end
236+
237+ function update_estimate! (estim:: NonLinMHE , u, ym, d)
238+ model, x̂ = estim. model, estim. x̂
239+ nx̂, nym, nu, nd, nŵ = estim. nx̂, estim. nym, model. nu, model. nd, estim. nx̂
240+ ŵ = zeros (nŵ) # ŵ(k) = 0 for warm-starting
241+ # --- adding new data in time windows ---
242+ estim. X̂[:] = @views [estim. X̂[nx̂+ 1 : end ] ; x̂]
243+ estim. Ym[:] = @views [estim. Ym[nym+ 1 : end ]; ym]
244+ estim. U[:] = @views [estim. U[nu+ 1 : end ] ; u]
245+ estim. D[:] = @views [estim. D[nd+ 1 : end ] ; d]
246+ estim. Ŵ[:] = @views [estim. Ŵ[nŵ+ 1 : end ] ; ŵ]
247+
248+ # estim.x̂0_past[:] =
249+ end
0 commit comments