import numpy as np
from numpy.linalg import inv
from numpy.linalg.linalg import cholesky
[docs]class SimpleKalman(object):
"""
Kim Chapter 10.2 Battery voltage estimation with measurement noise
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert February 2018
"""
def __init__(self, dt=0.2, initial_state=14, P=6):
"""
Initialize the object
"""
self.dt = dt
self.A = np.array([[1]])
self.H = np.array([[1]])
# Process model covariance
self.Q = np.array([[0]])
# Measurement model covariance
self.R = 4
self.x = np.array([[initial_state]])
self.K = None
# Error covariance initialize
self.P = P * np.eye(1)
self.volt = None
[docs] def next_sample(self, z):
"""
Update the Kalman filter state by inputting a new
scalar measurement. Return the state array as a tuple
Update all other Kalman filter quantities
"""
xp = self.A @ self.x
Pp = self.A @ self.P @ self.A.T + self.Q
self.K = Pp @ self.H.T * inv(self.H @ Pp @ self.H.T + self.R)
self.x = xp + self.K @ (np.array(np.array([[z]]) - self.H @ xp))
self.P = Pp - self.K @ self.H @ Pp
self.volt = self.x[0]
return self.volt
[docs]class PosKalman(object):
"""
Position Estimation from Position and Velocity Measurements
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert May 2018
"""
def __init__(self, Q, R, initial_state=[0, 20], dt=0.1):
"""
Initialize the object
"""
self.dt = dt
self.A = np.array([[1, dt], [0, 1]])
self.H = np.array([[1, 0], [0, 1]])
# Process model covariance
self.Q = Q
# Measurement model covariance
self.R = R
self.x = np.array([[initial_state[0]], [initial_state[1]]])
# Error covariance initialize
self.P = 5 * np.eye(2)
# Initialize state
self.x = np.array([[0.0], [0.0]])
[docs] def next_sample(self, z):
"""
Update the Kalman filter state by inputting a new
scalar measurement. Return the state array as a tuple
Update all other Kalman filter quantities
"""
xp = self.A @ self.x
Pp = self.A @ self.P @ self.A.T + self.Q
self.K = Pp @ self.H.T * inv(self.H @ Pp @ self.H.T + self.R)
self.x = xp + self.K @ (z - self.H @ xp)
self.P = Pp - self.K @ self.H @ Pp
return self.x
[docs]class DvKalman(object):
"""
Kim Chapter 11.2 Velocity from Position Estimation
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert December 2017
"""
def __init__(self, initial_state=[0, 20]):
"""
Initialize the object
"""
self.dt = 0.1
self.A = np.array([[1, self.dt], [0, 1]])
self.H = np.array([[1, 0]])
# Process model covariance
self.Q = np.array([[1, 0], [0, 3]])
# Measurement model covariance
self.R = 10
self.x = np.array([[initial_state[0]], [initial_state[1]]])
# Error covariance initialize
self.P = 5 * np.eye(2)
# Initialize pos and vel
self.pos = 0.0
self.vel = 0.0
[docs] def next_sample(self, z):
"""
Update the Kalman filter state by inputting a new
scalar measurement. Return the state array as a tuple
Update all other Kalman filter quantities
"""
xp = self.A @ self.x
Pp = self.A @ self.P @ self.A.T + self.Q
self.K = Pp @ self.H.T * inv(self.H @ Pp @ self.H.T + self.R)
self.x = xp + self.K @ (np.array([[z]] - self.H @ xp))
self.P = Pp - self.K @ self.H @ Pp
self.pos = self.x[0]
self.vel = self.x[1]
return self.pos, self.vel
[docs]class IntKalman(object):
"""
Kim Chapter 11.4 Position from Velocity Estimation
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert December 2017
"""
def __init__(self, initial_state=[0, 20]):
"""
Initialize the object
"""
self.dt = 0.1
self.A = np.array([[1, self.dt], [0, 1]])
self.H = np.array([[0, 1]])
# Process model covariance
self.Q = np.array([[1, 0], [0, 3]])
# Measurement model covariance
self.R = 10
self.x = np.array([[initial_state[0]], [initial_state[1]]])
# Error covariance initialize
self.P = 5 * np.eye(2)
# Initialize pos and vel
self.pos = 0.0
self.vel = 0.0
[docs] def next_sample(self, z):
"""
Update the Kalman filter state by inputting a new scalar measurement.
Return the state array as a tuple
Update all other Kalman filter quantities
"""
xp = self.A @ self.x
Pp = self.A @ self.P @ self.A.T + self.Q
self.K = Pp @ self.H.T * inv(self.H @ Pp @ self.H.T + self.R)
self.x = xp + self.K @ (np.array([[z]] - self.H @ xp))
self.P = Pp - self.K @ self.H @ Pp
self.pos = self.x[0]
self.vel = self.x[1]
return self.pos, self.vel
[docs]class RadarEKF(object):
"""
Kim Chapter 14.4 Radar Range Tracking
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert December 2017
"""
def __init__(self, dt=0.05, initial_state=[0, 90, 1100]):
"""
Initialize the object
"""
self.dt = dt
self.A = np.eye(3) + self.dt * np.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]])
# Process model covariance
self.Q = np.array([[0, 0, 0], [0, 0.001, 0], [0, 0, 0.001]])
# Measurement model covariance
self.R = np.array([[10]])
self.x = np.array(initial_state)
# Error covariance initialize
self.P = 10 * np.eye(3)
# Initialize pos and vel
self.pos = 0.0
self.vel = 0.0
self.alt = 0.0
[docs] def next_sample(self, z):
"""
Update the Kalman filter state by inputting a new scalar measurement.
Return the state array as a tuple
Update all other Kalman filter quantities
"""
H = self.Hjacob(self.x)
xp = self.A @ self.x
Pp = self.A @ self.P @ self.A.T + self.Q
self.K = Pp @ H.T * inv(H @ Pp @ H.T + self.R)
self.x = xp + self.K @ (np.array([z - self.hx(xp)]))
self.P = Pp - self.K @ H @ Pp
self.pos = self.x[0]
self.vel = self.x[1]
self.alt = self.x[2]
return self.pos, self.vel, self.alt
[docs] def hx(self, xhat):
"""
State vector predicted to slant range
"""
zp = np.sqrt(xhat[0] ** 2 + xhat[2] ** 2)
return zp
[docs] def Hjacob(self, xp):
"""
Jacobian used to linearize the measurement matrix H
given the state vector
"""
H = np.zeros((1, 3))
H[0, 0] = xp[0] / np.sqrt(xp[0] ** 2 + xp[2] ** 2)
H[0, 1] = 0
H[0, 2] = xp[2] / np.sqrt(xp[0] ** 2 + xp[2] ** 2)
return H
[docs]def sigma_points(xm, P, kappa):
"""
Calculate the Sigma Points of an unscented Kalman filter
Mark Wickert December 2017
Translated P. Kim's program from m-code
"""
n = xm.size
Xi = np.zeros((n, 2 * n + 1)) # sigma points = col of Xi
W = np.zeros(2 * n + 1)
Xi[:, 0, None] = xm
W[0] = kappa / (n + kappa)
U = cholesky((n + kappa) * P) # U'*U = (n+kappa)*P
for k in range(n):
Xi[:, k + 1, None] = xm + U[k, None, :].T # row of U
W[k + 1] = 1 / (2 * (n + kappa))
for k in range(n):
Xi[:, n + k + 1, None] = xm - U[k, None, :].T
W[n + k + 1] = 1 / (2 * (n + kappa))
return Xi, W
[docs]def ut(Xi, W, noise_cov=0):
"""
Unscented transformation
Mark Wickert December 2017
Translated P. Kim's program from m-code
"""
n, kmax = Xi.shape
xm = np.zeros((n, 1))
for k in range(kmax):
xm += W[k] * Xi[:, k, None]
xcov = np.zeros((n, n))
for k in range(kmax):
xcov += W[k] * (Xi[:, k, None] - xm) * (Xi[:, k, None] - xm).T
xcov += noise_cov
return xm, xcov
[docs]class RadarUKF(object):
"""
Kim Chapter 15.4 Radar Range Tracking UKF Version
Python > 3.5 is assumed so the operator @ can be used for matrix multiply
Mark Wickert December 2017
"""
def __init__(self, dt=0.05, initial_state=[0, 90, 1100]):
"""
Initialize the object
"""
self.dt = dt
self.n = 3
self.m = 1
# Process model covariance
self.Q = np.array([[0, 0, 0], [0, 0.001, 0], [0, 0, 0.001]])
# Measurement model covariance
self.R = np.array([[10]])
self.x = np.array([initial_state]).T
# Error covariance initialize
self.P = 100 * np.eye(3)
self.K = np.zeros((self.n, 1))
# Initialize pos and vel
self.pos = 0.0
self.vel = 0.0
self.alt = 0.0
[docs] def next_sample(self, z, kappa=0):
"""
Update the Kalman filter state by inputting a new scalar measurement.
Return the state array as a tuple
Update all other Kalman filter quantities
"""
Xi, W = sigma_points(self.x, self.P, 0)
fXi = np.zeros((self.n, 2 * self.n + 1))
for k in range(2 * self.n + 1):
fXi[:, k, None] = self.fx(Xi[:, k, None])
xp, Pp = ut(fXi, W, self.Q)
hXi = np.zeros((self.m, 2 * self.n + 1))
for k in range(2 * self.n + 1):
hXi[:, k, None] = self.hx(fXi[:, k, None])
zp, Pz = ut(hXi, W, self.R)
Pxz = np.zeros((self.n, self.m))
for k in range(2 * self.n + 1):
Pxz += W[k] * (fXi[:, k, None] - xp) @ (hXi[:, k, None] - zp).T
self.K = Pxz * inv(Pz)
self.x = xp + self.K * (z - zp)
self.P = Pp - self.K @ Pz @ self.K.T
self.pos = self.x[0]
self.vel = self.x[1]
self.alt = self.x[2]
return self.pos, self.vel, self.alt
[docs] def fx(self, x):
"""
The function f(x) in Kim
"""
A = np.eye(3) + self.dt * np.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]])
xp = A @ x
return xp
[docs] def hx(self, x):
"""
The range equation r(x1,x3)
"""
yp = np.sqrt(x[0] ** 2 + x[2] ** 2)
return yp