# Regressão linear 1: equações normais

Dado o dataset $(\mathbf{x}_{1}, y_{1}), \dots ,(\mathbf{x}_{N}, y_{N})$ onde $\mathbf{x}_i \in \mathbb{R}^{d}$ e $y_i \in \mathbb{R}$, podemos aproximar a função desconhecida $f:\mathbb{R}^{d} \rightarrow \mathbb{R}$ (lembrando que $y_i =f(\mathbf{x}_i)$) por meio de um modelo linear $h$:
$$
h(\mathbf{x}_{i}; \mathbf{w}, b) = \mathbf{w}^\top  \mathbf{x}_{i} + b
$$

Note que $h(\mathbf{x}_{i}; \mathbf{w}, b)$ é na verdade uma [transformação afim](https://en.wikipedia.org/wiki/Affine_transformation) de $\mathbf{x}_{i}$. Como em outros lugares, vamos usar o termo "linear" também para caracterizar uma transformação afim.

A saída de $h$ é uma transformação linear de $\mathbf{x}_{i}$. Usamos a notação $h(\mathbf{x}_{i}; \mathbf{w}, b)$ para deixar claro que $h$ é um modelo parametrizado, i.e., a transformação $h$ é definida pelos parâmetros $\mathbf{w}$ e $b$. Podemos pensar no vetor $\mathbf{w}$ como um vetor de *pesos* controlando o efeito de cada *feature* na predição.

Adicionando uma feature a mais na obsevação $\mathbf{x}_{i}$ (com o valor 1) -- coordenada artificial -- podemos simplificar a notação do modelo:

$$
h(\mathbf{x}_{i}; \mathbf{w}) = \hat{y}_{i} = \mathbf{w}^\top  \mathbf{x}_{i}
$$

Procuramos os melhores parâmetros $\mathbf{w}$ de modo que a predição $\hat{y}_{i}$ seja a mais próxima de $y_{i}$ de acordo com alguma métrica de erro. Usando o *erro quadrárico médio* como tal métrica podemos obter a seguinte função de custo:

\begin{equation}
J(\mathbf{w}) = \frac{1}{N}\sum_{i=1}^{N}(\hat{y}_{i} - y_{i})^{2}
\end{equation}

Desse modo, a tarefa de achar os parâmetros $\mathbf{w}$ se torna a tarefa de encontrar os valores de $\mathbf{w}$ para minimizar $J(\mathbf{w})$.

**Aqui vamos começar a explorar esse modelo olhando para um dataset bem simples**

```python
# all imports
import numpy as np
from util import get_hausing_prices_data, plot_points_regression

%matplotlib inline
```

### O dataset

Os dados que vamos trabalhar vão ser dados artificiais. Vamos pegar 100 observações com apenas uma *feature* (metros quadrados de um imóvel) e com isso vamos associar um valor (o preço desse imóvel em $). Nossa tarefa é construir um modelo que consiga predizer o valor de imóveis.

```python
X, y = get_hausing_prices_data(N=100)
```

    X shape = (100, 1)

    y shape = (100, 1)

    X:
    mean 645.0, sdt 323.65, min 1200.0, max 90.0

    y:
    mean 43777.37109375, sdt 16856.10, min 77876.25, max 5345.7275390625

### Plotando os dados

Acima temos algumas informações sobre os dados, podemos também visualizar cada ponto.

```python
plot_points_regression(X,
                       y,
                       title='Real estate prices prediction',
                       xlabel="m\u00b2",
                       ylabel='$')
```

### Equações normais

Dados $f:\mathbb{R}^{n\times m} \rightarrow \mathbb{R}$ e $\mathbf{A} \in \mathbb{R}^{n\times m}$, definimos a derivada de $f$ com respeito a $\mathbf{A}$ como:

\begin{equation*}
\nabla_{\mathbf{A}}f =  \begin{vmatrix}
\frac{\partial f}{\partial \mathbf{A}_{1,1}} & \dots & \frac{\partial f}{\partial \mathbf{A}_{1,m}} \\
\vdots &  \ddots & \vdots \\
\frac{\partial f}{\partial \mathbf{A}_{n,1}} &  \dots & \frac{\partial f}{\partial \mathbf{A}_{n,m}}
\end{vmatrix}
\end{equation*}

Seja $\mathbf{X} \in \mathbb{R}^{N\times m}$ a matriz cujas linhas são as observações do dataset (também chamada de *design matrix*) e seja $\mathbf{y} \in \mathbb{R}^{N}$ o vetor contendo todos os valores de $y_{i}$ (i.e., $\mathbf{X}_{i,:} = \mathbf{x}_{i}$ e $\mathbf{y}_{i} =y_{i}$). É fácil checar que:

\begin{equation}
J(\mathbf{w}) = \frac{1}{N}(\mathbf{X}\mathbf{w} - \mathbf{y})^{T}(\mathbf{X}\mathbf{w} - \mathbf{y})
\end{equation}

Usando certos conceitos básicos de derivada com matrizes podemos chegar no gradiente de $J(\mathbf{w})$ com respeito a $\mathbf{w}$

\begin{equation}
\nabla_{\mathbf{w}}J(\mathbf{w}) = \frac{2}{N} (\mathbf{X}^{T}\mathbf{X}\mathbf{w} -\mathbf{X}^{T}\mathbf{y})
\end{equation}

Assim, quando $\nabla_{\mathbf{w}}J(\mathbf{w}) = 0$ temos que

\begin{equation}
\mathbf{X}^{T}\mathbf{X}\mathbf{w} = \mathbf{X}^{T}\mathbf{y}
\end{equation}

Desse modo,

\begin{equation}
\mathbf{w} = (\mathbf{X}^{T}\mathbf{X})^{-1}\mathbf{X}^{T}\mathbf{y}
\end{equation}

A solução dada por essas equações são conhecidas como **equações normais**. Note que esse tipo de solução tem um custo, pois conforme cresce o número de variáveis, o tempo da inversão da matriz fica proibitivo. Vale a pena ler [esse material](http://cs229.stanford.edu/notes/cs229-notes1.pdf) para ver o argumento com mais detalhes.

### Exercício
Implemente a predição usando o método de equações normais. Usando apenas a biblioteca **numpy** você deve completar a função abaixo. Lembre que $\mathbf{X} \in \mathbb{R}^{N\times d}$, você precisa adicionar uma componente com apenas 1s a cada observação em $\mathbf{X}$ e realizar a computação descrita acima.

```python
import util
import numpy as np
def normal_equation_prediction(X, y):
    """
    Calculates the prediction using the normal equation method.ddd
    You should add a new column with 1s.

    :param X: design matrix
    :type X: np.array
    :param y: regression targets
    :type y: np.array
    :return: prediction
    :rtype: np.array
    """
    X = np.insert(X, 0, np.array([1 for i in X[0]]), axis = 1)
    weitghts = (np.linalg.pinv(X) @ y).flatten()
    prediction = np.array([weitghts @ i for i in X])
    X = np.delete(X, 0, axis = 1)
    return prediction

X, y = util.get_hausing_prices_data(300, verbose = False)
prediction = normal_equation_prediction(X, y)
util.plot_points_regression(X,
                           y,
                           title='Real estate prices prediction',
                           xlabel="m\u00b2",
                           ylabel='$',
                           prediction=prediction,
                           legend=True)
```

```python

```

```python

```

```python

```

```python

```

```python

```


# DH matrix for the SCARA 4DOF Robot
The dh paramters can be found in this [paper](https://www.atlantis-press.com/proceedings/icmemtc-16/25852282)

```python
%autosave 120
#Robot links lengths in meters
L1 = 250e-3
L2 = 350e-3
d1 = 300e-3
```

    Autosaving every 120 seconds

```python
import sys
sys.path.append('../')
from dh import dh_solver
#create an object
scara = dh_solver()
```

```python
import sympy
from sympy import Symbol

#adding the dh parameters in this order [ d, theta, a, alpha]
scara.add([d1                  ,    Symbol("theta1"),     L1,     0            ])
scara.add([0                   ,    Symbol("theta2"),     L2,     0            ])
scara.add([-1*Symbol("d3")     ,    0               ,     0 ,     sympy.pi   ])
scara.add([0                   ,    Symbol("theta4"),     0 ,     0            ])

```

```python
#to get the dh matrices in symbolic form
T = scara.calc_symbolic_matrices()
print(T)
```

    100%|██████████| 4/4 [00:00<00:00, 48.65it/s]

    Matrix([[((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*cos(theta3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(theta3))*cos(theta4) + (-(-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(theta3)*cos(alpha3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*cos(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*sin(alpha3))*sin(theta4), -((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*cos(theta3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(theta3))*sin(theta4)*cos(alpha4) + ((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(alpha3)*sin(theta3) - (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*cos(alpha3))*sin(alpha4) + (-(-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(theta3)*cos(alpha3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*cos(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*sin(alpha3))*cos(alpha4)*cos(theta4), ((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*cos(theta3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(theta3))*sin(alpha4)*sin(theta4) + ((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(alpha3)*sin(theta3) - (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*cos(alpha3))*cos(alpha4) - (-(-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(theta3)*cos(alpha3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*cos(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*sin(alpha3))*sin(alpha4)*cos(theta4), a1*cos(theta1) - a2*sin(alpha2)*sin(theta1)*cos(alpha1) + a2*cos(theta1)*cos(theta2) + a3*(-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*cos(theta3) + a3*(sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(alpha3) + a4*((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*cos(theta3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(theta3))*cos(theta4) + a4*(-(-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(theta3)*cos(alpha3) + (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*cos(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*sin(alpha3))*sin(alpha4) + d2*sin(alpha1)*sin(theta1) + d3*(sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1)) + d4*((-sin(theta1)*sin(theta2)*cos(alpha1) + cos(theta1)*cos(theta2))*sin(alpha3)*sin(theta3) - (sin(alpha1)*sin(alpha2)*sin(theta1) - sin(theta1)*cos(alpha1)*cos(alpha2)*cos(theta2) - sin(theta2)*cos(alpha2)*cos(theta1))*sin(alpha3)*cos(theta3) + (sin(alpha1)*sin(theta1)*cos(alpha2) + sin(alpha2)*sin(theta1)*cos(alpha1)*cos(theta2) + sin(alpha2)*sin(theta2)*cos(theta1))*cos(alpha3))], [((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*cos(theta3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(theta3))*cos(theta4) + (-(sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(theta3)*cos(alpha3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*cos(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*sin(alpha3))*sin(theta4), -((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*cos(theta3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(theta3))*sin(theta4)*cos(alpha4) + ((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(alpha3)*sin(theta3) - (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*cos(alpha3))*sin(alpha4) + (-(sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(theta3)*cos(alpha3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*cos(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*sin(alpha3))*cos(alpha4)*cos(theta4), ((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*cos(theta3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(theta3))*sin(alpha4)*sin(theta4) + ((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(alpha3)*sin(theta3) - (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*cos(alpha3))*cos(alpha4) - (-(sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(theta3)*cos(alpha3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*cos(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*sin(alpha3))*sin(alpha4)*cos(theta4), a1*sin(alpha1) + a2*sin(alpha2)*cos(alpha1)*cos(theta1) + a2*sin(theta1)*cos(theta2) + a3*(sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*cos(theta3) + a3*(-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(alpha3) + a4*((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*cos(theta3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(theta3))*cos(theta4) + a4*(-(sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(theta3)*cos(alpha3) + (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*cos(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*sin(alpha3))*sin(alpha4) - d2*sin(alpha1)*cos(theta1) + d3*(-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2)) + d4*((sin(theta1)*cos(theta2) + sin(theta2)*cos(alpha1)*cos(theta1))*sin(alpha3)*sin(theta3) - (-sin(alpha1)*sin(alpha2)*cos(theta1) - sin(theta1)*sin(theta2)*cos(alpha2) + cos(alpha1)*cos(alpha2)*cos(theta1)*cos(theta2))*sin(alpha3)*cos(theta3) + (-sin(alpha1)*cos(alpha2)*cos(theta1) + sin(alpha2)*sin(theta1)*sin(theta2) - sin(alpha2)*cos(alpha1)*cos(theta1)*cos(theta2))*cos(alpha3))], [((sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(theta3) + sin(alpha1)*sin(theta2)*cos(theta3))*cos(theta4) + ((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*sin(alpha3) + (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*cos(alpha3)*cos(theta3) - sin(alpha1)*sin(theta2)*sin(theta3)*cos(alpha3))*sin(theta4), -((sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(theta3) + sin(alpha1)*sin(theta2)*cos(theta3))*sin(theta4)*cos(alpha4) + ((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*sin(alpha3) + (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*cos(alpha3)*cos(theta3) - sin(alpha1)*sin(theta2)*sin(theta3)*cos(alpha3))*cos(alpha4)*cos(theta4) + ((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*cos(alpha3) - (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(alpha3)*cos(theta3) + sin(alpha1)*sin(alpha3)*sin(theta2)*sin(theta3))*sin(alpha4), ((sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(theta3) + sin(alpha1)*sin(theta2)*cos(theta3))*sin(alpha4)*sin(theta4) - ((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*sin(alpha3) + (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*cos(alpha3)*cos(theta3) - sin(alpha1)*sin(theta2)*sin(theta3)*cos(alpha3))*sin(alpha4)*cos(theta4) + ((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*cos(alpha3) - (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(alpha3)*cos(theta3) + sin(alpha1)*sin(alpha3)*sin(theta2)*sin(theta3))*cos(alpha4), a2*sin(alpha1)*sin(alpha2) + a3*(sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(alpha3) + a3*sin(alpha1)*sin(theta2)*cos(theta3) + a4*((sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(theta3) + sin(alpha1)*sin(theta2)*cos(theta3))*cos(theta4) + a4*((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*sin(alpha3) + (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*cos(alpha3)*cos(theta3) - sin(alpha1)*sin(theta2)*sin(theta3)*cos(alpha3))*sin(alpha4) + d1 + d2*cos(alpha1) + d3*(-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2)) + d4*((-sin(alpha1)*sin(alpha2)*cos(theta2) + cos(alpha1)*cos(alpha2))*cos(alpha3) - (sin(alpha1)*cos(alpha2)*cos(theta2) + sin(alpha2)*cos(alpha1))*sin(alpha3)*cos(theta3) + sin(alpha1)*sin(alpha3)*sin(theta2)*sin(theta3))], [0, 0, 0, 1]])

```python
scara.T_list
```

    [Matrix([
     [cos(theta1), -sin(theta1)*cos(alpha1),  sin(alpha1)*sin(theta1), a1*cos(theta1)],
     [sin(theta1),  cos(alpha1)*cos(theta1), -sin(alpha1)*cos(theta1), a1*sin(alpha1)],
     [          0,              sin(alpha1),              cos(alpha1),             d1],
     [          0,                        0,                        0,              1]]),
     Matrix([
     [cos(theta2), -sin(theta2)*cos(alpha2),  sin(alpha2)*sin(theta2), a2*cos(theta2)],
     [sin(theta2),  cos(alpha2)*cos(theta2), -sin(alpha2)*cos(theta2), a2*sin(alpha2)],
     [          0,              sin(alpha2),              cos(alpha2),             d2],
     [          0,                        0,                        0,              1]]),
     Matrix([
     [cos(theta3), -sin(theta3)*cos(alpha3),  sin(alpha3)*sin(theta3), a3*cos(theta3)],
     [sin(theta3),  cos(alpha3)*cos(theta3), -sin(alpha3)*cos(theta3), a3*sin(alpha3)],
     [          0,              sin(alpha3),              cos(alpha3),             d3],
     [          0,                        0,                        0,              1]]),
     Matrix([
     [cos(theta4), -sin(theta4)*cos(alpha4),  sin(alpha4)*sin(theta4), a4*cos(theta4)],
     [sin(theta4),  cos(alpha4)*cos(theta4), -sin(alpha4)*cos(theta4), a4*sin(alpha4)],
     [          0,              sin(alpha4),              cos(alpha4),             d4],
     [          0,                        0,                        0,              1]])]

```python
T1 = scara.calc_dh_matrix()
print(T1)
```

    100%|██████████| 4/4 [00:00<00:00, 2404.99it/s]
    100%|██████████| 4/4 [00:00<00:00, 15.73it/s]

    Matrix([[(-sin(theta1)*sin(theta2) + cos(theta1)*cos(theta2))*cos(theta4) - (-sin(theta1)*cos(theta2) - sin(theta2)*cos(theta1))*sin(theta4), -(-sin(theta1)*sin(theta2) + cos(theta1)*cos(theta2))*sin(theta4) - (-sin(theta1)*cos(theta2) - sin(theta2)*cos(theta1))*cos(theta4), 0, 0.35*cos(theta1)*cos(theta2) + 0.25*cos(theta1)], [-(-sin(theta1)*sin(theta2) + cos(theta1)*cos(theta2))*sin(theta4) + (sin(theta1)*cos(theta2) + sin(theta2)*cos(theta1))*cos(theta4), -(-sin(theta1)*sin(theta2) + cos(theta1)*cos(theta2))*cos(theta4) - (sin(theta1)*cos(theta2) + sin(theta2)*cos(theta1))*sin(theta4), 0, 0.35*sin(theta1)*cos(theta2)], [0, 0, -1, -d3 + 0.3], [0, 0, 0, 1]])

```python
T2 = sympy.simplify(T1)
print(T2)
```

    Matrix([[cos(theta1 + theta2 - theta4), sin(theta1 + theta2 - theta4), 0, (0.35*cos(theta2) + 0.25)*cos(theta1)], [sin(theta1 + theta2 - theta4), -cos(theta1 + theta2 - theta4), 0, 0.35*sin(theta1)*cos(theta2)], [0, 0, -1, -d3 + 0.3], [0, 0, 0, 1]])

```python
from IPython.display import Latex
a = sympy.latex(T2)
Latex(a)
```

\left[\begin{matrix}\cos{\left (\theta_{1} + \theta_{2} - \theta_{4} \right )} & \sin{\left (\theta_{1} + \theta_{2} - \theta_{4} \right )} & 0 & \left(0.35 \cos{\left (\theta_{2} \right )} + 0.25\right) \cos{\left (\theta_{1} \right )}\\\sin{\left (\theta_{1} + \theta_{2} - \theta_{4} \right )} & - \cos{\left (\theta_{1} + \theta_{2} - \theta_{4} \right )} & 0 & 0.35 \sin{\left (\theta_{1} \right )} \cos{\left (\theta_{2} \right )}\\0 & 0 & -1 & - d_{3} + 0.3\\0 & 0 & 0 & 1\end{matrix}\right]

```python
#makes it easier to copy and paste by each element alone
for i in range(4):
    for j in range(4):
        print("( {} , {} )".format(i,j))
        print(T2[i,j])
```

    ( 0 , 0 )
    cos(theta1 + theta2 - theta4)
    ( 0 , 1 )
    sin(theta1 + theta2 - theta4)
    ( 0 , 2 )
    0
    ( 0 , 3 )
    (0.35*cos(theta2) + 0.25)*cos(theta1)
    ( 1 , 0 )
    sin(theta1 + theta2 - theta4)
    ( 1 , 1 )
    -cos(theta1 + theta2 - theta4)
    ( 1 , 2 )
    0
    ( 1 , 3 )
    0.35*sin(theta1)*cos(theta2)
    ( 2 , 0 )
    0
    ( 2 , 1 )
    0
    ( 2 , 2 )
    -1
    ( 2 , 3 )
    -d3 + 0.3
    ( 3 , 0 )
    0
    ( 3 , 1 )
    0
    ( 3 , 2 )
    0
    ( 3 , 3 )
    1

```python

```


Imagine a rocket is resting stationary in intergalatic space, subjected to no gravity at all. Due to (many simultaneous) software malfunction(s) one of the thrusters on the side of the rocket turns on, and stays on.

This gives the rocket angular momentum as well as linear momentum, so it gets pushed to one side and then starts spinning too.

The faster it spins, the faster the thrusters change direction, so the direction of the boost changes more and more frequently.

What kind of trajectory will it draw out if the astronauts never manage to turn it off before they blackout from the centripedal force?

Let's ignore the fact that we live in a 3 dimensional world (and therefore the implications of the [tennis racket theorem](https://en.wikipedia.org/wiki/Tennis_racket_theorem), and assume that this problem only cause the rocket to spin in a single plane.

The key to solving this problem is to realize that, the angular speed of the rocket is a linear function of time: the longer it stay out of control, the faster it spins.

Therefore the angular speed at any one point is $\propto t$.

The acceleration vector $\vec{a}$ is a vector of constant magnitude, whose direction rotates faster and faster.

The velocity vector of the rocket is obtained by integrating the acceleration vector $\vec{a}$ wrt. time.

\begin{equation}
v_x = S(t)
\end{equation}
\begin{equation}
v_y = C(t)
\end{equation}

Where S and C are the [Fresnel integrals](https://en.wikipedia.org/wiki/Fresnel_integral).

We can then obtain the displacement vector by integrating wrt. time again.

The Fresnel integrals:

S:

C:

```python
# imports
import matplotlib.pyplot as plt
import numpy as np
import scipy.special as spc
import scipy.integrate as inte
import scipy.constants as k
```

```python
time_steps = 1001 # number of time steps to integrate over
t = np.linspace(0,10,time_steps)
c = (np.sqrt(k.pi/2)) # correction factor for the fresnel functions

# CHANGE CONSTNATS HERE
FM = 3 # Acceleration exerted by the nozzle, i.e.
# The ratio of (Mass of rocket):(force exerted by the ejected gas onto the nozzle)
# (in m/s2)

R_K = 1 # The radius of gyration of the rocket
# END OF USER-DEFINED CONSTANTS SECTION

R2K = R_K/2
u = np.sqrt(FM*R2K)
cu= c/u # constants
l = cu*FM # more constants
```

```python
vi = spc.fresnel(t/cu)
vx = l*vi[0] # the x-component of the velocity time series
vy = l*vi[1] # the y-component of the velocity time series

# create function to integrate the velocity over
velox = lambda x : spc.fresnel(x)[0]
veloy = lambda x : spc.fresnel(x)[1]

# calculate the positions by integration
x, y = [], []
for i in range (0, time_steps):
    xi = inte.quad(velox, 0, t[i]/cu)[0] # only the first element of the resulting tuple is the integration result
    x.append( l*cu*xi )
    yi = inte.quad(veloy, 0, t[i]/cu)[0]
    y.append( l*cu*yi )

velocity, = plt.plot(vx, vy, label='velocity')
plt.title('Phase plot of the rocket')
plt.legend()
plt.show()

position, = plt.plot(x, y, label='position')
plt.title('Trajectory of the rocket')
plt.legend()
plt.show()
```

Side note: the rocket's velocity trace forms what is called an [Euler spirl](https://en.wikipedia.org/wiki/Euler_spiral).

Exercise for the reader:
What do you think the graph would look like if we add gravity back into the picture? I.e. if this whole imaginary accident happened while it is closed to the surface, during a launch.

```python
# run the code below to find out!
g = 0.62 # m s^{-2} # Let's say we're launching from Pluto's surface

vi = spc.fresnel(t/cu)
vx = l*vi[0]
vy = l*vi[1]-g*t

# create function to integrate the velocity over
velox = lambda x : spc.fresnel(x)[0]
veloy = lambda x : spc.fresnel(x)[1]

# calculate the positions by integration
x, y = [], []
for i in range (0, time_steps):
    xi = inte.quad(velox, 0, t[i]/cu)[0] # only the first element of the resulting tuple is the integration result
    x.append( l*cu*xi )
    yi = inte.quad(veloy, 0, t[i]/cu)[0]
    y.append( l*cu*yi )

velocity, = plt.plot(vx, vy, label='velocity')
plt.title('Phase plot of the rocket')
plt.legend()
plt.show()

position, = plt.plot(x, y, label='position')
plt.title('Trajectory of the rocket')
plt.legend()
plt.show()
```


# Drive Wheel Motor Torque Calculations

Determine whether the drive system wheels have enough surface traction to achieve the desired speed and acceleration for the robot weight and angle of incline.

## Initialisations

```python
from math import *
import sympy as sp
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as pl
from matplotlib.ticker import FormatStrFormatter

# initialise drive wheel torque parameters
G_VW, N_DW, W_DW, R_W, V_max, t_a, a, C_rr, RF, u = \
sp.symbols(r"G_VW, N_DW, W_DW, R_W, V_max, t_a, \alpha, C_rr, RF, \mu")
```

## Force of Rolling Resistance

Rolling Resistance Force is the tractive force produced by the motor which turns the wheels forward, pushing against the surface. We will use the worse surface type the vehicle is expected to encounter in order to calculate this.

$${F_{RR} = G_{VW} \cdot 9.81 \cdot C_{rr}}$$

$F_{RR} =$ Force of rolling resistance $(N)$

$G_{VW} =$ Gross weight of vehicle $(kg)$

$C_{rr} =$ Surface friction for worst surface: dry wood $(0.010)$

| **Contact Surface** | **$C_{rr}$** |
| --- | --- |
| Concrete (good / fair / poor) | 0.010 / 0.015 / 0.020  |
| Asphalt (good / fair / poor)  | 0.012 / 0.017 / 0.022 |
| Wood (dry / dusty / wet) | 0.010 / 0.005 / 0.001 |
| Snow (2 inch / 4 inch) | 0.025 / 0.037  |
| Dirt (smooth / sandy) | 0.025 / 0.037  |
| Mud (firm / medium / soft)  | 0.037 / 0.090 / 0.150 |
| Grass (firm / soft) | 0.055 / 0.075  |
| Sand (firm / soft / dune) | 0.060 / 0.150 / 0.300 |

```python
F_RR = sp.symbols("F_RR", cls=sp.Function)
```

```python
F_RR = G_VW * 9.81 * C_rr
F_RR
```

$\displaystyle 9.81 C_{rr} G_{VW}$

```python
# Convert F_RR into a numpy expression
F_RR_f = sp.lambdify((G_VW, C_rr), F_RR)
F_RR_f
```

    <function _lambdifygenerated(G_VW, C_rr)>

## Force of Climbing Incline

Force of Grade Resistance is the Force required to move the vehicle up a desired incline. If vehicle is not required to climb an inclined surface, this value  becomes zero $(\sin(0) = 0)$.

$${F_{GR} = G_{VW} \cdot 9.81 \cdot \sin(\alpha)}$$

$F_{GR} =$ Force required to climb an incline (N)

$G_{VW} =$ Gross vehicle weight ($kg$)

$\alpha$ = maximum incline angle ($degrees$)

```python
F_GR = sp.symbols("F_GR", cls=sp.Function)
```

```python
F_GR = G_VW * 9.81 * sp.sin(a * sp.pi/180)
F_GR
```

$\displaystyle 9.81 G_{VW} \sin{\left(\frac{\pi \alpha}{180} \right)}$

```python
# Convert F_GR into a numpy expression
F_GR_f = sp.lambdify((G_VW, a), F_GR)
F_GR_f
```

    <function _lambdifygenerated(G_VW, Dummy_23)>

## Force of Acceleration

Force required to accelerate vehicle from stationary position to maximum velocity in the required time. This Force does not depend on incline of surface.

$${F_{A} = \frac{G_{VW} \cdot V_{max}}{t_{a}}}$$

$F_{A} =$ Force required for acceleration $(N)$

$G_{VW} =$ Gross vehicle weight $(kg)$

$V_{max} =$ Maximum speed $(m/s)$

$t_{a} =$ Time required for maximum speed $(s)$

```python
F_A = sp.symbols("F_A", cls=sp.Function)
```

```python
F_A = G_VW * V_max / t_a
F_A
```

$\displaystyle \frac{G_{VW} V_{max}}{t_{a}}$

```python
F_A_f = sp.lambdify((G_VW, V_max, t_a), F_A)
F_A_f
```

    <function _lambdifygenerated(G_VW, V_max, t_a)>

## Total Tractive Force

Total Tractive Force is the sum of the three Forces calculated in the previous sections. This is the cumulative Horizontal Force applied by the Drive Wheels to the ground.

$${F_{TT} = F_{RR} + F_{GR} + F_{A}}$$

$F_{TT} =$ Total tractive Force $(N)$

$F_{RR} =$ Force needed to overcome rolling resistance $(N)$

$F_{GR} =$ Grade Resistance Force required to climb an incline $(N)$

$F_{A} =$ Force required to accelerate to final velocity $(N)$

```python
F_TT = sp.symbols("F_TT", cls=sp.Function)
```

```python
F_TT = F_RR + F_GR + F_A
F_TT = F_TT.simplify()
F_TT
```

$\displaystyle \frac{G_{VW} \left(V_{max} + 9.81 t_{a} \left(C_{rr} + \sin{\left(\frac{\pi \alpha}{180} \right)}\right)\right)}{t_{a}}$

```python
F_TT_f = sp.lambdify((G_VW, C_rr, a, V_max, t_a), F_TT)
F_TT_f
```

    <function _lambdifygenerated(G_VW, C_rr, Dummy_24, V_max, t_a)>

## Motor Torque At Each Wheel

Wheel Motor Torque is the amount of Torque experienced at each Drive Wheel, based on wheel radius and accounting for internal drive mechanism losses.

$${T_{WM} = F_{TT} \cdot R_{W} \cdot RF}$$

$T_{WM} =$ Wheel Torque $(Nm)$

$F_{TT} =$ Total Tractive Force $(N)$

$R_{W} =$ Radius of wheel $(m)$

$RF =$ Resistance Factor. Accounts for drive component frictional losses. Typically 10 - 15%.

```python
T_WM = sp.symbols("T_WM", cls=sp.Function)
```

```python
T_WM = F_TT * R_W * RF
T_WM
```

$\displaystyle \frac{G_{VW} RF R_{W} \left(V_{max} + 9.81 t_{a} \left(C_{rr} + \sin{\left(\frac{\pi \alpha}{180} \right)}\right)\right)}{t_{a}}$

```python
T_WM_f = sp.lambdify((G_VW, C_rr, a, V_max, t_a, R_W, RF), T_WM)
T_WM_f
```

    <function _lambdifygenerated(G_VW, C_rr, Dummy_25, V_max, t_a, R_W, RF)>

## Maximum Tractive Torque

Verify vehicle can transmit required torque from Drive Wheels to the ground. This value depends on the number of Drive Wheels and hence the weight each Drive Wheel will experience.

$${M_{TT} = W_{DW} \cdot 9.81 \cdot \mu \cdot R_{W}}$$

$M_{TT} =$ Maximum Tractive Torque $(Nm)$

$W_{DW} =$ Weight (normal load) on drive wheel $(kg)$

$\mu =$ Frictional coefficient between the wheel and the ground (~0.7 for hard rubber on dry wood)

$R_{W} =$ Radius of wheel $(m)$

```python
M_TT = sp.symbols("M_TT", cls=sp.Function)
```

```python
M_TT = W_DW * 9.81 * u * R_W
M_TT
```

$\displaystyle 9.81 R_{W} W_{DW} \mu$

```python
M_TT_f = sp.lambdify((W_DW, R_W, u), M_TT)
M_TT_f
```

    <function _lambdifygenerated(W_DW, R_W, Dummy_26)>

## Sum of Maximum Tractive Torques

Determine limit for when wheel slippage will to occur by summing the Maximum Tractive Torques $(SUM\_{M_{TT}})$ for all Drive Wheels $(N_{DW})$.

$${SUM\_M_{TT} = M_{TT} \cdot N_{DW}}$$

$SUM\_M_{TT} =$ Sum of Maximum Tractive Torques $(Nm)$

$M_{TT} =$ Maximum Tractive Torque $(Nm)$

$N_{DW} =$ Number of drive wheels$

```python
SUM_M_TT = sp.symbols("SUM_M_TT", cls=sp.Function)
```

```python
SUM_M_TT = M_TT * N_DW
SUM_M_TT
```

$\displaystyle 9.81 N_{DW} R_{W} W_{DW} \mu$

```python
SUM_M_TT_f = sp.lambdify((W_DW, R_W, u, N_DW), SUM_M_TT)
SUM_M_TT_f
```

    <function _lambdifygenerated(W_DW, R_W, Dummy_27, N_DW)>

## Define Parameters

Set acceptable values for the Drive Wheel Torque Parameters

```python
G_VW = 20 # Gross vehicle weight (kg)
N_DW = 6 # Number of drive wheels
W_DW = 10/3 # (20/6) Weight on each drive wheel (kg)
R_W = 0.0508 # Radius of wheel (m)
V_max = 0.4 # Desired top speed (m/s)
t_a = 2 # Desired acceleration time  (s)
a = 35 # Maximum incline angle (degrees)
C_rr = 0.01 # Worst surface friction (dry wood)
RF = 1.15 # Resistance Factor (10 - 15%)
u = 0.7 # Frictional coefficient between the wheel and the ground (hard rubber on dry wood)
```

## Perform Calculations

Calculate $F_{RR}$ Force of rolling resistance $(N)$

```python
F_RR_f(G_VW, C_rr)
```

    1.9620000000000002

Calculate $F_{GR}$ Grade Resistance Force required to climb an incline $(N)$

```python
F_GR_f(G_VW, a)
```

    112.53569681207524

Calculate $F_{A}$ Force required to accelerate to final velocity $(N)$

```python
F_A_f(G_VW, V_max, t_a)
```

    4.0

Calculate $F_{TT}$ Total Tractive Force $(N)$

```python
F_TT_f(G_VW, C_rr, a, V_max, t_a)
```

    118.49769681207525

Calculate $T_{WM}$ Wheel Torque $(Nm)$

```python
T_WM_f(G_VW, C_rr, a, V_max, t_a, R_W, RF)
```

    6.922635447761435

Calculate $M_{TT}$ Maximum Tractive Torque $(Nm)$

```python
M_TT_f(W_DW, R_W, u)
```

    1.162812

Calculate $SUM\_M_{TT}$ Sum of Maximum Tractive Torques $(Nm)$

```python
SUM_M_TT_f(W_DW, R_W, u, N_DW)
```

    6.976872

## Parameters Check to Avoid Slippage

$$T_{WM} \leq SUM\_M_{TT}$$

The Wheel Motor Torque $(T_{WM})$ must be less than or equal to the sum of the Maximum Tractive Torques $(SUM\_M_{TT})$ to avoid slippage of the Drive Wheels.

```python
wheel_motor_torque = T_WM_f(G_VW, C_rr, a, V_max, t_a, R_W, RF)
wheel_motor_torque
```

    6.922635447761435

```python
sum_max_tractive_torques = SUM_M_TT_f(W_DW, R_W, u, N_DW)
sum_max_tractive_torques
```

    6.976872

```python
if (wheel_motor_torque <= sum_max_tractive_torques):
    print("Motor Torque Parameters are viable")
else:
    print("Motor Torque Parameters are NOT viable and slippage will occur.")
```

    Motor Torque Parameters are viable

## Graphing Parameter Variations
### Param 1: $G_{VW}$ Gross vehicle weight 10 - 25 kg

```python
G_VW_x_vals = np.linspace(10, 25, 100)
G_VW_y_vals = T_WM_f(G_VW_x_vals, C_rr, a, V_max, t_a, R_W, RF)
```

```python
# create data points for when slippage will occur
SUM_M_TT_x_vals = np.linspace(0, 50, 10)
SUM_M_TT_y_vals = np.full((len(SUM_M_TT_x_vals)), SUM_M_TT_f(W_DW, R_W, u, N_DW))
```

### Param 2: $\alpha$ Maximum incline angle 10 - 50 degrees

```python
a_x_vals = np.linspace(10, 50, 100)
a_y_vals = T_WM_f(G_VW, C_rr, a_x_vals, V_max, t_a, R_W, RF)
```

### Param 3: $V_{max}$ Desired top speed 0.1 - 1.0 m/s

```python
V_max_x_vals = np.linspace(0.1, 1, 100)
V_max_y_vals = T_WM_f(G_VW, C_rr, a, V_max_x_vals, t_a, R_W, RF)
```

### Generate Graphs

```python
fig, (ax1, ax2, ax3) = pl.subplots(1, 3, gridspec_kw={"wspace":0.25,"width_ratios": [1,1,1]}, figsize=(15,3.5))

# create gross vehicle weight plot
ax1.plot(G_VW_x_vals, G_VW_y_vals, label=r"$G_{VW}~(kg)$")
ax1.plot(SUM_M_TT_x_vals, SUM_M_TT_y_vals, label=r"$SUM\_M_{TT}~(N)$")
ax1.set_title(r"$G_{VW}~(kg)~~~vs~~~T_{WM}~(N)$", fontsize=15)
ax1.set_xlim(10, 25)
ax1.xaxis.set_major_formatter(FormatStrFormatter("%.0f"))
ax1.set_xlabel(r"$G_{VW}~(kg)$", fontsize=15)
ax1.set_ylabel(r"$T_{WM}~(N)$", fontsize=15)
ax1.legend(bbox_to_anchor=(0.98, 0.30), fontsize=10)
ax1.grid()

# create maximum incline angle plot
ax2.plot(a_x_vals, a_y_vals, label=r"$\alpha~(degrees)$")
ax2.plot(SUM_M_TT_x_vals, SUM_M_TT_y_vals, label=r"$SUM\_M_{TT}~(N)$")
ax2.set_title(r"$\alpha~(degrees)~~~vs~~~T_{WM}~(N)$", fontsize=15)
ax2.set_xlim(10, 50)
ax2.xaxis.set_major_formatter(FormatStrFormatter("%.0f"))
ax2.set_xlabel(r"$\alpha~(degrees)$", fontsize=15)
ax2.set_ylabel(r"$T_{WM}~(N)$", fontsize=15)
ax2.legend(bbox_to_anchor=(0.98, 0.30), fontsize=10)
ax2.grid()

# create desired top speed plot
ax3.plot(V_max_x_vals, V_max_y_vals, label=r"$V_{max}~(m/s)$")
ax3.plot(SUM_M_TT_x_vals, SUM_M_TT_y_vals, label=r"$SUM\_M_{TT}~(N)$")
ax3.set_title(r"$V_{max}~~~vs~~~T_{WM}~(N)$", fontsize=15)
ax3.set_xlim(0.1, 1)
ax3.xaxis.set_major_formatter(FormatStrFormatter("%.1f"))
ax3.set_xlabel(r"$V_{max}~(m/s)$", fontsize=15)
ax3.set_ylabel(r"$T_{WM}~(N)$", fontsize=15)
ax3.legend(bbox_to_anchor=(0.98, 0.30), fontsize=10)
ax3.grid()

pl.show()
```

Graph of robot weight (kg), angle of incline (degrees), robot top speed (m/s) vs Wheel Motor Torque (N). In each case, once the Wheel Motor Torque (N) crosses the sum of the Maximum Tractive Torques (N), represented by the horizontal line, wheel slippage will occur.

- Graph 1: robot weight varied (10 – 25 kg), angle of incline fixed (35 degrees), robot top speed fixed (0.4 m/s)
- Graph 2: angle of incline varied (10 – 50 degrees), robot weight fixed (20 kg), robot top speed fixed (0.4 m/s)
- Graph 3: robot top speed varied (0.1 – 1.0 m/s), robot weight fixed (20kg), angle of incline fixed (35 degrees)

Drive Wheel Motor Torque Calculations.ipynb


# Characterization of Discrete Systems

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Comunications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Eigenfunctions

An [eigenfunction](https://en.wikipedia.org/wiki/Eigenfunction) of a discrete system is defined as the input signal $x[k]$ which produces the output signal $y[k] = \mathcal{H}\{ x[k] \} = \lambda \cdot x[k]$ with $\lambda \in \mathbb{C}$. The weight $\lambda$ associated with $x[k]$ is known as scalar eigenvalue of the system. Hence besides a weighting factor, an eigenfunction is not modified by passing through the system.

[Complex exponential signals](../discrete_signals/standard_signals.ipynb#Complex-Exponential-Signal) $z^k$ with $z \in \mathbb{C}$ are eigenfunctions of discrete linear time-invariant (LTI) systems. Let's assume a generic LTI system with input signal $x[k] = z^k$ and output signal $y[k] = \mathcal{H}\{ x[k] \}$. Due to the time-invariance of the system, the response to a shifted input signal $x(k-\kappa) = z^{k - \kappa}$ reads

\begin{equation}
y[k- \kappa] = \mathcal{H}\{ x[k - \kappa] \} = \mathcal{H}\{ z^{- \kappa} \cdot z^k  \}
\end{equation}

Due to the linearity of the system this can be reformulated as

\begin{equation}
y[k- \kappa] =  z^{- \kappa} \cdot \mathcal{H}\{ z^k  \} = z^{- \kappa} \cdot y[k]
\end{equation}

If the complex exponential signal $z^k$ is an eigenfunction of the LTI system, the output
signal is a weighted exponential signal $y[k] = \lambda \cdot z^k$. Introducing $y[k]$ into the left- and right-hand side of above equation yields

\begin{equation}
\lambda z^k z^{- \kappa} = z^{- \kappa} \lambda z^k
\end{equation}

which obviously is fulfilled. This proves that the exponential signal $z^k$ is an eigenfunction of LTI systems.

**Example**

The output signal of the previously introduced [second-order recursive LTI system](difference_equation.ipynb#Second-Order-System) with the difference equation

\begin{equation}
y[k] - y[k-1] + \frac{1}{2} y[k-2] = x[k]
\end{equation}

is computed for a complex exponential signal $x[k] = z^k$ at the input. The output signal should be a weighted complex exponential due to above reasoning.

```python
%matplotlib inline
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

a = [1.0, -1.0, 1/2]
b = [1.0]
z = np.exp(0.02 + .5j)

k = np.arange(30)
x = z**k
y = signal.lfilter(b, a, x)
```

The real and imaginary part of the input and output signal is plotted.

```python
plt.figure(figsize=(10,8))

plt.subplot(221)
plt.stem(k, np.real(x))
plt.xlabel('$k$')
plt.ylabel(r'$\Re \{ x[k] \}$')

plt.subplot(222)
plt.stem(k, np.imag(x))
plt.xlabel('$k$')
plt.ylabel(r'$\Im \{ x[k] \}$')
plt.tight_layout()

plt.subplot(223)
plt.stem(k, np.real(y))
plt.xlabel('$k$')
plt.ylabel(r'$\Re \{ y[k] \}$')

plt.subplot(224)
plt.stem(k, np.imag(y))
plt.xlabel('$k$')
plt.ylabel(r'$\Im \{ y[k] \}$')
plt.tight_layout()
```

**Exercise**

* From the in- and output signal only, can we conclude that the system is LTI?

## Transfer Function

The complex eigenvalue $\lambda$ characterizes the properties of the transfer of a complex exponential signal $z^k$ with [complex frequency $z$](../discrete_signals/standard_signals.ipynb#Complex-Exponential-Signal) through a discrete LTI system. It is commonly termed as [*transfer function*](https://en.wikipedia.org/wiki/Transfer_function) and denoted by $H(z)=\lambda(z)$. Using this definition, the output signal $y[k]$ of an LTI system with complex exponential signal at the input reads

\begin{equation}
y[k] = \mathcal{H} \{ z^k \} = H(z) \cdot z^k
\end{equation}

Note that the concept of the transfer function is directly linked to the linearity and time-invariance of a system. Only in this case, complex exponential signals are eigenfunctions of the system and $H(z)$ describes the properties of an LTI system with respect to these.

Above equation can be rewritten in terms of the magnitude $| H(z) |$ and phase $\varphi(z)$ of the complex transfer function $H(z)$

\begin{equation}
y[k] = | H(z) | \cdot z^k = | H(z) | \cdot e^{\Sigma k + j \Omega k + j \varphi(z)}
\end{equation}

where $z = e^{\Sigma + j \Omega}$ has been substituted to derive the last equality. The magnitude $| H(z) |$ provides the frequency dependent attenuation of the eigenfunction $z^k$ by the system, while $\varphi(z)$ provides the phase-shift introduced by the system.

## Link between Transfer Function and Impulse Response

In order to establish a link between the transfer function $H(z)$ and the impulse response $h[k]$, the output signal $y[k] = \mathcal{H} \{ x[k] \}$ of an LTI system with input signal $x[k]$ is computed. It is given by convolving the input signal with the impulse response

\begin{equation}
y[k] = x[k] * h[k] = \sum_{\kappa = -\infty}^{\infty} x[k-\kappa] \cdot h[\kappa]
\end{equation}

For a complex exponential signal as input $x[k] = z^k$ the output of the LTI system is given as $y[k] = \mathcal{H} \{ z^k \} = H(z) \cdot z^k$. Introducing both signals into the left- and right-hand side of the convolution yields

\begin{equation}
H(z) \cdot z^k = \sum_{\kappa = -\infty}^{\infty} z^k \, z^{- \kappa} \cdot h[\kappa]
\end{equation}

which after canceling out $z^k$ results in

\begin{equation}
H(z) = \sum_{\kappa = -\infty}^{\infty} h[\kappa] \cdot z^{- \kappa}
\end{equation}

The transfer function $H(z)$ can be computed from the impulse response by summing over the impulse response $h[k]$  multiplied with the complex exponential function $z^k$. This constitutes a transformation, which is later introduced in more detail as [$z$-transform](https://en.wikipedia.org/wiki/Z-transform).

### Copyright

  To the extent possible under law,
  the person who associated CC0
  with this work has waived all copyright and related or neighboring
  rights to this work.


# Design of Digital Filters

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Design of Recursive Filters by the Bilinear Transform

Various techniques have been developed to derive digital realizations of analog systems. For instance the [impulse invariance](https://en.wikipedia.org/wiki/Impulse_invariance) method, the [matched Z-transform](https://en.wikipedia.org/wiki/Matched_Z-transform_method) and the [bilinear transform](https://en.wikipedia.org/wiki/Bilinear_transform). The following section introduces the bilinear transform and its application to the digital realization of analog systems and recursive filter design.

### The Bilinear Transform

The bilinear transform is used to map the transfer function $H(s) = \mathcal{L}\{ h(t) \}$ of a continuous system to the transfer function $H(z) = \mathcal{Z}\{ h[k] \}$ of a discrete system approximating the continuous system. The transform is designed such that if $H(s)$ is a [rational function](https://en.wikipedia.org/wiki/Rational_function) in $s$ then $H(z)$ is a rational function in $z^{-1}$. The coefficients of the powers of $z^{-1}$ are then the coefficients of the digital system.

Assuming ideal sampling with sampling interval $T$, the frequency variable $s$ of the Laplace transformation is linked to the frequency variable $z$ of the $z$-transform by

\begin{equation}
z = \mathrm{e}^{\,s T}
\end{equation}

For sampled signals the resulting mapping from the $s$-plane into the $z$-plane is shown in the following illustration:

The shading indicates how the different areas are mapped. The imaginary axis $s = \mathrm{j}\,\omega$ is mapped onto the unit circle $z = \mathrm{e}^{\,\mathrm{j}\,\Omega}$, representing the frequency response of the continuous and discrete system. The left half-plane of the $s$-plane is mapped into the unit circle of the $z$-plane.

For the desired mapping of $H(s)$ to $H(z)$ we need the inverse of above equation. It is given as

\begin{equation}
s = \frac{1}{T} \cdot \ln (z)
\end{equation}

However, when introduced into a rational transfer function $H(s)$ this non-linear mapping would not result in the desired rational transfer function $H(z)$. In order to achieve the desired mapping, $\ln(z)$ is expanded into the power series

\begin{equation}
\ln(z) = 2 \left( \frac{z-1}{z+1} + \frac{(z-1)^3}{3(z+1)^3} + \frac{(z-1)^5}{5(z+1)^5} + \dots \right)
\end{equation}

Using only the linear term as approximation of $\ln(z)$ yields the bilinear transform

\begin{equation}
s = \frac{2}{T} \cdot \frac{z - 1}{z + 1}
\end{equation}

and its inverse

\begin{equation}
z = \frac{2 + s T}{2 - s T}
\end{equation}

It worthwhile noting that this mapping rule is a special case of a [conformal map](https://en.wikipedia.org/wiki/Conformal_map).

Let's consider the mapping of the frequency response $H(\mathrm{j}\,\omega) = H(s) \big\vert_{s = \mathrm{j}\,\omega}$ of a continuous system to the frequency response $H_d(\mathrm{e}^{\,\mathrm{j}\,\omega T}) = H_d(z)\big\vert_{z = \mathrm{e}^{\,\mathrm{j}\,\Omega}}$ of a discrete system. Introducing the bilinear transform into the continuous system to yield its discrete counterpart results in

\begin{equation}
H_d(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = H \left( \frac{2}{T} \cdot \frac{z - 1}{z + 1} \right) \Bigg\vert_{z = \mathrm{e}^{\,\mathrm{j}\,\Omega}} = H \Big( \mathrm{j} \; \underbrace{\frac{2}{T} \cdot \tan(\frac{\Omega}{2})}_{\omega} \Big)
\end{equation}

The imaginary axis $s=\mathrm{j}\,\omega$ of the $s$-plane is mapped onto the unit circle $\mathrm{e}^{\,\mathrm{j}\,\Omega}$ of the $z$-plane. Note, that for sampled signals the mapping between the continuous frequency axis $\omega$ and the frequency axis $\Omega$ of the discrete system is $\Omega = \omega T$. However, for the bilinear transform the mapping is non-linear

\begin{align}
\omega &= \frac{2}{T} \cdot \tan(\frac{\Omega}{2}) \\
\Omega &= 2 \arctan(\frac{\omega T}{2})
\end{align}

In the following, this is illustrated for $T=1$

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

om = np.linspace(-10, 10, 200)
Om = 2*np.arctan(om*1/2)

plt.figure(figsize=(10,4))
plt.plot(om, Om, label=r'$2 \cdot \arctan(\frac{\omega T}{2})$')
plt.plot(om, om, 'k--', label=r'$\omega T$')
plt.xlabel(r'$\omega$')
plt.ylabel(r'$\Omega$')
plt.axis([-10, 10, -np.pi, np.pi])
plt.legend(loc=2)
plt.grid()
```

It is evident that the frequency axis deviates from the linear mapping $\Omega = \omega T$, especially for high frequencies. The frequency response of the digital filter $H_d(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ therefore deviates from the desired continuous frequency response $H(\mathrm{j}\,\omega)$. This is due to the first-order approximation of the mapping from the $s$-plane to the $z$-plane. The effect is known as *frequency warping*. It can be considered explicitly in the filter design stage, as shown in the examples.

Besides this drawback, the bilinear transform has a number of benefits:

* Stability and minimum-phase of the continuous filter is preserved. This is due to mapping of the left half-space of the $s$-plane into the unit-circle of the $z$-plane.
* The order of the continuous filter is preserved. This is due to the linear mapping rule.
* No aliasing distortion as for instance observed for the [impulse invariance method](https://en.wikipedia.org/wiki/Impulse_invariance).

The application of the bilinear transform to the design of digital filters is discussed in the following.

### Design of Digital Filter

We aim at designing a digital filter $H_d(z)$ that approximates a given continuous prototype $H(s)$ using the bilinear transform. For instance, the transfer function $H(s)$ may result from the analysis of an analog circuit or filter design technique. The transfer function $H_d(z)$ of the digital filter is then given by

\begin{equation}
H_d(z) = H(s) \Big\vert_{s = \frac{2}{T} \cdot \frac{z - 1}{z + 1}}
\end{equation}

The coefficients of the digital filter are derived by representing the numerator and denominator of $H_d(z)$ as polynomials with respect to $z^{-1}$. For instance, for a continuous system of second order (second order section)

\begin{equation}
H(s) = \frac{\beta_0 + \beta_1 s + \beta_2 s^2}{\alpha_0 + \alpha_1 s + \alpha_2 s^2}
\end{equation}

the bilinear transform results in

\begin{equation}
H_d(z) = \frac{(\beta_2 K^2 - \beta_1 K + \beta_0) \, z^{-2} + (2 \beta_0 - 2 \beta_2 K^2) \,  z^{-1} + (\beta_2 K^2 + \beta_1 K + \beta_0)}
{(\alpha_2 K^2 - \alpha_1 K + \alpha_0) \, z^{-2} + (2 \alpha_0 - 2 \alpha_2 K^2) \,  z^{-1} + (\alpha_2 K^2 + \alpha_1 K + \alpha_0)}
\end{equation}
where $K = \frac{2}{T}$.

As outlined in the previous section, the frequency response of the digital filter $H_d(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ will differ for high frequencies from the desired analog frequency response $H(\mathrm{j}\,\omega)$. For the design of a digital filter from an analog prototype, this can be coped for by replacing corner frequencies with

\begin{equation}
\omega_\text{cw} = \frac{2}{T} \cdot \tan \left(\frac{\omega_\text{c} T}{2} \right)
\end{equation}

where $\omega_\text{cw}$ denotes a *warped* corner frequency $\omega_\text{c}$. This techniques is known as *pre-warping*.

### Examples

The following two examples illustrate the digital realization of an analog system and the design of a recursive filter, respectively.

#### Digital realization of an analog system

A second-order lowpass filter can be realized by the following passive circuit

where $x(t)$ denotes the input and $y(t)$ the output signal (voltage). Analysis of the circuit reveals its transfer function as

\begin{equation}
H(s) = \frac{Y(s)}{X(s)} = \frac{1}{LC \, s^2 + RC \, s + 1}
\end{equation}

Introducing this into the bilinear transform of a second order section (SOS) given in the previous section yields

\begin{equation}
H_d(z) = \frac{T^2 \, z^{-2} + 2 T^2 \, z^{-1} + T^2}{(4 LC - 2 TRC + T^2) \, z^{-2} + (-8 LC + 2 T^2) \, z^{-1} + (4 LC - 2 TRC + T^2)}
\end{equation}

In the following, the frequency response of the analog filter and its digital realization is compared numerically.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sig

fs = 44100  # sampling frequency
fc = 1000  # corner frequency of the lowpass

# coefficients of analog lowpass filter
Qinf = 0.8
sinf = 2*np.pi*fc
C = 1e-6
L = 1/(sinf**2*C)
R = sinf*L/Qinf

B = [0, 0, 1]
A = [L*C, R*C, 1]

# cofficients of digital filter
T = 1/fs
b = [T**2, 2*T**2, T**2]
a = [(4*L*C+2*T*R*C+T**2), (-8*L*C+2*T**2), (4*L*C-2*T*R*C+T**2)]

# compute frequency responses
Om, Hd = sig.freqz(b, a, worN=1024)
tmp, H = sig.freqs(B, A, worN=fs*Om)

# plot results
f = Om*fs/(2*np.pi)
plt.figure(figsize = (10, 4))
plt.semilogx(f, 20*np.log10(np.abs(H)), label=r'$|H(j \omega)|$ of analog filter')
plt.semilogx(f, 20*np.log10(np.abs(Hd)), label=r'$|H_d(e^{j \Omega})|$ of digital filter')
plt.xlabel(r'$f$ in Hz')
plt.ylabel(r'dB')
plt.axis([100, fs/2, -70, 3])
plt.legend()
plt.grid()
```

**Exercise**

* Increase the corner frequency `fc` of the analog filter. What effect does this have on the deviations between the analog filter and its digital representation?

Solution: Increasing the corner frequency leads to larger deviations for the higher frequencies as the width of the stop-band of the filter decreases. These deviations may even lead to a different corner/[cut-off frequency](https://en.wikipedia.org/wiki/Cutoff_frequency) as the attenuation in the transition region between the pass- and the stop-band changes.

#### Design of digital filters from analog prototypes

The design of analog filters is a topic with a long lasting history. Consequently, many analog filter designs are known. For instance

* [Butterworth filters](https://en.wikipedia.org/wiki/Butterworth_filter)
* [Chebyshev_filters](https://en.wikipedia.org/wiki/Chebyshev_filter)
* [Cauer filters](https://en.wikipedia.org/wiki/Elliptic_filter)
* [Bessel filters](https://en.wikipedia.org/wiki/Bessel_filter)

The properties of these designs are well documented and therefore digital realizations are of interest. These can be achieved by the bilinear transform. In Python, the [`scipy.signal`](http://docs.scipy.org/doc/scipy/reference/signal.html) package provides implementations of various analog filter design techniques, as well as an implementation of the bilinear transform.

The design of a Butterworth bandpass using pre-warping is illustrated in the following.

```python
omc = 2*np.pi*np.array([5000, 6000])  # corner frequencies of bandpass
N = 2  # order of filter

# pre-warping of corner frequencies
omcp = 2*fs*np.tan(omc/(2*fs))

# design of analog filters with and without pre-warping
B, A = sig.butter(N, omc, btype='bandpass', analog=True)
Bp, Ap = sig.butter(N, omcp, btype='bandpass', analog=True)

# bilinear transform of analog prototypes
b, a = sig.bilinear(B, A, fs)
bp,ap = sig.bilinear(Bp, Ap, fs)

# compute frequency responses
Om, Hdp = sig.freqz(bp, ap, worN=1024)
Om, Hd = sig.freqz(b, a, worN=1024)
tmp, H = sig.freqs(B, A, worN=fs*Om)

# plot results
np.seterr(divide='ignore')
f = Om*fs/(2*np.pi)
plt.figure(figsize = (12, 8))
plt.semilogx(f, 20*np.log10(np.abs(H)), label=r'$|H(j \omega)|$ of analog prototype')
plt.semilogx(f, 20*np.log10(np.abs(Hd)), label=r'$|H_d(e^{j \Omega})|$ of digital filter without pre-warping')
plt.semilogx(f, 20*np.log10(np.abs(Hdp)), label=r'$|H_d(e^{j \Omega})|$ of digital filter with pre-warping')
plt.xlabel(r'$f$ in Hz')
plt.ylabel(r'dB')
plt.axis([100, fs/2, -70, 3])
plt.legend()
plt.grid()
```

**Exercise**

* What is improved by pre-warping?
* Change the corner frequencies `omc` of the analog prototype and examine the deviations from the analog prototype. When is pre-warping beneficial and when not?

Solution: The location and width of the pass-band of the digital filter is closer to the desired pass-band when pre-warping is used in the design process. Pre-warping is especially beneficial if the desired pass-band is located at higher frequencies, as the deviations caused by the bilinear transformation are less prominent for lower frequencies.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2018*.


# Advection using TVD Flux Limiters
## CH EN 6355 - Computational Fluid Dynamics
**Prof. Tony Saad (www.tsaad.net)

slides at: www.tsaad.net

Department of Chemical Engineering

University of Utah**

Here, we will implement the k-scheme or kappa-schemes for advection. It is easiest to implement this scheme since for different values of k, we recover all sorts of high-order flux approximations. We will assume a positive advecting velocity for illustration purposes.

We are solving the constant speed advection equation given by
\begin{equation}
u_t = - c u_x = - F_x;\quad F = cu
\end{equation}
We will use a simple Forward Euler explicit method. Using a finite volume integration, we get
\begin{equation}
u_i^{n+1} = u_i^n - \frac{\Delta t}{\Delta x} (F_{i+\tfrac{1}{2}}^n - F_{i-\tfrac{1}{2}}^n)
\end{equation}
Our TVD formulation is based on a finite volume discretization with face values for the dependent variable given by:
\begin{equation}
{\phi _f} = {\phi _{\rm{C}}} + {1 \over 2}\psi ({r_f})\left( {{\phi _{\rm{D}}} - {\phi _{\rm{C}}}} \right)
\end{equation}
where $\psi$ is the limiter function.

```python
import numpy as np
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
import matplotlib.animation as animation
plt.rcParams['animation.html'] = 'html5'
from matplotlib import cm
```

```python
def step(x,x0):
    x0 = 0.6
    x1 = 0.8
    result = x - x0
    result[x-x1<x1] = 1.0
    result[x<x0] = 0.0
    result[x>x1] = 0.0
    return result

def gaussian(x,x0):
    s = 0.08
    s = s*s
    result = np.exp( -(x-x0)**2/s)
    return result
```

```python
L = 1.0
n = 256 # cells
dx = L/n # n intervals
x = np.linspace(-3*dx/2, L + 3*dx/2, n+4) # include ghost cells - we will include 2 ghost cells on each side for high order schemes

# create arrays
phi = np.zeros(n+4) # cell centered quantity
f = np.zeros(n+4+1) # flux
u = np.ones(n+4+1) # velocity field - assumed to live on faces same as flux

x0 = 0.3
# u0 = np.zeros(N + 2)
# u0[1:-1] = np.sin(2*np.pi*x)
# u0 = np.zeros(N)
# phi0 = np.sin(np.pi*x)
phi0 = gaussian(x,x0) + step(x,x0)
# u0 = triangle(x,0.5,0.75,1)
# u0[0:N//2] = 1.0
plt.plot(x,phi0)
```

    [<matplotlib.lines.Line2D at 0x1132d0390>]

```python
cfl = 0.5
c = 1.0
dt = cfl*dx/abs(c)
print('dt=',dt)
print('dx=',dx)
# μ = 0.001
# dt = 0.02
```

    dt= 0.001953125
    dx= 0.00390625

```python
# finite volume implementation with arrays for fluxes
t = 0
tend = L/abs(c)
sol = []
sol.append(phi0)
ims = []

fig = plt.figure(figsize=[5,3],dpi=200)
plt.rcParams["font.family"] = "serif"
plt.rcParams["font.size"] = 10
plt.rc('text', usetex=True)

# plt.grid()
plt.xlim([0.,L])
plt.ylim([-0.25,1.25])
plt.xlabel('$x$')
plt.ylabel('$\phi$')
plt.tight_layout()
# plot initial condition
plt.plot(x,phi0,'darkred',animated=True)

schemename='Bounded CD'

i = 0
while t < tend:
    phin = sol[-1]

    # impose periodic conditions
    phin[-2] = phin[2]
    phin[-1] = phin[3]
    phin[0] = phin[-4]
    phin[1] = phin[-3]

    if (i%2==0):
        shift =int(np.ceil(c*(t-dt)/dx))
        im = plt.plot(x[2:-2], np.roll(phin[2:-2], -shift) ,'k-o',markevery=2,markersize=3.5,markerfacecolor='deepskyblue',
             markeredgewidth=0.25, markeredgecolor='k',linewidth=0.45, animated=True)
        ims.append(im)

    phi = np.zeros_like(phi0)

    # predictor - take half a step and use upwind
    # du/dt = -c*du/dx
    if c >= 0:
        ϕc = phin[1:-2] # phi upwind
    else:
        ϕc = phin[2:-1] # phi upwind

    f[2:-2] = c*ϕc
    phi[2:-2] = phin[2:-2] - dt/2.0/dx*(f[3:-2] - f[2:-3])
    phi[-2] = phi[2]
    phi[-1] = phi[3]
    phi[0] = phi[-4]
    phi[1] = phi[-3]

        # du/dt = -c*du/dx
    if c >= 0:
        ϕc = phi[1:-2] # phi upwind
        ϕu = phi[:-3]  # phi far upwind
        ϕd = phi[2:-1] # phi downwind
    else:
        ϕc = phi[2:-1] # phi upwind
        ϕu = phi[3:]  # phi far upwind
        ϕd = phi[1:-2] # phi downwind

    # compute r - ratio of successive gradients
    numerator = ϕc - ϕu
    denominator = ϕd - ϕc
    # find the locations where the denominator is > 1e-12 - that's where we will do the divisions. otherwise, set phi to 1
    divideloc = np.where(np.abs(denominator) > 1e-6)
    i1 = np.where(numerator < 1e-12)
    r = np.ones_like(ϕc)
    r[divideloc] = numerator[divideloc]/denominator[divideloc]

    # compute face values - these actually live on the faces
    ϕf = np.zeros_like(ϕc)

#     # bounded CD
#     schemename='Bounded CD'
#     ϕftilde = 0.5 + 0.5*ϕctilde

#     # minmod
#     schemename='MinMod'
#     psi = np.maximum(0,np.minimum(1,r))

    # SUPERBEE
    schemename='Superbee'
    a1 = np.maximum(0, np.minimum(1.0,2.0*r))
    psi = np.maximum(a1, np.minimum(2.0,r))

#     # MUSCL
#     schemename='MUSCL'
#     psi = np.maximum(0.0, np.minimum(np.minimum(2*r, (r+1)/2.0), 2.0))

    # now retrieve phif from phiftilde
    ϕf = ϕc + 0.5*psi*(ϕd - ϕc)
    f[2:-2] = ϕf
    f = c*f # multiply the flux by the velocity
    # advect
    phi[2:-2] = phin[2:-2] - dt/dx*(f[3:-2] - f[2:-3]) #+ dt/dx/dx*diffusion
    t += dt
    i+=1
    sol.append(phi)

plt.annotate(schemename, xy=(0.5, 0.8), xytext=(0.015, 0.9),fontsize=8)
plt.legend(('exact','numerical'),loc='upper left',fontsize=7)
ani = animation.ArtistAnimation(fig, ims, interval=100, blit=True,
                                repeat_delay=1000)

# ani.save('k-scheme-'+str(k)+'.mp4',dpi=300,fps=24)
```

```python
plt.plot(sol[0], label='initial condition')
plt.plot(sol[-1], label='one residence time')
plt.legend()
plt.grid()
```

```python
ani
```

# Create Animation in Moving Reference Frame

```python
"""
Create Animation in Moving Reference Frame
"""
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
matplotlib.use("Agg")
fig, ax = plt.subplots(figsize=(4,3),dpi=150)
ax.grid(True,color='0.9')
f0 = sol[0]
line0, = ax.plot(x[2:-2], f0[2:-2] ,'r-',linewidth=0.75, animated=True)
line1, = ax.plot(x[2:-2], f0[2:-2] ,'k-o',markevery=2,markersize=3.5,markerfacecolor='deepskyblue',
             markeredgewidth=0.25, markeredgecolor='k',linewidth=0.45, animated=True)

ann = ax.annotate('time ='+str(round(t,4))+' s.' + '\t'+ schemename, xy=(0,0), xytext=(40, 200),xycoords='figure points')
plt.tight_layout()
# plt.locator_params(axis='x', nbins=15)

# def animate(i):
# #     xt = x - i*c*dt/dx
# #     line.set_xdata(xt)
# #     ax.axes.set_xlim(xt[0],xt[-1])
# #     ax.grid()
# #     line.set_ydata(np.sin(xt))  # update the data
# #     line.set_ydata(sol[i])
#     print('time=',i*dt)
#     shift =int(np.ceil(i*c*dt/dx))
#     f = sol[i]
#     line.set_ydata(np.roll(f, -shift))
# #     im = plt.plot(x[2:-2], np.roll(phin[2:-2], -shift) ,'k-o',markevery=2,markersize=3.5,markerfacecolor='deepskyblue',
# #          markeredgewidth=0.25, markeredgecolor='k',linewidth=0.45, animated=True)
#     return line,

def animate_moving(i):
    print('time=',i*dt)
    t = i*dt
    xt = x + i*1.1*c*dt
    line0.set_xdata(xt[2:-2])
    line1.set_xdata(xt[2:-2])
    ax.axes.set_xlim(xt[0],0.0*dx + xt[-1])
    f = sol[i]
    ax.axes.set_ylim(-0.1,1.1*max(f))
    ann.set_text('time ='+str(round(t,4))+'s (' + str(i)+ ').\t' + schemename)
    shift =int(np.ceil(i*c*dt/dx))
    line1.set_ydata(np.roll(f[2:-2], -shift))

    f0 = sol[0]
    line0.set_ydata(f0[2:-2])
    return line0,line1

# Init only required for blitting to give a clean slate.
def init():
    line0.set_ydata(np.ma.array(x[2:-2], mask=True))
    line1.set_ydata(np.ma.array(x[2:-2], mask=True))
    return line0,line1

ani = animation.FuncAnimation(fig, animate_moving, np.arange(0,len(sol),2*int(1.0/cfl)), init_func=init,
                              interval=20, blit=False)
print('done!')
```

    done!

```python
ani.save(schemename+'_TVD.mp4',fps=24,dpi=200)
```

    time= 0.0
    time= 0.0078125
    time= 0.015625
    time= 0.0234375
    time= 0.03125
    time= 0.0390625
    time= 0.046875
    time= 0.0546875
    time= 0.0625
    time= 0.0703125
    time= 0.078125
    time= 0.0859375
    time= 0.09375
    time= 0.1015625
    time= 0.109375
    time= 0.1171875
    time= 0.125
    time= 0.1328125
    time= 0.140625
    time= 0.1484375
    time= 0.15625
    time= 0.1640625
    time= 0.171875
    time= 0.1796875
    time= 0.1875
    time= 0.1953125
    time= 0.203125
    time= 0.2109375
    time= 0.21875
    time= 0.2265625
    time= 0.234375
    time= 0.2421875
    time= 0.25
    time= 0.2578125
    time= 0.265625
    time= 0.2734375
    time= 0.28125
    time= 0.2890625
    time= 0.296875
    time= 0.3046875
    time= 0.3125
    time= 0.3203125
    time= 0.328125
    time= 0.3359375
    time= 0.34375
    time= 0.3515625
    time= 0.359375
    time= 0.3671875
    time= 0.375
    time= 0.3828125
    time= 0.390625
    time= 0.3984375
    time= 0.40625
    time= 0.4140625
    time= 0.421875
    time= 0.4296875
    time= 0.4375
    time= 0.4453125
    time= 0.453125
    time= 0.4609375
    time= 0.46875
    time= 0.4765625
    time= 0.484375
    time= 0.4921875
    time= 0.5
    time= 0.5078125
    time= 0.515625
    time= 0.5234375
    time= 0.53125
    time= 0.5390625
    time= 0.546875
    time= 0.5546875
    time= 0.5625
    time= 0.5703125
    time= 0.578125
    time= 0.5859375
    time= 0.59375
    time= 0.6015625
    time= 0.609375
    time= 0.6171875
    time= 0.625
    time= 0.6328125
    time= 0.640625
    time= 0.6484375
    time= 0.65625
    time= 0.6640625
    time= 0.671875
    time= 0.6796875
    time= 0.6875
    time= 0.6953125
    time= 0.703125
    time= 0.7109375
    time= 0.71875
    time= 0.7265625
    time= 0.734375
    time= 0.7421875
    time= 0.75
    time= 0.7578125
    time= 0.765625
    time= 0.7734375
    time= 0.78125
    time= 0.7890625
    time= 0.796875
    time= 0.8046875
    time= 0.8125
    time= 0.8203125
    time= 0.828125
    time= 0.8359375
    time= 0.84375
    time= 0.8515625
    time= 0.859375
    time= 0.8671875
    time= 0.875
    time= 0.8828125
    time= 0.890625
    time= 0.8984375
    time= 0.90625
    time= 0.9140625
    time= 0.921875
    time= 0.9296875
    time= 0.9375
    time= 0.9453125
    time= 0.953125
    time= 0.9609375
    time= 0.96875
    time= 0.9765625
    time= 0.984375
    time= 0.9921875
    time= 1.0

```python
ani.save(schemename+'_TVD.gif', writer='imagemagick',fps=24,dpi=200)
```

    time= 0.0
    time= 0.0078125
    time= 0.015625
    time= 0.0234375
    time= 0.03125
    time= 0.0390625
    time= 0.046875
    time= 0.0546875
    time= 0.0625
    time= 0.0703125
    time= 0.078125
    time= 0.0859375
    time= 0.09375
    time= 0.1015625
    time= 0.109375
    time= 0.1171875
    time= 0.125
    time= 0.1328125
    time= 0.140625
    time= 0.1484375
    time= 0.15625
    time= 0.1640625
    time= 0.171875
    time= 0.1796875
    time= 0.1875
    time= 0.1953125
    time= 0.203125
    time= 0.2109375
    time= 0.21875
    time= 0.2265625
    time= 0.234375
    time= 0.2421875
    time= 0.25
    time= 0.2578125
    time= 0.265625
    time= 0.2734375
    time= 0.28125
    time= 0.2890625
    time= 0.296875
    time= 0.3046875
    time= 0.3125
    time= 0.3203125
    time= 0.328125
    time= 0.3359375
    time= 0.34375
    time= 0.3515625
    time= 0.359375
    time= 0.3671875
    time= 0.375
    time= 0.3828125
    time= 0.390625
    time= 0.3984375
    time= 0.40625
    time= 0.4140625
    time= 0.421875
    time= 0.4296875
    time= 0.4375
    time= 0.4453125
    time= 0.453125
    time= 0.4609375
    time= 0.46875
    time= 0.4765625
    time= 0.484375
    time= 0.4921875
    time= 0.5
    time= 0.5078125
    time= 0.515625
    time= 0.5234375
    time= 0.53125
    time= 0.5390625
    time= 0.546875
    time= 0.5546875
    time= 0.5625
    time= 0.5703125
    time= 0.578125
    time= 0.5859375
    time= 0.59375
    time= 0.6015625
    time= 0.609375
    time= 0.6171875
    time= 0.625
    time= 0.6328125
    time= 0.640625
    time= 0.6484375
    time= 0.65625
    time= 0.6640625
    time= 0.671875
    time= 0.6796875
    time= 0.6875
    time= 0.6953125
    time= 0.703125
    time= 0.7109375
    time= 0.71875
    time= 0.7265625
    time= 0.734375
    time= 0.7421875
    time= 0.75
    time= 0.7578125
    time= 0.765625
    time= 0.7734375
    time= 0.78125
    time= 0.7890625
    time= 0.796875
    time= 0.8046875
    time= 0.8125
    time= 0.8203125
    time= 0.828125
    time= 0.8359375
    time= 0.84375
    time= 0.8515625
    time= 0.859375
    time= 0.8671875
    time= 0.875
    time= 0.8828125
    time= 0.890625
    time= 0.8984375
    time= 0.90625
    time= 0.9140625
    time= 0.921875
    time= 0.9296875
    time= 0.9375
    time= 0.9453125
    time= 0.953125
    time= 0.9609375
    time= 0.96875
    time= 0.9765625
    time= 0.984375
    time= 0.9921875
    time= 1.0

```python
import urllib
import requests
from IPython.core.display import HTML
def css_styling():
    styles = requests.get("https://raw.githubusercontent.com/saadtony/NumericalMethods/master/styles/custom.css")
    return HTML(styles.text)
css_styling()
```

CSS style adapted from https://github.com/barbagroup/CFDPython. Copyright (c) Barba group
<link href='http://fonts.googleapis.com/css?family=Merriweather' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Bitter' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Oxygen' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Lora' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>

```python

```


#Deterministic knockout prediction

**cameo** also contains state-of-the-art deterministic methods for knockout prediction:

Burgard, A.P., Pharkya, P., Maranas, C.D. (2003), "OptKnock: A Bilevel Programming Framework for Identifying Gene Knockout Strategies for Microbial Strain Optimization," Biotechnology and Bioengineering, 84(6), 647-657.

OptKnock uses the following bi-level formulation:

$$
\begin{matrix}
maximize & \mathit{v_{chemical}} & & (\mathbf{OptKnock}) \\
\mathit{y_j} & & & \\
subject~to & maximize & \mathit{v_{biomass}} & (\mathbf{Primal}) \\
& \mathit{v_j} & & & & \\
\end{matrix}\\
\begin{bmatrix}
subject~to  & \sum_{j=1}^{M}S_{ij}v_{j} = 0,\\
& v_{carbon\_uptake} = v_{carbon~target}\\
& v_{apt} \ge v_{apt\_main}\\
& v_{biomass} \ge v_{target_biomass}\\
& v_{j}^{min} \cdot y_j \le v_j \le v_{j}^{max} \cdot y_j, \forall j \in \boldsymbol{M} \\
\end{bmatrix}\\
\begin{align}
 & y_j = {0, 1}, & & \forall j \in \boldsymbol{M} & \\
 & \sum_{j \in M} (1 - y_j) \le K& & & \\
\end{align}
$$

```python
from cameo import models
from cameo.strain_design.deterministic.linear_programming import OptKnock
```

```python
model = models.bigg.iJO1366
```

On initialization, OptKnock will compute essential reaction, blocked reaction (via FVA, it is optional). All of those preprocessing conditions allow OptKnock to run faster by reducing the search space (the number of variables).

Exchange reactions are also removed from the formulation.

```python
optknock = OptKnock(model)
```

Running OptKnock requires at least two parameters:

* Target - the flux to maximize - e.g. Exchange of Succinate, EX\_succ\_e\_
* Number of knockouts - K

OptKnockout will return one solution by default, but *max_results* can be set to another number. It will try to find up to *max_results* unless the problem becomes infeasible, meaning that all solutions have been found.

```python
%time result = optknock.run(2, "EX_succ_e", max_results=2)
```

    CPU times: user 13min 50s, sys: 5.44 s, total: 13min 56s
    Wall time: 4min 47s

```python
result
```

```python

```


The classical free term coefficient for a smooth surface is either 0.5 or -0.5, depending on whether we are evaluating from the interior or the exterior. But with a halfspace surface, the free term can end up being either 0 or 1. The integrations below demonstrate this fact.

```python
import numpy as np
import sympy as sp
from tectosaur2 import gauss_rule, refine_surfaces, integrate_term
from tectosaur2.laplace2d import double_layer
from tectosaur2.elastic2d import elastic_t

qx, qw = gauss_rule(12)
t = sp.var("t")
circle = refine_surfaces(
    [(t, sp.cos(sp.pi * t), sp.sin(sp.pi * t))], (qx, qw), max_curvature=0.125
)
A = integrate_term(double_layer, circle.pts, circle)
print(A[:, 0, :, 0].sum(axis=1)[0])
A2 = integrate_term(double_layer, circle.pts, circle, limit_direction=-1)
print(A2[:, 0, :, 0].sum(axis=1)[0])

line = refine_surfaces(
    [(t, 100 * t, 0.0 * t)],
    (qx, qw),
    control_points=np.array([[0, 0, 100, 1]]),
)
A3 = integrate_term(
    double_layer, line.pts, line, singularities=np.array([[-100, 0], [100, 0]])
)
print(A3[:, 0, :, 0].sum(axis=1)[A3.shape[0] // 2])
A4 = integrate_term(
    double_layer,
    line.pts,
    line,
    limit_direction=-1,
    singularities=np.array([[-100, 0], [100, 0]]),
)
print(A4[:, 0, :, 0].sum(axis=1)[A3.shape[0] // 2])

```

    -0.9999999999999523
    -1.687538997430238e-14

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:174: UserWarning: Some integrals failed to converge during adaptive integration. This an indication of a problem in either the integration or the problem formulation.
      warnings.warn(

    -0.5000000000000002
    0.5000000000000002

```python
qx, qw = gauss_rule(12)
t = sp.var("t")
circle = refine_surfaces(
    [(t, sp.cos(sp.pi * t), sp.sin(sp.pi * t))], (qx, qw), max_curvature=0.125
)
A = integrate_term(elastic_t(0.25), circle.pts, circle)
print(A[:, :, :, :].sum(axis=2)[0])
A2 = integrate_term(elastic_t(0.25), circle.pts, circle, limit_direction=-1)
print(A2[:, :, :, :].sum(axis=2)[0])

line = refine_surfaces(
    [(t, 100 * t, 0.0 * t)], (qx, qw), control_points=np.array([[0, 0, 100, 1]])
)
A3 = integrate_term(
    elastic_t(0.25),
    line.pts,
    line,
    singularities=np.array([[-100, 0], [100, 0]]),
)
print(A3[:, :, :, :].sum(axis=2)[A3.shape[0] // 2])
A4 = integrate_term(
    elastic_t(0.25),
    line.pts,
    line,
    singularities=np.array([[-100, 0], [100, 0]]),
    limit_direction=-1,
)
print(A4[:, :, :, :].sum(axis=2)[A3.shape[0] // 2])

```

    [[-1.00000000e+00 -4.98212582e-15]
     [ 3.46944695e-16 -1.00000000e+00]]
    [[-2.88380431e-14 -5.15559817e-15]
     [-5.00988140e-15 -9.43689571e-15]]

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:180: UserWarning: Some expanded integrals reached maximum expansion order. These integrals may be inaccurate.
      warnings.warn(

    [[-5.00000000e-01  7.64249014e-06]
     [-7.64249014e-06 -5.00000000e-01]]
    [[ 5.00000000e-01  7.64249014e-06]
     [-7.64249014e-06  5.00000000e-01]]

```python

```