# Problema de selección de portafolio con preferencias media-varianza

En la clase pasada hablamos acerca de:
- preferencias,
- funciones de utilidad,
- la actitud de los inversionistas de cara al riesgo,
- la aversión al riesgo, entre otros.

Todas ellas son piezas que necesitamos para responder la pregunta de ¿cómo un inversionista toma la decisión óptima de selección de portafolio?

En esta clase al fin estamos listos para ensamblar estos conceptos y escribir el problema de selección de portafolios.

En el camino aprenderemos acerca del concepto de **utilidad esperada**, que nos permite trabajar con incertidumbre en el modelado económico (una de las ideas más importantes en economía). Esta idea tiene más de 60 años, y básicamente dice que los individuos, cuando están de cara a incertidumbre, maximizan el valor esperado de su utilidad (solo cierto si somos homo economicus).

Además del concepto de utilidad esperada, aprenderemos acerca de **preferencias media-varianza**. Es decir, supondremos que los inversionistas toman decisiones basados en un tipo particular de preferencias.

Con lo anterior, estableceremos el problema de selección de portafolios.

**Objetivos:**

- ¿Qué es utilidad esperada?
- ¿Qué son las preferencias media-varianza?
- Funciones de utilidad media-varianza.
- Enunciado y solución del problema básico de selección de portafolio.

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Utilidad esperada
- Básicamente combina las probabilidades de los resultados con cómo los inversionistas se sienten con dichos resultados.
- En otras palabras, la utilidad esperada multiplica la probabilidad de suceso de un evento con la utilidad que genera dicho evento.

Recordemos que las *funciones de utilidad* permiten a los inversionistas expresar cómo se sienten con los resultados, especialmente en los malos ratos.

Entonces la *utilidad esperada* es una herramienta que nos permite cuantificar cómo nos sentimos en nuestros malos momentos económicos, capturando el riesgo con la probabilidad de ocurrencia de dichos malos momentos.

Dado este marco de trabajo, cualquier decisión se puede escribir como la maximización de la utilidad esperada:
\begin{align}
\max_{\theta} & \quad E[U(W)], \\
\end{align}
mediante la escogencia de cierta variable $\theta$ (gastos, planes de ahorro, compra de activos, planes de producción, etc.).

Para nuestros propósitos, la variable de decisión serán los pesos o ponderaciones del portafolio.

Adicionalmente, en el contexto de la decisión de distribución de la riqueza entre activos, el problema de maximización tendrá comúnmente las siguientes restricciones:
- universo de inversión,
- posición en los activos dados.

**Ejemplo.**

Supongamos que un inversionista debe determinar la composición óptima de su portafolio, que contiene activos y bonos. Supongamos que son los únicos instrumentos disponibles.

Sean:
- $w_s$: peso o ponderación de activos en el portafolio,
- $w_b$: peso o ponderación de bonos en el portafolio,
- $r_s$: rendimiento de los activos, y
- $r_b$: rendimiento de los bonos.

De manera que podemos escribir el problema de selección de portafolios como la maximización de la utilidad esperade de nuestra riqueza futura, la cual dependerá de nuestros rendimientos:

\begin{align}
\max_{w_s,w_b} &\quad E[U(W)]\\
\text{s. a.}   &\quad W=W_0(1+w_sr_s+w_br_b)\\
               &\quad w_s+w_b=1
\end{align}

Preguntas:
- ¿Qué significan las restricciones?
- Ya que tenemos planteado este problema básico, ¿qué haría falta para empezar a resolverlo?
___

## 2. Preferencias media-varianza

### 2.1. Utilidad media-varianza

Entonces, ¿qué funciones de utilidad deberíamos de usar en este problema de selección de portafolios?

- La respuesta es: **preferencias media-varianza**.
- Éstas serán representadas en términos de funciones de utilidad como: **utilidad media-varianza**.

Usamos la *utilidad media-varianza* en el problema de selección de portafolios dado que ésta decribe el "trade-off" entre riesgo y rendimiento que enfrentan los inversionistas. La *utilidad media-varianza* está dada por la siguiente expresión:

$$U=E[r_p]-\frac{1}{2}\gamma\sigma_p^2,$$

donde
- $E[r_p]$ es el rendimiento esperado del portafolio,
- $\sigma_p^2$ es la varianza del portafolio, y
- $\gamma$ es el coeficiente de aversión al riesgo.

#### Intuición acerca de la función de utilidad media-varianza:
- Sólo se preocupa por medias :) y varianzas :(.
- Incrementa con: rendimiento esperado del portafolio.
- Decrece con: varianza del portafolio.
- Malos tiempos: rendimientos son bajos y las volatilidades son altas.
- Conecta bastante bien con la teoría moderna de portafolios, la cual caracteriza los rendimientos con medias y varianzas únicamente.
- Criticada por su limitación: supone que los inversionistas sólo se preocupan por medias y varianzas.

### 2.2. Curvas de indiferencia

*¿Recuerdan las curvas de nivel que se ven en cálculo de varias variables?*
- Bien, acá nos servirán para representar la utilidad media-varianza gráficamente.
- En el contexto de utilidad media-varianza, las curvas de nivel se llaman **curvas de indiferencia**.

Dados ciertos niveles de utilidad $U_1>U_2>U_3$, las curvas de indiferencia relativas a estos niveles de utilidad, son los lugares geométricos en el espacio de rendimiento esperado vs. volatilidad representados por las siguientes expresiones

$$U_1=E[r_p]-\gamma\sigma_p^2\Rightarrow E[r_p]=\gamma\sigma_p^2+U_1,$$

$$U_2=E[r_p]-\gamma\sigma_p^2\Rightarrow E[r_p]=\gamma\sigma_p^2+U_2,$$

$$U_3=E[r_p]-\gamma\sigma_p^2\Rightarrow E[r_p]=\gamma\sigma_p^2+U_3.$$

**Gráficamente**

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

```python
# Coeficiente de aversión al riesgo
g = 3
# Niveles de utilidad
U1, U2, U3 = 3, 2, 1
# Vector de volatilidades
sp = np.linspace(0.01, 0.7, 100)
# Curvas de indiferencia
Erp1, Erp2, Erp3 = 0.5*g*sp**2+U1, 0.5*g*sp**2+U2, 0.5*g*sp**2+U3
```

```python
# Gráfica
plt.figure(figsize=(8,6))
plt.plot(sp, Erp1, lw = 3, label="$U_1$")
plt.plot(sp, Erp2, lw = 3, label="$U_2$")
plt.plot(sp, Erp3, lw = 3, label="$U_3$")
plt.xlabel("Volatilidad $\sigma$")
plt.ylabel("Rendimiento esperado $E[r]$")
plt.legend(loc="best")
plt.title("Curvas de indiferencia")
plt.grid()
plt.show()
```

Bueno, ¿y porqué se llaman curvas de indiferencia?, ¿qué representa una cuva de indiferencia?
- Sobre las combinaciones de riesgo-rendimiento en una misma curva, la utilidad es indiferente.
- Las combinaciones de riesgo-rendimiento, para las cuales la utilidad tiene un nivel dado.

Volviendo al problema de selección de portafolios, queremos la utilidad más alta.
- ¿Cuál de las anteriores curvas de indiferencia corresponde a la utilidad más alta?
- Intuitivamente, ¿porqué?
- Curvas de indiferencia para niveles de utilidad más altos, tendrán...

Notamos además que las anteriores curvas de indiferencia son *paralelas* una con otra. Claro, las dibujamos con el mismo coeficiente de aversión al riesgo.

¿Cómo cambian estas curvas para coeficientes de aversión al riesgo más altos?

```python
# Coeficientes de aversión al riesgo
g1, g2, g3 = 3, 5, 7
# Nivel de utilidad
U = 1
# Vector de volatilidades
sp = np.linspace(0.01, 0.7, 100)
# Curvas de indiferencia
Erp1, Erp2, Erp3 = 0.5*g1*sp**2+U, 0.5*g2*sp**2+U, 0.5*g3*sp**2+U
```

```python
# Gráfica
plt.figure(figsize=(8,6))
plt.plot(sp, Erp1, lw = 3, label="$\gamma_1$")
plt.plot(sp, Erp2, lw = 3, label="$\gamma_2$")
plt.plot(sp, Erp3, lw = 3, label="$\gamma_3$")
plt.xlabel("Volatilidad $\sigma$")
plt.ylabel("Rendimiento esperado $E[r]$")
plt.legend(loc="best")
plt.title("Curvas de indiferencia")
plt.grid()
plt.show()
```

¿Cómo interpretamos las anteriores gráficas?, ¿qué pasa con las personas más aversas al riesgo?
- Para un nivel de volatilidad dado, una persona más aversa al riesgo requiere mayor rendimiento esperado para obtener el mismo nivel de utilidad.

Con todo lo anterior, el problema de selección de portafolios se puede plantear como *"encontrar la curva de indeferencia más alta dado el conjunto de oportunidades de inversión y restricciones"*.

## 3. Problema de selección de portafolios: una ilustración

Ahora ilustraremos el problema de selección de portafolios con algunos datos.
- Por ahora solo queremos ilustrar gráficamente cómo se resuelve este problema. Trabajar en la intuición.
- En las siguientes dos clases nos enfocaremos en cómo resolverlo analíticamente.

Acá tenemos el rendimiento medio anual y la volatilidad para dos instrumentos usando datos de EU: instrumentos de deuda (bonos) y acciones. Supondremos que el inversionista solo puede invertir en estas dos clases de instrumentos.

```python
# Importamos pandas
import pandas as pd
```

```python
# Datos
data=pd.DataFrame(index=['Stocks','Bonds', 'CorrSB'], columns=['Mean', 'Std'])
data['Mean'] = np.array([0.119,0.0591,0.113])
data['Std'] = np.array([0.1915,0.0833,None])
data.round(4)
```

      Mean
      Std

      Stocks
      0.1190
      0.1915

      Bonds
      0.0591
      0.0833

      CorrSB
      0.1130
      None

Entonces, ¿cuál es la distribución de riqueza óptima?, o más bien, ¿cuál es la composición óptima del portafolio para un inversionista dado su nivel de aversión al riesgo?

**Primero.** Recordamos que, para dos activos, podemos trazar la frontera de mínima varianza tomando todas las posibles combinaciones de los dos activos.

De nuevo, sean:
- $w_s=w$: peso o ponderación de activos en el portafolio,
- $w_b=1-w$: peso o ponderación de bonos en el portafolio,
- $r_s$: rendimiento de los activos, y
- $r_b$: rendimiento de los bonos.

Entonces

$$E[r_p]=wE[r_{s}]+(1-w)E[r_b]$$

$$\sigma_p^2=w^2\sigma_{s}^2+(1-w)^2\sigma_b^2+2w(1-w)\rho_{s,b}\sigma_s\sigma_b$$

```python
# Vector de w variando entre 0 y 1 con n pasos
n = 101
w = np.linspace(0, 1, n)
# Rendimientos esperados individuales
Ers, Erb = data.loc["Stocks", "Mean"], data.loc["Bonds", "Mean"]
# Volatilidades individuales
ss, sb = data.loc["Stocks", "Std"], data.loc["Bonds", "Std"]
# Correlacion
rsb = data.loc["CorrSB","Mean"]
```

```python
# Crear un DataFrame cuyas columnas sean rendimiento
# y volatilidad del portafolio para cada una de las w
# generadas
portfolios = pd.DataFrame(columns=["Ret", "Vol"], index=w)
portfolios.Ret = w*Ers+(1-w)*Erb
portfolios.Vol = ((w*ss)**2+((1-w)*sb)**2+2*w*(1-w)*rsb*ss*sb)**(1/2)
portfolios
```

      Ret
      Vol

      0.00
      0.059100
      0.083300

      0.01
      0.059699
      0.082705

      0.02
      0.060298
      0.082155

      0.03
      0.060897
      0.081650

      0.04
      0.061496
      0.081191

      0.05
      0.062095
      0.080779

      0.06
      0.062694
      0.080415

      0.07
      0.063293
      0.080099

      0.08
      0.063892
      0.079832

      0.09
      0.064491
      0.079614

      0.10
      0.065090
      0.079446

      0.11
      0.065689
      0.079328

      0.12
      0.066288
      0.079261

      0.13
      0.066887
      0.079244

      0.14
      0.067486
      0.079277

      0.15
      0.068085
      0.079361

      0.16
      0.068684
      0.079495

      0.17
      0.069283
      0.079679

      0.18
      0.069882
      0.079913

      0.19
      0.070481
      0.080195

      0.20
      0.071080
      0.080527

      0.21
      0.071679
      0.080907

      0.22
      0.072278
      0.081334

      0.23
      0.072877
      0.081808

      0.24
      0.073476
      0.082327

      0.25
      0.074075
      0.082892

      0.26
      0.074674
      0.083501

      0.27
      0.075273
      0.084153

      0.28
      0.075872
      0.084847

      0.29
      0.076471
      0.085582

      ...
      ...
      ...

      0.71
      0.101629
      0.140756

      0.72
      0.102228
      0.142414

      0.73
      0.102827
      0.144080

      0.74
      0.103426
      0.145755

      0.75
      0.104025
      0.147437

      0.76
      0.104624
      0.149128

      0.77
      0.105223
      0.150826

      0.78
      0.105822
      0.152532

      0.79
      0.106421
      0.154244

      0.80
      0.107020
      0.155964

      0.81
      0.107619
      0.157690

      0.82
      0.108218
      0.159422

      0.83
      0.108817
      0.161161

      0.84
      0.109416
      0.162905

      0.85
      0.110015
      0.164656

      0.86
      0.110614
      0.166412

      0.87
      0.111213
      0.168173

      0.88
      0.111812
      0.169940

      0.89
      0.112411
      0.171712

      0.90
      0.113010
      0.173489

      0.91
      0.113609
      0.175271

      0.92
      0.114208
      0.177057

      0.93
      0.114807
      0.178848

      0.94
      0.115406
      0.180643

      0.95
      0.116005
      0.182443

      0.96
      0.116604
      0.184246

      0.97
      0.117203
      0.186054

      0.98
      0.117802
      0.187866

      0.99
      0.118401
      0.189681

      1.00
      0.119000
      0.191500

101 rows × 2 columns

```python
# Gráfica
plt.figure(figsize=(8,6))
plt.plot(portfolios.Vol, portfolios.Ret, lw=3, label="Portafolios")
plt.xlabel("Volatilidad $\sigma$")
plt.ylabel("Rendimiento esperado $E[r]$")
plt.legend(loc="best")
plt.grid()
plt.show()
```

**Segundo.** Graficamos en la misma ventana, curvas de indiferencia.

```python
# Niveles de utilidad 0.04, 0.05, 0.06
U1, U2, U3 = 0.07, 0.08, 0.083
# Coeficiente de aversión al riesgo
g = 2
# Curvas de indiferencia
Erp1 = 0.5*g*portfolios.Vol**2+U1
Erp2 = 0.5*g*portfolios.Vol**2+U2
Erp3 = 0.5*g*portfolios.Vol**2+U3
```

```python
# Gráfica
plt.figure(figsize=(8,6))
plt.plot(portfolios.Vol, portfolios.Ret, lw=3, label="Portafolios")
plt.plot(portfolios.Vol, Erp1, '--', lw = 3, label="$U_1$")
plt.plot(portfolios.Vol, Erp2, '--', lw = 3, label="$U_2$")
plt.plot(portfolios.Vol, Erp3, '--', lw = 3, label="$U_3$")
plt.xlabel("Volatilidad $\sigma$")
plt.ylabel("Rendimiento esperado $E[r]$")
plt.legend(loc="best")
plt.grid()
plt.show()
```

**Tercero.** La elección óptima está dada por la curva de indiferencia para el nivel de utilidad más alto que es tangente a la frontera media-varianza.
- Claramente, esta selección depende del coeficiente de aversión al riesgo.

```python
# Gráfica con zoom
plt.figure(figsize=(8,6))
plt.plot(portfolios.Vol, portfolios.Ret, lw=3, label="Portafolios")
plt.plot(portfolios.Vol, Erp1, '--', lw = 3, label="$U_1$")
plt.plot(portfolios.Vol, Erp2, '--', lw = 3, label="$U_2$")
plt.plot(portfolios.Vol, Erp3, '--', lw = 3, label="$U_3$")
plt.xlabel("Volatilidad $\sigma$")
plt.ylabel("Rendimiento esperado $E[r]$")
plt.legend(loc="best")
plt.xlim([0.15, 0.19])
plt.ylim([0.105, 0.12])
plt.grid()
plt.show()
```

# Anuncios parroquiales

## 1. Quiz la siguiente clase.
## 2. Un par de artículos del WSJ y el NYT que discuten herramientas disponibles para la medición de su propia tolerancia al riesgo:
- [Artículo 1](https://www.nytimes.com/2016/02/13/your-money/as-stocks-fall-its-time-to-measure-your-risk-tolerance.html)
- [Artículo 2](https://www.wsj.com/articles/check-your-tolerance-for-investment-risk-now-before-markets-sag-1405619939)

Created with Jupyter by Esteban Jiménez Rodríguez.


Note that python fsps is needed and not installed on the CITA machines by default so all results will be in the pdf

```python
import fsps
import math
import numpy as np
import matplotlib.pyplot as plt
from sympy import *
import emcee
from IPython.display import display, Math
import corner
from astropy.cosmology import WMAP9 as cosmo
```

Part 1

```python
def create_plot(x, y, x_lim, x_label, y_lim, y_label, loglog=True):
    """Creates plot of x vs. y with xlim of x_lim and ylim of y_lim and
    respective labels (log-log scale or log-y scale)

    Parameters:
    x - np array of x values
    y - np array of corresponding y values
    x_lim - Tuple containing leftmost and rightmost x value to be on graph
    y_lim - Tuple containing lowest and highest y value to be on graph
    x_label - Label for x-axis
    y_label - Label for y-axis
    loglog - By default, graphs with a log x and y axis, if true, graphs
    with a log y axis only

    Returns:
    Function has no return value, however, it shows the matplotlib graph
    """
    plt.figure(figsize=(15, 9))

    if loglog:
        plt.loglog(x, y)
    else:
        plt.semilogy(x, y)

    plt.xlabel(x_label, fontsize=14)
    plt.xlim(x_lim)
    plt.ylabel(y_label, fontsize=14)
    plt.ylim(y_lim)
    plt.savefig("plot.pdf")
    plt.show()
```

```python
def linear_interpolation(x_s, y_s, x_val):
    """Determines y value at x_val by linearly interpolating y_s

    Parameters:
    x_s - np array of x values
    y_s - np array of corresponding y values
    x_val - x value at which y_s will be linearly interpolated

    Returns:
    y value linearly interpolated from y_s at x_val
    """
    index = np.abs(x_s - x_val).argmin()

    if x_s[index] > x_val:
        l_index = index - 1
        r_index = index
    elif x_s[index] < x_val:
        l_index = index
        r_index = index + 1
    else:
        return y_s[index]

    m = (y_s[r_index] - y_s[l_index])/(x_s[r_index] - x_s[l_index])
    b = y_s[r_index] - m*x_s[r_index]

    return b + m*x_val
```

Question 3.

```python
sp = fsps.StellarPopulation(imf_type=1, dust_type=2, dust1=0.0, dust2=0.5, logzsol=math.log10(0.2),sfh=0)
```

Question 4.

```python
wave, spec = sp.get_spectrum(tage=0.01)

create_plot(wave, spec, (10**2, 10**8), r"$\lambda$ ($\AA$)", (10**-17, 10**-3), r"L$_v$  $(L_\odot/Hz)$")
```

```python
sp.libraries
```

    (b'mist', b'miles')

```python
sp.stellar_mass
```

    0.9187920115803033

Question 5. (Incorrectly numbered as question 6 in assignment)

```python
def get_spectral_density(wave, spec, z):
    """Converts spectrum from (bolometric) luminosity in units of solar luminosities
    per hertz to flux density in units of microJansky

    Parameters:
    wave - np array of wavelengths (units don't matter)
    spec - np array of luminosities measured in solar luminosities per hertz
    z - Redshift of the galaxy

    Returns:
    New np array of wavelengths and the corresponding np array of flux densities
    measured in microJansky
    """
    d = cosmo.luminosity_distance(z)
    spec = 4.02*(10**13)*((1 + z)*spec)/(4*math.pi*d.value**2)
    wave = wave*(1 + z)

    return wave, spec
```

Question 6. (Incorrectly numbered as question 5 in assignment)

```python
wave_1, spec_1 = get_spectral_density(wave, spec, 1)

create_plot(wave_1, spec_1, (10**2, 10**8), r"$\lambda$ ($\AA$)", (10**-17, 10**-3), r"f$_v$  $(\mu Jy)$")
```

```python
wave_2, spec_2 = get_spectral_density(wave, spec, 2)

create_plot(wave_2, spec_2, (10**2, 10**8), r"$\lambda$ ($\AA$)", (10**-17, 10**-3), r"f$_v$  $(\mu Jy)$")
```

```python
wave_3, spec_3 = get_spectral_density(wave, spec, 3)

create_plot(wave_3, spec_3, (10**2, 10**8), r"$\lambda$ ($\AA$)", (10**-17, 10**-3), r"f$_v$  $(\mu Jy)$")
```

Question 7. (Incorrectly numbered as question 6 in assignment)

```python
z_incr = 0.01
z = 0.01
z_s = []
new_spec = []

while z <= 10:
    des_wave = 8000/(z + 1)

    close_spec = linear_interpolation(wave, spec, des_wave)

    np_close_wave, np_close_spec = get_spectral_density(np.array([-1]), np.array([close_spec]), z)

    z_s.append(z)
    new_spec.append(np_close_spec[0])
    z += z_incr

create_plot(np.array(z_s), np.array(new_spec), (0, 10), r"$z$", (7.46*10**-13, 1.66*10**-5), r"f$_v$  $(\mu Jy)$", loglog=False)
```

Question 8. (Incorrectly numbered as question 7 in assignment)

```python
def generate_SED(age, z, plot=False, x_lim=(10**2, 10**8), y_lim=(10**-17, 10**-4)):
    """Calculates and optionally plots SED of galaxy with a given age and redshift

    Parameters:
    age - Age of galaxy
    z - Redshift of galaxy
    plot - If true, draw an SED, otherwise

    Returns:
    np array of wavelengths and corresponding np array of flux densities in microJansky
    """
    wave, spec = sp.get_spectrum(tage=age)
    new_wave, new_spec = get_spectral_density(wave, spec, z)

    if plot:
        create_plot(new_wave, new_spec, x_lim, r"$\lambda$ ($\AA$)", y_lim, y_label = r"f$_v$  $(\mu Jy)$")

    return new_wave, new_spec

generate_SED(0.01, 1, plot=True)
```

```python
generate_SED(0.1, 1, plot=True)
```

```python
generate_SED(1, 1, plot=True)
```

```python
generate_SED(3, 1, plot=True)
```

```python
generate_SED(10, 1, plot=True)
```

Part 2

```python
def read_file(name):
    """Creates 3 np arrays of data, read in from a file consisting of three columns

    Parameters:
    name - name of file

    Returns:
    3 np arrays containing data in the three coloumns from the file
    """
    lst_1 = []
    lst_2 = []
    lst_3 = []

    with open(name) as reader:
        for line in reader:
            list_str = line.split()
            if len(list_str) == 3:
                lst_1.append(float(list_str[0]))
                lst_2.append(float(list_str[1]))
                lst_3.append(float(list_str[2]))
    return np.array(lst_1), np.array(lst_2), np.array(lst_3)
```

Question 1.

```python
def weighted_fitting(x_s, y_s, errs):
    """Calculates y-int and slope (and uncertainties) of
    line that fits data using weighted linear least-square fitting

    Parameters:
    x_s - numpy array of x values
    y_s - numpy array of corresponding y values
    errs - numpy array of corresponding errors in the y values

    Returns:
    Tuple containing y-int, uncertainty in y-int, slope, and
    uncertainy in slope
    """
    list_Y = []
    list_A = []
    list_C = []

    for i in range(len(x_s)):
        list_Y.append([y_s[i]])
        list_A.append([1, x_s[i]])

        C_row = []

        counter = 0
        while counter < len(x_s):
            if counter == i:
                C_row.append(errs[i]**2)
            else:
                C_row.append(0)

            counter += 1

        list_C.append(C_row)

    A = Matrix(list_A)
    Y = Matrix(list_Y)
    C = Matrix(list_C)

    cov = ((A.T*C.inv()*A).inv())

    b_error = cov[0, 0]
    m_error = cov[1, 1]

    X = cov*(A.T*C.inv()*Y)

    return X[0, 0], sqrt(b_error), X[1, 0], sqrt(m_error)
```

```python
def calc_chi(x_s, y_s, errs, b, m):
    """Calculates chi^2, a measure of how closely a line fits
    the given data

    Parameters:
    x_s - np array of x values
    y_s - np array of corresponding y values
    errs - np array of corresponding errors in the y values
    b - y-int of line
    m - slope of line

    Returns:
    The value of chi^2
    """
    return np.sum(((y_s - (b + m*x_s))**2)/(errs**2))
```

```python
x_s, y_s, errs = read_file('fitting_simple_data.txt')

b, b_err, m, m_err = weighted_fitting(x_s, y_s, errs)

txt = "\mathrm{{{3}}} = {0:.3f}_{{-{1:.3f}}}^{{{2:.3f}}}"
txt = txt.format(b, b_err, b_err, 'b')
display(Math(txt))
txt = "\mathrm{{{3}}} = {0:.3f}_{{-{1:.3f}}}^{{{2:.3f}}}"
txt = txt.format(m, m_err, m_err, 'm')
display(Math(txt))

x_line = np.linspace(0, 12)
y_line = b + m*x_line

plt.figure(figsize=(15, 9))
plt.scatter(x_s, y_s)
plt.errorbar(x_s, y_s, yerr=errs, fmt=".", capsize=0)
plt.xlabel('x')
plt.ylabel('y')

plt.plot(x_line, y_line)

plt.show()

print('chi^2 = ', calc_chi(x_s, y_s, errs, b, m))
```

Question 2.

```python
def spectrum_likelihood(wave_obs, spec_obs, errs, wave_theo, spec_theo):
    """Calculates the likelihood that a theoretical SED matches
    an observed SED

    Parameters:
    wave_obs - np array of observed wavelength values
    spec_obs - np array of corresponding observed flux values
    errs - np array of errors in observed flux values
    wave_theo - np array of theoretical wavelength values
    spec_theo - np array of corresponding theoretical flux values

    Returns:
    The likelihood, note that it is always less than 0
    """
    interpol_flux = []

    for i in range(len(wave_obs)):
        interpol_flux.append(linear_interpolation(wave_theo, spec_theo, wave_obs[i]))

    inter_flux = np.array(interpol_flux)

    return -np.sum(((spec_obs - inter_flux)**2)/(errs**2))
```

```python
wave_obs, spec_obs, errs = read_file('fitting_spectrum_1.txt')
create_plot(wave_obs, spec_obs, (10**3, 199600.0), r"$\lambda$ ($\AA$)", (10**-14, 10**-8), r"f$_v$  $(\mu Jy)$")
```

```python
wave_theo, spec_theo = generate_SED(0.1, 1)
spectrum_likelihood(wave_obs, spec_obs, errs, wave_theo, spec_theo)
```

    -3121221711061.225

```python
wave_obs, spec_obs, errs = read_file('fitting_spectrum_2.txt')

create_plot(wave_obs, spec_obs, (10**3, 99960.0), r"$\lambda$ ($\AA$)", (10**-12, 10**-7), r"f$_v$  $(\mu Jy)$")
```

```python
spectrum_likelihood(wave_obs, spec_obs, errs, wave_theo, spec_theo)
```

    -364732.5275292772

```python
def find_optimal_z_age(z_start, z_incr, z_end, age_start, age_incr, age_end, name):
    """Uses a brute force loop to estimate age and z that
    maximize the likelihood function

    Parameters:
    z_start - Starting z (the loop will start with z_start + z_incr)
    z_incr - Increment in z
    z_end - Value of z the loop will stop at
    age_start - Starting age (the loop will start with age_start + age_incr)
    age_incr - Increment in age
    age_end - Value of age the loop will stop at
    name - Name of file

    Returns:
    Returns tuple of the age and z that maximized the likelihood function
    """
    wave_obs, spec_obs, errs = read_file(name)
    max_likelihood = -10**10
    max_z = -1
    max_age = -1
    z = z_start

    while z <= z_end:
        z += z_incr
        age = age_start + age_incr
        while age <= age_end:
            wave_theo, spec_theo = generate_SED(age, z)
            likelihood = round(spectrum_likelihood(wave_obs, spec_obs, errs, wave_theo, spec_theo), 3)

            if likelihood > max_likelihood:
                max_likelihood = likelihood
                max_z = round(z, 3)
                max_age = round(age, 3)

            age += age_incr

    return max_age, max_z
```

```python
age, z = find_optimal_z_age(0.0, 0.2, 5, 0.0, 0.2, 10, 'fitting_spectrum_1.txt')
```

```python
generate_SED(age, z, plot=True, x_lim=(10**3, 199600.0), y_lim=(10**-14, 10**-8))
```

```python
find_optimal_z_age(0.0, 0.2, 5, 0.0, 0.2, 10, 'fitting_spectrum_2.txt')
```

```python
generate_SED(age, z, plot=True, x_lim=(10**3, 99960.0), y_lim=(10**-12, 10**-7))
```

Stretch Goals

Question 1.

```python
def log_likelihood(theta, x, y, yerr):
    """Calculates the log of the likelihood that the values
    match the model

    Parameters:
    theta - Tuple containing slope, y-int, and f parameter
    x - np array of x values
    y - np array of corresponding y values
    yerr - np array of corresponding errors in the y values

    Returns:
    Returns the log of the likelihood that the y values
    fit the model
    """
    m, b, f = theta
    model = m*x + b
    sigma2 = yerr**2 + (model**2)*(f**2)
    return -0.5*np.sum(((y - model)**2)/sigma2 + np.log(sigma2))
```

```python
def create_rand_pos(sig_1, mu_1, sig_2, mu_2, num_walkers):
    """Creates random position list for the walkers

    Parameters:
    sig_1 - Standard deviation for first parameter
    mu_1 - Mean value of first parameter
    sig_2 - Standard deviation for second parameter
    mu_2 - Mean value of second parameter
    num_walkers - Number of walkers

    Returns:
    2D np array contaning initial position for the walkers
    """
    array_1 = sig_1 * np.random.randn(num_walkers) + mu_1
    array_2 = sig_2 * np.random.randn(num_walkers) + mu_2

    lst_pos = []
    for i in range(len(array_1)):
        lst_pos.append([array_1[i], array_2[i]])

    return np.array(lst_pos)
```

```python
def prior(theta):
    """Calculates the prior probability that the values
    matches the model

    Parameters:
    theta - Tuple containing slope, y-int, and f parameter

    Returns:
    Returns the 1.0 if the parameters are within a
    reasonable range and 0.0 otherwise
    """
    m, b, f = theta
    if 2 < m < 12 and -15.0 < b < 15.0 and 0 < f < 100:
        return 1.0
    return 0.0
```

```python
def log_probability(theta, x, y, yerr):
    """Calculates the log of the probability that
    the values matches the model

    Parameters:
    theta - Tuple containing slope, y-int, and f parameter
    x - np array of x values
    y - np array of corresponding y values
    yerr - np array of corresponding errors in the y values

    Returns:
    Returns the sum of the log of the likelihood and the
    log of the prior
    """
    lp = np.log(prior(theta))
    if not np.isfinite(lp):
        return -np.inf
    return lp + log_likelihood(theta, x, y, yerr)
```

```python
pos = np.array([5.5597, -7.8453, 0.01]) + 1e-4 * np.random.randn(32, 3)
nwalkers, ndim = pos.shape

x_s, y_s, errs = read_file('fitting_simple_data.txt')

sampler = emcee.EnsembleSampler(nwalkers, ndim, log_probability, args=(x_s, y_s, errs))
sampler.run_mcmc(pos, 10000)
```

    /opt/python/3.7.3/lib/python3.7/site-packages/ipykernel_launcher.py:15: RuntimeWarning: divide by zero encountered in log
      from ipykernel import kernelapp as app

    (array([[ 6.33362698e+00, -1.37200425e+01,  1.45149347e-01],
            [ 4.87724734e+00, -4.41345382e+00,  5.03339197e-04],
            [ 5.18677888e+00, -7.60882649e+00,  1.02507701e-01],
            [ 5.61930880e+00, -8.81661778e+00,  4.80961776e-02],
            [ 6.18197208e+00, -1.09961192e+01,  1.23418185e-01],
            [ 5.36009131e+00, -8.25458980e+00,  1.58321003e-01],
            [ 5.42370403e+00, -7.20127370e+00,  3.72172467e-02],
            [ 5.11090425e+00, -4.63823283e+00,  5.97144207e-02],
            [ 5.72088020e+00, -8.88197850e+00,  1.01835833e-01],
            [ 4.41551692e+00, -4.78118874e+00,  9.53740988e-02],
            [ 4.65483016e+00, -4.82975797e+00,  7.53092708e-02],
            [ 4.53186657e+00, -5.10960341e+00,  3.51295579e-02],
            [ 5.67700421e+00, -1.08648392e+01,  2.09555479e-02],
            [ 5.49891283e+00, -9.06146337e+00,  7.43670114e-02],
            [ 5.27988338e+00, -5.68981036e+00,  8.18228956e-02],
            [ 5.17034194e+00, -7.61044009e+00,  9.95636655e-02],
            [ 5.32340016e+00, -7.65984673e+00,  1.50052518e-02],
            [ 4.92387988e+00, -4.59903583e+00,  1.00486380e-01],
            [ 4.47771095e+00, -6.81105987e+00,  4.96699167e-02],
            [ 4.62659793e+00, -7.12618442e+00,  1.05026658e-01],
            [ 5.59304083e+00, -6.86061482e+00,  1.49411046e-02],
            [ 5.80274766e+00, -1.02258515e+01,  1.31419203e-01],
            [ 5.72478276e+00, -9.78969236e+00,  1.39142569e-02],
            [ 6.16144718e+00, -1.02565345e+01,  4.93534264e-02],
            [ 5.32537445e+00, -7.04244627e+00,  7.72369962e-03],
            [ 4.81763085e+00, -7.61623322e+00,  3.76363337e-02],
            [ 4.60289168e+00, -6.26443515e+00,  7.17386923e-02],
            [ 5.56017185e+00, -7.91880942e+00,  1.47532848e-01],
            [ 4.79036013e+00, -4.78856931e+00,  1.37557717e-01],
            [ 6.26156554e+00, -1.26786146e+01,  2.00439391e-02],
            [ 5.11061046e+00, -7.29126002e+00,  7.41676848e-02],
            [ 5.69084570e+00, -8.97090962e+00,  7.26647312e-02]]),
     array([-298.3533462 , -292.98943435, -292.94998687, -292.53217902,
            -295.6272552 , -294.19615993, -292.31759039, -293.51736239,
            -293.49378134, -294.43918774, -293.39940019, -293.49201054,
            -293.456015  , -292.80781155, -293.32402788, -292.92017929,
            -292.04459625, -293.71550348, -294.93025331, -294.69416996,
            -292.93474954, -294.52449995, -292.68577695, -294.21700491,
            -292.09275635, -293.44648112, -293.83224996, -294.26537973,
            -294.20880273, -295.21205074, -292.57673195, -292.94266497]),
     ('MT19937', array([1466182691,  606170754, 2543722276, 1826066639,  211646718,
              865924328, 1923011466, 1839234061,  916819979, 4260035936,
             2845415622,  645459043, 3225688882, 1881327150,  817593334,
             3126063455, 4198567978,   55908045, 2133880683, 3084601428,
             2142101267, 1615178036, 2336478754, 2496556596, 1678550166,
             3587831194, 1985047484, 1507563058,  102112026,  304163543,
             3346731023, 1042896468,   93032331, 1258582899, 3208792930,
             2866889664, 2262710170, 2219459563, 2016095585, 2899209527,
             3855688204, 1568946856, 1848240861, 1131279054, 1113999611,
              208818819, 2913234401, 4142963675, 2129391538, 3591663181,
             2654516368, 1054290052, 2516388144, 3478700974, 2321464322,
              476471248, 3367747998, 2749007180, 3167541398, 2189248742,
             3141446016,  434880195, 3705660660, 1504693304, 3949043189,
             3376483493,  133683898, 1236067195, 2814292950, 1659130022,
             3041097961, 1375848395, 2990490829,  761537995,  329951961,
             4146046875, 1576702451, 2598946899, 2047060109, 1068245718,
              979173567, 1584342334, 2707814960, 3297997261, 2909365097,
             2445171886, 3398931861, 2232335257, 3980658073, 2930831251,
             2607494522, 2721398140, 1522385699, 1160859637, 1427204942,
              737473275, 2670961226,  646857846, 4089137396, 3592463882,
              630889616, 2681995034, 3082470739,  605709536, 3243818020,
             2503896657, 2580841657,  662645894, 2569216106,  910555551,
             2771059411, 3333106127, 2434178306, 3949871461, 3305942518,
             1858585655, 4071540626, 3834260109, 3451482896, 1255522083,
             2837203424, 2932835008, 3655587551, 3546786190, 1621131403,
             3689118226, 1899582298, 1309868446,  845238554, 3541042199,
             2237872646,  909030060, 2876518755, 2755295097,  852496782,
              841913933, 2529656646, 2308513285, 3602629451, 1268924281,
              744560714,  412288199, 1417255557, 1024414430, 2282289772,
              272930133,  715286195, 1825800419,  772141375, 2589319121,
              972259032,  692100825, 4244190884, 2684020575,  995964388,
               53605272, 1278253109,  163220217,  981154026,  698142553,
             3225006354, 3251941213, 1297959761, 3102769641, 2342527166,
              527680363,   40447491, 2067621853, 1307621064, 2302065349,
             1008750178,  445480243, 2834936264,  479574329, 3187285566,
             3286512906, 1195513341, 4022522346, 2541336011, 1875206631,
             1737515658, 4153602119, 2189425946, 2405886706, 1114248276,
              495137439, 2376896867,  411637893, 2269414888,  293560653,
             3638048433, 1685788835, 3377341984,  763618187, 1396231621,
             3494395605,  345257849, 1692789936, 3138188741, 1705908018,
             2321843982, 2726702755, 1369499360, 3217977102, 1472697886,
             1523393953, 1917009740, 1125051452, 4069327900, 4155468561,
             2339637318, 2374355854, 3240076502,  472306082, 1022386677,
              181392949, 2535154047,   49925435, 2221155113, 2004121065,
             2674330689, 1195663042,  728833916, 3202574626, 4105086968,
             3738319975, 3064272624, 1769115560,  756033159, 3126806902,
              214611932,  141257427, 1427186813, 3002553276, 3354054165,
             1625501757, 2763882227, 1059771794, 2699736342, 1829858487,
             3841958985, 2062794342, 1583448537, 3016737353, 1623796879,
             4109610571,  224636982, 2344136874,  964012439,  233414031,
             1509631193, 3274878183,  447068140,  184005732,   97903540,
              642563453, 1270511466, 2786408748,  989923568,  841676872,
             3540316021, 1804748809,   32657308, 2280509636, 1630641053,
              939836840, 3527214517, 3863507488, 3339213550,     941382,
             2322999271, 2046507992, 3438003850, 2863089719, 2667425856,
             1295826468, 2399353539, 1349708713, 4119348849, 1505474677,
             3073986386, 3484090396, 4078862043,  152196580, 1751134293,
              356529090,  556064050, 2105040800, 3465157277,  143803086,
             2672695234,  566121117, 3590024711, 2530069007, 2482761180,
             3994239247, 3353679149, 3760654881, 1524306228, 3015029269,
             1397278947, 3816287004, 1914076492, 2941607778,  461463870,
             1173025752, 3357474015, 3462898740, 1903357380, 4191812384,
             2810661116, 1297955145, 3988829794, 1752937208, 2767959309,
              726475778, 3509595818, 2186980455, 2669276576, 1176536384,
             1787515012, 1283714058, 2744922150,  400963417, 2148735165,
             1024472760, 2447894299, 3386271293, 1368080640, 1738155913,
             1121519406,  280837696, 1525774707, 2629202650,  189396436,
             1719059158, 3381191961,  599614686,  705612318,  201969062,
              722502921, 2236291909, 3038822125, 1049026884, 3688283166,
             2063214105,  201511604, 2300763253,  752140927, 2707231119,
             2643080028, 1269799859, 2307301494, 3524799704, 2834810490,
             1666365301, 2028387109,  693180019, 2474529056, 2417578980,
              505760047, 1126753697, 1082642571, 1996047864, 1568732791,
             2227817923,  920190655, 3757954098,  856781057,  433603288,
             1883453647, 3406272663, 1404225140, 1607405375,  678115555,
             3181799146,  221821424, 1827438536,   19791745, 1530694991,
             1301440313,  647326599, 3876743577,  452706458, 3709937779,
               73489335,   69062471, 3470900067, 1020946487,  333200848,
              739780538, 2058130451, 2854485982, 2208734626,  915837296,
              876655428, 3515638593, 1398805283, 2014262213, 1952760057,
             1836802215,  764721801, 1844965973, 1739002064,  977365137,
             1336533455, 2207204269,  738785288, 2158777903,  266003396,
             3491923254,  400238618,   41066087,  667351290, 3143678066,
             2755520370, 3822986875, 3143161056, 1088383330,  312013834,
             2689470618, 4256426849, 2185829825,  583217254, 3940144317,
             3114034826, 1665606372, 1156221216,  355532343, 3777073634,
             1788669923, 2031113294, 3239022394, 3735384727, 1194753031,
             1137969359, 2385064691, 2980715537,  195117552, 3909266176,
             3888659671,  743909867,  262850182, 3876244862, 3077139311,
              548140254, 4172980275, 3711627054, 3751678258, 1610174691,
             1823803146, 2015592703, 2683096289, 3734234899, 3672140798,
             3993390294, 3754106024, 2841641839, 4040541022, 3815592829,
             2929493679, 2271613577,  785494353, 2449946924, 2588909047,
             3413199132, 2400821800, 2555033918,  103013946, 3926938397,
             2416448213, 4187159074, 2216088980,  901723346, 1262332777,
             2783936431,  584134313,  700526710, 3651189482, 1033561285,
             2429640874, 1618594851, 3468328169, 2383208401, 1196601962,
             4286506431,  180479350, 1233779870, 3232011222, 4261282947,
             1372057305,  615877629, 1574709694, 3479382295, 3415279017,
             3163235975, 2745964691, 1996570682, 4231905913, 2146683743,
             3358899482,  218900534, 2239251134, 2062544713, 1962893861,
             3381663101, 4274733377, 2326174714, 2759016390, 3528031965,
             3605919922,  959385630, 1476600028, 3753823040, 3448393790,
             3175091973, 3151005953, 3987679569, 2475058839, 1370105074,
             4229127035,  769468910, 2424946524, 3351931721, 1653320952,
              421058743, 1571786486, 2693663119, 1784774671, 2295902906,
             3532811565, 2622804814,  366965330, 2480800590, 1235256892,
             2114409339, 4190380418, 2801789222, 1501754635, 2850720591,
             2974479289,  647244148, 3238900487, 1930775693, 2477578861,
              791112975, 4126575627, 4156752667, 3095764681, 4225473889,
             1316502486, 1927232114, 1481380279, 3687426790, 1385164702,
             1848597798,  339675871,  315069954, 1624400516, 3939577342,
             3427544177, 2203896583, 2013227218, 3845191842, 1414893465,
             3255730195,  792471328, 2768823846, 2032611874, 2952421264,
             4122553428, 4244188936,  484243516, 2733975040, 3887781582,
             2324468556, 3035986082, 1886541661, 2813810287, 4259512522,
             2911925671,  732846059, 3256550575, 1355705379, 4236909828,
             4093941084, 4244880699, 1222216488, 1918812273,  266520353,
             1896919105, 1558844531, 3331476082, 2090296822, 1790625131,
              681225801, 2002433872,  722386770, 1598803220, 2210191664,
             1947817185, 1422107590, 1236098313, 1850597042, 2701341332,
             2066026785,  782492487, 1337891038, 1431855099, 3608024889,
             1083964300, 2009472203,  632723507, 2003154257, 2721402347,
             2373400149, 2979117654, 2666297079, 3927960008, 1750217368,
             2071979224, 2934660547, 4270754306, 1664533903], dtype=uint32), 63, 0, 0.0))

```python
fig, axes = plt.subplots(3, figsize=(10, 7), sharex=True)
samples = sampler.chain
labels = ["m", "b", "f"]
for i in range(ndim):
    ax = axes[i]
    ax.plot(samples[:, :, i], "k", alpha=0.3)
    ax.set_xlim(0, len(samples))
    ax.set_ylabel(labels[i])
    ax.yaxis.set_label_coords(-0.1, 0.5)

axes[-1].set_xlabel("step number")
```

```python
print(sampler.get_autocorr_time())

flat_samples = sampler.chain[:, 40:, :].reshape((-1, ndim))
fig = corner.corner(flat_samples, labels=labels)
```

```python
for i in range(ndim):
    mcmc = np.percentile(flat_samples[:, i], [16, 50, 84])
    q = np.diff(mcmc)
    txt = "\mathrm{{{3}}} = {0:.3f}_{{-{1:.3f}}}^{{{2:.3f}}}"
    txt = txt.format(mcmc[1], q[0], q[1], labels[i])

    display(Math(txt))

    if i == 0:
        m_2 = mcmc[1]
    elif i == 1:
        b_2 = mcmc[1]
```

$\displaystyle \mathrm{m} = 5.261_{-0.455}^{0.444}$

$\displaystyle \mathrm{b} = -7.391_{-2.108}^{2.172}$

$\displaystyle \mathrm{f} = 0.052_{-0.037}^{0.056}$

```python
x_line = np.linspace(0, 12)
y_line = b_2 + m_2*x_line

plt.figure(figsize=(15, 9))
plt.scatter(x_s, y_s)
plt.errorbar(x_s, y_s, yerr=errs, fmt=".", capsize=0)

plt.plot(x_line, y_line)
plt.savefig("Data and line 2.pdf")
plt.show()

print('chi^2 = ', calc_chi(x_s, y_s, errs, b_2, m_2))
```

Question 3.

```python
def galaxy_prior(theta):
    """Calculates the prior probability that the values
    matches the model

    Parameters:
    theta - Tuple containing log(age) and z

    Returns:
    Returns the 1.0 if the parameters are within a
    reasonable range and 0.0 otherwise
    """
    log_age, z = theta
    if 0 < log_age < 5 and 0 < z < 2.0:
        return 1.0
    return 0.0
```

```python
def galaxy_probability(theta, wave_obs, spec_obs, errs):
    """Calculates the log of the probability that
    the values matches the model

    Parameters:
    theta - Tuple containing log(age) and z
    wave_obs - np array of observed wavelength values
    spec_obs - np array of corresponding observed flux values
    errs - np array of corresponding errors in the flux values

    Returns:
    Returns the sum of the log of the likelihood and the
    log of the prior
    """
    lp = np.log(galaxy_prior(theta))
    if not np.isfinite(lp):
        return -np.inf

    log_age, z = theta
    wave_theo, spec_theo = generate_SED(np.exp(log_age), z)
    return lp + spectrum_likelihood(wave_obs, spec_obs, errs, wave_theo, spec_theo)
```

```python
pos = create_rand_pos(1.0, 1.86, 0.5, 0.8, 16)
#pos = create_rand_pos(0.75, 1.22, 0.15, 0.2, 16)
nwalkers, ndim = pos.shape

wave, spec, yerr = read_file('fitting_spectrum_1.txt')
#wave, spec, yerr = read_file('fitting_spectrum_2.txt')

sampler = emcee.EnsembleSampler(nwalkers, ndim, galaxy_probability, args=(wave, spec, yerr))

sampler.run_mcmc(pos, 2500)
```

```python
fig, axes = plt.subplots(2, figsize=(10, 7), sharex=True)
samples = sampler.chain
labels = ["log(age)", "z"]
for i in range(ndim):
    ax = axes[i]
    ax.plot(samples[:, :, i], "k", alpha=0.3)
    ax.set_xlim(0, len(samples))
    ax.set_ylabel(labels[i])
    ax.yaxis.set_label_coords(-0.1, 0.5)

axes[-1].set_xlabel("step number")
```

```python
flat_samples = sampler.chain[:, 5:, :].reshape((-1, ndim))
fig = corner.corner(flat_samples, labels=labels)
```

```python
for i in range(ndim):
    mcmc = np.percentile(flat_samples[:, i], [16, 50, 84])
    q = np.diff(mcmc)
    txt = "\mathrm{{{3}}} = {0:.3f}_{{-{1:.3f}}}^{{{2:.3f}}}"
    txt = txt.format(mcmc[1], q[0], q[1], labels[i])

    display(Math(txt))

    if i == 0:
        m_2 = mcmc[1]
    elif i == 1:
        b_2 = mcmc[1]
```

Final Project/Computing Project Script.ipynb


# Predict gene knockout strategies

In cameo we have two ways of predicting gene knockout targets: using evolutionary algorithms (OptGene) or linear programming (OptKnock)

```python
from cameo import models
```

```python
model = models.bigg.e_coli_core.copy()
model.solver = "cplex"
```

```python
from cameo import phenotypic_phase_plane
```

```python
ppp = phenotypic_phase_plane(model, variables=[model.reactions.BIOMASS_Ecoli_core_w_GAM], objective=model.reactions.EX_ac_e)
ppp.plot()
```

## OptGene

OptGene is an approach to search for gene or reaction knockouts that relies on evolutionary algorithms[1]. The following image from authors summarizes the OptGene workflow.

Every iteration we keep the best 50 individuals so we can generate a library of targets.

```python
from cameo.strain_design.heuristic.evolutionary_based import OptGene
```

```python
optgene = OptGene(model)
```

```python
result = optgene.run(target="EX_ac_e",
                     biomass=model.reactions.BIOMASS_Ecoli_core_w_GAM,
                     substrate=model.metabolites.glc__D_e,
                     max_evaluations=5000,
                     plot=False)
```

    Starting optimization at Fri, 10 Jun 2016 06:38:55

    /opt/conda/envs/python3.4/lib/python3.4/site-packages/ipywidgets/widgets/widget_string.py:55: UserWarning:

    The Latex widget is deprecated. Use Label instead

    Finished after 00:00:11

```python
result
```

    /opt/conda/envs/python3.4/lib/python3.4/site-packages/ipywidgets/widgets/widget_string.py:55: UserWarning:

    The Latex widget is deprecated. Use Label instead

OptGene Result

Simulation: fba

Objective Function: $$bpcy = \frac{(BIOMASS\_Ecoli\_core\_w\_GAM * EX\_ac\_e)}{EX\_glc\_\_D\_e}$$

      reactions
      genes
      size
      fva_min
      fva_max
      target_flux
      biomass_flux
      yield
      fitness

      0
      (SUCDi, GND)
      ((b4090, b2296, b0723, b2029, b0724),)
      5.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      1
      (SUCDi, GND, GLUt2r)
      ((b0721, b3962, b4077, b2029, b2133),)
      5.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      2
      (FRD7, SUCDi, PGL, ADK1)
      ((b0474, b0767, b0118, b4154, b0721),)
      5.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      3
      (SUCDi, PGL, ADK1)
      ((b0767, b2492, b0903, b2464, b0474, b0721),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      4
      (FRD7, SUCDi, GND)
      ((b0903, b4152, b4232, b0724, b0723, b2029),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      5
      (SUCDi, GND, PPS)
      ((b1702, b2464, b0902, b0733, b0721, b2029), (...
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      6
      (SUCDi, GND, ADK1)
      ((b2464, b0474, b4395, b0721, b2935, b2029),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      7
      (FRD7, SUCDi, SUCOAS, GND)
      ((b0729, b4152, b1621, b1241, b0723, b2029),)
      6.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      8
      (SUCDi, GND, ME1)
      ((b1479, b1812, b0724, b1297, b0723, b2029),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      9
      (GLUSy, SUCDi, GND)
      ((b3952, b3212, b3925, b2464, b0721, b2029),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      10
      (GLUt2r, SUCDi, GND)
      ((b3386, b2464, b2133, b0721, b4077, b2029), (...
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      11
      (SUCDi, PGL, GND)
      ((b0767, b0722, b2464, b0118, b2935, b2029),)
      6.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      12
      (SUCDi, PGL)
      ((b0767, b0734, b0903, b2464, b0118, b0721, b4...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      13
      (SUCDi, GND, PPS)
      ((b1702, b3603, b2458, b0721, b0979, b0875, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      14
      (SUCDi, GND, PGL, ADK1)
      ((b0767, b0903, b2464, b0118, b0474, b0721, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      15
      (SUCDi, GND, FRUpts2)
      ((b1818, b0733, b4395, b0721, b0723, b2935, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      16
      (SUCDi, GND, AKGDH, ME2)
      ((b0727, b2463, b0724, b0721, b4090, b0723, b2...
      7.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      17
      (SUCDi, PGL, ADK1)
      ((b0767, b0903, b2464, b0118, b0474, b0721, b3...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      18
      (SUCDi, GND, AKGDH)
      ((b0727, b0451, b0724, b0721, b4090, b0723, b2...
      7.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      19
      (AKGDH, G6PDH2r, SUCDi, GND, PPS)
      ((b1702, b0727, b1852, b0721, b0875, b2029, b0...
      7.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      20
      (SUCDi, GND, ADK1)
      ((b3952, b0903, b2464, b0118, b0474, b0721, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      21
      (PGL, SUCDi)
      ((b0767, b0903, b2464, b0118, b1241, b0721, b4...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      22
      (ME1, SUCDi, GND, FRUpts2)
      ((b3925, b1818, b1479, b4395, b0723, b3115, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      23
      (GLUSy, ME1, SUCDi, GND)
      ((b3212, b1479, b0721, b1297, b0723, b3115, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      24
      (FRD7, SUCDi, GND, ME1)
      ((b0903, b4152, b1479, b0724, b0721, b0723, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      25
      (FRD7, SUCDi, GND, AKGDH)
      ((b4152, b0727, b0724, b0721, b4090, b0723, b2...
      7.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      26
      (FRD7, SUCDi, GND)
      ((b3603, b2133, b0451, b0721, b2417, b2029, b4...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      27
      (FRD7, ME1, SUCDi, GND)
      ((b4152, b1479, b1241, b0724, b0721, b0723, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      28
      (FRD7, GLUSy, ME1, SUCDi, GND)
      ((b3212, b4152, b1479, b0721, b1297, b0723, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      29
      (AKGDH, SUCDi, GND, PPS)
      ((b1702, b3603, b0727, b0721, b0875, b2029, b0...
      7.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      30
      (SUCDi, PGL, GND, ADK1)
      ((b0767, b2464, b0118, b0474, b0721, b2935, b2...
      7.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      31
      (SUCDi, PGL, GND)
      ((b0351, b0767, b0903, b2464, b0118, b0721, b2...
      8.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      32
      (FRD7, AKGDH, SUCDi, GND, ADK1)
      ((b0903, b4152, b2464, b0727, b0118, b0474, b0...
      8.0
      9.627604
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

      33
      (SUCDi, GND, ADK1)
      ((b0903, b2464, b0474, b0978, b0721, b4090, b3...
      8.0
      9.083537
      10.715737
      10.621957
      0.617037
      1.062196
      0.655414

```python
result.plot(0)
```

```python
result.display_on_map(0, "e_coli_core.Core metabolism")
```

  <link rel="stylesheet" href="//npmcdn.com/escher-vis@1.4.4/css/dist/builder.min.css"/>

## OptKnock

OptKnock uses a bi-level mixed integer linear programming approach to identify reaction knockouts[2]:

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

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

```python
optknock = OptKnock(model, fraction_of_optimum=0.1)
```

    Warning:  File contains basis.  Basis is loaded.

Running multiple knockouts with OptKnock can take a few hours or days...

```python
result = optknock.run(max_knockouts=1, target="EX_ac_e", biomass="BIOMASS_Ecoli_core_w_GAM")
```

    <IPython.core.display.Javascript object>

```python
result
```

    /opt/conda/envs/python3.4/lib/python3.4/site-packages/ipywidgets/widgets/widget_string.py:55: UserWarning:

    The Latex widget is deprecated. Use Label instead

OptKnock:

Target: EX_ac_e

      reactions
      size
      EX_ac_e
      biomass
      fva_min
      fva_max

      0
      {ATPS4r}
      1.0
      14.312267
      0.37423
      0.0
      14.369145

```python
result.plot(0)
```

```python
result.display_on_map(0, "e_coli_core.Core metabolism")
```

  <link rel="stylesheet" href="//npmcdn.com/escher-vis@1.4.4/css/dist/builder.min.css"/>

## References

[1]Patil, K. R., Rocha, I., Förster, J., & Nielsen, J. (2005). Evolutionary programming as a platform for in silico metabolic engineering. BMC Bioinformatics, 6, 308. doi:10.1186/1471-2105-6-308

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

## Exercises

* Use OptGene or OptKnock to find a growth coupled design for a product of your choice!

```python

```


```python
!pip install python-snap7==0.5
# va en version 1.1
import snap7.client as c
from snap7.util import * #set_int   set_bool
from snap7.snap7types import * # areas
import time
# !pip install keyboard
import keyboard  # using module keyboard
import os
import math
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
```

    Requirement already satisfied: python-snap7==0.5 in c:\users\usuario\.conda\envs\sistdin\lib\site-packages (0.5)

# Excitación del módulo  multivariable SIEMENS del PCJIC

En el laboratorio de automática del Politécnico Colombiano Jaime Isaza Cadavid se encuentra un módulo multivariable que está controlado con un PLC  SIEMENS. Por esta razón, se utiliza el módulo `SNAP7` para interactuar con el PLC desde el notebook.

Para instalar `SNAP7`:
- Use la instrucción
~~~
!pip install python-snap7==0.5
~~~

- Descargue la librería completa de [https://sourceforge.net/projects/snap7/files/1.2.1/](https://sourceforge.net/projects/snap7/files/1.2.1/)

- Copie `snap7.dll` y `snap7.lib` a su carpeta `System32`.

## 1. Establezca conexión con el PLC.

El PLC tiene configurada la dirección IP **192.168.0.1**. Este funcionará como un servidor al cuál debe conectar su PC configurándole una dirección estática dentro de la subred del PLC.  Su dirección puede ser **192.168.0.2**, por ejemplo. Una vez su equipo haya establecido comunicación con el PLC, establezca conexión desde el notebook con las siguientes instrucciones.

```python
#Conexión con el PLC
plc = c.Client()
IP_PLC = '192.168.0.1'
```

```python
try:
    plc.connect(IP_PLC,0,1)
    print("Conectado")
except:
    print("Algo no funcionó")
```

    Conectado

## 2. Identifique actuadores y sensores.

Con `SNAP7` puede manipular los actuadores del sistema y capturar los datos de los sensores. Se han construido algunas funciones para facilitar la interacción con el PLC.

Tenga en cuenta la siguiente información, que le orientará en cómo se ha conectado el sistema al PLC.

|Elemento   |  Punto  |
|-----------|---------|
| Presión 	|    IW96 |
| Flujo		|    IW98 |
| Nivel		|   IW100 |
| Run Bomba	|    Q0.5 |
| Bomba		|    QW80 |
| Válvula H20 |  QW96 |

```python
#ESCRIBIR SALIDA BOOLEANA
def escr_sal_bool(byte, bit, valor):
    lectura = plc.ab_read(byte, bit)
    set_bool(lectura, byte, bit, valor)
    plc.ab_write(0, lectura)
#escr_sal_bool(0,1,1)

#ESCRIBIR SALIDA ENTERO
def escr_sal_ent(byte,valor):
    lectura = plc.read_area(areas['PA'], 0, byte, 2) #PA: salidas, 0: bloque de datos, dirección, # bytes.
    # print(lectura)
    set_int(lectura, 0, valor)  # se da formato al valor deseado, en este caso entero
    plc.write_area(areas['PA'], 0, byte, lectura)  # Escribe en la dirección definida
#escr_sal_ent(90,9000)

#LEER MARCA ENTERA
def leer_ent_ent(byte):
    leer = plc.read_area(areas['PE'],0,byte,2) #PE: entradas, 0: bloque de datos, dirección, # bytes.
    leer_ent = get_int(leer,0) #Comando get_int(_bytearray, byte_index)
    return leer_ent

def AbrirValvulaQW96(valvulap):
    # Abre la valvula en QW96 a un porcentaje determinado
    valvula = ((7800 / 71.5) * (valvulap - 7.4)) + 6200
    escr_sal_ent(96,valvula)

def BombaQW80(motobombaHz):
    #Poner bomba en 60Hz salida QW80
    motobomba = motobombaHz * (22118 / 60) + 5530
    escr_sal_ent(80,motobomba)

def leerNivelPLCIW100():
    nivelplc = leer_ent_ent(100)
    nivelcm = ((60 / 15105) * (nivelplc - 10125)) + 20
    # print(nivelplc, nivelcm)
    return nivelcm

def leerPresionPLCIW96():
    presionplc = leer_ent_ent(96)
    # 0.64 5679
    # 0.4  5623
    mp = (0.64-0.4)/(5679-5623)
    presion =  mp* presionplc - mp *5623+0.4
    # print(presionplc, presion)
    return presion

def leerFlujoPLCIW98():
    flujoplc = leer_ent_ent(98)
    # 4.67 8773
    # 16.52  16957
    mf = (4.67-16.52)/(8773-16957)
    flujo =  mf* flujoplc - mf *16957+16.52
    # print(flujoplc, flujo)
    return flujo

def leerNada():
    return np.random.rand(1)[0]
```

## 3. Inicie el experimento.

El módulo multivariable tiene:
- 2 actuadores:
    - Motobomba
    - Válvula
- 3 sensores:
    - Flujo
    - Presión
    - Nivel

3.1 Escoja una variable como salida y un actuador para su experimento.

3.2 Ponga un punto de operación para el otro actuador.

**Establecer un punto para la bomba**
~~~
BombaQW80(60)
~~~

**Establecer un punto para la válvula**
~~~
AbrirValvulaQW96(100)
~~~

```python
escr_sal_bool(0,5,1) #Habilitar RUN motobomba
# Complete para establecer un punto de operación
BombaQW80(20)
AbrirValvulaQW96(100)
```

    bytearray(b']\x99')
    bytearray(b'\x15\x10')

## 5. Defina la entrada del sistema

Esta vez se configura una señal de referencia $r(t)$ en forma de un escalón.

\begin{equation}
r(t) = \begin{cases} V_0 & \forall t<t_0 \\  V_f &  \forall t > t_0 \end{cases}
\end{equation}

Defina:
- Duración para el experimento $t_f$
- Valor inicial para el actuador $V_0$
- Valor final para el actuador $V_f$

Recuerde el efecto de discretizar las señales con un tiempo de muestreo $t_m$.

**Lista de tiempos $t$**

```python
tm = 1
tf = 20
t = np.linspace(0.0 ,tf, round(tf/tm) + 1)

```

**Señal de entrada $U(t)$**

```python
t0 = 30
v0 = 20
vf = 60
r = np.where(t>=t0,vf,v0)

```

**Gráfica de la señal**

```python
plt.plot(t,r)
```

## 6. Realimente el sistema y varíe $k$

Defina un valor $k$.

- Use los valores definidos para $U(t)$ y las lecturas $y(t)$ del sensor para calcular el error $e(t)$ como:

$$e(t)=U(t)-y(t)$$

- Envíe hacia el actuador una señal $u(t)$ generada a partir de la señal $e(t)$ como:
$$u(t) = k \cdot e(t)$$

- Guarde la información necesaria. Para esto puede crear un archivo `csv` a partir de un **dataframe**.

Para leer los sensores se han definido las siguientes funciones:

~~~
leerNivelPLCIW100()
leerPresionPLCIW96()
leerFlujoPLCIW98()
~~~

```python
# Define k
k = 0.1

##

y = []
e = []
u = []
print('Para abortar el experimento presione ESPACIO')
abortado = False
for idt,ti in enumerate(t):
    ref_t = r[idt] # Valor de referencia
    y_t = leerPresionPLCIW96() # Salda del sistema
    e_t = ref_t - y_t # error
    u_t = k*e_t  # Señal de salida del controlador
    BombaQW80(u_t) # Salida hacia actuador
    y.append(y_t)
    e.append(e_t)
    u.append(u_t)

    if keyboard.is_pressed(' '):
        print('Abortando el experimento a los ' +
              str(ti) +' segundos')
        abortado = True
        escr_sal_bool(0,5,0)
        BombaQW80(5)
        AbrirValvulaQW96(0)

        break
    time.sleep(tm)

if abortado:
    y = y + [np.nan]*(len(r)-len(y))
    e = e + [np.nan]*(len(r)-len(e))
    u = u + [np.nan]*(len(r)-len(u))
d = {'Tiempo': t, 'Referencia': r, 'Error': e, 'Señal de control': u,'Salida': y }

df = pd.DataFrame(data=d)
df.head()
escr_sal_bool(0,5,0)
AbrirValvulaQW96(0)

# Gráfica de entrada y salida
plt.plot(df["Tiempo"],df["Referencia"],
         df["Tiempo"],10*df["Salida"])
```

    Para abortar el experimento presione ESPACIO
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'2f')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    bytearray(b'l\x00')
    Abortando el experimento a los 359.0 segundos
    bytearray(b'l\x00')
    bytearray(b'?\xad')
    bytearray(b'\x15\x10')

```python
# Guarda datos
archivo = "Datos/misDatos" + str(k) + ".csv"
df.to_csv(archivo, index=False)
```

## 6. Compare con el LGDR a partir del modelo

- Grafique el **Lugar Geométrico de las Raíces** a partir del modelo que obtuvo anteriormente para el sistema.
- Obtenga los polos y ceros de lazo cerrado para los valores $k$ que utilizó en el punto anterior.
- Compare las respuestas experimentales y las relacionadas con las raíces según el modelo.

```python
## Escriba su código
```

## Conclusiones
- Aquí
- Otra

```python

```