{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Table of Contents](./table_of_contents.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Kalman Filter Math"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#format the book\n",
"import book_format\n",
"book_format.set_style()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you've gotten this far I hope that you are thinking that the Kalman filter's fearsome reputation is somewhat undeserved. Sure, I hand waved some equations away, but I hope implementation has been fairly straightforward for you. The underlying concept is quite straightforward - take two measurements, or a measurement and a prediction, and choose the output to be somewhere between the two. If you believe the measurement more your guess will be closer to the measurement, and if you believe the prediction is more accurate your guess will lie closer to it. That's not rocket science (little joke - it is exactly this math that got Apollo to the moon and back!). \n",
"\n",
"To be honest I have been choosing my problems carefully. For an arbitrary problem designing the Kalman filter matrices can be extremely difficult. I haven't been *too tricky*, though. Equations like Newton's equations of motion can be trivially computed for Kalman filter applications, and they make up the bulk of the kind of problems that we want to solve. \n",
"\n",
"I have illustrated the concepts with code and reasoning, not math. But there are topics that do require more mathematics than I have used so far. This chapter presents the math that you will need for the rest of the book."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modeling a Dynamic System\n",
"\n",
"A *dynamic system* is a physical system whose state (position, temperature, etc) evolves over time. Calculus is the math of changing values, so we use differential equations to model dynamic systems. Some systems cannot be modeled with differential equations, but we will not encounter those in this book.\n",
"\n",
"Modeling dynamic systems is properly the topic of several college courses. To an extent there is no substitute for a few semesters of ordinary and partial differential equations followed by a graduate course in control system theory. If you are a hobbyist, or trying to solve one very specific filtering problem at work you probably do not have the time and/or inclination to devote a year or more to that education.\n",
"\n",
"Fortunately, I can present enough of the theory to allow us to create the system equations for many different Kalman filters. My goal is to get you to the stage where you can read a publication and understand it well enough to implement the algorithms. The background math is deep, but in practice we end up using a few simple techniques. \n",
"\n",
"This is the longest section of pure math in this book. You will need to master everything in this section to understand the Extended Kalman filter (EKF), the most common nonlinear filter. I do cover more modern filters that do not require as much of this math. You can choose to skim now, and come back to this if you decide to learn the EKF.\n",
"\n",
"We need to start by understanding the underlying equations and assumptions that the Kalman filter uses. We are trying to model real world phenomena, so what do we have to consider?\n",
"\n",
"Each physical system has a process. For example, a car traveling at a certain velocity goes so far in a fixed amount of time, and its velocity varies as a function of its acceleration. We describe that behavior with the well known Newtonian equations that we learned in high school.\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"v&=at\\\\\n",
"x &= \\frac{1}{2}at^2 + v_0t + x_0\n",
"\\end{aligned}\n",
"$$\n",
"\n",
"Once we learned calculus we saw them in this form:\n",
"\n",
"$$ \\mathbf v = \\frac{d \\mathbf x}{d t}, \n",
"\\quad \\mathbf a = \\frac{d \\mathbf v}{d t} = \\frac{d^2 \\mathbf x}{d t^2}\n",
"$$\n",
"\n",
"A typical automobile tracking problem would have you compute the distance traveled given a constant velocity or acceleration, as we did in previous chapters. But, of course we know this is not all that is happening. No car travels on a perfect road. There are bumps, wind drag, and hills that raise and lower the speed. The suspension is a mechanical system with friction and imperfect springs.\n",
"\n",
"Perfectly modeling a system is impossible except for the most trivial problems. We are forced to make a simplification. At any time $t$ we say that the true state (such as the position of our car) is the predicted value from the imperfect model plus some unknown *process noise*:\n",
"\n",
"$$\n",
"x(t) = x_{pred}(t) + noise(t)\n",
"$$\n",
"\n",
"This is not meant to imply that $noise(t)$ is a function that we can derive analytically. It is merely a statement of fact - we can always describe the true value as the predicted value plus the process noise. \"Noise\" does not imply random events. If we are tracking a thrown ball in the atmosphere, and our model assumes the ball is in a vacuum, then the effect of air drag is process noise in this context.\n",
"\n",
"In the next section we will learn techniques to convert a set of higher order differential equations into a set of first-order differential equations. After the conversion the model of the system without noise is:\n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax}$$\n",
"\n",
"$\\mathbf A$ is known as the *systems dynamics matrix* as it describes the dynamics of the system. Now we need to model the noise. We will call that $\\mathbf w$, and add it to the equation. \n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax} + \\mathbf w$$\n",
"\n",
"$\\mathbf w$ may strike you as a poor choice for the name, but you will soon see that the Kalman filter assumes *white* noise.\n",
"\n",
"Finally, we need to consider any inputs into the system. We assume an input $\\mathbf u$, and that there exists a linear model that defines how that input changes the system. For example, pressing the accelerator in your car makes it accelerate, and gravity causes balls to fall. Both are control inputs. We will need a matrix $\\mathbf B$ to convert $u$ into the effect on the system. We add that into our equation:\n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax} + \\mathbf{Bu} + \\mathbf{w}$$\n",
"\n",
"And that's it. That is one of the equations that Dr. Kalman set out to solve, and he found an optimal estimator if we assume certain properties of $\\mathbf w$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## State-Space Representation of Dynamic Systems"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We've derived the equation\n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax}+ \\mathbf{Bu} + \\mathbf{w}$$\n",
"\n",
"However, we are not interested in the derivative of $\\mathbf x$, but in $\\mathbf x$ itself. Ignoring the noise for a moment, we want an equation that recusively finds the value of $\\mathbf x$ at time $t_k$ in terms of $\\mathbf x$ at time $t_{k-1}$:\n",
"\n",
"$$\\mathbf x(t_k) = \\mathbf F(\\Delta t)\\mathbf x(t_{k-1}) + \\mathbf B(t_k)\\mathbf u (t_k)$$\n",
"\n",
"Convention allows us to write $\\mathbf x(t_k)$ as $\\mathbf x_k$, which means the \n",
"the value of $\\mathbf x$ at the k$^{th}$ value of $t$.\n",
"\n",
"$$\\mathbf x_k = \\mathbf{Fx}_{k-1} + \\mathbf B_k\\mathbf u_k$$\n",
"\n",
"$\\mathbf F$ is the familiar *state transition matrix*, named due to its ability to transition the state's value between discrete time steps. It is very similar to the system dynamics matrix $\\mathbf A$. The difference is that $\\mathbf A$ models a set of linear differential equations, and is continuous. $\\mathbf F$ is discrete, and represents a set of linear equations (not differential equations) which transitions $\\mathbf x_{k-1}$ to $\\mathbf x_k$ over a discrete time step $\\Delta t$. \n",
"\n",
"Finding this matrix is often quite difficult. The equation $\\dot x = v$ is the simplest possible differential equation and we trivially integrate it as:\n",
"\n",
"$$ \\int\\limits_{x_{k-1}}^{x_k} \\mathrm{d}x = \\int\\limits_{0}^{\\Delta t} v\\, \\mathrm{d}t $$\n",
"$$x_k-x_0 = v \\Delta t$$\n",
"$$x_k = v \\Delta t + x_0$$\n",
"\n",
"This equation is *recursive*: we compute the value of $x$ at time $t$ based on its value at time $t-1$. This recursive form enables us to represent the system (process model) in the form required by the Kalman filter:\n",
"\n",
"$$\\begin{aligned}\n",
"\\mathbf x_k &= \\mathbf{Fx}_{k-1} \\\\\n",
"&= \\begin{bmatrix} 1 & \\Delta t \\\\ 0 & 1\\end{bmatrix}\n",
"\\begin{bmatrix}x_{k-1} \\\\ \\dot x_{k-1}\\end{bmatrix}\n",
"\\end{aligned}$$\n",
"\n",
"We can do that only because $\\dot x = v$ is simplest differential equation possible. Almost all other in physical systems result in more complicated differential equation which do not yield to this approach. \n",
"\n",
"*State-space* methods became popular around the time of the Apollo missions, largely due to the work of Dr. Kalman. The idea is simple. Model a system with a set of $n^{th}$-order differential equations. Convert them into an equivalent set of first-order differential equations. Put them into the vector-matrix form used in the previous section: $\\dot{\\mathbf x} = \\mathbf{Ax} + \\mathbf{Bu}$. Once in this form we use of of several techniques to convert these linear differential equations into the recursive equation:\n",
"\n",
"$$ \\mathbf x_k = \\mathbf{Fx}_{k-1} + \\mathbf B_k\\mathbf u_k$$\n",
"\n",
"Some books call the state transition matrix the *fundamental matrix*. Many use $\\mathbf \\Phi$ instead of $\\mathbf F$. Sources based heavily on control theory tend to use these forms.\n",
"\n",
"These are called *state-space* methods because we are expressing the solution of the differential equations in terms of the system state. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Forming First Order Equations from Higher Order Equations\n",
"\n",
"Many models of physical systems require second or higher order differential equations with control input $u$:\n",
"\n",
"$$a_n \\frac{d^ny}{dt^n} + a_{n-1} \\frac{d^{n-1}y}{dt^{n-1}} + \\dots + a_2 \\frac{d^2y}{dt^2} + a_1 \\frac{dy}{dt} + a_0 = u$$\n",
"\n",
"State-space methods require first-order equations. Any higher order system of equations can be reduced to first-order by defining extra variables for the derivatives and then solving. \n",
"\n",
"\n",
"Let's do an example. Given the system $\\ddot{x} - 6\\dot x + 9x = u$ find the equivalent first order equations. I've used the dot notation for the time derivatives for clarity.\n",
"\n",
"The first step is to isolate the highest order term onto one side of the equation.\n",
"\n",
"$$\\ddot{x} = 6\\dot x - 9x + u$$\n",
"\n",
"We define two new variables:\n",
"\n",
"$$\\begin{aligned} x_1(u) &= x \\\\\n",
"x_2(u) &= \\dot x\n",
"\\end{aligned}$$\n",
"\n",
"Now we will substitute these into the original equation and solve. The solution yields a set of first-order equations in terms of these new variables. It is conventional to drop the $(u)$ for notational convenience.\n",
"\n",
"We know that $\\dot x_1 = x_2$ and that $\\dot x_2 = \\ddot{x}$. Therefore\n",
"\n",
"$$\\begin{aligned}\n",
"\\dot x_2 &= \\ddot{x} \\\\\n",
" &= 6\\dot x - 9x + t\\\\\n",
" &= 6x_2-9x_1 + t\n",
"\\end{aligned}$$\n",
"\n",
"Therefore our first-order system of equations is\n",
"\n",
"$$\\begin{aligned}\\dot x_1 &= x_2 \\\\\n",
"\\dot x_2 &= 6x_2-9x_1 + t\\end{aligned}$$\n",
"\n",
"If you practice this a bit you will become adept at it. Isolate the highest term, define a new variable and its derivatives, and then substitute."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### First Order Differential Equations In State-Space Form\n",
"\n",
"Substituting the newly defined variables from the previous section:\n",
"\n",
"$$\\frac{dx_1}{dt} = x_2,\\, \n",
"\\frac{dx_2}{dt} = x_3, \\, ..., \\, \n",
"\\frac{dx_{n-1}}{dt} = x_n$$\n",
"\n",
"into the first order equations yields: \n",
"\n",
"$$\\frac{dx_n}{dt} = \\frac{1}{a_n}\\sum\\limits_{i=0}^{n-1}a_ix_{i+1} + \\frac{1}{a_n}u\n",
"$$\n",
"\n",
"\n",
"Using vector-matrix notation we have:\n",
"\n",
"$$\\begin{bmatrix}\\frac{dx_1}{dt} \\\\ \\frac{dx_2}{dt} \\\\ \\vdots \\\\ \\frac{dx_n}{dt}\\end{bmatrix} = \n",
"\\begin{bmatrix}\\dot x_1 \\\\ \\dot x_2 \\\\ \\vdots \\\\ \\dot x_n\\end{bmatrix}=\n",
"\\begin{bmatrix}0 & 1 & 0 &\\cdots & 0 \\\\\n",
"0 & 0 & 1 & \\cdots & 0 \\\\\n",
"\\vdots & \\vdots & \\vdots & \\ddots & \\vdots \\\\\n",
"-\\frac{a_0}{a_n} & -\\frac{a_1}{a_n} & -\\frac{a_2}{a_n} & \\cdots & -\\frac{a_{n-1}}{a_n}\\end{bmatrix}\n",
"\\begin{bmatrix}x_1 \\\\ x_2 \\\\ \\vdots \\\\ x_n\\end{bmatrix} + \n",
"\\begin{bmatrix}0 \\\\ 0 \\\\ \\vdots \\\\ \\frac{1}{a_n}\\end{bmatrix}u$$\n",
"\n",
"which we then write as $\\dot{\\mathbf x} = \\mathbf{Ax} + \\mathbf{B}u$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Finding the Fundamental Matrix for Time Invariant Systems\n",
"\n",
"We express the system equations in state-space form with\n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax}$$\n",
"\n",
"where $\\mathbf A$ is the system dynamics matrix, and want to find the *fundamental matrix* $\\mathbf F$ that propagates the state $\\mathbf x$ over the interval $\\Delta t$ with the equation\n",
"\n",
"$$\\begin{aligned}\n",
"\\mathbf x(t_k) = \\mathbf F(\\Delta t)\\mathbf x(t_{k-1})\\end{aligned}$$\n",
"\n",
"In other words, $\\mathbf A$ is a set of continuous differential equations, and we need $\\mathbf F$ to be a set of discrete linear equations that computes the change in $\\mathbf A$ over a discrete time step.\n",
"\n",
"It is conventional to drop the $t_k$ and $(\\Delta t)$ and use the notation\n",
"\n",
"$$\\mathbf x_k = \\mathbf {Fx}_{k-1}$$\n",
"\n",
"Broadly speaking there are three common ways to find this matrix for Kalman filters. The technique most often used is the matrix exponential. Linear Time Invariant Theory, also known as LTI System Theory, is a second technique. Finally, there are numerical techniques. You may know of others, but these three are what you will most likely encounter in the Kalman filter literature and praxis."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The Matrix Exponential\n",
"\n",
"The solution to the equation $\\frac{dx}{dt} = kx$ can be found by:\n",
"\n",
"$$\\begin{gathered}\\frac{dx}{dt} = kx \\\\\n",
"\\frac{dx}{x} = k\\, dt \\\\\n",
"\\int \\frac{1}{x}\\, dx = \\int k\\, dt \\\\\n",
"\\log x = kt + c \\\\\n",
"x = e^{kt+c} \\\\\n",
"x = e^ce^{kt} \\\\\n",
"x = c_0e^{kt}\\end{gathered}$$\n",
"\n",
"Using similar math, the solution to the first-order equation \n",
"\n",
"$$\\dot{\\mathbf x} = \\mathbf{Ax} ,\\, \\, \\, \\mathbf x(0) = \\mathbf x_0$$\n",
"\n",
"where $\\mathbf A$ is a constant matrix, is\n",
"\n",
"$$\\mathbf x = e^{\\mathbf At}\\mathbf x_0$$\n",
"\n",
"Substituting $F = e^{\\mathbf At}$, we can write \n",
"\n",
"$$\\mathbf x_k = \\mathbf F\\mathbf x_{k-1}$$\n",
"\n",
"which is the form we are looking for! We have reduced the problem of finding the fundamental matrix to one of finding the value for $e^{\\mathbf At}$.\n",
"\n",
"$e^{\\mathbf At}$ is known as the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential). It can be computed with this power series:\n",
"\n",
"$$e^{\\mathbf At} = \\mathbf{I} + \\mathbf{A}t + \\frac{(\\mathbf{A}t)^2}{2!} + \\frac{(\\mathbf{A}t)^3}{3!} + ... $$\n",
"\n",
"That series is found by doing a Taylor series expansion of $e^{\\mathbf At}$, which I will not cover here.\n",
"\n",
"Let's use this to find the solution to Newton's equations. Using $v$ as a substitution for $\\dot x$, and assuming constant velocity we get the linear matrix-vector form \n",
"\n",
"$$\\begin{bmatrix}\\dot x \\\\ \\dot v\\end{bmatrix} =\\begin{bmatrix}0&1\\\\0&0\\end{bmatrix} \\begin{bmatrix}x \\\\ v\\end{bmatrix}$$\n",
"\n",
"This is a first order differential equation, so we can set $\\mathbf{A}=\\begin{bmatrix}0&1\\\\0&0\\end{bmatrix}$ and solve the following equation. I have substituted the interval $\\Delta t$ for $t$ to emphasize that the fundamental matrix is discrete:\n",
"\n",
"$$\\mathbf F = e^{\\mathbf A\\Delta t} = \\mathbf{I} + \\mathbf A\\Delta t + \\frac{(\\mathbf A\\Delta t)^2}{2!} + \\frac{(\\mathbf A\\Delta t)^3}{3!} + ... $$\n",
"\n",
"If you perform the multiplication you will find that $\\mathbf{A}^2=\\begin{bmatrix}0&0\\\\0&0\\end{bmatrix}$, which means that all higher powers of $\\mathbf{A}$ are also $\\mathbf{0}$. Thus we get an exact answer without an infinite number of terms:\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"\\mathbf F &=\\mathbf{I} + \\mathbf A \\Delta t + \\mathbf{0} \\\\\n",
"&= \\begin{bmatrix}1&0\\\\0&1\\end{bmatrix} + \\begin{bmatrix}0&1\\\\0&0\\end{bmatrix}\\Delta t\\\\\n",
"&= \\begin{bmatrix}1&\\Delta t\\\\0&1\\end{bmatrix}\n",
"\\end{aligned}$$\n",
"\n",
"We plug this into $\\mathbf x_k= \\mathbf{Fx}_{k-1}$ to get\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
"x_k &=\\begin{bmatrix}1&\\Delta t\\\\0&1\\end{bmatrix}x_{k-1}\n",
"\\end{aligned}$$\n",
"\n",
"You will recognize this as the matrix we derived analytically for the constant velocity Kalman filter in the **Multivariate Kalman Filter** chapter.\n",
"\n",
"SciPy's linalg module includes a routine `expm()` to compute the matrix exponential. It does not use the Taylor series method, but the [Padé Approximation](https://en.wikipedia.org/wiki/Pad%C3%A9_approximant). There are many (at least 19) methods to computed the matrix exponential, and all suffer from numerical difficulties[1]. You should be aware of the problems, especially when $\\mathbf A$ is large. If you search for \"pade approximation matrix exponential\" you will find many publications devoted to this problem. \n",
"\n",
"In practice this may not be of concern to you as for the Kalman filter we normally just take the first two terms of the Taylor series. But don't assume my treatment of the problem is complete and run off and try to use this technique for other problem without doing a numerical analysis of the performance of this technique. Interestingly, one of the favored ways of solving $e^{\\mathbf At}$ is to use a generalized ode solver. In other words, they do the opposite of what we do - turn $\\mathbf A$ into a set of differential equations, and then solve that set using numerical techniques! \n",
"\n",
"Here is an example of using `expm()` to solve $e^{\\mathbf At}$."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1. , 0.1],\n",
" [0. , 1. ]])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import numpy as np\n",
"from scipy.linalg import expm\n",
"\n",
"dt = 0.1\n",
"A = np.array([[0, 1], \n",
" [0, 0]])\n",
"expm(A*dt)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Time Invariance\n",
"\n",
"If the behavior of the system depends on time we can say that a dynamic system is described by the first-order differential equation\n",
"\n",
"$$ g(t) = \\dot x$$\n",
"\n",
"However, if the system is *time invariant* the equation is of the form:\n",
"\n",
"$$ f(x) = \\dot x$$\n",
"\n",
"What does *time invariant* mean? Consider a home stereo. If you input a signal $x$ into it at time $t$, it will output some signal $f(x)$. If you instead perform the input at time $t + \\Delta t$ the output signal will be the same $f(x)$, shifted in time.\n",
"\n",
"A counter-example is $x(t) = \\sin(t)$, with the system $f(x) = t\\, x(t) = t \\sin(t)$. This is not time invariant; the value will be different at different times due to the multiplication by t. An aircraft is not time invariant. If you make a control input to the aircraft at a later time its behavior will be different because it will have burned fuel and thus lost weight. Lower weight results in different behavior.\n",
"\n",
"We can solve these equations by integrating each side. I demonstrated integrating the time invariant system $v = \\dot x$ above. However, integrating the time invariant equation $\\dot x = f(x)$ is not so straightforward. Using the *separation of variables* techniques we divide by $f(x)$ and move the $dt$ term to the right so we can integrate each side:\n",
"\n",
"$$\\begin{gathered}\n",
"\\frac{dx}{dt} = f(x) \\\\\n",
"\\int^x_{x_0} \\frac{1}{f(x)} dx = \\int^t_{t_0} dt\n",
"\\end{gathered}$$\n",
"\n",
"If we let $F(x) = \\int \\frac{1}{f(x)} dx$ we get\n",
"\n",
"$$F(x) - F(x_0) = t-t_0$$\n",
"\n",
"We then solve for x with\n",
"\n",
"$$\\begin{gathered}\n",
"F(x) = t - t_0 + F(x_0) \\\\\n",
"x = F^{-1}[t-t_0 + F(x_0)]\n",
"\\end{gathered}$$\n",
"\n",
"In other words, we need to find the inverse of $F$. This is not trivial, and a significant amount of coursework in a STEM education is devoted to finding tricky, analytic solutions to this problem. \n",
"\n",
"However, they are tricks, and many simple forms of $f(x)$ either have no closed form solution or pose extreme difficulties. Instead, the practicing engineer turns to state-space methods to find approximate solutions.\n",
"\n",
"The advantage of the matrix exponential is that we can use it for any arbitrary set of differential equations which are *time invariant*. However, we often use this technique even when the equations are not time invariant. As an aircraft flies it burns fuel and loses weight. However, the weight loss over one second is negligible, and so the system is nearly linear over that time step. Our answers will still be reasonably accurate so long as the time step is short."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Example: Mass-Spring-Damper Model\n",
"\n",
"Suppose we wanted to track the motion of a weight on a spring and connected to a damper, such as an automobile's suspension. The equation for the motion with $m$ being the mass, $k$ the spring constant, and $c$ the damping force, under some input $u$ is \n",
"\n",
"$$m\\frac{d^2x}{dt^2} + c\\frac{dx}{dt} +kx = u$$\n",
"\n",
"For notational convenience I will write that as\n",
"\n",
"$$m\\ddot x + c\\dot x + kx = u$$\n",
"\n",
"I can turn this into a system of first order equations by setting $x_1(t)=x(t)$, and then substituting as follows:\n",
"\n",
"$$\\begin{aligned}\n",
"x_1 &= x \\\\\n",
"x_2 &= \\dot x_1 \\\\\n",
"\\dot x_2 &= \\ddot x_1 = \\ddot x\n",
"\\end{aligned}$$\n",
"\n",
"As is common I dropped the $(t)$ for notational convenience. This gives the equation\n",
"\n",
"$$m\\dot x_2 + c x_2 +kx_1 = u$$\n",
"\n",
"Solving for $\\dot x_2$ we get a first order equation:\n",
"\n",
"$$\\dot x_2 = -\\frac{c}{m}x_2 - \\frac{k}{m}x_1 + \\frac{1}{m}u$$\n",
"\n",
"We put this into matrix form:\n",
"\n",
"$$\\begin{bmatrix} \\dot x_1 \\\\ \\dot x_2 \\end{bmatrix} = \n",
"\\begin{bmatrix}0 & 1 \\\\ -k/m & -c/m \\end{bmatrix}\n",
"\\begin{bmatrix} x_1 \\\\ x_2 \\end{bmatrix} + \n",
"\\begin{bmatrix} 0 \\\\ 1/m \\end{bmatrix}u$$\n",
"\n",
"Now we use the matrix exponential to find the state transition matrix:\n",
"\n",
"$$\\Phi(t) = e^{\\mathbf At} = \\mathbf{I} + \\mathbf At + \\frac{(\\mathbf At)^2}{2!} + \\frac{(\\mathbf At)^3}{3!} + ... $$\n",
"\n",
"The first two terms give us\n",
"\n",
"$$\\mathbf F = \\begin{bmatrix}1 & t \\\\ -(k/m) t & 1-(c/m) t \\end{bmatrix}$$\n",
"\n",
"This may or may not give you enough precision. You can easily check this by computing $\\frac{(\\mathbf At)^2}{2!}$ for your constants and seeing how much this matrix contributes to the results."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Linear Time Invariant Theory\n",
"\n",
"[*Linear Time Invariant Theory*](https://en.wikipedia.org/wiki/LTI_system_theory), also known as LTI System Theory, gives us a way to find $\\Phi$ using the inverse Laplace transform. You are either nodding your head now, or completely lost. I will not be using the Laplace transform in this book. LTI system theory tells us that \n",
"\n",
"$$ \\Phi(t) = \\mathcal{L}^{-1}[(s\\mathbf{I} - \\mathbf{A})^{-1}]$$\n",
"\n",
"I have no intention of going into this other than to say that the Laplace transform $\\mathcal{L}$ converts a signal into a space $s$ that excludes time, but finding a solution to the equation above is non-trivial. If you are interested, the Wikipedia article on LTI system theory provides an introduction. I mention LTI because you will find some literature using it to design the Kalman filter matrices for difficult problems. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Numerical Solutions\n",
"\n",
"Finally, there are numerical techniques to find $\\mathbf F$. As filters get larger finding analytical solutions becomes very tedious (though packages like SymPy make it easier). C. F. van Loan [2] has developed a technique that finds both $\\Phi$ and $\\mathbf Q$ numerically. Given the continuous model\n",
"\n",
"$$ \\dot x = Ax + Gw$$\n",
"\n",
"where $w$ is the unity white noise, van Loan's method computes both $\\mathbf F_k$ and $\\mathbf Q_k$.\n",
" \n",
"I have implemented van Loan's method in `FilterPy`. You may use it as follows:\n",
"\n",
"```python\n",
"from filterpy.common import van_loan_discretization\n",
"\n",
"A = np.array([[0., 1.], [-1., 0.]])\n",
"G = np.array([[0.], [2.]]) # white noise scaling\n",
"F, Q = van_loan_discretization(A, G, dt=0.1)\n",
"```\n",
" \n",
"In the section *Numeric Integration of Differential Equations* I present alternative methods which are very commonly used in Kalman filtering."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Design of the Process Noise Matrix\n",
"\n",
"In general the design of the $\\mathbf Q$ matrix is among the most difficult aspects of Kalman filter design. This is due to several factors. First, the math requires a good foundation in signal theory. Second, we are trying to model the noise in something for which we have little information. Consider trying to model the process noise for a thrown baseball. We can model it as a sphere moving through the air, but that leaves many unknown factors - ball rotation and spin decay, the coefficient of drag of a ball with stitches, the effects of wind and air density, and so on. We develop the equations for an exact mathematical solution for a given process model, but since the process model is incomplete the result for $\\mathbf Q$ will also be incomplete. This has a lot of ramifications for the behavior of the Kalman filter. If $\\mathbf Q$ is too small then the filter will be overconfident in its prediction model and will diverge from the actual solution. If $\\mathbf Q$ is too large than the filter will be unduly influenced by the noise in the measurements and perform sub-optimally. In practice we spend a lot of time running simulations and evaluating collected data to try to select an appropriate value for $\\mathbf Q$. But let's start by looking at the math.\n",
"\n",
"\n",
"Let's assume a kinematic system - some system that can be modeled using Newton's equations of motion. We can make a few different assumptions about this process. \n",
"\n",
"We have been using a process model of\n",
"\n",
"$$ \\dot{\\mathbf x} = \\mathbf{Ax} + \\mathbf{Bu} + \\mathbf{w}$$\n",
"\n",
"where $\\mathbf{w}$ is the process noise. Kinematic systems are *continuous* - their inputs and outputs can vary at any arbitrary point in time. However, our Kalman filters are *discrete* (there are continuous forms for Kalman filters, but we do not cover them in this book). We sample the system at regular intervals. Therefore we must find the discrete representation for the noise term in the equation above. This depends on what assumptions we make about the behavior of the noise. We will consider two different models for the noise."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Continuous White Noise Model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We model kinematic systems using Newton's equations. We have either used position and velocity, or position, velocity, and acceleration as the models for our systems. There is nothing stopping us from going further - we can model jerk, jounce, snap, and so on. We don't do that normally because adding terms beyond the dynamics of the real system degrades the estimate. \n",
"\n",
"Let's say that we need to model the position, velocity, and acceleration. We can then assume that acceleration is constant for each discrete time step. Of course, there is process noise in the system and so the acceleration is not actually constant. The tracked object will alter the acceleration over time due to external, unmodeled forces. In this section we will assume that the acceleration changes by a continuous time zero-mean white noise $w(t)$. In other words, we are assuming that the small changes in velocity average to 0 over time (zero-mean). \n",
"\n",
"Since the noise is changing continuously we will need to integrate to get the discrete noise for the discretization interval that we have chosen. We will not prove it here, but the equation for the discretization of the noise is\n",
"\n",
"$$\\mathbf Q = \\int_0^{\\Delta t} \\mathbf F(t)\\mathbf{Q_c}\\mathbf F^\\mathsf{T}(t) dt$$\n",
"\n",
"where $\\mathbf{Q_c}$ is the continuous noise. The general reasoning should be clear. $\\mathbf F(t)\\mathbf{Q_c}\\mathbf F^\\mathsf{T}(t)$ is a projection of the continuous noise based on our process model $\\mathbf F(t)$ at the instant $t$. We want to know how much noise is added to the system over a discrete intervat $\\Delta t$, so we integrate this expression over the interval $[0, \\Delta t]$. \n",
"\n",
"We know the fundamental matrix for Newtonian systems is\n",
"\n",
"$$F = \\begin{bmatrix}1 & \\Delta t & {\\Delta t}^2/2 \\\\ 0 & 1 & \\Delta t\\\\ 0& 0& 1\\end{bmatrix}$$\n",
"\n",
"We define the continuous noise as \n",
"\n",
"$$\\mathbf{Q_c} = \\begin{bmatrix}0&0&0\\\\0&0&0\\\\0&0&1\\end{bmatrix} \\Phi_s$$\n",
"\n",
"where $\\Phi_s$ is the spectral density of the white noise. This can be derived, but is beyond the scope of this book. See any standard text on stochastic processes for the details. In practice we often do not know the spectral density of the noise, and so this turns into an \"engineering\" factor - a number we experimentally tune until our filter performs as we expect. You can see that the matrix that $\\Phi_s$ is multiplied by effectively assigns the power spectral density to the acceleration term. This makes sense; we assume that the system has constant acceleration except for the variations caused by noise. The noise alters the acceleration.\n",
"\n",
"We could carry out these computations ourselves, but I prefer using SymPy to solve the equation.\n",
"\n",
"$$\\mathbf{Q_c} = \\begin{bmatrix}0&0&0\\\\0&0&0\\\\0&0&1\\end{bmatrix} \\Phi_s$$\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\frac{\\Delta{t}^{5}}{20} & \\frac{\\Delta{t}^{4}}{8} & \\frac{\\Delta{t}^{3}}{6}\\\\\\frac{\\Delta{t}^{4}}{8} & \\frac{\\Delta{t}^{3}}{3} & \\frac{\\Delta{t}^{2}}{2}\\\\\\frac{\\Delta{t}^{3}}{6} & \\frac{\\Delta{t}^{2}}{2} & \\Delta{t}\\end{matrix}\\right] \\Phi_s$"
],
"text/plain": [
"⎡ 5 4 3⎤ \n",
"⎢\\Delta{t} \\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ────────── ──────────⎥ \n",
"⎢ 20 8 6 ⎥ \n",
"⎢ ⎥ \n",
"⎢ 4 3 2⎥ \n",
"⎢\\Delta{t} \\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ────────── ──────────⎥⋅\\Phiₛ\n",
"⎢ 8 3 2 ⎥ \n",
"⎢ ⎥ \n",
"⎢ 3 2 ⎥ \n",
"⎢\\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ────────── \\Delta{t} ⎥ \n",
"⎣ 6 2 ⎦ "
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import sympy\n",
"from sympy import (init_printing, Matrix, MatMul, \n",
" integrate, symbols)\n",
"\n",
"init_printing(use_latex='mathjax')\n",
"dt, phi = symbols('\\Delta{t} \\Phi_s')\n",
"F_k = Matrix([[1, dt, dt**2/2],\n",
" [0, 1, dt],\n",
" [0, 0, 1]])\n",
"Q_c = Matrix([[0, 0, 0],\n",
" [0, 0, 0],\n",
" [0, 0, 1]])*phi\n",
"\n",
"Q = integrate(F_k * Q_c * F_k.T, (dt, 0, dt))\n",
"\n",
"# factor phi out of the matrix to make it more readable\n",
"Q = Q / phi\n",
"MatMul(Q, phi)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For completeness, let us compute the equations for the 0th order and 1st order equations."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0th order discrete process noise\n"
]
},
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\Delta{t} \\Phi_s\\end{matrix}\\right]$"
],
"text/plain": [
"[\\Delta{t}⋅\\Phiₛ]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"F_k = Matrix([[1]])\n",
"Q_c = Matrix([[phi]])\n",
"\n",
"print('0th order discrete process noise')\n",
"integrate(F_k*Q_c*F_k.T,(dt, 0, dt))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1st order discrete process noise\n"
]
},
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\frac{\\Delta{t}^{3}}{3} & \\frac{\\Delta{t}^{2}}{2}\\\\\\frac{\\Delta{t}^{2}}{2} & \\Delta{t}\\end{matrix}\\right] \\Phi_s$"
],
"text/plain": [
"⎡ 3 2⎤ \n",
"⎢\\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ──────────⎥ \n",
"⎢ 3 2 ⎥ \n",
"⎢ ⎥⋅\\Phiₛ\n",
"⎢ 2 ⎥ \n",
"⎢\\Delta{t} ⎥ \n",
"⎢────────── \\Delta{t} ⎥ \n",
"⎣ 2 ⎦ "
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"F_k = Matrix([[1, dt],\n",
" [0, 1]])\n",
"Q_c = Matrix([[0, 0],\n",
" [0, 1]]) * phi\n",
"\n",
"Q = integrate(F_k * Q_c * F_k.T, (dt, 0, dt))\n",
"\n",
"print('1st order discrete process noise')\n",
"# factor phi out of the matrix to make it more readable\n",
"Q = Q / phi\n",
"MatMul(Q, phi)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Piecewise White Noise Model\n",
"\n",
"Another model for the noise assumes that the that highest order term (say, acceleration) is constant for the duration of each time period, but differs for each time period, and each of these is uncorrelated between time periods. In other words there is a discontinuous jump in acceleration at each time step. This is subtly different than the model above, where we assumed that the last term had a continuously varying noisy signal applied to it. \n",
"\n",
"We will model this as\n",
"\n",
"$$f(x)=Fx+\\Gamma w$$\n",
"\n",
"where $\\Gamma$ is the *noise gain* of the system, and $w$ is the constant piecewise acceleration (or velocity, or jerk, etc). \n",
"\n",
"Let's start by looking at a first order system. In this case we have the state transition function\n",
"\n",
"$$\\mathbf{F} = \\begin{bmatrix}1&\\Delta t \\\\ 0& 1\\end{bmatrix}$$\n",
"\n",
"In one time period, the change in velocity will be $w(t)\\Delta t$, and the change in position will be $w(t)\\Delta t^2/2$, giving us\n",
"\n",
"$$\\Gamma = \\begin{bmatrix}\\frac{1}{2}\\Delta t^2 \\\\ \\Delta t\\end{bmatrix}$$\n",
"\n",
"The covariance of the process noise is then\n",
"\n",
"$$Q = \\mathbb E[\\Gamma w(t) w(t) \\Gamma^\\mathsf{T}] = \\Gamma\\sigma^2_v\\Gamma^\\mathsf{T}$$.\n",
"\n",
"We can compute that with SymPy as follows"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\frac{\\Delta{t}^{4}}{4} & \\frac{\\Delta{t}^{3}}{2}\\\\\\frac{\\Delta{t}^{3}}{2} & \\Delta{t}^{2}\\end{matrix}\\right] \\sigma^{2}_{v}$"
],
"text/plain": [
"⎡ 4 3⎤ \n",
"⎢\\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ──────────⎥ \n",
"⎢ 4 2 ⎥ \n",
"⎢ ⎥⋅σ²ᵥ\n",
"⎢ 3 ⎥ \n",
"⎢\\Delta{t} 2⎥ \n",
"⎢────────── \\Delta{t} ⎥ \n",
"⎣ 2 ⎦ "
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"var = symbols('sigma^2_v')\n",
"v = Matrix([[dt**2 / 2], [dt]])\n",
"\n",
"Q = v * var * v.T\n",
"\n",
"# factor variance out of the matrix to make it more readable\n",
"Q = Q / var\n",
"MatMul(Q, var)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The second order system proceeds with the same math.\n",
"\n",
"\n",
"$$\\mathbf{F} = \\begin{bmatrix}1 & \\Delta t & {\\Delta t}^2/2 \\\\ 0 & 1 & \\Delta t\\\\ 0& 0& 1\\end{bmatrix}$$\n",
"\n",
"Here we will assume that the white noise is a discrete time Wiener process. This gives us\n",
"\n",
"$$\\Gamma = \\begin{bmatrix}\\frac{1}{2}\\Delta t^2 \\\\ \\Delta t\\\\ 1\\end{bmatrix}$$\n",
"\n",
"There is no 'truth' to this model, it is just convenient and provides good results. For example, we could assume that the noise is applied to the jerk at the cost of a more complicated equation. \n",
"\n",
"The covariance of the process noise is then\n",
"\n",
"$$Q = \\mathbb E[\\Gamma w(t) w(t) \\Gamma^\\mathsf{T}] = \\Gamma\\sigma^2_v\\Gamma^\\mathsf{T}$$.\n",
"\n",
"We can compute that with SymPy as follows"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\frac{\\Delta{t}^{4}}{4} & \\frac{\\Delta{t}^{3}}{2} & \\frac{\\Delta{t}^{2}}{2}\\\\\\frac{\\Delta{t}^{3}}{2} & \\Delta{t}^{2} & \\Delta{t}\\\\\\frac{\\Delta{t}^{2}}{2} & \\Delta{t} & 1\\end{matrix}\\right] \\sigma^{2}_{v}$"
],
"text/plain": [
"⎡ 4 3 2⎤ \n",
"⎢\\Delta{t} \\Delta{t} \\Delta{t} ⎥ \n",
"⎢────────── ────────── ──────────⎥ \n",
"⎢ 4 2 2 ⎥ \n",
"⎢ ⎥ \n",
"⎢ 3 ⎥ \n",
"⎢\\Delta{t} 2 ⎥ \n",
"⎢────────── \\Delta{t} \\Delta{t} ⎥⋅σ²ᵥ\n",
"⎢ 2 ⎥ \n",
"⎢ ⎥ \n",
"⎢ 2 ⎥ \n",
"⎢\\Delta{t} ⎥ \n",
"⎢────────── \\Delta{t} 1 ⎥ \n",
"⎣ 2 ⎦ "
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"var = symbols('sigma^2_v')\n",
"v = Matrix([[dt**2 / 2], [dt], [1]])\n",
"\n",
"Q = v * var * v.T\n",
"\n",
"# factor variance out of the matrix to make it more readable\n",
"Q = Q / var\n",
"MatMul(Q, var)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We cannot say that this model is more or less correct than the continuous model - both are approximations to what is happening to the actual object. Only experience and experiments can guide you to the appropriate model. In practice you will usually find that either model provides reasonable results, but typically one will perform better than the other.\n",
"\n",
"The advantage of the second model is that we can model the noise in terms of $\\sigma^2$ which we can describe in terms of the motion and the amount of error we expect. The first model requires us to specify the spectral density, which is not very intuitive, but it handles varying time samples much more easily since the noise is integrated across the time period. However, these are not fixed rules - use whichever model (or a model of your own devising) based on testing how the filter performs and/or your knowledge of the behavior of the physical model.\n",
"\n",
"A good rule of thumb is to set $\\sigma$ somewhere from $\\frac{1}{2}\\Delta a$ to $\\Delta a$, where $\\Delta a$ is the maximum amount that the acceleration will change between sample periods. In practice we pick a number, run simulations on data, and choose a value that works well."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using FilterPy to Compute Q\n",
"\n",
"FilterPy offers several routines to compute the $\\mathbf Q$ matrix. The function `Q_continuous_white_noise()` computes $\\mathbf Q$ for a given value for $\\Delta t$ and the spectral density."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.333 0.5 ]\n",
" [0.5 1. ]]\n"
]
}
],
"source": [
"from filterpy.common import Q_continuous_white_noise\n",
"from filterpy.common import Q_discrete_white_noise\n",
"\n",
"Q = Q_continuous_white_noise(dim=2, dt=1, spectral_density=1)\n",
"print(Q)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.05 0.125 0.167]\n",
" [0.125 0.333 0.5 ]\n",
" [0.167 0.5 1. ]]\n"
]
}
],
"source": [
"Q = Q_continuous_white_noise(dim=3, dt=1, spectral_density=1)\n",
"print(Q)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The function `Q_discrete_white_noise()` computes $\\mathbf Q$ assuming a piecewise model for the noise."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.25 0.5 ]\n",
" [0.5 1. ]]\n"
]
}
],
"source": [
"Q = Q_discrete_white_noise(2, var=1.)\n",
"print(Q)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.25 0.5 0.5 ]\n",
" [0.5 1. 1. ]\n",
" [0.5 1. 1. ]]\n"
]
}
],
"source": [
"Q = Q_discrete_white_noise(3, var=1.)\n",
"print(Q)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Simplification of Q\n",
"\n",
"Many treatments use a much simpler form for $\\mathbf Q$, setting it to zero except for a noise term in the lower rightmost element. Is this justified? Well, consider the value of $\\mathbf Q$ for a small $\\Delta t$"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0.00000002 0.00000078 0.00002083]\n",
" [0.00000078 0.00004167 0.00125 ]\n",
" [0.00002083 0.00125 0.05 ]]\n"
]
}
],
"source": [
"import numpy as np\n",
"\n",
"np.set_printoptions(precision=8)\n",
"Q = Q_continuous_white_noise(\n",
" dim=3, dt=0.05, spectral_density=1)\n",
"print(Q)\n",
"np.set_printoptions(precision=3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that most of the terms are very small. Recall that the only equation using this matrix is\n",
"\n",
"$$ \\mathbf P=\\mathbf{FPF}^\\mathsf{T} + \\mathbf Q$$\n",
"\n",
"If the values for $\\mathbf Q$ are small relative to $\\mathbf P$\n",
"then it will be contributing almost nothing to the computation of $\\mathbf P$. Setting $\\mathbf Q$ to the zero matrix except for the lower right term\n",
"\n",
"$$\\mathbf Q=\\begin{bmatrix}0&0&0\\\\0&0&0\\\\0&0&\\sigma^2\\end{bmatrix}$$\n",
"\n",
"while not correct, is often a useful approximation. If you do this for an important application you will have to perform quite a few studies to guarantee that your filter works in a variety of situations. \n",
"\n",
"If you do this, 'lower right term' means the most rapidly changing term for each variable. If the state is $x=\\begin{bmatrix}x & \\dot x & \\ddot{x} & y & \\dot{y} & \\ddot{y}\\end{bmatrix}^\\mathsf{T}$ Then Q will be 6x6; the elements for both $\\ddot{x}$ and $\\ddot{y}$ will have to be set to non-zero in $\\mathbf Q$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Stable Compution of the Posterior Covariance\n",
"\n",
"I've presented the equation to compute the posterior covariance as\n",
"\n",
"$$\\mathbf P = (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}$$\n",
"\n",
"and while strictly speaking this is correct, this is not how I compute it in `FilterPy`, where I use the *Joseph* equation\n",
"\n",
"\n",
"$$\\mathbf P = (\\mathbf I-\\mathbf {KH})\\mathbf{\\bar P}(\\mathbf I-\\mathbf{KH})^\\mathsf T + \\mathbf{KRK}^\\mathsf T$$\n",
"\n",
"\n",
"I frequently get emails and/or GitHub issues raised, claiming the implementation is a bug. It is not a bug, and I use it for several reasons. First, the subtraction $(\\mathbf I - \\mathbf{KH})$ can lead to nonsymmetric matrices results due to floating point errors. Covariances must be symmetric, and so becoming nonsymmetric usually leads to the Kalman filter diverging, or even for the code to raise an exception because of the checks built into `NumPy`.\n",
"\n",
"A traditional way to preserve symmetry is the following formula:\n",
"\n",
"$$\\mathbf P = (\\mathbf P + \\mathbf P^\\mathsf T) / 2$$\n",
"\n",
"This is safe because $\\sigma_{ij} = \\sigma_{ji}$ for all covariances in the matrix. Hence this operation averages the error between the differences of the two values if they have diverged due to floating point errors. \n",
"\n",
"If you look at the Joseph form for the equation above, you'll see there is a similar $\\mathbf{ABA}^\\mathsf T$ pattern in both terms. So they both preserve symmetry. But where did this equation come from, and why do I use it instead of\n",
"\n",
"\n",
"$$\\mathbf P = (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P} \\\\\n",
"\\mathbf P = (\\mathbf P + \\mathbf P^\\mathsf T) / 2$$\n",
"\n",
"\n",
"Let's just derive the equation from first principles. It's not too bad, and you need to understand the derivation to understand the purpose of the equation, and, more importantly, diagnose issues if you filter diverges due to numerical instability. This derivation comes from Brown[4].\n",
"\n",
"First, some symbology. $\\mathbf x$ is the true state of our system. $\\mathbf{\\hat x}$ is the estimated state of our system - the posterior. And $\\mathbf{\\bar x}$ is the estimated prior of the system. \n",
"\n",
"\n",
"Given that, we can define our model to be\n",
"\n",
"$$\\mathbf x_{k+1} = \\mathbf F_k \\mathbf x_k + \\mathbf w_k \\\\\n",
"\\mathbf z_k = \\mathbf H_k \\mathbf x_k + \\mathbf v_k$$\n",
"\n",
"In words, the next state $\\mathbf x_{k+1}$ of the system is the current state $k$ moved by some process $\\mathbf F_k$ plus some noise $\\mathbf w_k$. \n",
"\n",
"Note that these are definitions. No system perfectly follows a mathematical model, so we model that with the noise term $\\mathbf w_k$. And no measurement is perfect due to sensor error, so we model that with $\\mathbf v_k$\n",
"\n",
"I'll dispense with the subscript $k$ since in the remainder of the derivation we will only consider values at step $k$, never step $k+1$.\n",
"\n",
"Now we define the estimation error as the difference between the true state and the estimated state\n",
"\n",
"$$ \\mathbf e = \\mathbf x - \\mathbf{\\hat x}$$\n",
"\n",
"Again, this is a definition; we don't know how to compute $\\mathbf e$, it is just the defined difference between the true and estimated state.\n",
"\n",
"This allows us to define the covariance of our estimate, which is defined as the expected value of $\\mathbf{ee}^\\mathsf T$:\n",
"\n",
"$$\\begin{aligned}\n",
"P &= E[\\mathbf{ee}^\\mathsf T] \\\\\n",
"&= E[(\\mathbf x - \\mathbf{\\hat x})(\\mathbf x - \\mathbf{\\hat x})^\\mathsf T]\n",
"\\end{aligned}$$\n",
"\n",
"\n",
"Next, we define the posterior estimate as\n",
"\n",
"$$\\mathbf {\\hat x} = \\mathbf{\\bar x} + \\mathbf K(\\mathbf z - \\mathbf{H \\bar x})$$\n",
"\n",
"That looks like the equation from the Kalman filter, and for good reason. But as with the rest of the math so far, this is a **definition**. In particular, we have not defined $\\mathbf K$, and you shouldn't think of it as the Kalman gain, because we are solving this for *any* problem, not just for linear Kalman filters. Here, $\\mathbf K$ is just some unspecified blending value between 0 and 1."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we have our definitions, let's perform some substitution and algebra.\n",
"\n",
"The term $(\\mathbf x - \\mathbf{\\hat x})$ can be expanded by replacing $\\mathbf{\\hat x}$ with the definition above, yielding\n",
"\n",
"$$(\\mathbf x - \\mathbf{\\hat x}) = \\mathbf x - (\\mathbf{\\bar x} + \\mathbf K(\\mathbf z - \\mathbf{H \\bar x}))$$\n",
"\n",
"Now we replace $\\mathbf z$ with $\\mathbf H \\mathbf x + \\mathbf v$:\n",
"\n",
"$$\\begin{aligned}\n",
"(\\mathbf x - \\mathbf{\\hat x})\n",
"&= \\mathbf x - (\\mathbf{\\bar x} + \\mathbf K(\\mathbf z - \\mathbf{H \\bar x})) \\\\\n",
"&= \\mathbf x - (\\mathbf{\\bar x} + \\mathbf K(\\mathbf H \\mathbf x + \\mathbf v - \\mathbf{H \\bar x})) \\\\\n",
"&= (\\mathbf x - \\mathbf{\\bar x}) - \\mathbf K(\\mathbf H \\mathbf x + \\mathbf v - \\mathbf{H \\bar x}) \\\\\n",
"&= (\\mathbf x - \\mathbf{\\bar x}) - \\mathbf{KH}(\\mathbf x - \\mathbf{ \\bar x}) - \\mathbf{Kv} \\\\\n",
"&= (\\mathbf I - \\mathbf{KH})(\\mathbf x - \\mathbf{\\bar x}) - \\mathbf{Kv}\n",
"\\end{aligned}$$\n",
"\n",
"Now we can solve for $\\mathbf P$ if we note that the expected value of $(\\mathbf x - \\mathbf{\\bar x})$ is the prior covariance $\\mathbf{\\bar P}$, and that the expected value of $\\mathbf v$ is $E[\\mathbf{vv}^\\mathbf T] = \\mathbf R$:\n",
"\n",
"$$\\begin{aligned}\n",
"\\mathbf P &= \n",
" E\\big[[(\\mathbf I - \\mathbf{KH})(\\mathbf x - \\mathbf{\\bar x}) - \\mathbf{Kv})]\n",
" [(\\mathbf I - \\mathbf{KH})(\\mathbf x - \\mathbf{\\bar x}) - \\mathbf{Kv}]^\\mathsf T\\big ] \\\\\n",
" &= (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}(\\mathbf I - \\mathbf{KH})^\\mathsf T + \\mathbf{KRK}^\\mathsf T\n",
"\\end{aligned}$$\n",
"\n",
"which is what we came here to prove.\n",
"\n",
"Note that this equation is valid for *any* $\\mathbf K$, not just the optimal $\\mathbf K$ computed by the Kalman filter. And that is why I use this equation. In practice the Kalman gain computed by the filter is *not* the optimal value both because the real world is never truly linear and Gaussian, and because of floating point errors induced by computation. This equation is far less likely to cause the Kalman filter to diverge in the face of real world conditions.\n",
"\n",
"Where did $\\mathbf P = (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}$ come from, then? Let's finish the derivation, which is simple. Recall that the Kalman filter (optimal) gain is given by\n",
"\n",
"$$\\mathbf K = \\mathbf{\\bar P H^\\mathsf T}(\\mathbf{H \\bar P H}^\\mathsf T + \\mathbf R)^{-1}$$\n",
"\n",
"Now we substitute this into the equation we just derived:\n",
"\n",
"$$\\begin{aligned}\n",
"&= (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}(\\mathbf I - \\mathbf{KH})^\\mathsf T + \\mathbf{KRK}^\\mathsf T\\\\\n",
"&= \\mathbf{\\bar P} - \\mathbf{KH}\\mathbf{\\bar P} - \\mathbf{\\bar PH}^\\mathsf T\\mathbf{K}^\\mathsf T + \\mathbf K(\\mathbf{H \\bar P H}^\\mathsf T + \\mathbf R)\\mathbf K^\\mathsf T \\\\\n",
"&= \\mathbf{\\bar P} - \\mathbf{KH}\\mathbf{\\bar P} - \\mathbf{\\bar PH}^\\mathsf T\\mathbf{K}^\\mathsf T + \\mathbf{\\bar P H^\\mathsf T}(\\mathbf{H \\bar P H}^\\mathsf T + \\mathbf R)^{-1}(\\mathbf{H \\bar P H}^\\mathsf T + \\mathbf R)\\mathbf K^\\mathsf T\\\\\n",
"&= \\mathbf{\\bar P} - \\mathbf{KH}\\mathbf{\\bar P} - \\mathbf{\\bar PH}^\\mathsf T\\mathbf{K}^\\mathsf T + \\mathbf{\\bar P H^\\mathsf T}\\mathbf K^\\mathsf T\\\\\n",
"&= \\mathbf{\\bar P} - \\mathbf{KH}\\mathbf{\\bar P}\\\\\n",
"&= (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}\n",
"\\end{aligned}$$\n",
"\n",
"Therefore $\\mathbf P = (\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}$ is mathematically correct when the gain is optimal, but so is $(\\mathbf I - \\mathbf{KH})\\mathbf{\\bar P}(\\mathbf I - \\mathbf{KH})^\\mathsf T + \\mathbf{KRK}^\\mathsf T$. As we already discussed the latter is also correct when the gain is suboptimal, and it is also more numerically stable. Therefore I use this computation in FilterPy.\n",
"\n",
"It is quite possible that your filter still diverges, especially if it runs for hundreds or thousands of epochs. You will need to examine these equations. The literature provides yet other forms of this computation which may be more applicable to your problem. As always, if you are solving real engineering problems where failure could mean loss of equipment or life, you will need to move past this book and into the engineering literature. If you are working with 'toy' problems where failure is not damaging, if you detect divergence you can just reset the value of $\\mathbf P$ to some 'reasonable' value and keep on going. For example, you could zero out the non diagonal elements so the matrix only contains variances, and then maybe multiply by a constant somewhat larger than one to reflect the loss of information you just injected into the filter. Use your imagination, and test."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Deriving the Kalman Gain Equation\n",
"\n",
"If you read the last section, you might as well read this one. With this we will have derived the Kalman filter equations.\n",
"\n",
"Note that this derivation is *not* using Bayes equations. I've seen at least four different ways to derive the Kalman filter equations; this derivation is typical to the literature, and follows from the last section. The source is again Brown [4].\n",
"\n",
"In the last section we used an unspecified scaling factor $\\mathbf K$ to derive the Joseph form of the covariance equation. If we want an optimal filter, we need to use calculus to minimize the errors in the equations. You should be familiar with this idea. If you want to find the minimum value of a function $f(x)$, you take the derivative and set it equal to zero: $\\frac{x}{dx}f(x) = 0$.\n",
"\n",
"In our problem the error is expressed by the covariance matrix $\\mathbf P$. In particular, the diagonal expresses the error (variance) of each element in the state vector. So, to find the optimal gain we want to take the derivative of the trace (sum) of the diagonal.\n",
"\n",
"Brown reminds us of two formulas involving the derivative of traces:\n",
"\n",
"$$\\frac{d\\, trace(\\mathbf{AB})}{d\\mathbf A} = \\mathbf B^\\mathsf T$$\n",
"\n",
"$$\\frac{d\\, trace(\\mathbf{ACA}^\\mathsf T)}{d\\mathbf A} = 2\\mathbf{AC}$$\n",
"\n",
"where $\\mathbf{AB}$ is square and $\\mathbf C$ is symmetric.\n",
"\n",
"\n",
"We expand out the Joseph equation to:\n",
"\n",
"$$\\mathbf P = \\mathbf{\\bar P} - \\mathbf{KH}\\mathbf{\\bar P} - \\mathbf{\\bar P}\\mathbf H^\\mathsf T \\mathbf K^\\mathsf T + \\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R)\\mathbf K^\\mathsf T$$\n",
"\n",
"Now we need to the the derivative of the trace of $\\mathbf P$ with respect to $\\mathbf T$: $\\frac{d\\, trace(\\mathbf P)}{d\\mathbf K}$.\n",
"\n",
"The derivative of the trace the first term with respect to $\\mathbf K$ is $0$, since it does not have $\\mathbf K$ in the expression.\n",
"\n",
"The derivative of the trace of the second term is $(\\mathbf H\\mathbf{\\bar P})^\\mathsf T$.\n",
"\n",
"We can find the derivative of the trace of the third term by noticing that $\\mathbf{\\bar P}\\mathbf H^\\mathsf T \\mathbf K^\\mathsf T$ is the transpose of $\\mathbf{KH}\\mathbf{\\bar P}$. The trace of a matrix is equal to the trace of it's transpose, so it's derivative will be same as the second term.\n",
"\n",
"Finally, the derivative of the trace of the fourth term is $2\\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R)$.\n",
"\n",
"This gives us the final value of \n",
"\n",
"$$\\frac{d\\, trace(\\mathbf P)}{d\\mathbf K} = -2(\\mathbf H\\mathbf{\\bar P})^\\mathsf T + 2\\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R)$$\n",
"\n",
"We set this to zero and solve to find the equation for $\\mathbf K$ which minimizes the error:\n",
"\n",
"$$-2(\\mathbf H\\mathbf{\\bar P})^\\mathsf T + 2\\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R) = 0 \\\\\n",
"\\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R) = (\\mathbf H\\mathbf{\\bar P})^\\mathsf T \\\\\n",
"\\mathbf K(\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R) = \\mathbf{\\bar P}\\mathbf H^\\mathsf T \\\\\n",
"\\mathbf K= \\mathbf{\\bar P}\\mathbf H^\\mathsf T (\\mathbf H \\mathbf{\\bar P}\\mathbf H^\\mathsf T + \\mathbf R)^{-1}\n",
"$$\n",
"\n",
"This derivation is not quite iron clad as I left out an argument about why minimizing the trace minimizes the total error, but I think it suffices for this book. Any of the standard texts will go into greater detail if you need it."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Numeric Integration of Differential Equations"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We've been exposed to several numerical techniques to solve linear differential equations. These include state-space methods, the Laplace transform, and van Loan's method. \n",
"\n",
"These work well for linear ordinary differential equations (ODEs), but do not work well for nonlinear equations. For example, consider trying to predict the position of a rapidly turning car. Cars maneuver by turning the front wheels. This makes them pivot around their rear axle as it moves forward. Therefore the path will be continuously varying and a linear prediction will necessarily produce an incorrect value. If the change in the system is small enough relative to $\\Delta t$ this can often produce adequate results, but that will rarely be the case with the nonlinear Kalman filters we will be studying in subsequent chapters. \n",
"\n",
"For these reasons we need to know how to numerically integrate ODEs. This can be a vast topic that requires several books. However, I will cover a few simple techniques which will work for a majority of the problems you encounter.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Euler's Method\n",
"\n",
"Let's say we have the initial condition problem of \n",
"\n",
"$$\\begin{gathered}\n",
"y' = y, \\\\ y(0) = 1\n",
"\\end{gathered}$$\n",
"\n",
"We happen to know the exact answer is $y=e^t$ because we solved it earlier, but for an arbitrary ODE we will not know the exact solution. In general all we know is the derivative of the equation, which is equal to the slope. We also know the initial value: at $t=0$, $y=1$. If we know these two pieces of information we can predict the value at $y(t=1)$ using the slope at $t=0$ and the value of $y(0)$. I've plotted this below."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "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MNi9ezfJycnYbDb27NnDp59+yu233w7AXXfdRVhYGJMnT6ZNmzZurlZERERE6otCeyNnGAZbtmzBZrNhs9k4dOiQ8zlPT0+OHDnifNyxY0ceeeQRd5QpIiIiIleRQnsjl5qaSr9+/ZyPvb29GT16NFarlfHjxxMaGurG6kRERESkISi0NxIVFRWsXbsWm81GSUkJ//znPwGIj49n2LBhtGrVCqvVyi233EJgYKCbqxURERGRhqTQ7kZ2u52vvvoKm83GvHnzyMrKAsDLy4tXX32VoKAgAL7++mstzSgiIiLSgim0u8n/+3//j1deeYWzZ886x8LCwpg4cSJWqxVfX1/nuAK7iIiISMum0N4Azp07x9KlS7nxxhsJDw8HKuemnz17lqioKCZPnozVamXYsGFYLBY3VysiIiIijY1C+1WSl5fHokWLsNlsLFu2jOLiYv75z3/y85//HIAf//jH9O7dm8GDB+Ph4eHmakVERESkMVNor0eFhYV8/vnn2Gw2Vq5cSVlZmfO59u3bYzabnY8jIyOJjIx0R5kiIiIi0sQotNdReXk5np6VbSwuLubnP/85DocDgK5du2K1WrFarVx//fWamy4iIiIiLjFffpPq3n77bdq3b4+Pjw8JCQmsXbv2ktuvXr2ahIQEfHx86NChAzNmzHCp2Mbi5MmTvPHGGwwbNoxRo0Y5x1u1asWDDz7In/70J/bs2cO+fft48cUX6d27twK7iIiIiLis1lfaP/30Ux5//HHefvttBg8ezLvvvsvYsWPZu3cvcXFx1bY/duwY48aN48EHH2TWrFmsW7eOhx9+2LnueFORkZHBq6++yrx589i4caNz3Gw2c+bMGcLCwgCa/C8kIiIiItL41Dq0v/baazzwwAPOL1ROnz6d5cuX88477/Dyyy9X237GjBnExcUxffp0ALp168bmzZv529/+dtHQXlpaSmlpqfNxfn4+ULmuud1ur23JdfbYY4/xzjvvOB+bTCYGDhxIYmIikyZNIjAw0C11NQUX+qL+1J565zr1rm7UP9epd65T71yn3tWNO/tXm32aDMMwrnTjsrIy/Pz8+Pzzz5k8ebJz/LHHHmP79u2sXr262muGDh1K7969+fvf/+4cmzt3LrfffjtFRUU1LnH4wgsvMG3atGrjs2fPxs/P70rLrTdffvklb731Fj169GDgwIEMGDDAeWVdRERERMQVRUVF3HXXXeTl5TlvqnkxtbrSnp2dTUVFBVFRUVXGo6KiyMjIqPE1GRkZNW5fXl5OdnY20dHR1V4zdepUpkyZ4nycn59PbGwso0aNuuwBXQ0DBw6kb9++JCUlaR31WrLb7axcuZKRI0eqd7Wk3rlOvasb9c916p3r1DvXqXd1487+XZhNciVcWj3mh1+qNAzjkl+0rGn7msYv8Pb2xtvbu9q4xWJxy8kYGhpKcHCw2/bfHKh3rlPvXKfe1Y365zr1znXqnevUu7pxR/9qs79arR4TERGBh4dHtavqmZmZ1a6mX9C6desat/f09HTeHVRERERERC6uVqHdy8uLhIQEVq5cWWV85cqVDBo0qMbXDBw4sNr2K1asoG/fvvptUERERETkCtR6nfYpU6bwr3/9iw8++IB9+/bxxBNPkJqaykMPPQRUzkf/yU9+4tz+oYceIiUlhSlTprBv3z4++OAD3n//fZ588sn6OwoRERERkWas1nPa77jjDnJycvjTn/5Eeno6PXv2ZMmSJcTHxwOQnp5Oamqqc/v27duzZMkSnnjiCd566y3atGnDG2+80aTWaBcRERERcSeXvoj68MMP8/DDD9f43MyZM6uNDRs2jK1bt7qyKxERERGRFq/W02NERERERKRhKbSLiIiIiDRyCu0iIiIiIo2cQruIiIiISCOn0C4iIiIi0si5tHpMQzMMA4D8/Hy37N9ut1NUVER+fr5uCFVL6p3r1DvXqXd1o/65Tr1znXrnOvWubtzZvwvZ9kLWvZQmEdoLCgoAiI2NdXMlIiIiIiL1q6CggODg4EtuYzKuJNq7mcPh4NSpUwQGBmIymRp8//n5+cTGxnLixAmCgoIafP9NmXrnOvXOdepd3ah/rlPvXKfeuU69qxt39s8wDAoKCmjTpg1m86VnrTeJK+1ms5mYmBh3l0FQUJB+GFyk3rlOvXOdelc36p/r1DvXqXeuU+/qxl39u9wV9gv0RVQRERERkUZOoV1EREREpJFTaL8C3t7ePP/883h7e7u7lCZHvXOdeuc69a5u1D/XqXeuU+9cp97VTVPpX5P4IqqIiIiISEumK+0iIiIiIo2cQruIiIiISCOn0C4iIiIi0sgptIuIiIiINHIK7SIiIiIijZxCO/CXv/yFQYMG4efnR0hIyBW9xjAMXnjhBdq0aYOvry/Dhw9nz549VbYpLS3l0UcfJSIiAn9/fyZMmMDJkyevwhG4z9mzZ7n33nsJDg4mODiYe++9l9zc3Eu+xmQy1fjn//7v/5zbDB8+vNrzd95551U+moblSu/uv//+an0ZMGBAlW1awnkHte+f3W7n97//Pddeey3+/v60adOGn/zkJ5w6darKds3x3Hv77bdp3749Pj4+JCQksHbt2ktuv3r1ahISEvDx8aFDhw7MmDGj2jY2m43u3bvj7e1N9+7dmTt37tUq361q07s5c+YwcuRIWrVqRVBQEAMHDmT58uVVtpk5c2aNn38lJSVX+1AaXG16t2rVqhr7sn///irbtZTzDmrXv5r+22AymejRo4dzm5Zy7q1Zs4bx48fTpk0bTCYT8+bNu+xrmsxnniHGc889Z7z22mvGlClTjODg4Ct6zSuvvGIEBgYaNpvN2LVrl3HHHXcY0dHRRn5+vnObhx56yGjbtq2xcuVKY+vWrcaNN95o9OrVyygvL79KR9LwxowZY/Ts2dNYv369sX79eqNnz57GrbfeesnXpKenV/nzwQcfGCaTyThy5Ihzm2HDhhkPPvhgle1yc3Ov9uE0KFd6d9999xljxoyp0pecnJwq27SE884wat+/3NxcY8SIEcann35q7N+/39iwYYPRv39/IyEhocp2ze3c++9//2tYLBbjn//8p7F3717jscceM/z9/Y2UlJQatz969Kjh5+dnPPbYY8bevXuNf/7zn4bFYjGSk5Od26xfv97w8PAwXnrpJWPfvn3GSy+9ZHh6ehrffvttQx1Wg6ht7x577DHjr3/9q7Fp0ybj4MGDxtSpUw2LxWJs3brVuc2HH35oBAUFVfscbG5q27uvv/7aAIwDBw5U6cv3P7daynlnGLXvX25ubpW+nThxwggLCzOef/555zYt5dxbsmSJ8cwzzxg2m80AjLlz515y+6b0mafQ/j0ffvjhFYV2h8NhtG7d2njllVecYyUlJUZwcLAxY8YMwzAqf4AsFovx3//+17lNWlqaYTabjWXLltV77e6wd+9eA6hy0m7YsMEAjP3791/x+0ycONG46aabqowNGzbMeOyxx+qr1EbH1d7dd999xsSJEy/6fEs47wyj/s69TZs2GUCV/xA2t3PvhhtuMB566KEqY127djWefvrpGrd/6qmnjK5du1YZ++Uvf2kMGDDA+fj22283xowZ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uU1LOe8Mo/b9u+Cee+4x+vfvX+N7tpRzr6CgwJnjAOO1114ztm3b5lwlrCl/5im0G5XL6AHV/nz99dfObQDjww8/dD52OBzG888/b7Ru3drw9vY2hg4dauzatavK+xYXFxu//vWvjbCwMMPX19e49dZbjdTU1AY6qoaRk5Nj3H333UZgYKARGBho3H333dWW7Pph7wzDMN59913D19e3xvWvU1NTjaFDhxphYWGGl5eX0bFjR+M3v/lNtfXIm7ra9q6oqMgYNWqU0apVK8NisRhxcXHGfffdV+2cagnnnWHUvn/Hjh2r8ef8+z/rzfXce+utt4z4+HjDy8vL6NOnj7F69Wrnc/fdd58xbNiwKtuvWrXK6N27t+Hl5WW0a9euxl+uP//8c+Oaa64xLBaL0bVr1yrhqjmpTe+GDRtW4/l13333Obd5/PHHjbi4OMPLy8to1aqVMWrUKGP9+vUNeEQNpza9++tf/2p07NjR8PHxMUJDQ40f/ehHxuLFi6u9Z0s57wyj9j+3ubm5hq+vr/Hee+/V+H4t5dy7sHzoxX4Om/Jnnskwzs+2FxERERGRRqnZzmkXEREREWkuFNpFRERERBo5hXYRERERkUZOoV1EREREpJFTaBcRERERaeQU2kVEREREGjmFdhERERGRRk6hXURERESkkVNoFxERERFp5BTaRUREREQaOYV2EREREZFG7v8D8VC0izGz/2MAAAAASUVORK5CYII=\n",
"text/plain": [
"