# ¿Cómo funciona la suspensión de un auto?

> Una primer aproximación al modelo de la suspensión de un automovil es considerar el *oscilador armónico amortiguado*.

Referencia:
- https://es.wikipedia.org/wiki/Oscilador_arm%C3%B3nico#Oscilador_arm.C3.B3nico_amortiguado

Un **modelo** que describe el comportamiento del sistema mecánico anterior es

\begin{equation}
m\frac{d^2 x}{dt^2}=-c\frac{dx}{dt}-kx
\end{equation}

donde $c$ es la constante de amortiguamiento y $k$ es la constante de elasticidad.  Revisar modelado

Documentación de los paquetes que utilizaremos hoy.
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html
- https://docs.scipy.org/doc/scipy/reference/index.html

___
En `python` existe una función llamada _odeint_ del paquete _integrate_ de la libreria _scipy_, que permite integrar sistemas vectoriales de primer orden, del tipo

\begin{equation}
\frac{d\boldsymbol{y}}{dt} = \boldsymbol{f}(t,\boldsymbol{y}); \qquad \text{ con }\quad \boldsymbol{y}\in\mathbb{R}^n,\quad \boldsymbol{f}:\mathbb{R}_{+}\times\mathbb{R}^n\to\mathbb{R}^n
\end{equation}

con condiciones iniciales $\boldsymbol{y}(0) = \boldsymbol{y}_{0}$. Notar que  $\boldsymbol{y}$ representa un vector de $n$ componentes.

Ahora, si nos fijamos bien, el modelo del *oscilador armónico amortiguado* que obtuvimos es una ecuación diferencial ordinaria (EDO) de segundo orden. No hay problema. La podemos convertir en un sistema de ecuaciones de primer orden de la siguiente manera:

1. Seleccionamos el vector $\boldsymbol{y}=\left[y_1\quad y_2\right]^T$, con $y_1=x$ y $y_2=\frac{dx}{dt}$.
2. Notamos que $\frac{dy_1}{dt}=\frac{dx}{dt}=y_2$ y $\frac{dy_2}{dt}=\frac{d^2x}{dt^2}=-\frac{c}{m}\frac{dx}{dt}-\frac{k}{m}x=-\frac{c}{m}y_2-\frac{k}{m}y_1$.
3. Entonces, el modelo de segundo orden lo podemos representar como el siguiente sistema vectorial de primer orden:

\begin{equation}
\frac{d\boldsymbol{y}}{dt}=\left[\begin{array}{c}\frac{dy_1}{dt} \\ \frac{dy_2}{dt}\end{array}\right]=\left[\begin{array}{c}y_2 \\ -\frac{k}{m}y_1-\frac{c}{m}y_2\end{array}\right]=\left[\begin{array}{cc}0 & 1 \\-\frac{k}{m} & -\frac{c}{m}\end{array}\right]\boldsymbol{y}.
\end{equation}

```python
# Primero importamos todas las librerias, paquetes y/o funciones que vamos a utlizar

```

```python
# Definimos los parámetros k, m y B
k, m, B = 3, 1, 0.5

# Función f(t,y) que vamos a integrar

# Condiciones iniciales

# Especificamos los puntos de tiempo donde queremos la solución

# Solución numérica

```

¿Cómo entrega odeint las soluciones?

```python
# Averiguar la forma de solución

```

```python
# Mostrar la solución

```

- $yy$ es una matriz de n filas y 2 columnas.
- La primer columna de $yy$ corresponde a $y_1$.
- La segunda columna de $yy$ corresponde a $y_2$.

¿Cómo extraemos los resultados $y_1$ y $y_2$ independientemente?

```python
# Extraer y1 y y2

```

### Para hacer participativamente...
- Graficar en una misma ventana $y_1$ vs. $t$ y $y_2$ vs. $t$... ¿qué pueden observar?

```python
# Gráfica

```

- Graficar $y_2/\omega_0$ vs. $y_1$... ¿cómo se complementan estos gráficos? ¿conclusiones?

```python
# Gráfica

```

## Dependiendo de los parámetros, 3 tipos de soluciones

Teníamos
\begin{equation}
m\frac{d^2 x}{dt^2} + c\frac{dx}{dt} + kx = 0
\end{equation}

si recordamos que $\omega_0 ^2 = \frac{k}{m}$ y definimos $\frac{c}{m}\equiv 2\Gamma$, tendremos

\begin{equation}
\frac{d^2 x}{dt^2} + 2\Gamma \frac{dx}{dt}+ \omega_0^2 x = 0
\end{equation}

El comportamiento viene determinado por las raices de la ecuación característica. Ver en el tablero...

### Subamortiguado

Si $\omega_0^2 > \Gamma^2$ se tiene movimiento oscilatorio *subamortiguado*.

```python
omega0 = k/m
Gamma = B/(2*m)
```

```python
omega0**2, Gamma**2
```

    (9.0, 0.0625)

```python
omega0**2 > Gamma**2
```

    True

Entonces, el primer caso que ya habíamos presentado corresponde a movimiento amortiguado.

```python
# Gráfica, de nuevo

```

### Sobreamortiguado

Si $\omega_0^2 < \Gamma^2$ se tiene movimiento oscilatorio *sobreamortiguado*.

```python
# Nuevas constantes
k = .1 # Constante del muelle
m = 1.0   # Masa
B = 1 # Constante de amortiguación
```

Simular y graficar...

```python
omega0 = np.sqrt(k/m)
Gamma = B/(2*m)
```

```python
omega0**2, Gamma**2
```

    (0.10000000000000001, 0.25)

```python
omega0**2<Gamma**2
```

    True

```python
# Simular

```

```python
# Graficar

```

### Amortiguamiento crítico

Si $\omega_0^2 = \Gamma^2$ se tiene movimiento *críticamente amortiguado*.

```python
# Nuevas constantes
k = .0625 # Constante del muelle
m = 1.0   # Masa
B = .5 # Constante de amortiguación
```

Simular y graficar...

```python
omega0 = np.sqrt(k/m)
Gamma = B/(2*m)
```

```python
omega0**2, Gamma**2
```

    (0.0625, 0.0625)

```python
omega0**2 == Gamma**2
```

    True

```python
# Simular

```

```python
# Graficar

```

En resumen, se tiene entonces:

```python
fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2, 3, sharex='col',
                                                       sharey='row',figsize =(10,6))
ax1.plot(tt, y1, c = 'k')
ax1.set_title('Amortiguado', fontsize = 14)
ax1.set_ylabel('Posición', fontsize = 14)
ax2.plot(tt, y1s, c = 'b')
ax2.set_title('Sobreamortiguado', fontsize = 14)
ax3.plot(tt, y1c, c = 'r')
ax3.set_title('Crítico', fontsize = 16)
ax4.plot(tt, y2, c = 'k')
ax4.set_ylabel('Velocidad', fontsize = 14)
ax4.set_xlabel('tiempo', fontsize = 14)
ax5.plot(tt, y2s, c = 'b')
ax5.set_xlabel('tiempo', fontsize = 14)
ax6.plot(tt, y2c, c = 'r')
ax6.set_xlabel('tiempo', fontsize = 14)
plt.show()
```

> **Tarea**. ¿Cómo se ve el espacio fase para los diferentes casos así como para diferentes condiciones iniciales?

> En un gráfico como el anterior, realizar gráficas del plano fase para los distintos movimientos y para cuatro conjuntos de condiciones iniciales distintas
- y0 = [1, 1]
- y0 = [1, -1]
- y0 = [-1, 1]
- y0 = [-1, -1]

Hacer lo anterior en un nuevo notebook de jupyter llamado Tarea7_ApellidoNombre.ipynb y subir en el espacio habilitado.

Created with Jupyter by Lázaro Alonso. Modified by Esteban Jiménez Rodríguez.


```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import control
from ipywidgets import interact

ts = np.linspace(0, 10, 1000)

def pzmap_k(kc):
    plt.rcParams['figure.figsize'] = 10,4

    print(kc)
    ax_pzmap = plt.subplot2grid((1, 2), (0, 0))
    ax_resp = plt.subplot2grid((1, 2), (0, 1))

    gLC = control.feedback(kc*Gp,1)
    _, yLC = control.step_response(gLC, ts)

    pLC = gLC.pole()
    cLC = gLC.zero()

    ax_pzmap.plot(pLA.real, pLA.imag, 'rx',
                  cLA.real, cLA.imag, 'ro',
                  pLC.real, pLC.imag, 'bx',
                  cLC.real, cLC.imag, 'bo')
    ax_pzmap.legend(('p LA', 'c LA', 'p LC','c LC'))
    ax_pzmap.axis(visualizacion_s)
    ax_pzmap.grid()
    ax_resp.plot(ts,yLA,'r',ts,yLC,'b')
    ax_resp.axis(visualizacion_t)
    ax_resp.grid()

def rlocus_int(kc):
    print(kc)
    plt.rcParams['figure.figsize'] = 10,4
    ax_pzmap = plt.subplot2grid((1, 2), (0, 0))
    ax_resp = plt.subplot2grid((1, 2), (0, 1))
    _, y = control.step_response(Gp, ts)

    gLC = control.feedback(kc*Gp,1)
    _, yLC = control.step_response(gLC, ts)

    rlist,klist = control.root_locus(Gp,plot = False);

    pLC = gLC.pole()
    cLC = gLC.zero()

    ax_pzmap.plot(pLA.real, pLA.imag, 'rx',
                  cLA.real, cLA.imag, 'ro',
                  pLC.real, pLC.imag, 'bx',
                  cLC.real, cLC.imag, 'bo',
                  rlist.real, rlist.imag, '-k')
    ax_pzmap.legend(('p LA', 'c LA', 'p LC','c LC','LGDR'))
    ax_pzmap.axis(visualizacion_s)
    ax_pzmap.grid()
    ax_resp.plot(ts,yLA,'r',ts,yLC,'b')
    ax_resp.axis(visualizacion_t)
    ax_resp.grid()

```

# Lugar Geométrico de las Raíces

Dado el bucle de control y cosideraciones en los sistemas que lo componen, la función de transferencia de un proceso controlado es:

\begin{equation}
\frac{C(s)}{R(s)} = \frac{G_c(s)G_p(s)}{1 + G_c(s)G_p(s)H(s)}
\end{equation}

Para efectos prácticos considere que $H(s)=1$. Si además se tiene en cuenta que los sistemas son LTI, puede considerarse que sus modelos corresponden a una fracción de polinomios.

$$G_c(s)= \frac{N_c(s)}{D_c(s)}$$
$$G_p(s)= \frac{N_p(s)}{D_p(s)}$$

Y la función de transferencia de lazo cerrado queda:

$$\frac{C(s)}{R(s)} = \frac{\frac{N_c(s)N_p(s)}{D_c(s)D_p(s)}}{\frac{D_c(s)D_p(s) + N_c(s)N_p(s)}{D_c(s)D_p(s)}} = \frac{N_c(s)N_p(s)}{D_c(s)D_p(s) + N_c(s)N_p(s)}$$

Cuando se considera que $G_c(s)=k_c$, la función de transferencia de lazo cerrado se reduce a:

\begin{equation}
\frac{C(s)}{R(s)} = \frac{k_c G_p(s)}{1 + k_c G_p(s)}
\end{equation}

o

$$\frac{C(s)}{R(s)} = \frac{k_cN_p(s)}{D_p(s) + k_cN_p(s)}$$

Observe que:
- Los polos del sistema en lazo cerrado dependen de qué valor tome $k_c$.
- Los ceros del sistema en lazo cerrado son los mismos que en lazo abierto al menos si $k_c   \neq 0$.

**¿Cómo varía la ubicación de los polos en función de $k_c$?**

**Ejemplo**

Suponga un proceso modelado por:

$$G_p(s) = \frac{1}{s - 1}$$

y una estrategia de contro definida por:

$$G_c(s) = k_c$$

```python
# Se define la función de transferencia del proceso
Gp = control.tf(1, [1,-1])
Gp
```

$$\frac{1}{s - 1}$$

Varíe $k_c$ y observe cómo varía la posición de los polos del sistema.

```python
visualizacion_s = [-10, 10, -5, 5]
visualizacion_t = [0, 10, -2, 2]

pLA = Gp.pole()
cLA = Gp.zero()
_, yLA = control.step_response(Gp, ts)

interact(pzmap_k,
         kc=(-10., 10.))
```

    interactive(children=(FloatSlider(value=0.0, description='kc', max=10.0, min=-10.0), Output()), _dom_classes=(…

    <function __main__.pzmap_k(kc)>

¿Qué pasa con un sistema de segundo orden?

```python
# Se define la función de transferencia del proceso
Gp = control.tf(2, [1,4,1])
Gp

```

$$\frac{2}{s^2 + 4 s + 1}$$

```python
visualizacion_s = [-10, 10, -5, 5]
visualizacion_t = [0, 10, -2, 2]

pLA = Gp.pole()
cLA = Gp.zero()
_, yLA = control.step_response(Gp, ts)

interact(pzmap_k,
         kc=(0., 10.))
```

    interactive(children=(FloatSlider(value=5.0, description='kc', max=10.0), Output()), _dom_classes=('widget-int…

    <function __main__.pzmap_k(kc)>

Observe que en ambos casos, al variar el parámetro $k_c>0$,los polos del sistema en lazo cerrado describen trayectorias contiguas en el plano complejo $s$. A estas trayectorias se les conoce como [**Lugar Geométrico de las Raíces**](https://en.wikipedia.org/wiki/Root_locus).

En este caso se ha variado el valor $k_c$, sin embargo es posible generar lugares variando un parámetro diferente dentro de $G_c(s)$ o $H(s)$.

Hay técnicas numéricas para dibujar una aproximación del Lugar Geométrico de las Raíces, sin embargo, se usarán técnicas computacionales.

**Ejemplo**

Considere un sistema de orden 3.

$$G_p(s) = \frac{2}{s \left ( s^2 + 3s + 2\right ) }$$

Construya el Lugar de las raíces.

```python
# Se define la función de transferencia del proceso
Gp = control.tf(2, [1,3,2,0])
Gp
```

$$\frac{2}{s^3 + 3 s^2 + 2 s}$$

```python
visualizacion_s = [-5, 5, -5, 5]
visualizacion_t = [0, 10, -2, 4]

pLA = Gp.pole()
cLA = Gp.zero()
_, yLA = control.step_response(Gp, ts)

rlocus_int(2)
```

```python
visualizacion_s = [-5, 5, -5, 5]
visualizacion_t = [0, 10, -2, 4]

pLA = Gp.pole()
cLA = Gp.zero()
_, yLA = control.step_response(Gp, ts)

interact(rlocus_int,
         kc=(0., 5.))
```

    interactive(children=(FloatSlider(value=2.5, description='kc', max=5.0), Output()), _dom_classes=('widget-inte…

    <function __main__.rlocus_int(kc)>

Observe:
- Este sistema es inestable en lazo abierto.
- Al cerrar el lazo, el sistema se estabiliza pero si la ganancia sobrepasa un valor, se vuelve inestable nuevamente.

Retomando la expresión

$$\frac{C(s)}{R(s)} = \frac{k_cN_p(s)}{D_p(s) + k_cN_p(s)}$$

Se llama ecuación característica a $D_p(s) + k_cN_p(s) = 0$. Esta permite definir la ubicación de los polos de lazo cerrado.

**Preguntas**

- ¿Hacia dónde se mueven los polos de lazo cerrado cuando $k_c \rightarrow \infty$?
- ¿Puede encontrar los rangos de $k_c$ que hacen estable al sistema?

Repita los análisis realizados con:

$$G_p(s) = \frac{s+3}{\left (s+2 \right )\left (s+1 \right )} $$

```python

```


# Lab 05 Logistic Classification(Regression)
* Logistic Classfication은 True or False와 같은 Binary나 복수개의 다항 분류에 쓰입니다 (Bernoulli Distribution)

### 기본 Library 선언 및 Tensorflow 버전 확인

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

print(tf.__version__)
```

    1.10.0

### 강의에 설명할 Data입니다
* x_data가 2차원 배열이기에 2차원 공간에 표현하여 x1과 x2를 기준으로 y_data 0과 1로 구분하는 예제입니다
* Logistic Classification 통해 보라색과 노란색 y_data(Label)을 구분해 보겠습니다.

```python
x_data = [[1, 2],
          [2, 3],
          [3, 1],
          [4, 3],
          [5, 3],
          [6, 2]]
y_data = [[0],
          [0],
          [0],
          [1],
          [1],
          [1]]

x1 = [x[0] for x in x_data]
x2 = [x[1] for x in x_data]

colors = [int(y[0] % 3) for y in y_data]
plt.scatter(x1,x2, c=colors)

plt.xlabel("x1")
plt.ylabel("x2")
plt.show()
```

### 위 Data를 기준으로 가설의 검증을 통해 Logistic Classification 모델을 만들도록 하겠습니다
* X,Y는 실재 학습에 쓰일 Data (placeholder)
* W와 b은 학습을 통해 생성되는 모델에 쓰이는 Wegith와 Bias (초기값을 variable : 0이나 Random값으로 가능 tf.random_normal([2, 1]) )

```python
X = tf.placeholder(tf.float32, shape=[None, 2])
Y = tf.placeholder(tf.float32, shape=[None, 1])

W = tf.Variable(tf.zeros([2,1]), name='weight')
b = tf.Variable(tf.zeros([1]), name='bias')
```

### Sigmoid 함수를 가설로 선언합니다
* Sigmoid는 아래 그래프와 같이 0과 1의 값만을 리턴합니다 tf.sigmoid(tf.matmul(X, W) + b)와 같습니다

$$
\begin{align}
sigmoid(x) & = \frac{1}{1+e^{-x}}  \\\\\
\end{align}
$$

```python
hypothesis  = tf.div(1., 1. + tf.exp(tf.matmul(X, W) + b))
```

### 가설을 검증할 Cost 함수를 정의합니다
$$
\begin{align}
cost(h(x),y) & = −log(h(x))  &  if  &  y=1 \\\\\
cost(h(x),y) & = -log(1−h(x))  &  if  &  y=0
\end{align}
$$
* 위 두수식을 합치면 아래과 같습니다
$$
\begin{align}
cost(h(x),y) & = −y log(h(x))−(1−y)log(1−h(x))
\end{align}
$$

```python
cost = -tf.reduce_mean(Y * tf.log(hypothesis) + (1 - Y) *
                       tf.log(1 - hypothesis))

train = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(cost)
```

### 추론한 값은 0.5를 기준(Sigmoid 그래프 참조)로 0과 1의 값을 리턴합니다.
* Sigmoid 함수를 통해 예측값이 0.5보다 크면 1을 반환하고 0.5보다 작으면 0으로 반환합니다.

```python
predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
```

### 가설을 통해 실재 값과 비교한 정확도를 측정합니다

```python
accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, Y), dtype=tf.int32))
```

### Tensorflow를 통한 실행을 위해 Session를 선언합니다.
* 위의 Data를 Cost함수를 통해 학습시킨 후 모델을 생성합니다.
* 새로운 Data를 통한 검증 수행 [5,2]의 Data로 테스트 수행 (그래프상 1이 나와야 정상입니다)
* 새로운 Data를 통한 검증 수행 [2,2]의 Data로 테스트 수행 (그래프상 0이 나와야 정상입니다)

```python
test_x1 = [[5,2]]
test_x2 = [[2,2]]

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for step in range(10001):
        cost_val, _ = sess.run([cost, train], feed_dict={X: x_data, Y: y_data})
        if step % 1000 == 0:
            print(step, cost_val)

    # Accuracy report
    h, c, a = sess.run([hypothesis, predicted, accuracy],
                       feed_dict={X: x_data, Y: y_data})
    print("\nHypothesis: ", h, "\nCorrect (Y): ", c, "\nAccuracy: ", a)
#     print("W value : ", sess.run(W))
#     print("b value : ", sess.run(b))
    print("[5,2] : ",   sess.run(predicted,feed_dict={X:test_x1}))
    print("[2,2] : ",   sess.run(predicted,feed_dict={X:test_x2}))
```

    0 0.6931472
    1000 0.41448987
    2000 0.34960732
    3000 0.3013888
    4000 0.26361898
    5000 0.23360115
    6000 0.20939068
    7000 0.18956773
    8000 0.17310147
    9000 0.15924035
    10000 0.1474307

    Hypothesis:  [[0.02987642]
     [0.1576593 ]
     [0.30070737]
     [0.78328896]
     [0.9407705 ]
     [0.98057085]]
    Correct (Y):  [[0.]
     [0.]
     [0.]
     [1.]
     [1.]
     [1.]]
    Accuracy:  1
    [5,2] :  [[1.]]
    [2,2] :  [[0.]]


# Choice Function

```python
import warnings
warnings.simplefilter('ignore')
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from csrank import *
from keras.optimizers import SGD
```

## Learning Pareto-Optimality

We now try to solve the problem of predicting the Pareto-optimal set of points $O \subseteq X$.
In other words, it is the set of points which or not dominated by any other point.

Let us plot a small example:

```python
from csrank import ChoiceDatasetGenerator
seed = 12
n_train = 10000
n_test = 10000
n_features = 2
n_objects = 40
gen = ChoiceDatasetGenerator(dataset_type='pareto', random_state=seed,
                                n_train_instances=n_train,
                                n_test_instances=n_test,
                                n_objects=n_objects,
                                n_features=n_features)
```

```python
X_train, Y_train, X_test, Y_test = gen.get_single_train_test_split()
```

Let us plot a random instances. The pareto points are marked with orange color.

```python
colors = sns.color_palette('tab10', n_colors=2)
fig, ax = plt.subplots(figsize=(7,7))
inst = np.random.choice(n_train, size=300)
rows, cols = np.where(Y_train[inst]==1)
ax.scatter(X_train[inst].reshape(-1,2)[:, 0], X_train[inst].reshape(-1,2)[:, 1], c=[colors[0]], marker='.', alpha=0.5)
ax.scatter(X_train[rows, cols][:, 0], X_train[rows, cols][:, 1], c=[colors[1]], marker='.', alpha=0.5)
plt.show()
```

## The RankNet network
This network learns a latent utility score for each object in the given choice set $Q = \{\vec{x}_1, \ldots , \vec{x}_n\}$
\begin{equation}
	U(\vec{x}_i) = F(\vec{x}_i, \vec{\beta}) \enspace,
\end{equation}
where the weight vector $\vec{\beta}$ is estimated using pairwise preferences generated from the choices.
For Example, the decision maker is faced with choice set $Q = \{ \vec{x_1}, \ldots ,\vec{x_5} \}$ and chooses the object $\vec{x_4}$.
Then one can extract the following _pairwise preferences_, $\vec{x_4} \succ \vec{x_1}, \vec{x_4} \succ \vec{x_2}, \ldots$.

```python
ranknet = RankNetChoiceFunction(
    n_object_features=n_features,
    optimizer=SGD(lr=1e-4, nesterov=True, momentum=0.9))
```

We will run the training for only 10 epochs to get an idea of the convergence:

```python
ranknet.fit(X_train, Y_train, verbose=True, epochs=10)
```

    WARNING:tensorflow:From /home/pritha/anaconda3/envs/linenv/lib/python3.7/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
    Instructions for updating:
    Use tf.cast instead.
    Train on 2430581 samples, validate on 270065 samples
    Epoch 1/10
    2430581/2430581 [==============================] - 17s 7us/step - loss: 0.1507 - binary_accuracy: 0.9444 - val_loss: 0.0977 - val_binary_accuracy: 0.9681
    Epoch 2/10
    2430581/2430581 [==============================] - 16s 7us/step - loss: 0.0882 - binary_accuracy: 0.9685 - val_loss: 0.0825 - val_binary_accuracy: 0.9711
    Epoch 3/10
    2430581/2430581 [==============================] - 18s 7us/step - loss: 0.0817 - binary_accuracy: 0.9703 - val_loss: 0.0782 - val_binary_accuracy: 0.9716
    Epoch 4/10
    2430581/2430581 [==============================] - 17s 7us/step - loss: 0.0794 - binary_accuracy: 0.9711 - val_loss: 0.0751 - val_binary_accuracy: 0.9727
    Epoch 5/10
    2430581/2430581 [==============================] - 17s 7us/step - loss: 0.0782 - binary_accuracy: 0.9713 - val_loss: 0.0729 - val_binary_accuracy: 0.9737
    Epoch 6/10
    2430581/2430581 [==============================] - 40s 16us/step - loss: 0.0777 - binary_accuracy: 0.9716 - val_loss: 0.0718 - val_binary_accuracy: 0.9741
    Epoch 7/10
    2430581/2430581 [==============================] - 19s 8us/step - loss: 0.0772 - binary_accuracy: 0.9717 - val_loss: 0.0714 - val_binary_accuracy: 0.9742
    Epoch 8/10
    2430581/2430581 [==============================] - 14s 6us/step - loss: 0.0770 - binary_accuracy: 0.9717 - val_loss: 0.0712 - val_binary_accuracy: 0.9743
    Epoch 9/10
    2430581/2430581 [==============================] - 19s 8us/step - loss: 0.0767 - binary_accuracy: 0.9719 - val_loss: 0.0713 - val_binary_accuracy: 0.9743
    Epoch 10/10
    2430581/2430581 [==============================] - 17s 7us/step - loss: 0.0764 - binary_accuracy: 0.9719 - val_loss: 0.0727 - val_binary_accuracy: 0.9732
    [============================================================] 37854/37855 ...Tuning threshold

```python
scores = ranknet.predict_scores(X_test)
y_pred = ranknet.predict_for_scores(scores)
```

```python
from csrank.metrics_np import f1_measure
f1_measure(Y_test, y_pred)
```

    0.5941317737052706

Not converged yet, but let us visualize the scores it assigns to test instances:

```python
fig, ax = plt.subplots(figsize=(7,7))
inst = np.random.choice(n_test)
choices = np.where(Y_test[inst]==1)[0]
ax.scatter(X_test[inst][:, 0], X_test[inst][:, 1])
ax.scatter(X_test[inst][choices, 0], X_test[inst][choices, 1])
for i in range(n_objects):
    if Y_test[inst, i]:
        color = 'r'
    else:
        color = 'b'
    ax.text(X_test[inst, i, 0]-0.2,
            X_test[inst, i, 1]-0.2,
            s='{:.1f}'.format(scores[inst][i]),
            color=color)
```


```python
%load_ext autoreload
%autoreload 2

import utils
from sympy import sqrt
import sympy as sy
from sympy.physics.mechanics import dynamicsymbols, init_vprinting
init_vprinting()
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

```python
dh_params = [
    [0, 0, "d_1", 0],
    [0, 0, 0, "theta_2"],
    [0, "l_2", 0, "theta_3"],
]

# Center of mass of each link
m_center_points_0 = [
    sy.Matrix([0, 0, ]),
    sy.Matrix([0, 0, sy.Symbol("l_2")])
]

# Link mass
link_m = [sy.Symbol("m_1"), sy.Symbol("m_2"), sy.Symbol("m_3")]

# Link inertia tensor
link_I = [
    sy.diag(sy.Symbol("I_{xx1}"), sy.Symbol("I_{yy1}"), sy.Symbol("I_{zz1}")),
    sy.diag(sy.Symbol("I_{xx2}"), sy.Symbol("I_{yy2}"), sy.Symbol("I_{zz2}")),
    sy.diag(sy.Symbol("I_{xx3}"), sy.Symbol("I_{yy3}"), sy.Symbol("I_{zz3}"))
]
```

```python
utils.build_transf(dh_params)
```

$\displaystyle {}^0_1T = \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & d_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle {}^1_2T = \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & 0\\\sin{\left(\theta_{2} \right)} & \cos{\left(\theta_{2} \right)} & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle {}^2_3T = \left[\begin{matrix}\cos{\left(\theta_{3} \right)} & - \sin{\left(\theta_{3} \right)} & 0 & l_{2}\\\sin{\left(\theta_{3} \right)} & \cos{\left(\theta_{3} \right)} & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

    [Matrix([
     [1, 0, 0,   0],
     [0, 1, 0,   0],
     [0, 0, 1, d_1],
     [0, 0, 0,   1]]),
     Matrix([
     [cos(theta_2), -sin(theta_2), 0, 0],
     [sin(theta_2),  cos(theta_2), 0, 0],
     [           0,             0, 1, 0],
     [           0,             0, 0, 1]]),
     Matrix([
     [cos(theta_3), -sin(theta_3), 0, l_2],
     [sin(theta_3),  cos(theta_3), 0,   0],
     [           0,             0, 1,   0],
     [           0,             0, 0,   1]])]

```python

```

```python
t = dynamicsymbols("theta_1")
t.diff()
```

```python
[[dynamicsymbols(p) for p in params if isinstance(p, str)] for params in dh_params]
```

```python
utils.lagrange(dh_params, m_center_points_0, link_m, link_I, verbose=True, simple=True)
```

```python

```


# Linear Regression: Using a Decomposition (Cholesky Method)
--------------------------------

This script will use TensorFlow's function, `tf.cholesky()` to decompose our design matrix and solve for the parameter matrix from linear regression.

For linear regression we are given the system $A \cdot x = y$.  Here, $A$ is our design matrix, $x$ is our parameter matrix (of interest), and $y$ is our target matrix (dependent values).

For a Cholesky decomposition to work we assume that $A$ can be broken up into a product of a lower triangular matrix, $L$ and the transpose of the same matrix, $L^{T}$.

Note that this is when $A$ is square.  Of course, with an over determined system, $A$ is not square.  So we factor the product $A^{T} \cdot A$ instead.  We then assume:

$$A^{T} \cdot A = L^{T} \cdot L$$

For more information on the Cholesky decomposition and it's uses, see the following wikipedia link: [The Cholesky Decomposition](https://en.wikipedia.org/wiki/Cholesky_decomposition)

Given that $A$ has a unique Cholesky decomposition, we can write our linear regression system as the following:

\begin{align}
A \cdot x &= y \\
\Longleftrightarrow A^T \cdot A \cdot x &= A^T \cdot y \\
\Longleftrightarrow L^{T} \cdot L \cdot x &= A^{T} \cdot y
\end{align}

Then we break apart the system as follows:

$$L^{T} \cdot z = A^{T} \cdot y$$

and

$$L \cdot x = z$$

The steps we will take to solve for $x$ are the following

 1. Compute the Cholesky decomposition of $A$, where $A^{T} \cdot A = L^{T} \cdot L$.

 2. Solve ($L^{T} \cdot z = A^{T} \cdot y$) for $z$.

 3. Finally, solve ($L \cdot x = z$) for $x$.

We start by loading the necessary libraries.

```python
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.python.framework import ops
ops.reset_default_graph()
```

Next we create a graph session

```python
sess = tf.Session()
```

We use the same method of generating data as in the prior recipe for consistency.

```python
# Create the data
x_vals = np.linspace(0, 10, 100)
y_vals = x_vals + np.random.normal(0, 1, 100)
```

We generate the design matrix, $A$.

```python
# Create design matrix
x_vals_column = np.transpose(np.matrix(x_vals))
ones_column = np.transpose(np.matrix(np.repeat(1, 100)))
A = np.column_stack((ones_column, x_vals_column))
```

Next, we generate the

```python
# Create y matrix
y = np.transpose(np.matrix(y_vals))

# Create tensors
A_tensor = tf.constant(A)
y_tensor = tf.constant(y)
```

Now we calculate the square of the matrix $A$ and the Cholesky decomposition.

```python
# Find Cholesky Decomposition
tA_A = tf.matmul(tf.transpose(A_tensor), A_tensor)
L = tf.cholesky(tA_A)  # tf.cholesky returns L only. L is Lower triangular matrix
```

We solve the first equation. (see step 2 in the intro paragraph above)

```python
# Solve L*z=t(A)*y
tA_y = tf.matmul(tf.transpose(A_tensor), y)
z = tf.matrix_solve(L, tA_y)
```

We finally solve for the parameter matrix by solving the second equation (see step 3 in the intro paragraph).

```python
# Solve L' * sol = z
sol = tf.matrix_solve(tf.transpose(L), z)

solution_eval = sess.run(sol)  # shape=(2, 1)
```

Extract the coefficients and create the best fit line.

```python
# Extract coefficients
slope = solution_eval[1][0]
y_intercept = solution_eval[0][0]

print('slope: ' + str(slope))
print('y_intercept: ' + str(y_intercept))

# Get best fit line
best_fit = []
for i in x_vals:
    best_fit.append(slope*i+y_intercept)
```

    slope: 0.9669761720326052
    y_intercept: 0.056840205248713006

Finally, we plot the fit with Matplotlib.

```python
# Plot the results
plt.style.use("ggplot")
plt.plot(x_vals, y_vals, 'o', label='Data')
plt.plot(x_vals, best_fit, 'b-', label='Best fit line', linewidth=3)
plt.legend(loc='upper left')
plt.show()
```

```python

```


```python
import sympy
sympy.init_printing()
```

```python
t = sympy.symbols('t')
qh, q, omega_m, omega_b, omega_bh, K_q, E, alpha = sympy.symbols(
    '\hat{q}, q, omega_m, omega_b, \hat{\omega_b}, K_q, E, alpha',
    commutative=False)
M_q, w_q, mu, beta, a_sh, a_s, a_m, A, K_v, w_v, M_v = sympy.symbols(
    'M_q, w_q, mu, beta, \hat{a_s}, a_s, a_m, A, K_v, w_v, M_v',
    commutative=False)
I_ah, I_omegah, K_omega, M_omega, w_omega = sympy.symbols(
    '\hat{I_a}, \hat{I_\omega}, K_omega, M_omega, w_omega',
    commutative=False)
M_a, w_a, K_a, nu = sympy.symbols(
    'M_a, w_a, K_a, nu',
    commutative=False)
```

```python
qh_dot_expr = 0.5*qh*(omega_m - omega_bh) + K_q*E*qh
qh_dot_expr
```

```python
q_dot_expr = 0.5*q*(omega_m - omega_b) + M_q*w_q*q
q_dot_expr
```

```python
Vh_dot = 1/a_sh*qh*a_m/qh + A + K_v*E
Vh_dot
```

```python
Vh = A + 1/a_s*q*a_m/q + M_v*w_v
Vh
```

```python
omega_bh_dot = 1/qh*K_omega*E*qh
omega_bh_dot
```

```python
omega_b_dot = 1/q*M_omega*w_omega*q
omega_b_dot
```

```python
a_s_dot = a_s*M_a*w_a
a_s_dot
```

```python
a_sh_dot = a_sh*K_a*E
a_sh_dot
```

```python
def q_inv_dot(q, q_dot):
    return (-1/q*q_dot/q)
```

```python
mu_expr = qh/q
mu_inv_expr = q/qh
beta_expr = q*(omega_bh - omega_b)/q
alpha_expr = a_sh/a_s
I_ah_expr = 1/a_sh*qh*a_m/qh
I_omegah_expr = qh*(omega_m - omega_bh)/qh
assert mu_expr*mu_inv_expr == 1

assert 1/a_s - 1/a_sh*alpha_expr == 0
assert 1/q - 1/qh*mu_expr == 0
assert q - mu_inv_expr*qh == 0
assert mu_expr*q - qh == 0
assert (omega_bh - omega_b)/q - 1/q*beta_expr == 0
assert (mu_inv_expr*I_omegah_expr*mu_expr*q + beta_expr*q - \
 q*(-omega_b + omega_m)).simplify() == 0

sub_invariants = {
    I_ah_expr: I_ah,
    I_omegah_expr: I_omegah
}
sub_commutative = {
    1/a_sh/mu: 1/mu/a_sh
}
sub_terms = {
    1/a_s: alpha*1/a_sh,
    1/q: 1/qh*mu,
    q: 1/mu*qh,
}
sub_error = {
    mu_expr: mu,
    (-0.5*mu*beta_expr).expand(): -0.5*mu*beta,
}
sub_beta = {
    beta_expr: beta,
    (omega_bh - omega_b)/q: 1/q*beta,
    q*(-omega_b + omega_m): 1/mu*I_omegah*mu*q + beta*q,
    1/q: 1/qh*mu,
    q: 1/mu*qh,
}
```

```python
mu_dot_expr = (qh_dot_expr/q + qh*q_inv_dot(q, q_dot_expr)).expand().subs(
    qh, mu*q).subs(sub_error)
mu_dot_expr
```

```python
nu_dot_expr = Vh_dot - Vh
nu_dot_expr = nu_dot_expr.subs(
    sub_terms).subs(sub_commutative).subs(sub_invariants)
nu_dot_expr
```

```python
beta_dot = (q_dot_expr*(omega_bh - omega_b)/q +
    q*(omega_bh_dot - omega_b_dot)/q +
    q*(omega_bh - omega_b)*q_inv_dot(q, q_dot_expr))
beta_dot
```

```python
beta_dot1 = beta_dot.subs({
    beta_expr: beta,
    (omega_bh - omega_b)/q: 1/q*beta,
    q*(-omega_b + omega_m): 1/mu*I_omegah*mu*q + beta*q,
    1/q: 1/qh*mu,
    q: 1/mu*qh,
})
beta_dot2 = beta_dot1.expand().simplify()
beta_dot2
```

```python
cross = sympy.symbols('\\times', commutative=False)
sub_cross = {
    0.5/mu*I_omegah*mu*beta -0.5*beta/mu*I_omegah*mu :
        1/mu*I_omegah*mu * cross * beta,
    M_q*w_q*beta - beta*M_q*w_q: 2 * M_q*w_q* cross * beta,
}
beta_dot3 = beta_dot2.subs(sub_cross)
beta_dot3
```

```python
alpha_dot = 1/(a_s**2)*(a_s*a_sh_dot - a_sh*a_s_dot)
alpha_dot = alpha_dot.collect(a_s*a_sh).subs(1/a_s*a_sh, alpha)
alpha_dot
```

```python
e_dot = sympy.Matrix([mu_dot_expr, nu_dot_expr, beta_dot3, alpha_dot])
e_dot
```

Sympy uses the wrong rules for the derivative on the inverse, which
causes A to be wrong.

```python
(1/mu*I_ah*mu).diff(mu)
```

```python
(1/mu).diff(mu)
```

```python
nu_dot_expr.diff(mu)
```

```python
e = sympy.Matrix([mu, nu, beta, alpha])
e.T
```

```python
w = sympy.Matrix([w_q, w_v, w_omega, w_a])
w.T
```

```python
nu_dot_expr
```

```python
sub_lin = {
    mu: 1,
    nu: 0,
    beta: 0,
    alpha: 1,
    E: 0,
    w_q: 0,
    w_a: 0,
}
A = e_dot.jacobian(e).subs(sub_lin)
A
```

```python
M = e_dot.jacobian(w).subs(sub_lin)
M
```

```python
e_dot.jacobian(sympy.Matrix([E])).subs(sub_lin)
```


```python
import matplotlib.pyplot as plt
import numpy as np
from numpy import sin, cos, exp, log, pi, cosh, abs, sqrt, cosh
import mpl_toolkits.mplot3d

from ipywidgets import interact

from scipy.integrate import quad
from scipy.optimize import minimize_scalar
```

$\renewcommand{\vec}{\mathbf}$

# HW 12 Problem 3

In which you were asked to find a surface joining unit circles at $z = 0$ and $z = 1$ with surface as small as **you** can find. The optimal solution is beyond the scope of this class, but you might find it with a little [googling](http://paulbourke.net/geometry/catenoid/).

The extreme cases given, the cylinder and the 2 disks can both be considered as surfaces of revolution.

```python
u,v = np.meshgrid(np.linspace(0,1,100),np.linspace(0,2*pi,100))
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121,projection='3d')
ax2 = fig.add_subplot(122,projection='3d')
ax1.plot_surface(u*cos(v),u*sin(v),1 + 0*u,color='b',alpha=0.6)
ax1.plot_surface(u*cos(v),u*sin(v), 0*u,color='b',alpha=0.6)
ax1.plot([0,0],[0,0],[0,1],color='b',alpha=0.6,lw=3)
ax2.plot_surface(cos(v),sin(v),u,color='r',alpha=0.6);
t = np.linspace(0,2*pi,100)
for ax in [ax1,ax2]:
    ax.set_zlim(-1/2,3/2)
    ax.view_init(15,30)
    ax.plot([0,0],[-1,1],[0,0],'k',lw = 1,zorder=-1)
    ax.plot([-1,1],[0,0],[0,0],'k',lw = 1,zorder=-1)
    ax.plot([0,0],[0,0],[-1/2,3/2],'k',lw = 1,zorder=-1)

    for z in [0,1]:
        ax.plot(cos(t), sin(t), z,'k',lw=2)

```

## Surface area for surface of revolution

The parametric form of a surface of revolution $\Sigma$ about the $z$-axis is
$$\vec r(u,v) = \begin{bmatrix} R(u) \cos v \\ R(u) \sin v \\ u \end{bmatrix}$$
for $a \leq u \leq b, 0 \leq v \leq 2\pi$. $R(u)$ gives the radius of each circle at the level $z=u$.

The surface area can be computed in terms of $R$. First, we compute $\vec r_u \times r_v$:

$$\vec r_u \times r_v = \begin{vmatrix} \vec i & \vec j & \vec k \\ R'(u)\cos v & R'(u)\sin v & 1 \\ -R(u) \sin v & R(u) \cos v & 0 \end{vmatrix} = \begin{bmatrix} - R(u) \cos v \\ - R(u) \sin v \\ R(u)R'(u) \end{bmatrix} $$ which is oriented inward, but that is no bother as we are computing surface area.

\begin{equation}\text{SA}(\Sigma) = \iint_\Sigma dS = \int_0^{2\pi}\int_a^b |\vec r_u \times r_v|\,du\,dv = {2\pi}\int_a^b R(u)\sqrt{1 + (R'(u))^2}\,du \end{equation} (We can do the $v$ integral first as the interior is independent of $v$.)

## Back to the task at hand

Thus, to have our boundary circles for this problem, we need only restrict $0 \leq u \leq 1, 0 \leq v \leq 2\pi$ and ensure $R(0) = R(1) = 1$.

The surfaces above come from the following choices of $R(u)$. The 2-disk (blue) solution is not really a function, but you get the idea.

```python
u = np.linspace(0,1,100)
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

ax1.plot([0,0,1,1],[1,0,0,1],color='b',alpha=0.6,lw=2)
ax2.plot([0,1],[1,1],color='r',alpha=0.6,lw=2);
ax1.set_ylabel("$R$",fontsize=14)
ax1.set_xlabel("$u$",fontsize=14)
ax2.set_ylabel("$R$",fontsize=14)
ax2.set_xlabel("$u$",fontsize=14)

ax2.set_ylim(-0.10,1.1)
ax1.set_ylim(-0.1,1.1);

```

We will try different $R(u)$'s.

### Try 1 - absolute value

We "pinch in the waist" of our cylinder by finding an $R(u)$ with smaller values in the middle. Let's try straight lines first.

```python
u = np.linspace(0,1,100)
v = np.linspace(0,2*pi,100)
U,V = np.meshgrid(u,v)
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(121)
ax2 = fig.add_subplot(122,projection='3d')
ax.plot([0,1/2,1],[1,0,1],color='#003400',alpha=0.6,lw=2)
ax.set_ylabel("$R$",fontsize=14)
ax.set_xlabel("$u$",fontsize=14)

ax.set_ylim(-0.1,1.1);
ax2.plot_surface(abs(U-1/2)*2*cos(V),abs(U-1/2)*2*sin(V),U,color='#003400',alpha=0.6);
t = np.linspace(0,2*pi,100)
ax2.set_zlim(-1/2,3/2)
ax2.view_init(15,30)
ax2.plot([0,0],[-1,1],[0,0],'k',lw = 1,zorder=-1)
ax2.plot([-1,1],[0,0],[0,0],'k',lw = 1,zorder=-1)
ax2.plot([0,0],[0,0],[-1/2,3/2],'k',lw = 1,zorder=-1)

for z in [0,1]:
    ax2.plot(cos(t), sin(t), z,'k',lw=2)
```

The function is $$R(u) = 2|u - 1/2|.$$ Plugging into the SA formula above, we have $$\text{SA} = 2\pi \int_0^1 2|u - 1/2|\sqrt{5}\,du$$ as the derivative always has magnitude 2.

Easy as this is, we're sitting on a python interpreter with `scipy.integrate.quad`:

```python
quad(lambda u: 2*abs(u - 1/2)*sqrt(5), 0, 1)[0]
```

    1.118033988749895

Too big.

**Note**. The answer is of course this times $2\pi$, but as we are comparing it to the cylinder, it is easier to compare this number to 1.

Let's move the notch up and try $$R(u) = |u-1/2lambda u: abs(u-1/2) + 1/2| + 1/2.$$

```python
# Let's take all that code from early and make 1 function so we can try out new functions

def plot_R(fn):
    # Check that fn is a good candidate first
    assert np.isclose(fn(1),1)
    assert np.isclose(fn(0),1)

    u = np.linspace(0,1,100)
    v = np.linspace(0,2*pi,100)
    U,V = np.meshgrid(u,v)
    fig = plt.figure(figsize=(10,5))
    ax = fig.add_subplot(121)
    ax2 = fig.add_subplot(122,projection='3d')
    ax.plot(u,fn(u),color='#003400',alpha=0.6,lw=2)
    ax.set_ylabel("$R$",fontsize=14)
    ax.set_xlabel("$u$",fontsize=14)

    ax.set_ylim(-0.1,1.1);
    ax2.plot_surface(fn(U)*cos(V),fn(U)*sin(V),U,color='#003400',alpha=0.6);
    t = np.linspace(0,2*pi,100)
    ax2.set_zlim(-1/2,3/2)
    ax2.view_init(15,30)
    ax2.plot([0,0],[-1,1],[0,0],'k',lw = 1,zorder=-1)
    ax2.plot([-1,1],[0,0],[0,0],'k',lw = 1,zorder=-1)
    ax2.plot([0,0],[0,0],[-1/2,3/2],'k',lw = 1,zorder=-1)

    for z in [0,1]:
        ax2.plot(cos(t), sin(t), z,'k',lw=2)

# Test it here
plot_R(lambda u: abs(u-1/2) + 1/2)
```

And its surface area over $2\pi$, remembering $|R'(u)| = 1$ (except at the critical point which does not affect the integral).

```python
quad(lambda u: (abs(u-1/2) + 1/2)*sqrt(2),0,1)[0]
```

    1.0606601717798214

Better, but still worse than 1 (the cylinder). Before we abandon these, let's introduce a parameter $h$ for the height of the notch.

$$R(u) = 2(1-h)|u-1/2| + h$$

The leading coefficient is necessary to have $R(0) = R(1) = 1$.

```python
@interact(h=(0,1,.025))
def _(h=0.5):
    plot_R(lambda u: 2*(1-h)*abs(u-1/2) + h)
```

    interactive(children=(FloatSlider(value=0.5, description='h', max=1.0, step=0.025), Output()), _dom_classes=('…

Then we can make the surface area a function of $h$.

$$\text{SA}(h)= 2\pi \int_0^1 (2(1-h)|u-1/2| + h)\sqrt{1 + (2(1-h))^2}\,du $$

```python
h = np.linspace(0,1,50)
plt.plot(h,[quad(lambda u: (2*(1-hh)*abs(u-1/2) + hh)*sqrt(1 + (2*(1-hh))**2),0,1)[0] for hh in h])
plt.xlabel("$h$",fontsize=14)
plt.ylabel("surface area/$2\pi$",fontsize=14)
plt.plot([1,1]);
```

A ha. We can beat 1! In fact, we could find the minimum **for this family of functions**.

```python
minimize_scalar(lambda hh: quad(lambda u: (2*(1-hh)*abs(u-1/2) + hh)*sqrt(1 + (2*(1-hh))**2),0,1)[0], bounds=(0,1))
```

         fun: 0.9657112946959683
        nfev: 16
         nit: 12
     success: True
           x: 0.8535533905036866

So, at the "$x$" value (our $h$), we got a surface 96.57% the surface area of the cylinder.

### Try 2 - parabolas

We need a parabola that passes through $(0,1)$ and $(1,1)$ (in the "$Ru$"-plane). The first pick might be to start with a function with roots at $0$ and $1$ and translate it up.
$$R(u) = u(u-1) + 1$$

```python
plot_R(lambda u: u*(u-1) + 1)
```

$$\text{SA} = 2\pi \int_0^1 (u(u-1) + 1)\sqrt{1 + (2u -1)^2}\,du $$

```python
quad(lambda u: (u*(u-1) + 1)*sqrt(1 +(2*u - 1)**2), 0, 1)[0]
```

    0.9658847779003562

1st guess is lower! Let's try the parameter trick.

$$R(u) = 4(1-h)(u-1/2)^2 + h$$

```python
@interact(h=(0,1,.025))
def _(h=0.5):
    plot_R(lambda u: 4*(1-h)*abs(u-1/2)**2 + h)
```

    interactive(children=(FloatSlider(value=0.5, description='h', max=1.0, step=0.025), Output()), _dom_classes=('…

Then we can make the surface area a function of $h$.

$$\text{SA}(h)= 2\pi \int_0^1 (4(1-h)(u-1/2)^2 + h)\sqrt{1 + (8(1-h)(u-1/2)^2}\,du $$

```python
h = np.linspace(0,1,50)
plt.plot(h,[quad(lambda u: (4*(1-hh)*(u-1/2)**2 + hh)*sqrt(1 + (8*(1-hh)*(u-1/2))**2),0,1)[0] for hh in h])
plt.xlabel("$h$",fontsize=14)
plt.ylabel("surface area/$2\pi$",fontsize=14)
plt.plot([1,1]);
```

```python
minimize_scalar(lambda hh: quad(lambda u: (4*(1-hh)*(u-1/2)**2 + hh)*sqrt(1 + (8*(1-hh)*(u-1/2))**2),0,1)[0], bounds=(0,1))
```

         fun: 0.9536338661335718
        nfev: 17
         nit: 13
     success: True
           x: 0.8476399514049215

Better. Down to 95.36%.

### Try 3 - sines

$\sin(\pi u)$ has zeros at 0 and 1. Let's try changing the amplitude and subtracting from 1.

$$R(u) = 1-h \sin(\pi u)$$

```python
@interact(h=(0,1,.025))
def _(h=0.5):
    plot_R(lambda u: 1 - sin(pi*u)*h)
```

    interactive(children=(FloatSlider(value=0.5, description='h', max=1.0, step=0.025), Output()), _dom_classes=('…

Then we can make the surface area a function of $h$.

$$\text{SA}(h)= 2\pi \int_0^1 (1 - h \sin (\pi u))\sqrt{1 + (h\pi \cos (\pi u))^2}\,du $$

```python
h = np.linspace(0,1,50)
plt.plot(h,[quad(lambda u: (1 - hh*sin(pi*u))*sqrt(1 + (hh*pi*cos(pi*u))**2),0,1)[0] for hh in h])
plt.xlabel("$h$",fontsize=14)
plt.ylabel("surface area/$2\pi$",fontsize=14)
plt.plot([1,1]);
```

```python
minimize_scalar(lambda hh: quad(lambda u: (1 - hh*sin(pi*u))*sqrt(1 + (hh*pi*cos(pi*u))**2),0,1)[0], bounds=(0,1))
```

         fun: 0.9544260764319593
        nfev: 17
         nit: 13
     success: True
           x: 0.1558657457189923

Actually, not as good as parabolas.

### Try 4 - hyperbolic trig

Maybe you've heard something about hyperbolic trig functions.
$$\cosh t = \frac{e^{t} + e^{-t}}{2} \qquad\qquad \sinh t = \frac{e^{t} - e^{-t}}{2} $$

They have remarkable properties like $\cosh^2 t - \sinh^2 t = 1$ as well as being each others derivatives.

```python
x = np.linspace(-1,1)
plt.plot(x,cosh(x),label="$\cosh $")
plt.plot(x,np.sinh(x),label="$\sinh $");
plt.legend();
```

Transform cosh to fit our situation. $$R(u) = \frac{\cosh(2u - 1)}{\cosh(1)}$$

```python
plot_R(lambda u: cosh((u - 1/2)*2)/cosh(1))
```

$$\text{SA} = 2\pi \int_0^1 \frac{\cosh(2u - 1)}{\cosh(1)}\sqrt{1 + \left(\frac{2\sinh(2u - 1)}{\cosh(1)}\right)^2}\,du $$

```python
quad(lambda u: cosh(2*u - 1)/cosh(1)*sqrt(1 +(2*np.sinh(2*u - 1)/cosh(1))**2), 0 , 1)[0]
```

    0.9957453541985554

Not too good. Let's try a parameter. We can "squeeze" the function before scaling it down to hit our boundary.

$$R(u) = \frac{\cosh(h(2u - 1))}{\cosh(h)}$$

```python
@interact(h=(0,10,.1))
def _(h=0.5):
    plot_R(lambda u: cosh(h*(2*u - 1))/cosh(h))
```

    interactive(children=(FloatSlider(value=0.5, description='h', max=10.0), Output()), _dom_classes=('widget-inte…

$$\text{SA}(h) = 2\pi \int_0^1 \frac{\cosh(h(2u - 1))}{\cosh(h)}\sqrt{1 + \left(\frac{2h\sinh(h(2u - 1))}{\cosh(h)}\right)^2}\,du $$

```python
h = np.linspace(0,1,50)
plt.plot(h,[quad(lambda u: \
                 (cosh(hh*(2*u - 1))/cosh(hh))\
                 *sqrt(1 + (hh*2*np.sinh(hh*(2*u - 1))/cosh(hh))**2)\
                 ,0,1)[0] for hh in h])
plt.xlabel("$h$",fontsize=14)
plt.ylabel("surface area/$2\pi$",fontsize=14)
plt.plot([1,1]);
```

```python
minimize_scalar(lambda hh: quad(lambda u: \
                                (cosh(hh*(2*u - 1))/cosh(hh))\
                                *sqrt(1 + (hh*2*np.sinh(hh*(2*u - 1))/cosh(hh))**2)\
                                ,0,1)[0], bounds=(0,1))
```

         fun: 0.9536241067020027
        nfev: 14
         nit: 10
     success: True
           x: 0.5893877721855365

A hair under the quadratic functions. As good as I can do...


```python
import numpy as np
from scipy import linalg
from scipy import optimize
import sympy as sm
%matplotlib inline
import matplotlib.pyplot as plt
import ipywidgets as widgets
```

The following Model Project is based on the classic Solow model as we know it from Macroeconomics. First, we will model the Solow model in its simplest form. Next we will build on this simple Solow model by adding Total Factor Productivity (TFP).

1) Our model is defined by the framework below. Note that TFP is not part of the model yet and that we are assuming that we are in a small closed economy:

A small closed economy can be described by following equations:
\\[ Y_t = BK_t^\alpha L_t^{1-\alpha},\alpha\in(0,1) \\]

\\[ S_{t+1} = sY_t, s\in(0,1) \\]

\\[ L_{t+1} = (1+n)L_t, n>-1 \\]

where $Y_t = F(K_t,L_t)$ is GDP; $K_t$ is capital; $L_t$ is labor (growing with a constant rate of $n$); $S_t$ is total savings; s is the savings rate and $k_t = K_t/L_t$. Note also that $B$, alpha, $s$ and $n$ are exogenous parameters.

The transition equation, which shows how capital is accumulated, then becomes

\\[ k_{t+1} = \frac{1}{1+n}(sBk_t^{\alpha}+(1-\delta)k_t), 0<\delta =<1\\]

where, in addition to above defined parameters, $\delta$ is depreciation of capital.

**Steady state** for $k_t$ and $y_t$ is derived below

```python
#Below is run to get LaTex format
sm.init_printing(use_unicode=True)

# Define varibles
k = sm.symbols('k')
y = sm.symbols('y')
K = sm.symbols('K')
B = sm.symbols('B')
L = sm.symbols('L')
Y = sm.symbols('Y')
alpha = sm.symbols('alpha')
delta = sm.symbols('delta')
s = sm.symbols('s')
n = sm.symbols('n')

# Define transition equation and solve steady state for capital per capital
f=B*k**alpha
transition1=sm.Eq(k,((1)/(1+n)*(s*B*(k**alpha)+(1-delta)*k)))
steadystate_k1=sm.solve(transition1,k)
print('Steady state of k is')
steadystate_k1

```

```python
y=B*k**alpha
print('Steady state for y is')
steadystate_y1=y.subs({'k':steadystate_k1[0]})
steadystate_y1
```

Above we have found the expression for steady state in capital per capita and income per capita. We test our steady state expressions by plugging in arbitrary values for each parameter in each expression: $B=1.5$, $s=0.20$, $n=2\%$, $\alpha=\frac{1}{3}$ and $\delta=1$.  We use a simple function: solution(x), to retrieve a specific steady state value for each. See below.

We notice that this value seems plausible and proceed by solving for optimal steady states using more sophisticated functions. Again we start by choosing arbitrary parameter values.

```python
s=0.2
n=0.02
delta=1
alpha=1/3
B=10

opt_steadystate_k1= lambda steadystate_k1: steadystate_k1 - ( ((1)/(1+n)*(s*B*(steadystate_k1)**alpha+(1-delta)*steadystate_k1)) )
result1 = optimize.root_scalar(opt_steadystate_k1,bracket=[0.1,10],method='brentq')

print('the steady state for k is', result1.root)

```

    the steady state for k is 2.74564722358436

Given the specified parameter values, steady state of capital per capita is 2.75.

We now wish to investigate how the steady state value of capital per capita changes when the savings rate changes by an arbitrary amount.

```python
n=0.02
delta=1
alpha=1/3
B=10

savings = [0.05,0.1,0.2,0.25,0.4,0.5,0.75,0.9]
for s1 in savings:
    obj_kss = lambda steadystate_k1: steadystate_k1 - ((1)/(1+n)*(s1*B*(steadystate_k1)**alpha+(1-delta)*steadystate_k1))
    result1 = optimize.root_scalar(obj_kss,bracket=[0.1,1000],method='brentq')
    print(f'for savings = {s1:.3f} the steady state for k is',result1.root)

```

    for savings = 0.050 the steady state for k is 0.343205902947649
    for savings = 0.100 the steady state for k is 0.9707328852712966
    for savings = 0.200 the steady state for k is 2.7456472235841
    for savings = 0.250 the steady state for k is 3.8371586463550824
    for savings = 0.400 the steady state for k is 7.765863082169994
    for savings = 0.500 the steady state for k is 10.853123597305084
    for savings = 0.750 the steady state for k is 19.938461196567655
    for savings = 0.900 the steady state for k is 26.209787902323733

From above result it is instantly clear that the savings rate, s, has a substantial impact on the capital accumulation in the society. Below is a visualisation of how the parameteres impact the accumulation of capital as well as income.

In order to create this visualisation, our next step is to create an interactive graph plotting the transition curve and the 45 degree line including sliders for each of the parameters in the model. The purpose of this is to see the effect on the steady state point from changing one of more parameters. In order to do so, we first define the range of each slider. Then we set up the interactive figure and finally we set up the appropriate sliders.

```python
# We define below the possible numeric intervals of the parameters
alpha = np.arange(0,1)
s = np.arange(0,1)
k = np.arange(100)
B = np.arange(0,100)
n = np.arange(0,0.2)
delta = np.arange(0,1.1)

def interactive_transition1(B,s,alpha,n,delta,k):

    k0 = ((1)/(1+n)*(s*B*(k**alpha)+(1-delta)*k))
    m0 = k
    #plt.plot(m)

    fig = plt.figure(dpi=90)
    ax = fig.add_subplot(1,1,1)
    ax.plot(k0, label = 'Capital')
    ax.plot(m0, label = '45 degrees line')
    ax.set_xlim([0,15]) # fixed x range
    ax.set_ylim([0,15]) # fixed y range
    plt.xlabel('k in period t')
    plt.ylabel('k in period t+1')
    plt.title('Transition diagram')
    ax.grid(True)
    ax.legend(loc='upper left')

    y0 = B*((1)/(1+n)*(s*B*(k**alpha)+(1-delta)*k))**alpha

    fig = plt.figure(dpi=90)
    ax = fig.add_subplot(1,1,1)
    ax.plot(y0, label = 'Income per capita')
    ax.set_xlim([0,15]) # fixed x range
    ax.set_ylim([0,100]) # fixed y range
    plt.xlabel('k')
    plt.ylabel('y')
    plt.title('Income per capita')
    ax.grid(True)
    ax.legend(loc='upper left')

widgets.interact(interactive_transition1,
    #k1=widgets.fixed(k1),
    alpha=widgets.FloatSlider(description="$alpha$", min=0, max=1, step=0.005, value=0.33),
    s=widgets.FloatSlider(description="$s$", min=0, max=0.7, step=0.005, value=0.2),
    B=widgets.FloatSlider(description="$B$", min=0, max=50, step=1, value=10),
    n=widgets.FloatSlider(description="$n$", min=0, max=0.2, step=0.01, value=0.02),
    delta=widgets.FloatSlider(description="$delta$", min=0, max=1, step=0.01, value=0.4),
    k=widgets.fixed(k)
);
```

    interactive(children=(FloatSlider(value=10.0, description='$B$', max=50.0, step=1.0), FloatSlider(value=0.2, d…

In the figure we notice that as B, s and alpha increases, so does steady state for capital accumulation. Conversely, when n and delta increase, steady state for capital accumulation decreases.

**2) We now consider the Solow-model with a productive externality per worker where:**

A small closed economy can be described by following equations:
\\[ Y_t = A_tK_t^\alpha L_t^{1-\alpha},\alpha\in(0,1) \\]

\\[ A_t = Bk_t^{\phi(1-\alpha)}, B>0,\phi\in(0,1) \\]

\\[ K_{t+1} = sY_t, s\in(0,1) \\]

\\[ L_{t+1} = (1+n)L_t, n>-1 \\]

where $K_t$ is capital; $L_t$ is labor (growing with a constant rate of $n$); $A_t$ is total factor productivity; $Y_t = F(K_t,A_t,L_t)$ is GDP; $k_t = K_t/L_t$ and s is the savings rate.

Note that TFP is dependent on capital accumulation and therefore an increase in capital effects income accumulation via 2 channels: (i) directly through an increase in k and (ii) indirectly through an increase in productivity. Hence all increases in capital will have a larger effect on income accumulation in this model compared to the simple Solow model in part 1.

The transition equation then becomes

\\[ k_{t+1} = (\frac{sB}{1+n})k_t^{\alpha+\phi*(1-\alpha)}\\]

**Steady state** for $k_t$ and $y_t$ is derived below

```python
#Below is run to get LaTex format
sm.init_printing(use_unicode=True)
```

```python
# Define additional variables
# Define varibles
k = sm.symbols('k')
y = sm.symbols('y')
K = sm.symbols('K')
B = sm.symbols('B')
L = sm.symbols('L')
Y = sm.symbols('Y')
alpha = sm.symbols('alpha')
delta = sm.symbols('delta')
s = sm.symbols('s')
n = sm.symbols('n')
A = sm.symbols('A')
phi = sm.symbols('phi')

# Define income equation and transition equation and solve steady state for capital per capita
y=B*k**(alpha + phi*(1-alpha))
transition=sm.Eq(k,((s*B)/(1+n)*k**(alpha+phi*(1-alpha))))
steadystate_k=sm.solve(transition,k)
print('Steady state of k is')
steadystate_k

```

```python
print('Steady state for y is')
steadystate_y=y.subs({'k':steadystate_k[0]})
steadystate_y
```

Above we have found the expression for steady state in capital per capita and income per capits. We test our steady state expressions by plugging in arbitrary values for each parameter in each expression: $B=10$, $s=0.20$, $n=2\%$, $\alpha=\frac{1}{3}$, $\phi=0.56$.  We use a simple function: solution(x), to retrieve a specific steady state value for each. See below.

We notice that this value seems plausible and proceed by solving for optimal steady states using more sophisticated functions. Again we start by choosing arbitrary parameter values.

```python
print('Inserting the values gives the steadystate value of:')
Solution=sm.lambdify((B,s,n,alpha,phi),steadystate_k)
Solution(10, 0.2, 0.02, 1/3, 0.4)
```

```python
s=0.2
n=0.02
phi=0.4
alpha=1/3
B=10

f = lambda k: A*k**alpha
opt_steadystate_k= lambda steadystate_k: steadystate_k - ( ((s*B)/(1+n))*steadystate_k**(alpha+phi*(1-alpha)) )
result = optimize.root_scalar(opt_steadystate_k,bracket=[0.1,10],method='brentq')

print('the steady state for k is', result.root)

```

    the steady state for k is 5.383622007028104

Given the specified parameter values, steady state of capital per capita is 5.38.

We now wish to investigate how the steady state value of capital per capita changes when the savings rate changes by an arbitrary amount.

```python
n=0.02
phi=0.4
alpha=1/3
B=10
savings_rate = [0.05,0.1,0.2,0.25,0.4,0.5,0.75,0.9]
for s1 in savings_rate:
    f = lambda k: A*k**alpha
    opt_steadystate_k= lambda steadystate_k: steadystate_k - ( ((s1*B)/(1+n))*steadystate_k**(alpha+phi*(1-alpha)) )
    result = optimize.root_scalar(opt_steadystate_k,bracket=[0.1,1000],method='brentq')
    print(f'for s = {s1:.3f} the steady state for k is',result.root)

```

    for s = 0.050 the steady state for k is 0.16823818771955754
    for s = 0.100 the steady state for k is 0.951698907128675
    for s = 0.200 the steady state for k is 5.383622007028105
    for s = 0.250 the steady state for k is 9.40480060381148
    for s = 0.400 the steady state for k is 30.4543650281175
    for s = 0.500 the steady state for k is 53.20158626129952
    for s = 0.750 the steady state for k is 146.60633232770348
    for s = 0.900 the steady state for k is 231.2628344322685

Similar to part 1, it is instantly clear that the savings rate, s, has a substantial impact on the capital accumulation in the society. Below is a visualisation of how the parameteres impact the accumulation of capital as well as income.

Our next step is to create an interactive graph plotting the transition curve and the 45 degree line including sliders for each of the parameters in the model. The purpose of this is to see the effect on the steady state point from changing one of more parameters. In order to do so, we first define the range of each slider. Then we set up the interactive figure and finally we set up the appropriate sliders.

```python
alpha = np.arange(0,1)
s = np.arange(0,1)
k = np.arange(100)
B = np.arange(0,100)
n = np.arange(0,0.2)
phi = np.arange(0,1)
y = np.arange(100)

def interactive_transition(B,s,alpha,n,phi,k):

    k1 = ( ((s*B)/(1+n))*k**(alpha+phi*(1-alpha)) )
    m = k
    #plt.plot(m)

    fig = plt.figure(dpi=90)
    ax = fig.add_subplot(1,1,1)
    ax.plot(k1, label = 'Capital')
    ax.plot(m, label = '45 degrees line')
    ax.set_xlim([0,15]) # fixed x range
    ax.set_ylim([0,15]) # fixed y range
    plt.xlabel('k in period t')
    plt.ylabel('k in period t+1')
    plt.title('Transition diagram')
    ax.grid(True)
    ax.legend(loc='upper left')

    y1=B*( ((s*B)/(1+n))*k**(alpha+phi*(1-alpha)) )**(alpha + phi*(1-alpha))

    fig = plt.figure(dpi=90)
    ax = fig.add_subplot(1,1,1)
    ax.plot(y1, label='Income per capita')
    ax.set_xlim([0,15]) # fixed x range
    ax.set_ylim([0,100]) # fixed y range
    plt.xlabel('k ')
    plt.ylabel('y ')
    plt.title('Income per capita')
    ax.grid(True)
    ax.legend(loc='upper left')

widgets.interact(interactive_transition,
    #k1=widgets.fixed(k1),
    alpha=widgets.FloatSlider(description="$alpha$", min=0, max=1, step=0.005, value=0.33),
    s=widgets.FloatSlider(description="$s$", min=0, max=0.7, step=0.005, value=0.2),
    B=widgets.FloatSlider(description="$B$", min=0, max=50, step=1, value=10),
    n=widgets.FloatSlider(description="$n$", min=0, max=0.2, step=0.01, value=0.02),
    phi=widgets.FloatSlider(description="$phi$", min=0, max=1, step=0.01, value=0.56),
    k=widgets.fixed(k)
);
```

    interactive(children=(FloatSlider(value=10.0, description='$B$', max=50.0, step=1.0), FloatSlider(value=0.2, d…

Similar to part 1, we notice that as B, s, alpha and phi increase, so does steady state for capital accumulation. Conversely, when n increases, steady state for capital accumulation decreases. Importantly we notice that when total factor productivity is dependent on k, all changes in either k or the productivity factor, B, they will have a significantly higher effect on capital accumulation and thereby income.

We can therefore conclude that an economy with a productive externality per capita will increase capital accumulation.

```python

```


| |Pierre Proulx, ing, professeur|
|:---|:---|
|Département de génie chimique et de génie biotechnologique |** GCH200-Phénomènes d'échanges I **|

### Section10.2, chauffage d'un conducteur électrique

>  Le développement fait dans Transport Phenomena sera répété ici en développant les solutions avec le calculateur formel sympy et en traçant la solution avec sympy.plot. Les développements sont très similaires à la section 2.3, Les coordonnées cylindriques sont utilisées, évidemment. Remarquez bien comment la courbure de la géométrie a un effet sur le bilan.

```python
#
# Pierre Proulx
#
# Préparation de l'affichage et des outils de calcul symbolique
#
import sympy as sp
from IPython.display import *
sp.init_printing(use_latex=True)
%matplotlib inline
```

```python
# Paramètres, variables et fonctions
#
r,delta_r,L,k,S_c,R=sp.symbols('r,delta_r,L,k,S_c,R')
C1=sp.symbols('C1')
qr=sp.symbols('q_r')
T=sp.Function('T')(r)
```

```python
#
# Équation du bilan d'énergie
#
eq=2*sp.pi*r*L*qr(r)-2*sp.pi*(r+delta_r)*L*qr(r+delta_r)    # conduction
eq=eq+2*sp.pi*r*delta_r*L*S_c                                #source
#
# pose de la limite quand dr tends vers 0
#
eq=eq/(2*sp.pi*L*delta_r)
display(eq)
```

```python
eq1=sp.limit(eq,delta_r,0).doit()
display(eq1)
```

```python
eq2=sp.dsolve(eq1)
display(eq2)
```

```python
# Loi de Fourier.
Fourier=-k*sp.Derivative(T,r)
eq2=eq2.subs(qr(r),Fourier)
display(eq2)
```

```python
eq2=sp.dsolve(eq2)
display('Le profil de température général, avant de poser les conditions aux limites',eq2)
eq2=eq2.rhs #
```

```python
T_0=sp.symbols('T_0')
condition_1=sp.Eq(eq2.diff(r).subs(r,0),0)     # flux nul en r=0
condition_2=sp.Eq(eq2.subs(r,R),T_0)           # température connue en r=R
display('Les conditions aux limites, en r=0 flux nul et en r=R T=T_0',condition_1,condition_2)
constante=sp.solve([condition_1,condition_2],sp.symbols('C1,C2'),dict=True)

display('Les constantes',constante[0])  # les constantes apparaissent dans une liste de 1 élément, dans un dictionnaire
constante=constante[0] #
```

```python
T=eq2.subs(constante)
display(T)
```

```python
I,k_e=sp.symbols('I k_e')
source=I**2/k_e
T=T.subs(S_c,source)
display(T.simplify())
```

> #### Exemple 10.2-2
>> La différence est maintenant que la condition à la paroi est la loi de refroidissement de Newton au lieu de température fixée à T_0 , c'est l'équation 10.2-22:

>><center>
en r=R, $-k\frac {dT}{dr}=h(T-T_{air})$
</center>

>> On peut reprendre la solution au moment ou on a posé cette contition

```python
# constante=sp.solve(sp.Eq(eq2.rhs.subs(r,R),T_0),'C1',dict=True)   # T=T_0 à la paroi
#
# remplacé par
#
eqx=eq2
display(eq2)
T_air,h=sp.symbols('T_air h')
condition_1=sp.Eq(-k*eqx.diff(r).subs(r,R)-h*(eqx.subs(r,R)-T_air),0)
condition_2=sp.Eq(eq2.diff(r).subs(r,0),0)
display('Les conditions aux limites, en r=R flux de Fourier=flux de Newton et flux = 0 en r=0',
        condition_1,condition_2)
constanten=sp.solve([condition_1,condition_2],sp.symbols('C1,C2'),dict=True)
constanten=constanten[0]            # sort de la liste
display('les constantes avec une condition de coefficient de transfert en r=R',constanten)
T=eq2.subs(constanten)
display(T)
```


# <center> ЛАБОРАТОРНАЯ  РАБОТА 6 </center>
# <center> ПРИБЛИЖЕНИИ ФУНКЦИЙ  </center>

Теоретический материал к данной теме содержится в [1, глава 11].

```python
from sympy.solvers import solve
from sympy import exp, sin, cos, sqrt, log, ln, pi
from sympy import Rational as syR
from sympy import Symbol, diff
from scipy.misc import derivative

import numpy as np

import matplotlib.pyplot as plt
%matplotlib inline
```

## Задача 1.
Функция  $y=f(x)$ задана таблицей  значений $y_0,y_1,...,y_n$  в точках $x_0,x_1,...,x_n$. Используя метод наименьших квадратов (МНК), найти многочлен $P_m(x)=a_0+a_1x+...+a_mx^m$ наилучшего  среднеквадратичного  приближения оптимальной степени $m=m^*$. За оптимальное значение $m^*$ принять ту степень многочлена, начиная с которой величина $\sigma_m=\sqrt{\frac{1}{n-m}\sum_{k=0}^n (P_m(x_k)-y_k)^2}$ стабилизируется или начинает возрастать.
## Порядок решения задачи:
1. Задать векторы x и y исходных данных.
2. Используя функцию mnk, найти многочлены $Pm,$ $ m=0,1,2,..$, по методу наименьших квадратов. Вычислить соответствующие им значения  $\sigma_m$.
3. Построить   гистограмму зависимости $\sigma_m$  от $m$, на основании которой выбрать оптимальную степень m* многочлена наилучшего  среднеквадратичного  приближения.
4. На одном чертеже построить графики многочленов $Pm,$ $ m=0,1,2,..., m^*$, и точечный график исходной функции.

Ваша вектора x и y:
$x = {Task1[TASK_VARIANT][x_latex]}$
$y = {Task1[TASK_VARIANT][y_latex]}$

```python
# Вектора
x = {Task1[TASK_VARIANT][x_latex]}
y = {Task1[TASK_VARIANT][y_latex]}
```

Реализуйте функцию mnk для нахождения многочленов Pm методом наименших квадратов. Вычислите значения $\sigma_m$

```python
##sigma = []
##for m in range(9):
 ##   XX = X(m)
  ##  A = np.linalg.inv(np.dot(XX.T,XX)).dot(XX.T).dot(y)
 ##   sigma.append(np.sqrt(1/(x.size - m)* np.linalg.norm(XX.dot(A) - y,np.inf)))
```

Постройте гистограмму зависимости $\sigma_m$ от $m$. Выберите оптимальную степень $m^*$.

```python
##график
```

Постройте график многочлена и точечный график исходной функции на одном графике.

```python
##график двух функций
```

## Задача 6.2.
В  таблице приведены  результаты наблюдений за перемещением  x материальной точки  по оси  Ох  в моменты   времени  $t\in[t_0 ,T]$.  Известно, что движение  является равномерным и описывается линейной зависимостью $x(t)=vt+b$.  Используя  метод наименьших квадратов,   определить скорость $v$  и  спрогнозировать положение точки в момент времени  $t=2T$. На одном чертеже  построить график движения точки  и точечный график исходных наблюдений.

Ваши вектора t и x:
$t = {Task1[TASK_VARIANT][t_latex]}$
$x2 = {Task1[TASK_VARIANT][x2_latex]}$

```python
#вектора
t = {Task1[TASK_VARIANT][t_latex]}
x2 = {Task1[TASK_VARIANT][x2_latex]}
```

Функция имеет вид
$$
x(t) = \nu t + b
$$
Тогда вектор коэфицентов можно найти как
$$
w = (t^T t)^{-1} t^T x
$$
Найдите вектор коэффициентов.

```python
#код
```

Построте график движения точки  и точечный график исходных наблюдений на одном графике.

```python
#график
```

Определите скорость $v$  и  спрогнозирйте положение точки в момент времени  $t=2T$.

```python
#два числа-ответ
```

## Задача 6.3.
Зависимость между величинами x и  y описывается функцией   $y=f(x, a, b)$, где a и  b – неизвестные параметры. Найти  эти параметры, сведя исходную задачу к линейной задаче метода наименьших квадратов.

### УКАЗАНИЕ.
Свести исходную задачу к  линейной задаче МНК  можно, сделав подходящую замену переменных. Например, если исходная зависимость имеет вид $y=e^{a+bx^2}$, то  прологарифмировав исходное равенство и введя новые переменные $s=\ln y$ и $t=x^2$ , получаем задачу об определении коэффициентов линейной зависимости $s= a+b t$.

Ваши вектора x и y:
$x3 = {Task1[TASK_VARIANT][x3_latex]}$
$y3 = {Task1[TASK_VARIANT][y3_latex]}$

Зависимость ваших переменных:
$f3 = {Task1[TASK_VARIANT][f3_latex]}$

```python
#вектора x и y
x3 = {Task1[TASK_VARIANT][x3_latex]}
y3 = {Task1[TASK_VARIANT][y3_latex]}

#Зависимость
f3 = {Task1[TASK_VARIANT][f3_latex]}
```

Сведите исходную задачу к линейной МНК.

```python
#математическое решение
```

Построте график аналогично второй задаче.

```python
##график
```

```python

```

#### ЛИТЕРАТУРА:
1. Амосов А.А., Дубинский Ю.А., Копченова Н.В. Вычислительные методы для инженеров. М.: Высшая школа, 1994.

```python

```


```python
import pyprob
from pyprob import Model
from pyprob import InferenceEngine
from pyprob.distributions import Normal
from pyprob.dis import ModelDIS

import torch
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure();
```

    <Figure size 432x288 with 0 Axes>

# Defining the Model

```python
class GaussianUnknownMean(Model):
    def __init__(self):
        super().__init__(name='Gaussian with unknown mean') # give the model a name
        self.prior_mean = 1
        self.prior_std = math.sqrt(5)
        self.likelihood_std = math.sqrt(2)

    def forward(self): # Needed to specifcy how the generative model is run forward
        # sample the (latent) mean variable to be inferred:
        mu = pyprob.sample(Normal(self.prior_mean, self.prior_std)) # NOTE: sample -> denotes latent variables

        # define the likelihood
        likelihood = Normal(mu, self.likelihood_std)

        # Lets add two observed variables
        # -> the 'name' argument is used later to assignment values:
        #pyprob.observe(likelihood, name='obs0') # NOTE: observe -> denotes observable variables

        # return the latent quantity of interest
        return mu

class GaussianDIS(GaussianUnknownMean, ModelDIS):
    def __init__(self):
        super().__init__()
        self.obs = 8
        #self.bool_func = bool_func
    def dummy_bernoulli(self, x):
        return True
    def forward(self):
        x = super().forward()
        likelihood = Normal(x, self.likelihood_std)
        sample_obs = pyprob.sample(likelihood)
        euclidean_dist = np.abs(sample_obs - self.obs)
        delta_val = self.dummy_bernoulli(x)
        bool_func_dist = pyprob.distributions.Bernoulli(delta_val)
        pyprob.observe(bool_func_dist, name = "bool_func")
        dummy = pyprob.distributions.Normal(euclidean_dist,0.1)
        pyprob.observe(dummy, euclidean_dist, name = "distance") # Any other way to do this?
        return x

model = GaussianUnknownMean()
simulator = GaussianDIS()
```

# Finding the correct posterior analytically
Since all distributions are gaussians in this model, we can analytically compute the posterior and we can compare the true posterior to the inferenced one.

Assuming that the prior and likelihood are $p(x) = \mathcal{N}(\mu_0, \sigma_0)$ and $p(y|x) = \mathcal{N}(x, \sigma)$ respectively and, $y_1, y_2, \ldots y_n$ are the observed values, the posterior would be $p(x|y) = \mathcal{N}(\mu_p, \sigma_p)$ where,
$$
\begin{align}
\sigma_{p}^{2} & = \frac{1}{\frac{n}{\sigma^2} + \frac{1}{\sigma_{0}^{2}}} \\
\mu_p & = \sigma_{p}^{2} \left( \frac{\mu_0}{\sigma_{0}^{2}} + \frac{n\overline{y}}{\sigma^2} \right)
\end{align}
$$
The following class implements computing this posterior distribution. We also implement some helper functions and variables for plotting the correct posterior and prior.

```python
def plot_function(min_val, max_val, func, *args, **kwargs):
        x = np.linspace(min_val,max_val,int((max_val-min_val)*50))
        plt.plot(x, np.vectorize(func)(x), *args, **kwargs)

def get_dist_pdf(dist):
    return lambda x: math.exp(dist.log_prob(x))

class CorrectDistributions:
    def __init__(self, model):
        self.prior_mean = model.prior_mean
        self.prior_std = model.prior_std
        self.likelihood_std = model.likelihood_std
        self.prior_dist = Normal(self.prior_mean, self.prior_std)

    @property
    def observed_list(self):
        return self.__observed_list

    @observed_list.setter
    def observed_list(self, new_observed_list):
        self.__observed_list = new_observed_list
        self.construct_correct_posterior()

    def construct_correct_posterior(self):
        n = len(self.observed_list)
        posterior_var = 1/(n/self.likelihood_std**2 + 1/self.prior_std**2)
        posterior_mu = posterior_var * (self.prior_mean/self.prior_std**2 + n*np.mean(self.observed_list)/self.likelihood_std**2)
        self.posterior_dist = Normal(posterior_mu, math.sqrt(posterior_var))

    def prior_pdf(self, model, x):
        p = Normal(model.prior_mean,model.prior_stdd)
        return math.exp(p.log_prob(x))

    def plot_posterior(self, min_val, max_val):
        if not hasattr(self, 'posterior_dist'):
            raise AttributeError('observed values are not set yet, and posterior is not defined.')
        plot_function(min_val, max_val, get_dist_pdf(self.posterior_dist), label='correct posterior', color='orange')

    def plot_prior(self, min_val, max_val):
        plot_function(min_val, max_val, get_dist_pdf(self.prior_dist), label='prior', color='green')

```

```python
correct_dists = CorrectDistributions(model)
correct_dists.observed_list=[8]
```

# Training

For a sanity check, start with one training round.

```python
simulator.train(iterations = 1,
    importance_sample_size=5000,
    proposal_mixture_components=5,
    observe_embeddings={'bool_func': {'dim': 1, 'depth': 1}} # Dummy value as we currently have to observe something
)
```

    Creating new inference network...
    Observable bool_func: reshape not specified, using shape torch.Size([]).
    Observable bool_func: using embedding dim torch.Size([1]).
    Observable bool_func: observe embedding not specified, using the default FEEDFORWARD.
    Observable bool_func: using embedding depth 1.
    Observe embedding dimension: 1
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    New layers, address: 16__forward__mu__Normal__1, distribution: Normal
    New layers, address: 28__forward__sample_obs__Normal__1, distribution: Normal
    Total addresses: 2, parameters: 308
    0d:00:00:00 | 1    | 300       | +4.03e+00 | +3.95e+00 | +4.16e+00 | 0d:00:00:00 | +1.00e-03 | 1,006.0
    Stop condition reached. num_traces: 500
    0d:00:00:00 | 1    | 500       | +4.03e+00 | +3.95e+00 | +4.10e+00 | 0d:00:00:00 | +1.00e-03 | 1,163.2
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:20 | 0d:00:00:00 | #################### | 5000/5000 | 4807.00 | 240.37
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:00 | 0d:00:00:00 | #################### | 500/500 | 1,032.81
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:00 | 1    | 900       | +4.03e+00 | +3.95e+00 | +4.99e+00 | 0d:00:00:00 | +1.00e-03 | 770.5
    Stop condition reached. num_traces: 500
    0d:00:00:01 | 1    | 1,000     | +4.03e+00 | +3.95e+00 | +5.14e+00 | 0d:00:00:00 | +1.00e-03 | 852.5
    Training iterations 1  epsilon 1.92  ESS 500.0

```python
prior = simulator.prior_results(num_traces = 2000)
posterior_check = simulator.posterior(num_traces = 2000,
inference_engine = InferenceEngine.IMPORTANCE_SAMPLING_WITH_INFERENCE_NETWORK,
observe={"bool_func": 1})
posterior_check = simulator.update_DIS_posterior_weights(posterior_check)
results_check = np.array([x.result for x in posterior_check.values])
```

    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:03 | 0d:00:00:00 | #################### | 2000/2000 | 2000.00 | 643.56
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:08 | 0d:00:00:00 | #################### | 2000/2000 | 1920.54 | 230.02

Now do 50 more iterations.

```python
simulator.train(iterations = 50,
    importance_sample_size=5000,
    proposal_mixture_components=5,
    observe_embeddings={'bool_func': {'dim': 1, 'depth': 1}} # Dummy value as we currently have to observe something
)
```

    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:18 | 0d:00:00:00 | #################### | 5000/5000 | 4801.09 | 271.60
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:00 | 0d:00:00:00 | #################### | 500/500 | 1,204.59
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:01 | 1    | 1,300     | +4.03e+00 | +3.95e+00 | +4.96e+00 | 0d:00:00:00 | +1.00e-03 | 439.9
    Stop condition reached. num_traces: 500
    0d:00:00:01 | 1    | 1,500     | +4.03e+00 | +3.95e+00 | +5.01e+00 | 0d:00:00:00 | +1.00e-03 | 989.9
    Training iterations 1  epsilon 1.30  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:20 | 0d:00:00:00 | #################### | 5000/5000 | 4817.46 | 249.91
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:00 | 0d:00:00:00 | #################### | 500/500 | 829.38
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:02 | 1    | 1,800     | +4.03e+00 | +3.95e+00 | +4.99e+00 | 0d:00:00:00 | +1.00e-03 | 693.1
    Stop condition reached. num_traces: 500
    0d:00:00:02 | 1    | 2,000     | +4.03e+00 | +3.95e+00 | +5.02e+00 | 0d:00:00:00 | +1.00e-03 | 351.1
    Training iterations 2  epsilon 1.05  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:57 | 0d:00:00:00 | #################### | 5000/5000 | 4819.09 | 87.53
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:02 | 0d:00:00:00 | #################### | 500/500 | 234.92
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:04 | 1    | 2,400     | +4.03e+00 | +3.95e+00 | +4.99e+00 | 0d:00:00:02 | +1.00e-03 | 171.6
    Stop condition reached. num_traces: 500
    0d:00:00:05 | 1    | 2,500     | +4.03e+00 | +3.95e+00 | +5.05e+00 | 0d:00:00:02 | +1.00e-03 | 173.9
    Training iterations 3  epsilon 0.90  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:01:19 | 0d:00:00:00 | #################### | 5000/5000 | 4805.33 | 62.69
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:02 | 0d:00:00:00 | #################### | 500/500 | 209.79
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:06 | 1    | 2,900     | +4.03e+00 | +3.95e+00 | +4.99e+00 | 0d:00:00:01 | +1.00e-03 | 351.6
    Stop condition reached. num_traces: 500
    0d:00:00:06 | 1    | 3,000     | +4.03e+00 | +3.95e+00 | +4.83e+00 | 0d:00:00:01 | +1.00e-03 | 481.1
    Training iterations 4  epsilon 0.79  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:55 | 0d:00:00:00 | #################### | 5000/5000 | 4784.43 | 90.89
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:01 | 0d:00:00:00 | #################### | 500/500 | 499.84
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:07 | 1    | 3,300     | +4.03e+00 | +3.95e+00 | +4.96e+00 | 0d:00:00:00 | +1.00e-03 | 325.8
    Stop condition reached. num_traces: 500
    0d:00:00:08 | 1    | 3,500     | +4.03e+00 | +3.95e+00 | +4.80e+00 | 0d:00:00:01 | +1.00e-03 | 178.2
    Training iterations 5  epsilon 0.71  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:01:11 | 0d:00:00:00 | #################### | 5000/5000 | 4758.18 | 69.80
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:01 | 0d:00:00:00 | #################### | 500/500 | 263.98
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:09 | 1    | 3,900     | +4.03e+00 | +3.95e+00 | +4.77e+00 | 0d:00:00:01 | +1.00e-03 | 238.9
    Stop condition reached. num_traces: 500
    0d:00:00:10 | 1    | 4,000     | +4.03e+00 | +3.95e+00 | +4.90e+00 | 0d:00:00:01 | +1.00e-03 | 250.5
    Training iterations 6  epsilon 0.65  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:01:08 | 0d:00:00:00 | #################### | 5000/5000 | 4746.82 | 73.42
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:01 | 0d:00:00:00 | #################### | 500/500 | 415.50
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:11 | 1    | 4,400     | +4.03e+00 | +3.95e+00 | +4.87e+00 | 0d:00:00:01 | +1.00e-03 | 353.2
    Stop condition reached. num_traces: 500
    0d:00:00:12 | 1    | 4,500     | +4.03e+00 | +3.95e+00 | +4.82e+00 | 0d:00:00:01 | +1.00e-03 | 358.4
    Training iterations 7  epsilon 0.60  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:59 | 0d:00:00:00 | #################### | 5000/5000 | 4705.87 | 84.16
    OfflineDataset at: .
    Num. traces      : 500
    Sorted on disk   : False
    No pre-computed hashes found, generating: ./pyprob_hashes
    Hashing offline dataset for sorting
    Time spent  | Time remain.| Progress             | Traces  | Traces/sec
    0d:00:00:01 | 0d:00:00:00 | #################### | 500/500 | 351.63
    Sorting offline dataset
    Sorting done
    Num. trace types : 1
    Trace hash	Count
    2.94810057	500

    Continuing to train existing inference network...
    Total number of parameters: 308
    Train. time | Epoch| Trace     | Init. loss| Min. loss | Curr. loss| T.since min | Learn.rate| Traces/sec
    0d:00:00:13 | 1    | 4,900     | +4.03e+00 | +3.95e+00 | +4.70e+00 | 0d:00:00:01 | +1.00e-03 | 348.4
    Stop condition reached. num_traces: 500
    0d:00:00:13 | 1    | 5,000     | +4.03e+00 | +3.95e+00 | +4.86e+00 | 0d:00:00:01 | +1.00e-03 | 378.2
    Training iterations 8  epsilon 0.55  ESS 500.0
    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:36 | 0d:00:00:36 | ##########---------- | 2521/5000 | 2344.68 | 68.41

```python
posterior = simulator.posterior(num_traces = 2000,
inference_engine = InferenceEngine.IMPORTANCE_SAMPLING_WITH_INFERENCE_NETWORK,
observe={"bool_func": 1})
posterior = simulator.update_DIS_posterior_weights(posterior)
results = np.array([x.result for x in posterior.values])
```

    Time spent  | Time remain.| Progress             | Trace     | ESS    | Traces/sec
    0d:00:00:06 | 0d:00:00:00 | #################### | 2000/2000 |  99.34 | 296.71

# Plots

First do checks for single training round. This is to make sure that the algorithm isn't somehow 'cheating'

```python
posterior_check = simulator.update_DIS_posterior_weights(posterior_check)
results_check = np.array([x.result for x in posterior_check.values])
```

```python
plt.hist(results_check, bins=50, weights=np.array(posterior_check.weights), label='posterior',density=True)
plt.hist(results_check, bins=50, label='proposal',density = True)
correct_dists.plot_prior(min(prior.values_numpy()),max(prior.values_numpy()))
correct_dists.plot_posterior(min(results), max(results))
plt.legend()
```

Now check full results

```python
plt.hist(results, bins=50, weights=np.array(posterior.weights), label='posterior',density=True)
plt.hist(results, bins=50, label='proposal',density = True)
correct_dists.plot_prior(min(prior.values_numpy()),max(prior.values_numpy()))
correct_dists.plot_posterior(min(results), max(results))
plt.legend()
```

# Roughs

```python
importance_weights = np.exp(np.array([x.log_importance_weight for x in posterior.values]))
importance_weights = importance_weights/sum(importance_weights)
weights = posterior.weights
probs = posterior._categorical.probs
print(importance_weights[:10])
print(np.array(weights[:10].detach()))
print(np.array(probs[:10].detach()))
```

    [4.43934743e-04 1.12809762e-03 6.99781531e-05 3.08431125e-04
     2.07236835e-04 1.65965433e-04 2.79483327e-04 1.51536002e-03
     4.46125723e-04 3.05139416e-04]
    [5.16142642e-11 2.43493937e-16 3.47589046e-11 2.37514305e-08
     7.04764763e-05 8.20254264e-05 1.54813143e-05 2.09902897e-24
     4.43681561e-06 7.53436269e-09]
    [5.16142642e-11 2.43493937e-16 3.47589046e-11 2.37514305e-08
     7.04764763e-05 8.20254264e-05 1.54813143e-05 2.09902897e-24
     4.43681561e-06 7.53436269e-09]

```python
print(importance_weights.sum())
print(weights.sum())
```

    1.0000000000000004
    tensor(0.0158, dtype=torch.float64)

```python
plt.hist(results, bins=20, weights=importance_weights, label='posterior')
plt.hist(results, bins=20, label='proposal',density = True)
correct_dists.plot_prior(min(prior.values_numpy()),max(prior.values_numpy()))
correct_dists.plot_posterior(min(results), max(results))
plt.legend()
```

Why do importance weights bring the posterior back to the prior?


# The Efficient Frontier of Optimal Portfolio Transactions

### Introduction

[Almgren and Chriss](https://cims.nyu.edu/~almgren/papers/optliq.pdf) showed that for each value of risk aversion there is a unique optimal execution strategy. The optimal strategy is obtained by minimizing the **Utility Function** $U(x)$:

\begin{equation}
U(x) = E(x) + \lambda V(x)
\end{equation}

where $E(x)$ is the **Expected Shortfall**, $V(x)$ is the **Variance of the Shortfall**, and $\lambda$ corresponds to the trader’s risk aversion. The expected shortfall and variance of the optimal trading strategy are given by:

In this notebook, we will learn how to visualize and interpret these equations.

# The Expected Shortfall

As we saw in the previous notebook, even if we use the same trading list, we are not guaranteed to always get the same implementation shortfall due to the random fluctuations in the stock price. This is why we had to reframe the problem of finding the optimal strategy in terms of the average implementation shortfall and the variance of the implementation shortfall. We call the average implementation shortfall, the expected shortfall $E(x)$, and the variance of the implementation shortfall $V(x)$. So, whenever we talk about the expected shortfall we are really talking about the average implementation shortfall. Therefore, we can think of the expected shortfall as follows. Given a single trading list, the expected shortfall will be the value of the average implementation shortfall if we were to implement this trade list in the stock market many times.

To see this, in the code below we implement the same trade list on 50,000 trading simulations. We call each trading simulation an episode. Each episode will consist of different random fluctuations in stock price. For each episode we will compute the corresponding implemented shortfall. After all the 50,000 trading simulations have been carried out we calculate the average implementation shortfall and the variance of the implemented shortfalls. We can then compare these values with the values given by the equations for $E(x)$ and $V(x)$ from the Almgren and Chriss model.

```python
%matplotlib inline

import matplotlib.pyplot as plt

import utils

# Set the default figure size
plt.rcParams['figure.figsize'] = [17.0, 7.0]

# Set the liquidation time
l_time = 60

# Set the number of trades
n_trades = 60

# Set trader's risk aversion
t_risk = 1e-6

# Set the number of episodes to run the simulation
episodes = 10

utils.get_av_std(lq_time = l_time, nm_trades = n_trades, tr_risk = t_risk, trs = episodes)

# Get the AC Optimal strategy for the given parameters
ac_strategy = utils.get_optimal_vals(lq_time = l_time, nm_trades = n_trades, tr_risk = t_risk)
ac_strategy
```

# Extreme Trading Strategies

Because some investors may be willing to take more risk than others, when looking for the optimal strategy we have to consider a wide range of risk values, ranging from those traders that want to take zero risk to those who want to take as much risk as possible. Let's take a look at these two extreme cases. We will define the **Minimum Variance** strategy as that one followed by a trader that wants to take zero risk and the **Minimum Impact** strategy at that one followed by a trader that wants to take as much risk as possible. Let's take a look at the values of $E(x)$ and $V(x)$ for these extreme trading strategies. The `utils.get_min_param()` uses the above equations for $E(x)$ and $V(x)$, along with the parameters from the trading environment to calculate the expected shortfall and standard deviation (the square root of the variance) for these strategies. We'll start by looking at the Minimum Impact strategy.

```python
import utils

# Get the minimum impact and minimum variance strategies
minimum_impact, minimum_variance = utils.get_min_param()
```

### Minimum Impact Strategy

This trading strategy will be taken by trader that has no regard for risk. In the Almgren and Chriss model this will correspond to having the trader's risk aversion set to $\lambda = 0$. In this case the trader will sell the shares at a constant rate over a long period of time. By doing so, he will minimize market impact, but will be at risk of losing a lot of money due to the large variance. Hence, this strategy will yield the lowest possible expected shortfall and the highest possible variance, for a given set of parameters. We can see that for the given parameters, this strategy yields an expected shortfall of \$197,000 dollars but has a very big standard deviation of over 3 million dollars.

```python
minimum_impact
```

AC Optimal Strategy for Minimum Impact

  Number of Days to Sell All the Shares:	     250	       Initial Portfolio Value:	         $50,000,000.00

  Half-Life of The Trade:	                1,284,394.9	   Expected Shortfall:	                $197,000.00

  Utility:	                               $197,000.00	   Standard Deviation of Shortfall:	  $3,453,707.55

### Minimum Variance Strategy

This trading strategy will be taken by trader that wants to take zero risk, regardless of transaction costs. In the Almgren and Chriss model this will correspond to having a variance of $V(x) = 0$. In this case, the trader would prefer to sell the all his shares immediately, causing a known price impact, rather than risk trading in small increments at successively adverse prices. This strategy will yield the smallest possible variance, $V(x) = 0$, and the highest possible expected shortfall, for a given set of parameters. We can see that for the given parameters, this strategy yields an expected shortfall of over 2.5 million dollars but has a standard deviation equal of zero.

```python
minimum_variance
```

AC Optimal Strategy for Minimum Variance

  Number of Days to Sell All the Shares:	       1	         Initial Portfolio Value:	         $50,000,000.00

  Half-Life of The Trade:	                     0.2	        Expected Shortfall:	               $2,562,500.00

  Utility:	                               $2,562,500.00	   Standard Deviation of Shortfall:	      $0.00

# The Efficient Frontier

The goal of Almgren and Chriss was to find the optimal strategies that lie between these two extremes. In their paper, they showed how to compute the trade list that minimizes the expected shortfall for a wide range of risk values. In their model, Almgren and Chriss used the parameter $\lambda$ to measure a trader's risk-aversion. The value of $\lambda$ tells us how much a trader is willing to penalize the variance of the shortfall, $V(X)$, relative to expected shortfall, $E(X)$. They showed that for each value of $\lambda$ there is a uniquely determined optimal execution strategy. We define the **Efficient Frontier** to be the set of all these optimal trading strategies. That is, the efficient frontier is the set that contains the optimal trading strategy for each value of $\lambda$.

The efficient frontier is often visualized by plotting $(x,y)$ pairs for a wide range of $\lambda$ values, where the $x$-coordinate is given by the equation of the expected shortfall, $E(X)$, and the $y$-coordinate is given by the equation of the variance of the shortfall, $V(X)$. Therefore, for a given a set of parameters, the curve defined by the efficient frontier represents the set of optimal trading strategies that give the lowest expected shortfall for a defined level of risk.

In the code below, we plot the efficient frontier for $\lambda$ values in the range $(10^{-7}, 10^{-4})$, using the default parameters in our trading environment. Each point of the frontier represents a distinct strategy for optimally liquidating the same number of stocks. A risk-averse trader, who wishes to sell quickly to reduce exposure to stock price volatility, despite the trading costs incurred in doing so, will likely choose a value of $\lambda = 10^{-4}$. On the other hand, a trader
who likes risk, who wishes to postpones selling, will likely choose a value of $\lambda = 10^{-7}$. In the code, you can choose a particular value of $\lambda$ to see the expected shortfall and level of variance corresponding to that particular value of trader's risk aversion.

```python
%matplotlib inline

import matplotlib.pyplot as plt

import utils

# Set the default figure size
plt.rcParams['figure.figsize'] = [17.0, 7.0]

# Plot the efficient frontier for the default values. The plot points out the expected shortfall and variance of the
# optimal strategy for the given the trader's risk aversion. Valid range for the trader's risk aversion (1e-7, 1e-4).
utils.plot_efficient_frontier(tr_risk = 1e-6)
```

11. Deep RL for Finance - 1/.ipynb_checkpoints/Efficient Frontier-checkpoint.ipynb


```python
from sympy import *
import pandas as pd
```

```python
import pickle
```

```python
init_session()
```

    IPython console for SymPy 1.0 (Python 2.7.12-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at http://docs.sympy.org/1.0/

```python
t, P, e_r, e_d, delta_e, rho_e, g_e, i_r, i_d, delta_i, rho_i, g_i, b = symbols('t P \\tau_{er} \\tau_{ed} \\delta_e \\rho_e \\bar{g}_e \\tau_{ir} \\tau_{id} \\delta_i \\rho_i \\bar{g}_i \\beta', positive=True, real=True)
```

```python
estimateDict = { P: (1.9,2.1), e_r: (1.5,5), e_d: (8,20), delta_e: (0,0), rho_e: (2,7), g_e: (0.02,0.25), i_r : (1.5,5), i_d : (14,60), delta_i:(3,8), rho_i: (5,20), g_i:(0.04,0.5) , b:(0.5,5) }
```

```python
averageEstimateDict = {key: pow(value[0]*value[1],0.5) for key,value in estimateDict.items()}
```

```python
averageEstimateDict
```

```python
#a,b,c,d = -t/e_r, -t/e_d, -(t - delta_i)/i_r, -(t - delta_i)/i_d
```

```python
alpha = exp(-(t-delta_e)/e_d) - exp(-(t-delta_e)/e_r)
```

```python
alpha_prime = alpha.diff(t)
```

```python
theta_e = solve(alpha_prime,t) # Time to peak
```

```python
alpha_star = simplify(alpha.subs(t, theta_e[0]).doit())
```

```python
I_alpha_star = alpha_star.subs({ e_r:i_r, e_d: i_d})
```

```python
alpha_star
```

```python
E = Piecewise((0, t < delta_e), (g_e * (alpha/alpha_star), True))
```

```python
I = E.xreplace({g_e: g_i, rho_e: rho_i, e_r:i_r, e_d: i_d, delta_e: delta_i})
```

```python
C = E - I
```

```python
C = C.subs({delta_e:0})
```

```python
C_check = N(C.subs(averageEstimateDict))
```

```python
plot(C_check,(t,0,100))
```

    <sympy.plotting.plot.Plot at 0x7f1303d61090>

```python
theta_e
```

```python
#C_prime = ((g_e/alpha_star)*(t/e_d - t/e_r)) - ((g_i/I_alpha_star)*((t-delta_i)/i_d - (t-delta_i)/i_r))
```

```python
a_star, ai_star = symbols('a_*, a_i*')
```

```python
C_prime = ((g_e/a_star)*(e_d - e_r)/((t-e_d)*(t-e_r))) - ((g_i/ai_star)*(i_d - i_r)/((t-delta_i-i_d)*(t-delta_i-i_r)))
```

```python
C_prime
```

```python
C_prime = C_prime.subs({e_d: rho_e*e_r, i_d: rho_i*i_r, g_i: P*g_e})
```

```python
C_prime = simplify(C_prime)
```

```python
C_prime
```

```python
backup = pd.HDFStore('variables.h5')
solution = solveset(C_prime,t)
backup['solution'] = solution
backup.close()
```

```python
backup = pd.HDFStore('variables.h5')
solution = backup['solution']
```

```python
factor(solution)
```

```python
%store solution
```

```python
%store -r solution
```

```python
alpha_start
```

```python
N(solution.subs(averageEstimateDict).subs({a_star:alpha_star.subs(averageEstimateDict), ai_star: I_alpha_star.subs(averageEstimateDict)}))
```

```python
10.4315436555601 - 4.89
```

```python
with open ("solution.pkl",'w') as f:
    pickle.dump(solution, f)
```

```python
solution1 = solution.subs({e_d: rho_e*e_r, i_d: rho_i*i_r, g_i: P*g_e})
```

```python
simplify(solution)
```

```python
t_star1 = simplify(cancel(expand(solution[0])))
```

```python
len(solution.args)
```

```python
t_star = simplify(cancel(expand(t_star.subs({e_d: (rho_e*e_r), i_d: (rho_i*i_r), g_i:P*g_e}))))
```

```python
t_star
```

```python
C.subs(t,t_star)
```

```python
C_prime.subs(t,t_star)
```

```python
N(solution.subs(averageEstimateDict))
```


# A simple SymPy example

First we import SymPy and initialize printing:

```
from sympy import init_printing
from sympy import *
 init_printing()
```

Create a few symbols:

```
x,y,z = symbols('x y z')
```

Here is a basic expression:

```
e = x**2 + 2.0*y + sin(z); e
```

```
diff(e, x)
```

```
integrate(e, z)
```

```

```


```python
# https://colab.research.google.com/github/kassbohm/wb-snippets/blob/master/ipynb/HTM_03/Selbst/4.2.ipynb

from sympy import *
from sympy.physics.units import kilogram as kg
from sympy.physics.units import meter as meter

# 1:
mu, l = var("mu, l")

(l1, l2, l3) = (2*l, l, 2*l/3)
(m1, m2, m3) = (mu*l1, mu*l2, mu*l3)
(xi1, xi2, xi3) = (m1*l1**2/12, m2*l2**2/12, m3*l3**2/12)

# 2:
IC1 = xi1 * Matrix([
    [1,0,0],
    [0,0,0],
    [0,0,1],
    ])

IC2 = xi2 * Matrix([
    [0,0,0],
    [0,1,0],
    [0,0,1],
    ])

IC3 = xi3 * Matrix([
    [1,0,0],
    [0,1,0],
    [0,0,0],
    ])

pprint("\nIC1, IC2, IC3 / ( μl³ ):")
for x in [IC1, IC2, IC3]:
    tmp = x / (mu*l**3)
    pprint(tmp)

# 3:
M, dx, dy, dz = var("M, dx, dy, dz")

(dx1, dy1, dz1) = (0, l1/2, 0)
(dx2, dy2, dz2) = (l2/2, l1, 0)
(dx3, dy3, dz3) = (l2, l1, l3/2)

tmp = Matrix([
    [dy**2 + dz**2,0,0],
    [0,dx**2 + dz**2,0],
    [0,0,dx**2 + dy**2],
    ])
tmp+= Matrix([
    [     0,-dx*dy,-dx*dz],
    [-dy*dx,     0,-dy*dz],
    [-dz*dx,-dz*dy,     0],
    ])
tmp*=M

# 4:
pprint("\nI01, I02, I03 / ( μl³ ):")
I01 = IC1 + tmp.subs([(M,m1), (dx, dx1), (dy, dy1), (dz, dz1)])
I02 = IC2 + tmp.subs([(M,m2), (dx, dx2), (dy, dy2), (dz, dz2)])
I03 = IC3 + tmp.subs([(M,m3), (dx, dx3), (dy, dy3), (dz, dz3)])
for x in [I01, I02, I03]:
    tmp = x / (mu*l**3)
    pprint(tmp)

# 5:
pprint("\nI0 / ( μl³ ):")
I0 = I01 + I02 + I03
I0 /= mu*l**3
pprint(I0)

pprint("\nEigenvalues of I0 / (kg m²):")
f = lambda x: N(x,3)
lam = var("lambda")
eq = Eq(det(I0 - lam*eye(3,3)))
sol = solve(eq, lam)

mu_quant = 4*kg/meter
l_quant = 4*meter/10
for s in sol:
    tmp = N(s,5, chop=True)
    tmp = tmp*mu_quant*l_quant**3
    tmp /= kg * meter**2
    pprint(tmp)

pprint("\nb:")
b = Matrix([1, 2, S(2)/3])
b /= b.norm()
pprint(b)
pprint(N(b,4))

pprint("\nI0b / ( μl³ ):")
tmp = I0*b
pprint(tmp)
pprint("\nJ / ( μl³ ):")
J = b.transpose()*I0*b
J = J[0]
pprint(J)
pprint("\nJ / (kg m²):")
tmp = J
tmp *= mu_quant*l_quant**3
tmp /= kg
tmp /= meter*meter
pprint(N(tmp,4))

# IC1, IC2, IC3 / ( μl³ ):
# ⎡2/3  0   0 ⎤
# ⎢           ⎥
# ⎢ 0   0   0 ⎥
# ⎢           ⎥
# ⎣ 0   0  2/3⎦
# ⎡0   0     0  ⎤
# ⎢             ⎥
# ⎢0  1/12   0  ⎥
# ⎢             ⎥
# ⎣0   0    1/12⎦
# ⎡2/81   0    0⎤
# ⎢             ⎥
# ⎢ 0    2/81  0⎥
# ⎢             ⎥
# ⎣ 0     0    0⎦
#
# I01, I02, I03 / ( μl³ ):
# ⎡8/3  0   0 ⎤
# ⎢           ⎥
# ⎢ 0   0   0 ⎥
# ⎢           ⎥
# ⎣ 0   0  8/3⎦
# ⎡4   -1    0  ⎤
# ⎢             ⎥
# ⎢-1  1/3   0  ⎥
# ⎢             ⎥
# ⎣0    0   13/3⎦
# ⎡224             ⎤
# ⎢───   -4/3  -2/9⎥
# ⎢ 81             ⎥
# ⎢                ⎥
# ⎢       62       ⎥
# ⎢-4/3   ──   -4/9⎥
# ⎢       81       ⎥
# ⎢                ⎥
# ⎣-2/9  -4/9  10/3⎦
#
# I0 / ( μl³ ):
# ⎡764             ⎤
# ⎢───   -7/3  -2/9⎥
# ⎢ 81             ⎥
# ⎢                ⎥
# ⎢       89       ⎥
# ⎢-7/3   ──   -4/9⎥
# ⎢       81       ⎥
# ⎢                ⎥
# ⎣-2/9  -4/9  31/3⎦
#
# Eigenvalues of I0 / (kg m²):
# 2.5627
# 2.6593
# 0.11929
#
# b:
# ⎡3/7⎤
# ⎢   ⎥
# ⎢6/7⎥
# ⎢   ⎥
# ⎣2/7⎦
# ⎡0.4286⎤
# ⎢      ⎥
# ⎢0.8571⎥
# ⎢      ⎥
# ⎣0.2857⎦
#
# I0b / ( μl³ ):
# ⎡ 374 ⎤
# ⎢ ─── ⎥
# ⎢ 189 ⎥
# ⎢     ⎥
# ⎢-5/27⎥
# ⎢     ⎥
# ⎢ 52  ⎥
# ⎢ ──  ⎥
# ⎣ 21  ⎦
#
# J / ( μl³ ):
# 88
# ──
# 63
#
# J / (kg m²):
# 0.3576

```