# Construcción Curva OIS

```python
from IPython.display import Image
```

En el notebook anterior vimos que el valor de un derivado perfectamente colateralizado se obtiene descontando su payoff con un factor de descuento que proviene de la curva asociada a la tasa de remuneración del colateral.

Dicha tasa de remuneración es, en la gran mayoría de los casos, una tasa overnight. En el caso USD, para los CSA se utiliza *Effective Fed Funds* mientras que las contrapartes centrales están migrando a *SOFR*.

A continuación se verá cómo construir la curva asociada a *SOFR* a partir de cotizaciones de swaps de *SOFR* versus tasa fija. El procedimiento se conoce como **bootstrapping** por el hecho que la construcción de la curva se hace de forma incremental, partiendo de una solución parcial de fácil obtención.

## Bootstrapping para OIS

Tenemos que (notebook 4):

$$
\begin{equation}
\Pi\left(t,X\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T\right)\Pi\left(T,X\right)\right]
\end{equation}
$$

$$
\begin{equation}
D_c\left(t,T\right)=\exp\left[-\int_t^Tr_c\left(u\right)du\right]
\end{equation}
$$

$$
\begin{equation}
\mathbb{E}_t^{Q_f}\left[D_c\left(t,T\right)\right]=P_c\left(t,T\right)
\end{equation}
$$

$$
\begin{equation}
D_c\left(t,T\right)={B_c\left(t,T\right)}^{-1}
\end{equation}
$$

Consideremos el valor de la pata fija de un OIS. Para fijar las ideas, supongamos una pata a 2Y con cupones anuales. Suponemos, adicionalmente que el valor del nocional es 1 y que éste se paga al vencimiento. El valor de la pata fija es:

$$
\begin{equation}
\Pi^{fija}\left(t\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_1\right)\cdot r\cdot\frac{T_1-T_0}{360}+D_c\left(t,T_2\right)\left(1+r \cdot \frac{T_2-T_1}{360}\right)\right]
\end{equation}
$$

$$
\begin{equation}
\Pi^{fija}\left(t\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_1\right)\right]\cdot r\cdot\frac{T_1-T_0}{360}+\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_2\right)\right]\left(1+r \cdot \frac{T_2-T_1}{360}\right)
\end{equation}
$$

$$
\begin{equation}
\Pi^{fija}\left(t\right)=P_c\left(t,T_1\right)\cdot r\cdot\frac{T_1-T_0}{360}+P_c\left(t,T_2\right)\left(1+r \cdot \frac{T_2-T_1}{360}\right)
\end{equation}
$$

Vemos que la expresión (7) corresponde a la suma de:

- primer cupón de intereses traído a valor presente con el factor de descuento entre $t$ y $T_1$
- segundo cupón de intereses y nocional traído a valor presente con el factor de descuento entre $t$ y $T_2$

Donde $T_0$ representa la fecha en que el primer cupón del swap comienza a devengar intereses (usualmente 2 días hábiles después de la fecha de celebración del contrato).

Por otro lado, el valor de la pata flotante es:

$$
\begin{equation}
\Pi^{flot}\left(t\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_1\right) \cdot \left(\frac{B_c\left(t,T_1\right)}{B_c\left(t,T_0\right)}-1\right)\right]+\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_2\right) \cdot \frac{B_c\left(t,T_2\right)}{B_c\left(t,T_1\right)}\right]
\end{equation}
$$

Aplicando (4) se obtiene:

$$
\begin{equation}
\Pi^{flot}\left(t\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_0\right)-D_c\left(t,T_1\right)\right]+\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_1\right)\right]
\end{equation}
$$

Utilizando el hecho que el valor esperado es un operador lineal se llega a:

$$
\begin{equation}
\Pi^{flot}\left(t\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_0\right)\right]=P_c\left(t,T_0\right)
\end{equation}
$$

Si se supone que $t=T_0$ tenemos que:

$$
\begin{equation}
P_c\left(t,T_0\right)=P_c\left(T_0,T_0\right)=1
\end{equation}
$$

Y por lo tanto, utilizando (7) y (10) se deduce que:

$$
\begin{equation}
\Pi^{fija}\left(T_0\right)=P_c\left(t_0,T_1\right)\cdot r\cdot\frac{T_1-T_0}{360}+P_c\left(t_0,T_2\right)\left(1+r \cdot \frac{T_2-T_1}{360}\right)=1
\end{equation}
$$

O sea, el valor presente de los flujos de la pata fija, considerando que el nocional se paga al vencimiento y utilizando los factores de descuento de la curva OIS es 1 (más generalmente es igual al nocional si se considera un nocional distinto de 1).

La fórmula (12) se puede extender fácilmente a los OIS de cualquier vencimiento y la podemos escribir, en forma general, de la siguiente manera:

$$
\begin{equation}
\Pi^{fija,tenor}\left(T_0\right)=\sum\limits_{j=1}^{m-1}P_c\left(T_0,T_j\right)\cdot r\cdot\frac{T_j-T_{j-1}}{360}+P_c\left(T_m,T_{m-1}\right)\cdot\left(1+r\cdot\frac{T_m-T_{m-1}}{360}\right)=1
\end{equation}
$$

Donde $tenor$ representa el plazo del swap (6M, 1Y, 5Y, ...) y $m$ es el número de cupones del swap. Por ejemplo, en un swap con $tenor=5Y$ y periodicidad anual, $m=5$. Para el mismo $tenor$, si la periodicidad es semestral, entonces $m=10$.

La ecuación (13), por si sola, no sería muy útil si no existiera toda una familia de OIS a distintos plazos para los cuales puedo observar su tasa fija en un momento dado del tiempo. Por ejemplo, para los OIS de *SOFR*, se dispone del siguiente panel de precios durante las horas en que estos productos se transan (hay incluso más plazos, pero no se ven todos en esta imagen).

```python
Image(url="img/20201015_sofr_swaps.gif", width=900, height=720)
```

Los primeros 13 swaps son cero cupón, y por lo tanto, las ecuaciones correspondientes son muy sencillas:

$$
\begin{equation}
\Pi^{fija,tenor}\left(T_0\right)=P_c\left(T_0,T_{tenor}\right)\cdot\left(1+r\cdot\frac{T_{tenor}-T_0}{360}\right)=1
\end{equation}
$$

donde $tenor\in\ \left\{ 1W, 2W, 3W, 1M, 2M, 3M, 4M, 5M, 6M, 9M, 10M, 11M,1Y \right\}$.

Al swap cuyo $tenor=18M$ le corresponde la siguiente ecuación:

$$
\begin{equation}
\Pi^{fija,18M}\left(T_0\right)=P_c\left(T_0,T_1\right)\cdot r\cdot\frac{T_1-T_{0}}{360}+P_c\left(T_0,T_{2}\right)\cdot\left(1+r\cdot\frac{T_2-T_1}{360}\right)=1
\end{equation}
$$

En (15), gracias a las ecuaciones (14), el único término desconocido es $P_c\left(T_0,T_2\right)$ y por lo tanto, se puede resolver. Esto mismo se repite para los swaps cuyo $tenor \in \left\{ 2Y,3Y,4Y,5Y \right\}$.

Al swap cuyo $tenor=10Y$ le corresponde la siguiente ecuación:

$$
\begin{equation}
\Pi^{fija,10Y}\left(T_0\right)=\sum\limits_{j=1}^{9}P_c\left(T_0,T_j\right)\cdot r\cdot\frac{T_j-T_{j-1}}{360}+P_c\left(T_0,T_{10}\right)\cdot\left(1+r\cdot\frac{T_{10}-T_9}{360}\right)=1
\end{equation}
$$

En (16), los términos $P_c\left(T_0,T_j\right)$ con $j\in \left\{6,7,8,9,10\right\}$y son desconocidos, lo que nos fuerza a imponer alguna condición adicional que reduzca el número de incógnitas a 1. La condición que se impone es la siguiente:

$$
\begin{equation}
P_c\left(T_0,T_j\right)=g\left(T_j, P_c\left(T_0,T_5\right),  P_c\left(T_0,T_{10}\right)\right)
\end{equation}
$$

Donde $j\in\left\{6,7,8,9\right\}$ y $g$ es algún tipo de interpolación. Un razonamiento similar debe aplicarse al swap cuyo $tenor=20Y$.

### Ejercicio

Demuestre que, para valorizar, el valor esperado en $t$ de un flujo entre $T_1$ y $T_2$ de la pata flotante se puede calcular como:

$$
\begin{equation}
nominal\cdot r_{T_1,T_2}\cdot\frac{T_2-T_1}{360}
\end{equation}
$$

donde

$$
\begin{equation}
r_{T_1,T_2}=\left( \frac{P_c\left(t, T_1\right)}{P_c\left(t, T_2\right)}-1\right)\cdot\frac{360}{T_2-T_1}
\end{equation}
$$

Por una parte tenemos que (considerando $nominal=1$):

$$
\begin{equation}
\Pi^{flot}\left(t,T_1,T_2\right)=\mathbb{E}_t^{Q_f}\left[D_c\left(t,T_2\right)\left(\frac{B_c\left(t,T_2\right)}{B_c\left(t,T_1\right)}-1\right)\right]=P_c\left(t,T_1\right)-P_c\left(t,T_2\right)
\end{equation}
$$

Por otra parte también sabemos que (siempre con $nominal=1$):

$$
\begin{equation}
\Pi^{flot}\left(t,T_1,T_2\right)=P_c\left(t,T_2\right)\cdot\mathbb{E}_t^{Q_f^{T_2}}\left[ON\left(T_1,T_2\right)\cdot\frac{T_2-T_1}{360}\right]
\end{equation}
$$

Donde $ON\left(T_1,T_2\right)$ es la tasa equivalente al producto de los factores de capitalización de la tasa overnight asociada al OIS entre $T_1$ y $T_2$.

Juntando (20) y (21) llegamos a:

$$
\begin{equation}
\frac{T_2-T_1}{360}\cdot\mathbb{E}_t^{Q_f^{T_2}}\left[ON\left(T_1,T_2\right)\right]=\frac{P_c\left(t,T_1\right)-P_c\left(t,T_2\right)}{P_c\left(t,T_2\right)}
\end{equation}
$$

Por lo tanto, si denotamos $\mathbb{E}_t^{Q_f^{T_2}}\left[ON\left(T_1,T_2\right)\right]=r_{T_1,T_2}$ (22) implica (18) utilizando $nominal=1$. Es evidente que el resultado se generaliza para cualquier $nominal>0$.

## Implementación

En lo que sigue, se implementa el **bootstrapping** para swaps de *SOFR* versus tasa fija.

### Librerías

```python
from finrisk import QC_Financial_3 as Qcf
from scipy.optimize import root_scalar
import modules.auxiliary as aux
import pandas as pd
import math
```

### Variables Globales

Se da de alta un objeto de tipo `Qcf.BusinessCalendar`y se agregan los feriados relevantes para la implementación.

```python
bus_cal = Qcf.BusinessCalendar(Qcf.QCDate(1, 1, 2020), 20)
for agno in range(2020, 2071):
    f = Qcf.QCDate(12, 10, agno)
    if f.week_day() == Qcf.WeekDay.SAT:
        bus_cal.add_holiday(Qcf.QCDate(14, 10, agno))
        # pass
    elif f.week_day() == Qcf.WeekDay.SUN:
        bus_cal.add_holiday(Qcf.QCDate(13, 10, agno))
    elif f.week_day() == Qcf.WeekDay.MON:
        bus_cal.add_holiday(Qcf.QCDate(12, 10, agno))
    elif f.week_day() == Qcf.WeekDay.TUE:
        bus_cal.add_holiday(Qcf.QCDate(11, 10, agno))
    elif f.week_day() == Qcf.WeekDay.WED:
        bus_cal.add_holiday(Qcf.QCDate(10, 10, agno))
    elif f.week_day() == Qcf.WeekDay.THU:
        bus_cal.add_holiday(Qcf.QCDate(9, 10, agno))
    else:
        bus_cal.add_holiday(Qcf.QCDate(8,10, agno))
bus_cal.add_holiday(Qcf.QCDate(15, 2, 2021))
```

### Funciones

```python
def make_fixed_leg(start_date: str, tenor: str, rate_value: float) -> Qcf.Leg:
    """
    Construye un pata fija con algunos parámetros prefijados:

    - recibo o pago: R
    - nocional: 1,000,000
    - moneda: USD
    - periodicidad: 1Y
    - business adjustment rule: MODFOLLOW
    - stub period: SHORTBACK

    params:

    - start_date: fecha inicial de la pata en formato ISO
    - tenor: plazo estructurado de la pata (1Y, 2Y, 18M, ...)
    - rate_value: valor de la tasa fija

    return:

    - objeto `Qcf.Leg` con cashflows de tipo `FixedRateCashflow`
    """
    # Recibo o pago los flujos de esta pata
    rp = Qcf.RecPay.RECEIVE

    # Periodicidad de pago
    periodicidad = Qcf.Tenor('1Y')

    # Tipo de período irregular (si lo hay)
    periodo_irregular = Qcf.StubPeriod.SHORTFRONT

    # Regla para ajustes de días feriados
    bus_adj_rule = Qcf.BusyAdjRules.MODFOLLOW

    # Número de días después de la fecha final de devengo en que se paga el flujo
    lag_pago = 0

    # Nocional del contrato
    nocional = 10000000.0

    # Considera amortización
    amort_es_flujo = True

    # Moneda
    moneda = Qcf.QCUSD()

    # Es un bono de RF
    es_bono = False

    # Fecha de inicio de devengo del primer cupón
    fecha_inicial = Qcf.build_qcdate_from_string(start_date)

    # Fecha final de devengo, antes de ajustes, del último cupón
    qc_tenor = Qcf.Tenor(tenor)
    meses = qc_tenor.get_years()*12 + qc_tenor.get_months()
    if meses > 0:
        fecha_final = fecha_inicial.add_months(meses)
    else:
        dias = qc_tenor.get_days()
        fecha_final = fecha_inicial.add_days(dias)

    valor_tasa = rate_value
    tasa_cupon = Qcf.QCInterestRate(
        valor_tasa, Qcf.QCAct360(), Qcf.QCLinearWf())

    # Se da de alta el objeto y se retorna
    return Qcf.LegFactory.build_bullet_fixed_rate_leg(
        rp,
        fecha_inicial,
        fecha_final,
        bus_adj_rule,
        periodicidad,
        periodo_irregular,
        bus_cal,
        lag_pago,
        nocional,
        amort_es_flujo,
        tasa_cupon,
        moneda,
        es_bono)
```

```python
def error_with_rate(rate: float, *args) -> float:
    """
    Calcula el error entre el valor presente de una pata fija calculado con una curva cero y el nocional
    de la pata fija. Esta función está diseñada para servir de apoyo al proceso de bootstrapping de una curva
    cero cupón de OIS.

    params:

    - rate: valor a utilizar para la última tasa de la curva
    - args: iterable con plazos de la curva, tasas de la curva, objeto Qcf.Leg a valorizar y fecha de
    valorización.

    return:

    - float con el monto del error.
    """
    # Para usar la librería, los plazos y tasas deben estar en estos formatos.
    # Recordar que la librería está escrita en C++.
    plazos = Qcf.long_vec()
    for p in args[0]:
        plazos.append(p)

    tasas = Qcf.double_vec()
    for t in args[1]:
        tasas.append(t)

    pata = args[2]

    fecha_val = args[3]

    # Se construye una curva cero Qcf
    curva = Qcf.QCCurve(plazos, tasas)
    cuantos = curva.get_length()
    par = curva.get_values_at(cuantos - 1)
    curva.set_pair(par.tenor, rate) # La variable rate se transforma en la última tasa de la curva
    curva = Qcf.QCLinearInterpolator(curva)
    curva = Qcf.ZeroCouponCurve(curva, Qcf.QCInterestRate(
        0.0, Qcf.QCAct365(), Qcf.QCContinousWf()))

    # Se da de alta el objeto que calcula valores presentes
    vp = Qcf.PresentValue()

    # Se retorna la diferencia entre el vp calculado con la curva y el nominal
    return vp.pv(fecha_val, pata, curva) - pata.get_cashflow_at(0).get_nominal()
```

### Formatos `DataFrame`

```python
frmt = {'rate_value': '{:.8%}', 'valor_tasa': '{:.8%}',
        'nominal': '{:,.2f}', 'amortizacion': '{:,.2f}',
        'interes': '{:,.2f}', 'flujo': '{:,.2f}'
       }
```

### Obtiene Data

```python
swaps = pd.read_excel('data/20201012_sofr_zero.xlsx', sheet_name='upload')
swaps['start_date'] = swaps['start_date'].astype(str).str[:10]
```

```python
swaps.style.format(frmt)
```

        	        start_date	        tenor	        rate_value	        rate_type	        periodicity	        stub_period

                        0
                        2020-10-14
                        1D
                        0.08000000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        1
                        2020-10-14
                        7D
                        0.08290000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        2
                        2020-10-14
                        14D
                        0.07690000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        3
                        2020-10-14
                        21D
                        0.07630005%
                        LINACT360
                        1Y
                        SHORTFRONT

                        4
                        2020-10-14
                        1M
                        0.07700000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        5
                        2020-10-14
                        2M
                        0.07700000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        6
                        2020-10-14
                        3M
                        0.08000000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        7
                        2020-10-14
                        4M
                        0.07700000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        8
                        2020-10-14
                        5M
                        0.07500000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        9
                        2020-10-14
                        6M
                        0.07400000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        10
                        2020-10-14
                        7M
                        0.07260000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        11
                        2020-10-14
                        8M
                        0.07149995%
                        LINACT360
                        1Y
                        SHORTFRONT

                        12
                        2020-10-14
                        9M
                        0.07100000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        13
                        2020-10-14
                        10M
                        0.07040000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        14
                        2020-10-14
                        11M
                        0.06990000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        15
                        2020-10-14
                        12M
                        0.06929995%
                        LINACT360
                        1Y
                        SHORTFRONT

                        16
                        2020-10-14
                        18M
                        0.06330000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        17
                        2020-10-14
                        2Y
                        0.05600000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        18
                        2020-10-14
                        3Y
                        0.07400000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        19
                        2020-10-14
                        4Y
                        0.12400000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        20
                        2020-10-14
                        5Y
                        0.19100000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        21
                        2020-10-14
                        6Y
                        0.27300000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        22
                        2020-10-14
                        7Y
                        0.35400000%
                        LINACT360
                        1Y
                        SHORTFRONT

                        23
                        2020-10-14
                        8Y
                        0.43000001%
                        LINACT360
                        1Y
                        SHORTFRONT

                        24
                        2020-10-14
                        9Y
                        0.50099999%
                        LINACT360
                        1Y
                        SHORTFRONT

                        25
                        2020-10-14
                        10Y
                        0.56800002%
                        LINACT360
                        1Y
                        SHORTFRONT

                        26
                        2020-10-14
                        12Y
                        0.67400002%
                        LINACT360
                        1Y
                        SHORTFRONT

                        27
                        2020-10-14
                        15Y
                        0.78299999%
                        LINACT360
                        1Y
                        SHORTFRONT

                        28
                        2020-10-14
                        20Y
                        0.89499998%
                        LINACT360
                        1Y
                        SHORTFRONT

                        29
                        2020-10-14
                        25Y
                        0.93455002%
                        LINACT360
                        1Y
                        SHORTFRONT

                        30
                        2020-10-14
                        30Y
                        0.95700002%
                        LINACT360
                        1Y
                        SHORTFRONT

                        31
                        2020-10-14
                        40Y
                        0.92200002%
                        LINACT360
                        1Y
                        SHORTFRONT

                        32
                        2020-10-14
                        50Y
                        0.84599999%
                        LINACT360
                        1Y
                        SHORTFRONT

### Construye Objetos `Qcf`

```python
qc_swaps = []
for s in swaps.itertuples():
    leg = make_fixed_leg(s.start_date, s.tenor, s.rate_value)
    cuantos_flujos = leg.size()
    last_pmt_date = leg.get_cashflow_at(cuantos_flujos - 1).get_settlement_date()
    start_date = Qcf.build_qcdate_from_string(s.start_date)
    plazo = start_date.day_diff(last_pmt_date)
    qc_swaps.append((leg, plazo))
```

```python
aux.show_leg(qc_swaps[16][0], 'FixedRateCashflow', '').style.format(frmt)
```

        	        fecha_inicial	        fecha_final	        fecha_pago	        nominal	        amortizacion	        interes	        amort_es_flujo	        flujo	        moneda	        valor_tasa	        tipo_tasa

                        0
                        2020-10-14
                        2021-04-14
                        2021-04-14
                        10,000,000.00
                        0.00
                        3,200.17
                        True
                        3,200.17
                        USD
                        0.06330000%
                        LinAct360

                        1
                        2021-04-14
                        2022-04-14
                        2022-04-14
                        10,000,000.00
                        10,000,000.00
                        6,417.92
                        True
                        10,006,417.92
                        USD
                        0.06330000%
                        LinAct360

### Construye Curva Cero Cupón

Demuestra función objetivo.

```python
plazos = []
tasas = []

plazos.append(1) # Los plazos se calculan desde start_date (2020-10-14)
tasa = math.log(1 + .08 / 100 * 1 / 360)* 365.0 / 1
# 1 + .08% * 1/360 = exp(r * 1/365) -> log(1 + .08% * 1/360) * 365/1
tasas.append(tasa)

plazos.append(int(7))
tasas.append(.0)

# Puedo valorizar cualquiera de los swaps ya dado de alta.
error_with_rate(.0008, plazos, tasas, qc_swaps[1][0], Qcf.QCDate(14, 10, 2020))
```

    7.768488567322493

```python
plazos = []
tasas = []

plazos.append(1) # Los plazos se calculan desde start_date (2020-10-14)
tasa = math.log(1 + .08 / 100 * 1 / 360)* 365.0 / 1
# 1 + .08% * 1/360 = exp(r * 1/365) -> log(1 + .08% * 1/360) * 365/1
tasas.append(tasa)

for s in qc_swaps[1:]:
    plazos.append(s[1])
    tasas.append(0.0)

    x = root_scalar(
        error_with_rate,
        method='bisect',
        bracket=[0.0, .02],
        x0=.0008,
        args=(plazos, tasas, s[0], Qcf.QCDate(14, 10, 2020)),
        xtol=.00000000000000001
    )

    tasas[-1] = x.root
```

```python
for_df = []
for p, t in zip(plazos, tasas):
    for_df.append((p,t))
df_curva = pd.DataFrame(for_df, columns=['plazo', 'tasa'])
df_curva['df'] = df_curva.apply(lambda row: math.exp(-row['plazo']*row['tasa']/365), axis=1)
df_curva.style.format({'tasa':'{:.8%}', 'df':'{:.8%}'})
```

        	        plazo	        tasa	        df

                        0
                        1
                        0.08111102%
                        99.99977778%

                        1
                        7
                        0.08405071%
                        99.99838808%

                        2
                        14
                        0.07796689%
                        99.99700953%

                        3
                        21
                        0.07735805%
                        99.99554936%

                        4
                        33
                        0.07806669%
                        99.99294216%

                        5
                        61
                        0.07806435%
                        99.98695448%

                        6
                        92
                        0.08110282%
                        99.97955973%

                        7
                        125
                        0.07805901%
                        99.97327104%

                        8
                        152
                        0.07602963%
                        99.96834336%

                        9
                        182
                        0.07501375%
                        99.96260288%

                        10
                        212
                        0.07359260%
                        99.95726494%

                        11
                        243
                        0.07247552%
                        99.95176081%

                        12
                        273
                        0.07196674%
                        99.94618731%

                        13
                        306
                        0.07135643%
                        99.94019579%

                        14
                        335
                        0.07084780%
                        99.93499645%

                        15
                        365
                        0.07023778%
                        99.92978688%

                        16
                        547
                        0.06416086%
                        99.90389281%

                        17
                        730
                        0.05675784%
                        99.88654873%

                        18
                        1097
                        0.07500989%
                        99.77481323%

                        19
                        1462
                        0.12575198%
                        99.49756947%

                        20
                        1826
                        0.19386403%
                        99.03483661%

                        21
                        2191
                        0.27746329%
                        98.34825349%

                        22
                        2556
                        0.36034028%
                        97.50820167%

                        23
                        2924
                        0.43841259%
                        96.54885343%

                        24
                        3288
                        0.51168283%
                        95.49526595%

                        25
                        3653
                        0.58116575%
                        94.34948791%

                        26
                        4383
                        0.69174838%
                        92.02897968%

                        27
                        5479
                        0.80635259%
                        88.59974057%

                        28
                        7306
                        0.92532411%
                        83.09240116%

                        29
                        9133
                        0.96571892%
                        78.53377535%

                        30
                        10957
                        0.98810273%
                        74.33261856%

                        31
                        14610
                        0.94083244%
                        68.61968516%

                        32
                        18262
                        0.84642634%
                        65.47567760%

Se exporta a Excel este resultado.

```python
df_curva.to_excel('data/20201012_built_sofr_zero.xlsx', index=False)
```

### Comprobación

Con el resultado (raíces) se construye un objeto `Qcf.ZeroCouponCurve`.

```python
qc_plazos = Qcf.long_vec()
qc_tasas = Qcf.double_vec()
for p, t in zip(plazos, tasas):
    qc_plazos.append(p)
    qc_tasas.append(t)
curva_final = Qcf.QCCurve(qc_plazos, qc_tasas)
curva_final = Qcf.QCLinearInterpolator(curva_final)

# Se construye la curva
curva_final = Qcf.ZeroCouponCurve(
    curva_final,
    Qcf.QCInterestRate(0.0, Qcf.QCAct365(), Qcf.QCContinousWf())
)
```

Se calcula el valor presente de cada una de las patas fijas de los swaps.

```python
vp = Qcf.PresentValue()
for i, s in enumerate(qc_swaps):
    pv = vp.pv(Qcf.QCDate(14, 10, 2020), s[0], curva_final)
    print(f'{i}: {pv:,.8f}')
```


---
title: "Gibbs sampler as a product"
excerpt: "Awesome excerpt"
---

$$
x^2
$$

$x^2$

$$
\begin{align}
x &=2 \\
y &= 3
\end{align}
$$

My summer project at Civis Analytics this summer is to derive and implement a Gibbs sampler for one of their core algorithm. While I've had done several Gibbs samplers in my academic work, in those situations I knew that a solution exists, and the Gibbs sampler is written as a minimum viable product with little regard paid to maintainability and robustness. Now that my code will be used to make business decision worth many dollars, I have to make sure that my code is well tested and easy for other engineers to read and contribute.

At the heart of the approach is the Testing MCMC code framework by Roger Grosse. As the paper lays out, testing traditional MCMC code is difficult because:

- The output is probabilistic
- With regularization and prior the result can be reasonable even when the code is wrong
- When the code is right the result can still be wrong because the density we try to sample from is just difficult.

With all of that complications, the signal of a test quickly vanishes into the noise. Furthermore, exarcebating the problem is that the Gibbs sampler is frequently taught in school as a giant loop updating one parameter each. While such implementation facilitates pedagogy by keeping only the materials essential to the Gibbs sampler, it does not make use of useful engineering practices.

Consider a Gibbs sampler for a univariate normal model:

**Likelihood**

$$Data = Y_1, \dots, Y_n \sim i.i.d. N(\theta, \sigma^2)$$

The parameters are $(\theta, \sigma^2)$, the data is a vector of $Y_i$

**Prior**

$$
\begin{align}
p(\theta) &\sim N(\mu_0, \tau^2_0) \\
p(\sigma^2) &\sim Inverse-Gamma(\nu_0 / 2, \nu_0 \sigma^2_0 / 2)
\end{align}
$$

**Full conditional**

$$
\begin{align}
p(\theta | \tilde \sigma^2, Data) &= N(\mu_n, \tau^2_n) \\
p( \sigma^2 | \theta, Data) &= Inverse-Gamma(\frac{\nu_n}{2}, \frac{\nu_n \sigma_n^2(\theta)}{2})
\end{align}
$$

where

$$
\begin{align}
\tau_n^2 = \frac{1}{\frac{1}{\tau_0^2} + \frac{n}{\sigma^2}} \qquad &\text{and} \qquad \mu_n = \frac{\frac{\mu_0}{\tau_0^2} + \frac{n\bar y}{\sigma^2}}{\frac{1}{\tau_0^2} + \frac{n}{\sigma^2}} \\
\nu_n = \nu_0 + n \qquad &\text{and} \qquad \sigma^2_n(\theta) = \frac{1}{\nu_n} \left[ \nu_0\sigma_0^2 + n \frac{\sum (y_i - \theta)^2}{n}\right]
\end{align}
$$

Then the Gibbs sampler is a big for-loop:

for s in range(S):
- Draw $\theta$ from $p(\theta_s | \sigma^2_{s-1}, data)$
- Draw $\sigma^2$ from $p(\sigma^2_s | \theta_s, data)$

Now, how do we know whether we have derived this Gibbs sampler correctly? One approach is to set the parameter values ourselves and see whether the Gibbs sampler is able to recover it. (A colleague told me that this is called an oracle test, which doesn't seem widely used according to Google, but definitely should!)

```python
import numpy as np
import scipy.stats as stats
import pandas as pd
from plotnine import *

true_theta = 2
true_sigma2 = 3.5
y = np.random.normal(true_theta, np.sqrt(true_sigma2), size = 100)
```

```python
prior_mu_0 = 0
prior_tau2_0 = 1
prior_nu_0 = 1
prior_sigma2_0 = 0.01
```

```python
n = len(y)
ybar = np.mean(y)

# Initialize storage
S = 1000 # Number of samples that we'll draw from the Gibbs sampler
res_theta = np.empty(S)
res_sigma2 = np.empty(S)

# Starting value as the sample variance and mean
res_sigma2[0] = y.var()
res_theta[0] = ybar

# Gibbs sampler -- big loop
for s in range(1, S):
# Full conditional for theta
    sigma2 = res_sigma2[s - 1] # Get the current value of sigma2

    tau2_n = 1 / (1 / prior_tau2_0 + n / sigma2)
    mu_n = (prior_mu_0 / prior_tau2_0 + n * ybar / sigma2) / (1 / prior_tau2_0 + n / sigma2)

    # Generate and save theta
    res_theta[s] = scipy.stats.norm(mu_n, np.sqrt(tau2_n)).rvs()

    # Full conditional for sigma
    theta = res_theta[s] # Get the current value of theta

    nu_n = prior_nu_0 + n
    nu_sigma2_n = prior_nu_0 * prior_sigma2_0 + sum((y - theta) ** 2)
    res_sigma2[s] = 1 / scipy.stats.gamma(nu_n / 2, scale=2 / nu_sigma2_n).rvs()
```

```python
y = np.array([1.64, 1.70, 1.72, 1.74, 1.82, 1.82, 1.82, 1.90, 2.08])
theta_samples, sigma2_samples = gibbs_sampler(y = y, S = 1000,
                   mu_0 = 1.9, tau2_0 = 0.95 ** 2,
                   sigma2_0 = 0.01, nu_0 = 1)
```

```python
ggplot(pd.DataFrame({'theta':theta_samples})) + geom_density(aes('theta')) + theme_bw()
```

# Okay, but how do we test that big foor loop?

- It's probabilitistic -- so there is no expected outcome for unit test (e.g. ```assert actual_outcome == expected_outcome```)
- It's slow -- so we can't re-run the test suite everytime we make a small change
- It's not modular -- failures can come from doing the math wrong, from coding any of the conditionals wrong

# Solution 1: Unit test your conditional

For any value of $\theta, \theta'$

\begin{align}
\frac{p(\theta | z)}{p(\theta' | z)} &= \frac{p(\theta | z)p(z)}{p(\theta' | z)p(z)} \\
&= \frac{p(\theta, z)}{p(\theta', z)}
\end{align}

The LHS is hard to get right, but the RHS is very easy to get right (indeed, $p(\theta, z) = p(z | \theta) p(\theta)$). So the RHS can be the easy-to-verify "expected outcome" that we test the LHS against in a unit test.

This strategy also suggests that we modularize our MCMC code, separating each conditional $p(\theta | z)$ into its own function. This way we can unit test each conditional.

```python
class State(object):
    def __init__(self, theta, sigma2):
        self.theta = theta
        self.sigma2 = sigma2

class Model(object):
    def __init__(self, theta_prior, sigma2_prior):
        self.theta_prior = theta_prior
        self.sigma2_prior = sigma2_prior

    # Conditional distribution of theta
    def cond_theta(self, state, data):
        n = len(data)
        mean_prior = self.theta_prior.args[0]
        variance_prior = self.theta_prior.args[1] ** 2

        variance_post = 1 / (1 / variance_prior + n / state.sigma2)
        mean_post = variance_post * (mean_prior / variance_prior + n * data.mean() / state.sigma2)
        return scipy.stats.norm(mean_post, np.sqrt(variance_post))

    # Conditional distribution of theta
    def cond_sigma2(self, state, data):
        n = len(data)
        shape_prior = self.sigma2_prior.kwds['a']
        scale_prior = self.sigma2_prior.kwds['scale']

        shape_post = shape_prior + n / 2
        scale_post = scale_prior + np.sum((data - state.theta) ** 2) / 2
        return scipy.stats.invgamma(shape_post, scale=scale_post)

    # One Gibbs step is just iterating through the conditionals
    def gibbs_step(self, state, data):
        state.theta = self.cond_theta(state, data).rvs()
        state.sigma2 = self.cond_sigma2(state, data).rvs()
```

```python
import pytest

@pytest.fixture
def random_model():
    theta_prior = scipy.stats.norm(np.random.uniform(), np.random.uniform())
    sigma2_prior = scipy.stats.invgamma(a=np.random.uniform(),
                                        scale=np.random.uniform())
    return Model(theta_prior=theta_prior,
                 sigma2_prior=sigma2_prior)

@pytest.fixture
def random_state():
    return State(np.random.uniform(), np.random.uniform())

@pytest.fixture
def random_data():
    return np.random.normal(size=10)

def test_cond_theta(random_model, random_state, random_data):

    cond = random_model.cond_theta(random_state, random_data)
    new_state = copy.deepcopy(random_state)
    new_state.theta = np.random.uniform()

    assert np.allclose(cond.logpdf(new_state.theta) - cond.logpdf(state.theta),
                       random_model.joint_log_p(new_state, data) - \
                       random_model.joint_log_p(state, data))
```

```python

```


# Оценка бинарной классификации
Бинарная классификация одна из наиболее важных и часто встречаемых классов задач. Причем не только модели машинного обучения но и другие тесты также могут выдавать результат в виде бинарных классов. Например, медицинский тест на наличие заболевания (больной/здоровый), радиолокатор для определения вражеских объектов (свой/чужой) также являются бинарными классификаторами.

По этой причине важно оценить модель правильно и иногда несколькими разными способами. В этом материале мы рассмотрим несколько таких методов оценки. Для этого мы сначала обучим модель логистической регрессии, чтобы потом её оценить разными методами. В качестве такой задачи будем использовать набор данных breast cancer (рак груди) доступный в sklearn. Данный набор содержит данные по 569 пациентам, у которых выявлена опухоль груди, из которых 212 являются злокачественными (malignant) и 357 доброкачественными (benign). Данные содержат 30 признаков. Необходимо создать модель бинарной классификации, которая будет различать злокачественную опухоль от доброкачественной. Злокачественная опухоль соответствует отрицательным (0), а доброкачественна к положительным (1) классам.

Для начала симпортируем все необходимые модули.

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

# use seaborn plotting defaults
import seaborn as sns; sns.set()
```

С помощью функции `datasets.load_breast_cancer` загрузим набор данных и посмотрим их количество.

```python
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import numpy as np

breast_cancer = datasets.load_breast_cancer()
X = breast_cancer.data
y = breast_cancer.target
print('X shape                :', X.shape)
print('y shape                :', y.shape)
print('target names           :', breast_cancer.target_names)
print('positve (benign) cancer:', np.sum(y))
```

    X shape                : (569, 30)
    y shape                : (569,)
    target names           : ['malignant' 'benign']
    positve (benign) cancer: 357

Как обычно разобьем данные на тренировочный и тестовый наборы в пропорции 75% на 25%. При этом зафиксируем генератор случайных чисел, чтобы получать воспроизводимые результаты.

```python
random_state = np.random.RandomState(0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.25, random_state=random_state)
```

Теперь можно создать модель логистической регрессии и обучить её.

```python
classifier = LogisticRegression(random_state=random_state)
classifier.fit(X_train, y_train)
```

    LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
              intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
              penalty='l2',
              random_state=<mtrand.RandomState object at 0x0000025A9FD26B88>,
              solver='liblinear', tol=0.0001, verbose=0, warm_start=False)

С помощью этой модели можно предсказать тип опухоли

```python
y_test_pred = classifier.predict(X_test)
y_test_pred
```

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

Введем несколько определений, которые будем использовать дальше

|      Основные показатели                                           |
|----------------|---------------------------------------------------|
|  $T_p$         | True positive - истинно положительные результаты  |
|  $T_n$         | True negative - истинно отрицательные результаты  |
|  $F_p$         | False positive - ложно положительные результаты   |
|  $F_n$         | False negative - ложно отрицательные результаты   |

## Матрица заблуждений (confusion matrix)
Для начала будем оценивать результаты модели классификации с помощью так называемой матрицы заблуждений (confusion matrix). Матрица заблуждений показывает количество верных и ошибочных предсказаний для каждого класса. В задаче бинарной классификации матрица имеет размер 2x2. В sklearn для получения этой матрицы используется функция `confusion_matrix` из модуля `sklearn.metrics`. Для модели бинарной классификации она возвращает матрицу, у которой ячейка [0, 0] содержит количество истинно отрицательных (true negative, $T_n$), ячейка [1, 1] содержит количество истинно положительных (true positive, $T_p$) результатов. Ячейки [1, 0] и [0, 1] содержат количество ложноотрицательных (false negative, $F_n$) и ложноположительных (false positive, $F_p$) результатов соответственно.

```python
from sklearn.metrics import confusion_matrix
cmat = confusion_matrix(y_test, y_test_pred)
print(cmat)
```

    [[52  1]
     [ 5 85]]

Матрица заблуждений лучше всего визуализируется с помощью тепловой карты (heatmap). Для создания тепловой карты воспользуемся функцией `heatmap` из библиотеки `seaborn`.

```python
target_names = ['Malignant', 'Benign']
sns.heatmap(cmat.T, square=True, annot=True, fmt='d', cbar=False,
            xticklabels=target_names, yticklabels=target_names)
y_test.sum(), y_test_pred.sum()
```

## Точность, полнота и f1-score (precision, recall & f1-score)
В информационном поиске точность это мера релевантности результатов поиска. Например, если из всех результатов поиска только 60% релевантны запросу, то точность составляет 60%. В информационном поиске полнота это мера доли релевантных ответов в результате поиска относительно всех релевантных ответов. Например, если в целом в базе есть 100 релевантных запросу документов, из которых в результате поиска вернулось только 40, то полнота равна 40%.

Модель, у которой высокая полнота, но низкая точность, возвращает очень много результатов, среди которых много неверных ответов. Например, если модель может определить всех истинно больных но при этом очень часто определяет здорового человека больным, то у нее будет высокая полнота но низкая точность. И наоборот, если у модели высокая точность но низкая полнота, то она будет редко определять положительные классы, но эти кассы будут в большинстве случаев корректно классифицированы. Например, если модель не может определить всех больных, но при этом никогда не определяет здорового человека больным, то у этой модели высокая точность но низкая полнота.

Точность (P) и полнота (R) определяются следующими формулами
$$
\Large
\begin{align}
P &= \frac{T_p}{T_p + F_p} \\
R &= \frac{T_p}{T_p + F_n}
\end{align}
$$

Иногда межет быть желательно найти баланс между этими показателями. В таком случае можно использовать меру F1-score, которая является гармоническим средним (harmonic mean) значением между точность и полнотой:
$$
\Large F1 = 2 \frac{PR}{P + R}
$$

Функция `classification_report` возвращает точность (precision), полноту (recall) и f1-score для каждого класса. Колонка support отображает количество данных соответсующему классу в данных.

```python
from sklearn.metrics import classification_report
print(classification_report(y_test, y_test_pred, target_names=target_names))
```

                 precision    recall  f1-score   support

      Malignant       0.91      0.98      0.95        53
         Benign       0.99      0.94      0.97        90

    avg / total       0.96      0.96      0.96       143

# ROC-кривая (ROC curve)
Модель бинарной классификации может возвращать вероятность принадлежности одному из двух классов. Чтобы отнести пример к тому или другому классу необходимо подобрать порог вероятности, выше которого все предсказания будут считаться положительными, а ниже этого порога отрицательными. Выбор этого порогового значения позволяет балансировать точность и полноту.

ROC-кривая (англ. receiver operating characteristic, рабочая характеристика приёмника) - график, позволяющий оценить качество бинарной классификации при изменении порога решений (decision threshold). Использование этой кривой очень распространено. Изначально кривая была создана для оценки работы военных радаров (отсюда и название). Вскоре её начали использовать для оценки моделей в психологии, медицине, радиологии, метереологии и т.д.

ROC-кривая показывает зависимость изменения полноты по отношению к ложноположительным результатам при изменении порога верятности. Численно ROC-кривая характеризуется размером площади под кривой (ROC area under curve, ROC-AUC). В идеале эта площадь должна быть равна единице, тогда модель будет находить все истинно положительные результаты (полнота 100%). Для сравнения вместе ROC-кривой всегда рисуется диагональная линия, которая соответствует модели, которая возвращает ответы случайно в пропорции 50/50. ROC-AUC значение этой кривой равно 0.5. ROC-кривая любой другой модели должна быть выше этой линии иначе никакой ценности эта модель не будет из себя представлять. Можно попробовать интерактивные примеры ROC-кривой по ссылкам Understanding ROC curves и ROC curve demonstration.

Для получения ROC-кривой в sklearn используется функция `roc_curve` из модуля `sklearn.metrics`. ROC-AUC можно вычислить с помощью функции `roc_auc_score` из модуля `sklearn.metrics`.

```python
y_test_prob = classifier.predict_proba(X_test)[:, 1]
```

```python
from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, thresholds = roc_curve(y_test, y_test_prob, pos_label=1)
score = roc_auc_score(y_test, y_test_prob)
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.2f)' % score)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
```

Само значение порога вероятности в графике не отображается. Можно выбрать нужный порог и отобразить точку на графике самостоятельно. Например, следующий код вычисляет f1-score для каждого порога, который вернула функция `roc_curve`, и выбирает тот порог, при котором f1-score достигает максимума.

```python
from sklearn.metrics import f1_score

f1 = [f1_score(y_test, (y_test_prob > t).astype(int)) for t in thresholds]
best_threshold_idx = np.argmax(f1)
best_threshold = thresholds[best_threshold_idx]

plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.2f)' % score)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")

plt.scatter(fpr[best_threshold_idx], tpr[best_threshold_idx], s=50, c='b')

print('best f1 score          :', np.max(f1))
print('best f1 score threshold:', best_threshold)
print('false positive rate    :', fpr[best_threshold_idx])
print('true positive rate     :', tpr[best_threshold_idx])
```

## Precision-recall кривая
Иногда в задачах классификации классы могут быть сильно несбалансированы. Например, допустим, что при определении мошеннических транзакций (fraud detection) лишь одна из тысячи транзакций являются мошенническими (т.е. 0.1%). Если создать модель, которая всегда будет возвращать 0 (т.е. транзакция не мошенническая), то она будет работать с точностью 99.9%. Однако очевидно, что эта модель бесполезная. Для оценки моделей с такими сильно несбалансированными значениями классов можно использовать precision-recall кривую. Кривая показывает зависимость изменения точности по отношению к полноте при изменении порога верятности. Чтобы увеличить полноту необходимо понижать порог вероятности. Однако это ведет к сниженгию точности, так как при этом будут появляться все больше ложно положительных результатов.

```python
from sklearn.metrics import average_precision_score
average_precision = average_precision_score(y_test, y_test_prob)

print('Average precision-recall score: {0:0.2f}'.format(average_precision))
```

    Average precision-recall score: 1.00

```python
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt

precision, recall, thresholds = precision_recall_curve(y_test, y_test_prob)

plt.step(recall, precision, color='b', alpha=0.2, where='post')
plt.fill_between(recall, precision, step='post', alpha=0.2, color='b')

plt.xlabel('Recall')
plt.ylabel('Precision')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
plt.title('2-class Precision-Recall curve: AP={0:0.2f}'.format(average_precision))
print('precision', precision[:5])
print('recall', recall[:5])
print('thresholds', thresholds[:5])
```

04.07. Binary classification evaluation.ipynb


$$\newcommand{\ket}[1]{\left|{#1}\right\rangle}$$
$$\newcommand{\bra}[1]{\left\langle{#1}\right|}$$
$$\newcommand{\braket}[2]{\left\langle{#1}\middle|{#2}\right\rangle}$$
# Digital Quantum Simulations

In this tutorial, we will introduce time evolution of quantum states and how to simulate them using the Trotter-Suzuki formalism.  We then show to how to run the time evolution of a one dimensional Fermi-Hubbard chain with three sites.

## Contents
1. [Introduction](#introduction)
2. [Trotter-Suzuki Formalism](#ts-equation)
3. [The Fermi-Hubbard Model](#hubbard-models)
3. [Jordan-Wigner Transformation and Encoding](#jw-transform)
4. [One-Dimensional Hubbard Simulation](#1d-chain)

## 1. Introduction

In quantum mechanics, propagation in time of the wavefunction $\ket{\Psi(t)}$ is governed by the Shro\"{o}dinger equation

$$ i\hbar\ket{\Psi(t)} = H\ket{\Psi(t)}$$

where $H$ is the Hamiltonian operator of the system.  If we consider $H$ to be time-independent, the solution for $\ket{\Psi(t)}$ has the form

$$ \ket{\Psi(t)} = e^{iHt}\ket{\Psi(0)} $$

and simulating this requires the ability to perform the operation above.  In most cases, this reduces to matrix exponentiation, but proves to be very difficult on classical computers due to the exponential growth of the operator as the system size increases.

Efficient approximations to the solution of this equation are possible for Hamiltonians which carry local interactions between the system's consitutent subsystems.  These classes of Hamiltonians can be written in the form

$$ H = \sum_k^L H_k $$

where each $H_k$ acts locally on only a portion of the total system.  The advantage of writing the Hamiltonian in this way is that although the total time evolution operator $e^{iHt}$ may be difficult to compute, it is much easier to use $e^{iH_kt}$ as it acts on a much small subsytem and is straightforward to approximate using quantum circuits.

## 2. Trotter-Suzuki Formalism

## 3. The Fermi-Hubard Model

## 4. Jordan-Wigner Transformation and Encoding

## 5. One-Dimensional Hubbard Simulation
$$\newcommand{\ket}[1]{\left|{#1}\right\rangle}$$
$$\newcommand{\bra}[1]{\left\langle{#1}\right|}$$

The Hubbard Hamiltonian for a 1-D chain of sites is:

\begin{equation} H = -t\sum_{<ij>,\sigma}(\hat{c}_{i_\sigma}\hat{c}_{j_\sigma} + \hat{c}_{j_\sigma}^\dagger\hat{c}_{i_\sigma} ) + U\sum_i\hat{c}_{i_\uparrow}^{\dagger}\hat{c}_{i_\uparrow}\hat{c}_{i_\downarrow}^{\dagger}\hat{c}_{i_\downarrow}
\end{equation}
and under the Jordan-Wigner mapping, this Hamiltonian becomes:

\begin{equation}
H = -\frac{t}{2}\sum_{<ij>}Z_{i+1:j-1}(X_{i}X_{j} + Y_{i}Y_{j}) + \frac{U}{4}\sum_{ij}(I-Z_{i})(I-Z_{j})
\end{equation}
where $Z_{i}$, $X_{i}$, and $Y_{i}$ are the corresponding Pauli matrices acting on the $i^{th}$ fermionic mode and for a chain with only 3-sites, there are no Pauli $Z$ strings in the hopping term of the Hamiltonian.

### 3-Site Hamiltonian and Qubit Mapping

Writing out the sum over the three sites, the Hamiltonian becomes:

\begin{align}
     H = &-\frac{t}{2}(X_0X_1 + Y_0Y_1) - \frac{t}{2}(X_1X_2 + Y_1Y_2) \nonumber \\
&-\frac{t}{2}(X_3X_4 + Y_3Y_4) - \frac{t}{2}(X_4X_5 + Y_4Y_5) \nonumber\\
&+ \frac{U}{4}(I-Z_0)(I-Z_3)+ \frac{U}{4}(I-Z_1)(I-Z_4) + \frac{U}{4}(I-Z_2)(I-Z_5) \nonumber\\
= &H_{01} + H_{12} + H_{23} + H_{34} + H_{03} + H_{14} + H_{25}
\end{align}

#### Qubit Mapping

Each site in the 3-site chain is represented by two qubits, one for each spin, and the wavefunction is represented as

$$ \ket{\psi} = \ket{q_0}\ket{q_1}\ket{q_2}\ket{q_3}\ket{q_4}\ket{q_5} $$

where $\ket{q_i} = \{ \ket{0}, \ket{1} \} $ represent unoccupied or occupied sites, $i=0,1,2$ are the spin up electron occupations and $i=3,4,5$ are the spin down electron occupations.

### Time Evolution

We want to simulate the time evolution of $\ket{\psi}$ via

$$ \ket{\psi(t+\Delta t)} = e^{-iH\Delta t}\ket{\psi(t)} $$

We can do this via the Suzuki-Trotter formula which states that, to first order in $\Delta t$

$$e^{iH\Delta t} \approx e^{iH_{10}^{\uparrow}\Delta t}e^{iH_{12}^{\uparrow}\Delta t}e^{iH_{10}^{\downarrow}\Delta t}e^{iH_{12}^{\downarrow}\Delta t}e^{iH_0\Delta t}e^{iH_1\Delta t}e^{iH_2\Delta t} $$.

So what do the gates look like for each of these terms?

##### Hopping Terms

For each pair of hopping terms we have
\begin{equation}e^{-i\Delta t(\frac{-t}{2})(X_iX_j + Y_iY_j)} \approx e^{\frac{it\Delta t}{2}X_iX_j} e^{\frac{it\Delta t}{2}Y_iY_j}.
\label{eq:op-expansion}
\end{equation}

Expanding the first term on the right hand side

\begin{align}
e^{\frac{it\Delta t}{2}X_iX_j} = & \sum_{k=0}^{\infty} \frac{1}{k!}\left(\frac{it\Delta t}{2}X_iX_j\right)^k \nonumber \\
 =& \sum_{k, even}\frac{i^k}{k!}\left( \frac{t\Delta t}{2} \right)^k I + \sum_{k, odd}\frac{i^k}{k!}\left( \frac{t\Delta t}{2} \right)X_i X_j \nonumber \\
 = &\cos\left(\frac{t\Delta t}{2}\right)I + i\sin\left( \frac{t\Delta t}{2}\right)X_i X_j \nonumber \\
 = &\begin{pmatrix}\cos\theta & 0 & 0 & i\sin\theta \\ 0 & \cos\theta & i\sin\theta & 0 \\ 0 & i\sin\theta & \cos\theta & 0 \\ i\sin\theta & 0 & 0 & \cos\theta\end{pmatrix},
\end{align}
with $\theta=\frac{t\Delta t}{2}$, and written in the $\ket{q_{i}q_{j}}$ basis.

Similarly for the $Y_i Y_j$ terms

\begin{align}
    e^{i\frac{t\Delta t}{2}Y_i Y_j} =& \cos\left(\frac{t\Delta t}{2}\right)I + i\sin\left(\frac{t\Delta t}{2}\right)Y_i Y_j \nonumber \\
= & \begin{pmatrix}\cos\theta & 0 & 0 & -i\sin\theta \\ 0 & \cos\theta & i\sin\theta & 0 \\ 0 & i\sin\theta & \cos\theta & 0 \\ -i\sin\theta & 0 & 0 & \cos\theta\end{pmatrix}.
\end{align}

Note also that these matrices are diagonal save for the 4x4 block corresponding to a gate acting on qubits $i$ and $j$.

#### On-Site Terms

Now we'll expand the on-site term, $e^{i\frac{U\Delta t}{4}(I-Z_i)(I-Z_j)}$.  First we examine the powers of $(I-Z_i)(I-Z_j)$:

\begin{align} (I-Z_i)^2(I-Z_j)^2 &= (I + I - 2Z_i)(I+I-2Z_j) = 4(I-Z_i)(I-Z_j) \\
(I-Z_i)^3(I-Z_j)^3& = (I-Z_i)(I-Z_j)(I-Z_i)^2(I-Z_j)^2 \nonumber \\
&= 4(I-Z_i)^2(I-Z_j)^2 = 16(I-Z_i)(I-Z_j)\\
(I-Z_i)^4(I-Z_j)^4& = 16(I-Z_i)^2(I-Z_j)^2 = 4^3(I-Z_i)(I-Z_j)\\
\implies (I-Z_i)^k(I-Z_j)^k& = 4^{k-1}(I-Z_i)(I-Z_j), \end{align}

then writing out the expansion of $e^{i\frac{U\Delta t}{4}(I-Z_i)(I-Z_j)}$ we get

\begin{align} e^{i\frac{U\Delta t}{4}(I-Z_i)(I-Z_j)} &= \sum_k \frac{1}{k!}\left(\frac{i\Delta tU}{4}\right)^k(I-Z_i)^k(I-Z_j)^k \\
&= I+(I-Z_i)(I-Z_j)\sum_k \frac{\left(i\Delta tU\right)^k}{k!}\frac{4^{k-1}}{4^k} - \frac{1}{4}(I-Z_i)(I-Z_j) \\
& = I-\frac{1}{4}(I-Z_i)(I-Z_j) + \frac{1}{4}e^{iU\Delta t}(I-Z_i)(I-Z_j)  \\
& \boxed{= I-(I-Z_i)(I-Z_j)\left(1-e^{iU\Delta t} \right) } \\
&= \begin{pmatrix}1 & 0 & 0 & 0\\ 0 & 1& 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & e^{iU\Delta t} \end{pmatrix} .
\end{align}

$^1$https://web.cs.ucdavis.edu/~bai/publications/varneyleebai09.pdf

```python

```


```python
%load_ext autoreload
%autoreload 2

import sys
import pylab as pl
sys.path.insert(0, '..')

import pymoca.parser
import pymoca.backends.sympy.generator as generator
import pylab as pl
import sympy
import control
sympy.init_printing()
%load_ext autoreload

%autoreload 2
%matplotlib inline
```

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

# Modelica Source Code

```python
modelica_src = '''

model System
    input Real u;
    output Real x(start=1), v_x(start=1);
    Spring spring;
    Damper damper;
equation
    spring.x = x;
    damper.v = v_x;
    der(x) = v_x;
    der(v_x) = spring.f + damper.f - u;
end System;

model Spring
    Real x "displacement";
    Real f "force";
    parameter Real k = 2.0 "spring constant";
equation
    f = -k*x;
end Spring;

model Damper
    Real v "velocity";
    Real f "force";
    parameter Real c = 0.2 "damping constant";
equation
    f = -c*v;
end Damper;
'''
```

# Generation of Abstract Syntax Tree

```python
ast = pymoca.parser.parse(modelica_src)
ast.classes['System'].symbols.keys()
```

    odict_keys(['u', 'x', 'v_x', 'spring', 'damper'])

```python
ast
```

    {
      "annotation": [],
      "classes": {
        "Damper": {
          "annotation": [],
          "classes": {},
          "comment": "",
          "encapsulated": false,
          "equations": [
            {
              "comment": "",
              "left": {
                "child": [],
                "indices": [],
                "name": "f"
              },
              "right": {
                "operands": [
                  {
                    "operands": [
                      {
                        "child": [],
                        "indices": [],
                        "name": "c"
                      }
                    ],
                    "operator": "-"
                  },
                  {
                    "child": [],
                    "indices": [],
                    "name": "v"
                  }
                ],
                "operator": "*"
              }
            }
          ],
          "extends": [],
          "final": false,
          "functions": {},
          "imports": [],
          "initial_equations": [],
          "initial_statements": [],
          "name": "Damper",
          "partial": false,
          "statements": [],
          "symbols": {
            "c": {
              "class_modification": {
                "arguments": [
                  {
                    "redeclare": false,
                    "value": {
                      "component": {
                        "child": [],
                        "indices": [],
                        "name": "value"
                      },
                      "modifications": [
                        {
                          "value": 0.2
                        }
                      ]
                    }
                  }
                ]
              },
              "comment": "damping constant",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "c",
              "nominal": {
                "value": null
              },
              "order": 10,
              "outer": false,
              "prefixes": [
                "parameter"
              ],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "f": {
              "class_modification": null,
              "comment": "force",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "f",
              "nominal": {
                "value": null
              },
              "order": 9,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "v": {
              "class_modification": null,
              "comment": "velocity",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "v",
              "nominal": {
                "value": null
              },
              "order": 8,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            }
          },
          "type": "model"
        },
        "Spring": {
          "annotation": [],
          "classes": {},
          "comment": "",
          "encapsulated": false,
          "equations": [
            {
              "comment": "",
              "left": {
                "child": [],
                "indices": [],
                "name": "f"
              },
              "right": {
                "operands": [
                  {
                    "operands": [
                      {
                        "child": [],
                        "indices": [],
                        "name": "k"
                      }
                    ],
                    "operator": "-"
                  },
                  {
                    "child": [],
                    "indices": [],
                    "name": "x"
                  }
                ],
                "operator": "*"
              }
            }
          ],
          "extends": [],
          "final": false,
          "functions": {},
          "imports": [],
          "initial_equations": [],
          "initial_statements": [],
          "name": "Spring",
          "partial": false,
          "statements": [],
          "symbols": {
            "f": {
              "class_modification": null,
              "comment": "force",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "f",
              "nominal": {
                "value": null
              },
              "order": 6,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "k": {
              "class_modification": {
                "arguments": [
                  {
                    "redeclare": false,
                    "value": {
                      "component": {
                        "child": [],
                        "indices": [],
                        "name": "value"
                      },
                      "modifications": [
                        {
                          "value": 2.0
                        }
                      ]
                    }
                  }
                ]
              },
              "comment": "spring constant",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "k",
              "nominal": {
                "value": null
              },
              "order": 7,
              "outer": false,
              "prefixes": [
                "parameter"
              ],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "x": {
              "class_modification": null,
              "comment": "displacement",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "x",
              "nominal": {
                "value": null
              },
              "order": 5,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            }
          },
          "type": "model"
        },
        "System": {
          "annotation": [],
          "classes": {},
          "comment": "",
          "encapsulated": false,
          "equations": [
            {
              "comment": "",
              "left": {
                "child": [
                  {
                    "child": [],
                    "indices": [],
                    "name": "x"
                  }
                ],
                "indices": [],
                "name": "spring"
              },
              "right": {
                "child": [],
                "indices": [],
                "name": "x"
              }
            },
            {
              "comment": "",
              "left": {
                "child": [
                  {
                    "child": [],
                    "indices": [],
                    "name": "v"
                  }
                ],
                "indices": [],
                "name": "damper"
              },
              "right": {
                "child": [],
                "indices": [],
                "name": "v_x"
              }
            },
            {
              "comment": "",
              "left": {
                "operands": [
                  {
                    "child": [],
                    "indices": [],
                    "name": "x"
                  }
                ],
                "operator": "der"
              },
              "right": {
                "child": [],
                "indices": [],
                "name": "v_x"
              }
            },
            {
              "comment": "",
              "left": {
                "operands": [
                  {
                    "child": [],
                    "indices": [],
                    "name": "v_x"
                  }
                ],
                "operator": "der"
              },
              "right": {
                "operands": [
                  {
                    "operands": [
                      {
                        "child": [
                          {
                            "child": [],
                            "indices": [],
                            "name": "f"
                          }
                        ],
                        "indices": [],
                        "name": "spring"
                      },
                      {
                        "child": [
                          {
                            "child": [],
                            "indices": [],
                            "name": "f"
                          }
                        ],
                        "indices": [],
                        "name": "damper"
                      }
                    ],
                    "operator": "+"
                  },
                  {
                    "child": [],
                    "indices": [],
                    "name": "u"
                  }
                ],
                "operator": "-"
              }
            }
          ],
          "extends": [],
          "final": false,
          "functions": {},
          "imports": [],
          "initial_equations": [],
          "initial_statements": [],
          "name": "System",
          "partial": false,
          "statements": [],
          "symbols": {
            "damper": {
              "class_modification": null,
              "comment": "",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "damper",
              "nominal": {
                "value": null
              },
              "order": 4,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Damper"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "spring": {
              "class_modification": null,
              "comment": "",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "spring",
              "nominal": {
                "value": null
              },
              "order": 3,
              "outer": false,
              "prefixes": [],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Spring"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "u": {
              "class_modification": null,
              "comment": "",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "u",
              "nominal": {
                "value": null
              },
              "order": 0,
              "outer": false,
              "prefixes": [
                "input"
              ],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "v_x": {
              "class_modification": {
                "arguments": [
                  {
                    "redeclare": false,
                    "value": {
                      "component": {
                        "child": [],
                        "indices": [],
                        "name": "start"
                      },
                      "modifications": [
                        {
                          "value": 1.0
                        }
                      ]
                    }
                  }
                ]
              },
              "comment": "",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "v_x",
              "nominal": {
                "value": null
              },
              "order": 2,
              "outer": false,
              "prefixes": [
                "output"
              ],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            },
            "x": {
              "class_modification": {
                "arguments": [
                  {
                    "redeclare": false,
                    "value": {
                      "component": {
                        "child": [],
                        "indices": [],
                        "name": "start"
                      },
                      "modifications": [
                        {
                          "value": 1.0
                        }
                      ]
                    }
                  }
                ]
              },
              "comment": "",
              "dimensions": [
                {
                  "value": 1
                }
              ],
              "final": false,
              "fixed": {
                "value": false
              },
              "id": 0,
              "inner": false,
              "max": {
                "value": null
              },
              "min": {
                "value": null
              },
              "name": "x",
              "nominal": {
                "value": null
              },
              "order": 1,
              "outer": false,
              "prefixes": [
                "output"
              ],
              "redeclare": false,
              "start": {
                "value": 0
              },
              "type": {
                "child": [],
                "indices": [],
                "name": "Real"
              },
              "value": {
                "value": null
              },
              "visibility": "private"
            }
          },
          "type": "model"
        }
      },
      "comment": "",
      "encapsulated": false,
      "equations": [],
      "extends": [],
      "final": false,
      "functions": {},
      "imports": [],
      "initial_equations": [],
      "initial_statements": [],
      "name": null,
      "partial": false,
      "statements": [],
      "symbols": {},
      "type": ""
    }

# Generation of Sympy Model

```python
src_code = generator.generate(ast, 'System')
print(src_code)
```

    # do not edit, generated by pymoca

    from __future__ import print_function, division
    import sympy
    import sympy.physics.mechanics as mech
    from pymoca.backends.sympy.runtime import OdeModel
    from sympy import sin, cos, tan

    class System(OdeModel):

        def __init__(self):

            super(System, self).__init__()

            # states
            x, v_x = mech.dynamicsymbols('x, v_x')
            self.x = sympy.Matrix([x, v_x])
            self.x0 = {
                x : 1.0,
                v_x : 1.0,
                }

            # variables
            spring__x, spring__f, damper__v, damper__f = mech.dynamicsymbols('spring.x, spring.f, damper.v, damper.f')
            self.v = sympy.Matrix([spring__x, spring__f, damper__v, damper__f])

            # constants
            self.c = sympy.Matrix([])
            self.c0 = {
                }

            # parameters
            spring__k, damper__c = sympy.symbols('spring.k, damper.c')
            self.p = sympy.Matrix([spring__k, damper__c])
            self.p0 = {
                spring__k : 2.0,
                damper__c : 0.2,
                }

            # inputs
            u = mech.dynamicsymbols('u')
            self.u = sympy.Matrix([u])
            self.u0 = {
                u : 0,
                }

            # outputs
            x, v_x = mech.dynamicsymbols('x, v_x')
            self.y = sympy.Matrix([x, v_x])

            # equations
            self.eqs = [
                spring__f - (- spring__k * spring__x),
                damper__f - (- damper__c * damper__v),
                spring__x - (x),
                damper__v - (v_x),
                (x).diff(self.t) - (v_x),
                (v_x).diff(self.t) - (spring__f + damper__f - u),
                ]

            self.compute_fg()

```python
exec(src_code)
model = System()
model
```

    {'t': t, 'x': Matrix([
    [  x(t)],
    [v_x(t)]]), 'u': Matrix([[u(t)]]), 'y': Matrix([
    [  x(t)],
    [v_x(t)]]), 'p': Matrix([
    [spring.k],
    [damper.c]]), 'c': Matrix(0, 0, []), 'v': Matrix([
    [spring.x(t)],
    [spring.f(t)],
    [damper.v(t)],
    [damper.f(t)]]), 'x0': {x(t): 1.0, v_x(t): 1.0}, 'u0': {u(t): 0}, 'p0': {spring.k: 2.0, damper.c: 0.2}, 'c0': {}, 'eqs': [spring.k*spring.x(t) + spring.f(t), damper.c*damper.v(t) + damper.f(t), spring.x(t) - x(t), damper.v(t) - v_x(t), -v_x(t) + Derivative(x(t), t), -damper.f(t) - spring.f(t) + u(t) + Derivative(v_x(t), t)], 'f': Matrix([
    [                                 v_x(t)],
    [-damper.c*v_x(t) - spring.k*x(t) - u(t)]]), 'g': Matrix([
    [  x(t)],
    [v_x(t)]])}

```python
t = sympy.Symbol('t')
model.x.diff(t)
```

```python
sol = sympy.solve(model.eqs, list(model.v) + list(model.x.diff(t)))
sol
```

```python
model.x.diff(t).subs(sol)
```

```python
model.linearize_symbolic()
```

```python
ss = control.ss(*model.linearize())
ss
```

    A = [[ 0.   1. ]
     [-2.  -0.2]]

    B = [[ 0.]
     [-1.]]

    C = [[1. 0.]
     [0. 1.]]

    D = [[0.]
     [0.]]

```python
control.ss2tf(ss)
```

    Input 1 to output 1:
          -1
    ---------------
    s^2 + 0.2 s + 2

    Input 1 to output 2:
          -s
    ---------------
    s^2 + 0.2 s + 2

```python
control.bode(ss[0,0], omega=pl.logspace(-1,1,100));
```

```python
control.rlocus(ss[0,0], klist=pl.logspace(-2,1,1000));
pl.grid()
```

# Simulation of Sympy Model

```python
model.f
```

```python
import sympy
ss_sub = {}
ss_sub.update({model.x[i]: sympy.DeferredVector('x')[i] for i in range(len(model.x))})
#ss_sub.update({model.y[i]: sympy.DeferredVector('y')[i] for i in range(len(model.y))})
ss_sub
```

```python
res = model.simulate(t0=10, tf=20, dt=0.001)
```

```python
pl.plot(res['t'], res['x'])
pl.grid()
pl.xlabel('t, sec')
pl.ylabel('x')
```

```python
sympy.init_printing()
model.f
```

```python
model.f.jacobian(model.x)
```

# Lyapunov Stability

Using the energy function as a candidate function, we can show that the system has global exponential stability (GES).

```python
v_x, spring_k, t, V, x = sympy.symbols('v_x, spring.k, t, V, x')
V_eq = (v_x(t)**2 + spring_k*x(t)**2)/2
sympy.Eq(V, V_eq)
```

```python
dV_dt_eq = (sympy.Matrix([V_eq]).jacobian(model.x).dot(model.f)).simplify()
sympy.Eq(V(t).diff(t), dV_dt_eq)
```

```python
V_lam = sympy.lambdify(model.x, V_eq.subs(model.p0))
V_vals = [V_lam(*x) for x in res['x']]
pl.plot(res['t'], V_vals)
pl.xlabel('t')
pl.ylabel('V')
pl.grid()
```

# Parameter Sensitivity

```python
model.f.jacobian(model.p)
```