@@ -278,25 +278,25 @@ transcription for now.
278278- `model::SimModel` : (deterministic) model for the estimations.
279279- `He=nothing` : estimation horizon ``H_e``, must be specified.
280280- `i_ym=1:model.ny` : `model` output indices that are measured ``\m athbf{y^m}``, the rest
281- are unmeasured ``\m athbf{y^u}``.
281+ are unmeasured ``\m athbf{y^u}``.
282282- `σP_0=fill(1/model.nx,model.nx)` or *`sigmaP_0`* : main diagonal of the initial estimate
283- covariance ``\m athbf{P}(0)``, specified as a standard deviation vector.
283+ covariance ``\m athbf{P}(0)``, specified as a standard deviation vector.
284284- `σQ=fill(1/model.nx,model.nx)` or *`sigmaQ`* : main diagonal of the process noise
285- covariance ``\m athbf{Q}`` of `model`, specified as a standard deviation vector.
285+ covariance ``\m athbf{Q}`` of `model`, specified as a standard deviation vector.
286286- `σR=fill(1,length(i_ym))` or *`sigmaR`* : main diagonal of the sensor noise covariance
287- ``\m athbf{R}`` of `model` measured outputs, specified as a standard deviation vector.
287+ ``\m athbf{R}`` of `model` measured outputs, specified as a standard deviation vector.
288288- `nint_u=0`: integrator quantity for the stochastic model of the unmeasured disturbances at
289- the manipulated inputs (vector), use `nint_u=0` for no integrator (see Extended Help).
289+ the manipulated inputs (vector), use `nint_u=0` for no integrator (see Extended Help).
290290- `nint_ym=default_nint(model,i_ym,nint_u)` : same than `nint_u` but for the unmeasured
291- disturbances at the measured outputs, use `nint_ym=0` for no integrator (see Extended Help).
291+ disturbances at the measured outputs, use `nint_ym=0` for no integrator (see Extended Help).
292292- `σQint_u=fill(1,sum(nint_u))` or *`sigmaQint_u`* : same than `σQ` but for the unmeasured
293- disturbances at manipulated inputs ``\m athbf{Q_{int_u}}`` (composed of integrators).
293+ disturbances at manipulated inputs ``\m athbf{Q_{int_u}}`` (composed of integrators).
294294- `σPint_u_0=fill(1,sum(nint_u))` or *`sigmaPint_u_0`* : same than `σP_0` but for the unmeasured
295- disturbances at manipulated inputs ``\m athbf{P_{int_u}}(0)`` (composed of integrators).
295+ disturbances at manipulated inputs ``\m athbf{P_{int_u}}(0)`` (composed of integrators).
296296- `σQint_ym=fill(1,sum(nint_ym))` or *`sigmaQint_u`* : same than `σQ` for the unmeasured
297- disturbances at measured outputs ``\m athbf{Q_{int_{ym}}}`` (composed of integrators).
297+ disturbances at measured outputs ``\m athbf{Q_{int_{ym}}}`` (composed of integrators).
298298- `σPint_ym_0=fill(1,sum(nint_ym))` or *`sigmaPint_ym_0`* : same than `σP_0` but for the unmeasured
299- disturbances at measured outputs ``\m athbf{P_{int_{ym}}}(0)`` (composed of integrators).
299+ disturbances at measured outputs ``\m athbf{P_{int_{ym}}}(0)`` (composed of integrators).
300300- `Cwt=Inf` : slack variable weight ``C``, default to `Inf` meaning hard constraints only.
301301- `gc=(_,_,_,_,_,_,_,_,_,_,_)->nothing` or `gc!` : custom nonlinear inequality constraint function
302302 ``\m athbf{g_c}(\m athbf{X̂_e, V̂_e, Ŵ_e, U_e, Y_e^m, D_e, P̄, x̄, p}, ε)``, mutating or not
@@ -313,6 +313,8 @@ transcription for now.
313313- `hessian=false` : an `AbstractADType` backend for the Hessian of the Lagrangian, see
314314 `gradient` above for the options. The default `false` skip it and use the quasi-Newton
315315 method of `optim` (see Extended Help).
316+ - `covestim=nothing`: a [`StateEstimator`](@ref) object for the arrival covariance estimation
317+ ``\m athbf{P̂}_{k-N_k}(k-N_k+p)``, `nothing` means the default choice (see Extended Help).
316318- `direct=true`: construct with a direct transmission from ``\m athbf{y^m}`` (a.k.a. current
317319 estimator, in opposition to the delayed/predictor form).
318320
@@ -449,7 +451,7 @@ MovingHorizonEstimator estimator with a sample time Ts = 10.0 s:
449451 [`JuMP` documentation](@extref JuMP Common-mistakes-when-writing-a-user-defined-operator)
450452 for common mistakes when writing these functions. Also, an [`UnscentedKalmanFilter`](@ref)
451453 estimates the arrival covariance by default.
452-
454+
453455 One exception about AD: the selected backend for the Hessian of the Lagrangian function
454456 with `hessian=true` options is sparse:
455457 ```julia
@@ -469,7 +471,15 @@ MovingHorizonEstimator estimator with a sample time Ts = 10.0 s:
469471 )
470472 )
471473 ```
472- that is, it will test many coloring orders at preparation and keep the best.
474+ that is, it will test many coloring orders at preparation and keep the best. The
475+ argument `covestim` customizes the arrival covariance estimator. The supported types are
476+ [`SteadyKalmanFilter`](@ref), [`KalmanFilter`](@ref), [`UnscentedKalmanFilter`](@ref)
477+ and [`ExtendedKalmanFilter`](@ref). A constant arrival covariance is supported using
478+ the [`SteadyKalmanFilter`](@ref). The constant is fixed by `σP_0`, `σPint_ym_0` and
479+ `σPint_u_0` arguments. If `isnothing(σP_0)`, it is fixed at `covestim.cov.P̂`, which is
480+ the steady-state value ``\m athbf{P̂}(∞)`` for the [`SteadyKalmanFilter`](@ref). For
481+ [`NonLinModel`](@ref), construct a [`SteadyKalmanFilter`](@ref) with an arbitrary
482+ [`LinModel`](@ref), as long as the number of estimated states `nx̂` matches the MHE.
473483
474484 Note that if `Cwt≠Inf`, the attribute `nlp_scaling_max_gradient` of `Ipopt` is set to
475485 `10/Cwt` (if not already set), to scale the small values of ``ε``. Use the second
@@ -497,6 +507,7 @@ function MovingHorizonEstimator(
497507 gradient:: AbstractADType = DEFAULT_NONLINMHE_GRADIENT,
498508 jacobian:: AbstractADType = DEFAULT_NONLINMHE_JACOBIAN,
499509 hessian:: Union{AbstractADType, Bool, Nothing} = false ,
510+ covestim:: Union{StateEstimator, Nothing} = nothing ,
500511 direct = true ,
501512 σP_0 = sigmaP_0,
502513 σQ = sigmaQ,
@@ -507,25 +518,16 @@ function MovingHorizonEstimator(
507518 σQint_ym = sigmaQint_ym,
508519) where {NT<: Real , SM<: SimModel{NT} , JM<: JuMP.GenericModel }
509520 # estimated covariances matrices (variance = σ²) :
510- P̂_0 = Diagonal ([σP_0; σPint_u_0; σPint_ym_0]. ^ 2 )
521+ P̂_0 = isnothing (σP_0) ? nothing : Diagonal ([σP_0; σPint_u_0; σPint_ym_0]. ^ 2 )
511522 Q̂ = Diagonal ([σQ; σQint_u; σQint_ym ]. ^ 2 )
512523 R̂ = Diagonal ([σR;]. ^ 2 )
513524 isnothing (He) && throw (ArgumentError (" Estimation horizon He must be explicitly specified" ))
514525 return MovingHorizonEstimator (
515526 model, He, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂, Cwt;
516- gc, gc!, nc, p, direct, optim, gradient, jacobian, hessian
527+ gc, gc!, nc, p, optim, gradient, jacobian, hessian, covestim, direct
517528 )
518529end
519530
520- " Default optimizer for MHE, depending on the model and the number of custom NL constraints."
521- function default_optim_mhe (model:: SimModel , nc)
522- if model isa LinModel && iszero (nc)
523- return JuMP. Model (DEFAULT_LINMHE_OPTIMIZER, add_bridges= false )
524- else
525- return JuMP. Model (DEFAULT_NONLINMHE_OPTIMIZER, add_bridges= false )
526- end
527- end
528-
529531@doc raw """
530532 MovingHorizonEstimator(
531533 model, He, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂, Cwt=Inf;
@@ -536,20 +538,15 @@ end
536538 gradient=AutoForwardDiff(),
537539 jacobian=AutoForwardDiff(),
538540 hessian=false,
541+ covestim=nothing,
539542 direct=true,
540- covestim=default_covestim_mhe(model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
541543 )
542544
543545Construct the estimator from the augmented covariance matrices `P̂_0`, `Q̂` and `R̂`.
544546
545547This syntax allows nonzero off-diagonal elements in ``\m athbf{P̂_i}, \m athbf{Q̂, R̂}``,
546- where ``\m athbf{P̂_i}`` is the initial estimation covariance, provided by `P̂_0` argument. The
547- keyword argument `covestim` also allows specifying a custom [`StateEstimator`](@ref) object
548- for the estimation of covariance at the arrival ``\m athbf{P̂}_{k-N_k}(k-N_k+p)``. The
549- supported types are [`SteadyKalmanFilter`](@ref), [`KalmanFilter`](@ref),
550- [`UnscentedKalmanFilter`](@ref) and [`ExtendedKalmanFilter`](@ref). A constant arrival
551- covariance is supported with [`SteadyKalmanFilter`](@ref), and by setting the `P̂` argument
552- of [`setstate!`](@ref) at the desired value.
548+ where ``\m athbf{P̂_i}`` is the initial estimation covariance. Its value is provided by `P̂_0`
549+ argument. If `isnothing(P̂_0)`, its value will be fetch in `covestim.cov.P̂`.
553550"""
554551function MovingHorizonEstimator (
555552 model:: SM , He, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂, Cwt= Inf ;
@@ -561,14 +558,24 @@ function MovingHorizonEstimator(
561558 gradient:: AbstractADType = DEFAULT_NONLINMHE_GRADIENT,
562559 jacobian:: AbstractADType = DEFAULT_NONLINMHE_JACOBIAN,
563560 hessian:: Union{AbstractADType, Bool, Nothing} = false ,
561+ covestim:: Union{StateEstimator, Nothing} = nothing ,
564562 direct = true ,
565- covestim:: CE = default_covestim_mhe (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
566- ) where {NT<: Real , SM<: SimModel{NT} , JM<: JuMP.GenericModel , CE<: StateEstimator{NT} }
563+ ) where {NT<: Real , SM<: SimModel{NT} , JM<: JuMP.GenericModel }
564+ if isnothing (P̂_0)
565+ if isnothing (covestim)
566+ throw (ArgumentError (" a covestim argument should be specified to fetch its covariance P̂" ))
567+ end
568+ P̂_0 = covestim. cov. P̂
569+ end
567570 P̂_0, Q̂, R̂ = to_mat (P̂_0), to_mat (Q̂), to_mat (R̂)
568571 cov = KalmanCovariances (model, i_ym, nint_u, nint_ym, Q̂, R̂, P̂_0, He)
569572 gc! = get_mutating_gc_mhe (NT, gc)
570573 hessian = validate_hessian (hessian, gradient, DEFAULT_NONLINMHE_HESSIAN)
574+ if isnothing (covestim)
575+ covestim = default_covestim_mhe (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
576+ end
571577 validate_covestim (cov, covestim)
578+ setstate! (covestim, covestim. x̂0 + covestim. x̂op, P̂_0)
572579 return MovingHorizonEstimator {NT} (
573580 model,
574581 He, i_ym, nint_u, nint_ym, cov, Cwt,
@@ -578,17 +585,30 @@ function MovingHorizonEstimator(
578585 )
579586end
580587
581- function default_covestim_mhe (model:: LinModel , i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
582- return KalmanFilter (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
588+ " Default optimizer for MHE, depending on the model and the number of custom NL constraints."
589+ function default_optim_mhe (model:: SimModel , nc)
590+ if model isa LinModel && iszero (nc)
591+ return JuMP. Model (DEFAULT_LINMHE_OPTIMIZER, add_bridges= false )
592+ else
593+ return JuMP. Model (DEFAULT_NONLINMHE_OPTIMIZER, add_bridges= false )
594+ end
583595end
596+
597+ " Default arrival covariance estimator for MHE, depending on the model type only."
584598function default_covestim_mhe (model:: SimModel , i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
585- return UnscentedKalmanFilter (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
599+ if model isa LinModel
600+ return KalmanFilter (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
601+ else
602+ return UnscentedKalmanFilter (model, i_ym, nint_u, nint_ym, P̂_0, Q̂, R̂; direct)
603+ end
586604end
587605
588606" Validate covestim type and dimensions."
589607function validate_covestim (cov:: KalmanCovariances , covestim:: KalmanEstimator )
590- if size (cov. P̂) != size (covestim. cov. P̂)
591- throw (ArgumentError (" estimation covariance covestim.cov.P̂ size does not match the MHE" ))
608+ invP̄, P̂ = cov. invP̄, covestim. cov. P̂
609+ nx̂ = size (invP̄, 1 )
610+ if size (invP̄) != size (P̂)
611+ throw (ArgumentError (" P̂ covariance size $(size (P̂)) of covestim does match nx̂=$nx̂ " ))
592612 end
593613 return nothing
594614end
0 commit comments