# AS-AD-model with long-run growth and bubbles.

In the following, we analyze a basic AS-AD-model containing equilibria in the goods- and service markets,an inflation-targeting Taylor rule, short-run aggregate supply determined by a philips-curve with nominal wage rigidities, as well as rational expectations for inflation.

The model has been extended with exogenous growth in structural output, $\overline{y}$, in order to simulate an economy and nominal volatility around an underlying real growth.

The model has furthermore been extended with three types of chocks:
- Biased noise in the supply side of the economy resulting in negative correlation between inflation and output - the degree to which is decided by the tradeoff set in the Taylor-rule. We have made the Central Bank relatively averse of inflation-gaps from the target of 2 pct., setting a higher b-value than h.
- White noise in the demand side of the economy, however being almost completely neutralized by Central Bank monetary policy, thus not affecting the economy much.
- Risks of a bubble bursting if the economy is bullish, having a positive outputgap. Whenever this is the case, we have implemented a given risk of nominal GDP, $y_t$, dropping a certain relative amount in the supply-side of the economy. Say, a 0,4 pct. chance of $y_t$ falling by 20 pct. each period with a positive output-gap.

We are interested in seeing the effects of crashes with a Central Bank following a given Taylor-rule.

The model has been calibrated for one period lasting around a week. For a 1000-period simulation this means around 20 years.

```python
import numpy as np
import csv
import matplotlib.pyplot as plt
import pandas as pd
import math
from sympy import symbols, Eq, solve
from scipy import optimize
```

## The Model:

The long-run part of the model can be described by:
$$\tag{GROWTH} \overline{y}_t = \overline{y}_{t-1} + k$$
In which $k \sim N(\mu_g,\sigma^2_g)$ is a stochastic chock with drift $\mu_g$ and variance $\sigma^2_g$. Thus, the structural output grows exogenously over time. As a baseline we have sat $\sigma^2_g = 0$ in order to isolate short-run fluctuations from long run ones, and sat $\mu_g=0.0008$, as this growth-rate on a weekly basis leads to an annual growth of around 2,3 pct.

The short-run part of the model can be described by:
$$ \tag{IS} y_t - \overline{y} = \alpha_1(g-\overline{g}) - \alpha_2(r-\overline{r}) - \alpha_3(\tau - \overline{\tau}) + d_t$$
$$ \tag{MP} i_t^p = \pi_{t+1}^e + \overline{r} + h(\pi - \pi^*) + b(y-\overline{y})$$
$$ \tag{AS} y_t - \overline{y} = \frac{1}{\gamma}(\pi - \pi^*) + \frac{v_t}{\gamma}$$
$$ \tag{IE} \pi^e_{t+1} = \pi_t^* $$
$$ \tag{FE} i_t^p - \pi_t = r_t $$

This results in the final model being solved by the system:
$$ \tag{AS} y_t - \overline{y} = \frac{1}{\gamma}(\pi - \pi^*) + \frac{v_t}{\gamma}$$

$$ \tag{AD} y_t - \overline{y} = -\frac{\alpha_2 h}{1+\alpha_2 b}(\pi_t-\pi^*) + \frac{\alpha_1}{1+\alpha_2 b}(g-\overline{g}) - \frac{\alpha_3}{1+\alpha_2 b}(\tau-\overline{\tau})+\frac{d_t}{1+\alpha_2 b}$$

$$ \tag{SC} v_{t} = v_{t-1}+x_t + c_t$$

$$ \tag{DC} d_{t} = d_{t-1} + z_t $$

Where:

$x_t \sim N(\mu_s,\sigma_s^2)$ is the supply-chock and $\mu_s$ is a drift of nominal short-run supply. This has been set to 0,001, greater than that of structural supply, in order to create an effect of nominally faster growth than the structural one, which consequently offsets the negative effects of bubble bursts.

$z_t \sim N(\mu_d,\sigma_s^2)$ is the white-noise demand-chock, having $\mu_d = 0$.

$c_t \in \begin{cases}[-\rho y_t,0]  \hspace{2mm} \textrm{with probabilities} \hspace{2mm} [p, (1-p)]& \text{if } \hspace{2mm} (y_t-\overline{y}) > 0 \\
                      0                                    & \text{if } \hspace{2mm} (y_t-\overline{y})\le 0
        \end{cases}$

is a stochastic crash-variable having risk of $p$ of reducing nominal supply each period with a positive output-gap.

Note, that all lower-case variables are logged - and differences are thus approximate percentages

```python
#Defining exogenous variables:
alpha1 = 1 #Weight of public spending on consumption demand
alpha2 = 1 #Weight of real interest rate on consumption demand
alpha3 = 1 #Weight of taxation changes on consumption demand
gamma = 1.5 #Constant, philips
h = 1 #Central Banks inflation-rule
b = 0.5 #Central Banks output-rule
tau = 1 #Taxation
taubar = 1 #Baseline taxation
g = 1 #Public spending
gbar = 1 #Baseline public spending
ybar = 2 #Structural GDP
pibar = 2 #Inflation target
pi_exp = pibar #Inflationary expectations
p = 0.004 #Risk of crash each period
rho = 0.2 #Share of supply cut if crash
v = np.random.normal(loc=0.001, scale=0.01, size=None)#Noise with drift (SUPPLY)

#Setting number of periods in simulation:
simsize = 1000

#Defining the time series:
data = pd.DataFrame()
data['tid'] = range(0,1000)

```

## Diagram
Firstly, the model is set up for intuitive purposes, showing the AS-AD-diagram and correlation:

```python
#Defining the functions:

def AS(ybar,pi,pi_exp,gamma):
    y = ybar + (1/gamma)*(pi-pi_exp)
    return y

def AD(ybar,pi,pibar,alpha1,g,gbar,alpha2,alpha3,h,b,tau,taubar):
    y = ybar + (alpha1/(1+alpha2*b))*(g-gbar) -(alpha2*h/(1+alpha2*b))*(pi-pibar) -(alpha3/(1+alpha2*b))*(tau-taubar) + (1/(1+alpha2*b))*v
    return y

#Solving the partial equilibria with the given values:
AS_liste = []
for i in range(0,10):
    AS_liste.append(AS(ybar,i,pi_exp,gamma))

AD_liste = []
for i in range(0,10):
    AD_liste.append(AD(ybar,i,pibar,alpha1,g,gbar,alpha2,alpha3,h,b,tau,taubar))

#Showing the AS-AD-diagram:
fig, ax1 = plt.subplots(figsize=(6,4))
plt.title('AS-AD',fontsize=15,weight='bold',pad=23)
goldman_blue = '#64a8f0'
plt.plot(AD_liste, label = 'AD', c=goldman_blue)
plt.plot(AS_liste, label = 'SRAS', c='maroon')
plt.axvline(pibar,linestyle='--',c='black', label = 'LRAS')

plt.xlabel('$y$',fontsize=15)
plt.ylabel('$\pi$',fontsize=15)

ax1.set_xlim(0,5)
ax1.set_ylim(0,5)

plt.legend(frameon=False,fontsize=12)

plt.savefig('AS-AD.pdf')
```

The above graph shows the correlation between dupply(SRAS) and demand(AD) in the short-run with equilibrium in ($\overline{y},\overline{\pi})=(2,2)$, that represents the long-run equilibrium.

## The Growth

Creating the underlying exogenous growth in structural GDP, not dwelving further into purpose of or reason for the source of growth:

```python

#Created as a random walk with drift and zero variance as baseline:
drift = 0.0008
y_bar = [1]
for i in range(1,simsize):
    s = np.random.normal(loc=0,scale=0,size=None)
    y_bar.append(
        y_bar[i-1] + drift
    )
data['ybar'] = pd.DataFrame(y_bar)
```

## Simulation

The model is solved and simulated.

Note: it will take a couple of minutes to finish the simulation, since the model is solved in every iteration. This could have been avoided by creating a program that solved the model more efficiently.

```python
#Setting a seed, 10:
np.random.seed(10)

#Setting baseline-values and defining lists of solved values to later graph:
y_løsninger = []
pi_løsninger = []
v_vektor = [0]
d_vektor = [0]
c = 0
gab = [0]
crises = []

#Solving the AS-AD-model with regards to inflation and output by a for-loop:
for i in range(1,simsize):

    #Creating a stochastic chocks in both supply- and demand-side of economy in every iteration:
    v = np.random.normal(loc=0.001, scale=0.01, size=None) #Noise with drift (SUPPLY)
    d = np.random.normal(loc=0, scale=0.01, size=None) #White noise (DEMAND)
    v_vektor.append(
            v_vektor[i-1] + v + c
            )
    d_vektor.append(
        d_vektor[i-1] + d
    )

#Solving the model using optimizer from scipy:
    pi, y = symbols('pi y')
    obj1 = lambda pi, y : data['ybar'][i] + (1/gamma)*(pi-pi_exp) + (v_vektor[i])/gamma - y
    obj2 = lambda pi, y : data['ybar'][i] + (alpha1/(1+alpha2*b))*(g-gbar) -(alpha2*h/(1+alpha2*b))*(pi-pibar) -(alpha3/(1+alpha2*b))*(tau-taubar) + (1/(1+alpha2*b)*d_vektor[i] - y)
    obj  = lambda x : [obj1(x[0],x[1]),obj2(x[0],x[1])]
    sol = optimize.root(obj,x0= [pibar,ybar])
    løsning = {y:sol.x[1],pi:sol.x[0]}

    gab.append(løsning[y]-data['ybar'][i])

    #risk of crash.
    if gab[i] > 0:
        #c = np.random.choice(a=[0,100],p=(1-(gab[i]**2)*0.177,(gab[i]**2)*0.177)) Alternative crash, size-dependent on output-gap
        c = np.random.choice(a=[0,-løsning[y]*rho],p=(1-p,p))
        crises.append(c)
    else:
        c=0

#Appending realisations of the model:
    pi_løsninger.append(løsning[pi])
    y_løsninger.append(løsning[y])
    pi_exp = løsning[pi]

data['y_løs'] = pd.DataFrame(y_løsninger)
data['pi_løs'] = pd.DataFrame(pi_løsninger)

#Calculating inflationary- and output-gaps:
outputgab = [0]
for i in range(1,len(data['y_løs'])):
    outputgab.append(data['y_løs'][i] - data['ybar'][i])

inflationgab = [0]
for i in range(1,len(data['pi_løs'])):
    inflationgab.append(data['pi_løs'][i] - pibar)

data['outputgab'] = outputgab
data['inflationgab'] = inflationgab

```

```python
#Setting up the figure:
fig = plt.figure(figsize=(16,12), frameon=False)
title_font = {'size':'16', 'color':'black', 'weight':'bold',
              'verticalalignment':'bottom'}
plt.title('AS-AD - Short Run Simulation',**title_font)
plt.axis('off')

#Figure 1
ax = fig.add_subplot(4,1,(1,2))
plt.title('Levels',fontsize=15,weight='normal',pad=-20)
plt.plot(data['y_løs'][5:], label = '$y$ (left axis)',c=goldman_blue)
plt.plot(data['ybar'][5:], label = '$\overline{y}$ (left axis)', c='lightgrey', linestyle='--')
plt.ylabel('$y$')
legend1 = plt.legend(loc = 'upper left', frameon=False)

ax2 = ax.twinx()
plt.plot(data['pi_løs'][5:], label = '$\pi$ (right axis)',c='tomato')
plt.tick_params()
plt.ylabel('$\pi$', )
legend2 = plt.legend(loc = 'upper left', frameon=False,bbox_to_anchor=(0,0.885))
ax.add_artist(legend1)
ax2.set_ylim(1,3)

#Figure 2:
ax = fig.add_subplot(4,1,(3,4))
plt.title('Gaps',fontsize=15,weight='normal',pad=-20)
plt.plot(data['outputgab'][5:], label = 'Output gap: $y_t - \overline{y}$',c=goldman_blue)
plt.plot(data['inflationgab'][5:], label = 'Inflationary gap: $\pi_t - \overline{\pi}$',c='tomato')
ax.axhline(0,linestyle='--',c='black')
plt.legend(frameon=False,loc='upper left',bbox_to_anchor=(0,1))
plt.xlabel('Time')

plt.savefig('Simulation.pdf')
print(len(v_vektor),len(y_løsninger),len(data['y_løs']))
```

```python
#printing descriptive statistics of the second graph.
print('The mean of the inflationary gap')
print(data['inflationgab'].mean())
print('The standard deviation of inflationary gap')
print(data['inflationgab'].std())
print('The mean of the output gap')
print(data['outputgab'].mean())
print('The standard deviation of output gap')
print(data['outputgab'].std())

#correlation between inflationary gap and output gap:
#correlation = data['outputgab'].corr(data['inflationgab'])

```

    The mean of the inflationary gap
    0.06377045294937161
    The standard deviation of inflationary gap
    0.10493075628627888
    The mean of the output gap
    0.0050212492615558865
    The standard deviation of output gap
    0.09403345656070142

```python
from scipy.stats import pearsonr
data1=data.copy()
data1.dropna(inplace=True)
print('The correlation between the two gaps are:')
print(pearsonr(data1['outputgab'],data1['inflationgab'])[0])
```

    The correlation between the two gaps are:
    -0.7291005797863483

## Interpretation

- First of all, we see a relatively realistic cycle - although with unrealistically sudden one-period crashes. It is seen, that nominal GDP, $y_t$, tends to grow slightly faster than its structural counterpart, $\overline{y}$, however being neutralized by the bubble-bursts regularly.

- Secondly, we see a relatively stable inflation between 1,8 and 2,2 pct. (the target rate being 2), due to a relatively inflationary-conservative Central Bank. We opted for one of the heavy-weights, Paul Volcker.

- Thirdly, we see very - but not perfectly(!) - inversely correlated inflation and output. This is due to the supply-chocks hitting the economy harder (with a perfectly inversely correlated change between inflation and output, of which the relative sizes are decided by the slope of the AD-curve, as decied by the Central Banks trade-off in the Taylor-rule). Demand-chocks can to a certain degree be perfectly smoothed out by the Central Bank through monetary policy, which is why demand-chocks, although present, not affect the economy much. This is, however, the reason for a not *perfect* inverse correlation between the two variables.

- Fourth, this iteration has resulted in two large bubble-bursts over the twenty-year period, both of which result in a 20 pct. cut in output, followed by steady convergence back towards the trend.

- Fifth, the economy seems to grow around $ln(1,8) \approx 58$ pct. over the period of 1000 weeks, around 20 years, equivalent of $1,58^{\frac{1}{20}} - 1 \approx 2,3$ pct. per year. This seems realistic and consistent with other empirical findings in Western world.

- From the descriptive statistics we lastly conclude that the correlation between the inflationary and output gap is negative, which correpsonds with the graph and is based on the definition of the demand.

## Conclusion

The model has resulted in a relatively realistically-looking simulation of real-world business-cycles, however having its flaws, some of which have been mentioned througout the text.

```python

```


# Estimation of Temperature and Pressure of a Constant Volume Propane-Oxygen Mixture

A recent project required a first-order approximation to determine if an explosive gas mixture would result in a tank rupture.  The following analysis done in Python follows Coopers analysis [[1]]  It provides a reasonable approximation, however it is sensitive to the chemical reaction hieratchy assumed.

This post is based on an initial analysis of an article in [Inspire 12](https://en.wikipedia.org/wiki/Inspire_(magazine)).  I used the [pint](https://pint.readthedocs.io/en/latest/) library to provide [dimensional analysis](https://en.wikipedia.org/wiki/Dimensional_analysis) where I could.  A second post on this topic will be written where I used the [Cantera](https://cantera.org/) and [SDToolbox](http://shepherd.caltech.edu/EDL/PublicResources/sdt/) libraries to perform a more in depth analysis.

To start this analysis let's load the necessary Python libraries and some formating,

```python
# Setup for calcuations
import pint
u = pint.UnitRegistry()
u.default_format = '~P'
from prettytable import PrettyTable
from IPython.display import display, Math
from sympy import *
init_printing(use_unicode=True)
from sympy import *
init_printing(use_unicode=True)
from numpy import linspace
from sympy import lambdify
import matplotlib.pyplot as plt
%matplotlib inline

from IPython.display import set_matplotlib_formats
set_matplotlib_formats('pdf', 'png')
plt.rcParams['savefig.dpi'] = 75

plt.rcParams['figure.autolayout'] = False
plt.rcParams['figure.figsize'] = 1.61803398875*8, 8
plt.rcParams['axes.labelsize'] = 20
plt.rcParams['axes.titlesize'] = 22
plt.rcParams['font.size'] = 18
plt.rcParams['lines.linewidth'] = 2.0
plt.rcParams['lines.markersize'] = 8
plt.rcParams['legend.fontsize'] = 16

plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.weight'] = 'regular'
plt.rcParams['mathtext.fontset'] = 'dejavuserif'
```

## Volume of Standard 20 lb Propane Tank

To start this analysis we need to calculate the volume of a 20 lb propane tank.  Using the weight of water in a 20 lb propane tank, $wt_{H_{2}O}= 21.6\:kg$ and the density of water, $\rho_{H_{2}}= 1000\:\frac{kg}{m^3}$ we can calculate the volume of the tank,

```python
wt_water = 21.6*u.kilogram
rho_water = 1000*u.kilogram/u.meter**3
v_tank = wt_water/rho_water
display(Math((r"V_{{tank}} = %s" % latex(v_tank))))
```

$\displaystyle V_{{tank}} = 0.0216 m³$

## Explosive Gas Mixture Recipe

The sections of the article relevant to the preparation of the gas mixture are:

- Discharge gas from the propane tank until only, $P_{C_3H_8} = 4\:bar$, are left in it.
- Insert, $P_{O_2}= 9\:bar$

Therefore, the partial pressure of propane is,

```python
p_c3h8 = 4*u.bar
p_c3h8.ito(u.pascal)
r = 8.314*(u.meter**3*u.pascal)/(u.mole*u.kelvin)
t_c3h8 = 293.2*u.kelvin
p_c3h8.ito(u.kilopascal)
display(Math((r"P_{{C_3H_8}} = %s" % latex(p_c3h8))))
p_c3h8.ito(u.pascal)

```

$\displaystyle P_{{C_3H_8}} = 400.0 kPa$

Using the ideal gas law, the weight and moles of propane are,

```python
n_c3h8 = (p_c3h8*v_tank)/(r*t_c3h8)
mw_c3h8 = 44.0956*u.gram/u.mole
wt_c3h8 = n_c3h8*mw_c3h8
wt_c3h8.ito(u.pound)
display(Math((r"W_{{C_3H_8}} = {:3.3}\:lb".format(latex(wt_c3h8)))))
display(Math((r"n_{{C_3H_8}} = {:3.3}\:mol".format(latex(n_c3h8)))))
```

$\displaystyle W_{C_3H_8} = 0.3\:lb$

$\displaystyle n_{C_3H_8} = 3.5\:mol$

The partial pressure of the oxygen would be,

```python
p_o2 = 9*u.bar
p_o2.ito(u.pascal)
r = 8.314*(u.meter**3*u.pascal)/(u.mole*u.kelvin)
t_o2 = 293.2*u.kelvin
p_o2.ito(u.kilopascal)
display(Math((r"P_{{O_2}} = %s" % latex(p_o2))))
p_o2.ito(u.pascal)
```

$\displaystyle P_{{O_2}} = 900.0 kPa$

Again using the ideal gas law the weight and moles of oxygen are,

```python
n_o2 = (p_o2*v_tank)/(r*t_o2)
mw_o2 = 31.9988*u.gram/u.mole
wt_o2 = n_o2*mw_o2
wt_o2.ito(u.pound)
display(Math((r"W_{{O_2}} = {:3.3}".format(latex(wt_o2)))))
display(Math((r"n_{{O_2}} = {:3.3}".format(latex(n_o2)))))
```

$\displaystyle W_{O_2} = 0.5$

$\displaystyle n_{O_2} = 7.9$

The total gas mixture weight is,

```python
wt_tot = wt_c3h8 + wt_o2
display(Math((r"W_{{tot}} = %s" % latex(wt_tot))))
wt_tot.ito(u.grams)
```

$\displaystyle W_{{tot}} = 0.9071511924432267 lb$

## Chemical Reaction
The CHNO reaction hieratchy is,

1. All carbon is burned to CO.
2. All the hydrogen is burned to H2O.
3. Any oxygen left after CO and H2O formation burns CO to CO2. 4. All the nitrogen forms N2.
5. Any oxygen remaining forms O2.
6. Any hydrogen remaining form H2.
7. Any carbon reamaining forms C.

For our reaction substituting the moles of propane and oxygen we have,

$$3.544C_3H_8 + 7.975O_2 → 10.623CO + 5.327H_2O +  + 8.849H_2$$

Normalizing on the number of moles of propane we have,

$$C_3H_8 + 2.25O_2 → 1.5H_2O + 3CO + 2.5H_2$$

Leaving 2.5 moles of hydrogen unreacted for every mole of propane burned or a fuel rich reaction. Once the tank has ruptured this free hydrogen and the carbon monoxide will react with the oxygen in the air.

## Heat Produced

Because we are interested in the the pressure and temperature inside the tank we will ignore that it will eventually rupture and that the excess hydrogen will be consumed in the afterburn. In this case the heat evolved is equal to the heat of explosion of the propane.

$$ΔH_{expo} = ΣΔH_{fo}(products) - ΔH_{fo}(reactants)$$

From the National Institute of Standards and Testing (NIST) we have the enthalpy of formations for the products and reactants,

```python
# Enthalpy of Formation from NIST
d_H_c3h8 = -2354*u.joule/u.gram
d_H_h2o  = -13420*u.joule/u.gram
d_H_co   = -3945*u.joule/u.gram

# Molecular Weights from NIST
mw_h2o = 18.0153*u.gram/u.mole
mw_co  = 28.0101*u.gram/u.mole

# Moles of Reactants
n_h2o = 5.327*u.moles
n_co = 10.623*u.moles
n_h2 = 8.849*u.moles

pt = PrettyTable()

print_mole_c3h8 = f"{n_c3h8:0.2f}"
print_mole_h2o = f"{n_h2o:0.2f}"
print_mole_co = f"{n_co:0.2f}"
print_H_c3h8 = f"{d_H_c3h8*mw_c3h8:0.2f}"
print_H_h2o = f"{d_H_h2o*mw_h2o:0.2f}"
print_H_co = f"{d_H_co*mw_co:0.2f}"

pt.field_names = ["Compound","Hf - J/g", "MW - g/mol", "Hf - J/mol", "n"]
pt.add_row(["Propane", d_H_c3h8, mw_c3h8, print_H_c3h8, print_mole_c3h8])
pt.add_row(["Water", d_H_h2o, mw_h2o, print_H_h2o, print_mole_h2o])
pt.add_row(["Carbon Monoxide", d_H_co, mw_co, print_H_co, print_mole_co])

print(pt)
```

    +-----------------+--------------+---------------+-------------------------+------------+
    |     Compound    |   Hf - J/g   |   MW - g/mol  |        Hf - J/mol       |     n      |
    +-----------------+--------------+---------------+-------------------------+------------+
    |     Propane     | -2354.0 J/g  | 44.0956 g/mol | -103801.04 joule / mole | 3.54 mole  |
    |      Water      | -13420.0 J/g | 18.0153 g/mol | -241765.33 joule / mole | 5.33 mole  |
    | Carbon Monoxide | -3945.0 J/g  | 28.0101 g/mol | -110499.84 joule / mole | 10.62 mole |
    +-----------------+--------------+---------------+-------------------------+------------+

Calculating the heat of explosion we have,

```python
d_H_exp = (n_h2o*d_H_h2o*mw_h2o + n_co*d_H_co*mw_co)-n_c3h8*d_H_c3h8*mw_c3h8
display(Math(r'\Delta H_{{exp}} = {:.02f~P}'.format(d_H_exp)))
```

$\displaystyle \Delta H_{exp} = -2093813.84 J$

## TNT Equivalency
We we can equate the heat of detonation of propane to the heat of detonation of TNT to detrmine a TNT equivalency. We can get the experimentally derived heat of detonation of TNT from Cooper (page 132) which is,

```python
d_H_tnt = -247500*u.calorie/u.mole
d_H_tnt.ito(u.joules/u.mole)
display(Math(r'\Delta H_{{TNT}} = {:.02f~P}'.format(d_H_tnt)))
```

$\displaystyle \Delta H_{TNT} = -1035540.00 J/mol$

The molecular weight of TNT is,

```python
mw_tnt = 227.1*u.gram/u.mole
display(Math(r'MW_{{TNT}} = {:.02f~P}'.format(mw_tnt)))
```

$\displaystyle MW_{TNT} = 227.10 g/mol$

Dividing by the heat of detonation for TNT by the molecular weight we have,

```python
d_h_tnt = d_H_tnt/mw_tnt
display(Math(r'\Delta h_{{TNT}} = {:.02f~P}'.format(d_h_tnt)))
```

$\displaystyle \Delta h_{TNT} = -4559.84 J/g$

If we divide the heat of exposion of propane calculated above by the weight of the propane-oxygen mixture we have,

```python
d_h_exp = d_H_exp/wt_tot
display(Math(r'\Delta h_{{exp}} = {:.02f~P}'.format(d_h_exp)))
```

$\displaystyle \Delta h_{exp} = -5088.53 J/g$

So we can estimate the TNT equivalency by dividing the specific heat of explosion of propane by the specific heat of detonation of TNT or,

```python
tnt_eqv = d_h_exp/d_h_tnt
display(Math(r'E_{{\Delta h}} = {:.02f~P}'.format(tnt_eqv)))
```

$\displaystyle E_{\Delta h} = 1.12$

So theroretically, the propane-oxygen mixture is 1.12 times more powerful than TNT, however, the hierarchy of products plays a significant role in the amount of energy calculated to be released. Depending on the method used errors as large as $\pm 30\%$ can be observed as compared to experimentally determined values (Cooper pg 132). Using this margin of error a more realistic value for the TNT equivalency would be $1.12 \pm 0.34$.

## Temperature of the Gases

A simple method of determining the temperature of the gases inside the tank, $T_V$, is to use,

$$T_V = T_a\lambda$$

where, $T_a$ is the adiabatic flame temperature and $\lambda$ is the ratio of the specific heats of the gases, $\lambda=\frac{C_P}{C_V}$.  The adiabatic flame temperature can be found from,

$$Q=n\int_{T_0}^{T_a} \! C_P \, \mathrm{d}T$$

where $C_P$ is the average specific heat of the combustion gases at constant pressure, $n$ is the number of moles of combution gases. The average heat capactity, $C_P$, of the gases can be calculated using,

$$C_P=\sum_{i=n}^{1}\left(n_i\cdot C_{Pi}\right)$$

To calcualte the mole fractions of the product gases we first need the total moles of products,

```python
N_prod = n_h2o+n_co+n_h2
display(Math(r'n_{{total}} = {:.02f~P}'.format(N_prod)))
```

$\displaystyle n_{total} = 24.80 mol$

the mole fractions are then,

```python
n_frac_co = n_co/N_prod
n_frac_h2o = n_h2o/N_prod
n_frac_h2 = n_h2/N_prod
display(Math(r'X_{{CO}} = {:.02f~P}'.format(n_frac_co)))
display(Math(r'X_{{H_2O}} = {:.02f~P}'.format(n_frac_h2o)))
display(Math(r'X_{{H_2}} = {:.02f~P}'.format(n_frac_h2)))
```

$\displaystyle X_{CO} = 0.43$

$\displaystyle X_{H_2O} = 0.21$

$\displaystyle X_{H_2} = 0.36$

From Cooper Table 8.2 the molar heat capacities of carbon monoxide, water, and hydrogen are,

```python
T= symbols('T', positive = True)
Ta= symbols('Ta', positive = True, real = True)
C_P_co = Function('C_P_co')('T')
C_P_h2o = Function('C_P_h2o')('T')
C_P_h2 = Function('C_P_h2')('T')

C_P_co  = 6.350 + 1.811e-3*T - 0.2675e-6*T**2
C_P_h2o = 7.136 + 2.640e-3*T + 0.0459e-6*T**2
C_P_h2  = 6.946 - 0.196e-3*T + 0.4757e-6*T**2
display(Math((r"C_{{P-CO}} = %s" % latex(C_P_co))))
display(Math((r"C_{{P-H_2O}} = %s" % latex(C_P_h2o))))
display(Math((r"C_{{P-H2}} = %s" % latex(C_P_h2))))
```

$\displaystyle C_{{P-CO}} = - 2.675 \cdot 10^{-7} T^{2} + 0.001811 T + 6.35$

$\displaystyle C_{{P-H_2O}} = 4.59 \cdot 10^{-8} T^{2} + 0.00264 T + 7.136$

$\displaystyle C_{{P-H2}} = 4.757 \cdot 10^{-7} T^{2} - 0.000196 T + 6.946$

Multiplying through by the mole fraction and summing we have the average specific heat at constant pressure for carbon monoxide, water, and hydrogen,

```python
c_p = (n_frac_co.magnitude)*(C_P_co) + (n_frac_h2o.magnitude)*(C_P_h2o) + (n_frac_h2.magnitude)*(C_P_h2)
c_p

display(Math((r"C_{{P-avg}} = %s" % latex(c_p))))
```

$\displaystyle C_{{P-avg}} = 6.50157707972096 \cdot 10^{-8} T^{2} + 0.00127291943223517 T + 6.73150836727287$

Changing the units of the $\Delta H_{exp}$ to calories and integrating from $298\:K$ to $T_a$ we have,

$$Q=n\int_{T_0}^{T_a} \! C_P \, \mathrm{d}T$$

```python
d_H_exp.ito(u.calorie)
q = N_prod*integrate(c_p, (T, 298, Ta))

display(Math(r'\Delta H_{{exp}} = Q = {:.02f~P}'.format(d_H_exp)))
display(Math((r"Q = %s" % latex(q.magnitude))))
```

$\displaystyle \Delta H_{exp} = Q = -500433.52 cal$

$\displaystyle Q = 5.37442033333333 \cdot 10^{-7} Ta^{3} + 0.0157835645 Ta^{2} + 166.934676 Ta - 51162.3997565518$

The only thing we don't know in the equation is $T_a$, so solving for $T_a$ we have,

```python
sol = solve(q.magnitude + d_H_exp.magnitude, Ta)[0]*u.K
display(Math((r"T_a = {:6.6} \:K".format(latex(sol)))))
```

$\displaystyle T_a = 2605.4 \:K$

Plotting $Q$ vs. $T_a$ we have,

```python
sol = solve(q.magnitude + d_H_exp.magnitude, Ta)[0] # this was added because there are no units.
r = [sol, d_H_exp.magnitude]

lam_x = lambdify(Ta, q.magnitude, modules=['numpy'])

x_vals = linspace(0, 3000, 100)
y_vals = lam_x(x_vals)/1000

plt.plot(y_vals, x_vals, -d_H_exp.magnitude/1000, sol, 'ro')
plt.axhline(sol, color = 'red', linestyle = ':')
plt.axvline(-d_H_exp.magnitude/1000, color = 'red', linestyle = ':')

arrowprops = dict(
    arrowstyle = "-|>",
    connectionstyle = "angle, angleB=45,rad=10")
offset = 400
plt.annotate('$(%.1f, %.1f)$'%(-d_H_exp.magnitude/1000, sol), (-d_H_exp.magnitude/1000, sol),
             xytext=(350, 1500), arrowprops=arrowprops)

plt.ylabel(r"$T_a (K)$")
plt.xlabel("Q (kcal)")
plt.show()
```

Now we can use ratio of specific heats, $\lambda$, for the correction to constant volume. We must calculate $\lambda$ for this mixture of gases using,

$$\lambda_{avg} = \sum_i n_i\cdot \lambda_i$$

where $n_i$ is the mole fraction and $\lambda$ is the ratio of specific heats for each product.

```python
lambda_h2o = 1.324
lambda_co = 1.404
lambda_h2 = 1.410
lambda_avg = n_frac_co*lambda_co + n_frac_h2*lambda_h2 + n_frac_h2o*lambda_h2o
display(Math((r"\lambda_{{avg}} = {:3.3}".format(latex(lambda_avg)))))
```

$\displaystyle \lambda_{avg} = 1.3$

The temperature a constant volume is given by,

$$T_v = T_a\cdot \lambda_{avg}$$

```python
T_v = sol*lambda_avg
display(Math((r"T_{{V}} = {:6.6} \:K".format(latex(T_v*u.K)))))
```

$\displaystyle T_{V} = 3618.8 \:K$

## Pressure of Gases
Assuming pressures above 200 atm we can no longer use the ideal gas law equation of state (EOS). A common equation of state for pressures above 200 atm and commonly used in the field of interior balistics is the Noble-Able EOS:

$$P\left(V-0.025\cdot N_{{products}}\right)=0.0821\cdot N_{{products}}\cdot T$$

```python
v_tank.ito(u.liter)
P_v = (0.0821*N_prod.magnitude*T_v)/(v_tank.magnitude-0.025*N_prod.magnitude)
display(Math((r"P = {:5.5} \:atm \:(5158.28\:psi)".format(latex(P_v)))))
```

$\displaystyle P = 351.1 \:atm \:(5158.28\:psi)$

This is almost 5 times the burst pressure of a standard 20lb propane tank and would cause the tank to fail catostrophically.

## References

1.  P. Cooper, Exlosives Engineering. New York, NY: Wiley-VCH Verlag GmbH Co., 1996.


# Introduction to ML using ML4H

## Prerequisites
- Basic comfort with python, some linear algebra, some data science
- Follow the instructions in the main [README](https://github.com/broadinstitute/ml4h) for installing ML4H
- Now we are ready to teach the machines!

```python
# Imports
import os
import sys
import pickle
import random
from typing import List, Dict, Callable
from collections import defaultdict, Counter

import csv
import gzip
import h5py
import shutil
import zipfile
import pydicom
import numpy as np

from keras import metrics
from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten

%matplotlib inline
import matplotlib.pyplot as plt
from matplotlib import gridspec
```

### Linear Regression
We explore machine learning on Bio medical data using Cloud computing, Python, Tensorflow, and the ml4h codebase.

We will start with linear regression.  Our model is a vector, one weight for each input feature, and a single bias weight.

\begin{equation}
y = xw + b
\end{equation}

For notational convenience absorb the bias term into the weight vector by adding a 1 to the input data matrix $X$

\begin{equation}
y = [\textbf{1}, X][b, \textbf{w}]^T
\end{equation}

#### The Dense Layer is Matrix (Tensor) Multiplication

```python
def linear_regression():
    samples = 40
    real_weight = 2.0
    real_bias = 0.5
    x = np.linspace(-1, 1, samples)
    y = real_weight*x + real_bias + (np.random.randn(*x.shape) * 0.1)

    linear_model = Sequential()
    linear_model.add(Dense(1, input_dim=1))
    linear_model.compile(loss='mean_squared_error', optimizer='sgd')
    linear_model.summary()
    linear_model.fit(x, y, batch_size=1, epochs=6)

    learned_slope = linear_model.get_weights()[0][0][0]
    learned_bias = linear_model.get_weights()[1][0]
    print('Learned slope:',  learned_slope, 'real slope:', real_weight, 'learned bias:', learned_bias, 'real bias:', real_bias)

    plt.plot(x, y)
    plt.plot([-1,1], [-learned_slope+learned_bias, learned_slope+learned_bias], 'r')
    plt.show()
    print('Linear Regression complete!')
```

```python
linear_regression()
```

## Now Logistic Regression:
We take the real-valued predictions from linear regression and squish them with a sigmoid.

\begin{equation}
\textbf{y} = \sigma(X\textbf{w} + b)
\end{equation}

where
\begin{equation}
\sigma(x) = \frac{e^x}{1+e^x} = \frac{1}{1+e^{-x}}
\end{equation}

```python
def sigmoid(x):
    a = []
    for item in x:
        a.append(np.exp(item)/(1+np.exp(item)))
    return a

x = np.arange(-10., 10., 0.2)
sig = sigmoid(x)
plt.plot(x,sig)
plt.show()
```

```python
def logistic_regression(epochs = 600, num_labels = 10):
    train, test, valid = load_data('mnist.pkl.gz')

    train_y = make_one_hot(train[1], num_labels)
    valid_y = make_one_hot(valid[1], num_labels)
    test_y = make_one_hot(test[1], num_labels)

    logistic_model = Sequential()
    logistic_model.add(Dense(num_labels, activation='softmax', input_dim=784, name='mnist_templates'))
    logistic_model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
    logistic_model.summary()

    templates = logistic_model.layers[0].get_weights()[0]
    plot_templates(templates, 0)
    print('weights shape:', templates.shape)

    for e in range(epochs):
        trainidx = random.sample(range(0, train[0].shape[0]), 8192)
        x_batch = train[0][trainidx,:]
        y_batch = train_y[trainidx]
        logistic_model.train_on_batch(x_batch, y_batch)
        if e % 100 == 0:
            plot_templates(logistic_model.layers[0].get_weights()[0], e)
            print('Logistic Model test set loss and accuracy:', logistic_model.evaluate(test[0], test_y), 'at epoch', e)

def plot_templates(templates, epoch):
    n = 10
    templates = templates.reshape((28,28,n))
    plt.figure(figsize=(16, 8))
    for i in range(n):
        ax = plt.subplot(2, 5, i+1)
        plt.imshow(templates[:, :, i])
        plt.gray()
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

    plot_name = "./regression_example/mnist_templates_"+str(epoch)+".png"
    if not os.path.exists(os.path.dirname(plot_name)):
        os.makedirs(os.path.dirname(plot_name))
    plt.savefig(plot_name)

def make_one_hot(y, num_labels):
    ohy = np.zeros((len(y), num_labels))
    for i in range(0, len(y)):
        ohy[i][y[i]] = 1.0
    return ohy

def load_data(dataset):
    ''' Loads the dataset
    :param dataset: the path to the dataset (here MNIST)'''
    data_dir, data_file = os.path.split(dataset)
    if data_dir == "" and not os.path.isfile(dataset):
        # Check if dataset is in the data directory.
        new_path = os.path.join("data", dataset)
        if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz':
            dataset = new_path

    if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':
        from urllib.request import urlretrieve
        origin = ('http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz')
        print('Downloading data from %s' % origin)
        if not os.path.exists(os.path.dirname(dataset)):
            os.makedirs(os.path.dirname(dataset))
        urlretrieve(origin, dataset)

    print('loading data...')
    f = gzip.open(dataset, 'rb')
    if sys.version_info[0] == 3:
        u = pickle._Unpickler(f)
        u.encoding = 'latin1'
        train_set, valid_set, test_set = u.load()
    else:
        train_set, valid_set, test_set = pickle.load(f)
    f.close()

    return train_set, valid_set, test_set

def plot_mnist(sides):
    train, _, _ = load_data('mnist.pkl.gz')
    print(train[0].shape)
    mnist_images = train[0].reshape((-1, 28, 28, 1))
    sides = int(np.ceil(np.sqrt(min(sides, mnist_images.shape[0]))))
    _, axes = plt.subplots(sides, sides, figsize=(16, 16))
    for i in range(sides*sides):
        axes[i // sides, i % sides].imshow(mnist_images[i, ..., 0], cmap='gray')
```

# Look B4 U Learn!

```python
plot_mnist(25)
```

## Cross Entropy Loss:
Our favorite loss function for categorical data.
\begin{equation}
L(true, model) = -\sum_{x\in\mathcal{X}} true(x)\, \log model(x)
\end{equation}

Binary cross entropy with $N$ data points $x$ each with a binary label:
\begin{equation}
true(x) \in \{0, 1\} \\
L(true, model) = -\frac{1}{N}\sum^N_{i=1} true(x_i)\log(model(x_i)) + (1-true(x_i))log(1-model(x_i))
\end{equation}

This is the Kullback Leibler divergence between the true distribution and the predicted.
This function emerges in many fields as diverse as probability, information theory, and physics.
What is the information difference between the truth and our model?  How much data do I lose by replacing the truth with the model's predictions. What is the temperature difference between my predictions and the truth?!

Categorical cross entropy with $K$ different classes or labels:
\begin{equation}
true(x) \in \{0, 1, 2, ..., K\} \\
L(true, model) = -\frac{1}{N}\sum^N_{i=1}\sum^K_{j=1} y_{ik}\log(q_k(x_i)))
\end{equation}

```python
logistic_regression()
```

# Deep Models: "Hidden" Layers and The MultiLayerPerceptron

```python
def multilayer_perceptron():
    train, test, valid = load_data('mnist.pkl.gz')

    num_labels = 10
    train_y = make_one_hot(train[1], num_labels)
    valid_y = make_one_hot(valid[1], num_labels)
    test_y = make_one_hot(test[1], num_labels)

    mlp_model = Sequential()
    mlp_model.add(Dense(500, activation='relu', input_dim=784))
    mlp_model.add(Dense(num_labels, activation='softmax'))
    mlp_model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
    mlp_model.summary()
    mlp_model.fit(train[0], train_y, validation_data=(valid[0],valid_y), batch_size=32, epochs=3)
    print('Multilayer Perceptron trained. Test set loss and accuracy:', mlp_model.evaluate(test[0], test_y))

multilayer_perceptron()
```

# Convolutions Flip, Slide, Multiply, Add
Convolutions look for their kernel in a larger signal.

In convolution, you always and only find what you're looking with.

Convolution and cross correlation are deeply related:

\begin{equation}
f(t) \circledast g(t) \triangleq\ \int_{-\infty}^\infty f(\tau) g(t - \tau) \, d\tau. = \int_{-\infty}^\infty f(t-\tau) g(\tau)\, d\tau.
\end{equation}

```python
def convolutional_neural_network(filters=32, kernel_size=(3,3), padding='valid', num_labels = 10):
    train, test, valid = load_data('mnist.pkl.gz')

    train_y = make_one_hot(train[1], num_labels)
    valid_y = make_one_hot(valid[1], num_labels)
    test_y = make_one_hot(test[1], num_labels)

    print(train[0].shape)
    mnist_images = train[0].reshape((-1, 28, 28, 1))
    mnist_valid = valid[0].reshape((-1, 28, 28, 1))
    mnist_test = test[0].reshape((-1, 28, 28, 1))

    cnn_model = Sequential()
    cnn_model.add(Conv2D(input_shape=(28, 28, 1), filters=filters, kernel_size=kernel_size, padding=padding, activation='relu'))
    cnn_model.add(Conv2D(filters=filters, kernel_size=kernel_size, padding=padding, activation='relu'))
    cnn_model.add(Conv2D(filters=filters, kernel_size=kernel_size, padding=padding, activation='relu'))
    cnn_model.add(Flatten())
    cnn_model.add(Dense(16, activation='relu'))
    cnn_model.add(Dense(num_labels, activation='softmax'))
    cnn_model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
    cnn_model.summary()
    cnn_model.fit(mnist_images, train_y, validation_data=(mnist_valid, valid_y), batch_size=32, epochs=3)

    print('Convolutional Neural Network trained. Test set loss and accuracy:', cnn_model.evaluate(mnist_test, test_y))

convolutional_neural_network()
```

# Why (and When!) is Convolution Helpful?
- Decouples input size from model size
- Translationally Equivariant (Not Invariant), so we can find features wherever they might occur in the signal
- Local structure is often informative
- But not always! (eg Tabular data)


Водопьян А.О. Хабибуллин Р.А. 2019 г.

## Давление насыщения нефти газом

### Давление насыщения, корреляция Стендинга

Корреляция Стендинга для оценки давления насыщения нефти газом.

$$ P_b =  0.5197  \left( \frac{R_{sb}}{\gamma_g}\right)^{0.83} 10 ^{y_g} $$

где

$P_b$ - давление насыщения, $МПа$

$R_{sb}$ - газосодержание при давлении насыщения, $м^3/м^3 $

$\gamma_g$ - относительная плотность газа, безразмерная величина

$y_g$ - мольная доля газа, $ y_g = 1.225 +0.00164 T - \frac{ 1.769}{\gamma_o}$

$\gamma_o$ - относительная плотность нефти, безразмерная величина

$ T $ - температура, $ ^{\circ}\mathrm{K}$

Корреляции Standing базируются на 105 экспериментально определенных давлениях насыщения нефтяных систем Калифорнии. Диапазоны значений основных свойств, использованных для разработки данной корреляции, приведены в таблице ниже.

|
 Параметр  | Диапазон |
| :--- | :--- |
|
 давление насыщения,$P_b$ , $ МПа $  | 0.896…48.263  |
|
 температура, $^{\circ}\mathrm{K} $ | 310…400 |
|
 газосодержание при давлении насыщения, $R_{sb}$ , $м^3/м^3 $ | 3.6…254 |
|
 относительная плотность нефти по воде, $\gamma_o$ | 0.725…0.956  |
|
 относительная плотность газа, $\gamma_g$  | 0.59…0.95  |

ref "A Pressure-Volume-Temperature Correlation for Mixtures of California Oil and Gases", M.B. Standing, Drill. & Prod. Prac., API, 1947.

```python
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
from sympy import *
init_printing()
import sys
sys.path.append('../')
import uniflocpy.uPVT.PVT_correlations as PVT
```

```python
# Корреляция Стендинга для давления насыщения реализована
# в виде функции unf_pb_Standing_MPaa в модуле PVT_correlations.
# Подробные данные по функции включая исходный код приведены ниже

PVT.unf_pb_Standing_MPaa??
```

в приведеном коде использована коррекция значений давления насыщения при низких значениях газосодержания при давлении насыщения для обеспечения выхода на значение $P_b = 1$ при $R{sb} = 0$

---
Построим пару графиков, используя приведенную функцию

```python
# параметры определяющие диапазоны значений для построения графиков
rsb_set=np.arange(1,300,10)
t_set=np.arange(273,380,30)
t_set_def=np.array([313])
gg_set=np.arange(0.6,1,0.1)
gg_set_def=np.array([0.8])
go_set=np.arange(0.8,1,0.05)
go_set_def=np.array([0.86])
# функция для автоматизации построения графиков по давлению насыщения
def prep_plot(func,tset,goset,ggset,plot_title,plot_xlab,plot_ylab):
    for t in tset:
        for gg in ggset:
            for go in goset:
                pb_set=[]
                for rsb in rsb_set:
                    pb_set.append(func(rsb,t_K = t,gamma_gas = gg,gamma_oil = go))
                plt.plot(rsb_set, pb_set, label='t = %1.0f $ ^{\circ}\mathrm{K}$'%t +
                                                 ' $\gamma_g$ = %1.2f'%gg +
                                                 ' $\gamma_o$ = %1.2f'%go)
    plt.title(plot_title)
    plt.ylabel(plot_ylab, color = 'black')
    plt.xlabel(plot_xlab, color = 'black')
    plt.legend()

```

```python
# код для построения графиков
plt.figure(figsize=(15,8))
f = PVT.unf_pb_Standing_MPaa
# рисуем первый график
plt.subplot(221)
prep_plot(f,t_set,go_set_def,gg_set_def,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')
plt.grid()
# рисуем второй график
plt.subplot(222)
prep_plot(f,t_set_def,go_set,gg_set_def,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')
plt.grid()
# рисуем третий график
plt.subplot(223)
prep_plot(f,t_set_def,go_set_def,gg_set,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25, wspace=0.35)
# рисуем все
plt.grid()
plt.show()

```

---

### Давление насыщения, корреляция Valko McCain

Корреляция Valco McCain (2003) для оценки давления насыщения нефти газом разработана на основе банка данных нефтей со всего мира. На рисунке показаны источники данных, которые были использованы авторами для настройки корреляции.

Диапазоны значений основных свойств (1745 значений), использованных для разработки данной корреляции, приведены в таблице ниже.

|
 Параметр  | Мин|Среднее|Макс|
| :--- | :---: |:---:|:---:|
|
 давление насыщения,$P_b$ , $ МПа $  | 0.55  |15.0|45.5|
|
 температура, $^{\circ}\mathrm{С} $ | 15 |85|172|
|
 газосодержание при давлении насыщения, $R_{sb}$ , $м^3/м^3 $ | 2 |104|395|
|
 относительная плотность нефти по воде, $\gamma_o$ | 0.724  |0.846|1.02|
|
 относительная плотность газа на сепараторе, $\gamma_g$  | 0.555  |0.838|1.685|

По результатам сравнений с замеренными значениями абсолютная значение средней относительной ошибки (AARE) для корреляции составляет около 11%. Авторы отмечают, что полученная точность соответствует точности замеров использованных для построения корреляции и для построения более точных зависимостей потребуется сбор новых данных с повышенной точностью.

$$
ln P_b = 7.475 + 0.713 z + 0.0075 z^2
$$
где

$$
z = z_1+z_2+z_3+z_4
$$
$$
z_1 = -5.48 - 0.0375\cdot ln R_{sb}+0.281\cdot (ln R_{sb})^2 - 0.0206\cdot (ln R_{sb})^3
$$
$$
z_2 = 1.27 - 0.0449\cdot API +4.36 \cdot 10^{-4} API^2 -4.76 \cdot 10^{-6} API^3
$$
$$
z_3 = 4.51 - 10.84 \cdot \gamma_{gSP} +8.39\cdot \gamma_{gSP}^2 -2.34\cdot \gamma_{gSP}^3
$$
$$
z_4 = -0.7835 + 6.23 \cdot 10^{-3} \cdot T_R - 1.22 \cdot 10^{-5} \cdot T_R^2+ 1.03 \cdot 10^{-8} \cdot T_R^3
$$

где

* $p_b$ - давление насыщения, $psia$
* $R_{sb}$ - газосодержание при давлении насыщения, ${scf}/{STB}$
* $\gamma_{gSP}$ - удельная плотность газа, отобранного на сепараторе, безразмерная величина
* $T_R$ - пластовая температура, $F$

ref Reservoir oil bubblepoint pressures revisited; solution gas-oil ratios and surface gas specific gravities. P.P.Valko, W.D.McCain Jr. Journal of petroleum science and engineering 37(2003) 153-169

---
#### Пребразование единиц измерения для корреляции Валко Маккейна

```python
# объявления переменных необходимых для преобразования единиц в вырожении
rsb_scfSTB, rsb_m3m3 = symbols('R_sb[scfSTB] R_sb[m3m3]')
API, gamma_o = symbols('API gamma_o')
gamma_gSP = symbols('gamma_gSP')
T_RF,T_RK = symbols('T_R[F] T_R[K]')
z,z1,z2,z3,z4 = symbols('z,z1,z2,z3,z4')
p_bpsia, p_bMPaa = symbols('p_b[psia],p_b[MPaa]')
```

```python
# определение алгоритма расчета в американских промысловых единицах
eq1 = Eq(z,z1+z2+z3+z4)
eq2 = Eq(z1,  -5.48 - 0.03758 * ln(rsb_scfSTB)+ 0.281*  ln(rsb_scfSTB)**2 - 0.0206* ln(rsb_scfSTB)**3)
eq3 = Eq(z2,  1.27 - 0.0449* API +4.36 * 10**-4 *API**2 -4.76 * 10**-6 *API**3)
eq4 = Eq(z3,  4.51- 10.84 *gamma_gSP +8.39*gamma_gSP**2 -2.34*gamma_gSP**3 )
eq5 = Eq(z4,  -0.7835 + 6.23 * 10**-3 * T_RF - 1.22 * 10**-5 * T_RF**2+ 1.03 * 10**-8 * T_RF**3)
eq6 =Eq(ln(p_bpsia),(7.475 + 0.713 * z + 0.0075 * z**2))
# покажем выражения в печатном виде
display(eq6)
display(eq1)
display(eq2)
display(eq3)
display(eq4)
display(eq5)
```

```python
# выражения для преобразования единиц измерения из американских промысловых в практические метрические
scfSTB_to_m3m3 = rsb_m3m3/0.178107606679035
API_to_gamma_o = 141.5/gamma_o-131.5
F_to_K = T_RK*9/5-459.67
psi_to_MPa = p_bMPaa * 14.6959 * 10.1325
# покажем выражения в печатном виде
display(Eq(rsb_scfSTB , scfSTB_to_m3m3))
display(Eq(API,API_to_gamma_o))
display(Eq(T_RF,F_to_K))
display(Eq(p_bpsia,psi_to_MPa))
```

```python
# преобразование алгоритма в метрические единицы с использованием символьных вычислений
eq2_m=simplify(eq2.subs(rsb_scfSTB,scfSTB_to_m3m3))
eq3_m=simplify(eq3.subs(API,API_to_gamma_o))
eq5_m=simplify(eq5.subs(T_RF,F_to_K))
eq6_m=eq6.subs(p_bpsia, psi_to_MPa)
eq8=solve(eq6_m,p_bMPaa)
eq9=Eq(p_bMPaa, eq8[0])
# вывод результатов преобразований
display(eq9)
display(eq1)
display(eq2_m)
display(eq3_m)
display(eq4)
display(eq5_m)
```

```python
# расчет реализован в функции  unf_pb_Valko_MPaa
PVT.unf_pb_Valko_MPaa??
```

```python
plt.figure(figsize=(15,8))
f = PVT.unf_pb_Valko_MPaa
# рисуем первый график
plt.subplot(221)
prep_plot(f,t_set,go_set_def,gg_set_def,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')
plt.grid()
# рисуем второй график
plt.subplot(222)
prep_plot(f,t_set_def,go_set,gg_set_def,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')
plt.grid()
# рисуем третий график
plt.subplot(223)
prep_plot(f,t_set_def,go_set_def,gg_set,
              'Давление насыщения от газосодержания',
               '$R_{sb}, м^3/м^3$',
               '$P_b, MPa$')

plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25, wspace=0.35)
# рисуем все
plt.grid()
plt.show()
```

в приведеном коде использована коррекция значений давления насыщения при низких значениях газосодержания при давлении насыщения для обеспечения выхода на значение $P_b = 1$ при $R{sb} = 0$ и при больших значениях газосодержания

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

```python
PVT.unf_pb_AlMarhoun_MPaa??
```

```python

```

notebooks/uPVT. Bubble point pressure.ipynb


## Quantum Anomaly Detection with Quantum Density Estimation

### Density Estimation with Qubits

### Qiskit Hackathon Global 2021

---

**Group: Quantum Mind UN**

Diego Useche - diusecher@unal.edu.co

Oscar Alberto Bustos B. - oabustosb@unal.edu.co

**The problem:**

Anomaly Detection:
A common need when analyzing datasets is to find which observations deviates considerably from others. Such instances are known as anomalies or outliers, and the goal of anomaly detection (AD) is to detect those atypical or irregular data, generally in unsupervised scenarios. AD has many applications across a wide variety of domains, including intrusion or fraud detection in cybersecurity, industrial monitoring, disease detection for medical diagnosis and rare event detection in science.

Can we build a quantum circuit to perform Anomaly Detection?

**Our Approach**

Anomaly Detection based on Density Estimation:

*   Use normal data to induce a probability density function (pdf) (Density Estimation)
*   Use the pdf to estimate the density of new samples. If the density is below a threshold, then classify it as anomaly.

Quantum Density Estimation:

*   Combine Density Matrices with RFF [Rahimi2007] to perform Density Estimation [González2021]
*   Propose a quantum circuit to estimate expected values of density matrices.

**Main contributions**

*   We developed a new method for Anomaly Detection in quantum computers.
*   We implemented the method in a quantum circuit and showed that it’s a feasible approach.
*   Most quantum machine learning algorithms are based on pure states. Our
algorithm can work with mixed states and pure states.
*   We develop a new method to calculate the expected value of a density matrix.
*  We used this method to approximate a probability density function (pdf) in a quantum computer.

## Libraries

```python
!pip install qiskit
!pip install pylatexenc
```

    Collecting qiskit
      Downloading qiskit-0.32.0.tar.gz (13 kB)
    Collecting qiskit-terra==0.18.3
      Downloading qiskit_terra-0.18.3-cp37-cp37m-manylinux2010_x86_64.whl (6.1 MB)
         |████████████████████████████████| 6.1 MB 5.2 MB/s
    Collecting qiskit-aer==0.9.1
      Downloading qiskit_aer-0.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (17.9 MB)
         |████████████████████████████████| 17.9 MB 100 kB/s
    Collecting qiskit-ibmq-provider==0.18.0
      Downloading qiskit_ibmq_provider-0.18.0-py3-none-any.whl (237 kB)
         |████████████████████████████████| 237 kB 43.7 MB/s
    Collecting qiskit-ignis==0.6.0
      Downloading qiskit_ignis-0.6.0-py3-none-any.whl (207 kB)
         |████████████████████████████████| 207 kB 64.3 MB/s
    Collecting qiskit-aqua==0.9.5
      Downloading qiskit_aqua-0.9.5-py3-none-any.whl (2.1 MB)
         |████████████████████████████████| 2.1 MB 49.3 MB/s
    Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.4.1)
    Requirement already satisfied: numpy>=1.16.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.19.5)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.7.1)
    Collecting dlx<=1.0.4
      Downloading dlx-1.0.4.tar.gz (5.5 kB)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.0.1)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (5.4.8)
    Requirement already satisfied: fastdtw<=0.3.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (0.3.4)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (1.1.5)
    Requirement already satisfied: h5py<3.3.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (3.1.0)
    Collecting quandl
      Downloading Quandl-3.7.0-py2.py3-none-any.whl (26 kB)
    Collecting yfinance>=0.1.62
      Downloading yfinance-0.1.66-py2.py3-none-any.whl (25 kB)
    Collecting retworkx>=0.8.0
      Downloading retworkx-0.10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.4 MB)
         |████████████████████████████████| 1.4 MB 42.6 MB/s
    Collecting docplex>=2.21.207
      Downloading docplex-2.22.213.tar.gz (634 kB)
         |████████████████████████████████| 634 kB 50.5 MB/s
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.5->qiskit) (57.4.0)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (2.8.2)
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (2.23.0)
    Collecting requests-ntlm>=1.1.0
      Downloading requests_ntlm-1.1.0-py2.py3-none-any.whl (5.7 kB)
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.0->qiskit) (1.24.3)
    Collecting websocket-client>=1.0.1
      Downloading websocket_client-1.2.1-py2.py3-none-any.whl (52 kB)
         |████████████████████████████████| 52 kB 1.3 MB/s
    Collecting ply>=3.10
      Downloading ply-3.11-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 5.6 MB/s
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (2.6.0)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.18.3->qiskit) (0.3.4)
    Collecting symengine>0.7
      Downloading symengine-0.8.1-cp37-cp37m-manylinux2010_x86_64.whl (38.2 MB)
         |████████████████████████████████| 38.2 MB 24 kB/s
    Collecting tweedledum<2.0,>=1.1
      Downloading tweedledum-1.1.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (943 kB)
         |████████████████████████████████| 943 kB 9.3 kB/s
    Collecting python-constraint>=1.4
      Downloading python-constraint-1.4.0.tar.bz2 (18 kB)
    Collecting fastjsonschema>=2.10
      Downloading fastjsonschema-2.15.1-py3-none-any.whl (21 kB)
    Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from docplex>=2.21.207->qiskit-aqua==0.9.5->qiskit) (1.15.0)
    Requirement already satisfied: cached-property in /usr/local/lib/python3.7/dist-packages (from h5py<3.3.0->qiskit-aqua==0.9.5->qiskit) (1.5.2)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (2021.10.8)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.0->qiskit) (2.10)
    Collecting cryptography>=1.3
      Downloading cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl (3.5 MB)
         |████████████████████████████████| 3.5 MB 58.2 MB/s
    Collecting ntlm-auth>=1.0.2
      Downloading ntlm_auth-1.5.0-py2.py3-none-any.whl (29 kB)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (1.15.0)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.0->qiskit) (2.21)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.5->qiskit) (1.1.0)
    Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.5->qiskit) (3.0.0)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-aqua==0.9.5->qiskit) (1.2.1)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from yfinance>=0.1.62->qiskit-aqua==0.9.5->qiskit) (0.0.9)
    Collecting lxml>=4.5.1
      Downloading lxml-4.6.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (6.3 MB)
         |████████████████████████████████| 6.3 MB 54.7 MB/s
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->qiskit-aqua==0.9.5->qiskit) (2018.9)
    Collecting inflection>=0.3.1
      Downloading inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.5->qiskit) (8.11.0)
    Building wheels for collected packages: qiskit, dlx, docplex, python-constraint
      Building wheel for qiskit (setup.py) ... done
      Created wheel for qiskit: filename=qiskit-0.32.0-py3-none-any.whl size=11773 sha256=119c097d1b46c8548430527c0e257e8ba18e1675566b342daff7f3c366e5f8d7
      Stored in directory: /root/.cache/pip/wheels/95/84/c4/6a084a33c1909266fca66eed2303c3883e03f33c905b1ebed9
      Building wheel for dlx (setup.py) ... done
      Created wheel for dlx: filename=dlx-1.0.4-py3-none-any.whl size=5719 sha256=80b6f2f7a329794adafc1bec73a2695a511b2cb06a8052ab6386b2430e62144c
      Stored in directory: /root/.cache/pip/wheels/78/55/c8/dc61e772445a566b7608a476d151e9dcaf4e092b01b0c4bc3c
      Building wheel for docplex (setup.py) ... done
      Created wheel for docplex: filename=docplex-2.22.213-py3-none-any.whl size=696881 sha256=9b5bcd4ed179e7ffff63ff5f858fed5430f1e62a5a74962ba4759dfcd04322f6
      Stored in directory: /root/.cache/pip/wheels/90/69/6b/1375c68a5b7ff94c40263b151c86f58bd72200bf0c465b5ba3
      Building wheel for python-constraint (setup.py) ... done
      Created wheel for python-constraint: filename=python_constraint-1.4.0-py2.py3-none-any.whl size=24081 sha256=04de0b54c8bb91ad210555037ab1183c0e81c06b94e82383cc057301eb6676c1
      Stored in directory: /root/.cache/pip/wheels/07/27/db/1222c80eb1e431f3d2199c12569cb1cac60f562a451fe30479
    Successfully built qiskit dlx docplex python-constraint
    Installing collected packages: tweedledum, symengine, retworkx, python-constraint, ply, fastjsonschema, qiskit-terra, ntlm-auth, lxml, inflection, cryptography, yfinance, websocket-client, requests-ntlm, quandl, qiskit-ignis, docplex, dlx, qiskit-ibmq-provider, qiskit-aqua, qiskit-aer, qiskit
      Attempting uninstall: lxml
        Found existing installation: lxml 4.2.6
        Uninstalling lxml-4.2.6:
          Successfully uninstalled lxml-4.2.6
    Successfully installed cryptography-35.0.0 dlx-1.0.4 docplex-2.22.213 fastjsonschema-2.15.1 inflection-0.5.1 lxml-4.6.4 ntlm-auth-1.5.0 ply-3.11 python-constraint-1.4.0 qiskit-0.32.0 qiskit-aer-0.9.1 qiskit-aqua-0.9.5 qiskit-ibmq-provider-0.18.0 qiskit-ignis-0.6.0 qiskit-terra-0.18.3 quandl-3.7.0 requests-ntlm-1.1.0 retworkx-0.10.2 symengine-0.8.1 tweedledum-1.1.1 websocket-client-1.2.1 yfinance-0.1.66
    Collecting pylatexenc
      Downloading pylatexenc-2.10.tar.gz (162 kB)
         |████████████████████████████████| 162 kB 5.2 MB/s
    Building wheels for collected packages: pylatexenc
      Building wheel for pylatexenc (setup.py) ... done
      Created wheel for pylatexenc: filename=pylatexenc-2.10-py3-none-any.whl size=136833 sha256=d799a01bdacc8d8f0a16c1741cf310acf2bb79b093617f694fa42c1fea854baf
      Stored in directory: /root/.cache/pip/wheels/f1/8a/f5/33ee79d4473eb201b519fa40f989b842e373237395a3421f52
    Successfully built pylatexenc
    Installing collected packages: pylatexenc
    Successfully installed pylatexenc-2.10

```python
from qiskit import QuantumCircuit, transpile
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from qiskit import QuantumCircuit, Aer, execute
from qiskit.extensions import Initialize
from qiskit.visualization import plot_histogram
from qiskit.providers.aer import StatevectorSimulator
import tensorflow as tf
import matplotlib.pyplot as plt
from time import time

from scipy.stats import norm
import numpy as np
backend = Aer.get_backend('qasm_simulator')
```

## Expected Value of Density Matrices with Isometries

In the following section we present a proposed circuit to estimate the expected values of any (nxn) density matrix. For the Qiskit Fall Fest 2021 we proposed a circuit to compute the expected values of (4x4) density matrices. In this proposal we show the method for any (nxn) density matrix, in comparison with the method proposed in the Qiskit Fall Fest 2021, this circuit has less qubits and lower circuit depth.

We take two approaches mixed states,

$$\rho_{\text{train}} = \frac{1}{N}\sum_{i=0}^{N}|\psi_i><\psi_i>$$

And pure states,

$$|\psi_{\text{train}}>= \frac{\sum_{i=0}^{N}|\psi_i>}{|\sum_{i=0}^{N}|\psi_i>|}$$

Here we show how to compute,

$$<\phi|\rho_{\text{train}}|\phi>$$

for mixed states.

And,

$$<\phi|\psi_{\text{train}}>^2$$

for pure states. Where $|\phi>$ is some quantum state to predict.

The pure state scenario has been shown in in [QTML2021]. We propose the method to compute the expected value of the density matrix of a mixed state.

### Example Density Matrix (8x8)

```python
# First we create an example of a 8x8 density matrix

# Example to predict a
np.random.seed(0)
a = np.random.normal(size=(8))
a = a / np.linalg.norm(a)

# Create a density matrix from 100 random examples name it rho_train

x_train = np.random.normal(size=(100, 8))
x_train = x_train / np.linalg.norm(x_train, axis = 1).reshape(-1, 1)
rho_train = np.zeros((8, 8))

for i in range(100):
  rho_train += np.outer(x_train[i], x_train[i])

rho_train = rho_train / 100

(a**2).sum(), (x_train[0]**2).sum(), rho_train
```

    (1.0,
     1.0,
     array([[ 0.10908034,  0.01354419,  0.00993431, -0.01386612, -0.00787494,
              0.01479722, -0.00596694,  0.01245466],
            [ 0.01354419,  0.1377924 ,  0.01372431,  0.00254668,  0.02134911,
              0.00360863, -0.00630882, -0.01395152],
            [ 0.00993431,  0.01372431,  0.11009632, -0.00760918, -0.00112369,
             -0.00944604, -0.01427106, -0.01290296],
            [-0.01386612,  0.00254668, -0.00760918,  0.11971723, -0.01143899,
             -0.00606719, -0.02489165,  0.00018427],
            [-0.00787494,  0.02134911, -0.00112369, -0.01143899,  0.15663075,
              0.00121742,  0.0168858 ,  0.00839981],
            [ 0.01479722,  0.00360863, -0.00944604, -0.00606719,  0.00121742,
              0.10790513,  0.00942147, -0.00628848],
            [-0.00596694, -0.00630882, -0.01427106, -0.02489165,  0.0168858 ,
              0.00942147,  0.12274743,  0.01011429],
            [ 0.01245466, -0.01395152, -0.01290296,  0.00018427,  0.00839981,
             -0.00628848,  0.01011429,  0.1360304 ]]))

```python
# Find eigenvectors and eigenvalues
lambda_train, U_train = np.linalg.eigh(rho_train)

lambda_train, U_train
```

    (array([0.07578676, 0.08496879, 0.10153945, 0.11815496, 0.13391739,
            0.14029133, 0.16626093, 0.17908041]),
     array([[-0.65892144, -0.07785797, -0.22458393,  0.06764372,  0.42620744,
              0.5573519 , -0.10395923,  0.04056611],
            [ 0.19230474, -0.11489959,  0.54327706,  0.28693659,  0.23813675,
              0.07024289, -0.6592057 ,  0.2759331 ],
            [ 0.14608708,  0.66362319, -0.08453395, -0.59423105,  0.08139098,
              0.17508716, -0.37236461, -0.04550704],
            [-0.34353266,  0.48663178,  0.01015192,  0.41755232,  0.22385674,
             -0.55258241, -0.10770272, -0.32196705],
            [-0.13917622, -0.00653415, -0.41036267, -0.08575573,  0.02070984,
             -0.41636728, -0.12338989,  0.78474203],
            [ 0.4170536 ,  0.32244636, -0.444611  ,  0.60585602, -0.11250815,
              0.3647404 ,  0.00610511,  0.10781126],
            [-0.29949437,  0.43329548,  0.51191942,  0.09086638, -0.34212662,
              0.20210551,  0.36464705,  0.40171535],
            [ 0.32631991,  0.10849271,  0.13793149, -0.05439649,  0.75813203,
             -0.03432295,  0.50611781,  0.16632228]]))

```python
## Expected value of a 8x8 Density Matrix with Isometries

backend = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(6, 3)
qc.initialize(a, [0, 1, 2])
qc.initialize(np.sqrt(lambda_train), [3, 4, 5])
qc.isometry(U_train.T, [], [0, 1, 2]) # ArbRot as a isometry
qc.cnot(5, 2)
qc.cnot(4, 1)
qc.cnot(3, 0)
qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

counts = execute(qc, backend, shots=150000).result().get_counts()
plot_histogram(counts)

# Works
```

```python
qc.draw("mpl")
```

```python
# Comparison between the results obtained by the circuit and the expected result
exp_a_calculated = (counts["000"]/150000)
exp_a_theorical = a.T @ rho_train @ a

exp_a_calculated, exp_a_theorical
```

    (0.10266, 0.102462229484645)

#### Conclusion

The expected value of the density matrix obtained by the circuit is 0.10266, the expected result was 0.102462

### Example Pure State (8x8)

We show how to compute the projection between two quantum states, which corresponds to the expected value of a pure density matrix. We did not proposed this method, it is based on [QTML2021].

```python
# Given the eigenvalues this function return a Unitary gate which converts the |0> -> |psi_train>
def create_U_train(x_train_param, seed=0):
  np.random.seed(seed)
  size_U = len(x_train_param)
  U_train = np.zeros((size_U, size_U))
  x_1 = x_train_param
  U_train[:, 0] = x_1
  for i in range(1, size_U):
    x_i = np.random.randn(size_U)
    for j in range(0, i):
      x_i -= x_i.dot(U_train[:, j]) * U_train[:, j]
    x_i = x_i/np.linalg.norm(x_i)
    U_train[:, i] = x_i

  return U_train
```

```python
# Example to predict a
np.random.seed(0)
a = np.random.normal(size=(8))
a = a / np.linalg.norm(a)

# Create a density matrix from 100 random examples name it x_train for pure states
x_train = np.random.normal(size=(100, 8))
x_train = x_train.sum(axis = 0)
x_train = x_train / np.linalg.norm(x_train)

U_train = create_U_train(x_train)

(x_train**2).sum(), U_train
```

    (1.0, array([[-0.40956759,  0.31412729, -0.58688388,  0.30053684,  0.33094154,
             -0.3383931 ,  0.25238096,  0.10533287],
            [-0.2890886 , -0.02388176,  0.2264827 ,  0.02391376, -0.64282764,
             -0.60863405, -0.03189844,  0.28162186],
            [-0.45525577,  0.06370831, -0.15776071,  0.01154427, -0.35150837,
              0.1846809 , -0.17341667, -0.75889605],
            [-0.10917489,  0.59840266,  0.55940941,  0.25414967, -0.02590904,
              0.23805757,  0.44086826, -0.0275072 ],
            [-0.36445305,  0.36609086,  0.09623833, -0.75648236,  0.22533225,
              0.02330659, -0.2613604 ,  0.17887705],
            [-0.58494098, -0.56772558,  0.38459265,  0.18107349,  0.37946048,
              0.07902726,  0.01707193,  0.0656169 ],
            [ 0.00453847,  0.27835736,  0.09427881,  0.48769201,  0.08866809,
              0.06599   , -0.79755245,  0.16570447],
            [ 0.23345796,  0.06922096,  0.31683395, -0.03851947,  0.38780665,
             -0.64275429, -0.07847008, -0.51880026]]))

```python
## Density Estimation Aproach 2 matrices 8x8
## This method is a simpliflied version of the one presented before

backend = Aer.get_backend('qasm_simulator')
qc = QuantumCircuit(3, 3)
qc.initialize(a, [0, 1, 2])
qc.isometry(U_train.T, [], [0, 1, 2]) # ArbRot as a isometry
qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

counts = execute(qc, backend, shots=150000).result().get_counts()
plot_histogram(counts)

# Works
```

```python
qc.draw("mpl")
```

```python
exp_a_calculated = (counts["000"]/150000)
exp_a_theorical = np.dot(x_train, a)**2

exp_a_calculated, exp_a_theorical
```

    (0.19020666666666666, 0.19039803575411707)

## DMKDE for pdf Estimation

The Density Matrix Kernel Density Estimation (DMKDE) [Gonzalez2021] takes the expected values of density matrices with a quantum feauture map based on Random Fourier Feautures [Rahimi2007] to estimate probability Density Funtions.

Our contribution is to show that it is possible to implement the prediction phase of the DMKDE in a quantum computer. The method has been implemented in a qudit based quantum computer [Useche2021], in this project we show it is possible in a qubit-based quantum computer.

### (16x16) DMKDE Mixed State

```python
def calculate_constant_qmkde(gamma=1):
  constant_kernel = (2*np.pi)**(-1/2)
  sigma = (4*gamma)**(-1/2)
  constant_outside = 1/(sigma)
  return constant_kernel * constant_outside
```

```python
# ----------------------------------------------------------------------
# Plot a 1D density example
N = 1000
np.random.seed(1)
X = np.concatenate((np.random.normal(-1, 1, int(0.65 * N)),
                    np.random.normal(3, 1, int(0.35 * N))))[:, np.newaxis]

X_plot = np.linspace(-7, 7, 250)[:, np.newaxis]

true_dens = (0.65 * norm(-1, 1).pdf(X_plot[:, 0])
             + 0.35 * norm(3, 1).pdf(X_plot[:, 0]))
```

```python
from sklearn.kernel_approximation import RBFSampler
"""
  Code from https://arxiv.org/abs/2004.01227
"""
class QFeatureMap:

  def get_dim(self, num_features):
    pass

  def batch2wf(self, X):
    pass

  def batch2dm(self, X):
    pass

  def batch2dm(self, X):
    psi = self.batch2wf(X)
    rho = np.einsum('...i,...j', psi, np.conj(psi))
    return rho

class QFeatureMap_rff(QFeatureMap):

  def __init__(self, rbf_sampler):
    self.rbf_sampler = rbf_sampler
    self.weights = np.array(rbf_sampler.random_weights_)
    self.offset = np.array(rbf_sampler.random_offset_)
    self.dim = rbf_sampler.get_params()['n_components']

  def get_dim(self, num_features):
    return self.dim

  def batch2wf(self, X):
    vals = np.dot(X, self.weights) + self.offset
    vals = np.cos(vals)
    vals *= np.sqrt(2.) / np.sqrt(self.dim)
    norms = np.linalg.norm(vals, axis=1)
    psi = vals / norms[:, np.newaxis]
    return psi
```

```python
# Create the RandomFourierFeature map
dim = 16
rm_state = 2
feature_map_fourier = RBFSampler(gamma=2, n_components=dim, random_state=rm_state)
X_feat_train = feature_map_fourier.fit(X)
rffmap = QFeatureMap_rff(rbf_sampler=feature_map_fourier)
X_feat_train = rffmap.batch2wf(X)

X_feat_test = rffmap.batch2wf(X_plot)

X_feat_train.shape, X_feat_test.shape
```

    ((1000, 16), (250, 16))

```python
## Training
## Trainig of the DMKDE is done in a classical computer.

rho_train = np.zeros((16, 16))

for i in range(1000):
  rho_train += np.outer(X_feat_train[i], X_feat_train[i])

rho_train = rho_train / 1000

(X_feat_train[0]**2).sum()
```

    0.9999999999999998

#### Expected Result

In this section we show the implementation of the method in a classical computer to compare it with the implementation in a quantum computer.

```python
## Expected result
## This code presents the DMKDE in a classical computer.

prob_expected_mixed = np.zeros(250)

for i in range(len(prob_expected_mixed)):
  prob_expected_mixed[i] = X_feat_test[i].T @ rho_train @ X_feat_test[i]

prob_expected_mixed = calculate_constant_qmkde(2) * prob_expected_mixed

prob_expected_mixed.shape
```

    (250,)

```python
params = {
   'axes.labelsize': 8,
   'legend.fontsize': 10,
   'xtick.labelsize': 10,
   'ytick.labelsize': 10,
   'text.usetex': False,
   'figure.figsize': [5.5, 4.5]
   }
plt.rcParams.update(params)

plt.plot(X_plot[:, 0], true_dens, "maroon", label='True pdf')
colors = ['navy']
kernels = ['gaussian']
lw = 2
plt.plot(X_plot[:, 0], prob_expected_mixed, color='#006BB2', lw=lw,
            linestyle='-', label="DMKDE Mixed")
plt.text(4, 0.25, f"DM 16x16")
plt.legend(loc='upper left')
plt.xlabel('')
plt.ylabel('Probability')
```

#### Result in a Quantum Computer

Here we show the prediction phase in a quantum computer, which allowed us to estimate probability density functions. The result of the estimation of the probability density function in a quantum computer is comparable with the classical calculation.

```python
# Find eigenvectors and eigenvalues
lambda_train, U_train = np.linalg.eigh(rho_train)

lambda_train, U_train
```

    (array([2.21082101e-08, 5.45200938e-06, 5.46881200e-05, 5.29996186e-04,
            4.06247157e-03, 8.49074157e-03, 1.37341069e-02, 1.51956110e-02,
            4.25593877e-02, 4.66138644e-02, 6.87664093e-02, 9.94027023e-02,
            1.11339275e-01, 1.61207624e-01, 1.88196981e-01, 2.39840667e-01]),
     array([[ 9.36642734e-02, -3.48236799e-01, -1.19880008e-01,
              7.10257687e-02, -6.88923366e-02,  4.11767561e-01,
              3.35218929e-01,  5.54806261e-02, -1.31530676e-01,
              4.39846474e-01, -5.49419674e-02,  1.94445682e-01,
             -5.69147862e-02,  1.73710556e-01,  4.07222430e-01,
             -3.31596157e-01],
            [-1.73681555e-02,  6.32646887e-02,  2.84661009e-02,
             -9.69606735e-02,  7.24745838e-02,  1.56721379e-02,
              3.98932917e-04, -4.69592695e-02, -4.89115177e-01,
              1.98417622e-01, -2.95767141e-01,  3.17063101e-01,
             -3.13651380e-01, -4.81873831e-01,  2.92140757e-02,
              4.23954316e-01],
            [-8.42612846e-04,  3.62005454e-03, -5.66369984e-03,
              3.63620833e-02,  1.17052565e-01,  3.16036389e-01,
             -4.57852035e-01,  4.30631172e-01, -5.47248602e-02,
             -1.15785595e-02, -2.19305066e-01, -3.99529300e-01,
             -4.75432911e-01,  2.28611887e-01,  2.14825567e-02,
              1.92696141e-02],
            [ 3.26918651e-03, -8.03170616e-03,  2.76424904e-02,
             -2.63310848e-02, -6.16763355e-02, -2.90085601e-01,
              2.71883375e-01,  5.16724747e-01, -2.25300658e-01,
             -2.89873213e-01,  3.40529541e-01,  3.43399529e-01,
             -2.42227862e-01,  3.46894104e-01,  4.82110687e-02,
              1.39943117e-01],
            [ 2.27251757e-03, -6.61132613e-03,  2.85016056e-02,
             -2.76694414e-02, -4.16174803e-02, -3.03081614e-01,
              2.45438424e-01,  5.88178930e-01, -8.42492430e-02,
              1.71826714e-01, -3.10355537e-01, -3.51325282e-01,
              3.85046636e-01, -2.87925020e-01,  3.26129374e-02,
             -9.99526487e-02],
            [ 2.62337968e-01, -3.25869370e-01,  4.60879478e-01,
             -2.49184477e-01, -2.56818657e-01,  5.20066165e-02,
             -1.18628693e-01, -8.56572457e-02, -6.25195171e-02,
             -2.18744940e-01, -3.65301044e-01,  4.93586572e-02,
              2.83228490e-01,  3.02098322e-01,  1.40070382e-01,
              2.85786725e-01],
            [-4.87012623e-01, -1.00223484e-01,  2.50362861e-01,
              3.10422864e-01, -3.81006293e-01,  2.62900063e-01,
              1.55732759e-01,  1.87351447e-02, -1.94456082e-01,
             -8.95613920e-02, -1.14552112e-01,  3.20143461e-02,
             -4.28769205e-03,  1.16220856e-02, -5.26211685e-01,
             -1.33455593e-01],
            [ 1.12292171e-03, -3.13923158e-02, -3.85288030e-02,
             -1.98698733e-01, -3.98074686e-01, -1.93208730e-01,
             -1.75051188e-01, -7.78378404e-02, -1.97300502e-01,
              6.02091595e-01,  3.70560163e-01, -2.52952494e-01,
              8.32021099e-03,  2.09372254e-01, -1.71061070e-01,
              2.22808118e-01],
            [ 3.64484215e-02,  3.32806406e-02,  2.51281814e-01,
              3.72840303e-01,  6.13469478e-01, -2.25375155e-05,
              1.68332456e-01, -1.74541345e-02, -1.09864157e-03,
              3.16941468e-01, -3.53518751e-02, -1.79637498e-03,
              1.74485237e-01,  3.37876747e-01, -2.14855480e-01,
              3.12971966e-01],
            [ 2.45434715e-01, -2.25307658e-01,  6.69653744e-01,
              8.52231142e-02,  9.71177700e-02, -1.54617099e-01,
             -8.82222227e-02,  9.84037293e-04,  1.42435602e-02,
              7.90605501e-02,  3.08830630e-01, -5.40144316e-02,
             -2.68275114e-01, -3.27972985e-01,  1.61717910e-05,
             -3.28468453e-01],
            [ 2.74046235e-01,  5.51348924e-01,  6.52137058e-02,
              2.64496614e-01, -1.62071337e-01, -2.25042353e-01,
             -2.08511086e-01, -9.71905255e-02, -3.02458410e-01,
              7.99430806e-02, -2.57883989e-01,  1.86959636e-01,
              4.13983534e-02,  2.38231128e-01,  3.94204734e-02,
             -3.96882574e-01],
            [ 7.31609408e-04, -2.21783319e-03,  1.06114473e-02,
             -2.37872205e-02, -8.52382052e-02, -3.31364170e-01,
              4.96423608e-01, -3.28766051e-01,  1.26848837e-01,
             -3.02510760e-02, -3.16935438e-01, -3.59283452e-01,
             -5.01052000e-01,  1.70110361e-01,  3.21695852e-02,
              1.26839401e-02],
            [-2.12032897e-01,  5.32232126e-01,  3.66340093e-01,
             -5.86101365e-01,  1.06695886e-01,  3.06012246e-01,
              2.29235055e-01,  4.37407478e-02,  4.93621796e-02,
              9.19177481e-02,  6.59589298e-02, -1.71820298e-02,
              1.50200931e-02,  7.73660913e-02,  4.68801417e-02,
             -1.03060702e-01],
            [ 8.36590467e-03, -2.96905532e-02,  1.88688139e-02,
             -1.07371908e-01, -1.29869233e-01, -1.44044439e-01,
             -1.18229095e-01,  2.21179198e-01,  6.31084603e-01,
              3.22466800e-01, -2.73026566e-01,  4.63512602e-01,
             -1.68389920e-01,  2.07797608e-02, -2.57053692e-01,
             -1.21216301e-02],
            [ 6.63846002e-01,  1.36246006e-03, -2.00218399e-01,
             -1.90980545e-01,  1.54933018e-02,  2.63198364e-01,
              2.30804826e-01,  4.60495750e-02, -9.29132243e-02,
             -9.28387829e-02,  2.16413508e-02, -4.84519778e-02,
             -2.64666167e-02, -5.48555521e-02, -5.74258042e-01,
             -6.55060295e-02],
            [ 2.50034018e-01,  3.42593346e-01,  1.26654625e-01,
              4.20145370e-01, -3.91636969e-01,  2.77750967e-01,
              1.70413356e-01,  1.08512306e-01,  2.91291049e-01,
              5.86904692e-03,  1.49348232e-01, -9.37726883e-02,
             -2.53182554e-02, -1.73327122e-01,  2.38512683e-01,
              3.90709326e-01]]))

```python
# Predict with the quantum circuit
backend = Aer.get_backend('qasm_simulator')
prob_test_mixed = np.zeros(250)

exp_time = time()
for i in range(len(prob_test_mixed)):
  qc = QuantumCircuit(8, 4)
  qc.initialize(X_feat_test[i], [0, 1, 2, 3])
  qc.initialize(np.sqrt(lambda_train, dtype = np.float64), [4, 5, 6, 7])
  qc.isometry(U_train.T, [], [0, 1, 2, 3]) # ArbRot as a isometry
  qc.cnot(7, 3)
  qc.cnot(6, 2)
  qc.cnot(5, 1)
  qc.cnot(4, 0)
  qc.measure(0, 0)
  qc.measure(1, 1)
  qc.measure(2, 2)
  qc.measure(3, 3)
  counts = execute(qc, backend, shots=10000).result().get_counts()
  prob_zero = (counts["0000"]/10000)
  prob_test_mixed[i] = prob_zero
  if i % 10 == 0:
    print(i)
    print(time() - exp_time)
    exp_time = time()
```

    0
    10.685803174972534
    10
    103.40195870399475
    20
    103.74765133857727
    30
    103.71568632125854
    40
    103.55290651321411
    50
    108.12654328346252
    60
    103.71877217292786
    70
    103.65322875976562
    80
    104.24595355987549
    90
    103.60946369171143
    100
    103.48164296150208
    110
    103.90748643875122
    120
    103.55590033531189
    130
    103.48113203048706
    140
    103.84045147895813
    150
    103.6746301651001
    160
    103.85183334350586
    170
    103.78565764427185
    180
    103.15175127983093
    190
    103.7325005531311
    200
    104.15579414367676
    210
    103.49840307235718
    220
    103.18597960472107
    230
    103.97791171073914
    240
    103.48532247543335

```python
prob_test_mixed_2 = calculate_constant_qmkde(2) * prob_test_mixed
```

```python
params = {
   'axes.labelsize': 8,
   'legend.fontsize': 10,
   'xtick.labelsize': 10,
   'ytick.labelsize': 10,
   'text.usetex': False,
   'figure.figsize': [5.5, 4.5]
   }
plt.rcParams.update(params)

plt.plot(X_plot[:, 0], true_dens, "maroon", label='True pdf')
colors = ['navy']
kernels = ['gaussian']
lw = 2
plt.plot(X_plot[:, 0], prob_test_mixed_2, color='#006BB2', lw=lw,
            linestyle='-', label="DMKDE Mixed")
plt.text(4, 0.25, f"DM 16x16")
plt.legend(loc='upper left')
plt.xlabel('')
plt.ylabel('Probability')
```

### (16x16) DMKDE Pure State

```python
def calculate_constant_qmkde_pure(gamma=1):
  constant_kernel = (2*np.pi)**(-1/2)
  sigma = (2*gamma)**(-1/2)
  constant_outside = 1/(sigma)
  return constant_kernel * constant_outside
```

```python
# Given the eigenvalues this function return a Unitary gate which converts the |0> -> |lambda>
def create_U_train(x_train_param, seed=0):
  np.random.seed(seed)
  size_U = len(x_train_param)
  U_train = np.zeros((size_U, size_U))
  x_1 = x_train_param
  U_train[:, 0] = x_1
  for i in range(1, size_U):
    x_i = np.random.randn(size_U)
    for j in range(0, i):
      x_i -= x_i.dot(U_train[:, j]) * U_train[:, j]
    x_i = x_i/np.linalg.norm(x_i)
    U_train[:, i] = x_i

  return U_train
```

```python
# ----------------------------------------------------------------------
# Plot a 1D density example
N = 1000
np.random.seed(1)
X = np.concatenate((np.random.normal(-1, 1, int(0.65 * N)),
                    np.random.normal(3, 1, int(0.35 * N))))[:, np.newaxis]

X_plot = np.linspace(-7, 7, 250)[:, np.newaxis]

true_dens = (0.65 * norm(-1, 1).pdf(X_plot[:, 0])
             + 0.35 * norm(3, 1).pdf(X_plot[:, 0]))
```

```python
from sklearn.kernel_approximation import RBFSampler
"""
  Code from https://arxiv.org/abs/2004.01227
"""
class QFeatureMap:

  def get_dim(self, num_features):
    pass

  def batch2wf(self, X):
    pass

  def batch2dm(self, X):
    pass

  def batch2dm(self, X):
    psi = self.batch2wf(X)
    rho = np.einsum('...i,...j', psi, np.conj(psi))
    return rho

class QFeatureMap_rff(QFeatureMap):

  def __init__(self, rbf_sampler):
    self.rbf_sampler = rbf_sampler
    self.weights = np.array(rbf_sampler.random_weights_)
    self.offset = np.array(rbf_sampler.random_offset_)
    self.dim = rbf_sampler.get_params()['n_components']

  def get_dim(self, num_features):
    return self.dim

  def batch2wf(self, X):
    vals = np.dot(X, self.weights) + self.offset
    vals = np.cos(vals)
    vals *= np.sqrt(2.) / np.sqrt(self.dim)
    norms = np.linalg.norm(vals, axis=1)
    psi = vals / norms[:, np.newaxis]
    return psi
```

```python
# Create the RandomFourierFeature map
dim = 16
rm_state = 2 # originally 1
feature_map_fourier = RBFSampler(gamma=2, n_components=dim, random_state=rm_state)
X_feat_train = feature_map_fourier.fit(X)
rffmap = QFeatureMap_rff(rbf_sampler=feature_map_fourier)
X_feat_train = rffmap.batch2wf(X)

X_feat_test = rffmap.batch2wf(X_plot)

X_feat_train.shape, X_feat_test.shape
```

    ((1000, 16), (250, 16))

```python
## Training pure state and create the Unitary matrix to initialize such state

psi_train = X_feat_train.sum(axis = 0)
psi_train = psi_train / np.linalg.norm(psi_train)

U_train = create_U_train(psi_train)

(psi_train**2).sum()
```

    1.0

```python
## Expected result
psi_train_total = (X_feat_train.sum(axis = 0) / np.linalg.norm(X_feat_train.sum(axis = 0))).reshape(-1, 1)
prob_test_16_expected = X_feat_test @ psi_train_total
prob_test_16_expected = calculate_constant_qmkde_pure(2) * np.sqrt(prob_test_16_expected**2) * np.linalg.norm(X_feat_train.sum(axis = 0)) / 1000
psi_train_total.shape, X_feat_test.shape, prob_test_16_expected.shape
```

    ((16, 1), (250, 16), (250, 1))

```python
# Expected result in a classical computer
params = {
   'axes.labelsize': 8,
   'legend.fontsize': 10,
   'xtick.labelsize': 10,
   'ytick.labelsize': 10,
   'text.usetex': False,
   'figure.figsize': [5.5, 4.5]
   }
plt.rcParams.update(params)

plt.plot(X_plot[:, 0], true_dens, "maroon", label='True pdf')
colors = ['navy']
kernels = ['gaussian']
lw = 2
#plt.plot(X_plot[:, 0], prob_test_16_expected.ravel(), color='green', lw=lw,
            # linestyle='-', label="DMKDE Pure")
plt.plot(X_plot[:, 0], prob_test_16_expected.ravel(), label="DMKDE Pure")
plt.text(4, 0.25, f"DM 16x16")
plt.legend(loc='upper left')
plt.xlabel('')
plt.ylabel('Probability')
```

```python
# Predict with the pure state quantum circuit
prob_test = np.zeros(250)
backend = Aer.get_backend('qasm_simulator')
exp_time = time()

for i in range(len(prob_test)):
  #"""
  qc = QuantumCircuit(4, 4)
  qc.initialize(X_feat_test[i], [0, 1, 2, 3])
  qc.isometry(U_train.T, [], [0, 1, 2, 3]) # ArbRot as a isometry
  qc.measure(0, 0)
  qc.measure(1, 1)
  qc.measure(2, 2)
  qc.measure(3, 3)
  counts = execute(qc, backend, shots=10000).result().get_counts()
  try:
    prob_zero = (counts["0000"]/10000)
  except:
    prob_zero = 0
  prob_test[i] = prob_zero
  if i % 10 == 0:
    print(i)
    print(time() - exp_time)
    exp_time = time()
```

    0
    2.7068257331848145
    10
    24.05736780166626
    20
    23.878232717514038
    30
    24.635027647018433
    40
    23.471964359283447
    50
    23.447585344314575
    60
    24.054346084594727
    70
    23.90100908279419
    80
    24.12700867652893
    90
    24.507858991622925
    100
    24.163734912872314
    110
    23.561205625534058
    120
    23.547842741012573
    130
    23.78159809112549
    140
    23.780375957489014
    150
    24.738229274749756
    160
    24.042202711105347
    170
    24.1181857585907
    180
    24.31393265724182
    190
    23.36166214942932
    200
    23.55500054359436
    210
    23.418930053710938
    220
    23.71921706199646
    230
    23.528968572616577
    240
    23.778280019760132

```python
prob_test_pure = calculate_constant_qmkde_pure(2) * np.sqrt(prob_test) * (1/1000) * np.linalg.norm(X_feat_train.sum(axis = 0))
```

```python
params = {
   'axes.labelsize': 8,
   'legend.fontsize': 10,
   'xtick.labelsize': 10,
   'ytick.labelsize': 10,
   'text.usetex': False,
   'figure.figsize': [5.5, 4.5]
   }
plt.rcParams.update(params)

plt.plot(X_plot[:, 0], true_dens, "maroon", label='True pdf')
colors = ['navy']
kernels = ['gaussian']
lw = 2
plt.plot(X_plot[:, 0], prob_test_pure, color='green', lw=lw,
            linestyle='-', label="DMKDE Pure")
#plt.text(4, 0.25, f"DM 16x16")
plt.legend(loc='upper left')
plt.xlabel('')
plt.ylabel('Probability')
```

### Summary of Mixed and Pure

```python
params = {
   'axes.labelsize': 8,
   'legend.fontsize': 10,
   'xtick.labelsize': 10,
   'ytick.labelsize': 10,
   'text.usetex': False,
   'figure.figsize': [5.5, 4.5]
   }
plt.rcParams.update(params)

plt.plot(X_plot[:, 0], true_dens, "maroon", label='True pdf')
colors = ['navy']
kernels = ['gaussian']
lw = 2
plt.plot(X_plot[:, 0], prob_test_mixed_2, color='#006BB2', lw=lw,
            linestyle='-', label="DMKDE Mixed")
plt.plot(X_plot[:, 0], prob_test_pure, color='green', lw=lw,
            linestyle='-', label="DMKDE Pure")
plt.text(4, 0.25, f"DM 16x16")
plt.legend(loc='upper left')
plt.xlabel('')
plt.ylabel('Probability')
```

The Mixed state creates a better estimation of the probability density function, compared with the pure state.

## Acknowledgements

The team thanks specially to our mentor Fabio González for his valuable help and support. Also, we acknowledge the use of IBM Quantum services.

## References

[González2021] Fabio  A.  González,  Alejandro  Gallego,  Santiago  Toledo-Cortés,  and Vladimir Vargas-Calderón.  Learning with Density Matrices and Random Features.  2021.  URL http://arxiv.org/abs/2102.04394.

[QTML2021] Fabio A. González,  Vladimir Vargas-Calderón, Alejandro Gallego, Herbert Vinck-Posada. NON-PARAMETRIC MULTIVARIATE CONTINUOUS DENSITY ESTIMATION IN A QUANTUM COMPUTER. 2021.

[Useche2021] Diego H Useche, Andres Giraldo-Carvajal, Hernan M Zuluaga-Bucheli, Jose A Jaramillo-Villegas, and Fabio A González. Quantum measurement classification with qudits. arXiv preprint arXiv:2107.09781, 2021.

[Parzen1962] Parzen, E. On estimation of a probability density function and mode. The annals of mathematical statistics, 33(3): 1065–1076, 1962

[Rahimi2007] Rahimi, A. and Recht, B. Random features for large-scale kernel machines. In Proceedings of the 20th International Conference on Neural Information Processing Systems, NIPS’07, pp. 1177–1184, Red Hook, NY, USA, 2007. Curran Associates Inc. ISBN 9781605603520.

```python

```


```python
%%html
<!--Script block to left align Markdown Tables-->
<style>
  table {margin-left: 0 !important;}
</style>
```

<!--Script block to left align Markdown Tables-->
<style>
  table {margin-left: 0 !important;}
</style>

#### Notes
Leave script block above in place to left justify the table.
This problem can also be used as laboratory exercise in `matplotlib` lesson.
Dependencies: `matplotlib` and `math`; could also be solved using `numpy` and/or `pandas`

## Problem X:
A tracer gas is used to determine the air exchange rate in a room.  By injecting a stable gas into the room, then monitoring the decay in concentration with time one can estimate the air exchange rate $I$ (air exchanges per unit time).

The governing equation is given by

\begin{equation}
C(t)=C_0 e^{-I t}
\label{eqn:air}
\end{equation}

A plot of the $ln(C(t))$ versus $t$, should produce a straight line with negative slope proportinal to the exchange rate $I$.

Suppose a particular experiment produces the data below, estimate the exchange rate $I$ for these data.

|Time (hours)|Concentration (ppm)|
|---:|---:|
|0.0|10.0|
|0.5|8.0|
|1.0|6.0|
|1.5|5.0|
|2.0|3.3|

```python
# Produce a labeled plot (title,xaxis,yaxis) of the experimental results in the table, e.g. plot C versus t
```

```python
# Produce a labeled plot (title,xaxis,yaxis) of the experimental results in the table, e.g. plot ln(C) versus t
```

```python
# Define a function conc(t,c0,I) for the governing equation, test for values of t =0, c0=10, I=0.4
```

```python
# Make a plot using the function, with c0=10, I=0.4 for the same time values as the experiment set
```

```python
# Make a plot using the log of the function, with c0=10, I=0.4 for the same time values as the experiment set
```

```python
# Make a plot of both the model (markers and line) and the observed values (markers only) on the same plot,
# using trial and error find the value of I that produces best visual fit
```

What is your best visual fit for the room air tracer data ?

```python

```

```python

```

```python

```