SqKalmanObserver ​
Time-varying Kalman filter in square-root form (LowLevelParticleFilters.jl SqKalmanFilter) estimating the state of the discrete-time linear model
run as a clocked component. At every tick the prediction x(t|t-1) is corrected with the measurements that are available, the filtered estimate x(t|t) is output as xhat, and the control u applied at this tick is used to predict the estimate for the next tick. A measurement is used when it is finite and, with use_valid, when its entry of y_valid is true; the others are left out of the correction (the rows of C, D and the block of R2 of the available measurements are used), so that a sensor that drops out does not bias the estimate. Without available measurements the estimate is only predicted and its covariance grows.
The covariance is carried as the upper-triangular Cholesky factor R (covariance R'R), as in SqKalmanFilter, and both updates are QR factorizations, so it stays symmetric and positive semidefinite in finite precision. The notation is that of LowLevelParticleFilters' forward_trajectory: x, R are the prediction x(t|t-1) and its factor, xt, Rt the filtered estimate x(t|t) and its factor; sqkf_covariance forms the covariance from a carrier.
Measurements must be sampled before this component. With a nonzero D the correction uses the control of the tick, so the control must not be computed from xhat of the same tick.
Tunable parameters ​
R1, R2, x0 and R0 are ordinary parameters: the covariances are read at every tick, the initial estimate at initialization. A, B, C and D are structural. The filter runs in Julia and is not exported to C; use LinearMPCObserver with a stationary gain for C export.
Usage ​
MPCComponents.SqKalmanObserver(R1=_sqkf_R1(kf), R2=_sqkf_R2(kf), x0=_sqkf_x0(kf), R0=R1, xR0=sqkf_carrier0(x0, R0), u_dummy=fill(0.0, nu), valid_all=fill(1.0, ny))
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
A | State matrix (nx × nx) | – | fill(0.0, 0, 0) |
B | Input matrix (nx × nu) | – | fill(0.0, 0, 0) |
C | Measurement matrix (ny × nx) | – | fill(0.0, 0, 0) |
D | Feedthrough from the control to the measurements (ny × nu); empty for zero | – | fill(0.0, 0, 0) |
use_valid | Whether the y_valid input exists; without it, every finite measurement is used | – | false |
kf | Set-up filter built from the parameters above; pass build_sqkf(...) to bypass them | – | build_sqkf(..., C=C, D=D) |
nx | State dimension (derived from kf) | – | _sqkf_nx(kf) |
nu | Number of control inputs (derived from kf) | – | _sqkf_nu(kf) |
ny | Number of measurements (derived from kf) | – | _sqkf_ny(kf) |
n_valid | Width of the y_valid input: ny with use_valid, else 0 (derived) | – | (use_valid) ? (ny) : (0) |
has_D | Whether D is nonzero, so that the correction uses the control of the tick (derived from kf) | – | _sqkf_has_D(kf) |
nc | Length of the carriers [x; vec(R)], nx + nx² (derived) | – | nx + nx ^ 2 |
R1 | Process-noise covariance (nx × nx, e.g. diagonal([1e-4, 1e-4])); tunable | – | _sqkf_R1(kf) |
R2 | Measurement-noise covariance (ny × ny); tunable | – | _sqkf_R2(kf) |
x0 | Initial state estimate; tunable, read at initialization | – | _sqkf_x0(kf) |
R0 | Covariance of the initial state estimate (nx × nx), by default R1; tunable, read at initialization | – | R1 |
Connectors ​
y- This connector represents a real signal as an input to a component (RealInput)y_valid- This connector represents a boolean signal as an input to a component (BooleanInput)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)
Variables ​
| Name | Description | Units |
|---|---|---|
xR | Clocked state: the prediction x(t+1|t) and the upper-triangular factor of its covariance (column-major) | – |
xRt | The filtered estimate x(t|t) and the upper-triangular factor of its covariance (column-major) | – |
u_corr | Control used by the correction | – |
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Time-varying Kalman filter in square-root form (LowLevelParticleFilters.jl `SqKalmanFilter`)
estimating the state of the discrete-time linear model
```math
\\begin{aligned}
x(t+1) &= A x(t) + B u(t) + w(t), \\quad w \\sim N(0, R_1) \\\\
y(t) &= C x(t) + D u(t) + e(t), \\quad e \\sim N(0, R_2)
\\end{aligned}
```
run as a clocked component. At every tick the prediction `x(t|t-1)` is corrected with the
measurements that are available, the filtered estimate `x(t|t)` is output as `xhat`, and the
control `u` applied at this tick is used to predict the estimate for the next tick. A measurement
is used when it is finite and, with `use_valid`, when its entry of `y_valid` is true; the others
are left out of the correction (the rows of `C`, `D` and the block of `R2` of the available
measurements are used), so that a sensor that drops out does not bias the estimate. Without
available measurements the estimate is only predicted and its covariance grows.
The covariance is carried as the upper-triangular Cholesky factor `R` (covariance `R'R`), as in
`SqKalmanFilter`, and both updates are QR factorizations, so it stays symmetric and positive
semidefinite in finite precision. The notation is that of LowLevelParticleFilters'
`forward_trajectory`: `x`, `R` are the prediction `x(t|t-1)` and its factor, `xt`, `Rt` the filtered
estimate `x(t|t)` and its factor; `sqkf_covariance` forms the covariance from a carrier.
Measurements must be sampled before this component. With a nonzero `D` the correction uses the
control of the tick, so the control must not be computed from `xhat` of the same tick.
# Tunable parameters
`R1`, `R2`, `x0` and `R0` are ordinary parameters: the covariances are read at every tick, the initial
estimate at initialization. `A`, `B`, `C` and `D` are structural. The filter runs in Julia and is not
exported to C; use `LinearMPCObserver` with a stationary gain for C export.
"""
component SqKalmanObserver@[input clk extends Discrete]
"Sampled measurements; non-finite values are not used"
y = [RealInput@[clk]() for i in 1:ny] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 700, "x2": 0, "y2": 800}}}
}
"Validity of the measurements: y[i] is used when y_valid[i] is true (only with use_valid)"
y_valid = [BooleanInput@[clk]() for i in 1:n_valid] if use_valid {
"Dyad": {"placement": {"diagram": {"x1": 450, "y1": 1000, "x2": 550, "y2": 1100}}}
}
"Control applied at the current tick, used to predict the estimate for the next tick"
u = [RealInput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 200, "x2": 0, "y2": 300}}}
}
"State estimate filtered with the measurements of the current tick, x(t|t)"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 450, "x2": 1100, "y2": 550}}}
}
"State matrix (nx × nx)"
structural parameter A::Real[:, :] = fill(0.0, 0, 0)
"Input matrix (nx × nu)"
structural parameter B::Real[:, :] = fill(0.0, 0, 0)
"Measurement matrix (ny × nx)"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Feedthrough from the control to the measurements (ny × nu); empty for zero"
structural parameter D::Real[:, :] = fill(0.0, 0, 0)
"Whether the y_valid input exists; without it, every finite measurement is used"
structural parameter use_valid::Boolean = false
"Set-up filter built from the parameters above; pass build_sqkf(...) to bypass them"
structural parameter kf::SqKFRef = build_sqkf(A = A, B = B, C = C, D = D)
"State dimension (derived from kf)"
final structural parameter nx::Integer = _sqkf_nx(kf)
"Number of control inputs (derived from kf)"
final structural parameter nu::Integer = _sqkf_nu(kf)
"Number of measurements (derived from kf)"
final structural parameter ny::Integer = _sqkf_ny(kf)
"Width of the y_valid input: ny with use_valid, else 0 (derived)"
final structural parameter n_valid::Integer = use_valid ? ny : 0
"Whether D is nonzero, so that the correction uses the control of the tick (derived from kf)"
final structural parameter has_D::Boolean = _sqkf_has_D(kf)
"Length of the carriers [x; vec(R)], nx + nx² (derived)"
final structural parameter nc::Integer = nx + nx ^ 2
"Process-noise covariance (nx × nx, e.g. diagonal([1e-4, 1e-4])); tunable"
parameter R1::Real[nx, nx] = _sqkf_R1(kf)
"Measurement-noise covariance (ny × ny); tunable"
parameter R2::Real[ny, ny] = _sqkf_R2(kf)
"Initial state estimate; tunable, read at initialization"
parameter x0::Real[nx] = _sqkf_x0(kf)
"Covariance of the initial state estimate (nx × nx), by default R1; tunable, read at initialization"
parameter R0::Real[nx, nx] = R1
"Initial carrier: the prior x0 as the prediction for the first tick and the factor of R0"
final parameter xR0::Real[nc] = sqkf_carrier0(x0, R0)
"Zero placeholder used as the control of the correction when D is zero"
final parameter u_dummy::Real[nu] = fill(0.0, nu)
"Validity used when y_valid is absent: every measurement (nonzero entries are valid)"
final parameter valid_all::Real[ny] = fill(1.0, ny)
"Clocked state: the prediction x(t+1|t) and the upper-triangular factor of its covariance (column-major)"
variable xR::Real[nc]
"The filtered estimate x(t|t) and the upper-triangular factor of its covariance (column-major)"
variable xRt::Real[nc]
"Control used by the correction"
variable u_corr::Real[nu]
relations
if has_D
u_corr = u
else
u_corr = u_dummy
end
initial xR@(clk-1) = xR0
if use_valid
xRt@clk = sqkf_correct(xR@(clk-1), y@clk, y_valid@clk, u_corr@clk, R2, kf)
else
xRt@clk = sqkf_correct(xR@(clk-1), y@clk, valid_all, u_corr@clk, R2, kf)
end
xR@clk = sqkf_predict(xRt@clk, u@clk, R1, kf)
for i in 1:nx
xhat[i] = xRt[i]
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/SqKalmanObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {"control": "fieldset", "contents": ["A", "B", "C", "D"]},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Measurements": {"control": "fieldset", "contents": ["use_valid"]}
}
}
}
}
endFlattened Source
"""
Time-varying Kalman filter in square-root form (LowLevelParticleFilters.jl `SqKalmanFilter`)
estimating the state of the discrete-time linear model
```math
\\begin{aligned}
x(t+1) &= A x(t) + B u(t) + w(t), \\quad w \\sim N(0, R_1) \\\\
y(t) &= C x(t) + D u(t) + e(t), \\quad e \\sim N(0, R_2)
\\end{aligned}
```
run as a clocked component. At every tick the prediction `x(t|t-1)` is corrected with the
measurements that are available, the filtered estimate `x(t|t)` is output as `xhat`, and the
control `u` applied at this tick is used to predict the estimate for the next tick. A measurement
is used when it is finite and, with `use_valid`, when its entry of `y_valid` is true; the others
are left out of the correction (the rows of `C`, `D` and the block of `R2` of the available
measurements are used), so that a sensor that drops out does not bias the estimate. Without
available measurements the estimate is only predicted and its covariance grows.
The covariance is carried as the upper-triangular Cholesky factor `R` (covariance `R'R`), as in
`SqKalmanFilter`, and both updates are QR factorizations, so it stays symmetric and positive
semidefinite in finite precision. The notation is that of LowLevelParticleFilters'
`forward_trajectory`: `x`, `R` are the prediction `x(t|t-1)` and its factor, `xt`, `Rt` the filtered
estimate `x(t|t)` and its factor; `sqkf_covariance` forms the covariance from a carrier.
Measurements must be sampled before this component. With a nonzero `D` the correction uses the
control of the tick, so the control must not be computed from `xhat` of the same tick.
# Tunable parameters
`R1`, `R2`, `x0` and `R0` are ordinary parameters: the covariances are read at every tick, the initial
estimate at initialization. `A`, `B`, `C` and `D` are structural. The filter runs in Julia and is not
exported to C; use `LinearMPCObserver` with a stationary gain for C export.
"""
component SqKalmanObserver
"Sampled measurements; non-finite values are not used"
y = [RealInput@[clk]() for i in 1:ny] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 700, "x2": 0, "y2": 800}}}
}
"Validity of the measurements: y[i] is used when y_valid[i] is true (only with use_valid)"
y_valid = [BooleanInput@[clk]() for i in 1:n_valid] if use_valid {
"Dyad": {"placement": {"diagram": {"x1": 450, "y1": 1000, "x2": 550, "y2": 1100}}}
}
"Control applied at the current tick, used to predict the estimate for the next tick"
u = [RealInput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 200, "x2": 0, "y2": 300}}}
}
"State estimate filtered with the measurements of the current tick, x(t|t)"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 450, "x2": 1100, "y2": 550}}}
}
"State matrix (nx × nx)"
structural parameter A::Real[:, :] = fill(0.0, 0, 0)
"Input matrix (nx × nu)"
structural parameter B::Real[:, :] = fill(0.0, 0, 0)
"Measurement matrix (ny × nx)"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Feedthrough from the control to the measurements (ny × nu); empty for zero"
structural parameter D::Real[:, :] = fill(0.0, 0, 0)
"Whether the y_valid input exists; without it, every finite measurement is used"
structural parameter use_valid::Boolean = false
"Set-up filter built from the parameters above; pass build_sqkf(...) to bypass them"
structural parameter kf::SqKFRef = build_sqkf(A = A, B = B, C = C, D = D)
"State dimension (derived from kf)"
final structural parameter nx::Integer = _sqkf_nx(kf)
"Number of control inputs (derived from kf)"
final structural parameter nu::Integer = _sqkf_nu(kf)
"Number of measurements (derived from kf)"
final structural parameter ny::Integer = _sqkf_ny(kf)
"Width of the y_valid input: ny with use_valid, else 0 (derived)"
final structural parameter n_valid::Integer = use_valid ? ny : 0
"Whether D is nonzero, so that the correction uses the control of the tick (derived from kf)"
final structural parameter has_D::Boolean = _sqkf_has_D(kf)
"Length of the carriers [x; vec(R)], nx + nx² (derived)"
final structural parameter nc::Integer = nx + nx ^ 2
"Process-noise covariance (nx × nx, e.g. diagonal([1e-4, 1e-4])); tunable"
parameter R1::Real[nx, nx] = _sqkf_R1(kf)
"Measurement-noise covariance (ny × ny); tunable"
parameter R2::Real[ny, ny] = _sqkf_R2(kf)
"Initial state estimate; tunable, read at initialization"
parameter x0::Real[nx] = _sqkf_x0(kf)
"Covariance of the initial state estimate (nx × nx), by default R1; tunable, read at initialization"
parameter R0::Real[nx, nx] = R1
"Initial carrier: the prior x0 as the prediction for the first tick and the factor of R0"
final parameter xR0::Real[nc] = sqkf_carrier0(x0, R0)
"Zero placeholder used as the control of the correction when D is zero"
final parameter u_dummy::Real[nu] = fill(0.0, nu)
"Validity used when y_valid is absent: every measurement (nonzero entries are valid)"
final parameter valid_all::Real[ny] = fill(1.0, ny)
"Clocked state: the prediction x(t+1|t) and the upper-triangular factor of its covariance (column-major)"
variable xR::Real[nc]
"The filtered estimate x(t|t) and the upper-triangular factor of its covariance (column-major)"
variable xRt::Real[nc]
"Control used by the correction"
variable u_corr::Real[nu]
relations
if has_D
u_corr = u
else
u_corr = u_dummy
end
initial xR@(clk-1) = xR0
if use_valid
xRt@clk = sqkf_correct(xR@(clk-1), y@clk, y_valid@clk, u_corr@clk, R2, kf)
else
xRt@clk = sqkf_correct(xR@(clk-1), y@clk, valid_all, u_corr@clk, R2, kf)
end
xR@clk = sqkf_predict(xRt@clk, u@clk, R1, kf)
for i in 1:nx
xhat[i] = xRt[i]
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/SqKalmanObserver.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Model": {"control": "fieldset", "contents": ["A", "B", "C", "D"]},
"Noise": {"control": "fieldset", "contents": ["R1", "R2", "x0", "R0"]},
"Measurements": {"control": "fieldset", "contents": ["use_valid"]}
}
}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses