UKFObserver ​
Unscented 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.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables) determines its algebraic variables from the differential state and the control, and is filtered on the Julia backend as the ODE obtained by eliminating them: the estimate xhat and its covariance are over the differential state, the algebraic variables are solved for at every sigma point by Newton iterations from those at the estimate (or by continuation from the estimate when these do not converge), those 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 measurements may be algebraic variables. The step throws when the algebraic equations cannot be solved at a sigma point, for instance when the spread of the sigma points reaches states at which the solution branch of the estimate does not exist.
At every tick the estimate predicted for this tick is corrected with the sampled measurements y (the unscented transform of the measurement function), 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 (the unscented transform of the dynamics, plus the process noise R1). 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 (directly, or through an algebraic variable whose equations depend on u), and a controller computing u from xhat can be connected to u without forming an algebraic loop:
The noise is additive on the state and on the measurements, and the sigma points use LowLevelParticleFilters' default (trivial, equally weighted) weights. 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 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 and supersample are structural: the step is generated for them, and the C backend unrolls the sub-steps.
Usage ​
MPCComponents.UKFObserver(R1=_ukf_R1(ukf), R2=_ukf_R2(ukf), x0=_ukf_x0(ukf), R0=_ukf_R0(ukf), xR0=_ukf_carrier0(ukf, 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 |
backend | Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export) | – | Backend.Auto() |
alpha | Sigma-point spread parameter of the scaled unscented transform; positive | – | 1.0 |
beta | Prior-distribution parameter of the scaled unscented transform; two represents a Gaussian prior | – | 0.0 |
kappa | Secondary scaling parameter; state dimension plus kappa must be positive | – | 0.0 |
ukf | Set-up filter built from the parameters above; pass build_ukf(...) to bypass them | – | build_ukf(;...appa=kappa) |
nx | State dimension of the model (derived from ukf) | – | _ukf_nx(ukf) |
nu | Number of control inputs (derived from ukf) | – | _ukf_nu(ukf) |
ny | Number of measured outputs (derived from ukf) | – | _ukf_ny(ukf) |
nz | Number of algebraic variables of a DAE model, zero for an ODE model (derived from ukf) | – | _ukf_nz(ukf) |
nc | Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived) | – | _ukf_nc(ukf) |
use_c | Whether the static step is used (resolved from backend) | – | _ukf_use_c(ukf, 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) | – | _ukf_feedthrough(ukf) |
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) | – | _ukf_algebr...hrough(ukf) |
ncorr | Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived) | – | _ukf_ncorr(ukf) |
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 | – | _ukf_R1(ukf) |
R2 | Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable | – | _ukf_R2(ukf) |
x0 | Initial state estimate in state_variables order; tunable, read at initialization | – | _ukf_x0(ukf) |
R0 | Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization | – | _ukf_R0(ukf) |
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 ​
"""
Unscented 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.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables)
determines its algebraic variables from the differential state and the control, and is filtered on
the Julia backend as the ODE obtained by eliminating them: the estimate `xhat` and its covariance are
over the differential state, the algebraic variables are solved for at every sigma point by Newton
iterations from those at the estimate (or by continuation from the estimate when these do not
converge), those 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 measurements may be algebraic
variables. The step throws when the algebraic equations cannot be solved at a sigma point, for
instance when the spread of the sigma points reaches states at which the solution branch of the
estimate does not exist.
At every tick the estimate predicted for this tick is corrected with the sampled measurements `y`
(the unscented transform of the measurement function), 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 (the unscented transform of the dynamics, plus the process noise `R1`). 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 (directly, or through an algebraic variable whose equations depend on
`u`), and a controller computing `u` from `xhat` can be connected to `u` without forming an algebraic
loop:
```math
\\begin{aligned}
\\hat x_{k|k} &= \\hat x_{k|k-1} + K_k\\,(y_k - \\hat y_{k|k-1}) \\\\
\\hat x_{k+1|k} &= \\mathrm{UT}_f(\\hat x_{k|k}, R_{k|k}, u_k)
\\end{aligned}
```
The noise is additive on the state and on the measurements, and the sigma points use
LowLevelParticleFilters' default (trivial, equally weighted) weights. 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 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 and
`supersample` are structural: the step is generated for them, and the C backend unrolls the sub-steps.
"""
component UKFObserver@[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
"Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export)"
structural parameter backend::Backend = Backend.Auto()
"Sigma-point spread parameter of the scaled unscented transform; positive"
structural parameter alpha::Real = 1.0
"Prior-distribution parameter of the scaled unscented transform; two represents a Gaussian prior"
structural parameter beta::Real = 0.0
"Secondary scaling parameter; state dimension plus kappa must be positive"
structural parameter kappa::Real = 0.0
"Set-up filter built from the parameters above; pass build_ukf(...) to bypass them"
structural parameter ukf::UKFRef = build_ukf(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, Ts = Ts, supersample = supersample, stages = dae_stages, integrator = integrator, alpha = alpha, beta = beta, kappa = kappa)
"State dimension of the model (derived from ukf)"
final structural parameter nx::Integer = _ukf_nx(ukf)
"Number of control inputs (derived from ukf)"
final structural parameter nu::Integer = _ukf_nu(ukf)
"Number of measured outputs (derived from ukf)"
final structural parameter ny::Integer = _ukf_ny(ukf)
"Number of algebraic variables of a DAE model, zero for an ODE model (derived from ukf)"
final structural parameter nz::Integer = _ukf_nz(ukf)
"Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived)"
final structural parameter nc::Integer = _ukf_nc(ukf)
"Whether the static step is used (resolved from backend)"
final structural parameter use_c::Boolean = _ukf_use_c(ukf, 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 = _ukf_feedthrough(ukf)
"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 = _ukf_algebraic_feedthrough(ukf)
"Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived)"
final structural parameter ncorr::Integer = _ukf_ncorr(ukf)
"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] = _ukf_R1(ukf)
"Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable"
parameter R2::Real[ny, ny] = _ukf_R2(ukf)
"Initial state estimate in state_variables order; tunable, read at initialization"
parameter x0::Real[nx] = _ukf_x0(ukf)
"Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization"
parameter R0::Real[nx, nx] = _ukf_R0(ukf)
"Initial carrier: the prior x0 as the prediction for the first tick and R0 as its covariance"
final parameter xR0::Real[nc] = _ukf_carrier0(ukf, 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 = ukf_correct_c(xR@(clk-1), y@clk, u@clk, R2, ukf)
else
xc@clk = ukf_correct_c(xR@(clk-1), y@clk, R2, ukf)
end
xR@clk = ukf_predict_c(xc@clk, u@clk, R1, ukf)
else
if feedthrough
xc@clk = ukf_correct(xR@(clk-1), y@clk, u@clk, R2, ukf)
else
xc@clk = ukf_correct(xR@(clk-1), y@clk, R2, ukf)
end
xR@clk = ukf_predict(xc@clk, u@clk, R1, ukf)
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/UKFObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"outputs",
"state_variables",
"Ts",
"supersample",
"integrator",
"dae_stages"
]
},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Backend": {"control": "fieldset", "contents": ["backend"]}
}
}
}
}
endFlattened Source
"""
Unscented 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.
A semi-explicit index-1 DAE model (a plant whose compiled equations keep algebraic variables)
determines its algebraic variables from the differential state and the control, and is filtered on
the Julia backend as the ODE obtained by eliminating them: the estimate `xhat` and its covariance are
over the differential state, the algebraic variables are solved for at every sigma point by Newton
iterations from those at the estimate (or by continuation from the estimate when these do not
converge), those 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 measurements may be algebraic
variables. The step throws when the algebraic equations cannot be solved at a sigma point, for
instance when the spread of the sigma points reaches states at which the solution branch of the
estimate does not exist.
At every tick the estimate predicted for this tick is corrected with the sampled measurements `y`
(the unscented transform of the measurement function), 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 (the unscented transform of the dynamics, plus the process noise `R1`). 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 (directly, or through an algebraic variable whose equations depend on
`u`), and a controller computing `u` from `xhat` can be connected to `u` without forming an algebraic
loop:
```math
\\begin{aligned}
\\hat x_{k|k} &= \\hat x_{k|k-1} + K_k\\,(y_k - \\hat y_{k|k-1}) \\\\
\\hat x_{k+1|k} &= \\mathrm{UT}_f(\\hat x_{k|k}, R_{k|k}, u_k)
\\end{aligned}
```
The noise is additive on the state and on the measurements, and the sigma points use
LowLevelParticleFilters' default (trivial, equally weighted) weights. 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 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 and
`supersample` are structural: the step is generated for them, and the C backend unrolls the sub-steps.
"""
component UKFObserver
"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
"Backend: Auto and Julia run LowLevelParticleFilters in-process, C the exportable static step (required for C code export)"
structural parameter backend::Backend = Backend.Auto()
"Sigma-point spread parameter of the scaled unscented transform; positive"
structural parameter alpha::Real = 1.0
"Prior-distribution parameter of the scaled unscented transform; two represents a Gaussian prior"
structural parameter beta::Real = 0.0
"Secondary scaling parameter; state dimension plus kappa must be positive"
structural parameter kappa::Real = 0.0
"Set-up filter built from the parameters above; pass build_ukf(...) to bypass them"
structural parameter ukf::UKFRef = build_ukf(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, Ts = Ts, supersample = supersample, stages = dae_stages, integrator = integrator, alpha = alpha, beta = beta, kappa = kappa)
"State dimension of the model (derived from ukf)"
final structural parameter nx::Integer = _ukf_nx(ukf)
"Number of control inputs (derived from ukf)"
final structural parameter nu::Integer = _ukf_nu(ukf)
"Number of measured outputs (derived from ukf)"
final structural parameter ny::Integer = _ukf_ny(ukf)
"Number of algebraic variables of a DAE model, zero for an ODE model (derived from ukf)"
final structural parameter nz::Integer = _ukf_nz(ukf)
"Length of the clocked carrier [xcorr; xpred; vec(Rpred); zcorr; zpred], 2 nx + nx² + 2 nz (derived)"
final structural parameter nc::Integer = _ukf_nc(ukf)
"Whether the static step is used (resolved from backend)"
final structural parameter use_c::Boolean = _ukf_use_c(ukf, 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 = _ukf_feedthrough(ukf)
"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 = _ukf_algebraic_feedthrough(ukf)
"Length of the corrected estimate [xcorr; vec(Rcorr); zcorr], nx + nx² + nz (derived)"
final structural parameter ncorr::Integer = _ukf_ncorr(ukf)
"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] = _ukf_R1(ukf)
"Measurement-noise covariance (ny × ny in outputs order, e.g. diagonal([1e-3])); tunable"
parameter R2::Real[ny, ny] = _ukf_R2(ukf)
"Initial state estimate in state_variables order; tunable, read at initialization"
parameter x0::Real[nx] = _ukf_x0(ukf)
"Covariance of the initial state estimate (nx × nx in state_variables order); tunable, read at initialization"
parameter R0::Real[nx, nx] = _ukf_R0(ukf)
"Initial carrier: the prior x0 as the prediction for the first tick and R0 as its covariance"
final parameter xR0::Real[nc] = _ukf_carrier0(ukf, 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 = ukf_correct_c(xR@(clk-1), y@clk, u@clk, R2, ukf)
else
xc@clk = ukf_correct_c(xR@(clk-1), y@clk, R2, ukf)
end
xR@clk = ukf_predict_c(xc@clk, u@clk, R1, ukf)
else
if feedthrough
xc@clk = ukf_correct(xR@(clk-1), y@clk, u@clk, R2, ukf)
else
xc@clk = ukf_correct(xR@(clk-1), y@clk, R2, ukf)
end
xR@clk = ukf_predict(xc@clk, u@clk, R1, ukf)
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/UKFObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"outputs",
"state_variables",
"Ts",
"supersample",
"integrator",
"dae_stages"
]
},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Backend": {"control": "fieldset", "contents": ["backend"]}
}
}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses