Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Walking through the Kalman Filter code
** author's note: this code is somewhat old. This section needs to be edited; I would not pay a lot of attention to it right now. **
The kalman filter code that we are using is implemented in my Python library FilterPy
. If you are interested in the full implementation of the filter you should look in filterpy\kalman\kalman_filter.py
. In the following I will present a simplified implementation of the same code. The code in the library handles issues that are beyond the scope of this chapter, such as numerical stability and support for the extended Kalman filter, subject of a later chapter.
The code is implemented as the class KalmanFilter
. Some Python programmers are not a fan of object oriented (OO) Python, and eschew classes. I do not intend to enter into that battle other than to say that I have often seen OO abused. Here I use the class to encapsulate the data that is pertinent to the filter so that you do not have to store and pass around a half dozen variables everywhere.
The method __init__()
is used by Python to create the object. Here is the method
More than anything this method exists to document for you what the variable names are in the filter. To do anything useful with this filter you will have to modify most of these values. Some are set to useful values. For example, R
is set to an identity matrix; if you want the diagonals of R
to be 10. you may write (as we did earlier in this chapter) my_filter.R += 10.
.
The names used for each variable matches the math symbology used in this chapter. Thus, self.P
is the covariance matrix, self.x
is the state, and so on.
The predict function implements the predict step of the Kalman equations, which are
The corresponding code is
I haven't discussed the use of NumPy much until now, but this method illustrates the power of that package. We use NumPy's array
class to store our data and perform the linear algebra for our filters. array
implements matrix multiplication using the .dot()
method; if you use *
you will get element-wise multiplication. As a heavy user of linear algebra this design is somewhat distressing as I use matrix multiplication far more often than element-wise multiplication. However, this design is due to historical developments in the library and we must live with it. The Python community has recognized this problem, and in Python 3.5 we will have the @
operator to implement matrix multiplication.
With that in mind, the Python code self.F.dot(self.x)
implements the math expression .
NumPy's array
implements matrix transposition by using the .T
property. Therefore, F.T
is the python implementation of .
The update()
method implements the update equations of the Kalman filter, which are
The corresponding code is:
There are a few more complications in this piece of code compared to predict()
but it should still be quite clear.
The first complication are the lines:
This just lets you deal with missing data in a natural way. It is typical to use None
to indicate the absence of data. If there is no data for an update we skip the update equations. This bit of code means you can write something like:
instead of: z = read_sensor() if z is not None: my_kf.update(z)
Reasonable people will argue whether my choice is cleaner, or obscures the fact that we do not update if the measurement is None
. Having written a lot of avionics code my proclivity is always to do the safe thing. If we pass 'None' into the function I do not want an exception to occur; instead, I want the reasonable thing to happen, which is to just return without doing anything. If you feel that my choice obscures that fact, go ahead and write the explicit if
statement prior to calling update()
and get the best of both worlds.
The next bit of code lets you optionally pass in a value to override R
. It is common for the sensor noise to vary over time; if it does you can pass in the value as the optional parameter R
.
This code will use self.R if you do not provide a value for R
. If you did provide a value, it will check if you provided a scalar (number); if so it constructs a matrix of the correct dimension for you. Otherwise it assumes that you passed in a matrix of the correct dimension.
The rest of the code implements the Kalman filter equations, with one exception. Instead of implementing
it implements the somewhat more complicated form
The reason for this altered equation is that it is more numerically stable than the former equation, at the cost of being a bit more expensive to compute. It is not always possible to find the optimal value for , in which case the former equation will not produce good results because it assumes optimality. The longer reformulation used in the code is derived from more general math that does not assume optimality, and hence provides good results for non-optimal filters (such as when we can not correctly model our measurement error).
Various texts differ as to whether this form of the equation should always be used, or only used when you know you need it. I choose to expend a bit more processing power to ensure stability; if your hardware is very constrained and you are able to prove that the simpler equation is correct for your problem then you might choose to use it instead. Personally, I find that a risky approach and do not recommend it to non-experts. Brown's Introduction to Random Signals and Applied Kalman Filtering [3] discusses this issue in some detail, if you are interested.