Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
96129 views
1
# -*- coding: utf-8 -*-
2
3
"""Copyright 2015 Roger R Labbe Jr.
4
5
FilterPy library.
6
http://github.com/rlabbe/filterpy
7
8
Documentation at:
9
https://filterpy.readthedocs.org
10
11
Supporting book at:
12
https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python
13
14
This is licensed under an MIT license. See the readme.MD file
15
for more information.
16
"""
17
18
19
from __future__ import (absolute_import, division, print_function,
20
unicode_literals)
21
import numpy as np
22
from scipy.linalg import cholesky, qr, pinv
23
from numpy import dot, zeros, eye
24
from filterpy.common import setter, setter_scalar, dot3
25
26
27
28
29
30
class SquareRootKalmanFilter(object):
31
32
def __init__(self, dim_x, dim_z, dim_u=0):
33
""" Create a Kalman filter which uses a square root implementation.
34
This uses the square root of the state covariance matrix, which doubles
35
the numerical precision of the filter, Therebuy reducing the effect
36
of round off errors.
37
38
It is likely that you do not need to use this algorithm; we understand
39
divergence issues very well now. However, if you expect the covariance
40
matrix P to vary by 20 or more orders of magnitude then perhaps this
41
will be useful to you, as the square root will vary by 10 orders
42
of magnitude. From my point of view this is merely a 'reference'
43
algorithm; I have not used this code in real world software. Brown[1]
44
has a useful discussion of when you might need to use the square
45
root form of this algorithm.
46
47
You are responsible for setting the various state variables to
48
reasonable values; the defaults below will not give you a functional
49
filter.
50
51
**Parameters**
52
53
dim_x : int
54
Number of state variables for the Kalman filter. For example, if
55
you are tracking the position and velocity of an object in two
56
dimensions, dim_x would be 4.
57
58
This is used to set the default size of P, Q, and u
59
60
dim_z : int
61
Number of of measurement inputs. For example, if the sensor
62
provides you with position in (x,y), dim_z would be 2.
63
64
dim_u : int (optional)
65
size of the control input, if it is being used.
66
Default value of 0 indicates it is not used.
67
68
69
**Instance Variables:**
70
71
You will have to assign reasonable values to all of these before
72
running the filter. All must have dtype of float
73
74
x : ndarray (dim_x, 1), default = [0,0,0...0]
75
state of the filter
76
77
P : ndarray (dim_x, dim_x), default identity matrix
78
covariance matrix
79
80
Q : ndarray (dim_x, dim_x), default identity matrix
81
Process uncertainty matrix
82
83
R : ndarray (dim_z, dim_z), default identity matrix
84
measurement uncertainty
85
86
H : ndarray (dim_z, dim_x)
87
measurement function
88
89
F : ndarray (dim_x, dim_x)
90
state transistion matrix
91
92
B : ndarray (dim_x, dim_u), default 0
93
control transition matrix
94
95
96
**References**
97
98
[1] Robert Grover Brown. Introduction to Random Signals and Applied
99
Kalman Filtering. Wiley and sons, 2012.
100
"""
101
102
assert dim_x > 0
103
assert dim_z > 0
104
assert dim_u >= 0
105
106
self.dim_x = dim_x
107
self.dim_z = dim_z
108
self.dim_u = dim_u
109
110
self._x = zeros((dim_x,1)) # state
111
self._P = eye(dim_x) # uncertainty covariance
112
self._P1_2 = eye(dim_x) # sqrt uncertainty covariance
113
self._Q = eye(dim_x) # sqrt process uncertainty
114
self._Q1_2 = eye(dim_x) # sqrt process uncertainty
115
self._B = 0 # control transition matrix
116
self._F = 0 # state transition matrix
117
self._H = 0 # Measurement function
118
self._R1_2 = eye(dim_z) # sqrt state uncertainty
119
120
# Residual is computed during the innovation (update) step. We
121
# save it so that in case you want to inspect it for various
122
# purposes
123
self._y = zeros((dim_z, 1))
124
125
# identity matrix.
126
self._I = np.eye(dim_x)
127
128
self._M = np.zeros((dim_z + dim_x, dim_z + dim_x))
129
130
131
def update(self, z, R2=None):
132
"""
133
Add a new measurement (z) to the kalman filter. If z is None, nothing
134
is changed.
135
136
**Parameters**
137
138
z : np.array
139
measurement for this update.
140
141
R2 : np.array, scalar, or None
142
Sqrt of meaaurement noize. Optionally provide to override the
143
measurement noise for this one call, otherwise self.R2 will
144
be used.
145
"""
146
147
if z is None:
148
return
149
150
if R2 is None:
151
R2 = self._R1_2
152
elif np.isscalar(R2):
153
R2 = eye(self.dim_z) * R2
154
155
# rename for convienance
156
dim_z = self.dim_z
157
M = self._M
158
159
M[0:dim_z, 0:dim_z] = R2.T
160
M[dim_z:, 0:dim_z] = dot(self._H, self._P1_2).T
161
M[dim_z:, dim_z:] = self._P1_2.T
162
163
_, S = qr(M)
164
self._K = S[0:dim_z, dim_z:].T
165
N = S[0:dim_z, 0:dim_z].T
166
167
# y = z - Hx
168
# error (residual) between measurement and prediction
169
self._y = z - dot(self._H, self._x)
170
171
# x = x + Ky
172
# predict new x with residual scaled by the kalman gain
173
self._x += dot3(self._K, pinv(N), self._y)
174
self._P1_2 = S[dim_z:, dim_z:].T
175
176
177
178
def predict(self, u=0):
179
""" Predict next position.
180
181
**Parameters**
182
183
u : np.array
184
Optional control vector. If non-zero, it is multiplied by B
185
to create the control input into the system.
186
"""
187
188
# x = Fx + Bu
189
self._x = dot(self._F, self.x) + dot(self._B, u)
190
191
# P = FPF' + Q
192
T,P2 = qr(np.hstack([dot(self._F, self._P1_2), self._Q1_2]).T)
193
self._P1_2 = P2[:self.dim_x, :self.dim_x].T
194
195
196
def residual_of(self, z):
197
""" returns the residual for the given measurement (z). Does not alter
198
the state of the filter.
199
"""
200
201
return z - dot(self._H, self._x)
202
203
204
def measurement_of_state(self, x):
205
""" Helper function that converts a state into a measurement.
206
207
**Parameters**
208
209
x : np.array
210
kalman state vector
211
212
**Returns**
213
214
z : np.array
215
measurement corresponding to the given state
216
"""
217
return dot(self._H, x)
218
219
220
@property
221
def Q(self):
222
""" Process uncertainty"""
223
return dot(self._Q1_2.T, self._Q1_2)
224
225
226
@property
227
def Q1_2(self):
228
""" Sqrt Process uncertainty"""
229
return self._Q1_2
230
231
232
@Q.setter
233
def Q(self, value):
234
self._Q = setter_scalar(value, self.dim_x)
235
self._Q1_2 = cholesky (self._Q, lower=True)
236
237
@property
238
def P(self):
239
""" covariance matrix"""
240
return dot(self._P1_2.T, self._P1_2)
241
242
243
@property
244
def P1_2(self):
245
""" sqrt of covariance matrix"""
246
return self._P1_2
247
248
249
@P.setter
250
def P(self, value):
251
self._P = setter_scalar(value, self.dim_x)
252
self._P1_2 = cholesky(self._P, lower=True)
253
254
255
@property
256
def R(self):
257
""" measurement uncertainty"""
258
return dot(self._R1_2.T, self._R1_2)
259
260
@property
261
def R1_2(self):
262
""" sqrt of measurement uncertainty"""
263
return self._R1_2
264
265
266
@R.setter
267
def R(self, value):
268
self._R = setter_scalar(value, self.dim_z)
269
self._R1_2 = cholesky (self._R, lower=True)
270
271
@property
272
def H(self):
273
""" Measurement function"""
274
return self._H
275
276
277
@H.setter
278
def H(self, value):
279
self._H = setter(value, self.dim_z, self.dim_x)
280
281
282
@property
283
def F(self):
284
""" state transition matrix"""
285
return self._F
286
287
288
@F.setter
289
def F(self, value):
290
self._F = setter(value, self.dim_x, self.dim_x)
291
292
@property
293
def B(self):
294
""" control transition matrix"""
295
return self._B
296
297
298
@B.setter
299
def B(self, value):
300
""" control transition matrix"""
301
self._B = setter (value, self.dim_x, self.dim_u)
302
303
304
@property
305
def x(self):
306
""" filter state vector."""
307
return self._x
308
309
310
@x.setter
311
def x(self, value):
312
self._x = setter(value, self.dim_x, 1)
313
314
@property
315
def K(self):
316
""" Kalman gain """
317
return self._K
318
319
@property
320
def y(self):
321
""" measurement residual (innovation) """
322
return self._y
323
324