In this notebook, you will implement the forward longitudinal vehicle model. The model accepts throttle inputs and steps through the longitudinal dynamic equations. Once implemented, you will be given a set of inputs that drives over a small road slope to test your model.

The input to the model is a throttle percentage $x_\theta \in [0,1]$ which provides torque to the engine and subsequently accelerates the vehicle for forward motion.

The dynamic equations consist of many stages to convert throttle inputs to wheel speed (engine -> torque converter -> transmission -> wheel). These stages are bundled together in a single inertia term $J_e$ which is used in the following combined engine dynamic equations.

\begin{align}
    J_e \dot{\omega}_e &= T_e - (GR)(r_{eff} F_{load}) \\ m\ddot{x} &= F_x - F_{load}
\end{align}

Where $T_e$ is the engine torque, $GR$ is the gear ratio, $r_{eff}$ is the effective radius, $m$ is the vehicle mass, $x$ is the vehicle position, $F_x$ is the tire force, and $F_{load}$ is the total load force.

The engine torque is computed from the throttle input and the engine angular velocity $\omega_e$ using a simplified quadratic model.

\begin{align}
    T_e = x_{\theta}(a_0 + a_1 \omega_e + a_2 \omega_e^2)
\end{align}

The load forces consist of aerodynamic drag $F_{aero}$, rolling friction $R_x$, and gravitational force $F_g$ from an incline at angle $\alpha$. The aerodynamic drag is a quadratic model and the friction is a linear model.

\begin{align}
    F_{load} &= F_{aero} + R_x + F_g \\
    F_{aero} &= \frac{1}{2} C_a \rho A \dot{x}^2 = c_a \dot{x}^2\\
    R_x &= N(\hat{c}_{r,0} + \hat{c}_{r,1}|\dot{x}| + \hat{c}_{r,2}\dot{x}^2) \approx c_{r,1} \dot{x}\\
    F_g &= mg\sin{\alpha}
\end{align}

Note that the absolute value is ignored for friction since the model is used for only forward motion ($\dot{x} \ge 0$).

The tire force is computed using the engine speed and wheel slip equations.

\begin{align}
    \omega_w &= (GR)\omega_e \\
    s &= \frac{\omega_w r_e - \dot{x}}{\dot{x}}\\
    F_x &= \left\{\begin{array}{lr}
        cs, &  |s| < 1\\
        F_{max}, & \text{otherwise}
        \end{array}\right\}
\end{align}

Where $\omega_w$ is the wheel angular velocity and $s$ is the slip ratio.

We setup the longitudinal model inside a Python class below. The vehicle begins with an initial velocity of 5 m/s and engine speed of 100 rad/s. All the relevant parameters are defined and like the bicycle model, a sampling time of 10ms is used for numerical integration.

```python
from math import sin, atan2
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

class Vehicle():
    def __init__(self):

        # ==================================
        #  Parameters
        # ==================================

        #Throttle to engine torque
        self.a_0 = 400
        self.a_1 = 0.1
        self.a_2 = -0.0002

        # Gear ratio, effective radius, mass + inertia
        self.GR = 0.35
        self.r_e = 0.3
        self.J_e = 10
        self.m = 2000
        self.g = 9.81

        # Aerodynamic and friction coefficients
        self.c_a = 1.36
        self.c_r1 = 0.01

        # Tire force
        self.c = 10000
        self.F_max = 10000

        # State variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0

        self.sample_time = 0.01

    def reset(self):
        # reset state variables
        self.x = 0
        self.v = 5
        self.a = 0
        self.w_e = 100
        self.w_e_dot = 0
```

Implement the combined engine dynamic equations along with the force equations in the cell below. The function $\textit{step}$ takes the throttle $x_\theta$ and incline angle $\alpha$ as inputs and performs numerical integration over one timestep to update the state variables. Hint: Integrate to find the current position, velocity, and engine speed first, then propagate those values into the set of equations.

```python
class Vehicle(Vehicle):
    def step(self, throttle, alpha):
        # ==================================
        #  Implement vehicle model here
        # ==================================
        x_dot = self.v
        v_dot = self.a
        w_e_dot = self.w_e_dot
        self.x += x_dot * self.sample_time
        self.v += v_dot * self.sample_time
        self.w_e += w_e_dot * self.sample_time
        omega_w = self.GR * self.w_e # Wheel speed from engine speed
        s = omega_w * self.r_e / self.v - 1 # Slip ratio
        F_x = self.c * s if abs(s) < 1 else self.F_max
        F_g = self.m * self.g * sin(alpha)
        R_x = self.c_r1 * self.v
        F_aero = self.c_a * self.v**2
        F_load = F_aero + R_x + F_g
        T_e = throttle * (self.a_0 + self.a_1 * self.w_e + self.a_2 * self.w_e**2)
        self.a = (F_x - F_load) / self.m
        self.w_e_dot = (T_e - self.GR * self.r_e * F_load) / self.J_e
```

Using the model, you can send constant throttle inputs to the vehicle in the cell below. You will observe that the velocity converges to a fixed value based on the throttle input due to the aerodynamic drag and tire force limit. A similar velocity profile can be seen by setting a negative incline angle $\alpha$. In this case, gravity accelerates the vehicle to a terminal velocity where it is balanced by the drag force.

```python
sample_time = 0.01
time_end = 100
model = Vehicle()

t_data = np.arange(0,time_end,sample_time)
v_data = np.zeros_like(t_data)

# throttle percentage between 0 and 1
throttle = 0.2

# incline angle (in radians)
alpha = 0

for i in range(t_data.shape[0]):
    v_data[i] = model.v
    model.step(throttle, alpha)

plt.plot(t_data, v_data)
plt.show()
```

We will now drive the vehicle over a slope as shown in the diagram below.

To climb the slope, a trapezoidal throttle input is provided for the next 20 seconds as shown in the figure below.

The vehicle begins at 20% throttle and gradually increases to 50% throttle. This is maintained for 10 seconds as the vehicle climbs the steeper slope. Afterwards, the vehicle reduces the throttle to 0.

In the cell below, implement the ramp angle profile $\alpha (x)$ and throttle profile $x_\theta (t)$ and step them through the vehicle dynamics. The vehicle position $x(t)$ is saved in the array $\textit{x_data}$. This will be used to grade your solution.

```python
time_end = 20
t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)
v_data = np.zeros_like(t_data)
throttle_data = np.zeros_like(t_data)
alpha_data = np.zeros_like(t_data)

# reset the states
model.reset()

# ==================================
#  Learner solution begins here
# ==================================
n = t_data.shape[0]
throttle_data[:n//4] = [0.2 + 0.3 * i / (n//4) for i in range(n//4)]
throttle_data[n//4:3*n//4] = 0.5
throttle_data[3*n//4:] = [0.5 - 0.001 * i for i in range(n//4)]
for i in range(n):
    x_data[i] = model.x
    v_data[i] = model.v
    if model.x < 60:
        alpha_data[i] = atan2(3, 60)
    elif model.x < 150:
        alpha_data[i] = atan2(9, 90)
    model.step(throttle_data[i], alpha_data[i])

# ==================================
#  Learner solution ends here
# ==================================

# Plot x vs t for visualization
plt.plot(t_data, x_data)
plt.show()
```

If you have implemented the vehicle model and inputs correctly, you should see that the vehicle crosses the ramp at ~15s where the throttle input begins to decrease.

The cell below will save the time and vehicle inputs as text file named $\textit{xdata.txt}$. To locate the file, change the end of your web directory to $\textit{/notebooks/Course_1_Module_4/xdata.txt}$

Once you are there, you can download the file and submit to the Coursera grader to complete this assessment.

```python
data = np.vstack([t_data, x_data]).T
np.savetxt('xdata.txt', data, delimiter=', ')
```

Congratulations! You have now completed the assessment! Feel free to test the vehicle model with different inputs in the cell below, and see what trajectories they form. In the next module, you will see the longitudinal model being used for speed control. See you there!

```python
sample_time = 0.01
time_end = 30
model.reset()

t_data = np.arange(0,time_end,sample_time)
x_data = np.zeros_like(t_data)

# ==================================
#  Test various inputs here
# ==================================
for i in range(t_data.shape[0]):

    model.step(0,0)

plt.axis('equal')
plt.plot(x_data, y_data)
plt.show()
```


# Section 4.9 $\quad$ Rank of a Matrix

>**Definition** Let
\begin{equation*}
  A =
  \left[
    \begin{array}{cccc}
      a_{11} & a_{12} & \cdots & a_{1n} \\
      a_{21} & a_{22} & \cdots & a_{2n} \\
      \vdots & \vdots & \ddots & \vdots \\
      a_{m1} & a_{m2} & \cdots & a_{mn} \\
    \end{array}
  \right]
\end{equation*}
be an $m\times n$ matrix.
- The rows of $A$,

- The columns of $A$,

**Remark** If $A$ and $B$ are row equivalent matrices, then

We can use this remark to find a basis for a subspace spanned by a given set of vectors.

### Example 1

Find a basis for the subspace $V$ of $\mathbb{R}_5$ that is spanned by $S = \{\mathbf{v}_1,\mathbf{v}_2,\mathbf{v}_3,\mathbf{v}_4\}$ where
\begin{equation*}
   \mathbf{v}_1 = [1~ -2~ 0 ~ 3~ -4],~~~
   \mathbf{v}_2 = [3~ 2~ 8 ~ 1~ 4],~~~
   \mathbf{v}_3 = [2~ 3~ 7 ~ 2~ 3],~~~
   \mathbf{v}_4 = [-1~ 2~ 0 ~ 4~ 3],~~~
\end{equation*}

```python
from sympy import *

A = Matrix([[1, -2, 0, 3, -4], [3, 2, 8, 1, 4], [2, 3, 7, 2, 3], [-1, 2, 0, 4, 3]]);

A.rref()
```

    (Matrix([
     [1, 0, 2, 0, 0],
     [0, 1, 1, 0, 0],
     [0, 0, 0, 1, 0],
     [0, 0, 0, 0, 1]]), [0, 1, 3, 4])

**Remark**
-

-

### Example 2

Let $V$ be the subspace of Example $1$. Given that the vector $\mathbf{v} = [5~~4~~14~~6~~3]$ is in $V$, write $\mathbf{v}$ as a linear combinations of the base determined by Example $1$.

```python
from sympy import *

A = Matrix([[1, -2, 0, 3, -4], [3, 2, 8, 1, 4], [2, 3, 7, 2, 3], [-1, 2, 0, 4, 3]]);
B = A.rref()[0].T;
x1, x2, x3, x4 = symbols('x1 x2 x3 x4');
x = Matrix([x1, x2, x3, x4]);
Bx = B.dot(x);

solve(Matrix(Bx) - Matrix([5, 4, 14, 6, 3]))
```

    [{x3: 6, x2: 4, x4: 3, x1: 5}]

>**Definition** The dimension of the row (column) space of $A$ is called

**Remark** If $A$ and $B$ are row equivalent,

### Example 3

Compute the row rank of $A$ given by
\begin{equation*}
  A =
  \left[
    \begin{array}{ccccc}
      1 & -2 & 0 & 3 & -4 \\
      3 & 2 & 8 & 1 & 4 \\
      2 & 3 & 7 & 2 & 3 \\
      -1& 2 & 0 & 4 & -3 \\
    \end{array}
  \right]
\end{equation*}

```python
from sympy import *

A = Matrix([[1, -2, 0, 3, -4], [3, 2, 8, 1, 4], [2, 3, 7, 2, 3], [-1, 2, 0, 4, -3]]);

A.rank()
```

    3

### Example 4

Compute the column rank of $A$ in Example 3.

```python
from sympy import *

A = Matrix([[1, -2, 0, 3, -4], [3, 2, 8, 1, 4], [2, 3, 7, 2, 3], [-1, 2, 0, 4, -3]]);

A.rank()
```

    3

>**Theorem** Let $A$ be an $m\times n$ matrix. Then


# Revisão de conceitos estatísticos III (Estimadores)

Vamos explorar alguns conceitos estatísticos aplicados à análise de sinais.

```python
# importar as bibliotecas necessárias
import numpy as np # arrays
import matplotlib.pyplot as plt # plots
from scipy.stats import norm
plt.rcParams.update({'font.size': 14})
import IPython.display as ipd # to play signals
import sounddevice as sd
```

# Um conjunto de ruídos brancos

Vamos gerar um conjunto (ensemble) de ruídos brancos. Você pode encarar isso como se fosse uma série de gravações de um sinal aleatório. Por exemplo, como se você fosse medir a vibração em uma máquina complexa e tomasse $N_{rec}$ gravações. No caso do ruído branco temos um fenômeno governado por uma distribuição de probabilidade Gaussiana e constante com o tempo:

\begin{equation}
p(x) = \mathcal{N}(\mu_x, \sigma_x) = \frac{1}{\sqrt{2\pi}}\mathrm{e}^{-\frac{1}{2\sigma^2}(x-\mu_x)^2}
\end{equation}
em que $\mu_x$ é a média e $\sigma_{x}$ é o desvio padrão.

```python
# Tempo
fs = 2000
time = np.arange(0,2, 1/fs)

# parâmetros de um fenômeno, cuja p(x) varia com o tempo
mu_x = 1.2 - np.cos(2*np.pi*0.5*time)    # média
sigma_x = 1.2 - np.sin(2*np.pi*1*time)

# A densidade de probabilidade
x = np.arange(-10, 10, 0.01)
px = norm.pdf(x, loc= mu_x, scale = sigma_x)

# Número de gravações
N_rec = 500

xt = np.random.normal(loc = mu_x, scale = sigma_x, size=(N_rec,len(time)))
```

# Vamos escolher 5 gravações do conjunto de sinais gravados para plotar $x(t)$

```python
rec_choose = np.random.randint(51, size=5)
color = ['b', 'r', 'k', 'g', 'magenta']

fig, axs = plt.subplots(5, 1, figsize = (10, 8))
for i in np.arange(5):
    axs[i].plot(time, xt[rec_choose[i]], linewidth = 1, color = color[i])
    axs[i].axvline(0.25, color='grey',linestyle = '--', linewidth = 4, alpha = 0.8)
    axs[i].set_ylabel(r'$x(t)$')
    axs[i].set_xlim((0, time[-1]))
    axs[i].set_ylim((-4, 4))
axs[i].set_xlabel('tempo [s]')
plt.tight_layout()
```

# Computar os momentos para o estimador do conjunto.

```python
# Escolha 1 ou 2 instantes de tempo e calcule as amostras
n_1 = int(0.25*fs)
n_2 = int(0.73*fs)

# Primeiro momento
Ex_1 = (1/N_rec)*np.sum(xt[:, n_1])
Ex_2 = (1/N_rec)*np.sum(xt[:, n_2])
print("E[x] para t_1 é {:.4f}".format(Ex_1))
print("E[x] para t_2 é {:.4f}".format(Ex_2))

# Segundo momento
Ex2_1 = (1/N_rec)*np.sum((xt[:, n_1]-Ex_1)**2)
Ex2_2 = (1/N_rec)*np.sum((xt[:, n_2]-Ex_2)**2)
print("E[(x-\mu_x)^2] para t_1 é {:.4f}".format(Ex2_1))
print("E[(x-\mu_x)^2] para t_2 é {:.4f}".format(Ex2_2))
```

    E[x] para t_1 é 0.0272
    E[x] para t_2 é -0.0102
    E[(x-\mu_x)^2] para t_1 é 0.2420
    E[(x-\mu_x)^2] para t_2 é 0.2520

# Agora, tome apenas 1 das gravações (estimador do sinal)

Encare isso como sendo a única gravação que você fez.

```python
rec_choose = np.random.randint(51, size=1)
xt_rec = np.reshape(xt[rec_choose,:], len(time))

plt.figure(figsize = (10, 3))
plt.plot(time, xt_rec, linewidth = 1, color = 'g')
plt.ylabel(r'$x(t)$')
plt.xlim((0, time[-1]))
plt.ylim((-4, 4))
plt.xlabel('tempo [s]')
plt.tight_layout()
```

# Computar os momentos do sinal.

```python
# Primeiro momento
Ex = np.mean(xt_rec)
Ex2 = np.std(xt_rec)**2
x_rms = np.sqrt((1/len(time))*np.sum(xt_rec**2))

print("E[x] para o sinal é {:.4f}".format(Ex))
print("E[(x-\mu_x)^2] para o sinal é {:.4f}".format(Ex2))
print("O quadrado do valor RMS é {:.4f}".format(x_rms**2))
```

    E[x] para o sinal é 0.0159
    E[(x-\mu_x)^2] para o sinal é 0.2551
    O quadrado do valor RMS é 0.2553

Aula 52 - estacionaridade/.ipynb_checkpoints/estacionaridade-checkpoint.ipynb


# Perceptron

* Idea: Driven by mistakes

* Model:

\begin{align}
f(x)=sign(w^Tx+b),\\x{\in}R^p,w{\in}R^p\\\\
sign(a) = \left\{\begin{matrix}
+1,a\geqslant0
\\
-1,a<0
\end{matrix}\right.
\end{align}

* Loss funtion:

    1. Use the number of missclassification as loss

 \begin{align}
L(w)=\sum_{i=1}^{N}I\left \{y_i(w^Tx_i+b)<0\right \}\\
\left.\begin{matrix}
w^Tx_i+b>0,y_i>0
\\
w^Tx_i+b<0,y_i<0
\end{matrix}\right\}\Rightarrow \left\{\begin{matrix}
w^Tx_i+b>0, True
\\
w^Tx_i+b<0, False
\end{matrix}\right.
\end{align}

    **But in this case, the funtion is not derivative.**

    2. Use the distance as loss

 \begin{align}
min:L(w)=\sum_{x_i{\in}D}^{}-y_i(w^Tx_i+b)\\D:\left \{ Miss\ Classified\ Points\right \}\\
\Delta _{w}L = \sum_{}^{} -y_ix_i\\
\Delta _{b}L = \sum_{}^{} -y_i
\end{align}

* Algorithm: **SGD**

 \begin{align}
w^{(t+1)}=w^{(t)}-\lambda\Delta _{w}L \\
=w^{(t)} + \lambda\sum_{}^{} y_ix_i\\\\
b^{(t+1)}=b^{(t)}\lambda\Delta _{b}L \\
=b^{(t)} + \lambda\sum_{}^{} y_i
\end{align}

    b could be treated as w0

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import random
from sklearn.model_selection import train_test_split
%matplotlib inline
```

```python
class Perceptron():
    """
    Perceptron Classifier.

    Parameters
    ------------------
    learning_rate: float
        Learning rate between 0.0 and 1.0
    max_iter: int
        Passes (epochs) over the training set
    n_sgd: int (Not Using)
        The number of gradient for stochastic gradient descent.

    Attributes
    ------------------

    """

    def __init__(self,active_func = 'step',n_sgd=5,max_iter=100,learning_rate=0.01):

        self.learning_rate= 0.01
        self.max_iter = 100
        self.active_func = active_func
        if self.active_func == 'sigmoid':
            self.active_function = self._sigmoid_func
        elif self.active_func == 'step':
            self.active_function = self._unit_step_func
        self.n_sgd = 10

    def fit(self,X_train,y_train,verbose= True):
        """
        Fit method for training data.

        Parameters:
        -----------------------
        X_train: {array-like}, shape = [n_samples, n_features]
            Training matrix, where 'n_samples' is the number of samples
            and 'n_features' is the number of features
        y_train: {array-like}, shape = [n_samples]
            Target labels

        Attributes:
        -----------------------
        w_record_: list
            Record all w.
        error_rate_: list
            Record all missclassification rate.
        intercept_: float
            Stores the intercept b.
        coef_: list
            Stores the w

        Returns:
        ------------------------
        self: object

        """
        m, n = np.shape(X_train)
        # Add a column with 1s to replace b.
        X_train = np.column_stack((np.ones(m),X_train))
        # Since y = {+1,-1}, convert all zeros to negative

        # Add a extra for b.
        self.w = np.zeros(n+1)

        self.w_record_ = []
        self.error_rate_ = []

        for _ in range(self.max_iter):
            incorrect_points = np.where(np.multiply(y_train,self.sign(X_train).reshape(m,1)) <= 0)[0]
            c = len(incorrect_points) / float(m)
            self.error_rate_.append(c)
            for i in range(m):
                y_pred = self.sign(X_train[i])
                #print(y_pred)
                if y_train[i]*y_pred <= 0:
                    self.w += (self.learning_rate * X_train[i]*y_train[i]).T
                    self.w_record_.append(self.w.copy())

        self.intercept_ = self.w[0]
        self.coef_ = self.w[1:]

        #SGD
#             y_pred = self.predict(X_train,self.w).reshape(m,1)

#             incorrect_points = np.where(np.multiply(y_train,y_pred) <= 0)[0]

#             if len(incorrect_points) == 0:
#                 break
#             c = len(incorrect_points) / float(m)
#             self.error_rate_.append(c)

# #             ls_i = np.random.choice(incorrect_points,self.n_sgd)
# #             for i in ls_i:
# #                 self.w += (self.learning_rate * X_train[i]*y_train[i]).T
#             i = np.random.choice(incorrect_points)
#             self.w += (self.learning_rate * X_train[i]*y_train[i]).T
#             self.w_record_.append(self.w.copy())

    def sign(self,X):

        linear_output = np.dot(X,self.w)
        prediction = self.active_function(linear_output)

        return prediction

    def predict(self,X):
        m, n = np.shape(X)
        # Add a column with 1s to replace b.
        X = np.column_stack((np.ones(m),X))
        linear_output = np.dot(X,self.w)
        prediction = self.active_function(linear_output)
        return prediction

    def _unit_step_func(self, x):
        return np.where(x>0 ,1,-1)

    def _sigmoid_func(self,z):
        a = 1/(1+np.exp(-z))
        return np.where(a>0.5,1,-1)

```

```python
from sklearn import datasets
df = pd.read_csv('ex.csv')

df = df[df['target']!=2]
df['target'][df['target']==0]=-1
X_train,X_test,y_train,y_test = train_test_split(df.drop('target',axis=1),df['target'].to_frame(),test_size = 0.3,random_state=101)
# feature = df.drop('target',axis=1)
# target = df['target'].to_frame()
X_train = X_train.to_numpy()
y_train = y_train.to_numpy()
X_test = X_test.to_numpy()
y_test = y_test.to_numpy()

```

    /Users/qijin/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:5: SettingWithCopyWarning:
    A value is trying to be set on a copy of a slice from a DataFrame

    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      """

```python
p = Perceptron()
p.fit(X_train,y_train)
```

```python
plt.plot(p.error_rate_)
```

```python

x = np.linspace(1,4)
y = (-p.w[0] - p.w[1]*x)/p.w[2]

positive = df[df['target'].isin([1])]
negative = df[df['target'].isin([-1])]

fig, ax = plt.subplots()
ax.scatter(positive['f1'], positive['f2'], s=50, marker='x', label='Positive')
ax.scatter(negative['f1'], negative['f2'], s=50, marker='o', label='Negative')
ax.plot(x,y)
ax.legend()
plt.show()

```

```python
y_pred = p.predict(X_test)

```

```python
from sklearn.metrics import confusion_matrix
```

```python
confusion_matrix(y_test,y_pred)
```

    array([[9, 1],
           [0, 5]])

```python

```


# Fundamentals of Data Science
Winter Semester 2020

## Info:

* Betti Martina (1799160)
* Ramse Gaurav (1965564)
* Ruà Giacomo (1770210)
* Sferragatta Stefania (1948081)
* Yildiz Mert (1951070)

## Prof. Fabio Galasso, Alessandro Flaborea and Luca Franco
<galasso@di.uniroma1.it>, <flaborea.1841969@studenti.uniroma1.it>, <franco.luca.17@gmail.com>

## Exercise 2

In Exercise 2, you will re-derive and implement logistic regression and optimize the parameters with Gradient Descent and with the Newton's method. Also, in this exercise you will re-derive and implement Gassian Discriminant Analysis.
We will use the files logistic_x.txt and logistic_y.txt. The first contains the feature values $x^{(i)}_1$ and $x^{(i)}_2$ for the $i$-th data sample $x^{(i)}$. The second contains the ground truth label $y^{(i)}$ for each corresponding data sample.

Note that, differently from what seen in lecture, $y^{(i)}\in \{-1,1\}$.

The completed exercise should be handed in as a single notebook file. Use Markdown to provide equations. Use the code sections to provide your scripts and the corresponding plots.
Submit it by sending an email to galasso@di.uniroma1.it, flaborea.1841969@studenti.uniroma1.it and franco.luca.17@gmail.com by Wednesday November 25th, 23:59.

## Notation

- $x^i$ is the $i^{th}$ feature vector
- $y^i$ is the expected outcome for the $i^{th}$ training example
- $m$ is the number of training examples
- $n$ is the number of features

Let's start by setting up our Python environment and importing the required libraries:

## [15 points] Question 1: Logistic Regression with Gradient Ascent

### (a) [5 points] Equations for the log likelihood, its gradient, and the gradient ascent update rule.

Write and simplify the likelihood $L(\theta)$ and log-likelihood $l(\theta)$ of the parameters $\theta$.

Recall the probabilistic interpretation of the hypothesis $h_\theta(x)= P(y=1|x;\theta)$ and that $h_\theta(x)=\frac{1}{1+\exp(-\theta^T x)}$.

Also derive the gradient $\frac{\delta l(\theta)}{\delta \theta_j}$ of $l(\theta)$ and write the gradient update equation.

Question: in order to learn the optimal $\theta$, do you need to minimize or to maximize $l(\theta)$?

################# Do not write above this line #################

#### Likelihood:
$L(\theta)$ = $P(\vec{y}|x;\theta)$

=$\prod_{i=1}^{m}P(y^{(i)}|x^{(i)};\theta)$

=$\prod_{i=1}^{m}\frac{1}{\sqrt{2\pi}\sigma}\exp -\frac{(y^{(i)}-\theta^{T}x^{(i)})^{2}}{2\sigma^{2}}$

#### Log - Likelihood:

$l(\theta)$ = $log L(\theta)$

= $log \prod_{i=1}^{m}\frac{1}{\sqrt{2\pi}\sigma}\exp -\frac{(y^{(i)}-\theta^{T}x^{(i)})^{2}}{2\sigma^{2}}$

= $\sum_{i=1}^{m} [log \frac{1}{\sqrt{2\pi}\sigma} + log \exp -\frac{(y^{(i)}-\theta^{T}x^{(i)})^{2}}{2\sigma^{2}}] $

= $ m  log \frac{1}{\sqrt{2\pi}\sigma} + \sum_{i=1}^{m} - \frac{(y^{(i)}-\theta^{T}x^{(i)})^{2}}{2\sigma^{2}}$

#### Interpretation of the hypothesis $h_\theta(x)= P(y=1|x;\theta)$ :
##### Likelihood:

$L(\theta)$ = $P(\vec{y}|x;\theta)$  (according to the Bernoulli distribution)

= $ \prod_{i=1}^{m}P(y^{(i)}|x^{(i)};\theta)$

= $ \prod_{i=1}^{m} h_\theta(x^{(i)})^{y^{(i)}} (1-h_\theta(x^{(i)}))^{1-y^{(i)}}$

##### Log - likelihood:
$l(\theta)$ = $log L(\theta)$

= $\sum_{i=1}^{m} y^{(i)} log (h_\theta(x^{(i)}))+(1-y^{(i)}) log (1-h_\theta(x^{(i)})) $

##### Derive the gradient $\frac{\delta l(\theta)}{\delta \theta_j}$ of $l(\theta)$ :
$\theta_{j}$ = $\theta_{j} + \alpha \sum_{i=1}^{m} (y^{(i)}-h_\theta(x^{(i)}))x_{j}^{(i)} $

= $\theta_{j} + \alpha \frac{\delta l(\theta)}{\delta \theta_j}$

#### Question : minimize
Our main goal is to maximize the value of the likelihood which is the function that measures the goodness of fit of a statistical model. Since the logarithmic function is a monotone one, in order to pick the best combination of model parameter values, we are looking for the maximum of the log likelihood function.

################# Do not write below this line #################

### (b) [10 points] Implementation of logistic regression with Gradient Ascent

Code up the equations above to learn the logistic regression parameters. Consider as data the files logistic_x.txt and logistic_y.txt. The first contains the feature values $x^{(i)}_1$ and $x^{(i)}_2$ for the $i$-th data sample $x^{(i)}$. The second contains the ground truth label $y^{(i)}$ for each corresponding data sample.

```python
%matplotlib inline
import numpy as np # imports a fast numerical programming library
import scipy as sp # imports stats functions, amongst other things
import matplotlib as mpl # this actually imports matplotlib
import matplotlib.cm as cm # allows us easy access to colormaps
import matplotlib.pyplot as plt # sets up plotting under plt
import pandas as pd # lets us handle data as dataframes

# sets up pandas table display
pd.set_option('display.width', 500)
pd.set_option('display.max_columns', 100)
pd.set_option('display.notebook_repr_html', True)

import seaborn as sns # sets up styles and gives us more plotting options
```

Let's start by loading the data into two pandas DataFrames:

```python
df_x = pd.read_csv("./data/logistic_x.txt", sep="\ +", names=["x1","x2"], header=None, engine='python')
df_y = pd.read_csv('./data/logistic_y.txt', sep='\ +', names=["y"], header=None, engine='python')
df_y = df_y.astype(int)
df_x.head()
```

      x1
      x2

      0
      1.343250
      -1.331148

      1
      1.820553
      -0.634668

      2
      0.986321
      -1.888576

      3
      1.944373
      -1.635452

      4
      0.976734
      -1.353315

```python
df_y.head()
```

      y

      0
      -1

      1
      -1

      2
      -1

      3
      -1

      4
      -1

Let's get the NumPy arrays from the dataset, and add a column of 1's to $X$ to take into account the zero intercept

```python
x = np.hstack([np.ones((df_x.shape[0], 1)), df_x[["x1","x2"]].values])
y = df_y["y"].values
```

Adjust y so that $y^{(i)}\in [0,1]$

################# Do not write above this line #################

Your code here

```python
y= (y - y.min()) / (y.max() - y.min())
np.array(y,dtype=int)
```

    array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

################# Do not write below this line #################

```python
[x[:5,:],x[-5:,:]] # Plot the first and last 5 lines of x, now containing features x0 (constant=1), x1 and x2
```

    [array([[ 1.        ,  1.3432504 , -1.3311479 ],
            [ 1.        ,  1.8205529 , -0.6346681 ],
            [ 1.        ,  0.98632067, -1.8885762 ],
            [ 1.        ,  1.9443734 , -1.635452  ],
            [ 1.        ,  0.97673352, -1.3533151 ]]),
     array([[ 1.        ,  4.7748538 ,  0.09941521],
            [ 1.        ,  5.8274854 , -0.69005848],
            [ 1.        ,  2.2894737 ,  1.9707602 ],
            [ 1.        ,  2.494152  ,  1.4152047 ],
            [ 1.        ,  2.0847953 ,  1.3567251 ]])]

```python
[y[:5],y[-5:]] # Plot the first and last 5 lines of y
```

    [array([0., 0., 0., 0., 0.]), array([1., 1., 1., 1., 1.])]

Define the sigmoid function "sigmoid", the function to compute the gradient of the log likelihood  "grad_l" and the gradient ascent algorithm.

################# Do not write above this line #################

Your code here

```python
def sigmoid(x,theta):
    z = np.dot(x,theta)
    g = 1/(1 + np.exp(-z))
    #the sigmoid of x changes according to the value of x. In case  x = 0, g(x)= 1/2; in case x -> +inf g(x) tends to 1;
    # in case x -> -inf g(x) tends to 0
    return g # return the sigmoid of x
```

```python
def log_likelihood(theta,x,y):
    h_theta = sigmoid(x,theta)
    log_l= sum(y*np.log(h_theta) + (1-y)*np.log(1-h_theta))/len(y)
    return log_l # return the log likehood of theta according to data x and label y

```

```python
def grad_l(theta, x, y):
    m = len(y)
    h_theta=sigmoid(x,theta)
    G = np.dot((y-h_theta),x)/m

    return G # return the gradient G of the log likelihood
```

```python
def gradient_ascent(theta,x,y,G,alpha,iterations):
    m = len(y)
    log_l_history = np.zeros(iterations)
    theta_history = np.zeros((iterations,3))

    for i in range(1,iterations):
        hypothesis = sigmoid(x, theta)
        log_l_history[i] = log_likelihood(theta,x,y)
        theta = theta_history[i-1] + (alpha*G(theta,x,y))
        theta_history[i] = theta

    return theta, log_l_history, theta_history #as well as two lists containing the log likelihood's and values of theta at all iterations
```

################# Do not write below this line #################

Check your grad_l implementation:
grad_l applied to the theta_test (defined below) should provide a value for log_l_test close to the target_value (defined below); in other words the error_test should be 0, up to machine error precision.

```python
target_value = -0.4250958770469834
theta_test=np.array([-2,1,2])

log_l_test  = log_likelihood(theta_test,x,y)
error_test=np.abs(log_l_test-target_value)

print("{:f}".format(error_test))
```

    0.000000

Let's now apply the function gradient_ascent and print the final theta as well as theta_history

```python
# Initialize theta0
theta0 = np.zeros(x.shape[1])

# Run Gradient Ascent method
n_iter=1000
theta_final, log_l_history, theta_history = gradient_ascent(theta0,x,y,grad_l,alpha=0.5,iterations=n_iter)
```

Let's plot the log likelihood over iterations

```python
fig,ax = plt.subplots(num=2)

ax.set_ylabel('l(Theta)')
ax.set_xlabel('Iterations')
_=ax.plot(range(len(log_l_history)),log_l_history,'b.')
```

Plot the data and the decision boundary:

```python
df_x_pl=df_x.copy()
df_y_pl=df_y.copy()
df_x_pl.insert(0, "y", df_y)
df_x_pl["y"] = pd.to_numeric(df_x_pl["y"], downcast='signed')
df_x_pl.head()
```

      y
      x1
      x2

      0
      -1
      1.343250
      -1.331148

      1
      -1
      1.820553
      -0.634668

      2
      -1
      0.986321
      -1.888576

      3
      -1
      1.944373
      -1.635452

      4
      -1
      0.976734
      -1.353315

```python
# Generate vector to plot decision boundary
x1_vec = np.linspace(df_x_pl["x1"].min(),df_x_pl["x1"].max(),2);

# Plot raw data
sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);

# Plot decision boundary
plt.plot(x1_vec,(-x1_vec*theta_final[1]-theta_final[0])/theta_final[2], color="red");

# Save the theta_final value for later comparisons
theta_GA = theta_final.copy()
print(theta_GA)
```

################# Do not write above this line #################

Write now your considerations. Discuss in particular:
- what effects do the learning rate $\alpha$ and the number of iterations have on the computed $\theta$
- may you still achieve the same value if you decrease the learning rate $\alpha$ and increase the number of iterations n_iter up to an infinite number of them? Why?
- may you still achieve the same value if you increase the learning rate $\alpha$ substantially and decrease the number of iterations n_iter? Why?

Substantiate your answer with experiments and plots of the $l(\theta)$ and the decision boundary.

```python
alphas=[0.001,0.005,0.01,0.05,0.2,0.4,0.7,1]
iterations=range(10,1000,10)
log_l=[]
p=[]
for alpha in alphas:
    log_l=[]
    for i in iterations:
        theta_final, log_l_history, theta_history = gradient_ascent(theta0,x,y,grad_l,alpha,int(i))
        log_l.append(np.mean(log_l_history))
    p.append(log_l)
```

```python
plt.figure(figsize=(15,10))
palette=sns.color_palette("rocket_r",8)
g=sns.lineplot(data=p,palette=palette)
plt.legend(title='alpha', loc='lower right', labels=alphas)
plt.show(g)
```

According to our analysis we observe that the behaviour of the log-likelihood of theta is strictly dependent on both the number of iterations and the value of the learning rate. As we can see in the plot above for small values of alpha the maximum is approched slowly, consequently the steepness of the curve is minimal. So for small values of the learning rate we obtain a good result only after a large number of iterations. However, the log likelihood that we reach with larger values of alpha, is never obtained with alpha smaller than 0.2. \
In the case in which we have a small number of iterations and also a small value of alpha we risk to undershoot the maximum, since more iterations would be needed in order to reach the maximum. To conclude we can state that the learning rate needed to reach the maximum is inversely proportional to the number of iterations.

################# Do not write below this line #################

## [15 points] Question 2: Logistic Regression with the Newton's method

### (a) [5 points] Equations for the Hessian of the log likelihood

Derive the equation for the Hessian matrix $H$ of the log-likelihood $l(\theta)$ of the parameters $\theta$.

Recall that: $H_{i,j} = \frac{\delta^2 l(\theta)}{\delta \theta_i \delta \theta_j}$

Also write the parameter update rule for the Newton's method, as a function of the Hessian and gradient of $l(\theta)$.

Question: in order to learn the optimal $\theta$, do you need to minimize or to maximize $\nabla_{\theta} l$?

################# Do not write above this line #################

##### Hessian:

$H_{i,j} = \frac{\delta^2 l(\theta)}{\delta \theta_i \delta \theta_j}$

$z = \frac{1}{1 + \exp(- \theta^{T}x)}$

$H = -\frac{1}{m}\sum_{i=1}^{m}{(z(1-z))x^{(i)}x^{(i)^{T}}}$

##### Parameter Update Rule for Newton's Method $\frac{\delta l(\theta)}{\delta \theta_j}$ of $l(\theta)$ :
$\theta_{j + 1}$ = $\theta_{j} - H^{-1} G $

##### Question : Do we minimizing or maximizing $\nabla_{\theta} l$?

As we have calculate and store the $\nabla_{\theta} l$ values for each iteration, in order to learn the optimal value of $\theta$, we need to minimize the $\nabla_{\theta} l \,$ function.

################# Do not write below this line #################

### (b) [10 points] Implementation of logistic regression with the Newton's method

Code up the equations above to learn the logistic regression parameters. Consider as data the files logistic_x.txt and logistic_y.txt. The first contains the feature values  $x^{(i)}_1$ and $x^{(i)}_2$  for the  𝑖 -th data sample  $x^{(i)}$ . The second contains the ground truth label  $y^{(i)}$  for each corresponding data sample.

Define the Hessian function hess_l and the Newton's method function "newton":

################# Do not write above this line #################

Your code here

```python
def hess_l(theta, x, y):
    z = sigmoid(x, theta)
    diag = np.diag(z * (1 - z))
    hess = -(x.T.dot(diag.dot(x))/len(y))
    return hess

def newton(theta0, x, y, G, H, eps):

    theta_list = []
    delta_l =  np.linalg.norm(grad_l(theta0,x, y))
    l = log_likelihood(theta0, x,y)
    log_l_history = []
    theta_history = []
    G_history = []

    while delta_l>eps:
        H = hess_l(theta0,x,y)
        G = grad_l(theta0,x,y)
        H_inv = np.linalg.inv(H)
        theta_history.append(theta0)
        theta0 = theta0 - H_inv.dot(G)
        log_l_history.append(log_likelihood(theta0,x,y))
        delta_l = np.linalg.norm((grad_l(theta0,x,y)))

    theta_history= np.array(theta_history)
    log_l_history= np.array(log_l_history)
    # return the optimized theta parameters,
    # as well as two lists containing the log likelihood's and values of theta at all iterations
    return theta0, theta_history, log_l_history
```

################# Do not write below this line #################

Check your hess_l implementation: hess_l applied to the theta_test (defined below) should provide a value for hess_l_test close to the target_value (defined below); in other words the error_test should be 0, up to machine error precision.

```python
target_value = np.array([[-0.14357024, -0.43836134,  0.18767787], \
                         [-0.43836134, -1.72295801,  0.61817001], \
                         [ 0.18767787,  0.61817001, -0.51247901]])
theta_test=np.array([-2,1,2])

hess_l_test  = hess_l(theta_test,x,y)
error_test=np.sum(np.abs(hess_l_test-target_value),axis=(0,1))

print("{:f}".format(error_test))
```

    2.879550

Now run Newton's method to perform logistic regression:

```python
# Initialize theta0
theta0 = np.zeros(x.shape[1])

# Run Newton's method
theta_final, theta_history, log_l_history = newton(theta0,x,y,grad_l,hess_l,1e-6)
print(theta_final)
```

    [-2.62050954  0.76037096  1.17194549]

Let's plot the log likelihood over iterations

```python
fig,ax = plt.subplots(num=2)

ax.set_ylabel('l(Theta)')
ax.set_xlabel('Iterations')
_=ax.plot(range(len(log_l_history)),log_l_history,'b.')
```

Plot the data and our decision boundary:

```python
df_x_pl=df_x.copy()
df_y_pl=df_y.copy()
df_x_pl.insert(0, "y", df_y)
df_x_pl["y"] = pd.to_numeric(df_x_pl["y"],downcast='signed')
df_x_pl.head()
```

      y
      x1
      x2

      0
      -1
      1.343250
      -1.331148

      1
      -1
      1.820553
      -0.634668

      2
      -1
      0.986321
      -1.888576

      3
      -1
      1.944373
      -1.635452

      4
      -1
      0.976734
      -1.353315

```python
# Generate vector to plot decision boundary
x1_vec = np.linspace(df_x_pl["x1"].min(),df_x_pl["x1"].max(),2);

# Plot raw data
sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);

# Plot decision boundary
plt.plot(x1_vec,(-x1_vec*theta_final[1]-theta_final[0])/theta_final[2], color="red");
```

################# Do not write above this line #################

Write now your considerations. Discuss in particular:
- the different convergence speed of the Newton's method, compared to Gradient Ascent
- the incurred cost for the different convergence speed

In comparing the efficiency of Newton's method and the gradient ascent to find the optimum theta in the logistic regression model, the runtime complexity for each iteration in Newton's method is higher than the runtime complexity for each iteration in gradient descent. The memory complexity for each iteration in gradient ascent is $O(p)$, while the memory complexity for each iteration in Newton's method is $O(p^2)$. It can be seen, that Newton's method requires more number of operations and more amount of memory than gradient ascent at each iteration. However, Newton's method requires much less iterations to converge to the minimum because it uses the knowledge of the second derivative at each iteration, while gradient ascent only uses the knowledge of the first derivative at each iteration. This is a known trade-off between complexity and the speed of convergence of Newton's method and gradient ascent. Newton's method is very fast to converge yet requires a high amount of complexity for computing the inverse of the Hessian matrix.

**Note**: As we have dig a bit more to understand the logic of the methods which can be used in logistic regression we have found out that, according to Thomas (2003), the fastest algorithms turn out to be conjugate gradient ascent and *quasi-Newton* algorithms, which far outstrip Iterative Scaling and its variants. It inspired us to use these methods as a future work.

################# Do not write below this line #################

## [7 points] Question 3: Logistic Regression with non linear boundaries

### (a) Polynomial features for logistic regression

Define new features, e.g. of 2nd and 3rd degrees, and learn a logistic regression classifier by using the new features, by using the Newton's optimization algorithm (or the gradient ascent one, if you like).

In particular, we would consider a polynomial boundary with equation:

$f(x_1, x_2) = c_0 + c_1 x_1 + c_2 x_2 + c_3 x_1^2 + c_4 x_2^2 + c_5 x_1 x_2 + c_6 x_1^3 + c_7 x_2^3 + c_8 x_1^2 x_2 + c_9 x_1 x_2^2$

We would therefore compute 7 new features: 3 new ones for the quadratic terms and 4 new ones for the cubic terms.

Create new arrays by stacking x (from the previous questions) and the new 7 features (in the order x1x1, x2x2, x1x2, x1x1x1, x2x2x2, x1x1x2, x1x2x2). In particular create x_new_quad by additionally stacking with x the quadratic features, and x_new_cubic by additionally stacking with x the quadratic and the cubic features.

```python
# First extract features x1 and x2 from x and reshape them to x1 vector arrays
x1 = x[:,1]
x2 = x[:,2]
x1 = x1.reshape(x1.shape[0], 1)
x2 = x2.reshape(x2.shape[0], 1)
print(x[:5,:]) # For visualization of the first 5 values
print(x1[:5,:]) # For visualization of the first 5 values
print(x2[:5,:]) # For visualization of the first 5 values
```

    [[ 1.          1.3432504  -1.3311479 ]
     [ 1.          1.8205529  -0.6346681 ]
     [ 1.          0.98632067 -1.8885762 ]
     [ 1.          1.9443734  -1.635452  ]
     [ 1.          0.97673352 -1.3533151 ]]
    [[1.3432504 ]
     [1.8205529 ]
     [0.98632067]
     [1.9443734 ]
     [0.97673352]]
    [[-1.3311479]
     [-0.6346681]
     [-1.8885762]
     [-1.635452 ]
     [-1.3533151]]

################# Do not write above this line #################

Your code here

```python
# Define features: x1x1, x2x2, x1x2, x1x1x1, x2x2x2, x1x1x2, x1x2x2

# first intialize the variables ...
x1x1 = [];  x2x2 = []; x1x2= []; x1x1x1= []; x2x2x2= []; x1x1x2= []; x1x2x2= [];

# ... then define them
for i in range(0, len(x1)):
    x1x1.append(np.square(x1[i]))
    x2x2.append(np.square(x2[i]))
    x1x2.append(x1[i]*x2[i])

    x1x1x1.append(np.power(x1[i], 3))
    x2x2x2.append(np.power(x2[i], 3))
    x1x1x2.append(x1[i]*x1[i]*x2[i])
    x1x2x2.append(x1[i]*x2[i]*x2[i])

# Stack the features together together with x, to define
# x_new_quad [x, x1x1, x2x2, x1x2]
# and x_new_cubic [x, x1x1, x2x2, x1x2, x1x1x1, x2x2x2, x1x1x2, x1x2x2]

x_new_quad = np.hstack ((x, x1x1, x2x2, x1x2))

x_new_cubic = np.hstack((x, x1x1, x2x2, x1x2, x1x1x1, x2x2x2, x1x1x2, x1x2x2))
```

################# Do not write below this line #################

Now use the Newton's optimization algorithm to learn theta by maximizing the log-likelihood, both for the case of x_new_quad and x_new_cubic.

Note: you may alternatively use gradient ascent, as in Question 1, if you like.

```python
# Initialize theta0, in case of quadratic features
theta0_quad = np.zeros(x_new_quad.shape[1])

# Run Newton's method, in case of quadratic features
theta_final_quad, theta_history_quad, log_l_history_quad = newton(theta0_quad,x_new_quad,y,grad_l,hess_l,1e-6)

# Initialize theta0, in case of quadratic and cubic features
theta0_cubic = np.zeros(x_new_cubic.shape[1])

# Run Newton's method, in case of quadratic and cubic features
theta_final_cubic, theta_history_cubic, log_l_history_cubic = newton(theta0_cubic,x_new_cubic,y,grad_l,hess_l,1e-6)

# check and compare with previous results
print(theta_final_quad)
print(theta_final_cubic)
```

    [-2.01513398  0.17762534  1.91642971  0.07051926  0.18229119 -0.1295329 ]
    [-15.80125013  11.84659121   8.30909256  -3.03962928   1.86524086
      -2.79099534   0.25914767   0.14781713   0.24456441  -0.30583232]

    <ipython-input-9-0d66dff45168>:3: RuntimeWarning: divide by zero encountered in log
      log_l= sum(y*np.log(h_theta) + (1-y)*np.log(1-h_theta))/len(y)
    <ipython-input-9-0d66dff45168>:3: RuntimeWarning: invalid value encountered in multiply
      log_l= sum(y*np.log(h_theta) + (1-y)*np.log(1-h_theta))/len(y)

```python
# Plot the log likelihood values in the optimization iterations, in one of the two cases.
fig,ax = plt.subplots(num=2)

ax.set_ylabel('l(Theta)')
ax.set_xlabel('Iterations')
_=ax.plot(range(len(log_l_history_cubic)),log_l_history_cubic,'b.')
```

### (b) Plot the computed non-linear boundary and discuss the questions

First, define a boundary_function to compute the boundary equation for the input feature vectors $x_1$ and $x_2$, according to estimated parameters theta, both in the case of quadratic (theta_final_quad) and of quadratic and cubic features (theta_final_cubic). Refer for the equation to the introductory part of Question 3.

################# Do not write above this line #################

Your code here

```python
def boundary_function(x1_vec, x2_vec, theta_final):

    x1_vec, x2_vec = np.meshgrid(x1_vec,x2_vec)

    if len(theta_final) == 6:
        # boundary function value for features up to quadratic
        c_0, c_1, c_2, c_3, c_4, c_5 = theta_final

        f = c_0 + c_1*x1_vec + c_2*x2_vec + c_3*np.square(x1_vec) +\
        c_4*np.square(x2_vec)+c_5*np.multiply(x1_vec, x2_vec)

    elif len(theta_final) == 10:
        # boundary function value for features up to cubic
        c_0, c_1, c_2, c_3, c_4, c_5, c_6, c_7, c_8, c_9 = theta_final

        f = c_0 + c_1*x1_vec + c_2*x2_vec + c_3*np.square(x1_vec) +\
        c_4*np.square(x2_vec)+c_5*np.multiply(x1_vec, x2_vec) +\
        c_6*np.power(x1_vec,3) + c_7*np.power(x2_vec,3) +\
        c_8*np.multiply(np.square(x1_vec), x2_vec) +\
        c_9*np.multiply(x1_vec, np.square(x2_vec))

    else:
        raise("Number of Parameters is not correct")

    return x1_vec, x2_vec, f
```

################# Do not write below this line #################

Now plot the decision boundaries corresponding to the theta_final_quad and theta_final_cubic solutions.

```python
x1_vec = np.linspace(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1,200);
x2_vec = np.linspace(df_x_pl["x2"].min()-1,df_x_pl["x2"].max()+1,200);

x1_vec, x2_vec, f = boundary_function(x1_vec, x2_vec, theta_final_quad)

sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);

plt.contour(x1_vec, x2_vec, f, colors="red", levels=[0])
```

```python
x1_vec = np.linspace(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1,200);
x2_vec = np.linspace(df_x_pl["x2"].min()-1,df_x_pl["x2"].max()+1,200);

x1_vec, x2_vec, f = boundary_function(x1_vec, x2_vec, theta_final_cubic)

sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);

plt.contour(x1_vec, x2_vec, f, colors="red", levels=[0])
```

################# Do not write above this line #################

Write now your considerations. Discuss in particular:
- What happens if you further increase the degree of the polynomial. How would the boundary change? Would you incur underfitting or overfitting?
- Look at the boundary of the quadratic and cubic polynomial features. Do they meet your expectations? Why? Consider in particular the classifier decision boundary in areas where there are fewer training data, i.e. where the classifier is supposed to generalize. Hint: Try changing the minimum and maximum limits in vectors x1_vec and x2_vec before plotting the boundaries.
- Look at the values of the original features, the 2nd and 3rd degree ones. Do you expect that normalizing the input features (either by setting their range to [-1,1] or by subtracting their mean and dividing by their standard deviation) would improve the classifier? Why yes or why not?

**Considerations:**

* In general when we increase the degree of the polynomial boundary, the boundary becomes more flexible. Therefore it will fit more closely the training points.
    For this reason if we increase the degree we would get a more winding red line, that will select more and more closely every training point. Consequently we might incur in overfitting.

* For the boundary of the quadratic polynomial features we expect to plot a parabolic graph (and that's exactly what we got). The problem with this polynomial boundary (as with all quadratic boundaries) is that while we might get a good classification in areas where we have a high density of training data, as we move in areas where the data is more scattered the graph will decrease rapidly, causing an underfittitng.

    Let's analyze this point more closely. Consider the quadratic polynomial boundary in the section of the plane $[5,8] \times  [-5, 1] $ :

```python
x1_vec = np.linspace(5,8, 200)
x2_vec = np.linspace(-5, 1, 200)

x1_vec, x2_vec, f = boundary_function(x1_vec, x2_vec, theta_final_quad)

sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);
plt.contour(x1_vec, x2_vec, f, colors="red", levels=[0])
```

 From the graph above we can clearly see that the points on the right half of the plane will not be detected by the quadratic polynomial boundary.
    One way to fix this problem is to add to the quadratic component a cubic one:

```python
x1_vec = np.linspace(5,8, 200)
x2_vec = np.linspace(-5, 1, 200)

x1_vec, x2_vec, f = boundary_function(x1_vec, x2_vec, theta_final_cubic)

sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);
plt.contour(x1_vec, x2_vec, f, colors="red", levels=[0])
```

* Let's explore the ranges of the training data, the features we have started with have pretty similar ranges:

```python
print(max(x1) - min(x1))
print(max(x2) - min(x2))
```

    [7.13460119]
    [7.8193692]

When we square them (or elevate them  to the third power), obviously the ranges start increasing rapidly. For example:

```python
print(max(x2x2) - min(x2x2))
print(max(x1x1x1) - min(x1x1x1))
```

    [16.0517872]
    [457.30830535]

Therefore we end up with features at different scales.
In general having features at similar scales is good practice, since we don't want the existence of some *privileged* axis, meaning that a unit increase along one axis counts more than the others.

In particular in this code we are using the Newton's optimization algorithm to calculate `theta_final`: this algorithm uses the euclidean distance to calculate each iteration, meaning that the features with higher magnitude will weigh in more in the calculation of `theta_final`.

################# Do not write below this line #################

## [10 points, extra, not compulsory] Question 4: Gaussian Discriminant Analysis

### (a) Review and implement classification with Gradient Discriminant Analysis (GDA)

Recall the assumptions for GDA

$\begin{aligned} y & \sim \operatorname{Bernoulli}(\phi) \\ x \mid y=0 & \sim \mathcal{N}\left(\mu_{0}, \Sigma\right) \\ x \mid y=1 & \sim \mathcal{N}\left(\mu_{1}, \Sigma\right) \end{aligned}$

Recall the probability distributions:

$\begin{aligned} p(y) &=\phi^{y}(1-\phi)^{1-y} \\ p(x \mid y=0) &=\frac{1}{(2 \pi)^{d / 2}|\Sigma|^{1 / 2}} \exp \left(-\frac{1}{2}\left(x-\mu_{0}\right)^{T} \Sigma^{-1}\left(x-\mu_{0}\right)\right) \\ p(x \mid y=1) &=\frac{1}{(2 \pi)^{d / 2}|\Sigma|^{1 / 2}} \exp \left(-\frac{1}{2}\left(x-\mu_{1}\right)^{T} \Sigma^{-1}\left(x-\mu_{1}\right)\right) \end{aligned}$

Overall, the parameters of the model are $\phi, \Sigma, \mu_{0}$ and $\mu_{1}$ and the log-likelihood is given by:

$\begin{aligned} \ell\left(\phi, \mu_{0}, \mu_{1}, \Sigma\right) &=\log \prod_{i=1}^{n} p\left(x^{(i)}, y^{(i)} ; \phi, \mu_{0}, \mu_{1}, \Sigma\right) \\ &=\log \prod_{i=1}^{n} p\left(x^{(i)} \mid y^{(i)} ; \mu_{0}, \mu_{1}, \Sigma\right) p\left(y^{(i)} ; \phi\right) \end{aligned}$

Parameters may be estimated by maximum likelihood estimate as follows:

$\begin{aligned} \phi &=\frac{1}{n} \sum_{i=1}^{n} 1\left\{y^{(i)}=1\right\} \\ \mu_{0} &=\frac{\sum_{i=1}^{n} 1\left\{y^{(i)}=0\right\} x^{(i)}}{\sum_{i=1}^{n} 1\left\{y^{(i)}=0\right\}} \\ \mu_{1} &=\frac{\sum_{i=1}^{n} 1\left\{y^{(i)}=1\right\} x^{(i)}}{\sum_{i=1}^{n} 1\left\{y^{(i)}=1\right\}} \\ \Sigma &=\frac{1}{n} \sum_{i=1}^{n}\left(x^{(i)}-\mu_{y^{(i)}}\right)\left(x^{(i)}-\mu_{y^{(i)}}\right)^{T} \end{aligned}$

Consider the data variables as from the previous questions, x and y.
For this question, we would select features $x^{(i)}_1$ and $x^{(i)}_2$ into the new varible x12, dropping the $x_0=1$ convention.

```python
# First extract the sole features x1 and x2 from x into an x12 array and reshape it to x2 vector arrays
x12 = x[:,1:]
print(x[:5,:]) # For visualization of the first 5 values
print(x12[:5,:]) # For visualization of the first 5 values
print(y[:5]) # For visualization of the first 5 values
```

    [[ 1.          1.3432504  -1.3311479 ]
     [ 1.          1.8205529  -0.6346681 ]
     [ 1.          0.98632067 -1.8885762 ]
     [ 1.          1.9443734  -1.635452  ]
     [ 1.          0.97673352 -1.3533151 ]]
    [[ 1.3432504  -1.3311479 ]
     [ 1.8205529  -0.6346681 ]
     [ 0.98632067 -1.8885762 ]
     [ 1.9443734  -1.635452  ]
     [ 0.97673352 -1.3533151 ]]
    [0. 0. 0. 0. 0.]

Now code up the equations above to learn the GDA parameters from the training data.

################# Do not write above this line #################

Your code here

```python
import math

def compute_phi(y):

    phi = 1/len(y)*sum(y)

    return phi

def compute_mu0(x, y):

    numerator=np.zeros((2,))
    sum = list(y).count(0)

    for i in range (len(y)):
        if(y[i]==0):
            numerator+=x[i]
    mu0 = numerator/sum

    return mu0

def compute_mu1(x, y):

    numerator=np.zeros((2,))
    sum = list(y).count(1)

    for i in range (len(y)):
        if(y[i]==1):
            numerator+=x[i]
    mu1=numerator/sum

    return mu1

def compute_sigma(x, y, mu0, mu1):
    mu0=mu0.reshape(2,1)
    mu1=mu1.reshape(2,1)
    sigma=np.zeros((2,2))
    for i in range (len(y)):
        if(y[i]==1):
            sigma+=(x12[i].reshape((1,2)).T-mu1).dot((x12[i].reshape((1,2)).T-mu1).T)
        else:
            sigma+=(x12[i].reshape((1,2)).T-mu0).dot((x12[i].reshape((1,2)).T-mu0).T)
    sigma = sigma/len(y)
    return sigma

# computing p(x|y) for the Bayes rule
def p_x_given_y(x,mu,sigma):

    d = 2
    det = np.linalg.det(sigma)
    det_s = det**0.5
    m_pi =(2*math.pi)**(d/2)
    c = m_pi*det_s
    d = 1/c

    a = ((x-mu).T).dot(np.linalg.inv(sigma))
    b = a.dot(x-mu)
    e = -(0.5*b)
    p_x_giv_y = d*np.exp(e)

    return p_x_giv_y
```

################# Do not write below this line #################

```python
# Support functions

# Function p(y) for applying the Bayes rule
def p_y(y,phi):
    if y==1: return phi
    else: return 1-phi
```

```python
# Now estimate the GDA parameters
phi = compute_phi(y)
mu0 = compute_mu0(x12,y)
mu1 = compute_mu1(x12,y)
sigma = compute_sigma(x12, y, mu0, mu1)

phi,mu0,mu1,sigma
```

    (0.494949494949495,
     array([ 2.93068144, -1.44476469]),
     array([4.93616668, 0.76771897]),
     array([[ 2.68174521, -0.46404053],
            [-0.46404053,  1.88367821]]))

Now that we know both the distributions of $p(y)$ and $p(x|y)$, we may estimate the posterior probability for an input $x$ via Bayes Rule:

\begin{equation}
p(y \mid x)=\frac{p(x \mid y) p(y)}{p(x)}
\end{equation}
where $p(x)$ is the same for both classes and may be disregarded.

This is used to classify each data point, by comparing $p(y=1|x) \propto p(x \mid y=1) p(y=1)$ and $p(y=0|x) \propto p(x \mid y=0) p(y=0)$.

```python
selected_example=1
print('Selected example =', x12[selected_example,:])

# compute p(y=0|x) ~ p(x|y=0)*p(y=0)  &  p(y=1|x) ~ p(x|y=1)*p(y=1)

# y=0
print('p(y=0|x) ~', p_x_given_y(x12[selected_example,:],mu0,sigma)*p_y(0,phi))

# y=1
print('p(y=1|x) ~', p_x_given_y(x12[selected_example,:],mu1,sigma)*p_y(1,phi))
```

    Selected example = [ 1.8205529 -0.6346681]
    p(y=0|x) ~ 0.026129116841520316
    p(y=1|x) ~ 0.0020617882761784934

### (b) Plot the estimated likelihood functions, the decision boundary and write your remarks

```python
def boundary_gda(x1_vec, x2_vec, mu0, mu1, sigma, phi):

    x1_vec, x2_vec = np.meshgrid(x1_vec,x2_vec)

    f=np.zeros(x1_vec.shape)

    for i in range(f.shape[0]):
        for j in range(f.shape[1]):
            x12_ij = np.array( (x1_vec[i,j],x2_vec[i,j]) )
            f[i,j] = p_x_given_y(x12_ij,mu1,sigma)*p_y(1,phi) - p_x_given_y(x12_ij,mu0,sigma)*p_y(0,phi)

    return x1_vec, x2_vec, f

x1_vec = np.linspace(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1,200);
x2_vec = np.linspace(df_x_pl["x2"].min()-1,df_x_pl["x2"].max()+1,200);

x1_vec, x2_vec, f = boundary_gda(x1_vec, x2_vec, mu0, mu1, sigma, phi)
```

```python
from matplotlib import cm
from scipy.stats import multivariate_normal

# Define Gaussian pdfs
rv0 = multivariate_normal(mu0, sigma)
rv1 = multivariate_normal(mu1, sigma)

pairx12 = np.dstack((x1_vec, x2_vec))

# plot pdf contours
plt.contourf(x1_vec, x2_vec, rv0.pdf(pairx12),alpha=0.5,cmap=cm.Reds)
plt.contourf(x1_vec, x2_vec, rv1.pdf(pairx12),alpha=0.35,cmap=cm.Blues)

# Add the GDA classification to the plot
sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl);
plt.contour(x1_vec, x2_vec, f, colors="red", levels=[0])

plt.ylim(df_x_pl["x2"].min()-1,df_x_pl["x2"].max()+1)
plt.xlim(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1)
```

```python
# For comparison with the Logistic Regression boundary

# Generate vector to plot decision boundary
x1vec = np.linspace(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1,2)

# Plot raw data
sns.scatterplot(x="x1", y="x2", hue="y", data=df_x_pl)

# Plot decision boundary
log, = plt.plot(x1vec,(-x1vec*theta_GA[1]-theta_GA[0])/theta_GA[2], color="red")
gda = plt.contour(x1_vec, x2_vec, f, colors="green", levels=[0])

plt.legend([log, gda.collections[0]],['logistic regression','GDA'])

plt.ylim(df_x_pl["x2"].min()-1,df_x_pl["x2"].max()+1)
plt.xlim(df_x_pl["x1"].min()-1,df_x_pl["x1"].max()+1)

# plt.legend()
plt.show()
```

Write now your considerations. Discuss in particular:
- Gaussian Discriminant Analysis makes stronger modelling assumptions than logistic regression. When may this be helpful and where may this be harmful?

#### Conclusion marks:

When the assumption of Gaussian Discriminant Analysis (GDA) hold, for example if $P(x|y)$ is multivariate gaussian with shared $Σ$, the GDA will perform better than any models, including logistic regression. \
On the other hand, logistic regression is more robust and less sensative to model assumptions.
In this model we have used the same covariance matrix Σ for both classes. This means that whatever my normal distribution looks like for one class - no matter how tall/fat/slanty it is - we assumed that the other class' covariance matrix looks exactly like that as well. \
This is a very strong assumption which can certainly be false when Σ varies for different classes and it might not be applicable for other cases.

Credits for material: Andrew Ng, C. Combier


Lorenz Equations

#### Lorenz Equations
\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}

#### Cauchy-Schwarz Inequality
\begin{equation*}
\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
\end{equation*}

```python
!wget https://github.com/jupyter/notebook/blob/master/docs/source/examples/Notebook/Typesetting%20Equations.ipynb
```

    --2017-08-07 16:43:42--  https://github.com/jupyter/notebook/blob/master/docs/source/examples/Notebook/Typesetting%20Equations.ipynb
    Resolving github.com (github.com)... 192.30.253.112, 192.30.253.113
    Connecting to github.com (github.com)|192.30.253.112|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: unspecified [text/html]
    Saving to: 'Typesetting Equations.ipynb'

        [ <=>                                   ] 50,756      --.-K/s   in 0.002s

    2017-08-07 16:43:42 (24.1 MB/s) - 'Typesetting Equations.ipynb' saved [50756]

```python

```


## Rosenbrock

The definition ca be found in <cite data-cite="rosenbrock"></cite>. It is a non-convex function, introduced by Howard H. Rosenbrock in 1960 and also known as Rosenbrock's valley or Rosenbrock's banana function.

**Definition**

\begin{align}
\begin{split}
f(x) &=& \sum_{i=1}^{n-1} \bigg[100 (x_{i+1}-x_i^2)^2+(x_i - 1)^2 \bigg] \\
&&-2.048 \leq x_i \leq 2.048 \quad i=1,\ldots,n
\end{split}
\end{align}

**Optimum**

$$f(x^*) = 0 \; \text{at} \; x^* = (1,\ldots,1) $$

**Contour**

```python
import numpy as np
from pymoo.factory import get_problem, get_visualization

problem = get_problem("rosenbrock", n_var=2)
get_visualization("fitness-landscape", problem, angle=(45, 45), _type="surface").show()
```

```python

```


# The Koopman model - Robinson Crusoe economy

**What is the Robinson Crusoe economy?**

Imaging the classic everyday first world problem. You are on your way to a tropical paradise for the holidays, but on the way your plane crashes and leaves you stranded on a deserted island. As a human being, you have to eat in order to survive and the island only has two kinds of food supplies. You can either use your time on fishing or pick bananas. Each activity consumes time of the day, which you would rather spend on relaxation/leisure (since this technically is your holiday). As a rational utility optimizing individual with understanding of economic efficient decision making, you now have to decide how to allocate your time in order to achieve the maximum amount of utility (remeber it is you holiday, so you should get the most out of it).

# The model for optimizing your holiday

**The stats of you character**

The first question is how efficient your are at producing each of the two goods. The goods are given as:
1. Bananas (B)
* Fish (F)

The production of the two goods only require time as input, which leads to an alternative cost of lost leisure. Time will be abbreviated as L, while the inputs are abbreviated as $L_F$ and $L_B$. The production function for each good is given as follows:

$$ B = \sqrt{L_B} ,  F = \frac{\sqrt{L_F}}{2} $$

The utility you gain from the total consumption of each good is expressed by the utility function

$$ U(B,F) = \sqrt{B}\sqrt{F} $$

The final part to consider is the amount of inputs you have available at you disposal. A day consist of 24 hours, so the total time $L$ most be equal to 24. These 24 hours has to be distributed between production of fish and bananas and some type of leisure $l$ (a person has to sleep some time at least). The total amount of available inputs can then be characterized as
$$ L = L_B + L_F + l = 24 $$

Now that the model is presented, we begin to solve for the pareto efficient allocation of time between the two goods.

# The pareto efficient allocation

First we need to determine **the Production Possibilities Frontier (PPF)**, which defines all the efficiently combinations to produce of fish and bananas. In other words, it is the frontier where it is not possible to produce more of both goods by reallocating time, $L$.

We start by importing the relevant **packages**

```python
# Import relevant packages
import sympy as sm
from sympy.plotting import plot_implicit
import math as m
import matplotlib.pyplot as plt
import numpy as np
```

We then define all **symbols** for our analytical solution

```python
# Symbols for the Sympy optimization
l    = sm.symbols('l')
LF   = sm.symbols('LF')
LB   = sm.symbols('LB')
B    = sm.symbols('B')
F    = sm.symbols('F')
U    = sm.symbols('U')
L    = sm.symbols('L')
T    = sm.symbols('T')
PPF  = sm.symbols('PPF')
```

Now we use the symbols to set up our **model** in Sympy

```python
# leisure (time for sleep and fun)
l = 8
# Total amount of time available for production
L = 24-l

# Defining time spend on producing fish (it is arbitrary whether we isolate LF or LB)
LF = L - LB

# The production functions
Bprod = sm.Eq(B,LB**0.5)
Fprod = sm.Eq(F,LF**0.5/2)

# Utility function
U = B**0.5*F**0.5
Utility = sm.Eq(U,B**0.5*F**0.5)
```

The **PPF is then found** by isolating the time spend on picking bananas $L_B$ in both production functions, setting them equal and move all variables to the right hand side.

```python
# Solution when isolation LB in Banana production function
solB = sm.solve(Bprod,LB)
# Solution when isolation LB in Fishing production function
solF = sm.solve(Fprod,LB)

# The Production Possibilities Frontier
T = solB[0]-solF[0]
PPF = sm.Eq(0,T)

# Plot of the PPF from the symbolic definition (here L is set to 16)
plot_implicit((T), x_var=(B, 0, 5), y_var=(F, 0, 3), xlabel='Number of banans', ylabel='Number of fishs');
```

**The Pareto Efficient allocation** is the point on the PPF, where the utility is maximized. The problem is stated as follows

\\[
\begin{eqnarray*}
& \max_{B,F}U(B,F)\\
& \text{s.t.}\\
& PPF(B,F) = 0
\end{eqnarray*}
\\]

To solve the problem, **the Lagrangian is constructed and solved** w.r.t. B, F and $\lambda$

$$ \max_{B,F,\lambda}\mathcal{L}(B,F,\lambda) = \sqrt{B}\sqrt{F}+\lambda(PPF(B,F)-0) $$

**The first order conditions** (FOC) is calculated as follows

```python
# The symbol for lambda is defined as 'lam'
lam  = sm.symbols('lam')
# The Lagrangian is defined
Lag = B**0.5*F**0.5+lam*(T-0)

# The FOC for each variable is found
# FOC for Bananas
diffB = Lag.diff(B)
# FOC for Fish
diffF = Lag.diff(F)
# FOC for lambda
difflam = Lag.diff(lam)

# The FOC's are then printed
print('FOC for Bananas: {} = 0'.format(diffB))
print('FOC for Fish:    {} = 0'.format(diffF))
print('FOC for lambda:  {} = 0'.format(difflam))
```

    FOC for Bananas: 0.5*B**(-0.5)*F**0.5 + 2*B*lam = 0
    FOC for Fish:    0.5*B**0.5*F**(-0.5) + 8.0*F*lam = 0
    FOC for lambda:  B**2 + 4.0*F**2 - 16.0 = 0

**The equation system with three unknowns**

The way we find the pareto efficient allocation of the two goods, is to **solve the equation system** of the three FOC's equations with three unknown variables. The equation system is given as

\\[
\begin{eqnarray*}
\frac{\partial\mathcal{L}(B,F,\lambda)}{\partial B}      & = & \frac{1}{2}\frac{\sqrt{F}}{\sqrt{B}} + 2B\lambda & = & 0\\
\frac{\partial\mathcal{L}(B,F,\lambda)}{\partial F}      & = & \frac{1}{2}\frac{\sqrt{B}}{\sqrt{F}} + 8F\lambda & = & 0\\
\frac{\partial\mathcal{L}(B,F,\lambda)}{\partial\lambda} & = & B^2 + 4F^2 - L & = & 0
\end{eqnarray*}
\\]

**The solution is found in 4 steps:**
1. First we use the first two equation to define lambda with B and F
* Lambda is then inserted into the second equation to get an expression for B, defined by F
* The expression for B is then inserted into the third equation to isolate for F
* The expression for F is finally inserted into the expression for B

The final expression for B and F is the pareto efficient allocation of the two goods, given the amount of time you will spend on total production.

**Step 1** is to set eq. 1 and 2 equal and find lambda defined by B and F:

```python
# Setting eq. 1 and 2 equal
e1 = sm.Eq(diffB,diffF)

# Solve the equation for lambda
lams = sm.solve(e1, lam)

# Save the first result from the list as a variable and print it
lams = lams[0]
print('Lambda = {}'.format(lams))
```

    Lambda = 0.25*(B - F)/(sqrt(B)*sqrt(F)*(B - 4.0*F))

**Step 2** is to insert lambda into equation 2 in our system and solve for B:

```python
# Lambda inserted into equation 2
e2 = sm.Eq(0.5*B**(-0.5)*F**0.5 + 2*B*lams,0)

# B is defined by F
Bs = sm.solve(e2, B)

# Save the result in a variable and print it
Bs = Bs[1]
print('B = {}'.format(Bs))
```

    B = 2.0*F

**Step 3** is to define F by inserting the expression for B into equation 3

```python
# we want an analytical solution, so we define a new variable for total production input, L2
L2 = sm.symbols('L2')

# B is inserted into equation 3
e3 = sm.Eq(Bs**2 + 4*F**2 - L2,0)

# We solve for F
F_eff = sm.solve(e3, F)
F_eff = F_eff[1]
print('F* = {}'.format(F_eff))
```

    F* = 0.353553390593274*sqrt(L2)

**Step 4** uses the efficient amount of F to find the efficient amount of B

```python
# Insert efficient amount of F
B_eff = 2.0*F_eff

# Print the result
print('B* = {}'.format(B_eff))
```

    B* = 0.707106781186548*sqrt(L2)

Finally we print both definitions to get a clear view of the results

```python
print('B* =', B_eff)
print('F* =', F_eff)
```

    B* = 0.707106781186548*sqrt(L2)
    F* = 0.353553390593274*sqrt(L2)

The pareto efficient allocation of the goods on the Production Function Frontier is defined as a function of the total input for production, known as the time, $L$.

\\[
\begin{eqnarray*}
Bananas & = & 0.707106781186548\cdot\sqrt{L} & \approx 0.71\cdot\sqrt{L}\\
Fish    & = & 0.353553390593274\cdot\sqrt{L} & \approx 0.35\cdot\sqrt{L}
\end{eqnarray*}
\\]

```python
L2 = 16
B2 = 0.707106781186548*sm.sqrt(L2)
F2 = 0.353553390593274*sm.sqrt(L2)
print('Pareto efficient amount of:')
print('Bananas =', B2)
print('Fish    =', F2)
```

    Pareto efficient amount of:
    Bananas = 2.82842712474619
    Fish    = 1.41421356237310

**Other combinations:** It would also be interesting to see how the allocation of the goods are when another amount of production input is used. Below is the allocations calculated for different amounts of hour input.

```python
Labor = np.linspace(1,16,7)
for L2 in Labor:
    B2 = 0.707106781186548*sm.sqrt(L2)
    F2 = 0.353553390593274*sm.sqrt(L2)
    print(f'for an hour input of {L2:} the production of bananas = {B2:.1f} and fish = {F2:.1f}' )
```

    for an hour input of 1.0 the production of bananas = 0.7 and fish = 0.4
    for an hour input of 3.5 the production of bananas = 1.3 and fish = 0.7
    for an hour input of 6.0 the production of bananas = 1.7 and fish = 0.9
    for an hour input of 8.5 the production of bananas = 2.1 and fish = 1.0
    for an hour input of 11.0 the production of bananas = 2.3 and fish = 1.2
    for an hour input of 13.5 the production of bananas = 2.6 and fish = 1.3
    for an hour input of 16.0 the production of bananas = 2.8 and fish = 1.4

**Plot the final result:** The pareto efficent point on the PPF and the utility function is illustrated with their tangent line

```python
# Define symbols for the plot
B = sm.symbols('B')
F = sm.symbols('F')
k = sm.symbols('k')

# We start by finding the tangent to the pareto efficient point on PPF
MRS = U.diff(B)/U.diff(F)    # Marginal Rate of Substitution (MRS) gives the slope of the tangent in the point
MRS = sm.lambdify([B,F],MRS) # Lambdify MRS to ease the process of inserting the optimal point

Tan = sm.Eq(F2,k-0.5*B2)                  # The tangent is defined by this equation for the efficient point
k   = sm.solve(Tan,k)                     # Now we solve for the constant of the equation
Tan = sm.lambdify(B,-MRS(B2,F2)*B + k[0]) # The final equation lambdifyed to ease the process of drawing the equation

# The grid for the plot is constructed
fig, ax = plt.subplots(1,1)
xax,yax = np.linspace(0,6), np.linspace(0,4)
B, F = np.meshgrid(xax,yax)
ax.set_xlabel('Number of Bananas')
ax.set_ylabel('Number of Fish')

# Contour of the PPF
ax.contour(B, F, B**2 + 4*F**2 - L2, [0], colors=('b',));

# Contour of the utility function
ax.contour(B, F, B**0.5*F**0.5, [2], colors=('k'));

# Points array for the plot of the tangent
y = np.array([Tan(0),Tan(1),Tan(2),Tan(3),Tan(4),Tan(5),Tan(5.5)])
x = np.array([0,1,2,3,4,5,5.5])
ax.plot(x,y)

# We insert a point for the pareto efficient allocation of the goods
ax.plot([B2],[F2],"o")
ax.set_aspect('equal','datalim')
```

**Conclusion:**
The blue curve is the PPF, the black curve is the utility function and the tangent to the orange point is the tangent where $MRS=MRT$. The orange point is then the optimal allocation of fish and banans. So what did we learn here? If you think this model represent your productivity, you will optimize you utility on the island by using 16 hours on:
* Fishing 1,5 fish
* Pick almost 3 banans (banana trees are hard to scale)
* Probably get a hell of a lot of mosquito bites

The next part will look at how comparative advantages between agents is able to raise the level of utility.

# Comparative advantage

An common extention to the Koopman model is to include an extra person with a different production function. Each person will be better at producing one of the two goods and by trading between each other, they can recieve a higher utility, given that they have a **comparative advantage**.

Another way of looking at this type of extension is to see the increase in productivity from another person on the island, as an increase in the technology level in a simple Cobb-Douglas production function.

$$ c = A\cdot k^{1-\alpha}\cdot L^{\alpha} $$

The model is mixed up in this part, so instead of choosinn between two goods, the amount of the goods is collected in a varaible of total consumption. The agent in this model now gain utility from total consumption, $c$ and leisure $1-L$, where $L$ is the share of daily hours used for prodution.

Before we get into the model, we start by looking at the new production function. We start by define a function for the level of produced consumption:

```python
# Function for the level of consumption produced
def consum(k,L,alpha=0.50,A=1):
    return A*k**(1-alpha)*L**alpha

# Setting initial values
N = 100 # number of elements
x_max = 10 # maximum value

# Allocate numpy arrays
shape_tuple = (N,N)
k_values = np.empty(shape_tuple)
L_values = np.empty(shape_tuple)
c_values = np.empty(shape_tuple)

# Data points
for i in range(N): # 0,1,...,N-1
    for j in range(N): # 0,1,...,N-1
        k_values[i,j] = (i/(N-1))*x_max # in [0,x_max]
        L_values[i,j] = (j/(N-1))*x_max # in [0,x_max]
        c_values[i,j] = consum(k_values[i,j],L_values[i,j],alpha=0.5,A=1)
```

The function above fills three arrays with values for the capital, labor and consumption. The parameter $\alpha$ is set to $0.5$ and technology, $A=1$. The production function is illustrated below in 3D. Later we are gonna simplify the task by setting $k=1$. If you are stranded on a deserted island, you do not have any capital to work with, so the production function will be normalized.

```python
%matplotlib widget
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm # for colormaps

# Plotting the figure
fig = plt.figure() # create the figure
ax = fig.add_subplot(1,1,1,projection='3d') # create a 3d axis in the figure
ax.plot_surface(k_values,l_values,c_values,cmap=cm.magma); # create surface plot in the axis

# Adding labels
ax.set_xlabel('$Capital$')
ax.set_ylabel('$Labor$')
ax.set_zlabel('$Consumption$')
plt.title('Production function for goods of consumption')

# Adjusting the axis
ax.invert_xaxis()
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
```

    FigureCanvasNbAgg()

```python
# This has to run, so the next illustrations are not interactive.
%matplotlib inline
```

The graph gives a nice presentation of our production function in an interactive plot. The model for the rest of the notebook only looks at a "slice" of the 3D plot, to be exact, the slice is in the layer where $k=1$.

# Presenting the new model

The model consist of a total consumption level, given by the production function, a share of time used for leisure and a utility function, where leisure and consumption provide utility for the agent.

\\[
\begin{eqnarray*}
Production:\; & c & = & A\cdot L^{\alpha}\\
Utility:\;    & U & = & ln(c) + ln(1-L)\\
Leisure:\;    & l & = & 1-L
\end{eqnarray*}
\\]

We write up the model in the following way:

```python
# Symbols for the new model is defined
L     = sm.symbols('L') # Labor in production
A     = sm.symbols('A') # Technology
c     = sm.symbols('c') # Consumption
lamb  = sm.symbols('lamb') # Lambda
alpha = sm.symbols('alpha') # Alpha

# The functions of the model
f = A*L**alpha            # Production function for goods of consumption
uf = sm.ln(c)+sm.ln(1-L)  # Utility function
```

**The problem to be solved:**

The optimization problem the agent on the deserted island now faces is how much time to allocate for leisure and how much for production of consumption goods. The effects of comparative advantage will be clear when we start to adjust on the size for $A$, but first we solve the model analytically. The agent has to maximize the utility w.r.t leisure and consumption, where the level of consumption can not exceed the produced amount:

\\[
\begin{eqnarray*}
& \max_{L,c}U(L,c)\\
& \text{s.t.}\\
& A\cdot L^{\alpha} = c
\end{eqnarray*}
\\]

To solve the problem, **the Lagrangian is constructed and solved** w.r.t. L, c and $\lambda$

$$ \max_{L,c,\lambda}\mathcal{L}(L,c,\lambda) = ln(c) + ln(1-L) + \lambda(A\cdot L^{\alpha}-c) $$

The **FOC's** for each variable is defined below:

```python
# The Lagrangian
Lag = uf + lamb*(f - c)

# The FOC for each variable is found

# FOC for Consumption
dc    = Lag.diff(c)
# FOC for Labor
dL    = Lag.diff(L)
# FOC for lambda
dlamb = Lag.diff(lamb)

# The FOC's are then printed
print('FOC for Consumption: {} = 0'.format(dc))
print('FOC for Labor:       {} = 0'.format(dL))
print('FOC for Lambda:      {} = 0'.format(dlamb))
```

    FOC for Consumption: -lamb + 1/c = 0
    FOC for Labor:       A*L**alpha*alpha*lamb/L - 1/(-L + 1) = 0
    FOC for Lambda:      A*L**alpha - c = 0

**The equation system with three unknowns**

The optimal choice of consumption and leisure, is found by **solving the equation system** of the three FOC's equations with three unknown variables. The equation system is given as

\\[
\begin{eqnarray*}
\frac{\partial\mathcal{L}(L,c,\lambda)}{\partial L}      & = & \frac{1}{c} - \lambda & = & 0\\
\frac{\partial\mathcal{L}(L,c,\lambda)}{\partial c}      & = & A\cdot L^{\alpha}\cdot\alpha\frac{\lambda}{L} - \frac{1}{1-L} & = & 0\\
\frac{\partial\mathcal{L}(L,c,\lambda)}{\partial\lambda} & = & A\cdot L^{\alpha} - c & = & 0
\end{eqnarray*}
\\]

The system is alot easier to solve, so a single line of code is all we neew for this.

```python
# The equation system with three equations and three unknown is solved by
Lag_sol = sm.solve([dc,dL,dlamb],c,L,lamb)

# The solution to the equation system then becomes
print('c*      =  A*((alpha + 1)/alpha)**(-alpha)')
print('L*      =  (((alpha + 1)/alpha)**(-alpha))**(1/alpha)')
print('Lambda* =  (((alpha + 1)/alpha)**alpha/A)')
```

    c*      =  A*((alpha + 1)/alpha)**(-alpha)
    L*      =  (((alpha + 1)/alpha)**(-alpha))**(1/alpha)
    Lambda* =  (((alpha + 1)/alpha)**alpha/A)

**Simplify the results:**

The optimal allocation for leisure and consumption is simplified to the following expressions:

\\[
\begin{eqnarray*}
c^{*} & = & A\left(\frac{\alpha}{1+\alpha}\right)^{\alpha}\\
L^{*} & = & \frac{\alpha}{1+\alpha}
\end{eqnarray*}
\\]

It is clear to see that the amount of leisure only depends on a parameter, so it will be constant. This constant level stems from the substitution and income effect canceling out. If we become more productive, we do not have to work as much, because we are wealthier with the same amount of labor hours. On the other hand, when we become more productive, we like to work more to increase our utility even further. These effect offsets each other perfectly in this model.

The **comparative advantage** is present in the optimal level of consumption. If technology increase (e.i. a new and more productive agent enters the economy, så the production increases through trade), it is possible to increase the level of consumption and thereby the level of utility in optimium. In the next part, the optimal allocation of leisure and consumption is calcualted for different levels of technology.

```python
# The grid for the plot is constructed
fig, ax = plt.subplots(1,1)
xax,yax = np.linspace(0.1,0.9), np.linspace(0.01,25)
L, c = np.meshgrid(xax,yax)

# Labels are added
ax.set_xlabel('Share of hours as labor')
ax.set_ylabel('Amount of consumption')

# Parameter levels
alpha = 0.5
A1    = 7
A2    = 19
A25   = 32

# Contour of the utility function
U = np.log(c)+np.log(1-L)
ax.contour(L, c, U, [1,2,2.5], colors=('b'));

# Production functions are calculated for each level of A
def prod1(x):
        return A1*x**alpha
def prod2(x):
        return A2*x**alpha
def prod25(x):
        return A25*x**alpha

# Consumption level for each production function
prod_funk1 = prod1(xax)
prod_funk2 = prod2(xax)
prod_funk25 = prod25(xax)

# The points of optimal leisure and consumption are calculated
L_eff = (((alpha + 1)/alpha)**(-alpha))**(1/alpha)
c1  = A1*((alpha + 1)/alpha)**(-alpha)
c2  = A2*((alpha + 1)/alpha)**(-alpha)
c25 = A25*((alpha + 1)/alpha)**(-alpha)

# The plots and optimums are drawn
ax.plot(xax,prod_funk1,"--""r")   # A = 7
ax.plot(xax,prod_funk2,"--""r")   # A = 19
ax.plot(xax,prod_funk25,"--""r")  # A = 32
ax.plot(L_eff,c1,"p""g");
ax.plot(L_eff,c2,"p""g");
ax.plot(L_eff,c25,"p""g");
```

**Conclusion:** The graph above shows three different economies with different levels of technology. With a higher level of technology, it is possilbe to obtain a higher level of consumption. This illustrates how the comparative advantage of more specialiced agents in the economy works. If agents produce the good they are best at producing, the level of utility increases through a higher production frontier (the red stippled lines). This is a more general way of showing how comparative advantages works. So if you ever get stranded on a deserted island, always remeber to bring friends.


# Energy System Modelling - Tutorial II

**Imports**

```python
import numpy as np
import numpy.linalg
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('bmh')
%matplotlib inline
```

***
## Problem II.1

**(a) Compile the nodes list and the edge list.**
> **Remark:** While graph-theoretically both lists are unordered sets, let's agree on an ordering now which can serve as basis for the matrices in the following exercises: we sort everything in ascending numerical order, i.e. node 1 before node 2 and edge (1,2) before (1,4) before (2,3).

```python

```

***
**(b) Determine the order and the size of the network.**

```python

```

***
**(c) Compute the adjacency matrix $A$ and check that it is symmetric.**
> In graph theory and computer science, an adjacency matrix is a square matrix used to represent a finite graph. The elements of the matrix indicate whether pairs of vertices are adjacent or not in the graph.

Adjacency Matrix:

```python

```

Check for symmetry:

```python

```

***
**(d) Find the degree $k_n$ of each node $n$ and compute the average degree of the network.**
> In graph theory, the degree (or valency) of a vertex of a graph is the number of edges incident to the vertex, with loops counted twice.

```python

```

***
**(e) Determine the incidence matrix $K$ by assuming the links are always directed from smaller-numbered node to larger-numbered node, i.e.\ from node 2 to node 3, instead of from 3 to 2.**
> The unoriented incidence matrix (or simply incidence matrix) of an undirected graph is a $n \times m$ matrix $B$, where n and m are the numbers of vertices and edges respectively, such that $B_{i,j} = 1$ if the vertex $v_i$ and edge $e_j$ are incident and 0 otherwise.

```python

```

***
**(f) Compute the Laplacian $L$ of the network using $k_n$ and $A$. Remember that the Laplacian can also be computed as $L=KK^T$ and check that the two definitions agree.**
> The **Laplacian** (also: admittance matrix, Kirchhoff matrix, discrete Laplacian) is a matrix representation of a graph. It is defined as the difference of degree matrix and adjacency matrix. The **degree matrix** is a diagonal matrix which contains information about the degree of each vertex.

```python

```

***
**(g) Find the diameter of the network by looking at the graph.**
> The diameter of a network is the longest of all the calculated shortest paths in a network. It is the shortest distance between the two most distant nodes in the network. In other words, once the shortest path length from every node to all other nodes is calculated, the diameter is the longest of all the calculated path lengths. The diameter is representative of the linear size of a network.

```python

```

***
## Problem II.2

If you map the nodes to `0=DK, 1=DE, 2=CH, 3=IT, 4=AT, 5=CZ` the network represents a small part of the European electricity network (albeit very simplified). In the repository, you can find the power imbalance time series for the six countries for January 2017 in hourly MW at `./data/imbalance.csv`. They have been derived from physical flows as published by [ENTSO-E](https://transparency.entsoe.eu/transmission-domain/physicalFlow/show)

The linear power flow is given by
$$p_i = \sum_j \tilde{L}_{i,j}\theta_j \qquad \text{and} \qquad f_l = \frac{1}{x_l} \sum_i K_{i,l}\theta_i, \qquad \text{where} \qquad \tilde{L}_{i,j}= \sum_l K_{i,l}\frac{1}{x_l} K_{j,l}$$
is the weighted Laplacian. For simplicity, we assume identity reactance on all links $x_l = 1$.

***
**Read data**

```python
imbalance = pd.read_csv('data/imbalance.csv', index_col=0, parse_dates=True)
imbalance.head()
```

      DK
      DE
      CH
      IT
      AT
      CZ

      DateTime

      2017-01-01 00:00:00
      -79.73
      3486.25
      -3403.37
      -97.5
      -2867.33
      2961.68

      2017-01-01 01:00:00
      -109.14
      4248.17
      -3829.59
      -105.9
      -3149.98
      2946.44

      2017-01-01 02:00:00
      -101.49
      5314.56
      -4131.66
      -199.2
      -3646.87
      2764.66

      2017-01-01 03:00:00
      -197.96
      5450.71
      -4009.61
      -656.0
      -3345.56
      2758.42

      2017-01-01 04:00:00
      -526.15
      5695.72
      -3924.43
      -646.2
      -3393.12
      2794.18

\begin{equation}
p_u = \sum_v L_{u,v} \theta_v
\end{equation}

***
**(a) Compute the voltage angles $\theta_j$ and flows $f_l$ for the first hour in the dataset with the convention of $\theta_0 = 0$; i.e. the slack bus is at node 0.**
> **Remark:** Linear equation systems are solved efficiently using [`numpy.linalg.solve`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.solve.html).

Calculate the *voltage angles* first:

```python

```

Then, calculate the *flows*:

```python

```

***
**(b) Determine the average flow on each link for January 2017 and draw it as a directed network**

> **Hint:** You may want to make use of the function `np.vstack`.

```python

```


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

```python
x, y = symbols('x y')
K = Function('K')(x,y)
```

```python
u = exp(-x - y**2)
```

```python
f = - (K*u.diff(x)).diff(x) - (K*u.diff(y)).diff(y)
f
```

```python
f1 = f.subs(K, (x+y)**5).simplify()
f1
```

```python
lua_code(f1)
```

    '(-4*y^2*(x + y)^5 + 10*y*(x + y)^4 + (x + y)^5 + 5*(x + y)^4)*math.exp(-x - y^2)'

```python

```


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, G.F. Forsyth.

# Coding Assignment: Rocket

The equations of motion for a rocket in purely vertical flight are given by

\begin{align}
\frac{dh}{dt} &= v\\
(m_s+m_p) \frac{dv}{dt}& = -(m_s+m_p)g + \dot{m}_pv_e - \frac{1}{2}\rho v|v|AC_D
\end{align}

$h$ is the altitude of the rocket

$m_s = 50kg$ is the weight of the rocket shell

$g = 9.81 \frac{m}{s^2}$

$\rho = 1.091 \frac{kg}{m^3}$ is the average air density (assumed constant throughout flight)

$A = \pi r^2$ is the maximum cross sectional area of the rocket, where $r = 0.5 m$

$v_e = 325 \frac{m}{s}$ is the exhaust speed

$C_D = 0.15 $ is the drag coefficient

$m_{po} = 100 kg$ at time $t = 0$ is the initial weight of the rocket propellant

The mass of the remaining propellant is given by:

$$m_p = m_{po} - \int^t_0 \dot{m}_p d\tau$$

where $\dot{m}_p$ is the time-varying burn rate given by the following figure:

Propellant Burn Rate

Using Euler's method with a timestep of $\Delta t=0.1s$, create a Python script to calculate the altitude and velocity of the rocket from launch until crash down.

## Assessment:

To check your answers, you can register for [MAE 6286: Practical Numerical Methods with Python](http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about).

1. At time $t=3.2s$, what is the mass (in kg) of rocket propellant remaining in the rocket?

2. What is the maximum speed of the rocket in $\frac{m}{s}$?
    At what time does this occur (in seconds)?
    What is the altitude at this time (in meters)?

3. What is the rocket's maximum altitude during flight (in meters)? At what time (in seconds) does this occur?

4. At what time (in seconds) does the rocket impact the ground? What is the velocity of the rocket (in $\frac{m}{s}$) at time of impact?

## Derivation of the rocket equations

In case you are kind of confused about the rocket equations, here we show how to get to them.

We will start from the widely used [Reynolds' transport theorem](http://en.wikipedia.org/wiki/Reynolds_transport_theorem). For an extensive property $N$ in a volume $\Omega$, with corresponding intensive property $\eta = N/m$, the Reynolds' transport theorem reads

$$
\frac{DN}{Dt} =  \frac{\partial}{\partial t} \int_\Omega \eta \rho {\rm dV}  + \oint_{\partial \Omega} \eta \rho \mathbf{V} \cdot \mathbf{n} {\rm d} S
$$

where $m$ is mass, dV is an element of volume, $\mathbf{V}$ the velocity, $\mathbf{n}$ the unit normal vector pointing out of the control volume and $\rho$ density. We will use a control volume that encloses the rocket, and moves with it. If the velocity of the control volume is $\mathbf{V}_{\rm CV}$, we can write the velocity as $\mathbf{V} = \mathbf{V}_{\rm CV} + \mathbf{V}^\prime$, and the Reynolds' transport theorem becomes

$$
\frac{DN}{Dt} =  \frac{\partial}{\partial t} \int_\Omega \eta \rho {\rm dV}  + \oint_{\partial \Omega} \eta \rho \mathbf{V}_{\rm CV} \cdot \mathbf{n} {\rm d} S + \oint_{\partial \Omega} \eta \rho \mathbf{V}^\prime \cdot \mathbf{n} {\rm d} S.
$$

The second term in the right hand side of this equation can be rewritten using the divergence theorem

$$
\oint_{\partial \Omega} \eta \rho \mathbf{V}_{\rm CV} \cdot \mathbf{n} {\rm d} S = \int_{\Omega} \eta \rho \nabla \cdot \mathbf{V}_{\rm CV} {\rm dV},
$$

but for a non-deforming control volume, $\nabla \cdot \mathbf{V}_{\rm CV} = 0$, yielding

$$
\frac{DN}{Dt} =  \frac{\partial}{\partial t} \int_\Omega \eta \rho {\rm dV}  + \oint_{\partial \Omega} \eta \rho \mathbf{V}^\prime \cdot \mathbf{n} {\rm d} S.
$$

Replacing $N = m \mathbf{V}^\prime$, we get the momentum conservation equation with a moving non-deforming control volume:

$$
\frac{D(m\mathbf{V}^\prime)}{Dt} =  \frac{\partial}{\partial t} \int_\Omega \mathbf{V}^\prime \rho {\rm dV}  + \oint_{\partial \Omega} \mathbf{V}^\prime \rho (\mathbf{V}^\prime \cdot \mathbf{n}) {\rm d} S.
$$

For non-accelerating control volume, we can apply Newton's second law to compute the forces:

$$
\frac{D(m\mathbf{V}^\prime)}{Dt} = \frac{D(m\mathbf{V})}{Dt} =  \frac{D\mathbf{p}}{Dt} = \sum \mathbf{F}.
$$

However, in this case the control volume is moving with the accelerating rocket, and we need to be a bit more careful. We can write $\frac{D(m\mathbf{V}^\prime)}{Dt} = \frac{D(m\mathbf{V})}{Dt} - \frac{D(m\mathbf{V}_{\rm CV})}{Dt}$, and considering the control volume is not deforming, $\frac{D(m\mathbf{V}_{\rm CV})}{Dt} = m\mathbf{a}_{\rm CV}$, where $\mathbf{a}_{\rm CV}$ is the acceleration of the control volume. On the other hand, $\mathbf{V}$ is taken from an inertial (non-accelerating) frame of reference, and Newton's second law is valid there, then

$$
\frac{D(m\mathbf{V}^\prime)}{Dt} = \frac{D(m\mathbf{V})}{Dt} - \frac{D(m\mathbf{V}_{\rm CV})}{Dt} = \sum \mathbf{F} - m \mathbf{a}_{\rm CV}.
$$

This way we get a momentum equation for an accelerating control volume:

$$
\sum \mathbf{F} - m \mathbf{a}_{\rm CV} =  \frac{\partial}{\partial t} \int_\Omega \mathbf{V}^\prime \rho {\rm dV}  + \oint_{\partial \Omega} \mathbf{V}^\prime \rho (\mathbf{V}^\prime \cdot \mathbf{n}) {\rm d} S.
$$

Let's examine this last equation to make it specific for the rocket problem. First, the rocket is subject to two forces: gravity and drag. This makes the sum of forces:

$$
\sum \mathbf{F} = (m_c + m_p) \mathbf{g} - \frac{1}{2} \rho_a \mathbf{v} |\mathbf{v}| A  C_D.
$$

where $\mathbf{v}$ is the velocity of the rocket, equal to the velocity of the control volume $\mathbf{V}_{\rm CV}$, and $\rho_a$ is the density of air.

Now, let's deal with the volume integral. Check out Figure 1: the control volume encloses the whole rocket, and the border is slightly away from the nozzle. We can make a cut on the control volume to analyze it in two parts: the top part consisting of the container only, and the bottom part from the nozzle down. The top part is a large container that moves with the control volume, and the relative velocities of the propellant inside are negligible. In the bottom part, the fluid is moving quite fast, but the amount of fluid there is constant in time. These two considerations allow us to drop the volume integral.

#### Figure 1. Control volume of the rocket.

Done with the volume integral, now the surface integral. If we consider that the burnt propellant is coming out of the control volume with a constant velocity profile, we can rewrite the surface integral as

$$
\oint_{\partial \Omega} \mathbf{V}^\prime \rho_p (\mathbf{V}^\prime \cdot \mathbf{n}) {\rm d} S = \mathbf{V}^\prime \oint_{\partial \Omega} \rho_p (\mathbf{V}^\prime \cdot \mathbf{n}) {\rm d} S = \mathbf{V}^\prime \dot{m}_p = \mathbf{v}_e \dot{m}_p,
$$

where $\mathbf{v}_e$ is the exhaust velocity of the burnt propellant coming out of the nozzle with respect to the control volume, $\rho_p$ is the density of the propellant, and $\dot{m}_p$ is the mass flow rate of propellant. By saying that $\rho = \rho_p$ we are considering that the mass of air is negligible compared to the mass of propellant coming out of the nozzle.

Finally, the momentum equation for the rocket is

$$
(m_c + m_p) \mathbf{g} - \frac{1}{2} \rho_a \mathbf{v} |\mathbf{v}| A  C_D - (m_c + m_p)\frac{{\rm d} \mathbf{v}}{{\rm d}t} =  \mathbf{v}_e \dot{m}_p.
$$

In practical terms, we're only interested in the $y$ component. Vectors $\mathbf{g}$ and $\mathbf{v}_e$ are pointing down, which gives the scalar equation:

$$
-(m_c + m_p) g - \frac{1}{2} \rho_a v^2 A  C_D - (m_c + m_p)\frac{{\rm d} v}{{\rm d}t} =  -v_e \dot{m}_p.
$$

This is the equation you will work with for your assignment!

---

###### The cell below loads the style of the notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "PT Mono";
    font-size: 90%;
}

</style>


Traçar um esboço do gráfico e obter uma equação da parábola que satisfaça as condições dadas.

18. Vértice:$V(-2,3)$; Foco: $F(-2,1)$

Como o vértice da parábola se encontra fora da origem e está paralela ao eixo $y$ a sua equação é dada por $(x-h)^2 = 2p(y-k)$

Substituindo os pontos do vértice por $x = -2$ e $y = 3$ temos:

$(x -(-2))^2 = 2p(y-3)$

$(x+2)^2 = 2p(y-3)$

Achando o valor de $p$

$\frac{p}{2} = \sqrt{(-2-(-2))^2 + (1-3)^2}$

$\frac{p}{2} = \sqrt{0 + -2^2}$

$\frac{p}{2} = \pm \sqrt{4}$

$\frac{p}{2} = -2$

$p = -4$

Substituindo $p$ na fórmula

$(x+2)^2 = 2\cdot -4 \cdot (y-3)$

$x^2 + 4x + 4 = -8y + 24$

$x^2 + 4x + 8y - 20 = 0$

Gráfico da parábola

```python
from sympy import *
from sympy.plotting import plot_implicit
x, y = symbols("x y")
plot_implicit(Eq((x+2)**2, -8*(y-3)), (x,-10,10), (y,-5,5),
title=u'Gráfico da parábola', xlabel='x', ylabel='y');
```

Problemas Propostos. Pag. 172 - 175/18.ipynb