EKFObserver ​
Extended Kalman filter (LowLevelParticleFilters.jl) estimating the state of a nonlinear plant model, run as a clocked component.
The prediction model is the plant itself: a continuous-time Dyad component (or ModelingToolkit system) whose control inputs are named by inputs and whose measured signals are named by outputs, or a ContinuousDynamics built with continuous_dynamics. The model is discretized with the selected integrator (supersample sub-steps per sample interval) with the control held constant. Auto selects RK4 for an ODE and RadauIIA for a DAE; RadauIIA also supports a stiff ODE. Implicit integration requires the Julia backend.
At every tick the estimate predicted for this tick is corrected with the sampled measurements y, the corrected estimate is output as xhat, and the control u applied at this tick is used to predict the estimate and its covariance for the next tick:
where f is the discretized dynamics, g the measurement, and A_k, C_k their Jacobians with respect to the state at the corrected and the predicted estimate. jacobian_backend selects how these are obtained on the Julia backend: Symbolic derives them from the model equations (the Jacobian of f is the exact derivative of the RK4 one-step map, propagated through its stages with the symbolic Jacobian of the right-hand side), ForwardDiff differentiates the generated model functions by automatic differentiation. The correction and the prediction are separate clocked equations, so xhat does not depend on u of the same tick unless a measured output does, and a controller computing u from xhat can be connected to u without forming an algebraic loop.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables) is filtered on the Julia backend, as by UKFObserver, as the ODE obtained by eliminating its algebraic variables: the estimate xhat and its covariance are over the differential state, the algebraic variables at the corrected estimate are output as zhat, and the model is discretized by Radau IIA collocation with dae_stages nodes per sub-step. The Jacobians of this reduced model follow from the implicit function theorem applied to the collocation equations and to the algebraic equations, which therefore must have a nonsingular Jacobian with respect to the algebraic variables at the estimate.
The noise is additive on the state and on the measurements. The estimate, its prediction and the predicted covariance are Dyad clocked variables (the carrier xR), so the step is pure in its arguments and a model can be simulated repeatedly. The C backend (backend = C, selected for C code export) runs the same algorithm, with the symbolic Jacobians, as a static, allocation-free step; it agrees with the LowLevelParticleFilters backend to rounding.
Measurements must be sampled before this component. R1, R0 and x0 are given in the order of state_variables (the order of xhat, and of the carrier); R2 in the order of outputs.
Tunable parameters ​
R1, R2, x0 and R0 are ordinary parameters: the covariances are read at every tick, the initial estimate at initialization, so a filter can be retuned after the model is compiled, and in exported C code all four are fields of the parameter struct. They are full matrices of fixed shape – diagonal([1e-4, 1e-4]) builds a diagonal one. The model, the dimensions derived from it, supersample and jacobian_backend are structural: the step is generated for them.
Usage ​
MPCComponents.EKFObserver(R1=_ekf_R1(ekf), R2=_ekf_R2(ekf), x0=_ekf_x0(ekf), R0=_ekf_R0(ekf), xR0=_ekf_carrier0(ekf, x0, R0))
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
dynamics | Continuous-time prediction model: a plant component or system (then give inputs), a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated, or a ContinuousDynamics built with continuous_dynamics | – | |
inputs | Names of the control inputs of the model when dynamics is a system, in the order of u (e.g. ["tau"]) | – | [] |
disturbance_inputs | Names of the unknown disturbance inputs of dynamics when it is given as a system/plant component, e.g. the input w of an integrating disturbance state der(d) = w: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics) | – | [] |
outputs | Names of the measured signals (states, algebraic variables, or output connectors / observed variables of the model), in the order of y | – | [] |
state_variables | Names of the state variables carried by xhat, in order (a permutation of the state variables of the model); empty for the model's own order | – | [] |
Ts | Sample time [s] | – | |
supersample | Integration sub-steps per sample interval | – | 1 |
integrator | Prediction integrator: Auto selects RK4 for an ODE and RadauIIA for a DAE; RadauIIA also supports a stiff ODE and requires the Julia backend | – | ObserverIntegrator.Auto() |
dae_stages | Radau IIA collocation nodes per sub-step when implicit integration is used | – | 4 |
jacobian_backend | How the Jacobians of the model are obtained on the Julia backend: Symbolic (derived from the model equations) or ForwardDiff (automatic differentiation of the generated model functions); the other cases are not supported. The C backend always uses the symbolic Jacobians | – | ACADOSJacob....Symbolic() |
backend | Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export) | – | Backend.Auto() |
ekf | Set-up filter built from the parameters above; pass build_ekf(...) to bypass them | – | build_ekf(;...an_backend) |
nx | State dimension of the model (derived from ekf) | – | _ekf_nx(ekf) |
nu | Number of control inputs (derived from ekf) | – | _ekf_nu(ekf) |
ny | Number of measured outputs (derived from ekf) | – | _ekf_ny(ekf) |
nz | Number of algebraic variables of a DAE model, zero for an ODE model (derived from ekf) | – | _ekf_nz(ekf) |
nc | Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived) | – | _ekf_nc(ekf) |
use_c | Whether the static step is used (resolved from backend) | – | _ekf_use_c(ekf, backend) |
feedthrough | Whether the corrected estimate depends on the control of the current tick, so that the correction reads it: a measured output depends on the control directly, or through an algebraic variable of a DAE model (derived) | – | _ekf_feedthrough(ekf) |
algebraic_feedthrough | Whether the algebraic equations of a DAE model depend on the control, so that zhat is computed with the control of the current tick (derived) | – | _ekf_algebr...hrough(ekf) |
ncorr | Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived) | – | _ekf_ncorr(ekf) |
R1 | Process-noise covariance added to the state after each prediction (nx × nx in state_variables order, e.g. diagonal([1e-4, 1e-4])); tunable | – | _ekf_R1(ekf) |
R2 | Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable | – | _ekf_R2(ekf) |
x0 | Initial state estimate in state_variables order; tunable, read at initialization | – | _ekf_x0(ekf) |
R0 | Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization | – | _ekf_R0(ekf) |
Connectors ​
y- This connector represents a real signal as an input to a component (RealInput)u- This connector represents a real signal as an input to a component (RealInput)xhat- This connector represents a real signal as an output from a component (RealOutput)zhat- This connector represents a real signal as an output from a component (RealOutput)
Variables ​
| Name | Description | Units |
|---|---|---|
xR | Clocked state: the corrected estimate, the estimate predicted for the next tick and its covariance (column-major), in state_variables order, then the algebraic variables at both | – |
xc | The corrected estimate, its covariance (column-major) and the algebraic variables there, of the current tick, in state_variables order | – |
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Extended Kalman filter (LowLevelParticleFilters.jl) estimating the state of a nonlinear plant model,
run as a clocked component.
The prediction model is the plant itself: a continuous-time Dyad component (or ModelingToolkit
system) whose control inputs are named by `inputs` and whose measured signals are named by
`outputs`, or a `ContinuousDynamics` built with `continuous_dynamics`. The model is discretized
with the selected `integrator` (`supersample` sub-steps per sample interval) with the control held constant.
`Auto` selects RK4 for an ODE and RadauIIA for a DAE; `RadauIIA` also supports a stiff ODE.
Implicit integration requires the Julia backend.
At every tick the estimate predicted for this tick is corrected with the sampled measurements `y`,
the corrected estimate is output as `xhat`, and the control `u` applied at this tick is used to
predict the estimate and its covariance for the next tick:
```math
\\begin{aligned}
K_k &= R_{k|k-1} C_k^T (C_k R_{k|k-1} C_k^T + R_2)^{-1} \\\\
\\hat x_{k|k} &= \\hat x_{k|k-1} + K_k\\,(y_k - g(\\hat x_{k|k-1}, u_k)), \\quad R_{k|k} = (I - K_k C_k) R_{k|k-1} \\\\
\\hat x_{k+1|k} &= f(\\hat x_{k|k}, u_k), \\quad R_{k+1|k} = A_k R_{k|k} A_k^T + R_1
\\end{aligned}
```
where `f` is the discretized dynamics, `g` the measurement, and `A_k`, `C_k` their Jacobians with
respect to the state at the corrected and the predicted estimate. `jacobian_backend` selects how these
are obtained on the Julia backend: `Symbolic` derives them from the model equations (the Jacobian of `f`
is the exact derivative of the RK4 one-step map, propagated through its stages with the symbolic
Jacobian of the right-hand side), `ForwardDiff` differentiates the generated model functions by
automatic differentiation. The correction and the prediction are separate clocked equations, so
`xhat` does not depend on `u` of the same tick unless a measured output does, and a controller
computing `u` from `xhat` can be connected to `u` without forming an algebraic loop.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables) is
filtered on the Julia backend, as by `UKFObserver`, as the ODE obtained by eliminating its algebraic
variables: the estimate `xhat` and its covariance are over the differential state, the algebraic
variables at the corrected estimate are output as `zhat`, and the model is discretized by Radau IIA
collocation with `dae_stages` nodes per sub-step. The Jacobians of this reduced model follow from
the implicit function theorem applied to the collocation equations and to the algebraic equations,
which therefore must have a nonsingular Jacobian with respect to the algebraic variables at the
estimate.
The noise is additive on the state and on the measurements. The estimate, its prediction and the
predicted covariance are Dyad clocked variables (the carrier `xR`), so the step is pure in its
arguments and a model can be simulated repeatedly. The C backend (`backend = C`, selected for C code
export) runs the same algorithm, with the symbolic Jacobians, as a static, allocation-free step; it
agrees with the LowLevelParticleFilters backend to rounding.
Measurements must be sampled before this component. `R1`, `R0` and `x0` are given in the order of
`state_variables` (the order of `xhat`, and of the carrier); `R2` in the order of `outputs`.
# Tunable parameters
`R1`, `R2`, `x0` and `R0` are ordinary parameters: the covariances are read at every tick, the initial
estimate at initialization, so a filter can be retuned after the model is compiled, and in exported C
code all four are fields of the parameter struct. They are full matrices of fixed shape --
`diagonal([1e-4, 1e-4])` builds a diagonal one. The model, the dimensions derived from it,
`supersample` and `jacobian_backend` are structural: the step is generated for them.
"""
component EKFObserver@[input clk extends Discrete]
"Sampled measurements, in the order of outputs"
y = [RealInput@[clk]() for i in 1:ny] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 700, "x2": 0, "y2": 800}}}
}
"Control applied at the current tick, in the order of inputs"
u = [RealInput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 200, "x2": 0, "y2": 300}}}
}
"State estimate corrected with the current measurement, in the order of state_variables"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 250, "x2": 1100, "y2": 350}}}
}
"Algebraic variables of a DAE model at the corrected estimate, in the model's order; empty for an ODE model"
zhat = [RealOutput@[clk]() for i in 1:nz] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 650, "x2": 1100, "y2": 750}}}
}
"Continuous-time prediction model: a plant component or system (then give inputs), a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated, or a ContinuousDynamics built with continuous_dynamics"
structural parameter dynamics::MPCDynamics
"""
Names of the control inputs of the model when dynamics is a system, in the order of u (e.g. ["tau"])
"""
structural parameter inputs::String[:] = []
"Names of the unknown disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. the input `w` of an integrating disturbance state `der(d) = w`: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics)"
structural parameter disturbance_inputs::String[:] = []
"Names of the measured signals (states, algebraic variables, or output connectors / observed variables of the model), in the order of y"
structural parameter outputs::String[:] = []
"Names of the state variables carried by xhat, in order (a permutation of the state variables of the model); empty for the model's own order"
structural parameter state_variables::String[:] = []
"Sample time [s]"
structural parameter Ts::Real
"Integration sub-steps per sample interval"
structural parameter supersample::Integer = 1
"Prediction integrator: Auto selects RK4 for an ODE and RadauIIA for a DAE; RadauIIA also supports a stiff ODE and requires the Julia backend"
structural parameter integrator::ObserverIntegrator = ObserverIntegrator.Auto()
"Radau IIA collocation nodes per sub-step when implicit integration is used"
structural parameter dae_stages::Integer = 4
"How the Jacobians of the model are obtained on the Julia backend: Symbolic (derived from the model equations) or ForwardDiff (automatic differentiation of the generated model functions); the other cases are not supported. The C backend always uses the symbolic Jacobians"
structural parameter jacobian_backend::ACADOSJacobianBackend = ACADOSJacobianBackend.Symbolic()
"Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export)"
structural parameter backend::Backend = Backend.Auto()
"Set-up filter built from the parameters above; pass build_ekf(...) to bypass them"
structural parameter ekf::EKFRef = build_ekf(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, Ts = Ts, supersample = supersample, stages = dae_stages, integrator = integrator, jacobian_backend = jacobian_backend)
"State dimension of the model (derived from ekf)"
final structural parameter nx::Integer = _ekf_nx(ekf)
"Number of control inputs (derived from ekf)"
final structural parameter nu::Integer = _ekf_nu(ekf)
"Number of measured outputs (derived from ekf)"
final structural parameter ny::Integer = _ekf_ny(ekf)
"Number of algebraic variables of a DAE model, zero for an ODE model (derived from ekf)"
final structural parameter nz::Integer = _ekf_nz(ekf)
"Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived)"
final structural parameter nc::Integer = _ekf_nc(ekf)
"Whether the static step is used (resolved from backend)"
final structural parameter use_c::Boolean = _ekf_use_c(ekf, backend)
"Whether the corrected estimate depends on the control of the current tick, so that the correction reads it: a measured output depends on the control directly, or through an algebraic variable of a DAE model (derived)"
final structural parameter feedthrough::Boolean = _ekf_feedthrough(ekf)
"Whether the algebraic equations of a DAE model depend on the control, so that zhat is computed with the control of the current tick (derived)"
final structural parameter algebraic_feedthrough::Boolean = _ekf_algebraic_feedthrough(ekf)
"Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived)"
final structural parameter ncorr::Integer = _ekf_ncorr(ekf)
"Process-noise covariance added to the state after each prediction (nx × nx in state_variables order, e.g. diagonal([1e-4, 1e-4])); tunable"
parameter R1::Real[nx, nx] = _ekf_R1(ekf)
"Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable"
parameter R2::Real[ny, ny] = _ekf_R2(ekf)
"Initial state estimate in state_variables order; tunable, read at initialization"
parameter x0::Real[nx] = _ekf_x0(ekf)
"Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization"
parameter R0::Real[nx, nx] = _ekf_R0(ekf)
"Initial carrier: the prior x0 as the prediction for the first tick and R0 as its covariance"
final parameter xR0::Real[nc] = _ekf_carrier0(ekf, x0, R0)
"Clocked state: the corrected estimate, the estimate predicted for the next tick and its covariance (column-major), in state_variables order, then the algebraic variables at both"
variable xR::Real[nc]
"The corrected estimate, its covariance (column-major) and the algebraic variables there, of the current tick, in state_variables order"
variable xc::Real[ncorr]
relations
initial xR@(clk-1) = xR0
# The correction and the prediction are separate equations, so that the estimate does not depend on
# the control of the same tick (unless the measurement does): a controller computing u from xhat
# then forms no algebraic loop with the observer.
if use_c
if feedthrough
xc@clk = ekf_correct_c(xR@(clk-1), y@clk, u@clk, R2, ekf)
else
xc@clk = ekf_correct_c(xR@(clk-1), y@clk, R2, ekf)
end
xR@clk = ekf_predict_c(xc@clk, u@clk, R1, ekf)
else
if feedthrough
xc@clk = ekf_correct(xR@(clk-1), y@clk, u@clk, R2, ekf)
else
xc@clk = ekf_correct(xR@(clk-1), y@clk, R2, ekf)
end
xR@clk = ekf_predict(xc@clk, u@clk, R1, ekf)
end
# The corrected estimate is in state_variables order, so the estimate is its leading block
for i in 1:nx
xhat[i] = xc[i]
end
# The algebraic variables at the corrected estimate: from the prediction, which reads the control of
# the tick, when they depend on it, and otherwise from the correction, so that they do not depend on
# the control
if algebraic_feedthrough
for i in 1:nz
zhat[i] = xR[2 * nx + nx ^ 2 + i]
end
else
for i in 1:nz
zhat[i] = xc[nx + nx ^ 2 + i]
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/EKFObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"outputs",
"state_variables",
"Ts",
"supersample",
"integrator",
"dae_stages",
"jacobian_backend"
]
},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Backend": {"control": "fieldset", "contents": ["backend"]}
}
}
}
}
endFlattened Source
"""
Extended Kalman filter (LowLevelParticleFilters.jl) estimating the state of a nonlinear plant model,
run as a clocked component.
The prediction model is the plant itself: a continuous-time Dyad component (or ModelingToolkit
system) whose control inputs are named by `inputs` and whose measured signals are named by
`outputs`, or a `ContinuousDynamics` built with `continuous_dynamics`. The model is discretized
with the selected `integrator` (`supersample` sub-steps per sample interval) with the control held constant.
`Auto` selects RK4 for an ODE and RadauIIA for a DAE; `RadauIIA` also supports a stiff ODE.
Implicit integration requires the Julia backend.
At every tick the estimate predicted for this tick is corrected with the sampled measurements `y`,
the corrected estimate is output as `xhat`, and the control `u` applied at this tick is used to
predict the estimate and its covariance for the next tick:
```math
\\begin{aligned}
K_k &= R_{k|k-1} C_k^T (C_k R_{k|k-1} C_k^T + R_2)^{-1} \\\\
\\hat x_{k|k} &= \\hat x_{k|k-1} + K_k\\,(y_k - g(\\hat x_{k|k-1}, u_k)), \\quad R_{k|k} = (I - K_k C_k) R_{k|k-1} \\\\
\\hat x_{k+1|k} &= f(\\hat x_{k|k}, u_k), \\quad R_{k+1|k} = A_k R_{k|k} A_k^T + R_1
\\end{aligned}
```
where `f` is the discretized dynamics, `g` the measurement, and `A_k`, `C_k` their Jacobians with
respect to the state at the corrected and the predicted estimate. `jacobian_backend` selects how these
are obtained on the Julia backend: `Symbolic` derives them from the model equations (the Jacobian of `f`
is the exact derivative of the RK4 one-step map, propagated through its stages with the symbolic
Jacobian of the right-hand side), `ForwardDiff` differentiates the generated model functions by
automatic differentiation. The correction and the prediction are separate clocked equations, so
`xhat` does not depend on `u` of the same tick unless a measured output does, and a controller
computing `u` from `xhat` can be connected to `u` without forming an algebraic loop.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables) is
filtered on the Julia backend, as by `UKFObserver`, as the ODE obtained by eliminating its algebraic
variables: the estimate `xhat` and its covariance are over the differential state, the algebraic
variables at the corrected estimate are output as `zhat`, and the model is discretized by Radau IIA
collocation with `dae_stages` nodes per sub-step. The Jacobians of this reduced model follow from
the implicit function theorem applied to the collocation equations and to the algebraic equations,
which therefore must have a nonsingular Jacobian with respect to the algebraic variables at the
estimate.
The noise is additive on the state and on the measurements. The estimate, its prediction and the
predicted covariance are Dyad clocked variables (the carrier `xR`), so the step is pure in its
arguments and a model can be simulated repeatedly. The C backend (`backend = C`, selected for C code
export) runs the same algorithm, with the symbolic Jacobians, as a static, allocation-free step; it
agrees with the LowLevelParticleFilters backend to rounding.
Measurements must be sampled before this component. `R1`, `R0` and `x0` are given in the order of
`state_variables` (the order of `xhat`, and of the carrier); `R2` in the order of `outputs`.
# Tunable parameters
`R1`, `R2`, `x0` and `R0` are ordinary parameters: the covariances are read at every tick, the initial
estimate at initialization, so a filter can be retuned after the model is compiled, and in exported C
code all four are fields of the parameter struct. They are full matrices of fixed shape --
`diagonal([1e-4, 1e-4])` builds a diagonal one. The model, the dimensions derived from it,
`supersample` and `jacobian_backend` are structural: the step is generated for them.
"""
component EKFObserver
"Sampled measurements, in the order of outputs"
y = [RealInput@[clk]() for i in 1:ny] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 700, "x2": 0, "y2": 800}}}
}
"Control applied at the current tick, in the order of inputs"
u = [RealInput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 200, "x2": 0, "y2": 300}}}
}
"State estimate corrected with the current measurement, in the order of state_variables"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 250, "x2": 1100, "y2": 350}}}
}
"Algebraic variables of a DAE model at the corrected estimate, in the model's order; empty for an ODE model"
zhat = [RealOutput@[clk]() for i in 1:nz] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 650, "x2": 1100, "y2": 750}}}
}
"Continuous-time prediction model: a plant component or system (then give inputs), a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated, or a ContinuousDynamics built with continuous_dynamics"
structural parameter dynamics::MPCDynamics
"""
Names of the control inputs of the model when dynamics is a system, in the order of u (e.g. ["tau"])
"""
structural parameter inputs::String[:] = []
"Names of the unknown disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. the input `w` of an integrating disturbance state `der(d) = w`: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics)"
structural parameter disturbance_inputs::String[:] = []
"Names of the measured signals (states, algebraic variables, or output connectors / observed variables of the model), in the order of y"
structural parameter outputs::String[:] = []
"Names of the state variables carried by xhat, in order (a permutation of the state variables of the model); empty for the model's own order"
structural parameter state_variables::String[:] = []
"Sample time [s]"
structural parameter Ts::Real
"Integration sub-steps per sample interval"
structural parameter supersample::Integer = 1
"Prediction integrator: Auto selects RK4 for an ODE and RadauIIA for a DAE; RadauIIA also supports a stiff ODE and requires the Julia backend"
structural parameter integrator::ObserverIntegrator = ObserverIntegrator.Auto()
"Radau IIA collocation nodes per sub-step when implicit integration is used"
structural parameter dae_stages::Integer = 4
"How the Jacobians of the model are obtained on the Julia backend: Symbolic (derived from the model equations) or ForwardDiff (automatic differentiation of the generated model functions); the other cases are not supported. The C backend always uses the symbolic Jacobians"
structural parameter jacobian_backend::ACADOSJacobianBackend = ACADOSJacobianBackend.Symbolic()
"Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export)"
structural parameter backend::Backend = Backend.Auto()
"Set-up filter built from the parameters above; pass build_ekf(...) to bypass them"
structural parameter ekf::EKFRef = build_ekf(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, Ts = Ts, supersample = supersample, stages = dae_stages, integrator = integrator, jacobian_backend = jacobian_backend)
"State dimension of the model (derived from ekf)"
final structural parameter nx::Integer = _ekf_nx(ekf)
"Number of control inputs (derived from ekf)"
final structural parameter nu::Integer = _ekf_nu(ekf)
"Number of measured outputs (derived from ekf)"
final structural parameter ny::Integer = _ekf_ny(ekf)
"Number of algebraic variables of a DAE model, zero for an ODE model (derived from ekf)"
final structural parameter nz::Integer = _ekf_nz(ekf)
"Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived)"
final structural parameter nc::Integer = _ekf_nc(ekf)
"Whether the static step is used (resolved from backend)"
final structural parameter use_c::Boolean = _ekf_use_c(ekf, backend)
"Whether the corrected estimate depends on the control of the current tick, so that the correction reads it: a measured output depends on the control directly, or through an algebraic variable of a DAE model (derived)"
final structural parameter feedthrough::Boolean = _ekf_feedthrough(ekf)
"Whether the algebraic equations of a DAE model depend on the control, so that zhat is computed with the control of the current tick (derived)"
final structural parameter algebraic_feedthrough::Boolean = _ekf_algebraic_feedthrough(ekf)
"Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived)"
final structural parameter ncorr::Integer = _ekf_ncorr(ekf)
"Process-noise covariance added to the state after each prediction (nx × nx in state_variables order, e.g. diagonal([1e-4, 1e-4])); tunable"
parameter R1::Real[nx, nx] = _ekf_R1(ekf)
"Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable"
parameter R2::Real[ny, ny] = _ekf_R2(ekf)
"Initial state estimate in state_variables order; tunable, read at initialization"
parameter x0::Real[nx] = _ekf_x0(ekf)
"Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization"
parameter R0::Real[nx, nx] = _ekf_R0(ekf)
"Initial carrier: the prior x0 as the prediction for the first tick and R0 as its covariance"
final parameter xR0::Real[nc] = _ekf_carrier0(ekf, x0, R0)
"Clocked state: the corrected estimate, the estimate predicted for the next tick and its covariance (column-major), in state_variables order, then the algebraic variables at both"
variable xR::Real[nc]
"The corrected estimate, its covariance (column-major) and the algebraic variables there, of the current tick, in state_variables order"
variable xc::Real[ncorr]
relations
initial xR@(clk-1) = xR0
# The correction and the prediction are separate equations, so that the estimate does not depend on
# the control of the same tick (unless the measurement does): a controller computing u from xhat
# then forms no algebraic loop with the observer.
if use_c
if feedthrough
xc@clk = ekf_correct_c(xR@(clk-1), y@clk, u@clk, R2, ekf)
else
xc@clk = ekf_correct_c(xR@(clk-1), y@clk, R2, ekf)
end
xR@clk = ekf_predict_c(xc@clk, u@clk, R1, ekf)
else
if feedthrough
xc@clk = ekf_correct(xR@(clk-1), y@clk, u@clk, R2, ekf)
else
xc@clk = ekf_correct(xR@(clk-1), y@clk, R2, ekf)
end
xR@clk = ekf_predict(xc@clk, u@clk, R1, ekf)
end
# The corrected estimate is in state_variables order, so the estimate is its leading block
for i in 1:nx
xhat[i] = xc[i]
end
# The algebraic variables at the corrected estimate: from the prediction, which reads the control of
# the tick, when they depend on it, and otherwise from the correction, so that they do not depend on
# the control
if algebraic_feedthrough
for i in 1:nz
zhat[i] = xR[2 * nx + nx ^ 2 + i]
end
else
for i in 1:nz
zhat[i] = xc[nx + nx ^ 2 + i]
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/EKFObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"outputs",
"state_variables",
"Ts",
"supersample",
"integrator",
"dae_stages",
"jacobian_backend"
]
},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Backend": {"control": "fieldset", "contents": ["backend"]}
}
}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses