Skip to content

Commit cb13e71

Browse files
committed
Add Ensemble Kalman Filter(EnKF)
1 parent 1ee4cea commit cb13e71

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
"""
2+
3+
Ensemble Kalman Filter localization sample
4+
5+
author: Ryohei Sasaki
6+
7+
"""
8+
9+
import numpy as np
10+
import math
11+
import matplotlib.pyplot as plt
12+
13+
# Simulation parameter
14+
Qsim = np.diag([0.2, np.deg2rad(1.0)])**2
15+
Rsim = np.diag([1.0, np.deg2rad(30.0)])**2
16+
17+
DT = 0.1 # time tick [s]
18+
SIM_TIME = 50.0 # simulation time [s]
19+
MAX_RANGE = 20.0 # maximum observation range
20+
21+
# Ensemble Kalman filter parameter
22+
NP = 20 # Number of Particle
23+
24+
show_animation = True
25+
26+
27+
def calc_input():
28+
v = 1.0 # [m/s]
29+
yawrate = 0.1 # [rad/s]
30+
u = np.array([[v, yawrate]]).T
31+
return u
32+
33+
34+
35+
def observation(xTrue, xd, u, RFID):
36+
37+
xTrue = motion_model(xTrue, u)
38+
39+
z = np.zeros((0, 4))
40+
41+
for i in range(len(RFID[:, 0])):
42+
43+
dx = RFID[i, 0] - xTrue[0, 0]
44+
dy = RFID[i, 1] - xTrue[1, 0]
45+
d = math.sqrt(dx**2 + dy**2)
46+
angle = pi_2_pi(math.atan2(dy, dx) - xTrue[2, 0])
47+
if d <= MAX_RANGE:
48+
dn = d + np.random.randn() * Qsim[0, 0] # add noise
49+
anglen = angle + np.random.randn() * Qsim[1, 1] # add noise
50+
zi = np.array([dn, anglen,RFID[i, 0], RFID[i, 1]])
51+
z = np.vstack((z, zi))
52+
53+
# add noise to input
54+
ud = np.array([[
55+
u[0, 0] + np.random.randn() * Rsim[0, 0],
56+
u[1, 0] + np.random.randn() * Rsim[1, 1]]]).T
57+
58+
xd = motion_model(xd, ud)
59+
return xTrue, z, xd, ud
60+
61+
62+
def motion_model(x, u):
63+
F = np.array([[1.0, 0, 0, 0],
64+
[0, 1.0, 0, 0],
65+
[0, 0, 1.0, 0],
66+
[0, 0, 0, 0]])
67+
68+
B = np.array([[DT * math.cos(x[2, 0]), 0],
69+
[DT * math.sin(x[2, 0]), 0],
70+
[0.0, DT],
71+
[1.0, 0.0]])
72+
x = F.dot(x) + B.dot(u)
73+
74+
return x
75+
76+
77+
def calc_LM_Pos(x, landmarks):
78+
landmarks_pos=np.zeros((2*landmarks.shape[0],1))
79+
for (i,lm) in enumerate(landmarks):
80+
landmarks_pos[2*i] = x[0, 0] + lm[0] * math.cos(x[2, 0] + lm[1]) + np.random.randn() * Qsim[0, 0]/np.sqrt(2)
81+
landmarks_pos[2*i+1] = x[1, 0] + lm[0] * math.sin(x[2, 0] + lm[1]) + np.random.randn() * Qsim[0, 0]/np.sqrt(2)
82+
return landmarks_pos
83+
84+
def calc_covariance(xEst, px):
85+
cov = np.zeros((3, 3))
86+
87+
for i in range(px.shape[1]):
88+
dx = (px[:, i] - xEst)[0:3]
89+
cov += dx.dot(dx.T)
90+
91+
return cov
92+
93+
94+
def enkf_localization(px, xEst, PEst, z, u):
95+
"""
96+
Localization with Ensemble Kalman filter
97+
"""
98+
pz = np.zeros((z.shape[0]*2, NP)) # Particle store of z
99+
for ip in range(NP):
100+
x = np.array([px[:, ip]]).T
101+
102+
# Predict with random input sampling
103+
ud1 = u[0, 0] + np.random.randn() * Rsim[0, 0]
104+
ud2 = u[1, 0] + np.random.randn() * Rsim[1, 1]
105+
ud = np.array([[ud1, ud2]]).T
106+
x = motion_model(x, ud)
107+
px[:, ip] = x[:, 0]
108+
z_pos=calc_LM_Pos(x, z)
109+
pz[:, ip] = z_pos[:, 0]
110+
111+
x_ave=np.mean(px, axis=1)
112+
x_dif=px - np.tile(x_ave,(NP,1)).T
113+
114+
z_ave=np.mean(pz, axis=1)
115+
z_dif=pz - np.tile(z_ave,(NP,1)).T
116+
117+
U = 1/(NP-1)* x_dif @ z_dif.T
118+
V = 1/(NP-1)* z_dif @ z_dif.T
119+
120+
K = U @ np.linalg.inv(V) # Kalman Gain
121+
122+
z_lm_pos = z[:,[2,3]].reshape(-1,)
123+
124+
px_hat=px + K @ (np.tile(z_lm_pos,(NP,1)).T- pz)
125+
126+
xEst=np.average(px_hat, axis=1).reshape(4,1)
127+
pEst=calc_covariance(xEst, px_hat)
128+
129+
return xEst, PEst, px_hat
130+
131+
132+
def plot_covariance_ellipse(xEst, PEst): # pragma: no cover
133+
Pxy = PEst[0:2, 0:2]
134+
eigval, eigvec = np.linalg.eig(Pxy)
135+
136+
if eigval[0] >= eigval[1]:
137+
bigind = 0
138+
smallind = 1
139+
else:
140+
bigind = 1
141+
smallind = 0
142+
143+
t = np.arange(0, 2 * math.pi + 0.1, 0.1)
144+
145+
# eigval[bigind] or eiqval[smallind] were occassionally negative numbers extremely
146+
# close to 0 (~10^-20), catch these cases and set the respective variable to 0
147+
try:
148+
a = math.sqrt(eigval[bigind])
149+
except ValueError:
150+
a = 0
151+
152+
try:
153+
b = math.sqrt(eigval[smallind])
154+
except ValueError:
155+
b = 0
156+
157+
x = [a * math.cos(it) for it in t]
158+
y = [b * math.sin(it) for it in t]
159+
angle = math.atan2(eigvec[bigind, 1], eigvec[bigind, 0])
160+
R = np.array([[math.cos(angle), math.sin(angle)],
161+
[-math.sin(angle), math.cos(angle)]])
162+
fx = R.dot(np.array([[x, y]]))
163+
px = np.array(fx[0, :] + xEst[0, 0]).flatten()
164+
py = np.array(fx[1, :] + xEst[1, 0]).flatten()
165+
plt.plot(px, py, "--r")
166+
167+
168+
def pi_2_pi(angle):
169+
return (angle + math.pi) % (2 * math.pi) - math.pi
170+
171+
def main():
172+
print(__file__ + " start!!")
173+
174+
time = 0.0
175+
176+
# RFID positions [x, y]
177+
RFID = np.array([[10.0, 0.0],
178+
[10.0, 10.0],
179+
[0.0, 15.0],
180+
[-5.0, 20.0]])
181+
182+
# State Vector [x y yaw v]'
183+
xEst = np.zeros((4, 1))
184+
xTrue = np.zeros((4, 1))
185+
PEst = np.eye(4)
186+
187+
px = np.zeros((4, NP)) # Particle store of x
188+
189+
xDR = np.zeros((4, 1)) # Dead reckoning
190+
191+
# history
192+
hxEst = xEst
193+
hxTrue = xTrue
194+
hxDR = xTrue
195+
196+
while SIM_TIME >= time:
197+
time += DT
198+
u = calc_input()
199+
200+
xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)
201+
202+
xEst, PEst, px = enkf_localization(px, xEst, PEst, z, ud)
203+
204+
# store data history
205+
hxEst = np.hstack((hxEst, xEst))
206+
hxDR = np.hstack((hxDR, xDR))
207+
hxTrue = np.hstack((hxTrue, xTrue))
208+
209+
if show_animation:
210+
plt.cla()
211+
212+
for i in range(len(z[:, 0])):
213+
plt.plot([xTrue[0, 0], z[i, 2]], [xTrue[1, 0], z[i, 3]], "-k")
214+
plt.plot(RFID[:, 0], RFID[:, 1], "*k")
215+
plt.plot(px[0, :], px[1, :], ".r")
216+
plt.plot(np.array(hxTrue[0, :]).flatten(),
217+
np.array(hxTrue[1, :]).flatten(), "-b")
218+
plt.plot(np.array(hxDR[0, :]).flatten(),
219+
np.array(hxDR[1, :]).flatten(), "-k")
220+
plt.plot(np.array(hxEst[0, :]).flatten(),
221+
np.array(hxEst[1, :]).flatten(), "-r")
222+
#plot_covariance_ellipse(xEst, PEst)
223+
plt.axis("equal")
224+
plt.grid(True)
225+
plt.pause(0.001)
226+
227+
228+
if __name__ == '__main__':
229+
main()

0 commit comments

Comments
 (0)