# 1. Set up the notebook

Import modules.

```python
import numpy as np
import sympy as sym
import json
import matplotlib.pyplot as plt
from scipy import linalg
from scipy.interpolate import interp1d
```

Define a function to load data from hardware (and resample it at 100 Hz using linear interpolation). If `only_in_flight=True`, then only data for which the desired position was positive (i.e., "trying to fly" rather than "sitting on the ground" data) will be returned.

```python
def load_hardware_data(filename, t_min_offset=0, t_max_offset=0, only_in_flight=False):
    # load raw data
    with open(filename, 'r') as f:
        data = json.load(f)

    # convert lists to numpy arrays
    for val in data.values():
        for key in val.keys():
            val[key] = np.array(val[key])

    # create an array of times at which to subsample
    t_min = -np.inf
    t_max = np.inf
    for key, val in data.items():
        t_min = max(t_min, val['time'][0])
        t_max = min(t_max, val['time'][-1])
    t_min += t_min_offset * 1000
    t_max -= t_max_offset * 1000
    nt = int(1 + np.floor((t_max - t_min) / 10.))
    t = np.arange(0, 10 * nt, 10) / 1000.
    resampled_data = {'time': t}

    # resample raw data with linear interpolation
    for k, v in data.items():
        f = interp1d((v['time'] - t_min) / 1000., v['data'])
        resampled_data[k] = f(t)

    # truncate to times when o_z_des is positive
    if only_in_flight:
        if 'ae483log.o_z_des' not in resampled_data.keys():
            raise Exception('"ae483log.o_z_des" must be logged')
        i = np.argwhere(resampled_data['ae483log.o_z_des'] > 0).flatten()
        if len(i) == 0:
            raise Exception('o_z_des was never positive')
        if len(i) < 2:
            raise Exception('o_z_des was only positive for one time step')
        for key in resampled_data.keys():
            resampled_data[key] = resampled_data[key][i[0]:i[-1]]

    # return the resampled data
    return resampled_data
```

# 2. Derive models

## 2.1 Define symbolic variables

Define states.

```python
# components of position (meters)
o_x, o_y, o_z = sym.symbols('o_x, o_y, o_z')

# yaw, pitch, and roll angles (radians)
psi, theta, phi = sym.symbols('psi, theta, phi')

# components of linear velocity (meters / second)
v_x, v_y, v_z = sym.symbols('v_x, v_y, v_z')
```

Define inputs.

```python
# gyroscope measurements - components of angular velocity (radians / second)
w_x, w_y, w_z = sym.symbols('w_x, w_y, w_z')

# z-axis accelerometer measurement - specific force (meters / second^2)
a_z = sym.symbols('a_z')
```

Define outputs.

```python
n_x, n_y, r = sym.symbols('n_x, n_y, r')
```

Define parameters.

```python
g, k_flow = sym.symbols('g, k_flow')
```

Create linear and angular velocity vectors (in coordinates of the body frame).

```python
v_01in1 = sym.Matrix([[v_x], [v_y], [v_z]])
w_01in1 = sym.Matrix([[w_x], [w_y], [w_z]])
```

## 2.2 Define kinematics of orientation

### 2.2.1 Rotation matrix in terms of yaw, pitch, roll angles

Define individual rotation matrices.

```python
Rz = sym.Matrix([[sym.cos(psi), -sym.sin(psi), 0],
                 [sym.sin(psi), sym.cos(psi), 0],
                 [0, 0, 1]])

Ry = sym.Matrix([[sym.cos(theta), 0, sym.sin(theta)],
                 [0, 1, 0],
                 [-sym.sin(theta), 0, sym.cos(theta)]])

Rx = sym.Matrix([[1, 0, 0],
                 [0, sym.cos(phi), -sym.sin(phi)],
                 [0, sym.sin(phi), sym.cos(phi)]])
```

Apply sequential transformation to compute the rotation matrix that describes the orientation of the drone (i.e., of frame 1 in the coordinates of frame 0).

```python
R_1in0 = Rz * Ry * Rx
```

### 2.2.2 Map from angular velocity to angular rates

Recall that

$$\begin{bmatrix} \dot{\psi} \\ \dot{\theta} \\ \dot{\phi} \end{bmatrix} = N w_{0, 1}^{1}$$

for some matrix $N$. Here is how to compute that matrix for a ZYX (yaw, pitch, roll) Euler angle sequence.  First, we compute its inverse:

```python
Ninv = sym.Matrix.hstack((Ry * Rx).T * sym.Matrix([[0], [0], [1]]),
                              (Rx).T * sym.Matrix([[0], [1], [0]]),
                                       sym.Matrix([[1], [0], [0]]))
```

Then, we compute $N$ by taking the inverse of $N^{-1}$:

```python
N = sym.simplify(Ninv.inv())
```

## 2.3 Derive equations of motion

Ratio of net thrust to mass in terms of z-axis accelerometer measurement.

```python
f_z_over_m = a_z + (w_01in1.cross(v_01in1))[2]
```

Ratio of forces to mass.

```python
f_in1_over_m = R_1in0.T * sym.Matrix([[0], [0], [-g]]) + sym.Matrix([[0], [0], [f_z_over_m]])
```

Equations of motion.

```python
f = sym.Matrix.vstack(
    R_1in0 * v_01in1,
    N * w_01in1,
    (f_in1_over_m - w_01in1.cross(v_01in1)),
)
```

Show equations of motion, which have the form

$$\dot{s} = f(s, i, p)$$

where

$$
s = \begin{bmatrix} o_x \\ o_y \\ o_z \\ \psi \\ \theta \\ \phi \\ v_x \\ v_y \\ v_z \end{bmatrix}
\qquad\qquad
i = \begin{bmatrix} w_x \\ w_y \\ w_z \\ a_z \end{bmatrix}
\qquad\qquad
p = \begin{bmatrix} g \\ k_\text{flow} \end{bmatrix}.
$$

```python
f
```

## 2.4 Derive measurement equations

Create measurement equations.

```python
h = sym.Matrix([
    k_flow * (v_x - o_z * w_y) / o_z,        # <-- x flow (n_x)
    k_flow * (v_y + o_z * w_x) / o_z,        # <-- y flow (n_y)
    o_z / (sym.cos(phi) * sym.cos(theta)),   # <-- z range (r)
])
```

Show measurement equations, which have the form

$$o = h(s, i, p)$$

where

$$
o = \begin{bmatrix} n_x \\ n_y \\ r \end{bmatrix}
\qquad\qquad
s = \begin{bmatrix} o_x \\ o_y \\ o_z \\ \psi \\ \theta \\ \phi \\ v_x \\ v_y \\ v_z \end{bmatrix}
\qquad\qquad
i = \begin{bmatrix} w_x \\ w_y \\ w_z \\ a_z \end{bmatrix}
\qquad\qquad
p = \begin{bmatrix} g \\ k_\text{flow} \end{bmatrix}.
$$

```python
h
```

# 3. Derive state-space model

## 3.1 Choose equilibrium point

An equilibrium point of the nonlinear system is a choice of states $s_\text{eq}$ and inputs $i_\text{eq}$ - along with constant parameters $p_\text{eq}$ - for which

$$0 = f(s_\text{eq}, i_\text{eq}, p_\text{eq}).$$

Create a symbolic variable to describe the equilibrium value of $o_z$.

```python
o_z_eq = sym.symbols('o_z_eq')
```

Create a list of states, inputs, outputs, and parameters as symbolic variables.

```python
s = [o_x, o_y, o_z, psi, theta, phi, v_x, v_y, v_z]
i = [w_x, w_y, w_z, a_z]
o = [n_x, n_y, r]
p = [g, k_flow]
```

Create a list of state and input values at equilibrium in the **same order** as before.

```python
s_eq = [0, 0, o_z_eq, 0, 0, 0, 0, 0, 0]
i_eq = [0, 0, 0, g]
```

Make sure all equilibrium values are symbolic.

```python
s_eq = [sym.nsimplify(a) for a in s_eq]
i_eq = [sym.nsimplify(a) for a in i_eq]
```

Evaluate the equations of motion at the equilibrium point - if it actually *is* an equilibrium point, then the result should be a matrix of zeros:

```python
f.subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
```

## 3.2 Find $A$, $B$, $C$, and $D$

Recall that:

$$
A = \frac{\partial f}{\partial s}\biggr\vert_{(s, i, p) = (s_\text{eq}, i_\text{eq}, p_\text{eq})}
\qquad\quad
B = \frac{\partial f}{\partial i}\biggr\vert_{(s, i, p) = (s_\text{eq}, i_\text{eq}, p_\text{eq})}
\qquad\quad
C = \frac{\partial h}{\partial s}\biggr\vert_{(s, i, p) = (s_\text{eq}, i_\text{eq}, p_\text{eq})}
\qquad\quad
D = \frac{\partial h}{\partial i}\biggr\vert_{(s, i, p) = (s_\text{eq}, i_\text{eq}, p_\text{eq})}.
$$

Compute each Jacobian and plug in the equilibrium values as follows.

```python
A = f.jacobian(s).subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
B = f.jacobian(i).subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
C = h.jacobian(s).subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
D = h.jacobian(i).subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
```

Show $A$:

```python
A
```

Show $B$:

```python
B
```

Show $C$:

```python
C
```

Show $D$ (note that it is *not* zero in this case):

```python
D
```

## 3.3 Write linearized models

Define the state, input, and output of the state-space system (i.e., the linearized model of the equations of motion and of the measurement equations).

```python
x = sym.Matrix(s) - sym.Matrix(s_eq)
u = sym.Matrix(i) - sym.Matrix(i_eq)
y = sym.Matrix(o) - h.subs(tuple(zip(s, s_eq))).subs(tuple(zip(i, i_eq)))
```

Show the linearized equations of motion $Ax+Bu$.

```python
A * x + B * u
```

Show the linearized measurement equations $Cx+Du$.

```python
C * x + D * u
```

Show the output (which our model tells us should be $Cx+Du$).

```python
y
```

# 4. Study error in linearized models

#### Describe the flight test

Replace this cell with the following information, at minimum:

* A description of the flight trajectory, both in words and (if you like) a snippet of code from `flight.py`.
* A description of the flight conditions (e.g., where was the flight conducted, did you power cycle the drone just before flying, were you using the positioning system or only the onboard sensors, etc.).

#### Show a video of the flight test

Replace this cell with a video of your flight. Here are two ways to do this. (What you should *not* do is drag-and-drop your video into this notebook.)

##### Markdown

Put your video in the same directory as this notebook. Suppose this video is called `hardware_video.mov`. Then put the following code in a cell of type `Markdown` and evaluate it:
```

```

##### HTML
Put your video in the same directory as this notebook. Suppose this video is called `hardware_video.mov`. Then put the following code in a cell of type `Code` and evaluate it:
```
%%HTML

```
You can change the `width` parameter to resize your video.

## 4.1 Load and parse flight data

Load flight data.

```python
data = load_hardware_data(
    'hardware_data.json',  # <-- replace with name of file with hardware data
    t_min_offset=0.,       # <-- (optional) replace with how many seconds of data to ignore at start
    t_max_offset=0.,       # <-- (optional) replace with how many seconds of data to ignore at end
    only_in_flight=True,   # <-- (optional) only loads data for which o_z_des is positive
)
```

Parse flight data.

```python
# time
t = data['time']

# states
o_x = data['ae483log.o_x']
o_y = data['ae483log.o_y']
o_z = data['ae483log.o_z']
psi = data['ae483log.psi']
theta = data['ae483log.theta']
phi = data['ae483log.phi']
v_x = data['ae483log.v_x']
v_y = data['ae483log.v_y']
v_z = data['ae483log.v_z']

# inputs
w_x = data['ae483log.w_x']
w_y = data['ae483log.w_y']
w_z = data['ae483log.w_z']
a_z = data['ae483log.a_z']

# outputs
n_x = data['ae483log.n_x']
n_y = data['ae483log.n_y']
r = data['ae483log.r']
```

Find time step (should be 0.01).

```python
dt = t[1] - t[0]
print(f'dt = {dt:.4f}')
```

Define numerical values of each parameter.

```python
# Acceleration of gravity
g = 9.81

# Optical flow constant (do not modify)
k_flow = 0.01 * 30.0 / np.deg2rad(4.2)

# Equilibrium value of o_z
o_z_eq = 0.0 # <-- FIXME
```

## 4.2 Error in linearized equations of motion

### 4.2.1 Error in linear model of $\dot{o}_x$

Approximate $\dot{o}_x$ by finite difference and call this "ground truth."

```python
o_x_dot_true = (o_x[1:] - o_x[:-1]) / dt
```

Remember that, because of the way it is computed, the length of the finite difference approximation `o_x_dot_true` is one less than the length of `o_x` (and of `t`):

```python
print(f'len(o_x_dot_true) = {len(o_x_dot_true)}')
print(f'         len(o_x) = {len(o_x)}')
print(f'           len(t) = {len(t)}')
```

Predict $\dot{o}_x$ with linearized equations of motion.

In particular, note that the first element of $\dot{x}$ is $\dot{o}_x$, and that the first element of $Ax+Bu$ is $v_x$. So, our state-space model tells us that $\dot{o}_x \approx v_x$.

```python
o_x_dot_predicted = v_x
```

Compare the true value and the predicted values of $\dot{o}_x$ in a plot.

```python
plt.figure(figsize=(10, 5))
plt.plot(t[:-1], o_x_dot_true, label='$\dot{o}_x$ (true)', linewidth=1)
plt.plot(t, o_x_dot_predicted, '--', label='$\dot{o}_x$ (predicted)', linewidth=2)
plt.legend(fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.xlabel('time (s)', fontsize=14)
plt.show()
```

Compute the error in the linear model, i.e., the difference between the predicted and true values of $\dot{o}_x$.

```python
o_x_dot_err = o_x_dot_predicted[:-1] - o_x_dot_true
```

Plot a histogram of the error, showing mean and standard deviation.

```python
plt.figure(figsize=(5, 5))
plt.hist(o_x_dot_err, 50)
plt.xlabel('error', fontsize=14)
plt.ylabel('count', fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.title(
    'Error in prediction of $\dot{o}_x$\n' +
    f'(mean = {np.mean(o_x_dot_err):6.3f}, std = {np.std(o_x_dot_err):6.3f})',
    fontsize=14,
)
plt.show()
```

### 4.2.2 Error in linear model of $\dot{o}_y$

### 4.2.3 Error in linear model of $\dot{o}_z$

### 4.2.4 Error in linear model of $\dot{\psi}$

### 4.2.5 Error in linear model of $\dot{\theta}$

### 4.2.6 Error in linear model of $\dot{\phi}$

### 4.2.7 Error in linear model of $\dot{v}_x$

### 4.2.8 Error in linear model of $\dot{v}_y$

### 4.2.9 Error in linear model of $\dot{v}_z$

## 4.3 Error in linearized measurement equations

### 4.3.1 Error in linear model of $n_x$

Predict $n_x$ with the linearized measurement equations.

In particular, note that the first element of $y$ is $n_x$, and that the first element of $Cx+Du$ is

$$k_\text{flow} \left( \dfrac{v_x}{o_\text{z, eq}} - w_y \right),$$

so our linear model tells us that

$$n_x \approx k_\text{flow} \left( \dfrac{v_x}{o_\text{z, eq}} - w_y \right).$$

```python
n_x_predicted = k_flow * ((v_x / o_z_eq) - w_y)
```

Compare the true value and the predicted values of $n_x$ in a plot.

```python
plt.figure(figsize=(10, 5))
plt.plot(t, n_x, label='$n_x$ (true)', linewidth=1)
plt.plot(t, n_x_predicted, '--', label='$n_x$ (predicted)', linewidth=2)
plt.legend(fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.xlabel('time (s)', fontsize=14)
plt.show()
```

Compute the error in the linear model, i.e., the difference between the predicted and true values of $n_x$.

```python
n_x_err = n_x_predicted - n_x
```

Plot a histogram of the error, showing mean and standard deviation.

```python
plt.figure(figsize=(5, 5))
plt.hist(n_x_err, 50)
plt.xlabel('error', fontsize=14)
plt.ylabel('count', fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.title(
    'Error in prediction of $n_x$\n' +
    f'(mean = {np.mean(n_x_err):6.3f}, std = {np.std(n_x_err):6.3f})',
    fontsize=14,
)
plt.show()
```

### 4.3.2 Error in linear model of $n_y$

### 4.3.3 Error in linear model of $r$

## 4.4 Summary

The following table reports the mean and standard deviation of error in the linearized equations of motion:

|  | $\dot{o}_x$ | $\dot{o}_y$ | $\dot{o}_z$ | $\dot{\psi}$ | $\dot{\theta}$ | $\dot{\phi}$ | $\dot{v}_x$ | $\dot{v}_y$ | $\dot{v}_z$ |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| mean | 0.123 | 0.456 | 0.789 | 0.789 | 0.456 | 0.123 | 0.123 | 0.123 | 0.123 |
| std | 0.123 | 0.456 | 0.789 | 0.789 | 0.456 | 0.123 | 0.123 | 0.123 | 0.123 |

The following table reports the mean and standard deviation of error in the linearized measurement equations:

|  | $n_x$ | $n_y$ | $r$ |
| :--: | :--: | :--: | :--: |
| mean | 0.123 | 0.456 | 0.789 |
| std | 0.123 | 0.456 | 0.789 |

**Modify the text in this cell** to answer the following questions:

* Which equations of motion do you trust most (or least) and why?
* Which measurement equations do you trust most (or least) and why?


# NX31 Model Project

# Introduction

In this Model project we will examine the Overlapping Generations Model (OLG). First we will look at the General OLG model with  consumption and savings in two periods. Second we will expand with an extensions of the model, as we look at the Pay As You Go (PAYG) OLG model. The PAYG OLG model includes a tax, which the young generation pay from their wages. This tax is then transfered to the elderly generation for them to consume.
We will look an an OLG model with two generations, a generation which is working and a generation living on pension. They have a life-time utility function. In both models there are no savings in period two, as you consume everything as old.

The PAYG OLG model is like a state funded pension, where the young generation pays to the current old generation. When the young generation retires, a new young generation will pay a tax and thus fund their pension.

Our analysis of the model will consist of how the share of capital $\alpha$, the discount factor on consumption $\rho$, and the population growth $n$ effect in the two models differently.

## Packages and extensions

```python
# These two lines above makes sure that the functions and packages are automatically reloaded each time we run a cell.
%load_ext autoreload
%autoreload 2
```

```python
#Loading packages
import numpy as np
import scipy as sp
from scipy import linalg
from scipy import optimize
from scipy import interpolate
import sympy as sm
from IPython.display import display

%matplotlib inline
import matplotlib.pyplot as plt
import ipywidgets as widgets

# Enables sympy to write functions as math
sm.init_printing(use_unicode=True)
```

# The General OLG model:

As mentioned, we start with the General OLG model with savings and consumption. This model looks as shown below.

Population growth:

\begin{eqnarray*}
L_t=(1+n) L_{t-1}
\end{eqnarray*}

Life-time utility:

\begin{eqnarray*}
U_t=ln(c_{1,t} )+ \frac{1}{(1+\rho)}ln(c_{2,t+1} )
\end{eqnarray*}

Model contraints:

\begin{eqnarray*}
ρ>-1
\end{eqnarray*}
\begin{eqnarray*}
c_{1,t}=w_t-s_t
\end{eqnarray*}
\begin{eqnarray*}
c_{2,t+1}=(1+r_{t+1} ) s_t
\end{eqnarray*}

where

- $L_t$ is population
- $n$ is growth rate of population
- $U_t$ is life-time utility
- $c_1,_t$ is consumption as young
- $c_2,_t$ is consumption as old
- $\rho$ is the discount rate on consumption
- $w_t$ is wage
- $s_t$ is savings
- $r_t$ is the interest rate

## The firm problem

We start by optimizing the firm problem, this  will be the same in all extensions of our OLG models.

The output function is given by:

\begin{eqnarray*}
F(K_t,L_t)=Y_t=A(K_t)^\alpha(L_t)^{1-\alpha}
\end{eqnarray*}

The profit function is given by:

\begin{eqnarray*}
\Pi(K_t,L_t)=F(K_t,L_t)-w_tL_t-(r_t+\delta)K_t
\end{eqnarray*}

- $Y_t$ is output
- $A_t$ is total factor productivity (TFP)
- $K_t$ is capital
- $L_t$ is population / labor force
- $\alpha$ is the share of capital
- $\Pi$ is profit
- $w_t$ is wage
- $r_t$ is the real interest rate
- $\delta$ is the degree of depreciation

We will use the profit function and output function, to calculate our marginal products, $r$ and $w$. To do this, we will  use the sympy package, which has math solving tools. We start off by defining variables as symbols.

All symbols used for this model project is defined below.

```python
# Defines variables for sympy math symbols
K_t = sm.symbols('K_t')
L_t = sm.symbols('L_t')
A = sm.symbols('A')
alpha = sm.symbols('alpha')
delta = sm.symbols('delta')
rho = sm.symbols('rho')
w_t = sm.symbols('w_t')
r_t = sm.symbols('r_t')
n = sm.symbols('n')
k_t = sm.symbols('k_t')
Pi = sm.symbols('Pi_t')

s_t = sm.symbols('s_t')
U_t = sm.symbols('U_t')
U = sm.symbols('U_t')
r_plus = sm.symbols('r_t+1')
k = sm.symbols('k^*')

d = sm.symbols('d')
s_payg = sm.symbols('s_payg')
w_plus = sm.symbols('w_t+1')
k_payg = sm.symbols('k_payg')
U_payg = sm.symbols('U_paygt')
kpaygss = sm.symbols('k_payg^*')
```

Now that the symbols are defined, we now setup the functions and solve using the sympy package.

```python
# Solves the firm problem and finds r and w
print(f'The Profit of the Firm')
PI = A*(K_t**alpha)*(L_t**(1-alpha))-w_t*L_t-(r_t+delta)*K_t
display(sm.Eq(Pi,PI))
print(f'The First Order Conditions:')
f1 = sm.diff(PI,K_t)
f2 = sm.diff(PI,L_t)
display(sm.Eq(0,f1))
display(sm.Eq(0,f2))
print(f'The marginal products:')
f1solve = sm.solve(f1, r_t)[0]
f2solve = sm.solve(f2, w_t)[0]
display(sm.Eq(r_t,f1solve))
display(sm.Eq(w_t,f2solve))
```

Now that we have the marginal products of capital and labor. We are interested in having the results given by capital pr. capita, $k$. We do this using:

\begin{eqnarray*}
k_t = \frac{K_t}{L_t} , k_{t+1} = \frac{K_{t+1}}{L_{t+1}}
\end{eqnarray*}

The marginal products of capital, $r_t$, and labor, $w_t$, can be written as.

\begin{eqnarray*}
r_t = \alpha Ak_t^{\alpha-1}-\delta
\end{eqnarray*}

\begin{eqnarray*}
w_t = (1-\alpha) Ak_t^{\alpha}
\end{eqnarray*}

## The household problem

Next we will have to solve the household problem, by finding an expression for savings, $s_t$. Below we once again use the sympy package to solve the household problem and thus finds the savings rate.

```python
# Solve the household problem and find s
U_t = sm.log(w_t-s_t)+(1/(1+rho))*sm.log((1+r_plus)*s_t)
print(f'The Life-time Utility')
display(sm.Eq(U,U_t))
U_d = sm.diff(U_t,s_t)
print(f'The First Orden Condition:')
display(sm.Eq(0,U_d))
usolve = sm.solve(U_d,s_t)[0]
print(f'The Savings rate:')
display(sm.Eq(s_t,usolve))
```

## Finding steady state

### Steady state function

In order to reach a steady state for capital, $k$, we have to setup the transitions curve for capital. Below we use the model and the results we have found, to reach an expression for steady state value $k^*$.

The aggregated solution in the OLG model is defined as:
\begin{eqnarray*}
K_{t+1}=K_t (1-δ)+S_{1,t}+S_{2,t+1},  S_{2,t+1}≡-(1-δ) K_t=0
\end{eqnarray*}
Because there are no savings in second period, and we have assumed full depreciation ($\delta = 1$) we get:
\begin{eqnarray*}
K_{t+1}=S_{1,t}=s_t L_t
\end{eqnarray*}
Now we set up the transition curve:
\begin{eqnarray*}
\frac{K_{t+1}}{L_{t+1}} =k_{t+1}=s_t  \frac{L_t}{L_{t+1}} =s_t  \frac{L_t}{(L_t (1+n) )}=\frac{s_t}{(1+n)}=\frac{w_t}{(2+ρ)}  \frac{1}{(1+n)}=\frac{(1-α)Ak_t^α}{(2+ρ)(1+n)}
\end{eqnarray*}
In steady state we know that there are no change i capital so that:
\begin{eqnarray*}
k_{t+1} = k_t = k^*
\end{eqnarray*}

Next up we solve it using sympy, and find en expression for steady state $k^*$.

```python
# Solves transition curve to find SS k
ss = sm.Eq(k_t,(((1-alpha)*A*(k_t**alpha))/((2+rho)*(1+n))))
kss = sm.solve(ss,k_t)[0]
print(f'The steady state in the General OLG model is:')
display(sm.Eq(k,kss))
```

### Analytical solution

We will now use the function for the steady state $k^*$ in a lambdify function to find the value of the steady state $k^*$ using the analytical method.

```python
#Creates SS function for k
ss_func = sm.lambdify((alpha,n,rho,A),kss)
```

```python
# Solves SS for k, given input
ss_func(1/3,0.02,0.04,1)
```

```python
print(f'The steady state value of capital in the General OLG model is :')
display(sm.Eq(k,0.1814))
```

### Numerical solution

We define the numerical parameter for usage in the numerical solution as well as the visualization later on.

The share of capital, $\alpha$, is generally agreed to be one third, also described by Sørensen and Whitta-Jacobsen.
The growth rate of population, $n$, is set as default to two percent as Romer also would do.
The discount rate on consumption, $\rho$, is set to $0.04$, as default, which means that consumption as young generate more utility than consumption as old.
TFP/A has been normalized to one.

```python
# Defines global values for parameters
alpha_v = 1/3
n_v = 0.02
rho_v = 0.04
A_v = 1
```

To solve the numerical solution we use the function for the steady state $k^*$ from section 3.3.1 and the root_scalar optimizer.

```python
obj_kss = lambda kss: kss - ((1-alpha_v)*A_v*(kss**alpha_v))/((2+rho_v)*(1+n_v))
result = optimize.root_scalar(obj_kss,bracket=[0.1,100],method='brentq')
result
```

          converged: True
               flag: 'converged'
     function_calls: 8
         iterations: 7
               root: 0.18135001783804428

To make sure that the analytical and numerical solution have found the same result we assert this in the cell below.

```python
print(f'Analytical solution is: {ss_func(alpha_v,n_v,rho_v,A_v):.4f}')
print(f'Numerical solution is: {result.root:.4f}')

assert abs(ss_func(alpha_v,n_v,rho_v,A_v) - result.root) < 10**(-2)
```

    Analytical solution is: 0.1814
    Numerical solution is: 0.1814

## Visualization

To compare the General OLG model, with the PAYG OLG model, we will make visualizations of the models. First we will look at the General OLG model, therefore the figures below are all for the General OLG model, but with the option to change the share of capital $\alpha$, the discount factor on consumption $\rho$ and the population growth $n$.

### Interactive Figures
Below we define the function for the the transition curve. After that is done, we create the plots with interactive sliders, where you can see the changes in the transistion towards steady state

```python
# Defining transistion curve general model
def trans_func(k_t, alpha, A, rho, n):
    return ((1-alpha)*A*k_t**alpha)/((2+rho)*(1+n))
```

```python
# Define k_t
k_t = np.linspace(0,1,1000) #(min, max, steps)
```

```python
#Defining the plots
def plot_alpha(alpha):
    print(f"The analytic solution is {ss_func(alpha, n_v,rho_v,A_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('General OLG model - change in $\\alpha$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func(k_t, alpha, A_v, rho_v, n_v))
    plt.plot(ss_func(alpha, n_v, rho_v, A_v),ss_func(alpha, n_v,rho_v,A_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'General OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt

def plot_rho(rho):
    print(f"The analytic solution is {ss_func(alpha_v, n_v,rho,A_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('General OLG model - change in $\\rho$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func(k_t, alpha_v, A_v, rho, n_v))
    plt.plot(ss_func(alpha_v, n_v,rho,A_v),ss_func(alpha_v, n_v,rho,A_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'General OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt

def plot_n(n):
    print(f"The analytic solution is {ss_func(alpha_v, n,rho_v,A_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('General OLG model - change in $n$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func(k_t, alpha_v, A_v, rho_v, n))
    plt.plot(ss_func(alpha_v, n,rho_v,A_v),ss_func(alpha_v, n,rho_v,A_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'General OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt
```

Below we plot the transisition curve with the interactive sliders. The figure is also updating the steady state value of $k^*$, when the slider is moved. This is plotted as an orange dot.

```python
# Plotting plots, with interactive slider
widgets.interact(
        plot_alpha,
        alpha = widgets.FloatSlider(
            description="$\\alpha$",
            min=0.01,
            max=0.99,
            step=0.01,
            value=alpha_v,
            continuous_update=False,),)
widgets.interact(
        plot_rho,
        rho = widgets.FloatSlider(
            description="$\\rho$",
            min=-.99,
            max=0.99,
            step=0.1,
            value=rho_v,
            continuous_update=False,),)
widgets.interact(
        plot_n,
        n = widgets.FloatSlider(
            description="n",
            min=-.20,
            max=0.20,
            step=0.01,
            value=n_v,
            continuous_update=False,),)
```

    interactive(children=(FloatSlider(value=0.3333333333333333, continuous_update=False, description='$\\alpha$', …

    interactive(children=(FloatSlider(value=0.04, continuous_update=False, description='$\\rho$', max=0.99, min=-0…

    interactive(children=(FloatSlider(value=0.02, continuous_update=False, description='n', max=0.2, min=-0.2, ste…

    <function __main__.plot_n(n)>

We see that by changing the share of capital, $\alpha$, to a lower share, the steady state of capital moves up, and vice versa .
The steady state of capital goes up, when the depreciation of consumption, $\rho$, falls, which will make consumption in period two more valuable, and thus the consumer will save more.
Lastly we look at the population growth, $n$, which as the other two parameters, also make capital rise when population growth falls.

# The Pay-As-You-Go OLG model

The life-time utility is same as in the General OLG model, but constraints are different.
The consumption function have changed so that there is a tax when young and pension when old.

\begin{eqnarray*}
U_t=ln(c_{1,t} )+ \frac{1}{(1+\rho)}ln(c_{2,t+1} )
\end{eqnarray*}

constraints

\begin{eqnarray*}
ρ>-1
\end{eqnarray*}
\begin{eqnarray*}
c_{1,t}=w_t(1-d)-s_t
\end{eqnarray*}
\begin{eqnarray*}
c_{2,t+1}=(1+r_{t+1} ) s_t + w_{t+1}d(1+n)
\end{eqnarray*}

where

- $L$ is population
- $n$ is the rate of population
- $U$ is lifetime utility
- $c_1$ is consumption as young
- $c_2$ is consumption as old
- $\rho$ is the discount rate
- $w$ is wage
- $s$ is savings
- $r$ is the interest rate

## Household problem

```python
# Solve the household problem and find s
U_payg = sm.log(w_t*(1-d)-s_payg)+(1/(1+rho))*sm.log((1+r_plus)*s_payg+w_plus*d*(1+n))
print(f'The life-time utility')
display(sm.Eq(U,U_payg))
U_d_payg = sm.diff(U_payg,s_payg)
print(f'The First Order Condition:')
display(sm.Eq(0,U_d_payg))
usolve_payg = sm.solve((r_plus + 1)/((rho + 1)*(d*w_plus*(n + 1) + s_payg*(r_plus + 1))) - 1/(-s_payg + w_t*(-d + 1)),s_payg)[0]
print(f'The Savings rate:')
display(sm.Eq(s_payg,usolve_payg))
```

## Finding staedy state

### The transition curve

Now we set up the transition curve, and we have done some algebraic reduction of the savings function, and we insert the marginal products from the firm problem from the General OLG model.
\begin{eqnarray*}
\frac{K_{t+1}}{L_{t+1}} =k_{t+1}=s_t  \frac{L_t}{L_{t+1}} =s_t  \frac{L_t}{(L_t (1+n) )}=\frac{s_t}{(1+n)}=\frac{1}{(1+n)(2+\rho)}(w_t(1-d)-w_{t+1}d \frac{(1+\rho)(1+n)}{1+r_{t+1}}) = \frac{1}{(1+n)(2+\rho)}((1-\alpha)Ak_t^\alpha(1-d)-(1-\alpha)Ak_{t+1}^\alpha d \frac{(1+\rho)(1+n)}{1+\alpha Ak_{t+1}^{\alpha-1}-\delta})
\end{eqnarray*}

### Analytical solution
We have manually isolated the $k's$ in the transition curve function above, do to sympy not being able to do it.
We found the steady state in the PAYG OLG model only defined by parameters, and defined it as a function to show steady state in the analytic solution.

```python
# Defining steady state value of k
def kss_payg2(alpha,n,rho,A,delta,d):
    return ((alpha*(1-alpha)*(1-d))/((1+n)*(2+rho)*alpha+(1+n)*(1-alpha)*d*(1+rho)))**(1/(1-alpha))
print(f'The steady state in the PAYG OLG model is:')
display(sm.Eq(kpaygss,(((alpha*(1-alpha)*(1-d))/((1+n)*(2+rho)*alpha+(1+n)*(1-alpha)*d*(1+rho)))**(1/(1-alpha)))))
```

```python
# Solves SS for k, given input
kss_payg2(1/3,0.02,0.04,1,1,0.12)
```

```python
print(f'The steady state value of capital in the PAYG OLG model is :')
display(sm.Eq(kpaygss,0.1259))
```

### Numerical solution
Now will find the steady state with numerical analysis. First we define the transition curve function as a lambda function. Then we set up the objective function, which is the transition curve function minus $k_{t+1}$. Lastly we optimize with the root_scalar optimizer.

```python
# Defines global values for parameters
alpha_v = 1/3
n_v = 0.02
rho_v = 0.04
A_v = 1
delta_v = 1
d_v = 0.12
```

```python
# Solves the numerical solution
transition_payg = lambda k_payg, n, rho, alpha, d, A, delta: ((1/((1+n)*(2+rho)))*((1-alpha)*A*(k_payg**alpha)*(1-d)-(1-alpha)*A*(k_payg**alpha)*d*(((1+rho)*(1+n))/(1+alpha*A*(k_payg**(alpha - 1))-delta))))

obj_transition_payg = lambda x, n, rho, alpha, d, A, delta: x - transition_payg(x, n, rho, alpha, d, A, delta)
result_payg = optimize.root_scalar(obj_transition_payg, args=(n_v, rho_v, alpha_v, d_v, A_v, delta_v), bracket=[0.1,100],method='brentq')
result_payg
```

          converged: True
               flag: 'converged'
     function_calls: 8
         iterations: 7
               root: 0.12590629299561928

To make sure that the analytical and numerical solution have found the same result we assert this in the cell below.

```python
print(f'Analytical solution is: {kss_payg2(alpha_v,n_v,rho_v,A_v,delta_v,d_v):.4f}')
print(f'Numerical solution is: {result_payg.root:.4f}')

assert abs(kss_payg2(alpha_v,n_v,rho_v,A_v,delta_v,d_v) - result_payg.root) < 10**(-2)
```

    Analytical solution is: 0.1259
    Numerical solution is: 0.1259

## Visualization of the PAYG OLG model

We will again make visualizations of the model. The PAYG model, also has the option to change the share of capital, $\alpha$, the discount factor on consumption when old, $\rho$, and the population growth $n$.

```python
# Defining transistion curve PAYG model
def trans_func_payg(k_t, alpha, A, rho, n, d, delta):
    return (alpha*(1-alpha)*(1-d)*k_t**alpha)/((1+n)*(2+rho)*alpha+(1+n)*(1-alpha)*d*(1+rho))
k_t = np.linspace(0,1,1000)
```

```python
#Defining the plots
def plot_alpha_payg(alpha):
    print(f"The analytic solution is {kss_payg2(alpha, n_v, rho_v, A_v, delta_v, d_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('PAYG OLG model - change in $\\alpha$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func_payg(k_t, alpha, A_v, rho_v, n_v, d_v, delta_v))
    plt.plot(kss_payg2(alpha, n_v, rho_v, A_v, delta_v, d_v), kss_payg2(alpha, n_v, rho_v, A_v, delta_v, d_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'PAYG OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt

def plot_rho_payg(rho):
    print(f"The analytic solution is {kss_payg2(alpha_v, n_v, rho, A_v, delta_v, d_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('PAYG OLG model - change in $\\rho$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func_payg(k_t, alpha_v, A_v, rho, n_v, d_v, delta_v))
    plt.plot(kss_payg2(alpha_v, n_v, rho, A_v, delta_v, d_v),kss_payg2(alpha_v, n_v, rho, A_v, delta_v, d_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'PAYG OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt

def plot_n_payg(n):
    print(f"The analytic solution is {kss_payg2(alpha_v, n, rho_v, A_v, delta_v, d_v):.4f}")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('PAYG OLG model - change in $n$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,0.5]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func_payg(k_t, alpha_v, A_v, rho_v, n, d_v, delta_v))
    plt.plot(kss_payg2(alpha_v, n, rho_v, A_v, delta_v, d_v),kss_payg2(alpha_v, n, rho_v, A_v, delta_v, d_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line', 'PAYG OLG', 'Steady state'), loc='upper right')
    plt.grid()
    return plt
```

```python
widgets.interact(
        plot_alpha_payg,
        alpha = widgets.FloatSlider(
            description="$\\alpha$",
            min=0.01,
            max=0.99,
            step=0.01,
            value=alpha_v,
            continuous_update=False,
        ),
    )

widgets.interact(
        plot_rho_payg,
        rho = widgets.FloatSlider(
            description="$\\rho$",
            min=-.99,
            max=1,
            step=0.1,
            value=rho_v,
            continuous_update=False,
        ),
    )

widgets.interact(
        plot_n_payg,
        n = widgets.FloatSlider(
            description="n",
            min=-.10,
            max=0.1,
            step=0.01,
            value=n_v,
            continuous_update=False,
        ),
    )
```

    interactive(children=(FloatSlider(value=0.3333333333333333, continuous_update=False, description='$\\alpha$', …

    interactive(children=(FloatSlider(value=0.04, continuous_update=False, description='$\\rho$', max=1.0, min=-0.…

    interactive(children=(FloatSlider(value=0.02, continuous_update=False, description='n', max=0.1, min=-0.1, ste…

    <function __main__.plot_n_payg(n)>

The PAYG OLG model has the exact same properties on the parameters as in the General OLG model. The changes are just smaller, as the level of capital per capita is lower in steady state for the PAYG OLG model.

# Comparing
Before drawing our conclusion we will compare the to OLG models against each other in the figure below, to better get a grasp on their differences.
Using the same code and procedures as above we have contained it all in one plot.

```python
def plot_com(alpha, n, rho):
    print(f"Interactive figure comparing The General OLG Model and The PAYG OLG Model")
    plt.figure(dpi=100)
    plt.xlabel('k_t')
    plt.ylabel('k_t+1')
    plt.title('PAYG model - change in $\\alpha$, $n$ and $\\rho$')
    plt.xlim([0,1]) # fixed x range
    plt.ylim([0,1]) # fixed y range
    plt.plot(range(10),range(10), color = 'black')
    plt.plot(k_t,trans_func(k_t, alpha, A_v, rho, n))
    plt.plot(k_t,trans_func_payg(k_t, alpha, A_v, rho, n, d_v, delta_v))
    plt.plot(ss_func(alpha, n,rho ,A_v), ss_func(alpha, n,rho ,A_v), marker='x')
    plt.plot(kss_payg2(alpha, n, rho, A_v, delta_v, d_v), kss_payg2(alpha, n, rho, A_v, delta_v, d_v), marker='o')
    plt.legend(('45\N{DEGREE SIGN}-line','General OLG', 'PAYG OLG', 'Steady state for General OLG', 'Steady state for PAYG OLG'), loc='upper right')
    plt.grid()
    return plt

widgets.interact(
        plot_com,
        alpha = widgets.FloatSlider(
            description="$\\alpha$",
            min=0.01,
            max=0.99,
            step=0.01,
            value=alpha_v,
            continuous_update=False,
        ),
            n = widgets.FloatSlider(
            description="$n$",
            min=-0.2,
            max=2,
            step=0.01,
            value=n_v,
            continuous_update=False,
        ),
            rho = widgets.FloatSlider(
            description="$\\rho$",
            min=-0.99,
            max=0.99,
            step=0.01,
            value=rho_v,
            continuous_update=False,
        ),
    )
```

    interactive(children=(FloatSlider(value=0.3333333333333333, continuous_update=False, description='$\\alpha$', …

    <function __main__.plot_com(alpha, n, rho)>

# Conclusion

For all parameter values the steady state value of capital per capita is always larger in the General OLG model, than in the PAYG OLG model. This follow economic theory, as the way the PAYG OLG model is build, with the tax transfer of income. The young generation pays a part of their income straight to the elder generation, which requires a lower level of capital, as the old generation now needs a lower level of savings.


Solution to: [Day 4: Binomial Distribution I](https://www.hackerrank.com/challenges/s10-binomial-distribution-1/problem)

Table of Contents

- Table of Contents
- Notes
    - Random Variable
    - Binomial Experiment
    - Bernoulli Random Variable and Distribution
    - Binomial Distribution
    - Cumulative Probability
    - Example Problem
- Solution
    - Imports
    - Input
    - Get Probability
    - Main

```javascript
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')
```

    <IPython.core.display.Javascript object>

# Notes

## Random Variable
A random variable, 	**X**, is the real-valued function X: S -> R in which there is an event for each interval I where I <=R. You can think of it as the set of probabilities for the possible outcomes of a sample space.

For example, if you consider the random variable X as possible sums for the values rolled by 2 four-sided dice:

	X = {2, 3, 4, 5, 6, 7, 8}

	P(X = 2) = P({ (1, 1)}) = 1/16
	P(X = 3) = P({ (1, 2), (2, 1)}) = 2/16
	P(X = 4) = P({ (1, 3), (2, 2), (3, 1)}) = 3/16
	P(X = 5) = P({ (1, 4), (2, 3), (3, 2), (4, 1)}) = 4/16
	P(X = 6) = P({ (2, 4), (3, 3), (4, 2)}) = 3/16
	P(X = 7) = P({ (3, 4), (4, 3)}) = 2/16
	P(X = 8) = P({ (4, 4)}) = 1/16

## Binomial Experiment
A binomial experiment (or Bernoulli trial) is a statistical experiment that has the following properties:
	- The experiment consists of n repeated trials.
	- The trials are independent.
	- The outcome of each trial is either success (s) or failure (f).

## Bernoulli Random Variable and Distribution
The sample space of a binomial experiment only contains two points: **s** and **f**.

We define a Bernoulli random variable to be the random variable defined by:

\begin{equation}
\large
\begin{cases}
X(s) = 1 \\
X(f) = 0
\end{cases}

If we consider the probability of success to be **p** and the probability of failure to be **q** (where q = 1-p),
then the **probability mass function (PMF)** of X is:

\begin{equation}
\large
\mathrm{PMF} =
\begin{cases}
1-p = q, \text{ if x = 0} \\
p, \text{ if x = 1} \\
0, \text{otherwise}
\end{cases}
\end{equation}

This is also expressed as:

\begin{equation}
\large
f(x) = p^{x}(1-p)^{1-x}
\end{equation}

## Binomial Distribution
We define a binomial process to be a binomial experiment meeting the following conditions:

- The number of successes is x.
- The total number of trials is n.
- The probability of success of 1 trial is p.
- The probability of failure of 1 trial q, where q = 1 - p.
- b(x, n, p) is the binomial probability, meaning the probability of having exactly x successes out of n trials.

The binomial random variable is the number of successes, x, out of n trials.

The binomial distribution is the probability distribution for the binomial random variable, given by the following **probability mass function**:

\begin{equation}
\large
b(x, n, p) = {n \choose x} * p^{x} * p^{n-x}
\end{equation}

Note: Recall that ${n \choose x} is notation for combinations, and solved:
\begin{equation}
\large
nCr = \frac{nPr}{!r} = \frac{n!}{r! * (n - r)!}
\end{equation}

## Cumulative Probability
We consider the distribution function for some real-valued random variable, **X**, to be Fx(X) = P(X <= x).

Because this is a non-decreasing function that accumulates all the probabilities for the values of x up to (and including) X, we call it the **cumulative distribution function (CDF)** of X.

As the CDF expresses a cumulative range of values, we can use the following formula to find the cumulative probabilities for all x [a, b]:

\begin{equation}
\large
P(a < X \leq b) = F_{x}(b) - F_{x}(a)
\end{equation}

This formula takes a summation of the binomial probability mass function

## Example Problem

A fair coin is tossed n times. Find the following probabilities:
- Getting 5 heads.
- Getting at least 5 heads.
- Getting at most 5 heads.

* Note: Problem 2 & 3 are solved in Python Solution using CDF function.

To calculate the probability distribution of a binomial distribution, use the binominal probability mass function:

\begin{equation}
\large
\text{b(x, n, p)} = {n \choose x} * p^{x} * q^{n-x}
\end{equation}

\begin{equation}
\large
\text{5, 10, 0.5} = {10 \choose 5} ** 0.5^{5} * 0.5^{0.5} = 0.24609
\end{equation}

# Solution

The ratio of boys to girls for babies born in Russia is 1.09: 1.
If there is 1 child born per birth, what proportion of Russian families with exactly 6 children will have at least 3 boys?

Write a program to compute the answer using the above parameters. Then print your result, rounded to a scale of 3 decimal places (i.e., 1.234 format).

Input Format:
A single line containing the following values:
1.09 1

## Imports

```python
from typing import Tuple
```

## Input

```python
def get_input() -> Tuple[float, int]:
	"""Returns ipnut for binomial distribution problem i

	Returns:
		Tuple[float, int]: Represents respective probabilities of girl and boy babies.
	"""
	girl_prob, boy_prob = [x for x in input().split()]
	return (float(girl_prob), int(boy_prob))
```

## Get Probability

```python
def factorial(num: int) -> float:
	"""Returns factorial for num

	Args:
		num (int): Number to get factorial

	Returns:
		float: Factorial of num
	"""
	if num == 0:
		return 1

	output = num
	for i in range(num-1, 0, -1):
		output *= i
	return output
```

```python
##### Get p
def get_probability_p(girl_prob: float, boy_prob: int, desired_sex: str) -> float:
    """Returns probability of successful trial.

    Args:
        girl_prob (float): P(girl)
        boy_prob (int): P(boy)
        desired_sex (str): calc p for sex

    Returns:
        float: [description]
    """
    if desired_sex == 'm':
        return boy_prob / (girl_prob + boy_prob)
    return girl_prob / (girl_prob + boy_prob)

```

```python
def get_bin_mass_dist(x, n, p) -> float:
	"""Returns binomial mass distribution of p(x, n, p).

	Args:
		x ([type]): Success trials
		n ([type]): Total Trials
		p ([type]): P(X)

	Returns:
		float: probability mass distribution
	"""

	combinations = factorial(n) / (factorial(x) * factorial(n - x))
	bin_mass = combinations * p ** x * (1 - p) ** (n - x)
	return bin_mass
```

```python
def get_cum_prob_bin_dist(r: int, x: int, n: int, p: float) -> float:
	"""Returns cumulative prob of binary distribution with inclusive limits [r:x].

	Args:
		r (int): lower limit of dist
		x (int): upper limit of dist
		n (int): total trial
		p (float): probability of x

	Returns:
		float: cumulative probability of binary distribution
	"""
	cum_prob = 0
	for i in range(r, x + 1):
		bin_mass = get_bin_mass_dist(i, n, p)
		cum_prob += bin_mass
	return cum_prob
```

```python
def solution() -> None:
	"""Prints solution to Day 4: Binomial Distribution i.
	"""
	## Constants
	N = 6
	R = 3
	X = 6
	DESIRED_SEX = 'b'

	## funcs
	girl_prob, boy_prob = get_input()
	p = get_probability_p(girl_prob, boy_prob, DESIRED_SEX)
	cum_prob = get_cum_prob_bin_dist(R, X, N, p)
	print("{0:.3f}".format(cum_prob))
```

## Main

```python
def main():
	"""Prints solutions to:
		- Example problems 1, 2, & 3.
		- Day 4: Binomial Distribution i problem.
	"""
	print("\nExample 2")
	r, x, n, p = 0, 5, 10, 0.5
	print( get_cum_prob_bin_dist(r, x, n, p))

	print("\nExample 3")
	r, x, n, p = 5, 10, 10, 0.5
	print( get_cum_prob_bin_dist(r, x, n, p))

	print("\nSolution Problem")
	solution()
```

```python
if __name__ == "__main__":
	main()
```

    Example 2
    0.623046875

    Example 3
    0.623046875

    Solution Problem
    1.09 1
    0.696

```python

```


```python
import numpy as np
from scipy.optimize import minimize
from bokeh.plotting import figure, output_notebook, show
```

## 特定の比率でAに賭けた場合のリターン

### 最適化

```python
def bet_a(x):
    return ((1 + 2 * x) ** 0.5 * (1 - x) ** 0.5) * -1
```

```python
bet_a_bounds = np.array([[0, 1]])
bet_a0 = 0.1
minimize(bet_a, x0=bet_a0, method="SLSQP", bounds=bet_a_bounds)
```

         fun: -1.0606601717054944
         jac: array([ -1.67191029e-05])
     message: 'Optimization terminated successfully.'
        nfev: 7
         nit: 2
        njev: 2
      status: 0
     success: True
           x: array([ 0.24999112])

### 可視化

```python
x = np.linspace(0, 1, 100)

p = figure(plot_width=400, plot_height=300)
p.line(x, [-bet_a(f) for f in x])
p.yaxis.axis_label = 'リターン'
p.xaxis.axis_label = 'Aに賭ける比率'
output_notebook()
show(p)
```

    Loading BokehJS ...

## A、B、Cを組み合わせた場合の最適化

Aに賭ける金額、Bに賭ける金額、Cに賭ける金額、手元に残す資金をa, b, c, dとすると
下記の最大値をとるようにする
\begin{equation}
  (3a+d)^{\frac{1}{2}}(2b+d)^{\frac{1}{3}}(6c+d)^{\frac{1}{6}}
\end{equation}

```python
def bet_all(x):
    a, b, c, d = x
    m = \
        (3 * a + d) ** (1 / 2) * \
        (2 * b + d) ** (1 / 3) * \
        (6 * c + d) ** (1 / 6)
    return -m
```

```python
cons = (
    {'type': 'eq', 'fun': lambda x: 1 - sum(x)}
)
bet_all0 = np.array([0.5, 0.35, 0.15, 0])
bet_all_bounds = np.array([[0, 1], [0, 1], [0, 1], [0, 1]])
```

```python
minimize(bet_all, x0=np.array([0.4, 0.4, 0.2, 0]),
         constraints=cons, method="SLSQP", bounds=bet_all_bounds)
```

         fun: -1.0699129678097514
         jac: array([-1.06970009, -1.0707888 , -1.06880252, -1.07009485])
     message: 'Optimization terminated successfully.'
        nfev: 26
         nit: 4
        njev: 4
      status: 0
     success: True
           x: array([ 0.49988722,  0.33274228,  0.16673368,  0.00063682])

下記の比率と大体同じになる
* a: 1 / 2
* b: 1 / 3
* c: 1 / 6
* d: 0

```python
'a:{0:.2f}, b:{1:.2f}, c:{2:.2f}, d:{3:.2f}'.format(1 / 2, 1 / 3, 1 / 6, 0)
```

    'a:0.50, b:0.33, c:0.17, d:0.00'

```python
minimize(bet_all, x0=np.array([0.3, 0, 0.1, 0.6]),
         constraints=cons, method="SLSQP", bounds=bet_all_bounds)
```

         fun: -1.069913193919471
         jac: array([-1.07050577, -1.06819969, -1.07156689, -1.06952961])
     message: 'Optimization terminated successfully.'
        nfev: 27
         nit: 4
        njev: 4
      status: 0
     success: True
           x: array([ 0.29852377,  0.03112464,  0.06593063,  0.60442096])

下記の比率と大体同じになる
* a: 5 / 18
* b: 0
* c: 1 / 18
* d: 2 / 3

```python
'a:{0:.2f}, b:{1:.2f}, c:{2:.2f}, d:{3:.2f}'.format(5 / 18, 0, 1 / 18, 2 / 3)
```

    'a:0.28, b:0.00, c:0.06, d:0.67'

```python

```


# Supersymmetric harmonic oscillator

As a warmup, lets go through the steps to get from the Hamiltonian of this system to a quantum circuit that lets us evolve states according to the time evolution operator.

The superpotential is
\begin{equation}
    W(\hat{q}) = \frac{1}{2}m \hat{q}^2
\end{equation}
which leads to the following Hamiltonian,
\begin{equation}
    H = \frac{1}{2}\left(H_B+H_F\right), \quad
        H_B=\hat{p}^2+m^2\hat{q}^2, \quad H_F=m\left[\hat{b}^{\dagger},\hat{b}\right].
\end{equation}
This contains two terms, a bosonic piece and a fermionic piece.  For the bosonic part, we could work in the position or momentum basis, but this is just the Hamiltonian for a harmonic oscillator, so lets convert to the number basis.

```python
import sys
sys.path.append('..')
from src.sympy_utilities import *

import sympy as sp

h_b = 0.5*(p*p + m*m*q*q)

print('Original H = ' + str(h_b))
h_harmonic = sp.expand(h_b.subs(qp_to_ada))
print('Using creation/annihlation operators H = ' + str(h_harmonic))
```

    Original H = 0.5*(m**2*q**2 + p**2)
    Using creation/annihlation operators H = 0.5*m*a*ad + 0.5*m*ad*a

And we know the matrix elements of $A$ and $A^{\dagger}$, they are defined in HamiltonianTerms.  We of course must impose a cutoff at this point, truncating the allowed excitations of the harmonic oscillator

```python
h_matrix = convert(h_harmonic)

import numpy as np

matrix = np.array(h_matrix.subs({m: 1}).tolist())[:-1,:-1]
print(matrix)
```

    [[0.500000000000000 0 0 0 0 0 0 0]
     [0 1.50000000000000 0 0 0 0 0 0]
     [0 0 2.50000000000000 0 0 0 0 0]
     [0 0 0 3.50000000000000 0 0 0 0]
     [0 0 0 0 4.50000000000000 0 0 0]
     [0 0 0 0 0 5.50000000000000 0 0]
     [0 0 0 0 0 0 6.50000000000000 0]
     [0 0 0 0 0 0 0 7.50000000000000]]

Now that we have the expression as a matrix, we can convert it to pauli strings, see \textbf{MatrixToPauliStrings} for the details.

```python
from src.MatrixToPauliString import *
from src.BinaryEncodings import *

pauli_strings = matrix_to_pauli_strings(matrix, standard_encode)
pauli_strings = sp.expand(sp.nsimplify(pauli_strings)) # numeric simplify
print(pauli_strings)
```

    4*I^0*I^1*I^2 - 2*I^0*I^1*Z^2 - I^0*I^2*Z^1 - I^1*I^2*Z^0/2

But we can't forget about the fermionic piece!  We will always add the fermion onto the qubit after the highest numbered qubit for the boson.

```python
h_fermionic = m*b*bdag - m*bdag*b
print(h_fermionic)
```

    m*b*bd - m*bd*b

```python
fq = max_sympy_exponent(pauli_strings) + 1
b_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)+0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
bdag_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)-0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
commutation_lhs = sp.Symbol('X^'+str(fq), commutative=False)*sp.Symbol('Y^'+str(fq),commutative=False)-sp.Symbol('Y^'+str(fq), commutative=False)*sp.Symbol('X^'+str(fq),commutative=False)
commutation_rhs = 2*complex(0,1)*sp.Symbol('Z^'+str(fq))
```

```python
fermionic_strings = sp.simplify(h_fermionic.subs({b: b_ps, bdag: bdag_ps})).subs(commutation_lhs, commutation_rhs)
print(fermionic_strings)
```

    1.0*Z^3*m

```python
h_tot = sp.expand(pauli_strings) + sp.expand(fermionic_strings)
print(h_tot)
```

    4*I^0*I^1*I^2 - 2*I^0*I^1*Z^2 - I^0*I^2*Z^1 - I^1*I^2*Z^0/2 + 1.0*Z^3*m

At this point lets substitute in any parameters of the system (mass, coupling, etc).
This makes the qubit padding substantially easier given the way pauli strings are specified.

```python
new_h = identity_qubit_padded_H(h_tot.subs(m,1))
```

```python
print(new_h)
```

    4*I^0*I^1*I^2*I^3 + 1.0*I^0*I^1*I^2*Z^3 - 2*I^0*I^1*I^3*Z^2 - I^0*I^2*I^3*Z^1 - I^1*I^2*I^3*Z^0/2

Now lets use our qiskit utilities!

```python
from src.qiskit_utilities import pauli_string_to_trotter_step, pauli_string_to_op

print(pauli_string_to_op(new_h))
print(pauli_string_to_trotter_step(new_h,1))
```

    SummedOp([
      -1.0 * IZII,
      -2.0 * IIZI,
      4.0 * IIII,
      -0.5 * ZIII,
      IIIZ
    ])
    global phase: -4
          ┌───────┐   ┌───────┐
    q_0: ─┤ RZ(1) ├───┤ RZ(1) ├──
          ├───────┴┐  ├───────┴┐
    q_1: ─┤ RZ(-2) ├──┤ RZ(-2) ├─
          ├────────┤  ├────────┤
    q_2: ─┤ RZ(-1) ├──┤ RZ(-1) ├─
         ┌┴────────┴┐┌┴────────┴┐
    q_3: ┤ RZ(-0.5) ├┤ RZ(-0.5) ├
         └──────────┘└──────────┘

```python
import pickle

pickle.dump( new_h, open("DATA/ho_hamiltonian.p", "wb") )
```

# Supersymmetric Anharmonic Oscillator

The superpotential is
\begin{equation}
    W(\hat{q}) = \frac{1}{2}m\hat{q}^2 + \frac{1}{4}g\hat{q}^4
\end{equation}

The full Hamiltonian is
\begin{equation}
    H=\frac{1}{2}\left[\hat{p}^2 + m^2\hat{q}^2 + 2mg\hat{q}^4 + g^2\hat{q}^6 - (m+3g\hat{q}^2)\left[b^{\dagger},b\right]\right]
\end{equation}

```python
h_b = 0.5*(p*p + m*m*q*q + 2.*m*g*q*q*q*q + g*g*q*q*q*q*q*q)

print('Original H = ' + str(h_b))
h_harmonic = sp.expand(h_b.subs(qp_to_ada))
print('Using creation/annihlation operators H = ' + str(sp.simplify(h_harmonic)))
```

    Original H = 0.5*(g**2*q**6 + 2.0*g*m*q**4 + m**2*q**2 + p**2)
    Using creation/annihlation operators H = (0.0625*g**2*(a*ad*a*ad**2*a + a*ad*a*ad**3 + a*ad*a**2*ad*a + a*ad*a**2*ad**2 + a*ad*a**3*ad + a*ad*a**4 + a*ad**2*a*ad*a + a*ad**2*a**2*ad + a*ad**2*a**3 + a*ad**3*a*ad + a*ad**3*a**2 + a*ad**4*a + a*ad**5 + a**2*ad*a*ad**2 + a**2*ad*a**3 + a**2*ad**2*a*ad + a**2*ad**2*a**2 + a**2*ad**3*a + a**2*ad**4 + a**2*(ad*a)**2 + a**3*ad*a*ad + a**3*ad*a**2 + a**3*ad**2*a + a**3*ad**3 + a**4*ad*a + a**4*ad**2 + a**5*ad + a**6 + ad*a*ad*a**2*ad + ad*a*ad*a**3 + ad*a*ad**2*a*ad + ad*a*ad**2*a**2 + ad*a*ad**3*a + ad*a*ad**4 + ad*a**2*ad*a*ad + ad*a**2*ad**2*a + ad*a**2*ad**3 + ad*a**3*ad*a + ad*a**3*ad**2 + ad*a**4*ad + ad*a**5 + ad**2*a*ad*a**2 + ad**2*a*ad**3 + ad**2*a**2*ad*a + ad**2*a**2*ad**2 + ad**2*a**3*ad + ad**2*a**4 + ad**2*(a*ad)**2 + ad**3*a*ad*a + ad**3*a*ad**2 + ad**3*a**2*ad + ad**3*a**3 + ad**4*a*ad + ad**4*a**2 + ad**5*a + ad**6 + (a*ad)**2*a**2 + (a*ad)**3 + (a*ad**2)**2 + (a**2*ad)**2 + (ad*a)**2*ad**2 + (ad*a)**3 + (ad*a**2)**2 + (ad**2*a)**2) + 0.25*g*m**2*(a*ad*a**2 + a*ad**2*a + a*ad**3 + a**2*ad*a + a**2*ad**2 + a**3*ad + a**4 + ad*a*ad**2 + ad*a**2*ad + ad*a**3 + ad**2*a*ad + ad**2*a**2 + ad**3*a + ad**4 + (a*ad)**2 + (ad*a)**2) + 0.5*m**4*(a*ad + ad*a))/m**3

```python
h_matrix = convert(sp.expand(h_harmonic))
matrix = np.array(h_matrix.subs({m: 1, g: 1}).tolist())[:-1,:-1]
print(matrix)
```

    [[2.18750000000000 0 6.09879598773397 0 5.81753813911005 0
      1.67705098312484 0]
     [0 11.8125000000000 0 22.1985007939726 0 17.1163299220364 0
      4.43705983732471]
     [6.09879598773397 0 35.6875000000000 0 54.3430940874735 0
      36.7614777994574 0]
     [0 22.1985007939726 0 81.3125000000000 0 108.169788411552 0
      67.0226174511262]
     [5.81753813911005 0 54.3430940874735 0 156.187500000000 0
      189.306608937723 0]
     [0 17.1163299220364 0 108.169788411552 0 267.812500000000 0
      274.216340801383]
     [1.67705098312484 0 36.7614777994574 0 189.306608937723 0
      392.187500000000 0]
     [0 4.43705983732471 0 67.0226174511262 0 274.216340801383 0
      392.812500000000]]

```python
pauli_strings = matrix_to_pauli_strings(matrix, standard_encode)
pauli_strings = sp.expand(sp.nsimplify(pauli_strings)) # numeric simplify removes very small numbers
print(pauli_strings)
```

    335*I^0*I^1*I^2/2 + 2534359266234601*I^0*I^1*X^2/80000000000000 - 539*I^0*I^1*Z^2/4 + 49182024652081257*I^0*I^2*X^1/400000000000000 - 58*I^0*I^2*Z^1 + 3372539866389501*I^0*X^1*X^2/80000000000000 - 43522565295739943*I^0*X^1*Z^2/400000000000000 - 1617004543788743*I^0*X^2*Z^1/80000000000000 + 5707975433571519*I^0*Z^1*Z^2/80000000000000 - 335*I^1*I^2*Z^0/16 - 831198628691903*I^1*X^2*Z^0/80000000000000 + 57*I^1*Z^0*Z^2/8 - 10100943666989863*I^2*X^1*Z^0/400000000000000 - 75*I^2*Z^0*Z^1/8 - 5658670317827837*X^1*X^2*Z^0/400000000000000 + 6881002705742137*X^1*Z^0*Z^2/400000000000000 + 379246957374849*X^2*Z^0*Z^1/80000000000000 + 2243331453012137*Z^0*Z^1*Z^2/400000000000000

```python
h_fermionic = -(m+3*g*q*q)*(b*bdag - bdag*b)
h_fermionic = h_fermionic.subs(qp_to_ada)
print(h_fermionic)
```

    (-1.5*g*(a + ad)**2/m - m)*(b*bd - bd*b)

```python
fq = max_sympy_exponent(pauli_strings) + 1
b_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)+0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
bdag_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)-0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
commutation_lhs = sp.Symbol('X^'+str(fq), commutative=False)*sp.Symbol('Y^'+str(fq),commutative=False)-sp.Symbol('Y^'+str(fq), commutative=False)*sp.Symbol('X^'+str(fq),commutative=False)
commutation_rhs = 2*complex(0,1)*sp.Symbol('Z^'+str(fq))
```

```python
fermionic_strings = h_fermionic.subs({b*bdag - bdag*b: commutation_rhs})
print(fermionic_strings)
```

    2.0*I*Z^3*(-1.5*g*(a + ad)**2/m - m)

```python
sp.expand(fermionic_strings)
```

$\displaystyle - \frac{3.0 i Z^{3} g a ad}{m} - \frac{3.0 i Z^{3} g a^{2}}{m} - \frac{3.0 i Z^{3} g ad a}{m} - \frac{3.0 i Z^{3} g ad^{2}}{m} - 2.0 i Z^{3} m$

```python
h_matrix = convert(sp.expand(sp.simplify(fermionic_strings + 2.0*complex(0,1)*sp.Symbol('Z^'+str(fq))*m)))

matrix = np.array(h_matrix.subs({m: 1, g: 1}).tolist())[:-1,:-1]

print(matrix)
```

    [[-3.0*I*Z^3 0 -4.24264068711929*I*Z^3 0 0 0 0 0]
     [0 -9.0*I*Z^3 0 -7.34846922834953*I*Z^3 0 0 0 0]
     [-4.24264068711929*I*Z^3 0 -15.0*I*Z^3 0 -10.3923048454133*I*Z^3 0 0 0]
     [0 -7.34846922834954*I*Z^3 0 -21.0*I*Z^3 0 -13.4164078649987*I*Z^3 0 0]
     [0 0 -10.3923048454133*I*Z^3 0 -27.0*I*Z^3 0 -16.431676725155*I*Z^3 0]
     [0 0 0 -13.4164078649987*I*Z^3 0 -33.0*I*Z^3 0 -19.4422220952236*I*Z^3]
     [0 0 0 0 -16.431676725155*I*Z^3 0 -39.0*I*Z^3 0]
     [0 0 0 0 0 -19.4422220952236*I*Z^3 0 -45.0*I*Z^3]]

```python
f_pauli_strings = matrix_to_pauli_strings(matrix, standard_encode)
f_pauli_strings = sp.expand(sp.nsimplify(f_pauli_strings)) # numeric simplify
print(sp.N(f_pauli_strings))
```

    -24.0*I*I^0*I^1*I^2*Z^3 + 12.0*I*I^0*I^1*Z^2*Z^3 - 11.8662521839619*I*I^0*I^2*X^1*Z^3 - 1.25e-15*I^0*I^2*Z^1*Z^3 + 6.0*I*I^0*I^2*Z^1*Z^3 - 5.952178177603*I*I^0*X^1*X^2*Z^3 + 6.07069722622744*I*I^0*X^1*Z^2*Z^3 - 1.25e-15*I^0*Z^1*Z^2*Z^3 - 5.952178177603*I*I^0*Z^1*Z^2*Z^3 + 3.0*I*I^1*I^2*Z^0*Z^3 + 1.52909347782471*I*I^2*X^1*Z^0*Z^3 + 1.25e-15*I^2*Z^0*Z^1*Z^3 + 0.75602575489635*I*X^1*X^2*Z^0*Z^3 + 0.0238207927904113*I*X^1*Z^0*Z^2*Z^3 + 1.25e-15*Z^0*Z^1*Z^2*Z^3 + 0.75602575489635*I*Z^0*Z^1*Z^2*Z^3

```python
total_h = pauli_strings + f_pauli_strings - 2.0*complex(0,1)*sp.Symbol('Z^'+str(fq))
```

```python
sp.N(total_h)
```

$\displaystyle - 24.0 i I^{0} I^{1} I^{2} Z^{3} + 167.5 I^{0} I^{1} I^{2} + 31.6794908279325 I^{0} I^{1} X^{2} + 12.0 i I^{0} I^{1} Z^{2} Z^{3} - 134.75 I^{0} I^{1} Z^{2} - 11.8662521839619 i I^{0} I^{2} X^{1} Z^{3} + 122.955061630203 I^{0} I^{2} X^{1} - 1.25 \cdot 10^{-15} I^{0} I^{2} Z^{1} Z^{3} + 6.0 i I^{0} I^{2} Z^{1} Z^{3} - 58.0 I^{0} I^{2} Z^{1} - 5.952178177603 i I^{0} X^{1} X^{2} Z^{3} + 42.1567483298688 I^{0} X^{1} X^{2} + 6.07069722622744 i I^{0} X^{1} Z^{2} Z^{3} - 108.80641323935 I^{0} X^{1} Z^{2} - 20.2125567973593 I^{0} X^{2} Z^{1} - 1.25 \cdot 10^{-15} I^{0} Z^{1} Z^{2} Z^{3} - 5.952178177603 i I^{0} Z^{1} Z^{2} Z^{3} + 71.349692919644 I^{0} Z^{1} Z^{2} + 3.0 i I^{1} I^{2} Z^{0} Z^{3} - 20.9375 I^{1} I^{2} Z^{0} - 10.3899828586488 I^{1} X^{2} Z^{0} + 7.125 I^{1} Z^{0} Z^{2} + 1.52909347782471 i I^{2} X^{1} Z^{0} Z^{3} - 25.2523591674747 I^{2} X^{1} Z^{0} + 1.25 \cdot 10^{-15} I^{2} Z^{0} Z^{1} Z^{3} - 9.375 I^{2} Z^{0} Z^{1} + 0.75602575489635 i X^{1} X^{2} Z^{0} Z^{3} - 14.1466757945696 X^{1} X^{2} Z^{0} + 0.0238207927904113 i X^{1} Z^{0} Z^{2} Z^{3} + 17.2025067643553 X^{1} Z^{0} Z^{2} + 4.74058696718561 X^{2} Z^{0} Z^{1} + 1.25 \cdot 10^{-15} Z^{0} Z^{1} Z^{2} Z^{3} + 0.75602575489635 i Z^{0} Z^{1} Z^{2} Z^{3} + 5.60832863253034 Z^{0} Z^{1} Z^{2} - 2.0 i Z^{3}$

```python
new_h = identity_qubit_padded_H(total_h)
```

```python
from src.qiskit_utilities import pauli_string_to_trotter_step, pauli_string_to_op

print(pauli_string_to_op(new_h))
print(pauli_string_to_trotter_step(new_h,1))
```

    SummedOp([
      -58.0 * IZII,
      -108.80641323934985 * IXZI,
      -25.25235916747466 * ZXII,
      -14.146675794569592 * ZXXI,
      -20.212556797359287 * IZXI,
      -10.389982858648787 * ZIXI,
      -134.75 * IIZI,
      -20.9375 * ZIII,
      -9.375 * ZZII,
      -1.25e-15 * IZIZ,
      -1.25e-15 * IZZZ,
      1.25e-15 * ZZIZ,
      1.25e-15 * ZZZZ,
      7.125 * ZIZI,
      167.5 * IIII,
      4.740586967185612 * ZZXI,
      5.608328632530342 * ZZZI,
      31.67949082793251 * IIXI,
      42.15674832986876 * IXXI,
      71.34969291964399 * IZZI,
      17.20250676435534 * ZXZI,
      122.95506163020315 * IXII,
      0.0 * ZIIZ,
      0.0 * IIZZ,
      0.0 * IXIZ,
      0.0 * IXXZ,
      0.0 * ZXXZ,
      0.0 * ZXZZ,
      0.0 * IXZZ,
      0.0 * ZXIZ,
      0.0 * IIIZ
    ])
    global phase: -4.1372
         ┌───────┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐     ┌───┐┌───────┐┌───┐┌───┐»
    q_0: ┤ RZ(0) ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├─────┤ X ├┤ RZ(0) ├┤ X ├┤ X ├»
         └───────┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘     └─┬─┘└───────┘└─┬─┘└─┬─┘»
    q_1: ───────────┼────┼─────────────┼────┼────■─────────┼─────────────┼────■──»
           ┌───┐    │    │             │    │  ┌───┐┌───┐  │             │  ┌───┐»
    q_2: ──┤ H ├────■────┼─────────────┼────■──┤ H ├┤ H ├──■─────────────■──┤ H ├»
           └───┘         │             │       └───┘└───┘                   └───┘»
    q_3: ────────────────■─────────────■─────────────────────────────────────────»
                                                                                »
    «     ┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐     ┌───┐┌───┐┌───┐┌───────┐┌───┐»
    «q_0: ┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├─────┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├»
    «     └─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘┌───┐└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘»
    «q_1: ──■────┼────┼─────────────┼────┼────■──┤ H ├──■────┼────┼─────────────┼──»
    «     ┌───┐  │    │             │    │  ┌───┐├───┤       │    │             │  »
    «q_2: ┤ H ├──■────┼─────────────┼────■──┤ H ├┤ H ├───────■────┼─────────────┼──»
    «     └───┘       │             │       └───┘└───┘            │             │  »
    «q_3: ────────────■─────────────■─────────────────────────────■─────────────■──»
    «                                                                              »
    «     ┌───┐┌───┐          ┌───┐┌───┐┌───────┐┌───┐┌───┐     ┌───┐┌───────┐┌───┐»
    «q_0: ┤ X ├┤ X ├──────────┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├─────┤ X ├┤ RZ(0) ├┤ X ├»
    «     └─┬─┘└─┬─┘┌───┐┌───┐└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘┌───┐└─┬─┘└───────┘└─┬─┘»
    «q_1: ──┼────■──┤ H ├┤ H ├──■────┼─────────────┼────■──┤ H ├──┼─────────────┼──»
    «       │  ┌───┐├───┤└───┘       │             │  ┌───┐├───┤  │             │  »
    «q_2: ──■──┤ H ├┤ H ├────────────■─────────────■──┤ H ├┤ H ├──■─────────────■──»
    «          └───┘└───┘                             └───┘└───┘                   »
    «q_3: ─────────────────────────────────────────────────────────────────────────»
    «                                                                              »
    «     ┌───┐  ┌───────┐   ┌───┐┌───┐┌───────┐┌───┐                             »
    «q_0: ┤ X ├──┤ RZ(0) ├───┤ X ├┤ X ├┤ RZ(0) ├┤ X ├─────────────────────────────»
    «     └─┬─┘  └───────┘   └─┬─┘└─┬─┘└─┬───┬─┘└─┬─┘┌───┐┌────────────┐┌───┐┌───┐»
    «q_1: ──■──────────────────■────┼────┤ X ├────┼──┤ X ├┤ RZ(17.203) ├┤ X ├┤ X ├»
    «     ┌───┐┌────────────┐┌───┐  │    └─┬─┘    │  └─┬─┘└────────────┘└─┬─┘└─┬─┘»
    «q_2: ┤ H ├┤ RX(122.96) ├┤ H ├──┼──────■──────┼────┼──────────────────┼────■──»
    «     └───┘└────────────┘└───┘  │             │    │                  │       »
    «q_3: ──────────────────────────■─────────────■────■──────────────────■───────»
    «                                                                             »
    «                                                                   »
    «q_0: ──────────────────────────────────────────────────────────────»
    «          ┌───┐┌───────────┐┌───┐┌───┐┌───┐┌────────────┐┌───┐┌───┐»
    «q_1: ─────┤ X ├┤ RZ(71.35) ├┤ X ├┤ H ├┤ X ├┤ RZ(42.157) ├┤ X ├┤ H ├»
    «     ┌───┐└─┬─┘└───────────┘└─┬─┘├───┤└─┬─┘└────────────┘└─┬─┘├───┤»
    «q_2: ┤ H ├──■─────────────────■──┤ H ├──■──────────────────■──┤ H ├»
    «     └───┘                       └───┘                        └───┘»
    «q_3: ──────────────────────────────────────────────────────────────»
    «                                                                   »
    «                                                                    »
    «q_0: ───────────────────────────────────────────────────────────────»
    «     ┌────────────┐┌───┐┌───┐┌────────────┐┌───┐┌───┐┌───┐┌───┐┌───┐»
    «q_1: ┤ RX(31.679) ├┤ X ├┤ X ├┤ RZ(5.6083) ├┤ X ├┤ X ├┤ H ├┤ X ├┤ X ├»
    «     └────────────┘└─┬─┘└─┬─┘└────────────┘└─┬─┘└─┬─┘└───┘└─┬─┘└─┬─┘»
    «q_2: ────────────────■────┼──────────────────┼────■─────────■────┼──»
    «                          │                  │                   │  »
    «q_3: ─────────────────────■──────────────────■───────────────────■──»
    «                                                                    »
    «                                                         ┌───┐┌───┐┌───┐»
    «q_0: ────────────────────────────────────────────────────┤ X ├┤ X ├┤ X ├»
    «     ┌────────────┐┌───┐┌───┐┌───┐┌───┐┌───────────┐┌───┐└─┬─┘└─┬─┘└─┬─┘»
    «q_1: ┤ RZ(4.7406) ├┤ X ├┤ X ├┤ H ├┤ X ├┤ RZ(7.125) ├┤ X ├──■────┼────┼──»
    «     └────────────┘└─┬─┘└─┬─┘└───┘└─┬─┘└───────────┘└─┬─┘       │    │  »
    «q_2: ────────────────┼────■─────────┼─────────────────┼─────────■────┼──»
    «                     │              │                 │              │  »
    «q_3: ────────────────■──────────────■─────────────────■──────────────■──»
    «                                                                        »
    «     ┌───────┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───┐┌───────┐»
    «q_0: ┤ RZ(0) ├┤ X ├┤ X ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├»
    «     └───────┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───────┘»
    «q_1: ───────────┼────┼────■────┼────┼─────────────┼────┼────■────┼───────────»
    «                │    │         │    │             │    │         │           »
    «q_2: ───────────┼────■─────────■────┼─────────────┼────■─────────■───────────»
    «                │                   │             │                          »
    «q_3: ───────────■───────────────────■─────────────■──────────────────────────»
    «                                                                             »
    «     ┌───┐┌───┐┌───┐   ┌───────┐        ┌───┐                        »
    «q_0: ┤ X ├┤ X ├┤ X ├───┤ RZ(0) ├────────┤ X ├────────────────────────»
    «     └─┬─┘└─┬─┘└─┬─┘┌──┴───────┴──┐┌───┐└─┬─┘                        »
    «q_1: ──┼────■────┼──┤ RZ(-134.75) ├┤ H ├──┼──────────────────────────»
    «       │         │  └─────────────┘└───┘  │  ┌───┐┌────────────┐┌───┐»
    «q_2: ──■─────────■────────────────────────■──┤ X ├┤ RZ(-9.375) ├┤ X ├»
    «                                             └─┬─┘└────────────┘└─┬─┘»
    «q_3: ──────────────────────────────────────────■──────────────────■──»
    «                                                                     »
    «                                                                          »
    «q_0: ─────────────────────────────────────────────────────────────────────»
    «                    ┌───┐┌────────────┐┌───┐┌───┐┌───┐┌───┐┌─────────────┐»
    «q_1: ───────────────┤ X ├┤ RZ(-10.39) ├┤ X ├┤ H ├┤ H ├┤ X ├┤ RZ(-20.213) ├»
    «                    └─┬─┘└────────────┘└─┬─┘└───┘└───┘└─┬─┘└─────────────┘»
    «q_2: ─────────────────┼──────────────────┼──────────────■─────────────────»
    «     ┌─────────────┐  │                  │                                »
    «q_3: ┤ RZ(-20.938) ├──■──────────────────■────────────────────────────────»
    «     └─────────────┘                                                      »
    «                                                                      »
    «q_0: ─────────────────────────────────────────────────────────────────»
    «     ┌───┐┌───┐┌───┐┌───┐┌───┐┌─────────────┐┌───┐┌───┐┌───┐          »
    «q_1: ┤ X ├┤ H ├┤ H ├┤ X ├┤ X ├┤ RZ(-14.147) ├┤ X ├┤ X ├┤ H ├──────────»
    «     └─┬─┘├───┤└───┘└─┬─┘└─┬─┘└─────────────┘└─┬─┘└─┬─┘├───┤┌───┐┌───┐»
    «q_2: ──■──┤ H ├───────■────┼───────────────────┼────■──┤ H ├┤ H ├┤ X ├»
    «          └───┘            │                   │       └───┘└───┘└─┬─┘»
    «q_3: ──────────────────────■───────────────────■───────────────────■──»
    «                                                                      »
    «                                                                            »
    «q_0: ───────────────────────────────────────────────────────────────────────»
    «                                   ┌───┐┌─────────────┐┌───┐                »
    «q_1: ──────────────────────────────┤ X ├┤ RZ(-108.81) ├┤ X ├────────────────»
    «     ┌─────────────┐┌───┐┌───┐┌───┐└─┬─┘└─────────────┘└─┬─┘┌───┐┌─────────┐»
    «q_2: ┤ RZ(-25.252) ├┤ X ├┤ H ├┤ H ├──■───────────────────■──┤ H ├┤ RZ(-58) ├»
    «     └─────────────┘└─┬─┘└───┘└───┘                         └───┘└─────────┘»
    «q_3: ─────────────────■─────────────────────────────────────────────────────»
    «                                                                            »
    «                                                                            »
    «q_0: ───────────────────────────────────────────────────────────────────────»
    «                     ┌───┐┌─────────────┐┌───┐┌───┐                         »
    «q_1: ────────────────┤ X ├┤ RZ(-108.81) ├┤ X ├┤ H ├─────────────────────────»
    «     ┌─────────┐┌───┐└─┬─┘└─────────────┘└─┬─┘├───┤┌───┐┌───┐┌─────────────┐»
    «q_2: ┤ RZ(-58) ├┤ H ├──■───────────────────■──┤ H ├┤ H ├┤ X ├┤ RZ(-25.252) ├»
    «     └─────────┘└───┘                         └───┘└───┘└─┬─┘└─────────────┘»
    «q_3: ─────────────────────────────────────────────────────■─────────────────»
    «                                                                            »
    «                                                                      »
    «q_0: ─────────────────────────────────────────────────────────────────»
    «                    ┌───┐┌───┐┌─────────────┐┌───┐┌───┐┌───┐┌───┐┌───┐»
    «q_1: ───────────────┤ X ├┤ X ├┤ RZ(-14.147) ├┤ X ├┤ X ├┤ H ├┤ H ├┤ X ├»
    «     ┌───┐┌───┐┌───┐└─┬─┘└─┬─┘└─────────────┘└─┬─┘└─┬─┘├───┤└───┘└─┬─┘»
    «q_2: ┤ X ├┤ H ├┤ H ├──■────┼───────────────────┼────■──┤ H ├───────■──»
    «     └─┬─┘└───┘└───┘       │                   │       └───┘          »
    «q_3: ──■───────────────────■───────────────────■──────────────────────»
    «                                                                      »
    «                                                                          »
    «q_0: ─────────────────────────────────────────────────────────────────────»
    «     ┌─────────────┐┌───┐┌───┐┌───┐┌───┐┌────────────┐┌───┐     ┌───┐     »
    «q_1: ┤ RZ(-20.213) ├┤ X ├┤ H ├┤ H ├┤ X ├┤ RZ(-10.39) ├┤ X ├─────┤ H ├─────»
    «     └─────────────┘└─┬─┘└───┘└───┘└─┬─┘└────────────┘└─┬─┘     └───┘     »
    «q_2: ─────────────────■──────────────┼──────────────────┼─────────────────»
    «                                     │                  │  ┌─────────────┐»
    «q_3: ────────────────────────────────■──────────────────■──┤ RZ(-20.938) ├»
    «                                                           └─────────────┘»
    «                                       ┌───┐┌───────┐┌───┐┌───┐┌───┐┌───────┐»
    «q_0: ──────────────────────────────────┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├»
    «     ┌─────────────┐                   └─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘└───────┘»
    «q_1: ┤ RZ(-134.75) ├─────────────────────┼─────────────┼────■────┼───────────»
    «     └────┬───┬────┘┌────────────┐┌───┐  │             │         │           »
    «q_2: ─────┤ X ├─────┤ RZ(-9.375) ├┤ X ├──■─────────────■─────────■───────────»
    «          └─┬─┘     └────────────┘└─┬─┘                                      »
    «q_3: ───────■───────────────────────■────────────────────────────────────────»
    «                                                                             »
    «     ┌───┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐»
    «q_0: ┤ X ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├»
    «     └─┬─┘└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘»
    «q_1: ──┼────■────┼────┼─────────────┼────┼────■────┼────┼─────────────┼────┼──»
    «       │         │    │             │    │         │    │             │    │  »
    «q_2: ──■─────────■────┼─────────────┼────■─────────■────┼─────────────┼────■──»
    «                      │             │                   │             │       »
    «q_3: ─────────────────■─────────────■───────────────────■─────────────■───────»
    «                                                                              »
    «     ┌───┐                                                                   »
    «q_0: ┤ X ├───────────────────────────────────────────────────────────────────»
    «     └─┬─┘┌───┐┌───────────┐┌───┐┌───┐┌───┐┌───┐┌────────────┐┌───┐┌───┐┌───┐»
    «q_1: ──■──┤ X ├┤ RZ(7.125) ├┤ X ├┤ H ├┤ X ├┤ X ├┤ RZ(4.7406) ├┤ X ├┤ X ├┤ H ├»
    «          └─┬─┘└───────────┘└─┬─┘└───┘└─┬─┘└─┬─┘└────────────┘└─┬─┘└─┬─┘└───┘»
    «q_2: ───────┼─────────────────┼─────────■────┼──────────────────┼────■───────»
    «            │                 │              │                  │            »
    «q_3: ───────■─────────────────■──────────────■──────────────────■────────────»
    «                                                                             »
    «                                                                             »
    «q_0: ────────────────────────────────────────────────────────────────────────»
    «     ┌───┐┌───┐┌────────────┐┌───┐┌───┐┌────────────┐┌───┐┌───┐┌────────────┐»
    «q_1: ┤ X ├┤ X ├┤ RZ(5.6083) ├┤ X ├┤ X ├┤ RX(31.679) ├┤ H ├┤ X ├┤ RZ(42.157) ├»
    «     └─┬─┘└─┬─┘└────────────┘└─┬─┘└─┬─┘└───┬───┬────┘└───┘└─┬─┘└────────────┘»
    «q_2: ──■────┼──────────────────┼────■──────┤ H ├────────────■────────────────»
    «            │                  │           └───┘                             »
    «q_3: ───────■──────────────────■─────────────────────────────────────────────»
    «                                                                             »
    «                                                                             »
    «q_0: ────────────────────────────────────────────────────────────────────────»
    «     ┌───┐┌───┐┌───┐┌───────────┐┌───┐     ┌───┐┌───┐┌────────────┐┌───┐┌───┐»
    «q_1: ┤ X ├┤ H ├┤ X ├┤ RZ(71.35) ├┤ X ├─────┤ X ├┤ X ├┤ RZ(17.203) ├┤ X ├┤ X ├»
    «     └─┬─┘├───┤└─┬─┘└───────────┘└─┬─┘┌───┐└─┬─┘└─┬─┘└────────────┘└─┬─┘└─┬─┘»
    «q_2: ──■──┤ H ├──■─────────────────■──┤ H ├──■────┼──────────────────┼────■──»
    «          └───┘                       └───┘       │                  │       »
    «q_3: ─────────────────────────────────────────────■──────────────────■───────»
    «                                                                             »
    «     ┌───┐┌───────┐              ┌───┐┌───┐┌───────┐┌───┐┌───┐┌───────┐┌───┐»
    «q_0: ┤ X ├┤ RZ(0) ├──────────────┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├»
    «     └─┬─┘└───────┘              └─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬───┬─┘└─┬─┘»
    «q_1: ──┼───────────────────────────┼────■─────────────■────┼────┤ H ├────┼──»
    «       │    ┌───┐  ┌────────────┐  │  ┌───┐                │    └───┘    │  »
    «q_2: ──┼────┤ H ├──┤ RX(122.96) ├──┼──┤ H ├────────────────■─────────────■──»
    «       │    └───┘  └────────────┘  │  └───┘                                 »
    «q_3: ──■───────────────────────────■────────────────────────────────────────»
    «                                                                            »
    «     ┌───┐     ┌───┐┌───────┐┌───┐┌───┐          ┌───┐┌───┐┌───┐┌───────┐┌───┐»
    «q_0: ┤ X ├─────┤ X ├┤ RZ(0) ├┤ X ├┤ X ├──────────┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├»
    «     └─┬─┘     └─┬─┘└───────┘└─┬─┘└─┬─┘┌───┐┌───┐└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘»
    «q_1: ──■─────────┼─────────────┼────■──┤ H ├┤ H ├──■────┼────┼─────────────┼──»
    «     ┌───┐┌───┐  │             │  ┌───┐├───┤└───┘       │    │             │  »
    «q_2: ┤ H ├┤ H ├──■─────────────■──┤ H ├┤ H ├────────────■────┼─────────────┼──»
    «     └───┘└───┘                   └───┘└───┘                 │             │  »
    «q_3: ────────────────────────────────────────────────────────■─────────────■──»
    «                                                                              »
    «     ┌───┐┌───┐     ┌───┐┌───┐┌───┐┌───────┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───────┐»
    «q_0: ┤ X ├┤ X ├─────┤ X ├┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ X ├┤ X ├┤ X ├┤ RZ(0) ├»
    «     └─┬─┘└─┬─┘┌───┐└─┬─┘└─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───────┘»
    «q_1: ──┼────■──┤ H ├──■────┼────┼─────────────┼────┼────■────■────┼───────────»
    «       │  ┌───┐├───┤       │    │             │    │  ┌───┐┌───┐  │           »
    «q_2: ──■──┤ H ├┤ H ├───────■────┼─────────────┼────■──┤ H ├┤ H ├──■───────────»
    «          └───┘└───┘            │             │       └───┘└───┘              »
    «q_3: ───────────────────────────■─────────────■───────────────────────────────»
    «                                                                              »
    «     ┌───┐┌───┐     ┌───┐┌───┐┌───────┐┌───┐┌───┐┌───────┐
    «q_0: ┤ X ├┤ X ├─────┤ X ├┤ X ├┤ RZ(0) ├┤ X ├┤ X ├┤ RZ(0) ├
    «     └─┬─┘└─┬─┘     └─┬─┘└─┬─┘└───────┘└─┬─┘└─┬─┘└───────┘
    «q_1: ──┼────■─────────┼────┼─────────────┼────┼───────────
    «       │  ┌───┐┌───┐  │    │             │    │    ┌───┐
    «q_2: ──■──┤ H ├┤ H ├──■────┼─────────────┼────■────┤ H ├──
    «          └───┘└───┘       │             │         └───┘
    «q_3: ──────────────────────■─────────────■────────────────
    «

```python

```

```python
import pickle

pickle.dump( new_h, open("DATA/aho_hamiltonian.p", "wb") )
```

```python
# A much shorter version of getting the pauli string hamiltonian.
```

```python
import sys
sys.path.append('..')
from src.sympy_utilities import *
from src.MatrixToPauliString import *
from src.BinaryEncodings import *

import sympy as sp
import numpy as np
```

```python
# deleted the jupyter block that did this.
```

```python
h_harmonic = sp.expand(h_b.subs(qp_to_ada))
h_bmatrix = convert(h_harmonic)
bmatrix = np.array(h_bmatrix.subs({m:1, g:1, mu:1}).tolist())[:-1,:-1]
pauli_strings = matrix_to_pauli_strings(bmatrix, standard_encode)
```

```python
h_fermionic = -(m+2*g*q)*(b*bdag-bdag*b)
h_fermionic = h_fermionic.subs(qp_to_ada)
fq = max_sympy_exponent(pauli_strings) + 1

b_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)+0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
bdag_ps = 0.5*sp.Symbol('X^'+str(fq),commutative=False)-0.5*complex(0,1)*sp.Symbol('Y^'+str(fq),commutative=False)
commutation_lhs = sp.Symbol('X^'+str(fq), commutative=False)*sp.Symbol('Y^'+str(fq),commutative=False)-sp.Symbol('Y^'+str(fq), commutative=False)*sp.Symbol('X^'+str(fq),commutative=False)
commutation_rhs = 2*complex(0,1)*sp.Symbol('Z^'+str(fq))

fermionic_strings = h_fermionic.subs({b*bdag-bdag*b: commutation_rhs})
```

```python
h_fmatrix = convert(sp.expand(sp.simplify(fermionic_strings)))
fmatrix = np.array(h_fmatrix.subs({m: 1, g: 1}).tolist())[:-1,:-1]

f_pauli_strings = matrix_to_pauli_strings(fmatrix, standard_encode)
f_pauli_strings = sp.expand(sp.simplify(f_pauli_strings))
```

```python
total_h = pauli_strings + f_pauli_strings
total_h = identity_qubit_padded_H(sp.expand(sp.N(total_h)))
```

```python
import pickle

pickle.dump(total_h, open("DATA/double_well_hamiltonian.p", "wb"))
```


```python
import holoviews as hv
hv.extension('bokeh')
hv.opts.defaults(hv.opts.Curve(width=500))
```

```python
import numpy as np
import scipy.signal
import scipy.fft
```

# Fuga espectral y técnica de enventanado

## Espectro de una señal limitada en el tiempo

Diremos que una señal es limitada en el dominio del tiempo si

$$
s(t) = 0 \quad \forall |t| > T,
$$

para alguna constante $T$

Diremos que una señal es limitada en el dominio de la frecuencia o de ancho de banda limitado si

$$
S(\omega) = 0 \quad \forall |\omega| > \Omega,
$$

para alguna constante $\Omega$

### Señal limitada como resultado de enventanado

Como vimos en la lección anterior, el espectro de

$$
s(t) = \cos(2\pi f_0 t)
$$

es

$$
S(f) = \frac{1}{2} \left(\delta(f-f_0) + \delta(f+f_0) \right)
$$

Pero esto asume que $s(t)$ existen para todo $t$.

:::{warning}

En la práctica siempre trabajamos con señales de duración finita

:::

Una versión "finita" y de duración $T>0$ de la señal anterior es equivalente a

$$
s_T(t) = \cos(2 \pi f_0 t) \cdot \text{rect}(t/T),
$$

donde

$$
\text{rect}(x) = \begin{cases} 1 & |x| \leq 1 \\ 0 & |x| > 0 \end{cases}
$$

se llama **ventana rectangular** o pulso cuadrado

```python
T = 5 # Duración de la ventana
Fs = 20 # Frecuencia de muestre
f0 = 1.2345 # Frecuencia fundamental
t = np.arange(-6, 6, step=1/Fs)
s = np.cos(2*np.pi*f0*t)
w = np.zeros_like(t)
w[np.absolute(t) < T/2] = 1
sT = s*w
```

```python
p1 = hv.Curve((t, s), 'Tiempo [s]', 'Señal', label='Original').opts(line_width=4)
p2 = hv.Curve((t, sT), 'Tiempo [s]', 'Señal', label='Enventanada')
p3 = hv.Curve((t, w), 'Tiempo [s]', 'Señal', label='Ventana').opts(line_dash='dashed', color='k')
(p1 * p2 * p3)
```

### Transformada de Fourier de la ventana rectangular

En la lección 2 vimos que la transformada de Fourier del pulso cuadrado es

$$
S(f) = \frac{T}{\pi f T} \sin(\pi f T) = T \text{sinc}(f T)
$$

donde la función sinc se define como

$$
\text{sinc}(x) = \frac{1}{\pi x} \sin(\pi x)
$$

Puedes revisar la demostración de esta transformada [aquí](http://www.thefouriertransform.com/pairs/box.php)

El siguiente gráfico muestra como cambia $S(f)$ para distintos valores de $T$

```python
f = np.arange(-2, 2, step=1e-2)
S = {}
Ts = [2, 5, 10]
for T in Ts:
    S[T] = T*np.sinc(f*T)
```

```python
p1 = hv.Curve((f, S[2]), 'Frecuencia [Hz]', 'Espectro', label='T=2')
p2 = hv.Curve((f, S[5]), label='T=5')
p3 = hv.Curve((f, S[10]), label='T=10')
(p1 * p2 * p3)
```

:::{note}

Mientras mayor sea la duración temporal ($T$) de la ventana, más angosto y concentrado será su espectro

:::

### Transformada de Fourier de la señal enventanada

Usando la propiedad de modulación de la transformada de Fourier tenemos que

$$
\begin{align}
S_T(f) &= \mathbb{FT}[s_T(t)] \nonumber \\
&= \mathbb{FT}[\text{rect}_T(t) \cdot s(t) ] \nonumber \\
&=  \mathbb{FT}[\text{rect}_T(t)] * \mathbb{FT}[s_T(t)]    \nonumber \\
&=  T \text{sinc}(f T) *  \frac{1}{2} \left(\delta(f-f_0) + \delta(f+f_0) \right) \nonumber
\end{align}
$$

:::{note}

Multiplicar por una ventana rectangular en el tiempo es equivalente a convolucionar con una función $\text{sinc}$ en frecuencia

:::

En la lección anterior estudiamos lo que significa convolucionar con un impulso. En este caso el resultado de la convolución es

$$
S_T(f) = \frac{T}{2} \left [ \text{sinc}((f - f_0)T) +  \text{sinc}((f + f_0)T) \right]
$$

Es decir un $\text{sinc}$ ubicado en la frecuencia fundamental, como muestra el siguiente diagrama

Como vimos anteriormente el espectro del $\text{sinc}$ cambia con $T$. En general mientras más pequeño sea $T$

- más "corta" será  la señál enventanada $s_T(t)$
- más anchos serán los "lóbulos" de $S_T(f)$

El siguiente esquema muestra los lóbulos que tipicamente se observan en el espectro

- El lóbulo principal (*mainlobe* en la figura) es el que está relacionado a información real de la señal
- Los lóbulos laterales (*sidelobe* en la figura) son un efecto de la convolución con el **sinc**

:::{note}

La proporción entre el tamaño del lóbulo principal y los lóbulos laterales aumenta con $T$

:::

La siguiente figura muestra la señal coseno truncada con una ventana rectangular (columna izquierda) y su correspondiente espectro de amplitud (columna derecha). Observe como, a medida que $T$ disminuye, el lóbulo principal pierde definición

```python
Fs = 20 # Frecuencia de muestreo
f0 = 1.2345 # Frecuencia fundamental
t = np.arange(-10, 10, step=1/Fs)
s = np.cos(2*np.pi*f0*t)
f = scipy.fft.rfftfreq(n=len(s), d=1/Fs)

def rectangulo(t, T):
    w = np.zeros_like(t)
    w[np.absolute(t) < T/2] = 1.
    return w

p = []
for T in [15, 10, 5, 2]: # Duración de la ventana
    sT = s*rectangulo(t, T)
    ST = np.absolute(scipy.fft.rfft(sT))
    p.append(hv.Curve((t, sT), 'Tiempo [s]', 'Señal'))
    p.append(hv.Curve((f, ST), 'Frecuencia [Hz]', 'Espectro'))

hv.Layout(p).cols(2).opts(hv.opts.Curve(width=300, height=200))
```

## Fuga espectral

La aparición de los lóbulos laterales por truncamiento de la señal es un efecto que se conoce como **fuga espectral** (en inglés *spectral leak*)

La fuga espectral es una redistribución de la energía de un cierto componente espectral hacia sus frecuencias vecinas debido a las **discontinuidades o quiebres** en la periodicidad de la señal

Observe a continuación la diferencia entre el espectro de una señal donde su periodicidad calza perfecto con la ventana (fila superior) versus cuando esto no ocurre, que sería el caso más general (fila inferior)

```python
Fs, T = 20, 2
t = np.arange(-T, T, step=1/Fs)
f = scipy.fft.rfftfreq(n=len(t), d=1/Fs)
f_os = scipy.fft.rfftfreq(n=len(t)*10, d=1/Fs)

p = []
for f0 in [2.0, 2.15]: # Frecuencia fundamental
    sT = np.cos(2*np.pi*f0*t)
    ST = np.absolute(scipy.fft.rfft(sT))
    ST_os = np.absolute(scipy.fft.rfft(sT, n=len(sT)*10))

    p.append(hv.Curve((t, sT), 'Tiempo [s]', 'Señal'))
    p.append(hv.Curve((f, ST), 'Frecuencia [Hz]', 'Espectro') * hv.Curve((f_os, ST_os)).opts(alpha=0.25, color='k'))

hv.Layout(p).cols(2).opts(hv.opts.Curve(width=300, height=200))
```

- El inicio y término de la primera señal están conectados y su espectro tiene un lóbulo principal delgado (bien definido)
- El inicio y término de la segunda señal no es conectan y su espectro tiene un lóbulo principal grueso debido a la fuga de energía en sus vecinos

La fuga espectral es indeseada ya que no aporta información real. Podemos disminuir este efecto si hacemos que los bordes de la señal "calcen" antes de calcular su espectro. Este proceso de suavizado de bordes se llama **enventanado**

## Enventanado

Es el proceso de multiplicar la señal por una ventana para alterar las características de su espectro. Lamentablemente no es posible eliminar la fuga espectral completamente con enventanado pero si podemos modificar como se redistribuye la energía

Existen muchas ventanas y cada una representa un compromiso (*trade-off*) distinto entre la concentración o ancho del lóbulo principal y la atenuación de los lóbulos laterales

En general se tiene que

- Mientras más abrupto es el corte de la ventana, más concentrado será el lóbulo principal y más fuertes serán los lóbulos laterales
- Mientras más suave sea la ventana, menos concentrado será el lóbulo principal y más débiles serán los lóbulos laterales

Es decir que siempre estamos sacrificando resolución por limpieza

Revisemos a continuación algunas ventanas (columna de la izquierda), su efecto al multiplicar con la señal (columna central) y en el espectro resultante (columna derecha)

```python
from scipy.signal import cosine, blackman, tukey

f0, Fs, T = 2.15, 20, 2
t = np.arange(-T, T, step=1/Fs)
sT = np.cos(2*np.pi*f0*t)
f_os = scipy.fft.rfftfreq(n=len(t)*10, d=1/Fs)
rect = lambda N: np.ones(shape=(N,))

p = []
for window_fn in [rect, tukey, cosine, blackman]:
    w = window_fn(len(sT))
    ST_os = np.absolute(scipy.fft.rfft(sT*w, n=len(sT)*10))

    p.append(hv.Curve((t, w), 'Tiempo [s]', 'Ventana'))
    p.append(hv.Curve((t, sT*w), 'Tiempo [s]', 'Señal enventanada'))
    p.append(hv.Curve((f_os, ST_os), 'Frecuencia [Hz]', 'Espectro'))

hv.Layout(p).cols(3).opts(hv.opts.Curve(width=280, height=200))
```

Las ventanas espectrales más utilizadas están implementadas en el módulo [`scipy.signal`](https://docs.scipy.org/doc/scipy/reference/signal.windows.html?highlight=window#module-scipy.signal.windows)

En el ejemplo anterior usamos la ventana

- Rectangular: Borde con discontinuidad fuerte, es la que tiene más concentración de lóbulo principal pero también más lóbulos laterales
- [Tukey](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.tukey.html#scipy.signal.windows.tukey) con parámetro $\alpha=0.5$, es una transición entre la ventana rectangular y la ventana coseno
- [Cosine](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.cosine.html#scipy.signal.windows.cosine), como su nombre lo indica tiene forma de coseno en $[-\pi/2, \pi/2]$
- [Blackman](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.blackman.html#scipy.signal.windows.blackman): Borde muy suave, es la que tiene menos concentración de lóbulo principal pero también menos lóbulos laterales

Otra ventana interesante es la ["ventana de Kaiser"](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.kaiser.html#scipy.signal.windows.kaiser) cuyo parámetro $\beta$ le permite asemejarse a otros ventanas de forma similar a la ventana de Tukey pero más flexible

- $\beta=0$: ventana rectangular
- $\beta=5$: similar a ventana coseno
- $\beta=8.8$ similar a la ventana de Blackman

Ejemplo a continuación

```python
from scipy.signal import kaiser

f0, Fs, T = 2.15, 20, 2
t = np.arange(-T, T, step=1/Fs)
sT = np.cos(2*np.pi*f0*t)
f_os = scipy.fft.rfftfreq(n=len(t)*10, d=1/Fs)

p = []
for beta in [0, 5, 8.8]:
    w = kaiser(len(sT), beta=beta)
    ST = np.absolute(scipy.fft.rfft(sT*w, n=len(t)*10))

    p.append(hv.Curve((t, w), 'Tiempo [s]', 'Ventana'))
    p.append(hv.Curve((t, sT*w), 'Tiempo [s]', 'Señal enventanada'))
    p.append(hv.Curve((f_os, ST_os), 'Frecuencia [Hz]', 'Espectro'))

hv.Layout(p).cols(3).opts(hv.opts.Curve(width=280, height=200))
```

### ¿Cómo escoger que ventana usar?

Esta es una pregunta sumamente importante pero difícil de responder ya que depende de cada problema

A continuación algunas indicaciones

- Para propósito general o como primera aproximación usar una ventana que entregue un buen compromiso, por ejemplo Cosine, Hamming o Hanning
- Si se requiere separar/discriminar frecuencias muy cercanas conviene usar una ventana que resalte más el lóbulo principal, por ejemplo rectangular o Tukey
- Si las frecuencias de interés están muy separadas entonces conviene usar una ventana que suprima más fuertemente los lóbulos laterales como la de Blackman

```python

```


The module does not cope well with discontinuities.

```
from sympy.plotting import plot
```

```
plot(1/x)
```

```
plot(1/x,(x, 0, 3))
```

```
plot(Heaviside(x))
```

```
plot(sqrt(x))
```

```
plot(-sqrt(sqrt(x)),sqrt(sqrt(x)))
```

```
plot(LambertW(x))
```

```
plot(LambertW(x), sqrt(LambertW(x)), (x, -2, 1))
```