Skip to content

Commit c3067cd

Browse files
committed
Added Saver class, many documentation changes
This is an unfortunately large check in. Big change is moved the Saver class to common and made it work with any filter, not just the KalmanFilter class. Along the way many docstrings were modified.
1 parent c8e2810 commit c3067cd

31 files changed

+1709
-1027
lines changed

filterpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
for more information.
1515
"""
1616

17-
__version__ = "1.3.1"
17+
__version__ = "1.3.2"

filterpy/changelog.txt

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
Version 1.3.2
2+
=============
3+
4+
Fixed build error in Python 2.7 due to using print function without importing
5+
it from future.
6+
7+
Added filterpy.common.Saver class, which can save all the attribute of any
8+
filtering class. Replaces KalmanFilter.Saver, which only worked for the
9+
KalmanFilter class.
10+
11+
Added optional parameter specifying a Saver object to be passed into all of the
12+
batch_filter() functions/methods.
13+
14+
Added attribute z to most of the filter classes. This is mostly so the
15+
16+
17+
18+
Changes to documentation - mostly making it more consistent.
19+
20+
121
Version 1.3.1
222
=============
323

@@ -10,7 +30,7 @@ Version 1.3
1030

1131
* #113 added plotting of 3D covariance ellipsoid with plot_3d_covariance
1232
* Fixed bug where multivariate_gaussian accepted negative covariances
13-
* #108 used pylint symbolic names
33+
* #108 used pylint symbolic names
1434
* Got code in compliance with pylint
1535
* Fixed #105 - this was just test code, and it turns out the code as was was correct, so I deleted the second return statement
1636
* #88 fixed HInfinity.batch_filter to not use R
@@ -20,7 +40,7 @@ Version 1.3
2040
Version 1.2.5
2141
=============
2242

23-
#102 - Bug: UKF was using slow code path when using np.subtract.
43+
#102 - Bug: UKF was using slow code path when using np.subtract.
2444

2545

2646
Version 1.2.4
@@ -73,7 +93,7 @@ while performing filtering.
7393

7494
* Fixed bug where likelihood() returned None
7595

76-
* Altered RTS_smoother algorithm to also return the
96+
* Altered RTS_smoother algorithm to also return the
7797
predicted covariances
7898

7999

@@ -89,17 +109,17 @@ Version 0.1.5
89109

90110
* Fix #53: UKF rts_smoother does not use residual functions
91111
* Fixed #54: Comments in multivariate_multiply incorrectly called the
92-
covariance the mean.
112+
covariance the mean.
93113
* Added logpdf to stats. Computes logpdf - mostly a wrapper around
94-
stats.multivariate_normal.logpdf as older versions of that function
114+
stats.multivariate_normal.logpdf as older versions of that function
95115
do not support the allow_singular keyword. But it also flattens out the
96116
vectors for you so you do not have to do anything special with column vectors.
97117

98118
Version 0.1.4
99119
=============
100120

101121
* Added Cubature Kalman filter.
102-
* Bug in Q_continuous_white_noise(). The first term in the matrix should be (dt**3)/3, not (dt**4)/3.
122+
* Bug in Q_continuous_white_noise(). The first term in the matrix should be (dt**3)/3, not (dt**4)/3.
103123
* Added log-likelihood computation to UKF.
104124
* Added simplex points for UKF
105125
* fixed bug in KF matrix size check
@@ -113,7 +133,7 @@ time varying.
113133

114134

115135
* Github issue #40. Fixed behavior of multivariate_gaussian to accept list as
116-
the covariance matrix.
136+
the covariance matrix.
117137

118138

119139
Version 0.1.2
@@ -132,8 +152,8 @@ Version 0.1.1
132152
* Brought docstrings (mostly) into compliance with NumPy documentation style.
133153
This requires installation of numpy doc with
134154
pip install numpydoc
135-
136-
docs\conf.py has been modified to use numpydoc.
155+
156+
docs\conf.py has been modified to use numpydoc.
137157

138158

139159

@@ -149,7 +169,7 @@ I finish the book and flesh out a few points.
149169

150170
Color on this: There are various recusive equations for the fixed point
151171
filter that I have found in various book - Simon, Crassidis, and Grewal.
152-
None seem to work very well. I have code that works pretty good when R
172+
None seem to work very well. I have code that works pretty good when R
153173
is < 0.5 or so, but then the filter diverges when R is larger. I'm not seeing
154174
much in the literature that explains this very well, nor any evidence of
155175
this smoother actually being used in practice. I will give this a bit
@@ -185,12 +205,12 @@ correlated process and measurement noise.
185205

186206
* various bug fixes
187207

188-
208+
189209
Version 0.0.26
190210
==============
191211

192212
* Added likelihood and log-likelihood to the KalmanFilter
193-
class.
213+
class.
194214

195215
* Added an MMAE filter bank class.
196216

@@ -243,8 +263,8 @@ from filterpy.common to filterpy.stats and everything should work.
243263
Version 0.0.21
244264
==============
245265

246-
Added monte_carlo module which contains routines for MCMC - mostly
247-
for particle filtering.
266+
Added monte_carlo module which contains routines for MCMC - mostly
267+
for particle filtering.
248268

249269

250270
Version 0.0.20
@@ -268,7 +288,7 @@ Version 0.0.19
268288
BREAKING CHANGES!!
269289

270290
The unscented kalman filter code has been significantly altered. Your
271-
existing code will no longer run. Sorry, but it had to be done.
291+
existing code will no longer run. Sorry, but it had to be done.
272292

273293
As of version 0.0.18 there were separate classes for the UKF (Julier's)
274294
original formulation, and for the scaled UKF. But they are all the same thing,
@@ -294,7 +314,7 @@ Version 0.0.18
294314
==============
295315

296316
* Added args parameters to Hx and HJacobian of the ExtendedKalmanFilter
297-
class so you can pass additional data to them.
317+
class so you can pass additional data to them.
298318

299319
* Made an exception more human readable by including the size of the
300320
matrix that caused the shape error.
@@ -309,7 +329,7 @@ Version 0.0.16
309329
==============
310330

311331
* Added multivariate_multiply to stats module.
312-
* IMPORTANT: bug fix in the UKF RTS smoother routine.
332+
* IMPORTANT: bug fix in the UKF RTS smoother routine.
313333
* various typo fixes.
314334

315335
Version 0.0.15
@@ -321,8 +341,8 @@ A bunch of small changes and bug fixes. Documentation improvements.
321341
Version 0.0.14
322342
==============
323343

324-
The change to _dt was stupid in 0.0.13 . I put it back to _dt, and
325-
then added an optional dt parameter to the predict() function.
344+
The change to _dt was stupid in 0.0.13 . I put it back to _dt, and
345+
then added an optional dt parameter to the predict() function.
326346

327347

328348
Version 0.0.13
@@ -364,7 +384,7 @@ function call. On the other hand, failures are obsucre. This will be
364384
finalized in few releases.
365385

366386

367-
387+
368388
Version 0.0.9
369389
=============
370390

filterpy/common/helpers.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,162 @@
1616
for more information.
1717
"""
1818

19+
from __future__ import print_function
20+
from collections import defaultdict
21+
import copy
22+
import numpy as np
23+
24+
25+
class Saver(object):
26+
"""
27+
Helper class to save the states of any filter object.
28+
Each time you call save() all of the attributes (state, covariances, etc)
29+
are appended to lists.
30+
31+
Generally you would do this once per epoch - predict/update.
32+
33+
Then, you can access any of the states by using the [] syntax or by
34+
using the . operator.
35+
36+
.. code-block:: Python
37+
38+
my_saver = Saver()
39+
... do some filtering
40+
41+
x = my_saver['x']
42+
x = my_save.x
43+
44+
Either returns a list of all of the state `x` values for the entire
45+
filtering process.
46+
47+
If you want to convert all saved lists into numpy arrays, call to_array().
48+
49+
50+
Parameters
51+
----------
52+
53+
kf : object
54+
any object with a __dict__ attribute, but intended to be one of the
55+
filtering classes
56+
57+
save_current : bool, default=True
58+
save the current state of `kf` when the object is created;
59+
60+
skip_private: bool, default=False
61+
Control skipping any private attribute (anything starting with '_')
62+
Turning this on saves memory, but slows down execution a bit.
63+
64+
skip_callable: bool, default=False
65+
Control skipping any attribute which is a method. Turning this on
66+
saves memory, but slows down execution a bit.
67+
68+
ignore: (str,) tuple of strings
69+
list of keys to ignore.
70+
71+
Examples
72+
--------
73+
74+
.. code-block:: Python
75+
76+
kf = KalmanFilter(...whatever)
77+
# initialize kf here
78+
79+
saver = Saver(kf) # save data for kf filter
80+
for z in zs:
81+
kf.predict()
82+
kf.update(z)
83+
saver.save()
84+
85+
x = np.array(s.x) # get the kf.x state in an np.array
86+
plt.plot(x[:, 0], x[:, 2])
87+
88+
# ... or ...
89+
s.to_array()
90+
plt.plot(s.x[:, 0], s.x[:, 2])
91+
92+
"""
93+
94+
def __init__(self, kf, save_current=False,
95+
skip_private=False,
96+
skip_callable=False,
97+
ignore=()):
98+
""" Construct the save object, optionally saving the current
99+
state of the filter"""
100+
101+
self._kf = kf
102+
self._DL = defaultdict(list)
103+
self._skip_private = skip_private
104+
self._skip_callable = skip_callable
105+
self._ignore = ignore
106+
self._len = 0
107+
108+
if save_current:
109+
self.save()
110+
111+
112+
def save(self):
113+
""" save the current state of the Kalman filter"""
114+
115+
kf = self._kf
116+
v = copy.deepcopy(kf.__dict__)
117+
118+
if self._skip_private:
119+
for key in list(v.keys()):
120+
if key.startswith('_'):
121+
print('deleting', key)
122+
del v[key]
123+
124+
if self._skip_callable:
125+
for key in list(v.keys()):
126+
if callable(v[key]):
127+
del v[key]
128+
129+
for ig in self._ignore:
130+
if ig in v:
131+
del v[ig]
132+
133+
for key in list(v.keys()):
134+
self._DL[key].append(v[key])
135+
136+
self.__dict__.update(self._DL)
137+
self._len += 1
138+
139+
140+
def __getitem__(self, key):
141+
return self._DL[key]
142+
143+
144+
def __len__(self):
145+
return self._len
146+
147+
148+
@property
149+
def keys(self):
150+
""" list of all keys"""
151+
return list(self._DL.keys())
152+
153+
154+
def to_array(self):
155+
"""
156+
Convert all saved attributes from a list to np.array.
157+
158+
This may or may not work - every saved attribute must have the
159+
same shape for every instance. i.e., if `K` changes shape due to `z`
160+
changing shape then the call will raise an exception.
161+
162+
This can also happen if the default initialization in __init__ gives
163+
the variable a different shape then it becomes after a predict/update
164+
cycle.
165+
"""
166+
for key in self.keys:
167+
try:
168+
self.__dict__[key] = np.array(self._DL[key])
169+
except:
170+
# get back to lists so we are in a valid state
171+
self.__dict__.update(self._DL)
172+
173+
raise ValueError("could not convert {} into np.array".format(key))
174+
19175

20176
def runge_kutta4(y, x, dx, f):
21177
"""computes 4th order Runge-Kutta for dy/dx.
@@ -112,3 +268,22 @@ def pprint(label, arr, **kwargs):
112268
"""
113269

114270
print(pretty_str(label, arr), **kwargs)
271+
272+
273+
def reshape_z(z, dim_z, ndim):
274+
""" ensure z is a (dim_z, 1) shaped vector"""
275+
276+
z = np.atleast_2d(z)
277+
if z.shape[1] == dim_z:
278+
z = z.T
279+
280+
if z.shape != (dim_z, 1):
281+
raise ValueError('z must be convertible to shape ({}, 1)'.format(dim_z))
282+
283+
if ndim == 1:
284+
z = z[:, 0]
285+
286+
if ndim == 0:
287+
z = z[0, 0]
288+
289+
return z

0 commit comments

Comments
 (0)