# Examproject

```python
#import libraries
import numpy as np
import pandas as pd
from scipy import optimize
from matplotlib import pyplot as plt
import sympy as sm
import time
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

#import local modules
from examproject.humancapital import HumanCapitalAccumulation
from examproject.as_ad import ASAD
from examproject.exchange_economy import ExchangeEconomy
```

## Human capital accumulation

```python
rho = 2
beta = 0.96
gamma = 0.1
w = 2
b = 1
Delta = 0.1

h_vec = np.linspace(0.1,1.5,100)
```

```python

```

```python
question1 = HumanCapitalAccumulation(rho,beta,gamma, w, b, Delta, h_vec)
```

### Question 1

```python
data1 = question1.solution(question1.period2)
```

```python
data1.plot(0,[2,3,4])
data1.plot(0,1)

data1[data1["unemp u"]<data1["work u"]].head(1)
```

### Question 2

```python
data2 = question1.solution(question1.period1)
```

```python
data2.head()
```

      h
      best l
      best u
      work u
      unemp u

      0
      0.100000
      0
      -1.96
      -5.613391
      -1.96

      1
      0.114141
      0
      -1.96
      -4.988852
      -1.96

      2
      0.128283
      0
      -1.96
      -4.501010
      -1.96

      3
      0.142424
      0
      -1.96
      -4.109180
      -1.96

      4
      0.156566
      0
      -1.96
      -3.787372
      -1.96

```python
data2.plot(0,[2,3,4])
data2.plot(0,1)

data2[data2["unemp u"]<data2["work u"]].head(1)
```

### Question 3

```python
data3 = question1.solution(question1.utility)
```

```python
slice1 = data2.loc[data2["best l"]==1]
slice2 = data3.loc[data3["best l"]==1]

diff = slice1[~slice1["h"].isin(slice2["h"])]
```

```python
print(diff)
```

               h  best l    best u    work u  unemp u
    18  0.354545       1 -1.948004 -1.948004    -1.96
    19  0.368687       1 -1.890505 -1.890505    -1.96
    20  0.382828       1 -1.837070 -1.837070    -1.96
    21  0.396970       1 -1.787270 -1.787270    -1.96
    22  0.411111       1 -1.740733 -1.740733    -1.96
    23  0.425253       1 -1.697140 -1.697140    -1.96
    24  0.439394       1 -1.656210 -1.656210    -1.96
    25  0.453535       1 -1.617697 -1.617697    -1.96
    26  0.467677       1 -1.581387 -1.581387    -1.96
    27  0.481818       1 -1.547089 -1.547089    -1.96
    28  0.495960       1 -1.514633 -1.514633    -1.96
    29  0.510101       1 -1.483870 -1.483870    -1.96
    30  0.524242       1 -1.454666 -1.454666    -1.96
    31  0.538384       1 -1.426899 -1.426899    -1.96
    32  0.552525       1 -1.400463 -1.400463    -1.96

```python
fig = plt.figure(dpi=100)
ax = fig.add_subplot(1,1,1)
ax.plot(data3["h"],data3["best u"], label = "empl", color ="b")
ax.plot(data3["h"],data3["unemp u"], label = "unempl", color ="g")
ax.set_title("without period 2")
ax.set_xlabel("h_t")
ax.set_ylabel("u_t")
ax.legend()

fig1 = plt.figure(dpi=100)
ax2 = fig1.add_subplot(1,1,1)
ax2.plot(data2["h"],data2["best u"], label = "empl", color="b")
ax2.plot(diff["h"],diff["best u"], label = "b>wh in p1", color="orange")
ax2.plot(data2["h"],data2["unemp u"], label = "unempl", color="g")
ax2.set_title("with period 2")
ax2.set_xlabel("h_t")
ax2.set_ylabel("u_t")
ax2.legend()

```

# AS-AD Model

```python
par = {}
par["alpha"] = 5.76
par["h"] = 0.5
par["b"] = 0.5
par["phi"] = 0
par["gamma"]=0.075

sm.init_printing(use_unicode=True)
pi_t = sm.symbols('pi_t')
pi_t1 = sm.symbols('pi_t-1')
alpha = sm.symbols("alpha")
h = sm.symbols("h")
b = sm.symbols("b")
phi = sm.symbols("phi")
gamma = sm.symbols("gamma")
v_t = sm.symbols('v_t')
y_t = sm.symbols('y_t')
y_t1 = sm.symbols('y_t-1')
s_t = sm.symbols('s_t')
s_t1 = sm.symbols('s_t-1')
```

```python
adsras = sm.Eq(1/(h*alpha)*(v_t-(1+b*alpha)*y_t), pi_t1 + gamma*y_t - phi*gamma*y_t1 + s_t - phi*s_t1)
adsras
```

```python
y_sol = sm.solve(adsras, y_t)
y_sol
```

```python
admod = 1/(h*alpha)*(v_t-(1+b*alpha)*y_sol[0])
admod
```

```python
y_sol_num = sm.Eq(y_sol[0].subs(alpha, par["alpha"]).subs(h,par["h"]).subs(b,par["b"]).subs(phi,par["phi"]).subs(gamma,par["gamma"]),y_t)
y_sol_num
```

```python
admod_num = admod.subs(alpha, par["alpha"]).subs(h,par["h"]).subs(b,par["b"]).subs(phi,par["phi"]).subs(gamma,par["gamma"])
admod_num
```

```python

```

```python
_y_sol = sm.lambdify((y_t1, pi_t1, s_t, s_t1 ,v_t, alpha, gamma, h, phi, b), y_sol[0])
_admod = sm.lambdify((y_t1, pi_t1, s_t, s_t1 ,v_t, alpha, gamma, h, phi, b), admod)

def y_sol(y_t1, pi_t1, s_t, s_t1 ,v_t):
    return _y_sol(y_t1, pi_t1, s_t, s_t1 ,v_t, alpha = par["alpha"], h = par["h"], b = par["b"], phi = par["phi"], gamma = par["gamma"])

def pi_sol(y_t1, pi_t1, s_t, s_t1 ,v_t):
    return _admod(y_t1, pi_t1, s_t, s_t1 ,v_t, alpha = par["alpha"], h = par["h"], b = par["b"], phi = par["phi"], gamma = par["gamma"])
```

```python
y_graph = y_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t=0)

pi_graph = pi_sol(y_t1 = 0, pi_t1 = 0, s_t = 0, s_t1 = 0, v_t = 0)
```

```python
print(f"y in equilibrium:",y_graph)
print(f"pi in equilibrium:",pi_graph)
```

    y in equilibrium: 0.0
    pi in equilibrium: 0.0

```python

```

```python

```

```python
gamma = par["gamma"]
phi = par["phi"]
h = par["h"]
b = par["b"]
alpha = par["alpha"]
pi_t1 = 0
y_t1 = 0
s_t = 0
s_t1 = 0
v_t = 0

y_vec = np.linspace(-0.4, 0.4, 100)

asad1 = ASAD(gamma, phi, h, b, alpha)
sras1 = asad1.sras(pi_t1, y_vec, y_t1, s_t, s_t1)
ad1 = asad1.ad(v_t, y_vec)
ad2 = asad1.ad(0.1, y_vec)

fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(1,1,1)
ax.plot(y_vec,sras1)
ax.plot(y_vec,ad1)
ax.plot(y_vec,ad2)
ax.set_xlabel("outputgap")
ax.set_ylabel("inflationgap")
```

```python
delta = 0.80
omega = 0.15
ardata = asad1.ar_simulation(100, omega, delta, phi = asad1.phi)
ardata.head()

```

      iteration
      pi
      y
      s
      v
      c
      x

      0
      0.0
      0.000000
      0.000000
      0.0
      0.0000
      0.0
      0.0

      1
      1.0
      0.001831
      0.024414
      0.0
      0.1000
      0.0
      0.1

      2
      2.0
      0.003199
      0.018244
      0.0
      0.0800
      0.0
      0.0

      3
      3.0
      0.004202
      0.013375
      0.0
      0.0640
      0.0
      0.0

      4
      4.0
      0.004918
      0.009545
      0.0
      0.0512
      0.0
      0.0

```python

```

```python
fig = plt.figure(figsize = (10,6))
ax = fig.add_subplot(1,1,1)

ax.plot(ardata["iteration"], ardata["pi"])
ax.plot(ardata["iteration"], ardata["y"])
ax.set_xlabel("iteration")
ax.set_ylabel("pi and y")
ax.legend()
ax.grid(True)
```

```python
stochdata = asad1.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404, phi=asad1.phi)
stochdata.head(20)
```

      iteration
      pi
      y
      s
      v
      c
      x

      0
      0.0
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      1
      1.0
      0.001831
      0.024414
      0.000000
      0.100000
      0.000000
      0.100000

      2
      2.0
      0.012862
      0.985219
      -0.062861
      3.859693
      -0.062861
      3.779693

      3
      3.0
      0.031249
      -1.113500
      0.101899
      -4.230382
      0.111329
      -7.318137

      4
      4.0
      -0.228226
      -3.105331
      -0.026575
      -12.705975
      -0.041860
      -9.321669

      5
      5.0
      -0.494979
      -3.472084
      -0.006346
      -14.897225
      -0.002360
      -4.732445

      6
      6.0
      -0.464118
      -2.868983
      0.246035
      -12.468315
      0.246987
      -0.550534

      7
      7.0
      -0.649760
      -2.851512
      0.028221
      -12.935175
      -0.008684
      -2.960523

      8
      8.0
      -0.974973
      -2.574521
      -0.132124
      -12.797064
      -0.136357
      -2.448924

      9
      9.0
      -0.890738
      -1.814466
      0.220320
      -9.605454
      0.240138
      0.632197

      10
      10.0
      -0.737427
      -1.980342
      0.301837
      -9.807517
      0.268789
      -2.123154

      11
      11.0
      -0.944240
      -1.136883
      -0.121546
      -7.130516
      -0.166822
      0.715497

      12
      12.0
      -0.887666
      -1.765252
      0.188968
      -9.405656
      0.207200
      -3.701243

      13
      13.0
      -0.722313
      -2.615364
      0.361505
      -12.227876
      0.333160
      -4.703351

      14
      14.0
      -0.569150
      -1.212778
      0.244121
      -6.344731
      0.189896
      3.437569

      15
      15.0
      -0.377112
      -0.773096
      0.250020
      -4.085694
      0.213402
      0.990091

      16
      16.0
      -0.347564
      -1.158015
      0.116399
      -5.494082
      0.078896
      -2.225527

      17
      17.0
      -0.193708
      0.217668
      0.137531
      0.286674
      0.120071
      4.681939

      18
      18.0
      -0.478793
      -0.467384
      -0.250031
      -3.192375
      -0.270661
      -3.421714

      19
      19.0
      -0.712099
      -0.038694
      -0.230404
      -2.200978
      -0.192899
      0.352922

```python
fig1 = plt.figure(figsize = (10,6))
ax1 = fig1.add_subplot(1,1,1)

ax1.plot(stochdata["iteration"], stochdata["pi"])
ax1.plot(stochdata["iteration"], stochdata["y"])
ax1.set_xlabel("iteration")
ax1.set_ylabel("pi and y")
ax1.legend()
ax1.grid(True)

stochdata.plot(0,[5,6])
```

```python
yarray = np.array(stochdata["y"])
piarray = np.array(stochdata["pi"])

print(f"y variance is {np.var(yarray):.4f}")
print(f"pi variance is {np.var(piarray):.4f}")
print(f"y-pi corr is {(np.corrcoef(yarray, piarray)[1,0]):.4f}")
print(f"y autocorr is {(np.corrcoef(yarray[1:], yarray[:-1])[1,0]):.4f}")
print(f"pi autocorr is {(np.corrcoef(piarray[1:], piarray[:-1])[1,0]):.4f}")
```

    y variance is 2.0500
    pi variance is 1.2927
    y-pi corr is -0.1353
    y autocorr is 0.7682
    pi autocorr is 0.9813

```python

```

```python
def corr_phi_func():
    phi_vec = np.linspace(0.1,1,100)
    corrarray = np.empty(100)
    phiarray = np.empty(100)

    for i, phi in enumerate(phi_vec):
        asad2 = ASAD(gamma, phi, h, b, alpha)
        stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404)
        corrarray[i] = np.corrcoef(stoch2data["y"], stoch2data["pi"])[1,0]
        phiarray[i] = phi

    return corrarray, phiarray

plt.plot(phiarray, corrarray)
plt.xlabel("phi")
plt.ylabel("y-pi corr")
```

```python
def corr_func(phi):
    asad2 = ASAD(gamma, phi, h, b, alpha)
    stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = 3.492, sigmac = 0.2, seed = 404, phi = asad2.phi)
    return (np.corrcoef(stoch2data["y"], stoch2data["pi"])[1,0]-0.31)**2

phi_root = optimize.minimize(corr_func,x0=0)
phi_root
```

          fun: 8.552675723015356e-16
     hess_inv: array([[0.03023896]])
          jac: array([8.65147877e-09])
      message: 'Optimization terminated successfully.'
         nfev: 48
          nit: 6
         njev: 16
       status: 0
      success: True
            x: array([0.96166711])

```python
def corr_func2(vec):
    phi, sigmax, sigmac = vec
    asad2 = ASAD(gamma, phi, h, b, alpha)
    stoch2data = asad2.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = sigmax, sigmac = sigmac, seed = 404, phi=phi)
    yarray = np.array(stoch2data["y"])
    piarray = np.array(stoch2data["pi"])

    sq_y_var = (np.var(yarray)-1.64)**2
    sq_pi_var = (np.var(piarray)-0.21)**2
    sq_ypi_corr = (np.corrcoef(yarray, piarray)[1,0]-0.31)**2
    sq_y_auto = (np.corrcoef(yarray[1:], yarray[:-1])[1,0]-0.84)**2
    sq_pi_auto = (np.corrcoef(piarray[1:], piarray[:-1])[1,0]-0.48)**2

    sqlist = [sq_y_var, sq_pi_var, sq_ypi_corr, sq_y_auto, sq_pi_auto]

    return sqlist

def corr_min(vec):
    list = corr_func2(vec)
    sqsum = sum(list)

    return sqsum

phi_root = optimize.minimize(corr_min, x0 = [0,3.492,0.2], bounds = ((0,1), (10**-6,10**6), (10**-6,10**6)))
phi_root
```

          fun: 0.02357832638636935
     hess_inv: <3x3 LbfgsInvHessProduct with dtype=float64>
          jac: array([-3.62800068e-06, -2.98372438e-08, -3.75047216e-07])
      message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
         nfev: 92
          nit: 14
       status: 0
      success: True
            x: array([0.9717686 , 3.02301921, 0.21947609])

```python
phi_sol, sigmax_sol, sigmac_sol = phi_root.x
stochdata = asad1.stoch_simulation(N = 1000, omega = omega, delta = delta, sigmax = sigmax_sol, sigmac = sigmac_sol, seed = 404, phi=phi_sol)

yarray = np.array(stochdata["y"])
piarray = np.array(stochdata["pi"])

print(f"y variance is {np.var(yarray):.4f}")
print(f"pi variance is {np.var(piarray):.4f}")
print(f"y-pi corr is {(np.corrcoef(yarray, piarray)[1,0]):.4f}")
print(f"y autocorr is {(np.corrcoef(yarray[1:], yarray[:-1])[1,0]):.4f}")
print(f"pi autocorr is {(np.corrcoef(piarray[1:], piarray[:-1])[1,0]):.4f}")
```

    y variance is 1.6460
    pi variance is 0.0718
    y-pi corr is 0.2732
    y autocorr is 0.7845
    pi autocorr is 0.4845

# Exchange economy

```python

```

```python
# a. parameters
N = 50000
mu = np.array([3,2,1])
Sigma = np.array([[0.25, 0, 0], [0, 0.25, 0], [0, 0, 0.25]])
gamma = 0.8
zeta = 1

# b. random draws
seed = 1986
np.random.seed(seed)

# preferences
alphas = np.exp(np.random.multivariate_normal(mu, Sigma, size=N))
betas = alphas/np.reshape(np.sum(alphas,axis=1),(N,1))

# endowments
e1 = np.random.exponential(zeta,size=N)
e2 = np.random.exponential(zeta,size=N)
e3 = np.random.exponential(zeta,size=N)

elist = [e1,e2,e3]
```

```python

```

```python
fig, axs = plt.subplots(1,3, figsize=(14,6))
fig.suptitle('Budget shares')

ylims = (0,1700)

axs[0].hist(betas[:,0], bins=150)
axs[0].set_title("beta_1")
axs[0].set_ylim(ylims)

axs[1].hist(betas[:,1], bins=150)
axs[1].set_title("beta_2")
axs[1].set_ylim(ylims)

axs[2].hist(betas[:,2], bins=150)
axs[2].set_title("beta_3")
axs[2].set_ylim(ylims)

plt.show()
```

```python
for i in range(3):
    print(f"mean for beta_{str(i+1)} is {np.mean(betas[:,i]):.4f}")
    print(f"variance for beta_{str(i+1)} is {np.var(betas[:,i]):.4f}")
```

    mean for beta_1 is 0.6423
    variance for beta_1 is 0.0182
    mean for beta_2 is 0.2581
    variance for beta_2 is 0.0142
    mean for beta_3 is 0.0996
    variance for beta_3 is 0.0033

```python

```

```python
price_vec = np.linspace(0.5,10,50)
price1, price2 = np.meshgrid(price_vec, price_vec)
def plot3d():
    array1 = np.empty((len(price_vec), len(price_vec)))
    array2 = np.empty((len(price_vec), len(price_vec)))
    array3 = np.empty((len(price_vec), len(price_vec)))

    for i, p1 in enumerate(price_vec):
        for j,p2 in enumerate(price_vec):
            a = ExchangeEconomy(betas, elist, N, p1, p2)
            z = a.excess_demand()
            array1[i,j] = z[0]
            array2[i,j] = z[1]
            array3[i,j] = z[2]
    return array1, array2, array3
%time array1,array2,array3 = plot3d()
```

    Wall time: 5.89 s

```python
fig = plt.figure(figsize=(14,8))
fig.suptitle('Excess Demand')

ax1 = fig.add_subplot(131,projection='3d')
cs1 = ax1.plot_surface(price1,price2,array1, cmap=cm.jet)
ax1.invert_xaxis()
ax1.set_xlabel("p1")
ax1.set_ylabel("p2")

ax2 = fig.add_subplot(132,projection='3d')
cs2 = ax2.plot_surface(price1,price2,array2, cmap=cm.jet)
ax2.invert_xaxis()
ax2.set_xlabel("p1")
ax2.set_ylabel("p2")

ax3 = fig.add_subplot(133,projection='3d')
cs3 = ax3.plot_surface(price1,price2,array3, cmap=cm.jet)
ax3.invert_xaxis()
ax3.set_xlabel("p1")
ax3.set_ylabel("p2")
```

```python
b = ExchangeEconomy(betas, elist, N, 2, 2)
%time z = b.walras(kappa=0.15, epsilon=10**-5)
print(z)
```

    Wall time: 16.4 s
    (6.490055134537612, 2.6166939963966915)

```python
p1, p2 = z
c = ExchangeEconomy(betas, elist, N, p1, p2, gamma)
c.excess_demand()
```

    array([ 9.99628537e-06,  3.72918294e-06, -7.46345759e-05])

```python
utilarray = c.utility()

plt.hist(utilarray,200)
plt.show()

print(f"Mean is {np.mean(utilarray):.3f}")
print(f"Variance is {np.var(utilarray):.3f}")
```

```python

```

```python
e1new = np.zeros(N)+np.mean(e1)
e2new = np.zeros(N)+np.mean(e2)
e3new = np.zeros(N)+np.mean(e3)

newelist = [e1new, e2new, e3new]

e = ExchangeEconomy(betas, newelist, N, 2, 2, gamma)
newp1, newp2 = e.walras(0.15, 10**-5)
```

```python
f = ExchangeEconomy(betas, newelist, N, newp1, newp2, gamma)
util = f.utility()

plt.hist(util, 200)
plt.show

print(f"Mean is {np.mean(util):.3f}")
print(f"Variance is {np.var(util):.3f}")
```

```python
def gammafind():
    gammalist = []
    meanlist = []
    varlist = []
    for i in range(100):
        g = ExchangeEconomy(betas, newelist, N, newp1, newp2, (i+1)/100)
        util = g.utility()

        gammalist.append((i+1)/100)
        meanlist.append(np.mean(util))
        varlist.append(np.var(util))

    data = pd.DataFrame()
    data["gamma"]=gammalist
    data["mean"]=meanlist
    data["var"]=varlist

    return data

%time data = gammafind()
```

    Wall time: 15.4 s

```python
data.plot(0,1)
data.plot(0,2)
```

```python

```


# Lecture 13: Standard Normal, Normal normalizing constant

## Stat 110, Prof. Joe Blitzstein, Harvard University

----

## Proof of the Universality of $\operatorname{Unif}(0,1)$

Let $F$ be a _continuous_, _strictly increasing_ CDF.

Then

\begin{align}
  &X = F^{-1}(U) \sim F & &\text{if } U \sim \operatorname{Unif}(0,1) & &\text{(1)}\\
  \\
  &F(X) \sim \operatorname{Unif}(0,1) & &\text{if } X \sim F & &\text{(2)}
\end{align}

$F$ is just a function, and hence we can treat it like an operator.

And like $X$, $F(X)$ is just another r.v.

### Interpreting the Universality of $\operatorname{Unif}(0,1)$

_How to interpret this universality?_

\begin{align}
  &F(x) = P(X \le x) & &\text{does not imply that } \\
  \\
  \require{cancel} &\bcancel{F(X) = P(X \le X) = 1} \\
  \\
  \\
  &F(x) = 1 - e^{-x} & &\text{where } x \gt 0 \\
  &F(X) = 1 - e^{-X}
\end{align}

_First_ evaluate $F$ at $x$, and _then_ replace $x$ for $X$.

### Simulating random variable draws using the universality of $\operatorname{Unif}(0,1)$

These 2 approaches to universality of the Uniform are quite useful, like when you want to simulate draws from another r.v.

Let $F(x) = 1 - e^{-x}$, where $x \gt 0$. This is the exponential random distribution $\operatorname{Expo}(1)$.

Say we have $U \sim \operatorname{Unif}(0,1)$.

_How can we leverage universality of $\operatorname{Unif}(0,1)$ to simulate $X \sim F$?_

\begin{align}
  u &= 1 - e^{-x} & &\text{first we obtain }F^{-1}(x) \\
  e^{-x} &= 1 - u \\
  -x &= ln(1 - u)  \\
  x &= - ln(1-u) \\
  \\
  \Rightarrow F^{-1}(u) &= -ln(1-u) \\
  \Rightarrow F^{-1}(U) &= -ln(1-U) \sim F & &\text{by universality}
\end{align}

And so we could simulate 10 i.i.d. draws from $F(x) = 1 - e^{-x}$ by calculating $-ln(1-U)$ 10 times.

### Symmetry of  $\operatorname{Unif}(0,1)$

Note that

\begin{align}
  1 - U \sim \operatorname{Unif}(0,1) & &\text{symmetry of Uniform} \\
  \\
  a + b U & &\text{linear transformations are also } \operatorname{Unif}
\end{align}

Non-linear transformations will, in general, __not__ be $\operatorname{Unif}$.

----

## Independence of Random Variables

### General case

Say we have r.v. $X_1, X_2, \dots , X_n$.

#### Definition: independence of random variables
> $X_1, X_2, \dots , X_n$ are independent if, for all $x_1, x_2, \dots, x_n$
>
> \\begin{align}
>   P(X_1 \le x_1, X_2 \le x_2, \dots , X_n \le x_n) &= P(X_1 \le x_1) P(X_2 \le x_2) \cdots P(X_n \le x_n)
> \\end{align}

Note that in this general case, $P(X_1 \le x_1, X_2 \le x_2, \dots , X_n \le x_n)$ is called the __joint CDF__.

#### Definition: independence of discrete random variables
> Discrete r.v. $X_1, X_2, \dots , X_n$ are independent if, for all $x_1, x_2, \dots, x_n$
>
> \\begin{align}
>   P(X_1 = x_1, X_2 = x_2, \dots , X_n = x_n) &= P(X_1 = x_1) P(X_2 = x_2) \cdots P(X_n = x_n)
> \\end{align}

In the discrete case, $P(X_1 = x_1, X_2 = x_2, \dots , X_n = x_n)$ is called the __joint PMF__.

### Interpretation

Simply put, in both the general and discrete cases, knowing any subset of the r.v. gives us __no__ information about the rest.

This is _stronger_ than pair-wise independence.

#### Ex. A penny-matching game

Consider a penny-matching game where 2 players flip a penny. If the faces showing match (HH or TT), then one of the players wins; else the other player wins.

Let

\begin{align}
  X_1, X_2 &\sim \operatorname{Bern}\left(\frac{1}{2}\right) ~~ \text{, i.i.d} \\
  \\
  X_3 &=
    \begin{cases}
    1  & \quad \text{if } X_1 = X_2 \\
    0  & \quad \text{otherwise}\\
    \end{cases}
\end{align}

$X_1, X_2, X_3$ are _pair-wise independent_, but not independent.

- Knowing $X_1, X_2$ gives us complete knowledge of $X_3$.
- However, just knowing $X_1$ tells us nothing about $X_2$, nor can it say anything about $X_3$.
____

## Normal Distribution (Gaussian)

### Description

The most important distribution in all statistics, mostly due to the Central Limit Theorem.

The Central Limit Theorem is surprising; if you sum up a large number of i.i.d. random variables will always look like a bell-shaped curve. This is irrespective of continuous or discrete; beautiful or ugly.

### Notation

$X \sim \mathcal{N}(\mu, \operatorname{Var})$

### Parameters

- $\mu$ mean
- $\operatorname{Var}$ variance

```python
import matplotlib
import numpy as np
import matplotlib.pyplot as plt

from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,
                               AutoMinorLocator)
from scipy.stats import norm

%matplotlib inline

plt.xkcd()

_, ax = plt.subplots(figsize=(12,8))

# seme Normal parameters
mu_values = [0., 0., 0., -2.]
var_values = [0.4, 1.0, 2.0, 0.5]
params = list(zip(mu_values, var_values))

# qualitative color scheme
colors = ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3']

x = np.linspace(-6, 6, 500)
for i,(loc,scale) in enumerate(params):
    pdf = norm.pdf(x, loc=loc, scale=scale)
    ax.plot(x, pdf, color=colors[i], lw=3.2, label=r'$\mu = {}, \sigma^2 = {}$'.format(loc, scale))

# legend styling
legend = ax.legend()
for label in legend.get_texts():
    label.set_fontsize('large')
for label in legend.get_lines():
    label.set_linewidth(1.5)

# y-axis
ax.set_ylim([-0.01, 1.0])
ax.set_ylabel(r'$f(X)$')
ax.set_yticks(np.arange(0,1.1,.1))

# x-axis
ax.set_xlim([-5.0, 5.0])
ax.set_xlabel(r'$X$')

# x-axis tick formatting
majorLocator = MultipleLocator(1)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1)
ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(majorFormatter)
ax.xaxis.set_minor_locator(minorLocator)

ax.grid(color='grey', linestyle='-', linewidth=0.3)

plt.suptitle(r'Normal PDF: $f(x) =  \frac{1}{\sqrt{2\pi}} e^{-\frac{z^{2}}{2}}$')

plt.show()
```

### Probability density function

For the Standard Normal $\mathcal{N}(0,1)$

\begin{align}
  f(z) &= c e^{-\frac{z^{2}}{2}}
\end{align}

where $c$ is the normalizing constant that will let $f(x)$ integrate to 1.

_But what is $c$?_

If we can figure out what the integral of the PDF is, then the _inverse_ of that will be the constant $c$ that ensures $f(z) = c e^{-\frac{z^{2}}{2}} = 1$.

Consider the indefinite integral of the PDF of $\mathcal{N}$.

\begin{align}
  \int_{-\infty}^{\infty} e^{-\frac{z^2}{2}} ~~ dz
\end{align}

There is a theorem that states that this integral, as an indefinite integral, cannot be solved in closed form. But this can be solved...

First, we start by _multiplying the integral by itself_...

\begin{align}
  \int_{-\infty}^{\infty} e^{-\frac{z^2}{2}} ~~ dz \int_{-\infty}^{\infty} e^{-\frac{z^2}{2}} ~~ dz &= \int_{-\infty}^{\infty} e^{-\frac{x^2}{2}} ~~ dx \int_{-\infty}^{\infty} e^{-\frac{y^2}{2}} ~~ dy \\
  &= \iint_{-\infty}^{\infty} e^{-\frac{(x+y)^2}{2}} ~~ dxdy \\
  &= \int_{0}^{2\pi} \int_{0}^{\infty} e^{-\frac{r^2}{2}}~ \underbrace{r}_{\text{jacobian}} ~ drd\theta & \quad \text{switch to polar} \\
  &= \int_{0}^{2\pi} \left( \int_{0}^{\infty} e^{-u} du  \right) ~ d\theta & \quad \text{let } u = \frac{r^2}{2} \text{ , } du = r \\
  &= \int_{0}^{2\pi} \left( -e^{-\infty} + e^{-0}  \right) ~ d\theta \\
  &= \int_{0}^{2\pi} 1 ~ d\theta \\
  &= 2 \pi - 0 \\
  &= 2\pi \\
  \\
  \int_{-\infty}^{\infty} e^{-\frac{z^2}{2}} ~~ dz &= \sqrt{2\pi} \\
  \\
  \therefore c &= \boxed{\frac{1}{\sqrt{2\pi}} }
\end{align}

### Expected value

For the standard normal distribution, $\mathbb{E}(Z) = 0$. Let's prove that...

\begin{align}
  \mathbb{E}(Z) &= \int_{-\infty}^{\infty} z ~ \frac{e^{-\frac{z^2}{2}}}{\sqrt{2\pi}} ~ dz \\
  &= \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{\infty} z ~ e^{-\frac{z^2}{2}} ~ dz \\
  &= \frac{1}{\sqrt{2\pi}} (0) & \quad \text{since this is an odd function} \\
  &= 0 & \quad \blacksquare
\end{align}

```python
x = np.linspace(-5, 5, 500)
y = x * (np.e ** ((-x**2)/2))

_, ax = plt.subplots(figsize=(12,8))

ax.plot(x, y, color='black')
ax.fill_between(x, y, where=y >= 0, facecolor='blue', interpolate=True)
ax.fill_between(x, y, where=y < 0, facecolor='red', interpolate=True)
ax.axhline(y=0, color='black')
ax.axvline(x=0, color='black')

# y-axis
ax.set_ylim([-0.7, 0.7])
ax.set_ylabel(r'$g(x)$')
ax.set_yticks(np.arange(-.7,.8,.1))

# x-axis tick formatting
majorLocator = MultipleLocator(1)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1)
ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(majorFormatter)
ax.xaxis.set_minor_locator(minorLocator)

plt.title("$g(x)$ is an odd function")
plt.show()
```

Recall that if $g(x)$ is an _odd function_, then $g(x) + g(-x) = 0$.

Integrating an odd function $g(x)$ from $-a$ to $a$ is 0, since the portion _above the x-axis_ cancels out the portion _below the x-axis_.

### Variance

For the standard normal distribution, $\operatorname{Var}(Z) = 1$. Let's prove this as well...

\begin{align}
  \operatorname{Var}(Z) &= \mathbb{E}Z^2 - \mathbb{E}(Z)^2 \\
  &= \int_{-\infty}^{\infty} z^2 ~ \frac{e^{-\frac{z^2}{2}}}{\sqrt{2\pi}} ~ dz - (0)^2 & \quad \text{by LOTUS; and by calculations above} \\=
  &= 2 \int_{0}^{\infty} z^2 ~ \frac{e^{-\frac{z^2}{2}}}{\sqrt{2\pi}} ~ dz & \quad \text{this is an even function} \\
  &= \frac{2}{\sqrt{2\pi}} \int_{0}^{\infty} z^2 ~ e^{-\frac{z^2}{2}} ~ dz & \quad \text{set us up for integration by parts} \\
  &= \frac{2}{\sqrt{2\pi}} \int_{0}^{\infty} z ~ z ~ e^{-\frac{z^2}{2}} ~ dz  \\
  &= \frac{2}{\sqrt{2\pi}} \int_{0}^{\infty} u ~ du ~ dv & \quad \text{let } u = z \text{, } \quad dv = z ~ e^{-\frac{z^2}{2}} \\
  & & \quad du = dz \text{, } \quad v = -e^{-\frac{z^2}{2}} \\
  &= \frac{2}{\sqrt{2\pi}} \left( \left.(uv)\right\vert_{0}^{\infty} - \int_{0}^{\infty} v du   \right) \\
  &= \frac{2}{\sqrt{2\pi}} \left( 0 + \int_{0}^{\infty} e^{-\frac{z^2}{2}} ~ dz  \right) \\
  &= \frac{2}{\sqrt{2\pi}} \left( \frac{\sqrt{2\pi}}{2} \right) & \quad \text{from calculation of normalization constant }c \\
  &= 1 & \quad \blacksquare
\end{align}

```python
x = np.linspace(-5, 5, 500)
y = (x**2) * (np.e ** ((-x**2)/2))

_, ax = plt.subplots(figsize=(12,8))

ax.plot(x, y, color='black')
ax.fill_between(x, y, where=y >= 0, facecolor='blue', interpolate=True)
ax.axhline(y=0, color='black')
ax.axvline(x=0, color='black')

# y-axis
ax.set_ylim([-0.01, .8])
ax.set_ylabel(r'$g(x)$')
ax.set_yticks(np.arange(0,.9,.1))

# x-axis tick formatting
majorLocator = MultipleLocator(1)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(1)
ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(majorFormatter)
ax.xaxis.set_minor_locator(minorLocator)

plt.title("$g(x)$ is an even function")
plt.show()
```

## Standard Normal Distribution

This standard normal distribution $\mathcal{N}(0,1)$ is so important that it has its own name and notation.

$\mathcal{Z} \sim \mathcal{N}(0,1)$, where mean $\mu = 0$ and variance $\sigma^2 = 1$

### Notation

The standard normal distribution's CDF is notated by $\mathbb{\Phi}(Z)$.

### Cumulative distribution function

The cumulative distribution function of $\Phi(Z)$ is

\begin{align}
  \mathbb{\Phi}(Z) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{z} e^{-\frac{t^2}{2}} ~~ dt
\end{align}

### Property of $\Phi(-Z)$

\begin{align}
  \mathbb{\Phi}(-Z) = 1 - \mathbb{\Phi}(Z)
\end{align}

----

View [Lecture 13: Normal distribution | Statistics 110](http://bit.ly/2PmgrWh) on YouTube.


```python
import grouptesting
from grouptesting.model import *
from grouptesting.algorithms import *
import autograd.numpy as np
from autograd import grad
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import math
from scipy.stats import bernoulli
from scipy.optimize import minimize, rosen, rosen_der
from sympy import *

def nCr(n,k):
    return math.factorial(n) // math.factorial(k) // math.factorial(n - k)

np.random.seed(4000)

```

## Plot ideas:

1. NCOMP vs. NDD for some q, or for three different qs (just to show empirical performance of NDD vs NCOMP) x
2. NCOMP with an achievability bound for some q x
4. NDD with achievability found for some q x

### 2. NCOMP with an achievability bound for some q

```python
# Define initial parameters
num_tests = 100
n = 1000 # population size
theta = 0.3
C = 2
n_theta = C * (n**theta)
k = round(n_theta) # number of infected
alpha = 0.5
p = alpha/k # Bernoulli test design probability parameter
eta = 0.001
q_results = []

############ RUN EXPERIMENT #####################
for q in [0.05]:
    NCOMP_ber_acc = []

    ############## Achievable bound 1 for T ################
    ## Choose delta according to z
    T_array = np.linspace(1, n, 25)
    z = ((1- alpha/(k) * (1-q))**(k-1) + (1/q)*(1 - alpha/(k) * (1-q))**k) / 2
    l = (1 - alpha / k * (1-q))**(k-1)
    u = (1 - alpha / k * (1-q))**(k) * (1/q)
    print("l-1, u-1:", l-1, u-1)
    delta = z - 1
    assert (l-1) < delta < (u-1)

    ########### Achievability Bound 2 Method 2 #######
    eta_1, alpha_1, theta_1, q_1 = eta, alpha, theta, q
    n_1 = n
    n_theta_1 = n_theta
    d_1 = round(n_theta_1)
    eps = 1e-12

    # Objective
    Tminus = lambda delta_1: ((1 + eta_1) * theta_1 * (1/(q_1**2)) * (d_1) * (np.log(n_1))) / (alpha_1 * (1 - np.exp(-2)) * (eps + (1+delta_1) - (1- alpha_1/(d_1) * (1-q_1))**(d_1-1))**2)
    Tplus = lambda delta_1: ((1 + eta_1) * (1/(q_1**2)) * (d_1) * (np.log(n_1))) / (alpha_1 * (1 - np.exp(-2)) * (eps + (1+delta_1) - (1/q_1) * (1- alpha_1/(d_1) * (1-q_1))**d_1)**2)
    ff = lambda delta_1: max(Tminus(delta_1), Tplus(delta_1))

    # Constraints
    l = (1 - alpha_1 / d_1 * (1-q_1))**(d_1-1)
    u = (1 - alpha_1 / d_1 * (1-q_1))**(d_1) * (1/q_1)
    bound = (l-1, u-1)

    x0 = delta
    options={'disp': None, 'maxcor': 100, 'ftol': 1e-14, 'gtol': 1e-012, 'eps': 1e-08, 'maxfun': 15000, 'maxiter': 15000, 'iprint': 1, 'maxls': 20}
    gff = grad(ff)
    res = minimize(ff, x0, tol=1e-12, bounds=[bound], jac=gff)
    achiev_bound = res.fun[0]

    ###### Main Experiment loop #####

    for T in T_array:
        NCOMP_ber_error = []
        NDD_ber_error = []
        COMP_ber_error = []
        print("T: ", int(round(T)))
        for test in range(num_tests):
            sigma = D(n, k) # Generate the vector of defectives
            X_ber = Ber(n, int(round(T)), p)
            y_ber = Y(dilution_noise(X_ber, q), sigma)

            # NCOMP - Bernoulli
            sigma_hat_ber = NCOMP(X_ber, y_ber, q=q, delta=delta)
            err = error(sigma, sigma_hat_ber)
            NCOMP_ber_error.append(error(sigma, sigma_hat_ber))

        acc = (num_tests - np.sum(np.array(NCOMP_ber_error))) / num_tests
        NCOMP_ber_acc.append(acc)

    ################ Plot ####################
    PAL = ['#fef0d9','#fdcc8a','#fc8d59','#d7301f']
    plt.plot(T_array, NCOMP_ber_acc,'o-', c=PAL[-2], label="NCOMP - q = " + str(round(q, 2)))

#     use LaTeX fonts in the plot
    plt.rc('text', usetex=True)
    plt.rc('font', family='serif')
    matplotlib.rcParams['text.usetex'] = True

    plt.axvline(x=float(achiev_bound), ymin=0, ymax=1, color='black', linestyle='dotted', label="Achievability Bound", linewidth=2)
    plt.xticks(ticks=[10, round((achiev_bound)), round(n)])
    plt.yticks(ticks=[0.0, 1.0])
    plt.xlabel(r'Number of tests (T)', fontsize=11)
    plt.ylabel(r'Success Probability', fontsize=11)
    plt.legend()
    plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.png')
    plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.eps')
    plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.pdf')
    plt.show()
```

```python
## Redo the plot because the latex font only comes in the second time you save it for some reason.
PAL = ['#fef0d9','#fdcc8a','#fc8d59','#d7301f']
plt.plot(T_array, NCOMP_ber_acc,'o-', c=PAL[-2], label="NCOMP - q = " + str(round(q, 2)))

#     use LaTeX fonts in the plot
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
matplotlib.rcParams['text.usetex'] = True

plt.axvline(x=float(achiev_bound), ymin=0, ymax=1, color='black', linestyle='dotted', label="Achievability Bound", linewidth=2)
plt.xticks(ticks=[10, round(float(achiev_bound)), round(n)])
plt.yticks(ticks=[0.0, 1.0])
plt.xlabel(r'Number of tests (T)', fontsize=11)
plt.ylabel(r'Success Probability', fontsize=11)
plt.legend()
plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.png')
plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.eps')
plt.savefig('plots/NCOMP Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.pdf')
plt.show()
```

### 3. NDD with Achievability bound for some q

```python
# Define initial para200
n = 1000 # population size
theta = 0.3
C = 2
n_theta = C * (n**theta)
k = round(n_theta) # number of infected
eta = 0.001
q_results = []

np.random.seed(23455324)

############ RUN EXPERIMENT #####################
for q in [0.05]:
    alpha = 0.5
    p = alpha/k # Bernoulli test design probability parameter
    print("q: ", q)
    NDD_ber_acc = []
    NCOMP_ber_acc = []

    T_array = np.linspace(1, n, 25)
    pi_init = (q+0.07)

    ########### Achievability Bound 2 Method 2 #######
    eta_1, alpha_1, theta_1, q_1 = eta, alpha, theta, q
    n_1 = n
    n_theta_1 = n_theta
    d_1 = round(n_theta_1)
    eps = 1e-12

    # Objective
    D_ = lambda _eps: (_eps)*np.log(_eps) - _eps + 1
    T1 = lambda pi: (d_1 * np.log(d_1) * np.exp(alpha_1 * (1-q_1)) / ((alpha_1 * q_1)*D_(pi / (q_1 * np.exp(-alpha_1 * (1-q_1))))))
    T2 = lambda pi: ((1 - (theta_1 - eps)) * d_1 * np.log(n) * np.exp(alpha_1*(1-q_1)) / (alpha_1 * D_(pi * np.exp(alpha_1 * (1-q_1)))))
    T3 = lambda pi: (np.exp(alpha_1) * d_1 * np.log(d_1) / (alpha_1 * (1-q_1)))
    ff = lambda pi: max(T1(pi), T2(pi), T3(pi))

    bound = (q, np.exp(-alpha * (1-q))-0.01)

    x0 = pi_init
    gff = grad(ff)
    res = minimize(ff, x0, tol=1e-12, bounds=[bound], jac=gff)
    achiev_bound = res.fun[0]
    pi_NDD = res.x[0]
    assert q <= pi_NDD <= 1

    ###### Main Experiment loop #####
    for T in T_array:
        NDD_ber_error = []
        DD_ber_error = []
        NCOMP_ber_error = []
        print("T: ", int(round(T)))
        for test in range(num_tests):
            sigma = D(n, k) # Generate the vector of defectives
            X_ber = Ber(n, int(round(T)), p)
            y_ber = Y(dilution_noise(X_ber, q), sigma)

            # NDD - Bernoulli
            sigma_hat_ber = NDD(X_ber, y_ber, pi=(pi_NDD), alpha=alpha, T = T, d = k)
            err = error(sigma, sigma_hat_ber)
            NDD_ber_error.append(error(sigma, sigma_hat_ber))

        acc = (num_tests - np.sum(np.array(NDD_ber_error))) / num_tests
        NDD_ber_acc.append(acc)

    ################ Plot ####################
    PAL = ['#f1eef6','#bdc9e1','#74a9cf','#0570b0']
    plt.plot(T_array, NDD_ber_acc,'o-', c=PAL[-1], label="NDD - q = " + str(round(q, 2)))

    # use LaTeX fonts in the plot
    plt.rc('text', usetex=True)
    plt.rc('font', family='serif')
    matplotlib.rcParams['text.usetex'] = True

    plt.axvline(x=ff(pi_NDD), ymin=0, ymax=1, color='black', linestyle='dotted', label="Achievability bound", linewidth=3)
    plt.xticks(ticks=[round(10.0), round(ff(pi_NDD)), round(n)])
    plt.yticks(ticks=[0.0, 1.0])
    plt.xlabel(r'Number of tests (T)', fontsize=11)
    plt.ylabel(r'Success Probability', fontsize=11)
    plt.legend()
    plt.savefig('plots/NDD Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', pi = ' + str(round(pi_NDD, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.png')
    plt.savefig('plots/NDD Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', pi = ' + str(round(pi_NDD, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.eps')
    plt.savefig('plots/NDD Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', pi = ' + str(round(pi_NDD, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.pdf')
    plt.show()
```

### 1. NDD vs NCOMP for some/different qs

```python
# Define initial parameters
num_tests = 100
n = 1000 # population size
theta = 0.3
C = 2
n_theta = C * (n**theta)
k = round(n_theta) # number of infected
eta = 0.001
q_results = []

############ RUN EXPERIMENT #####################
for q in [0.000001, 0.15, 0.35, 0.50]:
    alpha = 0.5
    p = alpha/k # Bernoulli test design probability parameter
    print("q: ", q)
    NDD_ber_acc = []
    DD_ber_acc = []
    NCOMP_ber_acc = []

    pi_init = (q+0.07)

    ########### Achievability Bound 2 Method 2 #######
    eta_1, alpha_1, theta_1, q_1 = eta, alpha, theta, q
    n_1 = n
    n_theta_1 = n_theta
    d_1 = round(n_theta_1)
    eps = 1e-12

    # Objective
    D_ = lambda _eps: (_eps)*np.log(_eps) - _eps + 1
    T1 = lambda pi: (d_1 * np.log(d_1) * np.exp(alpha_1 * (1-q_1)) / ((alpha_1 * q_1)*D_(pi / (q_1 * np.exp(-alpha_1 * (1-q_1))))))
    T2 = lambda pi: ((1 - (theta_1 - eps)) * d_1 * np.log(n) * np.exp(alpha_1*(1-q_1)) / (alpha_1 * D_(pi * np.exp(alpha_1 * (1-q_1)))))
    T3 = lambda pi: (np.exp(alpha_1) * d_1 * np.log(d_1) / (alpha_1 * (1-q_1)))
    ff = lambda pi: max(T1(pi), T2(pi), T3(pi))

    bound = (q, np.exp(-alpha * (1-q))-0.01)
    x0 = pi_init
    gff = grad(ff)
    res = minimize(ff, x0, tol=1e-6, bounds=[bound], jac=gff)
    achiev_bound = res.fun[0]
    pi_NDD = res.x[0]
    assert q <= pi_NDD <= 1

    T_array = np.linspace(1, 2000, 25)

    ###### Main Experiment loop #####
    for T in T_array:
        NDD_ber_error = []
        DD_ber_error = []
        NCOMP_ber_error = []
        print("T: ", int(round(T)))
        for test in range(num_tests):
            sigma = D(n, k) # Generate the vector of defectives
            X_ber = Ber(n, int(round(T)), p)
            y_ber = Y(dilution_noise(X_ber, q), sigma)

            # NCOMP - Bernoulli
            # Choose delta according to z
            z = ((1- alpha/(k) * (1-q))**(k-1) + (1/q)*(1 - alpha/(k) * (1-q))**k) / 2
            l = (1 - alpha / k * (1-q))**(k-1)
            u = (1 - alpha / k * (1-q))**(k) * (1/q)
            delta_NCOMP = z - 1
            assert (l-1) < delta_NCOMP < (u-1)
            sigma_hat_ber = NCOMP(X_ber, y_ber, q=q, delta=delta_NCOMP)
            err = error(sigma, sigma_hat_ber)
            NCOMP_ber_error.append(error(sigma, sigma_hat_ber))

            # NDD - Bernoulli
            sigma_hat_ber = NDD(X_ber, y_ber, pi=(pi_NDD), alpha=alpha, T = T, d = k)
            err = error(sigma, sigma_hat_ber)
            NDD_ber_error.append(error(sigma, sigma_hat_ber))

        acc = (num_tests - np.sum(np.array(NDD_ber_error))) / num_tests
        NDD_ber_acc.append(acc)
        acc = (num_tests - np.sum(np.array(NCOMP_ber_error))) / num_tests
        NCOMP_ber_acc.append(acc)

    q_results.append([q, T_array, NCOMP_ber_acc, NDD_ber_acc])
```

    q:  1e-06
    T:  1
    T:  84
    T:  168
    T:  251
    T:  334
    T:  417
    T:  501
    T:  584
    T:  667
    T:  751
    T:  834
    T:  917
    T:  1000
    T:  1084
    T:  1167
    T:  1250
    T:  1334
    T:  1417
    T:  1500
    T:  1584
    T:  1667
    T:  1750
    T:  1833
    T:  1917
    T:  2000
    q:  0.15
    T:  1
    T:  84
    T:  168
    T:  251
    T:  334
    T:  417
    T:  501
    T:  584
    T:  667
    T:  751
    T:  834
    T:  917
    T:  1000
    T:  1084
    T:  1167
    T:  1250
    T:  1334
    T:  1417
    T:  1500
    T:  1584
    T:  1667
    T:  1750
    T:  1833
    T:  1917
    T:  2000
    q:  0.35
    T:  1
    T:  84
    T:  168
    T:  251
    T:  334
    T:  417
    T:  501
    T:  584
    T:  667
    T:  751
    T:  834
    T:  917
    T:  1000
    T:  1084
    T:  1167
    T:  1250
    T:  1334
    T:  1417
    T:  1500
    T:  1584
    T:  1667
    T:  1750
    T:  1833
    T:  1917
    T:  2000
    q:  0.5
    T:  1
    T:  84
    T:  168
    T:  251
    T:  334
    T:  417
    T:  501
    T:  584
    T:  667
    T:  751
    T:  834
    T:  917
    T:  1000
    T:  1084
    T:  1167
    T:  1250
    T:  1334
    T:  1417
    T:  1500
    T:  1584
    T:  1667
    T:  1750
    T:  1833
    T:  1917
    T:  2000

```python
PAL1 = ['#eff3ff','#bdd7e7','#6baed6','#3182bd','#08519c']
PAL2 = ['#feedde','#fdbe85','#fd8d3c','#e6550d','#a63603']
for i in range(len(q_results)):
    print(i)
    q, T_array, NCOMP_ber_acc, NDD_ber_acc = q_results[i]

    if (i==0):
        plt.plot(T_array, NDD_ber_acc,'o-', c=PAL1[-1-i], label="NDD - q = 0.0 to 0.5", linewidth=2)
        plt.plot(T_array, NCOMP_ber_acc,'--', c=PAL2[-1-i], label="NCOMP - q = 0.0 to 0.5", linewidth=2)
    else:
        plt.plot(T_array, NDD_ber_acc,'o-', c=PAL1[-1-i], linewidth=2)
        plt.plot(T_array, NCOMP_ber_acc,'--', c=PAL2[-1-i], linewidth=2)

    # use LaTeX fonts in the plot
    plt.rc('text', usetex=True)
    plt.rc('font', family='serif')
    matplotlib.rcParams['text.usetex'] = True

    plt.yticks(ticks=[0.0, 1.0])
    plt.xlabel(r'Number of tests (T)', fontsize=11)
    plt.ylabel(r'Success Probability', fontsize=11)

plt.legend(loc='lower right')
plt.savefig('plots/NCOMP vs NDD Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.png')
plt.savefig('plots/NCOMP vs NDD Report Nico 2 today' + str(round(C, 2)) + 'qs, q = ' + str(round(q, 2)) + ', delta = ' + str(round(delta, 2)) + ', n = ' + str(n) + ', theta = ' + str(round(theta, 2)) + '.pdf')
plt.show()
```

```python

```

notebooks/Semester Project Plots Nico.ipynb


[Sascha Spors](https://orcid.org/0000-0001-7225-9992),
Professorship Signal Theory and Digital Signal Processing,
[Institute of Communications Engineering (INT)](https://www.int.uni-rostock.de/),
Faculty of Computer Science and Electrical Engineering (IEF),
[University of Rostock, Germany](https://www.uni-rostock.de/en/)

# Tutorial Signals and Systems (Signal- und Systemtheorie)

Summer Semester 2021 (Bachelor Course #24015)

- lecture: https://github.com/spatialaudio/signals-and-systems-lecture
- tutorial: https://github.com/spatialaudio/signals-and-systems-exercises

WIP...
The project is currently under heavy development while adding new material for the summer semester 2021

Feel free to contact lecturer [frank.schultz@uni-rostock.de](https://orcid.org/0000-0002-3010-0294)

## Task 10.3

```python
#import sys
#import os
#cur_fol = os.getcwd()
# print(cur_fol)
#sys.path.append(cur_fol + '/../')

import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import numpy as np
import sympy as sp
from scipy.signal import tf2zpk, dlti
from scipy.signal import TransferFunction, ZerosPolesGain, bilinear
from scipy.signal import dimpulse, dstep, dfreqresp, dbode
from scipy.fftpack import fft

#from sig_sys_tools import zplane_plot
from scipy.special import diric
```

```python
Nf = 2**10  # values along frequency axis
Om = np.arange(-Nf, Nf) * (2*np.pi/Nf)  # get frequency vector for bode, fft

def plot_rect_psinc():
    plt.figure(figsize=(8, 6))
    ax = plt.subplot(2, 2, 1)
    plt.stem(k, x, use_line_collection=True,
             linefmt='C0:', markerfmt='C0o', basefmt='C0:')
    plt.stem(k+N, x*0, use_line_collection=True,
             linefmt='C0:', markerfmt='C0o', basefmt='C0:')
    plt.stem(k-N, x*0, use_line_collection=True,
             linefmt='C0:', markerfmt='C0o', basefmt='C0:')
    ax.xaxis.set_major_locator(MaxNLocator(integer=True))
    plt.xlabel(r'$k$')
    plt.ylabel(r'$x[k]$')
    plt.grid(True)
    plt.subplot(2, 2, 2)
    plt.plot(Om, np.abs(X))
    plt.xlim(-2*np.pi, +2*np.pi)
    plt.xticks(np.arange(-4, 5)*np.pi/2,
               [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$',
                r'$0$', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$'])
    plt.xlabel(r'$\Omega$')
    plt.ylabel(r'$|X(\Omega)|$')
    plt.grid(True)
    plt.subplot(2, 2, 3)
    plt.plot(Om, np.real(X), label=r'$\Re\{X(\Omega)\}$')
    plt.plot(Om, np.imag(X), label=r'$\Im\{X(\Omega)\}$')
    plt.xlim(-2*np.pi, +2*np.pi)
    plt.xticks(np.arange(-4, 5)*np.pi/2,
               [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$',
                r'$0$', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$'])
    plt.xlabel(r'$\Omega$')
    plt.legend()
    plt.grid(True)
    plt.subplot(2, 2, 4)
    plt.plot(Om, np.unwrap(np.angle(X))*180/np.pi)
    plt.xlim(-2*np.pi, +2*np.pi)
    plt.xticks(np.arange(-4, 5)*np.pi/2,
               [r'$-2\pi$', r'$-\frac{3\pi}{2}$', r'$-\pi$', r'$-\frac{\pi}{2}$',
                r'$0$', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$'])
    plt.ylim(-180, +180)
    plt.yticks(np.arange(-180, 180+45, 45))
    plt.grid(True)
    plt.xlabel(r'$\Omega$')
    plt.ylabel(r'$\angle X(\Omega)$ / deg')
    plt.tight_layout()
```

# Rect4 vs. Periodic Sinc Linearphase

```python
N = 4
kappa = -(N-1)/2
kappa = 0

x = np.ones(N)
k = np.arange(N) + kappa
X = diric(Om, N)*N * np.exp(-1j*Om*(N-1)/2) * np.exp(-1j*Om*kappa)
plot_rect_psinc()
plt.savefig('psinc_767B4C89FE_N4_linphase.pdf')
```

# Rect5 vs. Periodic Sinc Linearphase

```python
N = 5
kappa = -(N-1)/2
kappa = 0

x = np.ones(N)
k = np.arange(N) + kappa
X = diric(Om, N)*N * np.exp(-1j*Om*(N-1)/2) * np.exp(-1j*Om*kappa)
plot_rect_psinc()
plt.savefig('psinc_767B4C89FE_N5_linphase.pdf')
```

# Rect5 vs. Periodic Sinc Zerophase

```python
N = 5
kappa = -(N-1)/2
#kappa = 0
print(kappa)
x = np.ones(N)
k = np.arange(N) + kappa
X = diric(Om, N)*N * np.exp(-1j*Om*(N-1)/2) * np.exp(-1j*Om*kappa)
plot_rect_psinc()
plt.savefig('psinc_767B4C89FE_N5_zerophase.pdf')
```

# Rect31 vs. Periodic Sinc Zerophase

```python
Om = np.arange(-2*Nf, 2*Nf) * (2*np.pi/Nf)

N = 31
kappa = -(N-1)/2
#kappa = 0

x = np.ones(N)
k = np.arange(N) + kappa
X = diric(Om, N)*N * np.exp(-1j*Om*(N-1)/2) * np.exp(-1j*Om*kappa)

plt.figure(figsize=(8, 6))
ax = plt.subplot(2, 1, 1)
plt.stem(k, x, use_line_collection=True,
         linefmt='C0:', markerfmt='C0o', basefmt='C0:')
plt.stem(k+N, x*0, use_line_collection=True,
         linefmt='C0:', markerfmt='C0o', basefmt='C0:')
plt.stem(k-N, x*0, use_line_collection=True,
         linefmt='C0:', markerfmt='C0o', basefmt='C0:')
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
plt.xlabel(r'$k$')
plt.ylabel(r'$x[k]$')
plt.grid(True)

plt.subplot(2, 1, 2)
plt.plot(Om, np.real(X), label=r'$\Re\{X(\Omega)\}$')
plt.plot(Om, np.imag(X), label=r'$\Im\{X(\Omega)\}$')
plt.xlim(-4*np.pi, +4*np.pi)
plt.xticks(np.arange(-4, 5)*np.pi,
           [r'$-4\pi$', r'$-3\pi$', r'$-2\pi$', r'$-\pi$', r'$0$',
            r'$\pi$', r'$2\pi$', r'$3\pi$', r'$4\pi$'])
plt.xlabel(r'$\Omega$')
plt.legend()
plt.grid(True)
plt.savefig('psinc_767B4C89FE_N31_zerophase.pdf')
```

## Copyright

This tutorial is provided as Open Educational Resource (OER), to be found at
https://github.com/spatialaudio/signals-and-systems-exercises
accompanying the OER lecture
https://github.com/spatialaudio/signals-and-systems-lecture.
Both are licensed under a) the Creative Commons Attribution 4.0 International
License for text and graphics and b) the MIT License for source code.
Please attribute material from the tutorial as *Frank Schultz,
Continuous- and Discrete-Time Signals and Systems - A Tutorial Featuring
Computational Examples, University of Rostock* with
``main file, github URL, commit number and/or version tag, year``.


# Simulating Molecules using VQE

In this tutorial, we introduce the Variational Quantum Eigensolver (VQE), motivate its use, explain the necessary theory, and demonstrate its implementation in finding the ground state energy of molecules.

## Contents
1. [Introduction](#introduction)
2. [The Variational Method of Quantum Mechanics](#varmethod)
    1. [Mathematical Background](#backgroundmath)
    2. [Bounding the Ground State](#groundstate)
3. [The Variational Quantum Eigensolver](#vqe)
    1. [Variational Forms](#varforms)
    2. [Simple Variational Forms](#simplevarform)
    3. [Parameter Optimization](#optimization)
    4. [Example with a Single Qubit Variational Form](#example)
    5. [Structure of Common Variational Forms](#commonvarforms)
4. [VQE Implementation in Qiskit](#implementation)
    1. [Running VQE on a Statevector Simulator](#implementationstatevec)
    2. [Running VQE on a Noisy Simulator](#implementationnoisy)
5. [Problems](#problems)
6. [References](#references)

## Introduction
In many applications it is important to find the minimum eigenvalue of a matrix. For example, in chemistry, the minimum eigenvalue of a Hermitian matrix characterizing the molecule is the ground state energy of that system. In the future, the quantum phase estimation algorithm may be used to find the minimum eigenvalue. However, its implementation on useful problems requires circuit depths exceeding the limits of hardware available in the NISQ era. Thus, in 2014, Peruzzo *et al.* proposed VQE to estimate the ground state energy of a molecule using much shallower circuits [1].

Formally stated, given a Hermitian matrix $H$ with an unknown minimum eigenvalue $\lambda_{min}$, associated with the eigenstate $|\psi_{min}\rangle$, VQE provides an estimate $\lambda_{\theta}$ bounding $\lambda_{min}$:

\begin{align*}
    \lambda_{min} \le \lambda_{\theta} \equiv \langle \psi(\theta) |H|\psi(\theta) \rangle
\end{align*}

where $|\psi(\theta)\rangle$ is the eigenstate associated with $\lambda_{\theta}$. By applying a parameterized circuit, represented by $U(\theta)$, to some arbitrary starting state $|\psi\rangle$, the algorithm obtains an estimate $U(\theta)|\psi\rangle \equiv |\psi(\theta)\rangle$ on $|\psi_{min}\rangle$. The estimate is iteratively optimized by a classical controller changing the parameter $\theta$ minimizing the expectation value of $\langle \psi(\theta) |H|\psi(\theta) \rangle$.

## The Variational Method of Quantum Mechanics
### Mathematical Background

VQE is an application of the variational method of quantum mechanics. To better understand the variational method, some preliminary mathematical background is provided. An eigenvector, $|\psi_i\rangle$, of a matrix $A$ is invariant under transformation by $A$ up to a scalar multiplicative constant (the eigenvalue $\lambda_i$). That is,

\begin{align*}
    A |\psi_i\rangle = \lambda_i |\psi_i\rangle
\end{align*}

Furthermore, a matrix $H$ is Hermitian when it is equal to its own conjugate transpose.

\begin{align*}
    H = H^{\dagger}
\end{align*}

The spectral theorem states that the eigenvalues of a Hermitian matrix must be real. Thus, any eigenvalue of $H$ has the property that $\lambda_i = \lambda_i^*$. As any measurable quantity must be real, Hermitian matrices are suitable for describing the Hamiltonians of quantum systems. Moreover, $H$ may be expressed as

\begin{align*}
    H = \sum_{i = 1}^{N} \lambda_i |\psi_i\rangle \langle \psi_i |
\end{align*}

where each $\lambda_i$ is the eigenvalue corresponding to the eigenvector $|\psi_i\rangle$. Furthermore, the expectation value of the observable $H$ on an arbitrary quantum state $|\psi\rangle$ is given by

\begin{align}
    \langle H \rangle_{\psi} &\equiv \langle \psi | H | \psi \rangle
\end{align}

Substituting $H$ with its representation as a weighted sum of its eigenvectors,

\begin{align}
    \langle H \rangle_{\psi} = \langle \psi | H | \psi \rangle &= \langle \psi | \left(\sum_{i = 1}^{N} \lambda_i |\psi_i\rangle \langle \psi_i |\right) |\psi\rangle\\
    &= \sum_{i = 1}^{N} \lambda_i \langle \psi | \psi_i\rangle \langle \psi_i | \psi\rangle \\
    &= \sum_{i = 1}^{N} \lambda_i | \langle \psi_i | \psi\rangle |^2
\end{align}

The last equation demonstrates that the expectation value of an observable on any state can be expressed as a linear combination using the eigenvalues associated with $H$ as the weights. Moreover, each of the weights in the linear combination is greater than or equal to 0, as $| \langle \psi_i | \psi\rangle |^2 \ge 0$ and so it is clear that

\begin{align}
    \lambda_{min} \le \langle H \rangle_{\psi} = \langle \psi | H | \psi \rangle = \sum_{i = 1}^{N} \lambda_i | \langle \psi_i | \psi\rangle |^2
\end{align}

The above equation is known as the **variational method** (in some texts it is also known as the variational principle) [2]. It is important to note that this implies that the expectation value of any wave function will always be at least the minimum eigenvalue associated with $H$. Moreover, the expectation value of the eigenstate $|\psi_{min}\rangle$ is given by $\langle \psi_{min}|H|\psi_{min}\rangle = \langle \psi_{min}|\lambda_{min}|\psi_{min}\rangle = \lambda_{min}$. Thus, as expected, $\langle H \rangle_{\psi_{min}}=\lambda_{min}$.

### Bounding the Ground State
When the Hamiltonian of a system is described by the Hermitian matrix $H$ the ground state energy of that system, $E_{gs}$, is the smallest eigenvalue associated with $H$. By arbitrarily selecting a wave function $|\psi \rangle$ (called an *ansatz*) as an initial guess approximating $|\psi_{min}\rangle$, calculating its expectation value, $\langle H \rangle_{\psi}$, and iteratively updating the wave function, arbitrarily tight bounds on the ground state energy of a Hamiltonian may be obtained.

## The Variational Quantum Eigensolver
### Variational Forms
A systematic approach to varying the ansatz is required to implement the variational method on a quantum computer. VQE does so through the use of a parameterized circuit with a fixed form. Such a circuit is often called a *variational form*, and its action may be represented by the linear transformation $U(\theta)$. A variational form is applied to a starting state $|\psi\rangle$ (such as the vacuum state $|0\rangle$, or the Hartree Fock state) and generates an output state $U(\theta)|\psi\rangle\equiv |\psi(\theta)\rangle$. Iterative optimization over $|\psi(\theta)\rangle$ aims to yield an expectation value $\langle \psi(\theta)|H|\psi(\theta)\rangle \approx E_{gs} \equiv \lambda_{min}$. Ideally, $|\psi(\theta)\rangle$ will be close to $|\psi_{min}\rangle$ (where 'closeness' is characterized by either state fidelity, or Manhattan distance) although in practice, useful bounds on $E_{gs}$ can be obtained even if this is not the case.

Moreover, a fixed variational form with a polynomial number of parameters can only generate transformations to a polynomially sized subspace of all the states in an exponentially sized Hilbert space. Consequently, various variational forms exist. Some, such as Ry and RyRz are heuristically designed, without consideration of the target domain. Others, such as UCCSD, utilize domain specific knowledge to generate close approximations based on the problem's structure. The structure of common variational forms is discussed in greater depth later in this document.

### Simple Variational Forms
When constructing a variational form we must balance two opposing goals. Ideally, our $n$ qubit variational form would be able to generate any possible state $|\psi\rangle$ where $|\psi\rangle \in \mathbb{C}^N$ and $N=2^n$. However, we would like the variational form to use as few parameters as possible. Here, we aim to give intuition for the construction of variational forms satisfying our first goal, while disregarding the second goal for the sake of simplicity.

Consider the case where $n=1$. The U3 gate takes three parameters, $\theta, \phi$ and $\lambda$, and represents the following transformation:

$$
\begin{align}
    U3(\theta, \phi, \lambda) = \begin{pmatrix}\cos(\frac{\theta}{2}) & -e^{i\lambda}\sin(\frac{\theta}{2}) \\ e^{i\phi}\sin(\frac{\theta}{2}) & e^{i\lambda + i\phi}\cos(\frac{\theta}{2}) \end{pmatrix}
\end{align}
$$

Up to a global phase, any possible single qubit transformation may be implemented by appropriately setting these parameters. Consequently, for the single qubit case, a variational form capable of generating any possible state is given by the circuit:

Moreover, this universal 'variational form' only has 3 parameters and thus can be efficiently optimized. It is worth emphasising that the ability to generate an arbitrary state ensures that during the optimization process, the variational form does not limit the set of attainable states over which the expectation value of $H$ can be taken. Ideally, this ensures that the minimum expectation value is limited only by the capabilities of the classical optimizer.

A less trivial universal variational form may be derived for the 2 qubit case, where two body interactions, and thus entanglement, must be considered to achieve universality. Based on the work presented by *Shende et al.* [3] the following is an example of a universal parameterized 2 qubit circuit:

Allow the transformation performed by the above circuit to be represented by $U(\theta)$. When optimized variationally, the expectation value of $H$ is minimized when $U(\theta)|\psi\rangle \equiv |\psi(\theta)\rangle \approx |\psi_{min}\rangle$. By formulation, $U(\theta)$ may produce a transformation to any possible state, and so this variational form may obtain an arbitrarily tight bound on two qubit ground state energies, only limited by the capabilities of the classical optimizer.

### Parameter Optimization
Once an efficiently parameterized variational form has been selected, in accordance with the variational method, its parameters must be optimized to minimize the expectation value of the target Hamiltonian. The parameter optimization process has various challenges. For example, quantum hardware has various types of noise and so objective function evaluation (energy calculation) may not necessarily reflect the true objective function. Additionally, some optimizers perform a number of objective function evaluations dependent on cardinality of the parameter set. An appropriate optimizer should be selected by considering the requirements of an application.

A popular optimization strategy is gradient descent where each parameter is updated in the direction yielding the largest local change in energy. Consequently, the number of evaluations performed depends on the number of optimization parameters present. This allows the algorithm to quickly find a local optimum in the search space. However, this optimization strategy often gets stuck at poor local optima, and is relatively expensive in terms of the number of circuit evaluations performed. While an intuitive optimization strategy, it is not recommended for use in VQE.

An appropriate optimizer for optimizing a noisy objective function is the *Simultaneous Perturbation Stochastic Approximation* optimizer (SPSA). SPSA approximates the gradient of the objective function with only two measurements. It does so by concurrently perturbing all of the parameters in a random fashion, in contrast to gradient descent where each parameter is perturbed independently. When utilizing VQE in either a noisy simulator or on real hardware, SPSA is recommended as the classical optimizer.

When noise is not present in the cost function evaluation (such as when using VQE with a statevector simulator), a wide variety of classical optimizers may be useful. Two such optimizers supported by Qiskit Aqua are the *Sequential Least Squares Programming* optimizer (SLSQP) and the *Constrained Optimization by Linear Approximation* optimizer (COBYLA). It is worth noting that COBYLA only performs one objective function evaluation per optimization iteration (and thus the number of evaluations is independent of the parameter set's cardinality). Therefore, if the objective function is noise-free and minimizing the number of performed evaluations is desirable, it is recommended to try COBYLA.

### Example with a Single Qubit Variational Form

We will now use the simple single qubit variational form to solve a problem similar to ground state energy estimation. Specifically, we are given a random probability vector $\vec{x}$ and wish to determine a possible parameterization for our single qubit variational form such that it outputs a probability distribution that is close to $\vec{x}$ (where closeness is defined in terms of the Manhattan distance between the two probability vectors).

We first create the random probability vector in python:

```python
import numpy as np
np.random.seed(999999)
target_distr = np.random.rand(2)
# We now convert the random vector into a valid probability vector
target_distr /= sum(target_distr)
```

We subsequently create a function that takes the parameters of our single U3 variational form as arguments and returns the corresponding quantum circuit:

```python
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
def get_var_form(params):
    qr = QuantumRegister(1, name="q")
    cr = ClassicalRegister(1, name='c')
    qc = QuantumCircuit(qr, cr)
    qc.u3(params[0], params[1], params[2], qr[0])
    qc.measure(qr, cr[0])
    return qc
```

Now we specify the objective function which takes as input a list of the variational form's parameters, and returns the cost associated with those parameters:

```python
from qiskit import Aer, transpile, assemble
backend = Aer.get_backend("qasm_simulator")
NUM_SHOTS = 10000

def get_probability_distribution(counts):
    output_distr = [v / NUM_SHOTS for v in counts.values()]
    if len(output_distr) == 1:
        output_distr.append(1 - output_distr[0])
    return output_distr

def objective_function(params):
    # Obtain a quantum circuit instance from the paramters
    qc = get_var_form(params)
    # Execute the quantum circuit to obtain the probability distribution associated with the current parameters
    t_qc = transpile(qc, backend)
    qobj = assemble(t_qc, shots=NUM_SHOTS)
    result = backend.run(qobj).result()
    # Obtain the counts for each measured state, and convert those counts into a probability vector
    output_distr = get_probability_distribution(result.get_counts(qc))
    # Calculate the cost as the distance between the output distribution and the target distribution
    cost = sum([np.abs(output_distr[i] - target_distr[i]) for i in range(2)])
    return cost
```

Finally, we create an instance of the COBYLA optimizer, and run the algorithm. Note that the output varies from run to run. Moreover, while close, the obtained distribution might not be exactly the same as the target distribution, however, increasing the number of shots taken will increase the accuracy of the output.

```python
from qiskit.aqua.components.optimizers import COBYLA

# Initialize the COBYLA optimizer
optimizer = COBYLA(maxiter=500, tol=0.0001)

# Create the initial parameters (noting that our single qubit variational form has 3 parameters)
params = np.random.rand(3)
ret = optimizer.optimize(num_vars=3, objective_function=objective_function, initial_point=params)

# Obtain the output distribution using the final parameters
qc = get_var_form(ret[0])
t_qc = transpile(qc, backend)
qobj = assemble(t_qc, shots=NUM_SHOTS)
counts = backend.run(qobj).result().get_counts(qc)
output_distr = get_probability_distribution(counts)

print("Target Distribution:", target_distr)
print("Obtained Distribution:", output_distr)
print("Output Error (Manhattan Distance):", ret[1])
print("Parameters Found:", ret[0])

```

    <ipython-input-2-9c1182150c31>:6: DeprecationWarning: The QuantumCircuit.u3 method is deprecated as of 0.16.0. It will be removed no earlier than 3 months after the release date. You should use QuantumCircuit.u instead, which acts identically. Alternatively, you can decompose u3 in terms of QuantumCircuit.p and QuantumCircuit.sx: u3(ϴ,φ,λ) = p(φ+π) sx p(ϴ+π) sx p(λ) (2 pulses on hardware).
      qc.u3(params[0], params[1], params[2], qr[0])

    Target Distribution: [0.51357006 0.48642994]
    Obtained Distribution: [0.5205, 0.4795]
    Output Error (Manhattan Distance): 0.023259881261160853
    Parameters Found: [1.53439896 0.07596288 0.65167175]

### Structure of Common Variational Forms
As already discussed, it is not possible for a polynomially parameterized variational form to generate a transformation to any state. Variational forms can be grouped into two categories, depending on how they deal with this limitation. The first category of variational forms use domain or application specific knowledge to limit the set of possible output states. The second approach uses a heuristic circuit without prior domain or application specific knowledge.

The first category of variational forms exploit characteristics of the problem domain to restrict the set of transformations that may be required. For example, when calculating the ground state energy of a molecule, the number of particles in the system is known *a priori*. Therefore, if a starting state with the correct number of particles is used, by limiting the variational form to only producing particle preserving transformations, the number of parameters required to span the new transformation subspace can be greatly reduced. Indeed, by utilizing similar information from Coupled-Cluster theory, the variational form UCCSD can obtain very accurate results for molecular ground state energy estimation when starting from the Hartree Fock state. Another example illustrating the exploitation of domain-specific knowledge follows from considering the set of circuits realizable on real quantum hardware. Extant quantum computers, such as those based on super conducting qubits, have limited qubit connectivity. That is, it is not possible to implement 2-qubit gates on arbitrary qubit pairs (without inserting swap gates). Thus, variational forms have been constructed for specific quantum computer architectures where the circuits are specifically tuned to maximally exploit the natively available connectivity and gates of a given quantum device. Such a variational form was used in 2017 to successfully implement VQE for the estimation of the ground state energies of molecules as large as BeH$_2$ on an IBM quantum computer [4].

In the second approach, gates are layered such that good approximations on a wide range of states may be obtained. Qiskit Aqua supports three such variational forms: RyRz, Ry and SwapRz (we will only discuss the first two). All of these variational forms accept multiple user-specified configurations. Three essential configurations are the number of qubits in the system, the depth setting, and the entanglement setting. A single layer of a variational form specifies a certain pattern of single qubit rotations and CX gates. The depth setting says how many times the variational form should repeat this pattern. By increasing the depth setting, at the cost of increasing the number of parameters that must be optimized, the set of states the variational form can generate increases. Finally, the entanglement setting selects the configuration, and implicitly the number, of CX gates. For example, when the entanglement setting is linear, CX gates are applied to adjacent qubit pairs in order (and thus $n-1$ CX gates are added per layer). When the entanglement setting is full, a CX gate is applied to each qubit pair in each layer. The circuits for RyRz corresponding to `entanglement="full"` and `entanglement="linear"` can be seen by executing the following code snippet:

```python
from qiskit.circuit.library import EfficientSU2
entanglements = ["linear", "full"]
for entanglement in entanglements:
    form = EfficientSU2(num_qubits=4, entanglement=entanglement)
    if entanglement == "linear":
        print("=============Linear Entanglement:=============")
    else:
        print("=============Full Entanglement:=============")
    # We initialize all parameters to 0 for this demonstration
    display(form.draw(fold=100))
    print()
```

Assume the depth setting is set to $d$. Then, RyRz has $2n\times (d+1)$ parameters and Ry (not shown) has $n\times (d+1)$ parameters.

## VQE Implementation in Qiskit
This section illustrates an implementation of VQE using the programmatic approach. Qiskit Aqua also enables a declarative implementation, however, it reveals less information about the underlying algorithm. This code, specifically the preparation of qubit operators, is based on the code found in the Qiskit Tutorials repository (and as of July 2019, may be found at: https://github.com/Qiskit/qiskit-tutorials ).

The following libraries must first be imported.

```python
from qiskit.aqua.algorithms import VQE, NumPyEigensolver
import matplotlib.pyplot as plt
import numpy as np
from qiskit.chemistry.components.variational_forms import UCCSD
from qiskit.chemistry.components.initial_states import HartreeFock
from qiskit.circuit.library import EfficientSU2
from qiskit.aqua.components.optimizers import COBYLA, SPSA, SLSQP
from qiskit.aqua.operators import Z2Symmetries
from qiskit import IBMQ, BasicAer, Aer
from qiskit.chemistry.drivers import PySCFDriver, UnitsType
from qiskit.chemistry import FermionicOperator
from qiskit.aqua import QuantumInstance
from qiskit.ignis.mitigation.measurement import CompleteMeasFitter
from qiskit.providers.aer.noise import NoiseModel
```

### Running VQE on a Statevector Simulator
We demonstrate the calculation of the ground state energy for LiH at various interatomic distances. A driver for the molecule must be created at each such distance. Note that in this experiment, to reduce the number of qubits used, we freeze the core and remove two unoccupied orbitals. First, we define a function that takes an interatomic distance and returns the appropriate qubit operator, $H$, as well as some other information about the operator.

```python
def get_qubit_op(dist):
    driver = PySCFDriver(atom="Li .0 .0 .0; H .0 .0 " + str(dist), unit=UnitsType.ANGSTROM,
                         charge=0, spin=0, basis='sto3g')
    molecule = driver.run()
    freeze_list = [0]
    remove_list = [-3, -2]
    repulsion_energy = molecule.nuclear_repulsion_energy
    num_particles = molecule.num_alpha + molecule.num_beta
    num_spin_orbitals = molecule.num_orbitals * 2
    remove_list = [x % molecule.num_orbitals for x in remove_list]
    freeze_list = [x % molecule.num_orbitals for x in freeze_list]
    remove_list = [x - len(freeze_list) for x in remove_list]
    remove_list += [x + molecule.num_orbitals - len(freeze_list)  for x in remove_list]
    freeze_list += [x + molecule.num_orbitals for x in freeze_list]
    ferOp = FermionicOperator(h1=molecule.one_body_integrals, h2=molecule.two_body_integrals)
    ferOp, energy_shift = ferOp.fermion_mode_freezing(freeze_list)
    num_spin_orbitals -= len(freeze_list)
    num_particles -= len(freeze_list)
    ferOp = ferOp.fermion_mode_elimination(remove_list)
    num_spin_orbitals -= len(remove_list)
    qubitOp = ferOp.mapping(map_type='parity', threshold=0.00000001)
    qubitOp = Z2Symmetries.two_qubit_reduction(qubitOp, num_particles)
    shift = energy_shift + repulsion_energy
    return qubitOp, num_particles, num_spin_orbitals, shift
```

First, the exact ground state energy is calculated using the qubit operator and a classical exact eigensolver. Subsequently, the initial state $|\psi\rangle$ is created, which the VQE instance uses to produce the final ansatz $\min_{\theta}(|\psi(\theta)\rangle)$. The exact result and the VQE result at each interatomic distance is stored. Observe that the result given by `vqe.run(backend)['energy'] + shift` is equivalent the quantity $\min_{\theta}\left(\langle \psi(\theta)|H|\psi(\theta)\rangle\right)$, where the minimum is not necessarily the global minimum.

When initializing the VQE instance with `VQE(qubitOp, var_form, optimizer, 'matrix')` the expectation value of $H$ on $|\psi(\theta)\rangle$ is directly calculated through matrix multiplication. However, when using an actual quantum device, or a true simulator such as the `qasm_simulator` with `VQE(qubitOp, var_form, optimizer, 'paulis')` the calculation of the expectation value is more complicated. A Hamiltonian may be represented as a sum of a Pauli strings, with each Pauli term acting on a qubit as specified by the mapping being used. Each Pauli string has a corresponding circuit appended to the circuit corresponding to $|\psi(\theta)\rangle$. Subsequently, each of these circuits is executed, and all of the results are used to determine the expectation value of $H$ on $|\psi(\theta)\rangle$. In the following example, we initialize the VQE instance with `matrix` mode, and so the expectation value is directly calculated through matrix multiplication.

Note that the following code snippet may take a few minutes to run to completion.

```python
backend = BasicAer.get_backend("statevector_simulator")
distances = np.arange(0.5, 4.0, 0.1)
exact_energies = []
vqe_energies = []
optimizer = SLSQP(maxiter=5)
for dist in distances:
    qubitOp, num_particles, num_spin_orbitals, shift = get_qubit_op(dist)
    result = NumPyEigensolver(qubitOp).run()
    exact_energies.append(np.real(result.eigenvalues) + shift)
    initial_state = HartreeFock(
        num_spin_orbitals,
        num_particles,
        qubit_mapping='parity'
    )
    var_form = UCCSD(
        num_orbitals=num_spin_orbitals,
        num_particles=num_particles,
        initial_state=initial_state,
        qubit_mapping='parity'
    )
    vqe = VQE(qubitOp, var_form, optimizer)
    vqe_result = np.real(vqe.run(backend)['eigenvalue'] + shift)
    vqe_energies.append(vqe_result)
    print("Interatomic Distance:", np.round(dist, 2), "VQE Result:", vqe_result, "Exact Energy:", exact_energies[-1])

print("All energies have been calculated")
```

    Interatomic Distance: 0.5 VQE Result: -7.039710215379314 Exact Energy: [-7.03973252]
    Interatomic Distance: 0.6 VQE Result: -7.313344303226737 Exact Energy: [-7.31334583]
    Interatomic Distance: 0.7 VQE Result: -7.500921096057088 Exact Energy: [-7.50092209]
    Interatomic Distance: 0.8 VQE Result: -7.6309769149071265 Exact Energy: [-7.63097825]
    Interatomic Distance: 0.9 VQE Result: -7.720810794644169 Exact Energy: [-7.72081241]
    Interatomic Distance: 1.0 VQE Result: -7.782240655784512 Exact Energy: [-7.7822424]
    Interatomic Distance: 1.1 VQE Result: -7.823597493382595 Exact Energy: [-7.82359928]
    Interatomic Distance: 1.2 VQE Result: -7.850696622382944 Exact Energy: [-7.85069838]
    Interatomic Distance: 1.3 VQE Result: -7.867561602146681 Exact Energy: [-7.86756329]
    Interatomic Distance: 1.4 VQE Result: -7.876999876364884 Exact Energy: [-7.87700149]
    Interatomic Distance: 1.5 VQE Result: -7.881014173918588 Exact Energy: [-7.88101572]
    Interatomic Distance: 1.6 VQE Result: -7.881070663277413 Exact Energy: [-7.88107204]
    Interatomic Distance: 1.7 VQE Result: -7.878267162103786 Exact Energy: [-7.87826817]
    Interatomic Distance: 1.8 VQE Result: -7.873440112020817 Exact Energy: [-7.87344029]
    Interatomic Distance: 1.9 VQE Result: -7.867233666618265 Exact Energy: [-7.86723396]
    Interatomic Distance: 2.0 VQE Result: -7.860152328043125 Exact Energy: [-7.86015321]
    Interatomic Distance: 2.1 VQE Result: -7.852595105806076 Exact Energy: [-7.85259583]
    Interatomic Distance: 2.2 VQE Result: -7.844878726181193 Exact Energy: [-7.84487909]
    Interatomic Distance: 2.3 VQE Result: -7.837257439443965 Exact Energy: [-7.83725797]
    Interatomic Distance: 2.4 VQE Result: -7.8299350445800435 Exact Energy: [-7.829937]
    Interatomic Distance: 2.5 VQE Result: -7.823070191944695 Exact Energy: [-7.82307664]
    Interatomic Distance: 2.6 VQE Result: -7.816782592132487 Exact Energy: [-7.81679515]
    Interatomic Distance: 2.7 VQE Result: -7.811153437640691 Exact Energy: [-7.81116828]
    Interatomic Distance: 2.8 VQE Result: -7.80621829754091 Exact Energy: [-7.80622956]
    Interatomic Distance: 2.9 VQE Result: -7.801962397987873 Exact Energy: [-7.8019736]
    Interatomic Distance: 3.0 VQE Result: -7.798352411305384 Exact Energy: [-7.79836343]
    Interatomic Distance: 3.1 VQE Result: -7.795326815802424 Exact Energy: [-7.79534045]
    Interatomic Distance: 3.2 VQE Result: -7.792800696732087 Exact Energy: [-7.79283481]
    Interatomic Distance: 3.3 VQE Result: -7.790603797885796 Exact Energy: [-7.79077401]
    Interatomic Distance: 3.4 VQE Result: -7.788715355036123 Exact Energy: [-7.7890889]
    Interatomic Distance: 3.5 VQE Result: -7.78721577056921 Exact Energy: [-7.78771697]
    Interatomic Distance: 3.6 VQE Result: -7.786080395256832 Exact Energy: [-7.78660376]
    Interatomic Distance: 3.7 VQE Result: -7.785203510040527 Exact Energy: [-7.78570291]
    Interatomic Distance: 3.8 VQE Result: -7.784479536529381 Exact Energy: [-7.78497559]
    Interatomic Distance: 3.9 VQE Result: -7.783853365368745 Exact Energy: [-7.78438961]
    All energies have been calculated

```python
plt.plot(distances, exact_energies, label="Exact Energy")
plt.plot(distances, vqe_energies, label="VQE Energy")
plt.xlabel('Atomic distance (Angstrom)')
plt.ylabel('Energy')
plt.legend()
plt.show()
```

Note that the VQE results are very close to the exact results, and so the exact energy curve is hidden by the VQE curve.

### Running VQE on a Noisy Simulator

Here, we calculate the ground state energy for H$_2$ using a noisy simulator and error mitigation.

First, we prepare the qubit operator representing the molecule's Hamiltonian:

```python
driver = PySCFDriver(atom='H .0 .0 -0.3625; H .0 .0 0.3625', unit=UnitsType.ANGSTROM, charge=0, spin=0, basis='sto3g')
molecule = driver.run()
num_particles = molecule.num_alpha + molecule.num_beta
qubitOp = FermionicOperator(h1=molecule.one_body_integrals, h2=molecule.two_body_integrals).mapping(map_type='parity')
qubitOp = Z2Symmetries.two_qubit_reduction(qubitOp, num_particles)
```

Now, we load a device coupling map and noise model from the IBMQ provider and create a quantum instance, enabling error mitigation:

```python
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
backend = Aer.get_backend("qasm_simulator")
device = provider.get_backend("ibmq_lima")
coupling_map = device.configuration().coupling_map
noise_model = NoiseModel.from_backend(device.properties())
quantum_instance = QuantumInstance(backend=backend,
                                   shots=8192,
                                   noise_model=noise_model,
                                   coupling_map=coupling_map,
                                   measurement_error_mitigation_cls=CompleteMeasFitter,
                                   cals_matrix_refresh_period=30)
```

    /usr/local/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqfactory.py:192: UserWarning: Timestamps in IBMQ backend properties, jobs, and job results are all now in local time instead of UTC.
      warnings.warn('Timestamps in IBMQ backend properties, jobs, and job results '

Finally, we must configure the optimizer, the variational form, and the VQE instance. As the effects of noise increase as the number of two qubit gates circuit depth increase, we use a heuristic variational form (RYRZ) rather than UCCSD as RYRZ has a much shallower circuit than UCCSD and uses substantially fewer two qubit gates.

The following code may take a few minutes to run to completion.

```python
exact_solution = NumPyEigensolver(qubitOp).run()
print("Exact Result:", np.real(exact_solution.eigenvalues) + molecule.nuclear_repulsion_energy)
optimizer = SPSA(maxiter=100)
var_form = EfficientSU2(qubitOp.num_qubits, entanglement="linear")
vqe = VQE(qubitOp, var_form, optimizer=optimizer)
ret = vqe.run(quantum_instance)
vqe_result = np.real(ret['eigenvalue']+ molecule.nuclear_repulsion_energy)
print("VQE Result:", vqe_result)
```

    Exact Result: [-1.13722138]
    VQE Result: -1.0949628247613228

When noise mitigation is enabled, even though the result does not fall within chemical accuracy (defined as being within 0.0016 Hartree of the exact result), it is fairly close to the exact solution.

## Problems
1. You are given a Hamiltonian $H$ with the promise that its ground state is close to a maximally entangled $n$ qubit state. Explain which variational form (or forms) is likely to efficiently and accurately learn the ground state energy of $H$. You may also answer by creating your own variational form, and explaining why it is appropriate for use with this Hamiltonian.
2. Calculate the number of circuit evaluations performed per optimization iteration, when using the COBYLA optimizer, the `qasm_simulator` with 1000 shots, and a Hamiltonian with 60 Pauli strings.
3. Use VQE to estimate the ground state energy of BeH$_2$ with an interatomic distance of $1.3$Å. You may re-use the function `get_qubit_op(dist)` by replacing `atom="Li .0 .0 .0; H .0 .0 " + str(dist)` with `atom="Be .0 .0 .0; H .0 .0 -" + str(dist) + "; H .0 .0 " + str(dist)` and invoking the function with `get_qubit_op(1.3)`. Note that removing the unoccupied orbitals does not preserve chemical precision for this molecule. However, to get the number of qubits  required down to 6 (and thereby allowing efficient simulation on most laptops), the loss of precision is acceptable. While beyond the scope of this exercise, the interested reader may use qubit tapering operations to reduce the number of required qubits to 7, without losing any chemical precision.

## References
1. Peruzzo, Alberto, et al. "A variational eigenvalue solver on a photonic quantum processor." *Nature communications* 5 (2014): 4213.
2. Griffiths, David J., and Darrell F. Schroeter. Introduction to quantum mechanics. *Cambridge University Press*, 2018.
3. Shende, Vivek V., Igor L. Markov, and Stephen S. Bullock. "Minimal universal two-qubit cnot-based circuits." arXiv preprint quant-ph/0308033 (2003).
4. Kandala, Abhinav, et al. "Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets." Nature 549.7671 (2017): 242.

```python
import qiskit
qiskit.__qiskit_version__
```

    {'qiskit-terra': '0.16.0',
     'qiskit-aer': '0.7.0',
     'qiskit-ignis': '0.5.0',
     'qiskit-ibmq-provider': '0.11.0',
     'qiskit-aqua': '0.8.0',
     'qiskit': '0.23.0'}


```python
import numpy as np
import sympy as sy
from sympy.utilities.codegen import codegen
import control.matlab as cm
import re
```

```python
z = sy.symbols('z', real=False)
r1,s0,s1 = sy.symbols('r1,s0,s1', real=True)
h = 0.04
tau = 0.262
pc1 = -5-1j*5
pc2 = np.conjugate(pc1)
po1 = -4
hpt = h/tau
Km = 0.746*41.8
A2p = sy.poly(sy.simplify(sy.expand((z-np.exp(h*pc1))*(z-np.exp(h*pc2)))), z)
Acp = A2p*sy.poly((z-np.exp(h*po1)), z)
Ap = sy.poly((z-1)*(z-np.exp(-hpt)), z)
Bp = sy.poly(Km*tau*(hpt-1+np.exp(-hpt))*z + Km*tau*(1-np.exp(-hpt)-hpt*np.exp(-hpt)), z)
Rp = sy.poly(z+r1, z)
Sp = sy.poly(s0*z + s1, z)
dioph=(Ap*Rp+Bp*Sp-Acp).all_coeffs()
print dioph
```

    [1.0*r1 + 0.0905485642505141*s0 + 0.598553639437276, -1.85841144421398*r1 + 0.0860565404440641*s0 + 0.0905485642505141*s1 - 1.17944710048824, 0.858411444213975*r1 + 0.0860565404440641*s1 + 0.571209063848815]

```python
sol=sy.solve(dioph, (r1,s0,s1))
print sol[r1]
print sol[s0]
print sol[s1]

t0 = A2p.evalf(subs={z:1})/Bp.evalf(subs={z:1,})
print t0
```

    -0.632898354797954
    0.379296078794350
    -0.324459627611956
    0.370876886395060

```python
# Reorganize solution expression for matlab code generation
sol_expr = ('RST_DC_lab', [Bp.all_coeffs()[0], Bp.all_coeffs()[1],
                           Ap.all_coeffs()[1], Ap.all_coeffs()[2],
                           sol[r1], sol[s0], sol[s1], A2p.subs(z, 1)/Bp.subs(z,1), h,np.exp(h*po1) ])
```

```python
# Export to matlab code
[(m_name, m_code)] = codegen(sol_expr, 'octave')
```

```python
m_code = m_code.replace("out1", "b0").replace("out2", "b1").replace("out3", "a1").replace("out4", "a2")
m_code = m_code.replace("out5", "r1").replace("out6", "s0").replace("out7", "s1").replace("out8", "t0")
m_code = m_code.replace("out9", "h").replace("out10", "obsPole")
m_code = m_code.replace("function ", "% function ")
m_code = m_code.replace("end", "")
print m_code
with open("/home/kjartan/Dropbox/undervisning/tec/MR2007/labs/dc_rst_design.m", "w") as text_file:
    text_file.write(m_code)
```

    % function [b0, b1, a1, a2, r1, s0, s1, t0, h, b00] = RST_DC_lab()
      %RST_DC_LAB  Autogenerated by sympy
      %   Code generated with sympy 0.7.6.1
      %
      %   See http://www.sympy.org/ for more information.
      %
      %   This file is part of 'project'

      b0 = 0.0905485642505141;
      b1 = 0.0860565404440641;
      a1 = -1.85841144421398;
      a2 = 0.858411444213975;
      r1 = -0.632898354797954;
      s0 = 0.37929607879435;
      s1 = -0.324459627611956;
      t0 = 0.37087688639506;
      h = 0.04;
      b00 = 0.852143788966211;

```python
m_code.replace??
```

```python

G = Km * cm.tf([1], [tau, 1, 0])
Gd = Km * cm.tf([tau*(hpt-1+np.exp(-hpt)), tau*(1-(1+hpt)*np.exp(-hpt))], [1, -(1+np.exp(-hpt)), np.exp(-hpt)], h)
Gd2 = cm.c2d(G, h)
print Gd
print Gd2
```

      0.09055 z + 0.08606
    ----------------------
    z^2 - 1.858 z + 0.8584

    dt = 0.04

      0.09055 z + 0.08606
    ----------------------
    z^2 - 1.858 z + 0.8584

    dt = 0.04

```python
print A2p
print A2p.evalf(subs={z:1})
print Bp
print Bp.evalf(subs={z:1})

```

    Poly(z**2 - 1.58555290309441*z - (-0.792776451547206 + 0.168974310731771*I)*(0.792776451547206 + 0.168974310731771*I), z, domain='EX')
    0.0714939167206446
    Poly(0.00133942860759726*z + 0.00124712240506047, z, domain='RR')
    0.00258655101265772

```python
0.3/(5*np.sqrt(2))
```

    0.042426406871192847

```python
np.exp(-0.21)*np.sin(0.21)
```

    0.16897431073177133

```python
np.exp(0.03*(-14))
```

    0.65704681981505675

```python
 0.746*41.8

```

    31.182799999999997

```python

```