# generate the GRW
import numpy as np
import matplotlib.pyplot as plt
1955)
np.random.seed(# time interval and time-step
= 1
T = 50
N = T/N
dt = np.linspace(0,1,N)
t # set parameters
= 1
sig_w = 0.5
sig_v = 1
F = sig_w**2
Q = 1
H = sig_v**2
R # initialize
= 0
x0 = 1
P0 # simulate data
= np.zeros(N)
X = np.zeros(N)
Y = x0
x # loop over time
for j in range(N):
= sig_w*np.random.randn()
w = F*x + w;
x = H*x + sig_v*np.random.randn()
y = x
X[j] = y
Y[j] # plot the GRW
'.')
plt.plot(t, X, t, Y, 'Signal', 'Measurement'])
plt.legend(['t')
plt.xlabel( plt.show()
5 Example 2 - scalar, Gaussian random walk
Suppose that we have measurements of the scalar \(y_k\) from the Gaussian random walk model \[\begin{align} x_{k} & =x_{k-1}+ w_{k-1},\quad & w_{k-1}\sim\mathcal{N}(0,Q), \\ y_{k} & =x_{k}+v_{k},\quad & v_{k}\sim\mathcal{N}(0,R). \end{align}\]
This very basic system is found in many applications where \(x_k\) represents a slowly varying quantity that we measure directly. The process noise, \(w_k,\) takes into account fluctuations in the state \(x_k.\) The measurement noise, \(v_k,\) accounts for measurement instrument errors. The difference with the previous example, is that here \(x\) varies randomly over the time-steps.
We want to estimate the state \(x_k\) over time, taking into account the measurements \(y_k.\) That is, we would like to compute the filtering density,
\[ p({x}_{k}\mid{y}_{1:k})=\mathcal{N}\left({x}_{k}\mid{m}_{k},{P}_{k}\right). \]
We proceed by simply writing down the three stages of the Kalman filter, noting that (as in the previous example) \(F_k =1\) and \(H_k =1\) for this model. We obtain:
- Initialization: Define the prior mean \({x}_0\) and prior covariance \({P}_0.\)
- Prediction: \[\begin{align*} {\hat{x}}_{k} & = {x}_{k-1},\\ \hat{{P}}_{k} & = {P}_{k-1} + {Q}. \end{align*}\]
- Correction: Define \[\begin{align*} {d}_{k} & ={y}_{k}-{\hat{x}}_{k},\quad\textrm{the innovation},\\ {S}_{k} & =\hat{{P}}_{k}+{R},\quad\textrm{the measurement covariance},\\ {K}_{k} & =\hat{{P}}_{k}{S}_{k}^{-1},\quad\textrm{the Kalman gain,} \end{align*}\] then update, \[\begin{align*} {x}_{k} & ={\hat{x}}_{k} + K_k {d}_{k},\\ P_{k} & =\hat{P}_{k}-\frac{\hat{P}_{k}^{2}}{S_{k}}. \end{align*}\]
Note that this formulation is identical with the previous example, except the nature of the measurements, \(y_k.\)
5.1 Implementation of the KF
Here is a straigthforward, matrix-based implementation of the KF that follows exactly the thoeretical formulation above.
For more generality, below we will rewrite the Kalman filter as a class.
# scalar KF
= x0
x = P0
P # Allocate space for estimated position
= np.zeros(N)
estimated_positions = np.zeros(N)
estimated_covariance # Kalman Filter Loop
for k in range(N):
# Predict
= F*x
x = F*P*F + Q
P # Correct
= Y[k] - H*x
d = H*P*H + R
S = P*H / S
K = x + K*d
x = P - K*S*K
P # Store the filtered position and covariance
= x
estimated_positions[k] = P
estimated_covariance[k]
# Plot the true positions, noisy measurements, and the Kalman filter estimates
# and the 2 sigma upper and lower analytic population bounds
= estimated_positions - 1.96*np.sqrt(estimated_covariance)
lower_bound = estimated_positions + 1.96*np.sqrt(estimated_covariance)
upper_bound
= plt.subplots(1, figsize=(10,6))
fig, ax ='C0', alpha=0.2,
ax.fill_between(t, lower_bound, upper_bound, facecolor='2 sigma range')
label='upper left')
ax.legend(loc='True Position', color='green')
ax.plot(t, X, label='Noisy Measurements', color='orange', marker='o')
ax.scatter(t, Y, label='Kalman Filter Estimate', color='blue')
ax.plot(t, estimated_positions, label
'Time Step')
plt.xlabel('Position')
plt.ylabel('Kalman Filter Estimation Over Time')
plt.title(
plt.legend() plt.show()
5.2 Implementation of KF as a class
For more generality, we rewrite the Kalman filter as a class.
class KalmanFilter:
"""
An implementation of the classic Kalman Filter for a SCALAR linear dynamic systems.
The Kalman Filter is an optimal recursive data processing algorithm which
aims to estimate the state of a system from noisy observations.
Attributes:
F (np.ndarray): The state transition matrix.
B (np.ndarray): The control input marix.
H (np.ndarray): The observation matrix.
u (np.ndarray): the control input.
Q (np.ndarray): The process noise covariance matrix.
R (np.ndarray): The measurement noise covariance matrix.
x (np.ndarray): The mean state estimate of the previous step (k-1).
P (np.ndarray): The state covariance of previous step (k-1).
"""
def __init__(self, F=None, B=None, H=None, Q=None, R=None, x0=None, P0=None):
"""
Initializes the Kalman Filter with the necessary matrices and initial state.
Parameters:
F (np.ndarray): The state transition matrix.
B (np.ndarray): The control input marix.
H (np.ndarray): The observation matrix.
u (np.ndarray): the control input.
Q (np.ndarray): The process noise covariance matrix.
R (np.ndarray): The measurement noise covariance matrix.
x0 (np.ndarray): The initial state estimate.
P0 (np.ndarray): The initial state covariance matrix.
"""
self.F = F # State transition matrix
self.B = B # Control input matrix
self.u = u # Control input
self.H = H # Observation matrix
self.Q = Q # Process noise covariance
self.R = R # Measurement noise covariance
self.x = x0 # Initial state estimate
self.P = P0 # Initial estimate covariance
def predict(self):
"""
Predicts the state and the state covariance for the next time step.
"""
self.x = self.F @ self.x + self.B @ self.u
self.P = self.F @ self.P @ self.F.T + self.Q
#return self.x
def update(self, z):
"""
Updates the state estimate with the latest measurement.
Parameters:
z (np.ndarray): The measurement at the current step.
"""
= z - self.H @ self.x
y = self.H @ self.P @ self.H.T + self.R
S = self.P @ self.H.T @ np.linalg.inv(S)
K self.x = self.x + K @ y
= np.eye(self.P.shape[0])
I self.P = (I - K @ self.H) @ self.P
# generate the GRW
import numpy as np
import matplotlib.pyplot as plt
1955)
np.random.seed(# time interval and time-step
= 1
T = 50
N = T/N
dt = np.linspace(0,1,N)
t # set parameters
= 1
sig_w = 0.5
sig_v = 1
F = sig_w**2
Q = 1
H = sig_v**2
R # initialize
= 0
x0 = 1
P0 # simulate data
= np.zeros(N)
X = np.zeros(N)
Y = x0
x # loop over time
for j in range(N):
= Q*np.random.randn()
w = F*x + w;
x = H*x + sig_v*np.random.randn()
y = x
X[j] = y# ready to execute the KF...
Y[j]
# Kalman Filter Initialization
= np.array([[1]]) # State transition matrix
F = np.array([[0]]) # No control input
B = np.array([[0]]) # No control input
u = np.array([[1]]) # Measurement function
H = np.array([[sig_w**2]]) # Process noise covariance
Q = np.array([[sig_v**2]]) # Measurement noise covariance
R = np.array([[0]]) # Initial state estimate
x0 = np.array([[1]]) # Initial estimate covariance
P0
= KalmanFilter(F, B, H, Q, R, x0, P0)
kf
# Allocate space for estimated position
= np.zeros(N)
estimated_positions
# Kalman Filter Loop
for k in range(N):
# Predict
kf.predict() # Correct
= np.array([[Y[k]]])
measurement
kf.update(measurement) # Store the filtered position
= np.ndarray.item(kf.x[0])
estimated_positions[k]
# Plot the true positions, noisy measurements, and the Kalman filter estimates
=(10, 6))
plt.figure(figsize='True Position', color='green')
plt.plot(t, X, label='Noisy Measurements', color='red', marker='o')
plt.scatter(t, Y, label='Kalman Filter Estimate', color='blue')
plt.plot(t, estimated_positions, label
'Time Step')
plt.xlabel('Position')
plt.ylabel('Kalman Filter Estimation Over Time')
plt.title(
plt.legend() plt.show()