```python
import numpy as np
import matplotlib.pyplot as plt
import math
import scipy.integrate as inte
from numpy import matmul as mm
from control import lqr
import scipy.linalg
import slycot
```

```python
from sympy import *
th,phi,dth,dphi,u = symbols('th phi dth dphi u')
```

def lqr(A,B,Q,R):
    X = np.matrix(scipy.linalg.solve_continuous_are(A, B, Q, R))
    K = np.matrix(scipy.linalg.inv(R)*(B.T*X))
    eigVals, eigVecs = scipy.linalg.eig(A-B*K)
    return K, X, eigVals

def dlqr(A,B,Q,R):
    X = np.matrix(scipy.linalg.solve_discrete_are(A, B, Q, R))
    K = np.matrix(scipy.linalg.inv(B.T*X*B+R)*(B.T*X*A))
    eigVals, eigVecs = scipy.linalg.eig(A-B*K)
    return K, X, eigVals

```python
def eomsym(params,th,phi,dth,dphi,u):
    A = params['mr']*params['r']*params['d']*cos(phi)
    B = params['ir']+params['mr']*(params['d']**2)
    C = params['mr']*params['g']*params['d']
    D = params['mr']*(params['r']**2)
    E = params['r']*params['mr']*params['d']*cos(phi)
    F = params['mr']*params['r']*params['d']
    tau = u
    tmp1 = A*F*sin(phi)*dphi**2-C*D*sin(phi)+(A+D)*tau
    tmp2 = A*E-B*D; phidd = tmp1/tmp2
    thdd = -((A+B)*phidd-C*sin(phi)+tau)/A
    qdd = Matrix([thdd,phidd])
    return qdd
```

```python
def eom(params,th,phi,dth,dphi,u):
    A = params['mr']*params['r']*params['d']*np.cos(phi)
    B = params['ir']+params['mr']*(params['d']**2)
    C = params['mr']*params['g']*params['d']
    D = params['mr']*(params['r']**2)
    E = params['r']*params['mr']*params['d']*np.cos(phi)
    F = params['mr']*params['r']*params['d']
    tau = u
    tmp1 = A*F*np.sin(phi)*dphi**2-C*D*np.sin(phi)+(A+D)*tau
    tmp2 = A*E-B*D; phidd = tmp1/tmp2
    thdd = -((A+B)*phidd-C*np.sin(phi)+tau)/A
    qdd = np.array([thdd,phidd])
    return qdd
```

```python
params = {}; params['g'] = 9.81
params['mr'] = .25; params['ir'] = .0001
params['d'] = .1; params['r'] = .02

qdd = eomsym(params,th,phi,dth,dphi,u)
f = Matrix([dth,dphi])
g = Matrix([qdd])
der = Matrix([th,phi,dth,dphi])
A = f.jacobian(der)
A2= g.jacobian(der)
b = f.jacobian(Matrix([u]))
b2= g.jacobian(Matrix([u]))
```

```python
A
```

$\displaystyle \left[\begin{matrix}0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

```python
A2
```

$\displaystyle \left[\begin{matrix}0 & \frac{2000.0 \left(- u - \frac{\left(0.0005 \cos{\left(\phi \right)} + 0.0026\right) \left(2.5 \cdot 10^{-7} dphi^{2} \sin{\left(\phi \right)} \cos{\left(\phi \right)} + u \left(0.0005 \cos{\left(\phi \right)} + 0.0001\right) - 2.4525 \cdot 10^{-5} \sin{\left(\phi \right)}\right)}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}} + 0.24525 \sin{\left(\phi \right)}\right) \sin{\left(\phi \right)}}{\cos^{2}{\left(\phi \right)}} + \frac{2000.0 \left(- \frac{\left(0.0005 \cos{\left(\phi \right)} + 0.0026\right) \left(- 2.5 \cdot 10^{-7} dphi^{2} \sin^{2}{\left(\phi \right)} + 2.5 \cdot 10^{-7} dphi^{2} \cos^{2}{\left(\phi \right)} - 0.0005 u \sin{\left(\phi \right)} - 2.4525 \cdot 10^{-5} \cos{\left(\phi \right)}\right)}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}} - \frac{5.0 \cdot 10^{-7} \left(0.0005 \cos{\left(\phi \right)} + 0.0026\right) \left(2.5 \cdot 10^{-7} dphi^{2} \sin{\left(\phi \right)} \cos{\left(\phi \right)} + u \left(0.0005 \cos{\left(\phi \right)} + 0.0001\right) - 2.4525 \cdot 10^{-5} \sin{\left(\phi \right)}\right) \sin{\left(\phi \right)} \cos{\left(\phi \right)}}{\left(2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}\right)^{2}} + 0.24525 \cos{\left(\phi \right)} + \frac{0.0005 \left(2.5 \cdot 10^{-7} dphi^{2} \sin{\left(\phi \right)} \cos{\left(\phi \right)} + u \left(0.0005 \cos{\left(\phi \right)} + 0.0001\right) - 2.4525 \cdot 10^{-5} \sin{\left(\phi \right)}\right) \sin{\left(\phi \right)}}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}}\right)}{\cos{\left(\phi \right)}} & 0 & - \frac{0.001 dphi \left(0.0005 \cos{\left(\phi \right)} + 0.0026\right) \sin{\left(\phi \right)}}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}}\\0 & \frac{- 2.5 \cdot 10^{-7} dphi^{2} \sin^{2}{\left(\phi \right)} + 2.5 \cdot 10^{-7} dphi^{2} \cos^{2}{\left(\phi \right)} - 0.0005 u \sin{\left(\phi \right)} - 2.4525 \cdot 10^{-5} \cos{\left(\phi \right)}}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}} + \frac{5.0 \cdot 10^{-7} \left(2.5 \cdot 10^{-7} dphi^{2} \sin{\left(\phi \right)} \cos{\left(\phi \right)} + u \left(0.0005 \cos{\left(\phi \right)} + 0.0001\right) - 2.4525 \cdot 10^{-5} \sin{\left(\phi \right)}\right) \sin{\left(\phi \right)} \cos{\left(\phi \right)}}{\left(2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}\right)^{2}} & 0 & \frac{5.0 \cdot 10^{-7} dphi \sin{\left(\phi \right)} \cos{\left(\phi \right)}}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}}\end{matrix}\right]$

```python
b
```

$\displaystyle \left[\begin{matrix}0\\0\end{matrix}\right]$

```python
b2
```

$\displaystyle \left[\begin{matrix}\frac{2000.0 \left(- \frac{\left(0.0005 \cos{\left(\phi \right)} + 0.0001\right) \left(0.0005 \cos{\left(\phi \right)} + 0.0026\right)}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}} - 1\right)}{\cos{\left(\phi \right)}}\\\frac{0.0005 \cos{\left(\phi \right)} + 0.0001}{2.5 \cdot 10^{-7} \cos^{2}{\left(\phi \right)} - 2.6 \cdot 10^{-7}}\end{matrix}\right]$

```python
A.subs([(th,0),(phi,0),(dth,0),(dphi,0),(u,0)])
```

$\displaystyle \left[\begin{matrix}0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

```python
A2.subs([(th,0),(phi,0),(dth,0),(dphi,0),(u,0)])
```

$\displaystyle \left[\begin{matrix}0 & -14714.9999999999 & 0 & 0\\0 & 2452.49999999998 & 0 & 0\end{matrix}\right]$

```python
A_1 = np.vstack((np.array(A.subs([(th,0),(phi,1),(dth,0),(dphi,0),(u,0)])),
          np.array(A2.subs([(th,0),(phi,1),(dth,0),(dphi,0),(u,0)])))).astype(float)
b_1 = np.vstack((np.array(b.subs([(th,0),(phi,1),(dth,0),(dphi,0),(u,0)])),
          np.array(b2.subs([(th,0),(phi,1),(dth,0),(dphi,0),(u,0)])))).astype(float)
A_1 = np.matrix(A_1)
b_1 = np.matrix(b_1)
print(A_1)
print(b_1)
```

    [[  0.           0.           1.           0.        ]
     [  0.           0.           0.           1.        ]
     [  0.         698.49085038   0.           0.        ]
     [  0.         -63.27637257   0.           0.        ]]
    [[    0.        ]
     [    0.        ]
     [17326.11921085]
     [-1979.22366384]]

```python
b_1.shape
```

    (4, 1)

```python
K,S,E = lqr(A_1,b_1,10000*np.eye(4),1000)
```

```python
def circle(x,y,r):
    ang = np.arange(0,2*np.pi,0.01)
    xp = r*np.cos(ang)
    yp = r*np.sin(ang)
    plt.plot(x+xp,y+yp,color='k',linewidth=2)
```

```python
def controller(params,t,X):
    k = -np.array([.0089,1.0553,.0112,.125])
    u = -mm(k,X)
    return u
```

```python
def dynamics(params,t,X):
    Xd = np.zeros(X.shape);
    th,phi,dth,dphi = X[0],X[1],X[2],X[3]
    u = controller(params,t,X)
    Xd[:2] = X[2:]
    Xd[2:] = eom(params,th,phi,dth,dphi,u)
    return Xd
def robotSim():
    params = {}; params['g'] = 9.81
    params['mr'] = .25; params['ir'] = .0001
    params['d'] = .1; params['r'] = .02
    t0,t1 = 0,2; y0 = np.array([0,1,0,0])
    t = np.linspace(t0,t1,445)
    y = np.zeros((len(t),len(y0))); y[0,:] = y0
    r = inte.ode(lambda t,y:dynamics(params,t,y)).set_integrator("dopri5")
    r.set_initial_value(y0, t0)   # initial values
    for i in range(1, t.size):
        y[i, :] = r.integrate(t[i]) # get one more value, add it to the array
        if not r.successful():
           raise RuntimeError("Could not integrate")
    return t,y
```

```python
t,y = robotSim()
plt.figure(figsize=(20,10))
plt.subplot(4,1,1)
plt.plot(t,y[:,1])

plt.subplot(4,1,2)
plt.plot(t,y[:,3])

plt.subplot(4,1,3)
plt.plot(t,y[:,0])

plt.subplot(4,1,4)
plt.plot(t,y[:,2])
```

```python
fig = plt.figure(figsize=(15,5))
plt.ylim([-.3,.3]); plt.xlim([-2,2])
j=0
for i in np.array([0,20,40,60,80,100]):
    ax = plt.subplot(3,2,j+1)
    th,phi,dth,dphi = y[i,0],y[i,1],y[i,2],y[i,3]
    x = th*.02
    plt.ylim([-.3,.3]); plt.xlim([-2,2])
    plt.gca().set_aspect('equal', adjustable='box')
    ax.plot(np.array([x,x+.1*2*np.sin(phi)]),
            np.array([0,2*.1*np.cos(phi)]),
            color=.5*np.array([1,1,1]),linewidth=12)
    circle(x,0,.02)
    ax.plot(np.array([-2,2]),
           np.array([-.02,-.02]),
           color=.3*np.array([1,1,1]))
    j+=1
```

```python
%matplotlib inline
%load_ext autoreload
%autoreload 2
%matplotlib notebook

fig = plt.figure(figsize=(10,10))
plt.ylim([-.3,.3]); plt.xlim([-2,2])
ax = plt.gca()
plt.ion()
fig.show()
fig.canvas.draw()

for i in range(len(t)):
    th,phi,dth,dphi = y[i,0],y[i,1],y[i,2],y[i,3]
    x = th*.02
    ax.clear()
    plt.ylim([-.3,.3]); plt.xlim([-2,2])
    plt.gca().set_aspect('equal', adjustable='box')
    ax.plot(np.array([x,x+.1*2*np.sin(phi)]),
            np.array([0,2*.1*np.cos(phi)]),
            color=.5*np.array([1,1,1]),linewidth=12)
    circle(x,0,.02)
    ax.plot(np.array([-2,2]),
           np.array([-.02,-.02]),
           color=.3*np.array([1,1,1]))

    fig.canvas.draw()
```

```python

```


## Дисциплина "Вычислительный практикум"
## Задание №5
# Приближённое вычисление интеграла по квадратурным формулам наивысшей алгебраической степени точности
## Ковальчуков Александр, 223 группа
### Вариант №4

## Постановка задачи

Требуется приближенно вычислить интеграл

$$\int_a^b \varphi(x)dx$$

где подынтегральная функция может не быть достаточно гладкой на промежутке интегрирования,
но представима в виде $\varphi(x) = \rho(x) f(x)$, где $\rho(x)$ содержит особенности, а $f(x)$ является
достаточно гладкой на $[a, b]$
Далее будем рассматривать квадратурную формулу вида:

$$\int_a^b \rho(x) f(x) dx \approx \sum_{k=1}^n A_k f(x_k) \label{eq:f1} \tag{1}$$

где $x_k$ - узлы квадратурной формулы, а $A_k$ - коэффициенты

$\rho(x)$ - называется весовой функцией, и для неё должны существовать моменты
весовой функции:

$$|\mu_k| = |\int_a^b \rho(x) x^k dx| < \infty, \;\; k = 0, 1, 2, \dots n$$

Квадратурная формула будет интерполяционной, если

$$A_k = \int_a^b \rho(x) \frac{\omega(x)}{(x - x_k) \omega'(x_k)} dx$$

###  Теорема 1
Для того, чтобы квадратурная формула $\eqref{eq:f1}$ была точна для любого многочлена степени
не выше $2n - 1$, необходимо и достаточно, чтобы

 - узлы  $x_1, x_2, \dots, x_n$ являлись корнями ортогонального относительно веса $\rho(x)$
 и отрезка $[a, b]$ многочлена $\omega(x) = (x - x_1)(x - x_2)\dots(x - x_n)$
 - формула $\eqref{eq:f1}$ была интерполяционной

###  Теорема 2
Пусть отрезок интегрирования $[a, b]$ конечен. Если функция $f(x)$ имеет непрерывную на $[a, b]$
производную порядка $2n$, то существует точка $\nu \in [a, b]$, такая что погрешность квадратурной
формулы $\eqref{eq:f1}$ гауссова типа имеет представление

$$R_n(f) = \frac{f^{2n}(\mu)}{(2n)!} \int_a^b \rho(x) \omega^2(x)dx$$

Код программы написан ня языке `python` с использованием интерактивной среды `Jupyter notebook`.

```python
import math
import pandas as pd
```

Задача: Различными методами вычислить интеграл:

$$\int_0^1 \frac{e^x}{\sqrt[3]x}$$

```python
I = 2.3435910933259677
```

####  Точное значение интеграла:

$$\int_0^1 \frac{e^x}{\sqrt[3]x} = \sqrt[3]{-1}(\Gamma(\frac{2}{3}, -1) - \Gamma(\frac{2}{3})) \approx 2.3435910933259677$$

#### Формула средних прямоугольников с тремя узлами

```python
def middle(a, b, func, w, m):
    h = (b - a) / m
    alpha = a + h/2
    return h * sum(w(alpha + k * h) * func(alpha + k * h) for k in range(m))

f = lambda x: math.e**x / x**(1/3)
w = lambda x: 1
M = 3
A = 0
B = 1
I_middle = middle(A, B, f, w, M)
print(I_middle)
print(abs(I_middle - I))
```

    2.2230282386930704
    0.1205628546328974

####  Значение интеграла по формуле средних прямоугольников с тремя узлами:
$$\int_0^1 \frac{e^x}{\sqrt[3]x} \approx 2.2230282386930704$$
####  Величина абсолютной фактической погрешности:
0.120561561606013

#### Интерополяционная формула с весом $\frac{1}{\sqrt[3]{x}}$ по узлам $\frac{1}{6}, \; \frac{1}{2}, \; \frac{5}{6}$

```python
from sympy import Symbol, integrate, lambdify, solve, re
x = Symbol('x')

def definite_integral(func, a, b):
    indefinite_integral = lambdify(x, integrate(func, x))
    return indefinite_integral(b) - indefinite_integral(a)

A = 0
B = 1
x1 = 1 / 6
x2 = 1 / 2
x3 = 5 / 6
w = 1 / x**(1/3)
f =  lambda x: math.e**x
omega = (x - x1) * (x - x2) * (x - x3)
d_omega = lambda x: 3 * x**2 - 2 * (x1 + x2 + x3) * x + (x1 * x2 + x2 * x3 + x1 * x3)

mu0, mu1, mu2 = map(definite_integral, [w * x**k for k in range(3)], [A]*3, [B]*3)

A1 = 1 / d_omega(x1) * (mu2 - (x2 + x3) * mu1 + x2 * x3 * mu0)
A2 = 1 / d_omega(x2) * (mu2 - (x1 + x3) * mu1 + x1 * x3 * mu0)
A3 = 1 / d_omega(x3) * (mu2 - (x1 + x2) * mu1 + x1 * x2 * mu0)

I_interpolate = A1 * f(x1) + A2 * f(x2) + A3 * f(x3)
print(I_interpolate)
print(abs(I - I_interpolate))
print(f'x1: {x1}, x2: {x2}, x3: {x3}')
print(f'A1: {A1}, A2: {A2}, A3: {A3}')
```

    2.3459717130858713
    0.002380619759903446
    x1: 0.16666666666666666, x2: 0.5, x3: 0.8333333333333334
    A1: 0.9000000000000001, A2: 0.14999999999999966, A3: 0.44999999999999996

####  Значение интеграла по интерополяционной формуле с заданными узлами:
$$\int_0^1 \frac{e^x}{\sqrt[3]x} \approx 2.3459717130858713 $$
####  Величина абсолютной фактической погрешности:
0.00238191278678812

#### Формула Гаусса с двумя узлами

```python
A = 0
B = 1
w = lambda x: 1
f = lambda x: math.e**x / x**(1/3)
t = 1 / 3**(1/2)

I_Gauss = (B - A) / 2 * (f((B - A) / 2 * - t + (B + A) / 2) + f((B - A) / 2 * t + (B + A) / 2))
print(I_Gauss)
print(abs(I - I_Gauss))
print("x1", (B - A) / 2 * - t + (B + A) / 2)
print("A1", (B - A) / 2)
print("x2", (B - A) / 2 * t + (B + A) / 2)
print("A2", (B - A) / 2)
```

    2.2278071408202447
    0.11578395250572315
    x1 0.21132486540518708
    A1 0.5
    x2 0.7886751345948129
    A2 0.5

####  Значение интеграла по формуле Гаусса:
$$\int_0^1 \frac{e^x}{\sqrt[3]x} \approx 2.2278071408202447 $$
####  Величина абсолютной фактической погрешности:
0.11578395250572315

#### Формула типа Гаусса с двумя узлами

```python
import numpy as np
from sympy import Symbol, integrate, lambdify, solve, re
x = Symbol('x')

def definite_integral(func, a, b):
    indefinite_integral = lambdify(x, integrate(func, x))
    return indefinite_integral(b) - indefinite_integral(a)

def solve_kramer_rule(A, b):
    n, m = A.shape
    if n == m == len(b):
        solution = [0] * n
        det_A = np.linalg.det(A)
        for i in range(n):
            B = A.copy()
            B[:, i] = b
            solution[i] = np.linalg.det(B) / det_A
        return solution
    else:
        raise ValueError("matrix A and list b must have the same length")

A = 0
B = 1
w = 1 / x**(1/3)
f =  lambda x: math.e**x

mu0, mu1, mu2, mu3 = map(definite_integral, [w * x**k for k in range(4)], [A]*4, [B]*4)

A = np.array([[mu1, mu0], [mu2, mu1]])
b = np.array([-mu2, -mu3])

[a1, a2] = solve_kramer_rule(A, b)

print(a1, a2)

array = list(map(re, solve(x**2 + a1 * x + a2)))
# print(array)
[x1, x2] = array
print(f'x1: {x1}, x2: {x2}')
omega = (x - x1) * (x - x2)
d_omega = lambda x: 2 * x - (x1 + x2)

A1 = 1 / d_omega(x1) * (mu1 - x2 * mu0)
A2 = 1 / d_omega(x2) * (mu1 - x1 * mu0)

print(f"Коэффициенты квадратурной формулы: A1 = {A1}, A2 = {A2}")
print("Нулевой момент:", mu0)
print("Сумма коэффициентов:",A1 + A2)

I_Gauss_type = A1 * f(x1) + A2 * f(x2)
print(I_Gauss_type)
print(abs(I - I_Gauss_type))
```

    -0.9090909090909111 0.11363636363636441
    x1: 0.149627093977301, x2: 0.759463815113608
    Коэффициенты квадратурной формулы: A1 = 0.884164078649987, A2 = 0.615835921350013
    Нулевой момент: 1.5
    Сумма коэффициентов: 1.50000000000000
    2.34299053839243
    0.000600554933535502

####  Значение интеграла по формуле Гаусса:
$$\int_0^1 \frac{e^x}{\sqrt[3]x} \approx 2.34299053839243 $$
####  Величина абсолютной фактической погрешности:
0.000600554933535502

### Выводы на основе тестирования
Из всех представленных квадратурных формул самая маленькая абсолютная фактическая погрешность вычисления интеграла

$$\int_0^1 \frac{e^x}{\sqrt[3]x}$$

оказалась у квадратурной формулы типа Гаусса с двумя узлами и составила  0.000600554933535502


```python
# Header starts here.
from sympy.physics.units import *
from sympy import *

# Rounding:
import decimal
from decimal import Decimal as DX
def iso_round(obj, pv, rounding=decimal.ROUND_HALF_EVEN):
    import sympy
    """
    Rounding acc. to DIN EN ISO 80000-1:2013-08
    place value = Rundestellenwert
    """
    assert pv in set([
        # place value   #  round to:
        1,              #  1
        0.1,            #  1st digit after decimal
        0.01,           #  2nd
        0.001,          #  3rd
        0.0001,         #  4th
        0.00001,        #  5th
        0.000001,       #  6th
        0.0000001,      #  7th
        0.00000001,     #  8th
        0.000000001,    #  9th
        0.0000000001,   # 10th
        ])
    try:
        tmp = DX(str(float(obj)))
        obj = tmp.quantize(DX(str(pv)), rounding=rounding)
    except:
        for i in range(len(obj)):
            tmp = DX(str(float(obj[i])))
            obj[i] = tmp.quantize(DX(str(pv)), rounding=rounding)
    return obj

# LateX:
kwargs = {}
kwargs["mat_str"] = "bmatrix"
kwargs["mat_delim"] = ""
# kwargs["symbol_names"] = {FB: "F^{\mathsf B}", }

# Units:
(k, M, G ) = ( 10**3, 10**6, 10**9 )
(mm, cm, deg) = ( m/1000, m/100, pi/180)
Newton = kg*m/s**2
Pa     = Newton/m**2
MPa    = M*Pa
GPa    = G*Pa
kN     = k*Newton

half = S(1)/2

# Header ends here.
#
a, b, c = var("a, b, c", positive=True)
F = var("F", positive=True)
alpha, beta = var("alpha, beta", positive=True)

prec = 0.01

sub_list= [
    (a, 4 *m),
    (b, 4 *m),
    (c, 2 *m),
    (F, 6 *Newton),
    (alpha, 30 *deg),
    (beta,  60 *deg),
    ]

ca = cos(alpha)
sa = sin(alpha)
cb = cos(beta)
sb = sin(beta)

print("\nd:")
rP = Matrix([-a, b, c])
d = rP/rP.norm()
pprint(d)

print("\nf:")
f = Matrix([-sa*cb, ca*cb, sb])
pprint(f)
tmp = f
tmp = tmp.subs(sub_list)
tmp = iso_round(tmp, prec)
pprint(tmp)

print("\nF / N:")
F = F*f
tmp = F
tmp = tmp.subs(sub_list)
tmp /= Newton
tmp = iso_round(tmp, 0.01)
pprint(tmp)

pprint("\nFd / N:")
Fd = F.dot(d)
tmp = Fd
tmp = tmp.subs(sub_list)
tmp /= Newton
tmp = iso_round(tmp, 0.01)
pprint(tmp)

print("\nFd / N:")
Fd = Fd*d
tmp = Fd
Fpa = tmp.applyfunc(simplify)
tmp = tmp.subs(sub_list)
tmp = tmp.applyfunc(simplify)
tmp /= Newton
tmp = iso_round(tmp, 0.01)
pprint(tmp)

print("\nFpe / N:")
Fpe = F-Fd
Fpe = Fpe.applyfunc(simplify)
tmp = Fpe
tmp = tmp.subs(sub_list)
tmp /= Newton
tmp = iso_round(tmp, 0.01)
pprint(tmp)

# d:
# ⎡       -a        ⎤
# ⎢─────────────────⎥
# ⎢   ______________⎥
# ⎢  ╱  2    2    2 ⎥
# ⎢╲╱  a  + b  + c  ⎥
# ⎢                 ⎥
# ⎢        b        ⎥
# ⎢─────────────────⎥
# ⎢   ______________⎥
# ⎢  ╱  2    2    2 ⎥
# ⎢╲╱  a  + b  + c  ⎥
# ⎢                 ⎥
# ⎢        c        ⎥
# ⎢─────────────────⎥
# ⎢   ______________⎥
# ⎢  ╱  2    2    2 ⎥
# ⎣╲╱  a  + b  + c  ⎦
#
# f:
# ⎡-sin(α)⋅cos(β)⎤
# ⎢              ⎥
# ⎢cos(α)⋅cos(β) ⎥
# ⎢              ⎥
# ⎣    sin(β)    ⎦
# ⎡-0.25⎤
# ⎢     ⎥
# ⎢0.43 ⎥
# ⎢     ⎥
# ⎣0.87 ⎦
#
# F / N:
# ⎡-1.5⎤
# ⎢    ⎥
# ⎢2.6 ⎥
# ⎢    ⎥
# ⎣5.2 ⎦
#
# Fd / N:
# 4.46
#
# Fd / N:
# ⎡-2.98⎤
# ⎢     ⎥
# ⎢2.98 ⎥
# ⎢     ⎥
# ⎣1.49 ⎦
#
# Fpe / N:
# ⎡1.48 ⎤
# ⎢     ⎥
# ⎢-0.38⎥
# ⎢     ⎥
# ⎣3.71 ⎦

```


[Table of Contents](http://nbviewer.ipython.org/github/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/table_of_contents.ipynb)

# The Extended Kalman Filter

```python
#format the book
%matplotlib inline
%load_ext autoreload
%autoreload 2
from __future__ import division, print_function
import book_format
book_format.load_style()
```

<style>
@import url('http://fonts.googleapis.com/css?family=Source+Code+Pro');
@import url('http://fonts.googleapis.com/css?family=Vollkorn');
@import url('http://fonts.googleapis.com/css?family=Arimo');

    div.cell{
        width: 900px;
        margin-left: 0% !important;
        margin-right: auto;
    }
    div.text_cell code {
        background: transparent;
        color: #000000;
        font-weight: 600;
        font-size: 11pt;
        font-style: bold;
        font-family:  'Source Code Pro', Consolas, monocco, monospace;
   }
    h1 {
        font-family: 'Open sans',verdana,arial,sans-serif;
	}

    div.input_area {
        background: #F6F6F9;
        border: 1px solid #586e75;
    }

    .text_cell_render h1 {
        font-weight: 200;
        font-size: 30pt;
        line-height: 100%;
        color:#c76c0c;
        margin-bottom: 0.5em;
        margin-top: 1em;
        display: block;
        white-space: wrap;
    }
    h2 {
        font-family: 'Open sans',verdana,arial,sans-serif;
    }
    .text_cell_render h2 {
        font-weight: 200;
        font-size: 16pt;
        font-style: italic;
        line-height: 100%;
        color:#c76c0c;
        margin-bottom: 0.5em;
        margin-top: 1.5em;
        display: inline;
        white-space: wrap;
    }
    h3 {
        font-family: 'Open sans',verdana,arial,sans-serif;
    }
    .text_cell_render h3 {
        font-weight: 200;
        font-size: 14pt;
        line-height: 100%;
        color:#d77c0c;
        margin-bottom: 0.5em;
        margin-top: 2em;
        display: block;
        white-space: nowrap;
    }
    h4 {
        font-family: 'Open sans',verdana,arial,sans-serif;
    }
    .text_cell_render h4 {
        font-weight: 100;
        font-size: 14pt;
        color:#d77c0c;
        margin-bottom: 0.5em;
        margin-top: 0.5em;
        display: block;
        white-space: nowrap;
    }
    h5 {
        font-family: 'Open sans',verdana,arial,sans-serif;
    }
    .text_cell_render h5 {
        font-weight: 200;
        font-style: normal;
        color: #1d3b84;
        font-size: 16pt;
        margin-bottom: 0em;
        margin-top: 0.5em;
        display: block;
        white-space: nowrap;
    }
    div.text_cell_render{
        font-family: 'Arimo',verdana,arial,sans-serif;
        line-height: 125%;
        font-size: 120%;
        text-align:justify;
        text-justify:inter-word;
    }
    div.output_subarea.output_text.output_pyout {
        overflow-x: auto;
        overflow-y: scroll;
        max-height: 50000px;
    }
    div.output_subarea.output_stream.output_stdout.output_text {
        overflow-x: auto;
        overflow-y: scroll;
        max-height: 50000px;
    }
    div.output_wrapper{
        margin-top:0.2em;
        margin-bottom:0.2em;
}

    code{
      font-size: 70%;
    }
    .rendered_html code{
    background-color: transparent;
    }
    ul{
        margin: 2em;
    }
    ul li{
        padding-left: 0.5em;
        margin-bottom: 0.5em;
        margin-top: 0.5em;
    }
    ul li li{
        padding-left: 0.2em;
        margin-bottom: 0.2em;
        margin-top: 0.2em;
    }
    ol{
        margin: 2em;
    }
    ol li{
        padding-left: 0.5em;
        margin-bottom: 0.5em;
        margin-top: 0.5em;
    }
    ul li{
        padding-left: 0.5em;
        margin-bottom: 0.5em;
        margin-top: 0.2em;
    }
    a:link{
       font-weight: bold;
       color:#447adb;
    }
    a:visited{
       font-weight: bold;
       color: #1d3b84;
    }
    a:hover{
       font-weight: bold;
       color: #1d3b84;
    }
    a:focus{
       font-weight: bold;
       color:#447adb;
    }
    a:active{
       font-weight: bold;
       color:#447adb;
    }
    .rendered_html :link {
       text-decoration: underline;
    }
    .rendered_html :hover {
       text-decoration: none;
    }
    .rendered_html :visited {
      text-decoration: none;
    }
    .rendered_html :focus {
      text-decoration: none;
    }
    .rendered_html :active {
      text-decoration: none;
    }
    .warning{
        color: rgb( 240, 20, 20 )
    }
    hr {
      color: #f3f3f3;
      background-color: #f3f3f3;
      height: 1px;
    }
    blockquote{
      display:block;
      background: #fcfcfc;
      border-left: 5px solid #c76c0c;
      font-family: 'Open sans',verdana,arial,sans-serif;
      width:680px;
      padding: 10px 10px 10px 10px;
      text-align:justify;
      text-justify:inter-word;
      }
      blockquote p {
        margin-bottom: 0;
        line-height: 125%;
        font-size: 100%;
      }
</style>

At this point in the book we have developed the theory for the linear Kalman filter. Then, in the last two chapters we broached the topic of using Kalman filters for nonlinear problems. I chose to start off with the Unscented Kalman filter, which probably felt like quite a departure from the linear Kalman filter math. It was also a departure from the historical development of the Kalman filter.

Shortly after Dr. Kálmán published his paper on the Kalman filter people recognized that his theory was limited to linear problems and immediately began work on extending the theory to work with nonlinear problems. As I have said elsewhere the only equation we really know how to solve is $\mathbf{Ax} = \mathbf{b}$. That is a slight exaggeration. Many of us have spent years at school learning sophisticated tricks and techniques to handle equations that are not linear. Yet after all that work the vast majority of equations that arise from simple physical systems remain intractable. Certainly there is no way to find general analytic solutions to the Kalman filter equations for nonlinear systems.

In this chapter we will learn the Extended Kalman filter (EKF). The EKF handles nonlinearity by linearizing the system at the point of the current estimate, and then the usual Kalman filter is used to filter this linearized system. It was one of the very first techniques used for nonlinear problems, and it remains the most common technique. Most filters in real world use are EKFs.

The EKF provides significant mathematical challenges to the designer of the filter; this is the most challenging chapter of the book. To be honest, I do everything I can to avoid the EKF in favor of other techniques that have been developed to filter nonlinear problems. However, the topic is unavoidable; all classic papers and a majority of current papers in the field use the EKF. Even if you do not use the EKF in your own work you will need to be familiar with the topic to be able to read the literature.

## Linearizing the System Model

The extended Kalman filter (EKF) works by linearizing the system model for each update. For example, consider the problem of tracking a cannonball in flight. Obviously it follows a curved flight path. However, if our update rate is small enough, say 1/10 second, then the trajectory over that time is nearly linear. If we linearize that short segment we will get an answer very close to the actual value, and we can use that value to perform the prediction step of the filter. There are many ways to linearize a set of nonlinear differential equations, and the topic is somewhat beyond the scope of this book. In practice, a Taylor series approximation is frequently used with EKFs, and that is what we will use.

Consider the function $f(x)=x^2−2x$. We want a linear approximation of this function so that we can use it in the Kalman filter. We will see how it is used in the Kalman filter in the next section, so don't worry about that yet. We can see that there is no single linear function (line) that gives a close approximation of this function. However, during each innovation (update) of the Kalman filter we know its current state, so if we linearize the function at that value we will have a close approximation. For example, suppose our current state is $x=1.5$. What would be a good linearization for this function?

We can use any linear function that passes through the curve at (1.5,-0.75). For example, consider using f(x)=8x−12.75 as the linearization, as in the plot below.

```python
import numpy as np
import matplotlib.pyplot as plt

xs =  np.arange(0, 2, 0.01)
ys = [x**2 - 2*x for x in xs]

def y(x):
    return 8*x - 12.75

plt.plot(xs, ys)
plt.plot([1.25, 1.75], [y(1.25), y(1.75)])
plt.xlim(1, 2)
plt.ylim([-1.5, 1]);
```

This is not a good linearization for $f(x)$. It is exact for $x=1.5$, but quickly diverges when $x$ varies by a small amount.

A much better approach is to use the slope of the function at the evaluation point as the linearization. We find the slope by taking the first derivative of the function:

$$f(x) = x^2 -2x \\
\frac{df}{dx} = 2x - 2$$,

so the slope at 1.5 is $2*1.5-2=1$. Let's plot that.

```python
def y(x):
    return x - 2.25

plt.plot(xs, ys)
plt.plot([1, 2], [y(1), y(2)])
plt.xlim(1, 2)
plt.ylim([-1.5, 1]);
```

Here we can see that this linearization is much better. It is still exactly correct at $x=1.5$, but the errors are very small as x varies. Compare the tiny error at $x=1.4$ vs the very large error at $x=1.4$ in the previous plot. This does not constitute a formal proof of correctness, but this sort of geometric depiction should be fairly convincing. Certainly it is easy to see that in this case if the line had any other slope the errors would accumulate more quickly.

## Linearizing the Kalman Filter

To implement the extended Kalman filter we will leave the linear equations as they are, and use partial derivatives to evaluate the system matrix $\mathbf{F}$ and the measurement matrix $\mathbf{H}$ at the state at time t ($\mathbf{x}_t$). In other words we linearize the equations at time t by finding the slope (derivative) of the equations at that time. Since $\mathbf{F}$ also depends on the control input vector $\mathbf{u}$ we will need to include that term:

$$
\begin{aligned}
F
&\equiv {\frac{\partial{f}}{\partial{x}}}\biggr|_{{x_t},{u_t}} \\
H &\equiv \frac{\partial{h}}{\partial{x}}\biggr|_{x_t}
\end{aligned}
$$

All this means is that at each update step we compute $\mathbf{F}$ as the partial derivative of our function $f()$ evaluated at x. We then use a computational technique, such as Taylor expansion, to turn this into a set of linear equations.

For nonlinear problems our function $f()$ is a set of differential equations. Modeling physical systems with differential equations is well outside the scope of this book. You will need to be reasonably well versed in this branch of applied mathematics to successfully implement the EKF for your problem. If you have not read it yet, please read the section **Modeling Dynamic Systems** in the **Kalman Filter Math** chapter as it contains the math that you will need to complete this chapter.

I think the easiest way to understand the EKF is to start off with an example. Perhaps the reason for some of my mathematical choices will not be clear, but trust that the end result will be an EKF.

## Example: Tracking a Flying Airplane

We will start by simulating tracking an airplane by using ground based radar. Radars work by emitting a beam of radio waves and scanning for a return bounce. Anything in the beam's path will reflects some of the signal back to the radar. By timing how long it takes for the reflected signal to get back to the radar the system can compute the *slant distance* - the straight line distance from the radar installation to the object.

For this example we want to take the slant range measurement from the radar and compute the horizontal position (distance of aircraft from the radar measured over the ground) and altitude of the aircraft, as in the diagram below.

```python
import ekf_internal
ekf_internal.show_radar_chart()
```

As discussed in the introduction, our measurement model is the nonlinear function $x=\sqrt{slant^2 - altitude^2}$. Therefore we will need a nonlinear

Predict step:

$$
\begin{array}{ll}
\textbf{Linear} & \textbf{Nonlinear} \\
x = Fx & x = \underline{f(x)} \\
P = FPF^T + Q & P = FPF^T + Q
\end{array}
$$

Update step:

$$
\begin{array}{ll}
\textbf{Linear} & \textbf{Nonlinear} \\
K = PH^T(HPH^T + R)^{-1}& K = PH^T(HPH^T + R)^{-1}\\
x = x + K(z-Hx) & x = x + K(z-\underline{h(x)}) \\
P = P(I - KH) & P = P(I - KH)\\
\end{array}
$$

As we can see there are two minor changes to the Kalman filter equations, which I have underlined. The first change replaces the equation $\mathbf{x} = \mathbf{Fx}$ with $\mathbf{x} = f(\mathbf{x})$. In the Kalman filter, $\mathbf{Fx}$ is how we compute the new state based on the old state. However, in a nonlinear system we cannot use linear algebra to compute this transition. So instead we hypothesize a nonlinear function $f()$ which performs this function. Likewise, in the Kalman filter we convert the state to a measurement with the linear function $\mathbf{Hx}$. For the extended Kalman filter we replace this with a nonlinear function $h()$, giving $\mathbf{z}_x = h(\mathbf{x})$.

The only question left is how do we implement and use $f()$ and $h()$ in the Kalman filter if they are nonlinear? We reach for the single tool that we have available for solving nonlinear equations - we linearize them at the point we want to evaluate the system.  For example, consider the function $f(x) = x^2 -2x$.

The rest of the equations are unchanged, so $f()$ and $h()$ must produce a matrix that approximates the values of the matrices $\mathbf{F}$ and $\mathbf{H}$ at the current value for $\mathbf{x}$. We do this by computing the partial derivatives of the state and measurements functions:

### Design the State Variables

So we want to track the position of an aircraft assuming a constant velocity and altitude, and measurements of the slant distance to the aircraft. That means we need 3 state variables - horizontal distance, velocity, and altitude.

$$\mathbf{x} = \begin{bmatrix}distance \\velocity\\ altitude\end{bmatrix}=  \begin{bmatrix}x_{pos} \\x_{vel}\\ x_{alt}\end{bmatrix}$$

### Design the System Model

We will model this as a set of differential equations. So we need an equation in the form
$$\dot{\mathbf{x}} = \mathbf{Ax} + \mathbf{w}$$

where $\mathbf{w}$ is the system noise.

Let's work out the equation for each of the rows in $\mathbf{x}.$

The first row is $\dot{x}_{pos}$, which is the velocity of the airplane. So we can say

$$\dot{x}_{pos} = x_{vel}$$

The second row is $\dot{x}_{vel}$, which is the acceleration of the airplane. We assume constant velocity, so the acceleration equals zero. However, we also assume system noise due to things like buffeting winds, errors in control inputs, and so on, so we need to add an error $w_{acc}$ to the term, like so

$$\dot{x}_{vel} = 0 + w_{acc}$$

The final row contains $\dot{x}_{alt}$, which is the rate of change in the altitude. We assume a constant altitude, so this term is 0, but as with acceleration we need to add in a noise term to account for things like wind, air density, and so on. This gives us

$$\dot{x}_{alt} = 0 + w_{alt}$$

We turn this into matrix form with the following:

$$\dot{\mathbf{x}} = \begin{bmatrix} 0 & 1 & 0 \\ 0& 0& 0 \\ 0&0&0\end{bmatrix}
\begin{bmatrix}x_{pos} \\x_{vel}\\ x_{alt}\end{bmatrix} + \begin{bmatrix}0 \\w_{vel}\\ w_{alt}\end{bmatrix}
$$

Now we have our differential equations for the system we can somehow solve for them to get our familiar Kalman filter state equation

$$ \mathbf{x}=\mathbf{Fx}$$

Solving an arbitrary set of differential equations is beyond the scope of this book, however most Kalman filters are amenable to Taylor-series expansion which I will briefly explain here without proof. The section **Modeling Dynamic Systems** in the **Kalman Filter Math** chapter contains much more information on this technique.

Given the partial differential equation

$$\mathbf{F} = \frac{\partial f(\mathbf{x})}{\partial x}$$

the solution is $e^{\mathbf{F}t}$. This is a standard answer learned in a first year partial differential equations course, and is not intuitively obvious from the material presented so far. However, we can compute the exponential matrix $e^{\mathbf{F}t}$ using a Taylor-series expansion in the form:

$$\Phi = \mathbf{I} + \mathbf{F}\Delta t + \frac{(\mathbf{F}\Delta t)^2}{2!} +  \frac{(\mathbf{F}\Delta t)^3}{3!} + \ldots$$

You may expand that equation to as many terms as required for accuracy, however many problems only use the first term

$$\Phi \approx \mathbf{I} + \mathbf{F}\Delta t$$

We can then compute the system matrix by substituting $\Phi$ in $x(t_k) = \Phi(\Delta t)x(t_{k-1})$. Thus, $\Phi$ is our system matrix.

We cannot use Greek symbols in Python, so the code uses the symbol `F` for $\Phi$. This is admittedly confusing. In the math above $\mathbf{F}$ represents the system of partial differential equations, and $\Phi$ is the system matrix. In the Python the partial differential equations are not represented in the code, and the system matrix is `F`.

### Design the Measurement Model

The measurement function for our filter needs to take the filter state $\mathbf{x}$ and turn it into a slant range distance. This is nothing more than the Pythagorean theorem.

$$h(\mathbf{x}) = \sqrt{x_{pos}^2 + x_{alt}^2}$$

The relationship between the slant distance and the position on the ground is nonlinear due to the square root term.
So what we need to do is linearize the measurement function at some point. As we discussed above, the best way to linearize an equation at a point is to find its slope, which we do by taking its derivative.

$$
\mathbf{H} \equiv \frac{\partial{h}}{\partial{x}}\biggr|_x
$$

The derivative of a matrix is called a Jacobian, which in general takes the form

$$\frac{\partial \mathbf{h}}{\partial \mathbf{x}} =
\begin{bmatrix}
\frac{\partial h_1}{\partial x_1} & \frac{\partial h_1}{\partial x_2} &\dots \\
\frac{\partial h_2}{\partial x_1} & \frac{\partial h_2}{\partial x_2} &\dots \\
\vdots & \vdots
\end{bmatrix}
$$

In other words, each element in the matrix is the partial derivative of the function $h$ with respect to the variables $x$. For our problem we have

$$\mathbf{H} = \begin{bmatrix}\frac{\partial h}{\partial x_{pos}} & \frac{\partial h}{\partial x_{vel}} & \frac{\partial h}{\partial x_{alt}}\end{bmatrix}$$

where $h(x) = \sqrt{x_{pos}^2 + x_{alt}^2}$ as given above.

Solving each in turn:

$$\begin{aligned}
\frac{\partial h}{\partial x_{pos}} &= \\ &=\frac{\partial}{\partial x_{pos}} \sqrt{x_{pos}^2 + x_{alt}^2} \\ &= \frac{x_{pos}}{\sqrt{x^2 + x_{alt}^2}}
\end{aligned}$$

and

$$\begin{aligned}
\frac{\partial h}{\partial x_{vel}} &=\\
&= \frac{\partial}{\partial x_{vel}} \sqrt{x_{pos}^2 + x_{alt}^2} \\
&= 0
\end{aligned}$$

and

$$\begin{aligned}
\frac{\partial h}{\partial x_{alt}} &=\\ &= \frac{\partial}{\partial x_{alt}} \sqrt{x_{pos}^2 + x_{alt}^2} \\ &= \frac{x_{alt}}{\sqrt{x_{pos}^2 + x_{alt}^2}}
\end{aligned}$$

giving us

$$\mathbf{H} =
\begin{bmatrix}
\frac{x_{pos}}{\sqrt{x_{pos}^2 + x_{alt}^2}} &
0 &
&
\frac{x_{alt}}{\sqrt{x_{pos}^2 + x_{alt}^2}}
\end{bmatrix}$$

This may seem daunting, so step back and recognize that all of this math is doing something very simple. We have an equation for the slant range to the airplane which is nonlinear. The Kalman filter only works with linear equations, so we need to find a linear equation that approximates $\mathbf{H}$ As we discussed above, finding the slope of a nonlinear equation at a given point is a good approximation. For the Kalman filter, the 'given point' is the state variable $\mathbf{x}$ so we need to take the derivative of the slant range with respect to $\mathbf{x}$.

To make this more concrete, let's now write a Python function that computes the Jacobian of $\mathbf{H}$. The `ExtendedKalmanFilter` class will be using this to generate `ExtendedKalmanFilter.H` at each step of the process.

```python
from math import sqrt
def HJacobian_at(x):
    """ compute Jacobian of H matrix for state x """

    horiz_dist = x[0]
    altitude   = x[2]
    denom = sqrt(horiz_dist**2 + altitude**2)
    return array ([[horiz_dist/denom, 0., altitude/denom]])
```

Finally, let's provide the code for $h(\mathbf{x})$

```python
def hx(x):
    """ compute measurement for slant range that would correspond
    to state x.
    """

    return (x[0]**2 + x[2]**2) ** 0.5
```

Now lets write a simulation for our radar.

```python
from numpy.random import randn
import math

class RadarSim(object):
    """ Simulates the radar signal returns from an object flying
    at a constant altityude and velocity in 1D.
    """

    def __init__(self, dt, pos, vel, alt):
        self.pos = pos
        self.vel = vel
        self.alt = alt
        self.dt = dt

    def get_range(self):
        """ Returns slant range to the object. Call once for each
        new measurement at dt time from last call.
        """

        # add some process noise to the system
        self.vel = self.vel  + .1*randn()
        self.alt = self.alt + .1*randn()
        self.pos = self.pos + self.vel*self.dt

        # add measurement noise
        err = self.pos * 0.05*randn()
        slant_dist = math.sqrt(self.pos**2 + self.alt**2)

        return slant_dist + err
```

Now we can implement our filter. I have not yet designed $\mathbf{R}$ and $\mathbf{Q}$ which is required to get optimal performance. However, we have already covered a lot of confusing material and I want you to see concrete examples as soon as possible. Therefore I will use 'reasonable' values for $\mathbf{R}$ and $\mathbf{Q}$.

The `FilterPy` library provides the class `ExtendedKalmanFilter`. It works very similar to the `KalmanFilter` class we have been using, except that it allows you to provide functions that compute the Jacobian of $\mathbf{H}$ and the function $h(\mathbf{x})$. We have already written the code for these two functions, so let's get going.

We start by importing the filter and creating it. There are 3 variables in `x` and only 1 measurement. At the same time we will create our radar simulator.

    from filterpy.kalman import ExtendedKalmanFilter

    rk = ExtendedKalmanFilter(dim_x=3, dim_z=1)
    radar = RadarSim(dt, pos=0., vel=100., alt=1000.)

We will initialize the filter near the airplane's actual position

    rk.x = array([radar.pos, radar.vel-10, radar.alt+100])

We assign the system matrix using the first term of the Taylor series expansion we computed above.

    dt = 0.05
    rk.F = eye(3) + array([[0, 1, 0],
                           [0, 0, 0],
                           [0, 0, 0]])*dt

After assigning reasonable values to $\mathbf{R}$, $\mathbf{Q}$, and $\mathbf{P}$ we can run the filter with a simple loop

    for i in range(int(20/dt)):
        z = radar.get_range()
        rk.update(array([z]), HJacobian_at, hx)
        rk.predict()

Putting that all together along with some boilerplate code to save the results and plot them, we get

```python
from filterpy.kalman import ExtendedKalmanFilter
from numpy import eye, array, asarray

dt = 0.05
rk = ExtendedKalmanFilter(dim_x=3, dim_z=1)
radar = RadarSim(dt, pos=0., vel=100., alt=1000.)

# make an imperfect starting guess
rk.x = array([radar.pos-100, radar.vel+100, radar.alt+1000])

rk.F = eye(3) + array([[0, 1, 0],
                       [0, 0, 0],
                       [0, 0, 0]])*dt

rk.R = radar.alt * 0.05 # 5% of distance
rk.Q = array([[0, 0, 0],
              [0, 1, 0],
              [0, 0, 1]]) * 0.001
rk.P *= 50

xs = []
track = []
for i in range(int(20/dt)):
    z = radar.get_range()
    track.append((radar.pos, radar.vel, radar.alt))

    rk.update(array([z]), HJacobian_at, hx)
    xs.append(rk.x)
    rk.predict()

xs = asarray(xs)
track = asarray(track)
time = np.arange(0, len(xs)*dt, dt)
ekf_internal.plot_radar(xs, track, time)
```

## Using SymPy to compute Jacobians

Depending on your experience with derivatives you may have found the computation of the Jacobian above either fairly straightforward, or quite difficult. Even if you found it easy, a slightly more difficult problem easily leads to very difficult computations.

As explained in Appendix A, we can use the SymPy package to compute the Jacobian for us.

```python
import sympy
sympy.init_printing()

x_pos, x_vel, x_alt = sympy.symbols('x_pos, x_vel x_alt')

H = sympy.Matrix([sympy.sqrt(x_pos**2 + x_alt**2)])

state = sympy.Matrix([x_pos, x_vel, x_alt])
H.jacobian(state)
```

This result is the same as the result we computed above, and at much less effort on our part!

## Designing Q

**author's note: ignore this, it  to be revised - noise in position and altitude is independent, not dependent**

Now we need to design the process noise matrix $\mathbf{Q}$. From the previous section we have the system equation

$$\dot{\mathbf{x}} = \begin{bmatrix} 0 & 1 & 0 \\ 0& 0& 0 \\ 0&0&0\end{bmatrix}
\begin{bmatrix}x_{pos} \\x_{vel}\\ x_{alt}\end{bmatrix} + \begin{bmatrix}0 \\w_{vel}\\ w_{alt}\end{bmatrix}
$$

where our process noise is

$$w = \begin{bmatrix}0 \\w_{vel}\\ w_{alt}\end{bmatrix}$$

We know from the Kalman filter math chapter that

$$\mathbf{Q} = E(ww^T)$$

where $E(\bullet)$ is the expected value. We compute the expected value as

$$\mathbf{Q} = \int_0^{dt} \Phi(t)\mathbf{Q}\Phi^T(t) dt$$

Rather than do this by hand, let's use sympy.

```python
import sympy
from sympy import Matrix
sympy.init_printing(use_latex='mathjax')
w_vel, w_alt, dt = sympy.symbols('w_vel w_alt \Delta{t}')
w = Matrix([[0, w_vel, w_alt]]).T
phi = Matrix([[1, dt, 0], [0, 1, 0], [0,0,1]])

q = w*w.T

sympy.integrate(phi*q*phi.T, (dt, 0, dt))
```

$$\left[\begin{matrix}\frac{\Delta{t}^{3} w_{vel}^{2}}{3} & \frac{\Delta{t}^{2} w_{vel}^{2}}{2} & \frac{w_{alt} w_{vel}}{2} \Delta{t}^{2}\\\frac{\Delta{t}^{2} w_{vel}^{2}}{2} & \Delta{t} w_{vel}^{2} & \Delta{t} w_{alt} w_{vel}\\\frac{w_{alt} w_{vel}}{2} \Delta{t}^{2} & \Delta{t} w_{alt} w_{vel} & \Delta{t} w_{alt}^{2}\end{matrix}\right]$$

## Robot Localization

So, time to try a real problem. I warn you that this is far from a simple problem. However, most books choose simple, textbook problems with simple answers, and you are left wondering how to implement a real world solution.

We will consider the problem of robot localization. We already implemented this in the **Unscented Kalman Filter** chapter, and I recommend you read that first. In this scenario we have a robot that is moving through a landscape with sensors that give range and bearings to various landmarks. This could be a self driving car using computer vision to identify trees, buildings, and other landmarks. Or, it might be one of those small robots that  vacuum your house. It could be a search and rescue device meant to go into dangerous areas to search for survivors. It doesn't matter too much.

Our robot is wheeled, which means that it manuevers by turning it's wheels. When it does so, the robot pivots around the rear axle while moving forward. This is nonlinear behavior which we will have to account for. The robot has a sensor that gives it approximate range and bearing to known targets in the landscape. This is nonlinear because computing a position from  a range and bearing requires square roots and trigonometry.

### Robot Motion Model

```python
ekf_internal.plot_bicycle()
```

At a first approximation n automobile steers by turning the front tires while moving forward. The front of the car moves in the direction that the wheels are pointing while pivoting around the rear tires. This simple description is complicated by issues such as slippage due to friction, the differing behavior of the rubber tires at different speeds, and the need for the outside tire to travel a different radius than the inner tire. Accurately modelling steering requires an ugly set of differential equations. For Kalman filtering, especially for lower speed robotic applications a simpler *bicycle model* has been found to perform well.

I have depicted this model above. Here we see the front tire is pointing in direction $\alpha$. Over a short time period the car moves forward and the rear wheel ends up further ahead and slightly turned inward, as depicted with the blue shaded tire. Over such a short time frame we can approximate this as a turn around a radius $R$. If you google bicycle model you will find that we can compute the turn angle $\beta$ with

$$\beta = \frac{d}{w} \tan{(\alpha)}$$

and the turning radius R is given by

$$R = \frac{d}{\beta}$$

where the distance the rear wheel travels given a forward velocity $v$ is $d=v\Delta t$.

If we let $\theta$ be our current orientation then we can compute the position $C$ before the turn starts as

$$ C_x = x - R\sin(\theta) \\
C_y = y + R\cos(\theta)
$$

After the move forward for time $\Delta t$ the new position and orientation of the robot is

$$\begin{aligned} x &= C_x + R\sin(\theta + \beta) \\
y &= C_y - R\cos(\theta + \beta) \\
\theta &= \theta + \beta
\end{aligned}
$$

Once we substitute in for $C$ we get

$$\begin{aligned} x &= x - R\sin(\theta) + R\sin(\theta + \beta) \\
y &= y + R\cos(\theta) - R\cos(\theta + \beta) \\
\theta &= \theta + \beta
\end{aligned}
$$

You don't really need to understand this math in detail, as it is already a simplification of the real motion. The important thing to recognize is that our motion model is nonlinear, and we will need to deal with that with our Kalman filter.

### Design the State Variables

For our robot we will maintain the position and orientation of the robot:

$$\mathbf{x} = \begin{bmatrix}x \\ y \\ \theta\end{bmatrix}$$

I could include velocities into this model, but as you will see the math will already be quite challenging.

Our control input $\mathbf{u}$ is the velocity and steering angle

$$\mathbf{u} = \begin{bmatrix}v \\ \alpha\end{bmatrix}$$

### Design the System Model

In general we model our system as a nonlinear motion model plus noise.

$$x^- = x + f(x, u) + \mathcal{N}(0, Q)$$

Using the motion model for a robot that we created above, we can expand this to

$$\begin{bmatrix}x\\y\\\theta\end{bmatrix}^- = \begin{bmatrix}x\\y\\\theta\end{bmatrix} +
\begin{bmatrix}- R\sin(\theta) + R\sin(\theta + \beta) \\
R\cos(\theta) - R\cos(\theta + \beta) \\
\beta\end{bmatrix}$$

We linearize this with a taylor expansion at $x$:

$$f(x, u) \approx \mathbf{x} + \frac{\partial f(x, u)}{\partial x}$$

We replace $f(x, u)$ with our state estimate $\mathbf{x}$, and the derivative is the Jacobian of $f$.

The Jacobian $\mathbf{F}$ is

$$\mathbf{F} = \frac{\partial f(x, u)}{\partial x} =\begin{bmatrix}
\frac{\partial \dot{x}}{\partial x} &
\frac{\partial \dot{x}}{\partial y} &
\frac{\partial \dot{x}}{\partial \theta}\\
\frac{\partial \dot{y}}{\partial x} &
\frac{\partial \dot{y}}{\partial y} &
\frac{\partial \dot{y}}{\partial \theta} \\
\frac{\partial \dot{\theta}}{\partial x} &
\frac{\partial \dot{\theta}}{\partial y} &
\frac{\partial \dot{\theta}}{\partial \theta}
\end{bmatrix}
$$

When we calculate these we get

$$\mathbf{F} = \begin{bmatrix}
1 & 0 & -R\cos(\theta) + R\cos(\theta+\beta) \\
0 & 1 & -R\sin(\theta) + R\sin(\theta+\beta) \\
0 & 0 & 1
\end{bmatrix}$$

We can double check our work with SymPy.

```python
from sympy import symbols
a, x, y, v, w, theta, time = symbols('a, x, y, v, w, theta, t')
d = v*time
beta = (d/w)*sympy.tan(a)
R = w/sympy.tan(a)

fxu = Matrix([[x-R*sympy.sin(theta)+R*sympy.sin(theta+beta)],
              [y+R*sympy.cos(theta)-R*sympy.cos(theta+beta)],
              [theta+beta]])

fxu.jacobian(Matrix([x, y, theta]))
```

$$\left[\begin{matrix}1 & 0 & - \frac{w \cos{\left (\theta \right )}}{\tan{\left (a \right )}} + \frac{w}{\tan{\left (a \right )}} \cos{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )}\\0 & 1 & - \frac{w \sin{\left (\theta \right )}}{\tan{\left (a \right )}} + \frac{w}{\tan{\left (a \right )}} \sin{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )}\\0 & 0 & 1\end{matrix}\right]$$

Now we can turn our attention to the noise. Here, the noise is in our control input, so it is in *control space*. In other words, we command a specific velocity and steering angle, but we need to convert that into errors in $x, y, \theta$. In a real system this might vary depending on velocity, so it will need to be recomputed for every prediction. I will chose this as the noise model; for a real robot you will need to choose a model that accurately depicts the error in your system.

$$\mathbf{M} = \begin{bmatrix}0.01 vel^2 & 0 \\ 0 & \sigma_\alpha^2\end{bmatrix}$$

If this was a linear problem we would convert from control space to state space using the by now familiar $\mathbf{FMF}^\mathsf{T}$ form. Since our motion model is nonlinear we do not try to find a closed form solution to this, but instead linearize it with a Jacobian which we will name $\mathbf{V}$.

$$\mathbf{V} = \frac{\partial f(x, u)}{\partial u} \begin{bmatrix}
\frac{\partial \dot{x}}{\partial v} & \frac{\partial \dot{x}}{\partial \alpha} \\
\frac{\partial \dot{y}}{\partial v} & \frac{\partial \dot{y}}{\partial \alpha} \\
\frac{\partial \dot{\theta}}{\partial v} & \frac{\partial \dot{\theta}}{\partial \alpha}
\end{bmatrix}$$

Let's compute that with SymPy:

```python
fxu.jacobian(Matrix([v, a]))
```

$$\left[\begin{matrix}t \cos{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )} & \frac{t v}{\tan{\left (a \right )}} \left(\tan^{2}{\left (a \right )} + 1\right) \cos{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )} - \frac{w \sin{\left (\theta \right )}}{\tan^{2}{\left (a \right )}} \left(- \tan^{2}{\left (a \right )} - 1\right) + \frac{w}{\tan^{2}{\left (a \right )}} \left(- \tan^{2}{\left (a \right )} - 1\right) \sin{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )}\\t \sin{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )} & \frac{t v}{\tan{\left (a \right )}} \left(\tan^{2}{\left (a \right )} + 1\right) \sin{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )} + \frac{w \cos{\left (\theta \right )}}{\tan^{2}{\left (a \right )}} \left(- \tan^{2}{\left (a \right )} - 1\right) - \frac{w}{\tan^{2}{\left (a \right )}} \left(- \tan^{2}{\left (a \right )} - 1\right) \cos{\left (\frac{t v}{w} \tan{\left (a \right )} + \theta \right )}\\\frac{t}{w} \tan{\left (a \right )} & \frac{t v}{w} \left(\tan^{2}{\left (a \right )} + 1\right)\end{matrix}\right]$$

**authors note: explain FPF better**

This gives us the final form of our prediction equations:

$$\begin{aligned}
\mathbf{x}^- &= \mathbf{x} +
\begin{bmatrix}- R\sin(\theta) + R\sin(\theta + \beta) \\
R\cos(\theta) - R\cos(\theta + \beta) \\
\beta\end{bmatrix}\\
\mathbf{P}^- &=\mathbf{FPF}^{\mathsf{T}} + \mathbf{VMV}^{\mathsf{T}}
\end{aligned}$$

One final point. This form of linearization is not the only way to predict $\mathbf{x}$. For example, we could use a numerical integration technique like *Runge Kutta* to compute the position of the robot in the future. In fact, if the time step is relatively large you will have to do that. As I am sure you are realizing, things are not as cut and dried with the EKF as it was for the KF. For a real problem you have to very carefully model your system with differential equations and then determine the most appropriate way to solve that system. The correct approach depends on the accuracy you require, how nonlinear the equations are, your processor budget, and numerical stability concerns. These are all topics beyond the scope of this book.

### Design the Measurement Model

Now we need to design our measurement model. For this problem we are assuming that we have a sensor that receives a noisy bearing and range to multiple known locations in the landscape. The measurement model must convert the state $\begin{bmatrix}x & y&\theta\end{bmatrix}^\mathsf{T}$ into a range and bearing to the landmark. Using $p$ be the position of a landmark, the range $r$ is

$$r = \sqrt{(p_x - x)^2 + (p_y - y)^2}$$

We assume that the sensor provides bearing relative to the orientation of the robot, so we must subtract the robot's orientation from the bearing to get the sensor reading, like so:

$$\phi = \arctan(\frac{p_y - y}{p_x - x}) - \theta$$

Thus our function is

$$\begin{aligned}
\mathbf{x}& = h(x,p) &+ \mathcal{N}(0, R)\\
&= \begin{bmatrix}
\sqrt{(p_x - x)^2 + (p_y - y)^2} \\
\arctan(\frac{p_y - y}{p_x - x}) - \theta
\end{bmatrix} &+ \mathcal{N}(0, R)
\end{aligned}$$

This is clearly nonlinear, so we need linearize $h(x, p)$  at $\mathbf{x}$ by taking its Jacobian. We compute that with SymPy below.

```python
px, py = symbols('px, py')
z = Matrix([[sympy.sqrt((px-x)**2 + (py-y)**2)],
            [sympy.atan2(py-y, px-x) - theta]])
z.jacobian(Matrix([x, y, theta]))
```

$$\left[\begin{matrix}\frac{- px + x}{\sqrt{\left(px - x\right)^{2} + \left(py - y\right)^{2}}} & \frac{- py + y}{\sqrt{\left(px - x\right)^{2} + \left(py - y\right)^{2}}} & 0\\- \frac{- py + y}{\left(px - x\right)^{2} + \left(py - y\right)^{2}} & - \frac{px - x}{\left(px - x\right)^{2} + \left(py - y\right)^{2}} & -1\end{matrix}\right]$$

Now we need to write that as a Python function. For example we might write:

```python
from math import sqrt

def H_of(x, landmark_pos):
    """ compute Jacobian of H matrix where h(x) computes the range and
    bearing to a landmark for state x """

    px = landmark_pos[0]
    py = landmark_pos[1]
    hyp = (px - x[0, 0])**2 + (py - x[1, 0])**2
    dist = sqrt(hyp)

    H = array(
        [[-(px - x[0, 0]) / dist, -(py - x[1, 0]) / dist, 0],
         [ (py - x[1, 0]) / hyp,  -(px - x[0, 0]) / hyp, -1]])
    return H
```

We also need to define a function that converts the system state into a measurement.

```python
from math import atan2
def Hx(x, landmark_pos):
    """ takes a state variable and returns the measurement that would
    correspond to that state.
    """
    px = landmark_pos[0]
    py = landmark_pos[1]
    dist = sqrt((px - x[0, 0])**2 + (py - x[1, 0])**2)

    Hx = array([[dist],
                [atan2(py - x[1, 0], px - x[0, 0]) - x[2, 0]]])
    return Hx
```

### Design Measurement Noise

This is quite straightforward as we need to specify measurement noise in measurement space, hence it is linear. It is reasonable to assume that the range and bearing measurement noise is independent, hence

$$R=\begin{bmatrix}\sigma_{range}^2 & 0 \\ 0 & \sigma_{bearing}^2\end{bmatrix}$$

### Implementation

We will use `FilterPy`'s `ExtendedKalmanFilter` class to implment the filter. The prediction of $\mathbf{x}$ is nonlinear, so we will have to override the method `predict()` to implement this. I'll want to also use this code to simulate the robot, so I'll add a method `move()` that computes the position of the robot which both `predict()` and my simulation can call. You would not need to do this for a real robot, of course.

The matrices for the prediction step are quite large; while trying to implement this I made several errors before I finally got it working. I only found my errors by using SymPy's `evalf` function, which allows you to evaluate a SymPy `Matrix` for specific values of the variables. I decided to demonstrate this technique, and to eliminate a possible source of bugs, by using SymPy in the Kalman filter. You'll need to understand a couple of points.

First, `evalf` uses a dictionary to pass in the values you want to use. For example, if your matrix contains an x and y, you can write

    M.evalf(subs={x:3, y:17})

to evaluate the matrix for `x=3` and `y=17`.

Second, `evalf` returns a `sympy.Matrix` object. You can convert it to a numpy array with `numpy.array(m)`, but the result uses type `object` for the elements in the array. You can convert the array to an array of floats with ``numpy.array(m).astype(float)`.

So, here is the code:

```python
from filterpy.kalman import ExtendedKalmanFilter as EKF
from numpy import dot, array, sqrt
class RobotEKF(EKF):
    def __init__(self, dt, wheelbase, sigma_vel, sigma_steer):
        EKF.__init__(self, 3, 2, 2)
        self.dt = dt
        self.wheelbase = wheelbase
        self.sigma_vel = sigma_vel
        self.sigma_steer = sigma_steer

        a, x, y, v, w, theta, time = symbols('a, x, y, v, w, theta, t')
        d = v*time
        beta = (d/w)*sympy.tan(a)
        r = w/sympy.tan(a)

        self.fxu = Matrix([[x-r*sympy.sin(theta)+r*sympy.sin(theta+beta)],
                           [y+r*sympy.cos(theta)-r*sympy.cos(theta+beta)],
                           [theta+beta]])

        self.F_j = self.fxu.jacobian(Matrix([x, y, theta]))
        self.V_j = self.fxu.jacobian(Matrix([v, a]))

        # save dictionary and it's variables for later use
        self.subs = {x: 0, y: 0, v:0, a:0, time:dt, w:wheelbase, theta:0}
        self.x_x, self.x_y, self.v, self.a, self.theta = x, y, v, a, theta

    def predict(self, u=0):
        self.x = self.move(self.x, u, self.dt)

        self.subs[self.theta] = self.x[2, 0]
        self.subs[self.v] = u[0]
        self.subs[self.a] = u[1]

        F = array(self.F_j.evalf(subs=self.subs)).astype(float)
        V = array(self.V_j.evalf(subs=self.subs)).astype(float)

        # covariance of motion noise in control space
        M = array([[self.sigma_vel*u[0]**2, 0], [0, self.sigma_steer**2]])

        self.P = dot(F, self.P).dot(F.T) + dot(V, M).dot(V.T)

    def move(self, x, u, dt):
        h = x[2, 0]
        v = u[0]
        steering_angle = u[1]

        dist = v*dt

        if abs(steering_angle) < 0.0001:
            # approximate straight line with huge radius
            r = 1.e-30
        b = dist / self.wheelbase * tan(steering_angle)
        r = self.wheelbase / tan(steering_angle) # radius
        sinh = sin(h)
        sinhb = sin(h + b)
        cosh = cos(h)
        coshb = cos(h + b)
        return x + array([[-r*sinh + r*sinhb],
                          [r*cosh - r*coshb],
                          [b]])
```

Now we have another issue to handle. The residual is notionally computed as $y = z - h(x)$ but this will not work because our measurement contains an angle in it. Suppose z has a bearing of $1^\circ$ and $h(x)$ has a bearing of $359^\circ$. Naively subtracting them would yield a bearing difference of $-358^\circ$, which will throw off the computation of the Kalman gain. The correct angle difference in this case is $-2^\circ$. So we will have to write code to correctly compute the bearing residual.

```python
def residual(a,b):
    """ compute residual between two measurement containing [range, bearing]. Bearing
    is normalized to [0, 360)"""
    y = a - b
    if y[1] > np.pi:
        y[1] -= 2*np.pi
    if y[1] < -np.pi:
        y[1] += 2*np.pi
    return y
```

The rest of the code runs the simulation and plots the results, and shouldn't need too much comment by now. I create a variable `landmarks` that contains the coordinates of the landmarks. I update the simulated robot position 10 times a second, but run the EKF only once. This is for two reasons. First, we are not using Runge Kutta to integrate the differental equations of motion, so a narrow time step allows our simulation to be more accurate. Second, it is fairly normal in embedded systems to have limited processing speed. This forces you to run your Kalman filter only as frequently as absolutely needed.

```python
from filterpy.stats import plot_covariance_ellipse
from math import sqrt, tan, cos, sin, atan2
dt = 1.0

def run_localization(landmarks, sigma_vel, sigma_steer, sigma_range, sigma_bearing):
    ekf = RobotEKF(dt, wheelbase=0.5, sigma_vel=sigma_vel, sigma_steer=sigma_steer)
    ekf.x = array([[2, 6, .3]]).T
    ekf.P = np.diag([.1, .1, .1])
    ekf.R = np.diag([sigma_range**2, sigma_bearing**2])

    sim_pos = ekf.x.copy() # simulated position
    u = array([1.1, .01]) # steering command (vel, steering angle radians)

    plt.scatter(landmarks[:, 0], landmarks[:, 1], marker='s', s=60)
    for i in range(200):
        sim_pos = ekf.move(sim_pos, u, dt/10.) # simulate robot
        plt.plot(sim_pos[0], sim_pos[1], ',', color='g')

        if i % 10 == 0:
            ekf.predict(u=u)

            plot_covariance_ellipse((ekf.x[0,0], ekf.x[1,0]), ekf.P[0:2, 0:2], std=6,
                                    facecolor='b', alpha=0.08)

            x, y = sim_pos[0, 0], sim_pos[1, 0]
            for lmark in landmarks:
                d = np.sqrt((lmark[0] - x)**2 + (lmark[1] - y)**2)
                a = atan2(lmark[1] - y, lmark[0] - x) - sim_pos[2, 0]
                z = np.array([[d + randn()*sigma_range], [a + randn()*sigma_bearing]])

                ekf.update(z, HJacobian=H_of, Hx=Hx, residual=residual,
                           args=(lmark), hx_args=(lmark))

            plot_covariance_ellipse((ekf.x[0,0], ekf.x[1,0]), ekf.P[0:2, 0:2], std=6,
                                    facecolor='g', alpha=0.4)
    plt.axis('equal')
    plt.show()
    return ekf
```

```python
landmarks = array([[5, 10], [10, 5], [15, 15], [20, 5]])
landmarks = array([[5, 10], [10, 5], [15, 15]])

ekf = run_localization(landmarks, sigma_vel=0.1, sigma_steer=np.radians(1),
                      sigma_range=0.3, sigma_bearing=0.1)
print(ekf.P.diagonal())
```

```python
landmarks = array([[5, 10], [10, 5], [15, 15], [20, 5]])

ekf = run_localization(landmarks, sigma_vel=0.1, sigma_steer=np.radians(1),
                      sigma_range=0.3, sigma_bearing=0.1)
print(ekf.P.diagonal())
```

I have plotted the landmarks as solid squares. The path of the robot is drawn with a dashed line, which is admittedly hard to see. The covariance after the predict step is drawn in a very light shade of blue, and the covariance after all of the landmark measurements are incorporated are shown in green. To make them visible at this scale I have set the ellipse boundary at 6$\sigma$.

From this we can see that there is a lot of uncertainty added by our motion model, and that most of the error in in the direction of motion. We can see that from the shape of the blue ellipses. After a few steps we can see that the filter incorporates the landmark measurements.

We can see the fantastic effect that multiple landmarks has on our uncertainty by only using the first two landmarks.

```python
ekf = run_localization(landmarks[0:2], sigma_vel=1.e-10, sigma_steer=1.e-10,
                       sigma_range=1.4, sigma_bearing=.05)
print(ekf.P.diagonal())
```

We can see that the covariance gets smaller as it passes through the landmarks but quickly expands once past them. Let's see what happens with only one landmark

```python
ekf = run_localization(landmarks[0:1], sigma_vel=1.e-10, sigma_steer=1.e-10,
                       sigma_range=1.4, sigma_bearing=.05)
print(ekf.P.diagonal())
```

As you probably suspected, only one landmark produces a very bad covariance. What is worse, the filter starts to diverge from the robot's path. On the other hand, a large number of landmarks allows us to make very accurate estimates.

```python
landmarks = array([[5, 10], [10,  5], [15, 15], [20,  5], [15, 10],
                   [10,14], [23, 14], [25, 25], [10, 20]])

ekf = run_localization(landmarks, sigma_vel=0.1, sigma_steer=np.radians(1),
                      sigma_range=0.3, sigma_bearing=0.1)
print(ekf.P.diagonal())
```

### Discussion

I said that this was a 'real' problem, and in some ways it is. I've seen alternative presentations that used robot motion models that led to much easier Jacobians. On the other hand, my model of a automobile's movement is itself simplistic in several ways. First, it uses the *bicycle model* to compute how it moves. A real car has two sets of tires, and each travels on a different radius. The wheels do not grip the surface perfectly. I also assumed that the robot was able to instaneously respond to my control input changes. In fact, I didn't even bother changing the control input during the run. Sebastian Thrun writes in *Probabalistic Robots* that simplied models are justified because the filters perform well when used to track real vehicles. The lesson here is that while you have to have a reasonably accurate nonlinear model, it does not need to be perfect to operate well. As a designer you will need to balance the fidelity of your model with the difficulty of the math and the computation required to implement the equations.

Another way in which this problem was simplistic is that we assumed that we knew the correspondance between the landmarks and measurements. But suppose we are using radar - how would we know that a specific signal return corresponded to a specific building in the local scene? This question hints at SLAM algorithms - simultaneous localization and mapping. SLAM is not the point of this book, so I will not elaborate on this topic.

However, this example should underscore how difficult EKFs can be. EKF have a well deserved reputation for difficulty. Especially when the problem is highly nonlinear you must design

## UKF vs EKF

I implemented this tracking problem using and unscented Kalman filter in the previous chapter. The difference in implementation should be very clear. Computing the Jacobians for the state and measurement models was not trivial and we used a very rudimentary model for the motion of the car. I am justified in using this model because the research resulting from the DARPA car challenges has shown that it works well in practice. Nonetheless, a different problem, such as an aircraft or rocket will yield a very difficult to impossible to compute Jacobian. In contrast, the UKF only requires you to provide a function that computes the system motion model and another for the measurement model. This is will always be easier than deriving a Jacobian analytically. In fact, there are many physical processes for which we cannot find an analytical solution. It is beyond the scope of this book, but in that case you have to design a numerical method to compute the Jacobian. That is a very nontrivial undertaking, and you will spend a significant portion of a master's degree at a STEM school learning various techniques to handle such situations. Even then you'll likely only be able to solve problems related to your field - an aeronautical engineer learns a lot about Navier Stokes equations, but not much about modelling chemical reaction rates.

So, UKFs are easy. Are they accurate? Everything I have read states that there is no way to prove that a UKF will always perform as well or better than an EKF. However, in practice, they do perform better. You can search and find any number of research papers that prove that the UKF outperforms the EKF in various problem domains. It's not hard to understand why this would be true. The EKF works by linearizing the system model and measurement model at a single point.

Let's look at a specific example. I will take the function $f(x) = x^3$ as our nonlinear function and pass a Gaussian distribution through it. I will compute an accurate answer using a monte carlo simulation. I do this by generating 50,000 points distributed according to the Gaussian, passing each point through the function, and then computing the mean and variance of the result.

First, let's see how the EKF fairs. The EkF linearizes the function by taking the derivative and evaluating it the mean $x$ to get the slope tangent to the function at that point. This slope becomes the linear function that we use to transform the Gaussian. Here is a plot of that.

```python
import nonlinear_plots
nonlinear_plots.plot_ekf_vs_mc()
```

We can see from both the graph and the print out at the bottom that the EKF has introduced quite a bit of error.

In contrast, here is the performance of the UKF evaluated with the same Gaussian and function.b

```python
nonlinear_plots.plot_ukf_vs_mc(alpha=0.001, beta=3., kappa=1.)
```

Here we can see that the computation of the UKF's mean is accurate to 2 decimal places. The standard deviation is slightly off, but you can also fine tune how the UKF computes the distribution by using the $\alpha$, $\beta$, and $\gamma$ parameters for generating the sigma points. Here I used $\alpha=0.001$, $\beta=3$, and $\gamma=1$. Feel free to modify them in the function call to see the result - you should be able to get better results than I did. However, ovoid overtuning the UKF for a specific test - it may perform better for your test case, but worse in general.

This is one contrived example, but as I said the literature is filled with detailed studies of real world problems that exhibit similar performance differences between the two filters.


```python
import numpy as np
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import sympy as sym
from sympy.plotting import plot
import pandas as pd
from IPython.display import display
from IPython.core.display import Math
from scipy.optimize import minimize
```

QUESTÃO 3

```python
def cf_f1(x):
    """Cost function: sum of forces."""
    return x[0] + x[1] + x[2]

def cf_f2(x):
    """Cost function: sum of forces squared."""
    return x[0]**2 + x[1]**2 + x[2]**2

def cf_fpcsa2(x, a):
    """Cost function: sum of squared muscle stresses."""
    return (x[0]/a[0])**2 + (x[1]/a[1])**2 + (x[2]/a[2])**2

def cf_fmmax3(x, m):
    """Cost function: sum of cubic forces normalized by moments."""
    return (x[0]/m[0])**3 + (x[1]/m[1])**3 + (x[2]/m[2])**3
def cf_f1d(x):
    """Derivative of cost function: sum of forces."""
    dfdx0 = 1
    dfdx1 = 1
    dfdx2 = 1
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_f2d(x):
    """Derivative of cost function: sum of forces squared."""
    dfdx0 = 2*x[0]
    dfdx1 = 2*x[1]
    dfdx2 = 2*x[2]
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_fpcsa2d(x, a):
    """Derivative of cost function: sum of squared muscle stresses."""
    dfdx0 = 2*x[0]/a[0]**2
    dfdx1 = 2*x[1]/a[1]**2
    dfdx2 = 2*x[2]/a[2]**2
    return np.array([dfdx0, dfdx1, dfdx2])

def cf_fmmax3d(x, m):
    """Derivative of cost function: sum of cubic forces normalized by moments."""
    dfdx0 = 3*x[0]**2/m[0]**3
    dfdx1 = 3*x[1]**2/m[1]**3
    dfdx2 = 3*x[2]**2/m[2]**3
    return np.array([dfdx0, dfdx1, dfdx2])
```

```python
# time elbow_flexion BIClong BICshort BRA
r_ef = np.loadtxt('./../../../data/r_elbowflexors.mot', skiprows=7)
f_ef = np.loadtxt('./../../../data/f_elbowflexors.mot', skiprows=7)

m_ef = r_ef*1
m_ef[:, 2:] = r_ef[:, 2:]*f_ef[:, 2:]

a_ef = np.array([624.3, 435.56, 987.26])/50  # 50 N/cm2
```

3.1

```python
M = 33  # desired torque at the elbow
iang = 92  # which will give the closest value to 90 degrees

r  = r_ef[iang, 2:]
f0 = f_ef[iang, 2:]
a  = a_ef
m  = m_ef[iang, 2:]
x0 = f_ef[iang, 2:]/235  # far from the correct answer for the sum of torques
print('M =', M)
print('x0 =', x0)
print('r * x0 =', np.sum(r*x0))

bnds = ((0, f0[0]), (0, f0[1]), (0, f0[2]))
```

```python
# use this in combination with the parameter bounds:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)})
# to enter everything as constraints:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[0]-x[0],
         'jac' : lambda x, r, f0, M: np.array([-1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[1]-x[1],
         'jac' : lambda x, r, f0, M: np.array([0, -1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[2]-x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, -1]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[0],
         'jac' : lambda x, r, f0, M: np.array([1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[1],
         'jac' : lambda x, r, f0, M: np.array([0, 1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, 1]), 'args': (r, f0, M)})
```

```python
f1r = minimize(fun=cf_f1, x0=x0, args=(), jac=cf_f1d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
f2r = minimize(fun=cf_f2, x0=x0, args=(), jac=cf_f2d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
fpcsa2r = minimize(fun=cf_fpcsa2, x0=x0, args=(a,), jac=cf_fpcsa2d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
fmmax3r = minimize(fun=cf_fmmax3, x0=x0, args=(m,), jac=cf_fmmax3d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

3.2

```python
M = 17  # desired torque at the elbow
iang = 79.3  # which will give the closest value to 90 degrees

r  = r_ef[iang, 2:]
f0 = f_ef[iang, 2:]
a  = a_ef
m  = m_ef[iang, 2:]
x0 = f_ef[iang, 2:]/235  # far from the correct answer for the sum of torques
print('M =', M)
print('x0 =', x0)
print('r * x0 =', np.sum(r*x0))

bnds = ((0, f0[0]), (0, f0[1]), (0, f0[2]))
```

```python
# use this in combination with the parameter bounds:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)})
# to enter everything as constraints:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[0]-x[0],
         'jac' : lambda x, r, f0, M: np.array([-1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[1]-x[1],
         'jac' : lambda x, r, f0, M: np.array([0, -1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[2]-x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, -1]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[0],
         'jac' : lambda x, r, f0, M: np.array([1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[1],
         'jac' : lambda x, r, f0, M: np.array([0, 1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, 1]), 'args': (r, f0, M)})
```

```python
f1r = minimize(fun=cf_f1, x0=x0, args=(), jac=cf_f1d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
f2r = minimize(fun=cf_f2, x0=x0, args=(), jac=cf_f2d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
fpcsa2r = minimize(fun=cf_fpcsa2, x0=x0, args=(a,), jac=cf_fpcsa2d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
fmmax3r = minimize(fun=cf_fmmax3, x0=x0, args=(m,), jac=cf_fmmax3d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

QUESTÃO 4

4.1

```python
M = 10  # desired torque at the elbow

f0 = np.array([1000, 250, 700])
r  = np.array([2, 5, 4])
a  = np.array([33, 8, 23])

m = r*f0
x0 = f0*10  # far from the correct answer for the sum of torques
print('M =', M)
print('x0 =', x0)
print('r * x0 =', np.sum(r*x0))

bnds = ((0, f0[0]), (0, f0[1]), (0, f0[2]))
```

    M = 10
    x0 = [10000  2500  7000]
    r * x0 = 60500

```python
# use this in combination with the parameter bounds:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)})
# to enter everything as constraints:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[0]-x[0],
         'jac' : lambda x, r, f0, M: np.array([-1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[1]-x[1],
         'jac' : lambda x, r, f0, M: np.array([0, -1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[2]-x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, -1]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[0],
         'jac' : lambda x, r, f0, M: np.array([1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[1],
         'jac' : lambda x, r, f0, M: np.array([0, 1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, 1]), 'args': (r, f0, M)})
```

```python
f1r = minimize(fun=cf_f1, x0=x0, args=(), jac=cf_f1d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
f2r = minimize(fun=cf_f2, x0=x0, args=(), jac=cf_f2d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
fpcsa2r = minimize(fun=cf_fpcsa2, x0=x0, args=(a,), jac=cf_fpcsa2d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
fmmax3r = minimize(fun=cf_fmmax3, x0=x0, args=(m,), jac=cf_fmmax3d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 2.0
                Iterations: 5
                Function evaluations: 5
                Gradient evaluations: 5
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 2.22222222279
                Iterations: 6
                Function evaluations: 7
                Gradient evaluations: 6
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 0.00693490166681
                Iterations: 14
                Function evaluations: 14
                Gradient evaluations: 14
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 1.56234818338e-08
                Iterations: 2
                Function evaluations: 3
                Gradient evaluations: 2

```python
dat = np.vstack((np.around(r*100,1), np.around(a,1), np.around(f0,0), np.around(m,1)))
opt = np.around(np.vstack((f1r.x, f2r.x, fpcsa2r.x, fmmax3r.x)), 1)
er = ['-', '-', '-', '-',
      np.sum(r*f1r.x)-M, np.sum(r*f2r.x)-M, np.sum(r*fpcsa2r.x)-M, np.sum(r*fmmax3r.x)-M]
data = np.vstack((np.vstack((dat, opt)).T, er)).T

rows = ['$\text{Moment arm}\;[cm]$', '$pcsa\;[cm^2]$', '$F_{max}\;[N]$', '$M_{max}\;[Nm]$',
        '$\sum F_i$', '$\sum F_i^2$', '$\sum(F_i/pcsa_i)^2$', '$\sum(F_i/M_{max,i})^3$']
cols = ['Biceps long head', 'Biceps short head', 'Brachialis', 'Error in M']
df = pd.DataFrame(data, index=rows, columns=cols)
print('\nComparison of different cost functions for solving the distribution problem')
df
```

    Comparison of different cost functions for solving the distribution problem

      Biceps long head
      Biceps short head
      Brachialis
      Error in M

      $\text{Moment arm}\;[cm]$
      200.0
      500.0
      400.0
      -

      $pcsa\;[cm^2]$
      33.0
      8.0
      23.0
      -

      $F_{max}\;[N]$
      1000.0
      250.0
      700.0
      -

      $M_{max}\;[Nm]$
      2000.0
      1250.0
      2800.0
      -

      $\sum F_i$
      -0.0
      2.0
      -0.0
      -1.7763568394e-15

      $\sum F_i^2$
      0.4
      1.1
      0.9
      0.0

      $\sum(F_i/pcsa_i)^2$
      1.5
      0.2
      1.5
      0.0

      $\sum(F_i/M_{max,i})^3$
      5.0
      0.0
      0.0
      0.0

4.2

```python
M = 10  # desired torque at the elbow

f0 = np.array([5000, 2850, 1700])
r  = np.array([2, 5, 4])
a  = np.array([33, 8, 23])

m = r*f0
x0 = f0/235  # far from the correct answer for the sum of torques
print('M =', M)
print('x0 =', x0)
print('r * x0 =', np.sum(r*x0))

bnds = ((0, f0[0]), (0, f0[1]), (0, f0[2]))
```

    M = 10
    x0 = [ 21.27659574  12.12765957   7.23404255]
    r * x0 = 132.127659574

```python
# use this in combination with the parameter bounds:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)})
# to enter everything as constraints:
cons = ({'type': 'eq',
         'fun' : lambda x, r, f0, M: np.array([r[0]*x[0] + r[1]*x[1] + r[2]*x[2] - M]),
         'jac' : lambda x, r, f0, M: np.array([r[0], r[1], r[2]]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[0]-x[0],
         'jac' : lambda x, r, f0, M: np.array([-1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[1]-x[1],
         'jac' : lambda x, r, f0, M: np.array([0, -1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: f0[2]-x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, -1]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[0],
         'jac' : lambda x, r, f0, M: np.array([1, 0, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[1],
         'jac' : lambda x, r, f0, M: np.array([0, 1, 0]), 'args': (r, f0, M)},
        {'type': 'ineq', 'fun' : lambda x, r, f0, M: x[2],
         'jac' : lambda x, r, f0, M: np.array([0, 0, 1]), 'args': (r, f0, M)})
```

```python
f1r = minimize(fun=cf_f1, x0=x0, args=(), jac=cf_f1d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
f2r = minimize(fun=cf_f2, x0=x0, args=(), jac=cf_f2d,
               constraints=cons, method='SLSQP',
               options={'disp': True})
fpcsa2r = minimize(fun=cf_fpcsa2, x0=x0, args=(a,), jac=cf_fpcsa2d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
fmmax3r = minimize(fun=cf_fmmax3, x0=x0, args=(m,), jac=cf_fmmax3d,
                   constraints=cons, method='SLSQP',
                   options={'disp': True})
```

    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 2.0
                Iterations: 6
                Function evaluations: 6
                Gradient evaluations: 6
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 2.22222226434
                Iterations: 6
                Function evaluations: 6
                Gradient evaluations: 6
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 0.00693481358806
                Iterations: 11
                Function evaluations: 11
                Gradient evaluations: 11
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: 1.25e-10
                Iterations: 1
                Function evaluations: 2
                Gradient evaluations: 1

```python
dat = np.vstack((np.around(r*100,1), np.around(a,1), np.around(f0,0), np.around(m,1)))
opt = np.around(np.vstack((f1r.x, f2r.x, fpcsa2r.x, fmmax3r.x)), 1)
er = ['-', '-', '-', '-',
      np.sum(r*f1r.x)-M, np.sum(r*f2r.x)-M, np.sum(r*fpcsa2r.x)-M, np.sum(r*fmmax3r.x)-M]
data = np.vstack((np.vstack((dat, opt)).T, er)).T

rows = ['$\text{Moment arm}\;[cm]$', '$pcsa\;[cm^2]$', '$F_{max}\;[N]$', '$M_{max}\;[Nm]$',
        '$\sum F_i$', '$\sum F_i^2$', '$\sum(F_i/pcsa_i)^2$', '$\sum(F_i/M_{max,i})^3$']
cols = ['Biceps long head', 'Biceps short head', 'Brachialis', 'Error in M']
df = pd.DataFrame(data, index=rows, columns=cols)
print('\nComparison of different cost functions for solving the distribution problem')
df
```

    Comparison of different cost functions for solving the distribution problem

      Biceps long head
      Biceps short head
      Brachialis
      Error in M

      $\text{Moment arm}\;[cm]$
      200.0
      500.0
      400.0
      -

      $pcsa\;[cm^2]$
      33.0
      8.0
      23.0
      -

      $F_{max}\;[N]$
      5000.0
      2850.0
      1700.0
      -

      $M_{max}\;[Nm]$
      10000.0
      14250.0
      6800.0
      -

      $\sum F_i$
      0.0
      2.0
      0.0
      0.0

      $\sum F_i^2$
      0.4
      1.1
      0.9
      0.0

      $\sum(F_i/pcsa_i)^2$
      1.5
      0.2
      1.5
      0.0

      $\sum(F_i/M_{max,i})^3$
      5.0
      -0.0
      0.0
      4.08562073062e-14

```python

```