# Simulation

* This notebook contains the codes to conduct simulation in Section 6.
  - Codes for Section 6.4 are in another notebook. See [the notebook for miners' profit](https://github.com/okuchap/BDA/blob/master/notebook/miner_profit.ipynb)
* You can view and download this notebook at my [github repo](https://github.com/okuchap/DAA_Lucas). (https://github.com/okuchap/DAA_Lucas)

```python
%matplotlib inline
%load_ext autoreload
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import pathlib
import time
from datetime import datetime
import scipy.optimize as opt
np.set_printoptions(precision=3)
np.set_printoptions(suppress=True)
plt.style.use("ggplot")
mpl.pyplot.rcParams['figure.figsize'] = (5.0, 4.0)
plt.rcParams["font.size"] = 13
#mpl.rcParams["font.family"] = 'Osaka'

import sys
sys.path.append('../code/')

from simulation_fixed_path import *
from hash import *
from plot_path import *
```

## Specification of Hash function

* We need to tune the shape of the hash supply function $\Phi$.

* Use BTC data from October 2018 to December 2018 to obtain MLS.
    - See also [the notebook for empirical analysis](https://github.com/okuchap/BDA/blob/master/notebook/empirical_analysis.ipynb)
    - We have estimated the shape of the hash supply function:
$$
\hat{\Phi}(R) := 23.59 + 9.02 R
$$
    - Note that the range of expected return in the target period is $[1.58, 3.12]$.

* The upper bound of the hash supply is set to be 55 EHash.

* Other properties that the hash supply function is expected to satisfy:
    - the supply should be almost 0 in case $R < 0.8$.
    - the supply increases rapidly on the interval $[1, 2]$.
    - ubd=55, center=1.5, slope=3 seems to work well.

* We have decided to use the following hash supply function that sarisfies the conditions above in our simulation:
\[
\Phi(R) := \frac{55}{1 + \mathrm{e}^{-3(R - 1.5)}}
\]

* See the graph below for its shape:

```python
plot_hash(center=1.5, slope=3, ubd=55)
```

## Simulation

**Notation**

* discrete time $t \in \mathbb{N}$
* $B(t)$: blocktime, or time needed for generate $t$-th block (min)
* $S(t)$: price in period $t$ (USD)
* $H(t)$: hash rate in period $t$ (EH/s)
* $W(t)$: "winning rate" = "the probability that one exahash calculation finds out the nonce" in period $t$
* $M(t)$: the block reward in period $t$ (measured by the cryptocurrency)

* The timing in period $t$:
    - First, $W(t), S(t)$ is determined;
    - Then, $H(t)$ is determined;
    - Lastly, $B(t)$ realizes.

**Ecosystem**

1. Price motion
$$
S(t+1) - S(t) = \mu S(t)B(t) + \sigma S(t)\sqrt{B(t)} \epsilon(t)
$$

2. Block generation
$$
B(t) \sim \mathrm{Exp}(W(t)H(t))
$$
where $\mathrm{Exp}(\lambda)$ denotes the exponential distribution with intensity $\lambda$.

3. Hash supply
$$
H(t) = h(W(t)M(t)S(t))
$$
where $M(t) \equiv 12.5$(USD) is a fixed block reward and $R(t) := W(t)M(t)S(t)$ is the expected return in period $t$(USD/EH).

4. Difficulty Adjustment (BTC)

$T := 2016$
\begin{equation}
    W(t+1) = \begin{cases}
        W(t)\cdot\cfrac{\sum_{s = t - T + 1}^{t}B(s)}{T\cdot \bar{B}} & \text{ if }t \equiv 0 \;(\bmod\; T)\\
        W(t) & \text{ otherwise}
    \end{cases}
\end{equation}

* In the real-world, BTC deploys `sim_BTC_bounded`:

    - In case $t \equiv 0 \mod 2016$
$$
\frac{W(t+1)}{W(t)} :=
\min \left\{
\max \left\{
0.25,
\frac{\sum_{s = t - T + 1}^{t}B(s)}{T\cdot \bar{B}}
\right\}, 4
\right\}
\in [0.25, 4]
$$

* The update rule of BCH is

\[
W(t+1) = \frac{\sum_{s=t-T+1}^t B(s)}{B^*} \cdot \left( \sum_{s=t-T+1}^t \frac{1}{W(s)} \right)^{-1}
\]

* The update rule of DAA-2'(below, `pseudoBCH`) is
$$
    W(t+1) =
    \cfrac{\sum_{s=t-T+1}^t W(s)}{T}
    \cdot
    \cfrac{\sum_{s = t - T + 1}^{t}B(s)}{T\cdot \bar{B}}.
$$

### Volatility $\sigma$

* Historical volatility(/minute) in the target period is $\hat{\sigma} := 0.001117728$

### Implementation

* See `code/simulation_fixed_path.py` for details.

### Data

* The raw data is contained in `BTCdata_2.csv`

* As we did in [the notebook for empirical analysis](https://github.com/okuchap/BDA/blob/master/notebook/empirical_analysis.ipynb), we fixed the data on blocktime for the whole dataset. The data with adjusted blocktime is stored in `BTCdata_timeadjusted.csv`.

  - See the Appendix of this notebook to see how we make `BTCdata_timeadjusted.csv` and related datesets we use below.

* Pick some period from the real-world data used in the simulation.

```python
path = '../data/BTCdata_presim.csv'
df = pd.read_csv(path)
df['time'] = pd.to_datetime(df['time'])
df = df.rename(columns={'blocktime': 'block_times', 'price': 'prices', 'probability of success /Eh': 'winning_rates'})
df.head()
```

      height
      time
      block_times
      prices
      winning_rates
      expected return (USD/Eh)

      0
      549427
      2018-11-09 19:26:14
      5.783333
      6343.32
      0.000032
      2.587754

      1
      549428
      2018-11-09 19:26:20
      0.100000
      6343.32
      0.000032
      2.574300

      2
      549429
      2018-11-09 19:37:51
      11.516667
      6341.26
      0.000032
      2.593078

      3
      549430
      2018-11-09 19:45:57
      8.100000
      6341.26
      0.000032
      2.595087

      4
      549431
      2018-11-09 19:54:51
      8.900000
      6341.26
      0.000032
      2.591786

```python
df.shape
```

    (2016, 6)

```python
fig = plt.figure()
ax = plt.subplot()
x = df['time']
y = df['expected return (USD/Eh)']
ax.plot(x,y)
ax.set_title('Expected Reward (USD/EH) in the pre-simulation period')
plt.setp(ax.get_xticklabels(), rotation=45)
plt.tight_layout()
plt.show()
```

### Generate simulation data

* First, try generating 1000 observations to see how our code works.

```python
from simulation_fixed_path import *

path = '../data/BTCdata_presim.csv'
df = pd.read_csv(path)
df['time'] = pd.to_datetime(df['time'])
df = df.rename(columns={'blocktime': 'block_times', 'price': 'prices', 'probability of success /Eh': 'winning_rates'})
df.tail()
```

      height
      time
      block_times
      prices
      winning_rates
      expected return (USD/Eh)

      2011
      551438
      2018-11-25 08:35:20
      6.250000
      3604.59
      0.000035
      1.591790

      2012
      551439
      2018-11-25 08:52:45
      17.416667
      3604.59
      0.000035
      1.610502

      2013
      551440
      2018-11-25 09:21:09
      28.400000
      3604.59
      0.000035
      1.630853

      2014
      551441
      2018-11-25 09:22:16
      1.116667
      3604.59
      0.000035
      1.585960

      2015
      551442
      2018-11-25 09:22:42
      0.433333
      3604.59
      0.000035
      1.577661

```python
# generate price data (1000 obs.)
# price shock = 0
init_price = df.loc[143, 'prices']
sim = simulation()
num_iter = 1000

df_price = pd.DataFrame()
for iter in range(num_iter):
    df_price['iter_{}'.format(iter)] = sim.generate_prices(init_price=init_price)
df_price.to_csv('/Volumes/Data/research/BDA/sim_prices_ps=0.csv', index=False)
```

```python
# generate r.v.'s following Exp(1) (1000 obs.)
# price shock = 0
sim = simulation()
num_iter = 1000

df_exprvs = pd.DataFrame()
for iter in range(num_iter):
    df_exprvs['iter_{}'.format(iter)] = sim.generate_exprvs()
df_exprvs.to_csv('/Volumes/Data/research/BDA/sim_exprvs.csv', index=False)
```

```python
df_exprvs = pd.read_csv('/Volumes/Data/research/BDA/simulation/sim_exprvs.csv')
df_price = pd.read_csv('/Volumes/Data/research/BDA/simulation/sim_prices_ps=0.csv')
```

The example below show how we can use the class for simulation defined in `simulation_fixed_path.py`.

```python
sim = simulation(prev_data=df)
```

```python
x = sim.block_times.cumsum()
y = sim.winning_rates
plt.plot(x,y)
plt.show()
```

```python
x = sim.block_times.cumsum()
y = sim.prices
plt.plot(x,y)
plt.show()
```

```python
x = sim.block_times.cumsum()
y = sim.hash_rates
plt.plot(x,y)
plt.show()
```

#### Simulation with a small data set

* Next, generate a small data set to estimate how long the simulation will take if we increase the number of iterations.

```python
# no price shock
## takes 74 minutes

num_iter = 1000
price_shock = 0

df_BTC = pd.DataFrame()
df_BCH = pd.DataFrame()
df_BTC_bdd = pd.DataFrame()
df_BCH_bdd = pd.DataFrame()
df_pseudoBCH = pd.DataFrame()
df_DAA_4 = pd.DataFrame()

for iter in range(num_iter):
    prices = df_price.loc[:, 'iter_{}'.format(iter)]
    exprvs = df_exprvs.loc[:, 'iter_{}'.format(iter)]
    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_BTC(prices=prices, exprvs=exprvs)
    df_BTC = pd.concat([df_BTC, _], axis=1)

    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_BCH(prices=prices, exprvs=exprvs)
    df_BCH = pd.concat([df_BCH, _], axis=1)

    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_BTC_bdd(prices=prices, exprvs=exprvs)
    df_BTC_bdd = pd.concat([df_BTC_bdd, _], axis=1)

    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_BCH_bdd(prices=prices, exprvs=exprvs)
    df_BCH_bdd = pd.concat([df_BCH_bdd, _], axis=1)

    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_pseudoBCH(prices=prices, exprvs=exprvs)
    df_pseudoBCH = pd.concat([df_pseudoBCH, _], axis=1)

    _ = pd.DataFrame()
    _['iter_{}'.format(iter)] = sim.sim_DAA_4(prices=prices, exprvs=exprvs)
    df_DAA_4 = pd.concat([df_DAA_4, _], axis=1)

df_BTC.to_csv('/Volumes/Data/research/BDA/simulation/BTC_blocktime_ps{}.csv'.format(price_shock))
df_BCH.to_csv('/Volumes/Data/research/BDA/simulation/BCH_blocktime_ps{}.csv'.format(price_shock))
df_BTC_bdd.to_csv('/Volumes/Data/research/BDA/simulation/BTC_bdd_blocktime_ps{}.csv'.format(price_shock))
df_BCH_bdd.to_csv('/Volumes/Data/research/BDA/simulation/BCH_bdd_blocktime_ps{}.csv'.format(price_shock))
df_pseudoBCH.to_csv('/Volumes/Data/research/BDA/simulation/pseudoBCH_blocktime_ps{}.csv'.format(price_shock))
df_DAA_4.to_csv('/Volumes/Data/research/BDA/simulation/DAA_4_blocktime_ps{}.csv'.format(price_shock))

```

```python
df_BTC = pd.read_csv('/Volumes/Data/research/BDA/simulation/BTC_blocktime_ps0.csv', index_col=0)
df_BCH = pd.read_csv('/Volumes/Data/research/BDA/simulation/BCH_blocktime_ps0.csv', index_col=0)
df_BTC_bdd = pd.read_csv('/Volumes/Data/research/BDA/simulation/BTC_bdd_blocktime_ps0.csv', index_col=0)
df_BCH_bdd = pd.read_csv('/Volumes/Data/research/BDA/simulation/BCH_bdd_blocktime_ps0.csv', index_col=0)
df_pseudoBCH = pd.read_csv('/Volumes/Data/research/BDA/simulation/pseudoBCH_blocktime_ps0.csv', index_col=0)
df_DAA_4 = pd.read_csv('/Volumes/Data/research/BDA/simulation/DAA_4_blocktime_ps0.csv', index_col=0)
```

```python
for df in [df_BTC, df_BTC_bdd, df_BCH, df_BCH_bdd, df_pseudoBCH, df_DAA_4]:
    mean, std, over60, over120, over180 = stats_fixed_path(df)
    print('mean: {:.3f}, std: {:.3f}, >60: {}, >120: {}, >180: {}'.format(mean, std, over60, over120, over180))
```

    mean: 14.792, std: 21.472, >60: 1.0, >120: 0.911, >180: 0.644
    mean: 14.784, std: 21.419, >60: 1.0, >120: 0.911, >180: 0.644
    mean: 10.035, std: 10.223, >60: 1.0, >120: 0.194, >180: 0.001
    mean: 10.035, std: 10.223, >60: 1.0, >120: 0.194, >180: 0.001
    mean: 10.017, std: 10.203, >60: 1.0, >120: 0.185, >180: 0.0
    mean: 10.092, std: 10.311, >60: 1.0, >120: 0.224, >180: 0.004

#### Visual comparison among different DAAs

```python
# import data
path = '../data/BTCdata_presim.csv'
df = pd.read_csv(path)
df['time'] = pd.to_datetime(df['time'])
df = df.rename(columns={'blocktime': 'block_times', 'price': 'prices', 'probability of success /Eh': 'winning_rates'})
#df.tail()

df_exprvs = pd.read_csv('/Volumes/Data/research/BDA/simulation/sim_exprvs.csv')
df_price = pd.read_csv('/Volumes/Data/research/BDA/simulation/sim_prices_ps=0.csv')
```

```python
# 22, 482
# 3, 6
no_iter = 3

prices = df_price.loc[:, 'iter_{}'.format(no_iter)]
exprvs = df_exprvs.loc[:,'iter_{}'.format(no_iter)]

sim_BTC = simulation(prev_data=df)
sim_BTC.sim_BTC(prices=prices, exprvs=exprvs)

mpl.pyplot.rcParams['figure.figsize'] = (10.0, 4.0)
x = sim_BTC.block_times.cumsum()
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax1.plot(x, sim_BTC.winning_rates)
ax1.set_title('Winning rate')
ax1.set_ylim(0.00002, 0.0001)
#ax1.tick_params(direction = "inout", length = 5)
ax1.set_xticks(np.array([0,50000,100000]))
ax2 = fig.add_subplot(1,2,2)
ax2.plot(x, sim_BTC.prices)
ax2.set_title('Price')
plt.show()
```

```python
sim_BTC = simulation(prev_data=df)
sim_BTC.sim_BTC(prices=prices, exprvs=exprvs)
sim_BCH = simulation(prev_data=df)
sim_BCH.sim_BCH(prices=prices, exprvs=exprvs)
```

    array([ 0.142,  9.027,  8.86 , ..., 24.216,  0.817, 30.432])

```python
# the number of blocks created in the simulations
print(sim_BTC.block_times.shape[0], sim_BCH.block_times.shape[0])
```

    7703 12048

* Below is Figure 6 in Section 6.2.

```python
mpl.pyplot.rcParams['figure.figsize'] = (10.0, 4.0)

fig = plt.figure()
time = np.arange(12096)*10/1440
ax1 = fig.add_subplot(1,2,1)
ax1.plot(time, prices[:12096])
ax1.set_xlabel('time (day)')
ax1.set_ylabel('Price (USD/BTC)')
ax1.set_title('Price Shock')

ax2 = fig.add_subplot(1,2,2)
ax2.bar(np.arange(12048), exprvs[:12048])
ax2.set_xlabel('Block number')
ax2.set_ylabel('$\delta(t) \sim$ Exp(1)')
ax2.set_title('Block Shock')
ax2.axvline(x=7703, ls='--', color='magenta')
plt.tight_layout()
fig.align_labels()
plt.show()
```

```python
sim_BTC = simulation(prev_data=df)
sim_BTC.sim_BTC(prices=prices, exprvs=exprvs)
sim_BCH = simulation(prev_data=df)
sim_BCH.sim_BCH(prices=prices, exprvs=exprvs)
```

    array([ 0.142,  9.027,  8.86 , ..., 24.216,  0.817, 30.432])

Given a price $S(t)$, we can compare the realized path with the optimal path.

In order to obtain the optimal winning rate in period $t$, $W^*(t)$, we need to solve the following equasion with respect to $W^*(t)$.

\[
W^*(t) \cdot \Phi(W^*(t) \cdot M \cdot S(t)) = 1/(60  B^*)
\]
, where
\[
\Phi(R) = u \cdot \frac{1}{1 + \exp(-s(R - c))}
\]
Note that $B^*$ is multiplied by 60 in order for the unit of time to be second.

The above equasion is equivalent to:
\[
W^*(t) = \frac{-\frac{1}{s} \log(W^*(t) \cdot u \cdot (60  B^*) - 1) + c}{M \cdot S(t)} =: f(W^*(t))
\]

Though the equasion cannot be solved explicitly, it can be solved numerically since $f$ is a contraction mapping on some interval.

* Below is Figure 7 in Sectino 6.2.

```python
%autoreload
import warnings
warnings.filterwarnings('ignore')
mpl.pyplot.rcParams['figure.figsize'] = (15.0, 12.0)

sim_list = [sim_BTC, sim_BCH]
title_list = ['DAA-1(2016)', 'DAA-2(144)']
plot_paths_2(exprvs=exprvs, sim_list=sim_list, title_list=title_list)
```

```python
# save the sample above as a csv
df_sample_BTC = pd.DataFrame()
df_sample_BTC['BTC_price'] = sim_BTC.prices
df_sample_BTC['BTC_winning_rate'] = sim_BTC.winning_rates
df_sample_BTC['BTC_blocktime'] = sim_BTC.block_times
df_sample_BTC['BTC_reward'] = sim_BTC.prices * sim_BTC.winning_rates * 12.5
df_sample_BTC['BTC_hash_rates'] = sim_BTC.hash_rates

df_sample_BCH = pd.DataFrame()
df_sample_BCH['BCH_price'] = sim_BCH.prices
df_sample_BCH['BCH_winning_rate'] = sim_BCH.winning_rates
df_sample_BCH['BCH_blocktime'] = sim_BCH.block_times
df_sample_BCH['BCH_reward'] = sim_BCH.prices * sim_BCH.winning_rates * 12.5
df_sample_BCH['BCH_hash_rates'] = sim_BCH.hash_rates

dir_sim='/Volumes/Data/research/BDA/simulation/'
df_sample_BTC.to_csv(dir_sim+'sample_path_BTC.csv', index=False)
df_sample_BCH.to_csv(dir_sim+'sample_path_BCH.csv', index=False)
```

### Experiment with a larger data set

* Additionally generate simulation data.
* We use the data with its sample size 5000.

```python
generate_price_paths(price_shock=0, num_iter=5000, filename='_5000obs')
generate_exprvs(num_iter=5000, filename='_5000obs')
#generate_price_paths(price_shock=2000, num_iter=5000, filename='_5000obs')
```

```python
path = '../data/BTCdata_presim.csv'
df = pd.read_csv(path)
df['time'] = pd.to_datetime(df['time'])
df = df.rename(columns={'blocktime': 'block_times', 'price': 'prices', 'probability of success /Eh': 'winning_rates'})
#df.head()
```

```python
# takes 18 hours
generate_simulation_data(num_iter=5000, prev_data=df)
```

#### Statistics

```python
dir_sim = '/Volumes/Data/research/BDA/simulation/'
filelist = [dir_sim+'BTC_blocktime_ps0_5000obs.csv',
            dir_sim+'BCH_blocktime_ps0_5000obs.csv',
            dir_sim+'BTC_bdd_blocktime_ps0_5000obs.csv',
            dir_sim+'BCH_bdd_blocktime_ps0_5000obs.csv',
            dir_sim+'pseudoBCH_blocktime_ps0_5000obs.csv',
            dir_sim+'DAA_4_blocktime_ps0_5000obs.csv',
            dir_sim+'BTC_blocktime_ps2000_5000obs.csv',
            dir_sim+'BCH_blocktime_ps2000_5000obs.csv',
            dir_sim+'BTC_bdd_blocktime_ps2000_5000obs.csv',
            dir_sim+'BCH_bdd_blocktime_ps2000_5000obs.csv',
            dir_sim+'pseudoBCH_blocktime_ps2000_5000obs.csv',
            dir_sim+'DAA_4_blocktime_ps2000_5000obs.csv'
            ]
```

```python
%autoreload
df_stats = make_stats(filelist=filelist, dir_sim=dir_sim)
```

* The table below corresponds to Table 2 in Section 6.3.

```python
df_stats
```

      BTC_blocktime_ps0_5000obs.csv
      BCH_blocktime_ps0_5000obs.csv
      BTC_bdd_blocktime_ps0_5000obs.csv
      BCH_bdd_blocktime_ps0_5000obs.csv
      pseudoBCH_blocktime_ps0_5000obs.csv
      DAA_4_blocktime_ps0_5000obs.csv
      BTC_blocktime_ps2000_5000obs.csv
      BCH_blocktime_ps2000_5000obs.csv
      BTC_bdd_blocktime_ps2000_5000obs.csv
      BCH_bdd_blocktime_ps2000_5000obs.csv
      pseudoBCH_blocktime_ps2000_5000obs.csv
      DAA_4_blocktime_ps2000_5000obs.csv

      mean
      14.044305
      11.242365
      14.042658
      11.242365
      11.226132
      11.290592
      77.352414
      11.287808
      77.060483
      11.287808
      11.262357
      11.313835

      std
      18.139603
      10.920091
      18.112977
      10.920091
      10.901839
      11.001274
      108.690643
      11.340362
      105.508436
      11.340362
      11.314051
      11.219285

      over60
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000

      over120
      0.850430
      0.183763
      0.850430
      0.183763
      0.178964
      0.212158
      1.000000
      0.945411
      1.000000
      0.945411
      0.943411
      0.735653

      over180
      0.533693
      0.002400
      0.533693
      0.002400
      0.002000
      0.002200
      1.000000
      0.551890
      1.000000
      0.551890
      0.549090
      0.281144

```python
df_stats.to_csv(dir_sim+'stats_5000obs.csv')
```

#### Compute optimal $w^*$

* (i) the grid size of the interval from which the initial value $w_0$ is chosen and (ii) the tolerance affects whether the contraction mapping successfully converges or not.

* For the moment, I choose `tol=1e-9` for (ii). (See the function `optimal_winning_rate` for details)

**Strategy**

* For $S \in [0, 10000]$, compute 'optimal_winning_rate'(S) and store the results $(W^*(S))_S$ as 'optimal_winning_rates.csv'.

* Compute $(W^*(S))_S$ with sufficiently fine grids; we can use it with interpolation.

```python
prices_7000_11000 = np.arange(7000, 11001, 50)
res_7000_11000 = []
for price in prices_7000_11000:
    temp = optimal_winning_rate(price, W_init_low=1e-5, W_init_high=1e-4, W_grid=1e-11, tol=1e-9, num_iter=3000)
    res_7000_11000.append(temp)
res_7000_11000 = np.array(res_7000_11000)
```

    ../code/simulation_fixed_path.py:922: RuntimeWarning: invalid value encountered in log

```python
_ = np.arange(100, 7000, 50)
_.shape
```

    (138,)

```python
res_ = np.hstack([res_under8000[:138], res_7000_11000])
res_.shape
```

    (219,)

```python
prices_6000_7000 = np.arange(6000, 7000, 50)
res_6000_7000 = []
for price in prices_6000_7000:
    temp = optimal_winning_rate(price, W_init_low=1e-5, W_init_high=1e-4, W_grid=1e-11, tol=1e-9, num_iter=3000)
    res_6000_7000.append(temp)
res_6000_7000 = np.array(res_6000_7000)
```

    ../code/simulation_fixed_path.py:922: RuntimeWarning: invalid value encountered in log

```python
res_6000_11000 = np.hstack([res_6000_7000, res_7000_11000])
res_6000_11000.shape
```

    (101,)

```python
prices_6000 = np.arange(100, 6000, 50)
res_6000 = []
for price in prices_6000:
    temp = optimal_winning_rate(price, W_init_low=1e-5, W_init_high=1e-3, W_grid=1e-11, tol=1e-9, num_iter=3000)
    res_6000.append(temp)
res_6000 = np.array(res_6000)
```

```python
df_w = pd.DataFrame()
df_w['opt_w'] =res
df_w.index = np.arange(100, 11001, 50)
df_w.head()
```

      opt_w

      100
      0.000480

      150
      0.000370

      200
      0.000306

      250
      0.000263

      300
      0.000232

```python
plt.plot(df_w)
plt.show()
```

```python
df_w.loc[100, 'opt_w']
```

    0.0004804473847941309

```python
dir_sim='/Volumes/Data/research/BDA/simulation/'
df_w.to_csv(dir_sim+'opt_w.csv', index=True)
```

## Appendix: Data Cleaning

```python
# This is the same function defined in the notebook for empirical analysis
def adjust_unixtime(raw_times):
    '''
    adjust block generation time.
    Conduct linear interpolation if the order of time stamps are reversed.

    Parameters
    ----------
    raw_times:
        pandas Series containing raw time stamps(unixtime)
        The index should starts at zero.

    Returns
    -------
    times:
        Adjusted time stamps.
    '''
    times = raw_times.copy()
    for i in range(times.shape[0] - 1):
        if times[i+1] - times[i] < 0:
            count = 1
            diff = -1
            while diff < 0:
                count += 1
                diff = times[i+count] - times[i]
            delta = times[i+count] - times[i]
            for j in range(count-1):
                times[i+j+1] = times[i] + (delta/count)*(j+1)

    return times
```

```python
path = '../data/BTCdata_2.csv'
df_original = pd.read_csv(path, header=0)
#df_original.head()
```

```python
columns = ['height', 'unixtime_precise', 'Close', 'probability of success /Eh', 'expected return (USD/Eh)']
df = df_original.loc[:, columns]
df['time'] = pd.to_datetime(df['unixtime_precise'].astype(int), unit='s')
name_dict = {'unixtime_precise':'unixtime', 'Close':'price'}
df = df.rename(columns=name_dict)
#df.head()

df['adjusted_unixtime'] = adjust_unixtime(df['unixtime'])
df['adjusted_blocktime'] = df['adjusted_unixtime'].diff()/60.0
df.head()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime

      0
      450001
      1485382343
      895.44
      0.000593
      6.935004
      2017-01-25 22:12:23
      1485382343
      NaN

      1
      450002
      1485383957
      894.97
      0.000593
      7.424957
      2017-01-25 22:39:17
      1485383957
      26.900000

      2
      450003
      1485384299
      894.97
      0.000593
      7.184180
      2017-01-25 22:44:59
      1485384299
      5.700000

      3
      450004
      1485384419
      894.97
      0.000593
      7.025926
      2017-01-25 22:46:59
      1485384419
      2.000000

      4
      450005
      1485384847
      894.97
      0.000593
      7.086069
      2017-01-25 22:54:07
      1485384847
      7.133333

```python
df.to_csv('../data/BTCdata_timeadjusted.csv', index=False)
```

```python
df.head()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime

      0
      450001
      1485382343
      895.44
      0.000593
      6.935004
      2017-01-25 22:12:23
      1485382343
      NaN

      1
      450002
      1485383957
      894.97
      0.000593
      7.424957
      2017-01-25 22:39:17
      1485383957
      26.900000

      2
      450003
      1485384299
      894.97
      0.000593
      7.184180
      2017-01-25 22:44:59
      1485384299
      5.700000

      3
      450004
      1485384419
      894.97
      0.000593
      7.025926
      2017-01-25 22:46:59
      1485384419
      2.000000

      4
      450005
      1485384847
      894.97
      0.000593
      7.086069
      2017-01-25 22:54:07
      1485384847
      7.133333

```python
df['adjusted_time'] = pd.to_datetime(df['adjusted_unixtime'].astype(int), unit='s')
df.head()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime
      adjusted_time

      0
      450001
      1485382343
      895.44
      0.000593
      6.935004
      2017-01-25 22:12:23
      1485382343
      NaN
      2017-01-25 22:12:23

      1
      450002
      1485383957
      894.97
      0.000593
      7.424957
      2017-01-25 22:39:17
      1485383957
      26.900000
      2017-01-25 22:39:17

      2
      450003
      1485384299
      894.97
      0.000593
      7.184180
      2017-01-25 22:44:59
      1485384299
      5.700000
      2017-01-25 22:44:59

      3
      450004
      1485384419
      894.97
      0.000593
      7.025926
      2017-01-25 22:46:59
      1485384419
      2.000000
      2017-01-25 22:46:59

      4
      450005
      1485384847
      894.97
      0.000593
      7.086069
      2017-01-25 22:54:07
      1485384847
      7.133333
      2017-01-25 22:54:07

```python
df_2018OctDec = df[(df['adjusted_time'] >= datetime(2018,10,1)) & (df['adjusted_time'] <= datetime(2019,1,1))]
df_2018OctDec = df_2018OctDec.reset_index(drop=True)
```

```python
df_2018OctDec.head()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime
      adjusted_time

      0
      543835
      1538352272
      6621.3
      0.000033
      2.720620
      2018-10-01 00:04:32
      1538352272
      18.633333
      2018-10-01 00:04:32

      1
      543836
      1538352799
      6621.3
      0.000033
      2.711797
      2018-10-01 00:13:19
      1538352799
      8.783333
      2018-10-01 00:13:19

      2
      543837
      1538352841
      6621.3
      0.000033
      2.694189
      2018-10-01 00:14:01
      1538352841
      0.700000
      2018-10-01 00:14:01

      3
      543838
      1538352940
      6621.3
      0.000033
      2.697526
      2018-10-01 00:15:40
      1538352940
      1.650000
      2018-10-01 00:15:40

      4
      543839
      1538353463
      6621.3
      0.000033
      2.714195
      2018-10-01 00:24:23
      1538353463
      8.716667
      2018-10-01 00:24:23

```python
df_2018OctDec.tail()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime
      adjusted_time

      12619
      556454
      1546298203
      3692.35
      0.000041
      1.918427
      2018-12-31 23:16:43
      1546298203
      4.900000
      2018-12-31 23:16:43

      12620
      556455
      1546299695
      3696.16
      0.000041
      1.934937
      2018-12-31 23:41:35
      1546299695
      24.866667
      2018-12-31 23:41:35

      12621
      556456
      1546299762
      3696.16
      0.000041
      1.917165
      2018-12-31 23:42:42
      1546299762
      1.116667
      2018-12-31 23:42:42

      12622
      556457
      1546300239
      3696.16
      0.000041
      1.920990
      2018-12-31 23:50:39
      1546300239
      7.950000
      2018-12-31 23:50:39

      12623
      556458
      1546300592
      3696.16
      0.000041
      1.918471
      2018-12-31 23:56:32
      1546300592
      5.883333
      2018-12-31 23:56:32

```python
df_2018OctDec.to_csv('../data/BTCdata_2018OctDec.csv', index=False)
```

```python
df_2018OctDec = pd.read_csv('../data/BTCdata_2018OctDec.csv')
df_2018OctDec.head()
```

      height
      unixtime
      price
      probability of success /Eh
      expected return (USD/Eh)
      time
      adjusted_unixtime
      adjusted_blocktime
      adjusted_time

      0
      543835
      1538352272
      6621.3
      0.000033
      2.720620
      2018-10-01 00:04:32
      1538352272
      18.633333
      2018-10-01 00:04:32

      1
      543836
      1538352799
      6621.3
      0.000033
      2.711797
      2018-10-01 00:13:19
      1538352799
      8.783333
      2018-10-01 00:13:19

      2
      543837
      1538352841
      6621.3
      0.000033
      2.694189
      2018-10-01 00:14:01
      1538352841
      0.700000
      2018-10-01 00:14:01

      3
      543838
      1538352940
      6621.3
      0.000033
      2.697526
      2018-10-01 00:15:40
      1538352940
      1.650000
      2018-10-01 00:15:40

      4
      543839
      1538353463
      6621.3
      0.000033
      2.714195
      2018-10-01 00:24:23
      1538353463
      8.716667
      2018-10-01 00:24:23

```python
columns = ['height', 'adjusted_time', 'adjusted_blocktime', 'price', 'probability of success /Eh', 'expected return (USD/Eh)']
df_sim = df_2018OctDec.loc[:, columns]
name_dict = {'adjusted_time':'time', 'adjusted_blocktime':'blocktime'}
df_sim = df_sim.rename(columns=name_dict)
df_sim.head()
```

      height
      time
      blocktime
      price
      probability of success /Eh
      expected return (USD/Eh)

      0
      543835
      2018-10-01 00:04:32
      18.633333
      6621.3
      0.000033
      2.720620

      1
      543836
      2018-10-01 00:13:19
      8.783333
      6621.3
      0.000033
      2.711797

      2
      543837
      2018-10-01 00:14:01
      0.700000
      6621.3
      0.000033
      2.694189

      3
      543838
      2018-10-01 00:15:40
      1.650000
      6621.3
      0.000033
      2.697526

      4
      543839
      2018-10-01 00:24:23
      8.716667
      6621.3
      0.000033
      2.714195

```python
df_sim.to_csv('../data/BTCdata_2018OctDec_sim.csv', index=False)
```

```python
fig = plt.figure()
ax = plt.subplot()
x = df_sim['time']
y = df_sim['expected return (USD/Eh)']
ax.plot(x,y)
plt.setp(ax.get_xticklabels(), rotation=45)
plt.tight_layout()
plt.show()
```

```python
df_sim['time'] = pd.to_datetime(df_sim['time'])
df_Nov = df_sim[(df_sim['time'] >= datetime(2018,11,1)) & (df_sim['time'] <= datetime(2018,12,1))]
df_Nov.head()
```

      height
      time
      blocktime
      price
      probability of success /Eh
      expected return (USD/Eh)

      4379
      548214
      2018-11-01 00:18:31
      20.666667
      6304.82
      0.000032
      2.578444

      4380
      548215
      2018-11-01 00:22:32
      4.016667
      6304.82
      0.000032
      2.572693

      4381
      548216
      2018-11-01 00:23:22
      0.833333
      6304.82
      0.000032
      2.557799

      4382
      548217
      2018-11-01 00:24:57
      1.583333
      6304.82
      0.000032
      2.558311

      4383
      548218
      2018-11-01 00:26:47
      1.833333
      6304.82
      0.000032
      2.559274

```python
df_Nov.shape
```

    (3870, 6)

```python
fig = plt.figure()
ax = plt.subplot()
x = df_Nov['time']
y = df_Nov['expected return (USD/Eh)']
ax.plot(x,y)
plt.setp(ax.get_xticklabels(), rotation=45)
plt.tight_layout()
plt.show()
```

```python
df_Nov['expected return (USD/Eh)'].idxmin()
```

    7607

```python
df_Nov['expected return (USD/Eh)'][7607]
```

    1.5776613819999998

```python
df_Nov.loc[7607]
```

    height                                     551442
    time                          2018-11-25 09:22:42
    blocktime                                0.433333
    price                                     3604.59
    probability of success /Eh            3.49948e-05
    expected return (USD/Eh)                  1.57766
    Name: 7607, dtype: object

* The lowest expected return is recorded at 2018-11-25 09:22:42(height 551442).

```python
height_last = 551442
T = 2016
df_pre_simulation = df_Nov[(df_Nov['height'] >= height_last-T+1) & (df_Nov['height'] <= height_last)]
```

```python
df_pre_simulation.head()
```

      height
      time
      blocktime
      price
      probability of success /Eh
      expected return (USD/Eh)

      5592
      549427
      2018-11-09 19:26:14
      5.783333
      6343.32
      0.000032
      2.587754

      5593
      549428
      2018-11-09 19:26:20
      0.100000
      6343.32
      0.000032
      2.574300

      5594
      549429
      2018-11-09 19:37:51
      11.516667
      6341.26
      0.000032
      2.593078

      5595
      549430
      2018-11-09 19:45:57
      8.100000
      6341.26
      0.000032
      2.595087

      5596
      549431
      2018-11-09 19:54:51
      8.900000
      6341.26
      0.000032
      2.591786

```python
df_pre_simulation.tail()
```

      height
      time
      blocktime
      price
      probability of success /Eh
      expected return (USD/Eh)

      7603
      551438
      2018-11-25 08:35:20
      6.250000
      3604.59
      0.000035
      1.591790

      7604
      551439
      2018-11-25 08:52:45
      17.416667
      3604.59
      0.000035
      1.610502

      7605
      551440
      2018-11-25 09:21:09
      28.400000
      3604.59
      0.000035
      1.630853

      7606
      551441
      2018-11-25 09:22:16
      1.116667
      3604.59
      0.000035
      1.585960

      7607
      551442
      2018-11-25 09:22:42
      0.433333
      3604.59
      0.000035
      1.577661

```python
df_pre_simulation.shape
```

    (2016, 6)

```python
df_pre_simulation.to_csv('../data/BTCdata_presim.csv', index=False)
```

```python

```


Lambda School Data Science

*Unit 2, Sprint 1, Module 2*

---

# Regression & Classification, Module 2
- Go from simple regression (1 feature) to multiple regression (2+ features)
- Get and plot coefficients
- Use regression metrics: MAE (Mean Absolute Error) and $R^2$ Score
- Understand how ordinary least squares regression minimizes the sum of squared errors
- Explain why overfitting is a problem. Do train/test split

### Setup

You can work locally (follow the [local setup instructions](https://lambdaschool.github.io/ds/unit2/local/)) or on Colab (run the code cell below).

```python
import os, sys
in_colab = 'google.colab' in sys.modules

# If you're in Colab...
if in_colab:
    # Pull files from Github repo
    os.chdir('/content')
    !git init .
    !git remote add origin https://github.com/LambdaSchool/DS-Unit-2-Regression-Classification.git
    !git pull origin master

    # Install required python packages
    !pip install -r requirements.txt

    # Change into directory for module
    os.chdir('module2')
```

```python
# Ignore this Numpy warning when using Plotly Express:
# FutureWarning: Method .ptp is deprecated and will be removed in a future version. Use numpy.ptp instead.
import warnings
warnings.filterwarnings(action='ignore', category=FutureWarning, module='numpy')
```

# Go from simple regression (1 feature) to multiple regression (2+ features)

## Overview: Predict Elections! 🗳

```python
import pandas as pd
import plotly.express as px

df = pd.read_csv('../data/bread_peace_voting.csv')
px.scatter(
    df,
    x='Average Recent Growth in Personal Incomes',
    y='Incumbent Party Vote Share',
    text='Year',
    title='US Presidential Elections, 1952-2016',
    trendline='ols',  # Ordinary Least Squares
)
```

<meta charset="utf-8" />

#### Douglas Hibbs, [Background Information on the ‘Bread and Peace’ Model of Voting in Postwar US Presidential Elections](https://douglas-hibbs.com/background-information-on-bread-and-peace-voting-in-us-presidential-elections/)

> Aggregate two-party vote shares going to candidates of the party holding the presidency during the postwar era are well explained by just two fundamental determinants:

> (1) Positively by weighted-average growth of per capita real disposable personal income over the term.
> (2) Negatively by cumulative US military fatalities (scaled to population) owing to unprovoked, hostile deployments of American armed forces in foreign wars.

#### Data sources
- 1952-2012: Douglas Hibbs, [2014 lecture at Deakin University Melbourne](http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf), Slide 40
- 2016, Vote Share: [The American Presidency Project](https://www.presidency.ucsb.edu/statistics/elections)
- 2016, Recent Growth in Personal Incomes: [The 2016 election economy: the "Bread and Peace" model final forecast](https://angrybearblog.com/2016/11/the-2016-election-economy-the-bread-and-peace-model-final-forecast.html)
- 2016, US Military Fatalities: Assumption that Afghanistan War fatalities in 2012-16 occured at the same rate as 2008-12

> Fatalities denotes the cumulative number of American military fatalities per millions of US population the in Korea, Vietnam, Iraq and Afghanistan wars during the presidential terms preceding the 1952, 1964, 1968, 1976 and 2004, 2008 and 2012 elections. —[Hibbs](http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf), Slide 33

## Follow Along

Look at the data

```python
df
```

      Year
      Incumbent Party Candidate
      Other Candidate
      Average Recent Growth in Personal Incomes
      US Military Fatalities per Million
      Incumbent Party Vote Share

      0
      1952
      Stevenson
      Eisenhower
      2.40
      190
      44.60

      1
      1956
      Eisenhower
      Stevenson
      2.89
      0
      57.76

      2
      1960
      Nixon
      Kennedy
      0.85
      0
      49.91

      3
      1964
      Johnson
      Goldwater
      4.21
      1
      61.34

      4
      1968
      Humphrey
      Nixon
      3.02
      146
      49.60

      5
      1972
      Nixon
      McGovern
      3.62
      0
      61.79

      6
      1976
      Ford
      Carter
      1.08
      2
      48.95

      7
      1980
      Carter
      Reagan
      -0.39
      0
      44.70

      8
      1984
      Reagan
      Mondale
      3.86
      0
      59.17

      9
      1988
      Bush, Sr.
      Dukakis
      2.27
      0
      53.94

      10
      1992
      Bush, Sr.
      Clinton
      0.38
      0
      46.55

      11
      1996
      Clinton
      Dole
      1.04
      0
      54.74

      12
      2000
      Gore
      Bush, Jr.
      2.36
      0
      50.27

      13
      2004
      Bush, Jr.
      Kerry
      1.72
      4
      51.24

      14
      2008
      McCain
      Obama
      0.10
      14
      46.32

      15
      2012
      Obama
      Romney
      0.95
      5
      52.00

      16
      2016
      Clinton
      Trump
      0.10
      5
      48.20

What's the average Incumbent Party Vote Share?

```python
df['Incumbent Party Vote Share'].mean()
```

    51.82823529411765

Add another feature to the scatterplot

```python
px.scatter_3d(
df,
x = 'Average Recent Growth in Personal Incomes',
y = 'US Military Fatalities per Million',
z = 'Incumbent Party Vote Share',
text = 'Year',
title = 'US Presidential Elections'
)
```

<meta charset="utf-8" />

Plot the hyperplane of best fit

```python
import itertools
import numpy as np
import plotly.express as px
import plotly.graph_objs as go
from sklearn.linear_model import LinearRegression

def regression_3d(df, x, y, z, num=100, **kwargs):
    """
    Visualize linear regression in 3D: 2 features + 1 target

    df : Pandas DataFrame
    x : string, feature 1 column in df
    y : string, feature 2 column in df
    z : string, target column in df
    num : integer, number of quantiles for each feature
    """

    # Plot data
    fig = px.scatter_3d(df, x, y, z, **kwargs)

    # Fit Linear Regression
    features = [x, y]
    target = z
    model = LinearRegression()
    model.fit(df[features], df[target])

    # Define grid of coordinates in the feature space
    xmin, xmax = df[x].min(), df[x].max()
    ymin, ymax = df[y].min(), df[y].max()
    xcoords = np.linspace(xmin, xmax, num)
    ycoords = np.linspace(ymin, ymax, num)
    coords = list(itertools.product(xcoords, ycoords))

    # Make predictions for the grid
    predictions = model.predict(coords)
    Z = predictions.reshape(num, num).T

    # Plot predictions as a 3D surface (plane)
    fig.add_trace(go.Surface(x=xcoords, y=ycoords, z=Z))

    return fig
```

```python
regression_3d(
df,
x = 'Average Recent Growth in Personal Incomes',
y = 'US Military Fatalities per Million',
z = 'Incumbent Party Vote Share',
text = 'Year',
title = 'US Presidential Elections'
)
```

<meta charset="utf-8" />

Fit Linear Regression with 2 features

- Jake VanderPlas, [_Python Data Science Handbook,_ Chapter 5.2, Introducing Scikit-Learn — Basics of the API](https://jakevdp.github.io/PythonDataScienceHandbook/05.02-introducing-scikit-learn.html#Basics-of-the-API)
- Scikit-Learn documentation, [sklearn.linear_model.LinearRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html)

```python
# 1. Import the appropriate estimator class from Scikit-Learn

from sklearn.linear_model import LinearRegression

# 2. Instantiate this class

model = LinearRegression()

# 3. Arrange X features matrix & y target vector

features = ['Average Recent Growth in Personal Incomes', 'US Military Fatalities per Million']
target = 'Incumbent Party Vote Share'

X = df[features]
y = df[target]

# 4. Fit the model

model.fit(X, y)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

## Challenge

# Get and plot coefficients

```python
model.intercept_
```

    46.88551694148949

```python
model.coef_
```

    array([ 3.40621407, -0.05375223])

 y = coef1 * x1 + coef2 * x2 + b

## Overview

## Follow Along

What's the equation for the hyperplane?

Can you relate the intercept and coefficients to what you see in the plot?

```python

```

What if ...

Income growth = 0%, fatalities = 0

```python
model.predict([[0, 0]])
```

    array([46.88551694])

Income growth = 1% (fatalities = 0)

```python
model.predict([[1, 0]])
```

    array([50.29173101])

The difference between these predictions = ?

```python
model.predict([[1, 0]]) - model.predict([[0, 0]])
```

    array([3.40621407])

What if... income growth = 2% (fatalities = 0)

```python

```

The difference between these predictions = ?

```python
model.predict([[2, 0]]) - model.predict([[1, 0]])
```

    array([3.40621407])

What if... (income growth=2%) fatalities = 100

```python

```

The difference between these predictions = ?

```python

```

What if income growth = 3% (fatalities = 100)

```python

```

The difference between these predictions = ?

```python

```

What if (income growth = 3%) fatalities = 200

```python

```

The difference between these predictions = ?

```python

```

Plot coefficients

```python

```

## Challenge

#  Use regression metrics: MAE (Mean Absolute Error) and $R^2$ Score

## Overview

## Follow Along

How's the error?

```python
y_pred = model.predict(X)

df["Predicted"] = y_pred
df['Error'] = y_pred - y
df
```

      Year
      Incumbent Party Candidate
      Other Candidate
      Average Recent Growth in Personal Incomes
      US Military Fatalities per Million
      Incumbent Party Vote Share
      Predicted
      Error

      0
      1952
      Stevenson
      Eisenhower
      2.40
      190
      44.60
      44.847507
      0.247507

      1
      1956
      Eisenhower
      Stevenson
      2.89
      0
      57.76
      56.729476
      -1.030524

      2
      1960
      Nixon
      Kennedy
      0.85
      0
      49.91
      49.780799
      -0.129201

      3
      1964
      Johnson
      Goldwater
      4.21
      1
      61.34
      61.171926
      -0.168074

      4
      1968
      Humphrey
      Nixon
      3.02
      146
      49.60
      49.324458
      -0.275542

      5
      1972
      Nixon
      McGovern
      3.62
      0
      61.79
      59.216012
      -2.573988

      6
      1976
      Ford
      Carter
      1.08
      2
      48.95
      50.456724
      1.506724

      7
      1980
      Carter
      Reagan
      -0.39
      0
      44.70
      45.557093
      0.857093

      8
      1984
      Reagan
      Mondale
      3.86
      0
      59.17
      60.033503
      0.863503

      9
      1988
      Bush, Sr.
      Dukakis
      2.27
      0
      53.94
      54.617623
      0.677623

      10
      1992
      Bush, Sr.
      Clinton
      0.38
      0
      46.55
      48.179878
      1.629878

      11
      1996
      Clinton
      Dole
      1.04
      0
      54.74
      50.427980
      -4.312020

      12
      2000
      Gore
      Bush, Jr.
      2.36
      0
      50.27
      54.924182
      4.654182

      13
      2004
      Bush, Jr.
      Kerry
      1.72
      4
      51.24
      52.529196
      1.289196

      14
      2008
      McCain
      Obama
      0.10
      14
      46.32
      46.473607
      0.153607

      15
      2012
      Obama
      Romney
      0.95
      5
      52.00
      49.852659
      -2.147341

      16
      2016
      Clinton
      Trump
      0.10
      5
      48.20
      46.957377
      -1.242623

How does this compare to guessing?

```python
df['Error'].mean()
```

    -3.761696836377001e-15

```python
df['Error'].abs().mean()
```

    1.3975663494016117

```python
# Mean Absolute Error (MAE)
from sklearn.metrics import mean_absolute_error

mean_absolute_error(y, y_pred)
```

    1.3975663494016117

How does this compare to guessing?

```python
# What was the average vote share?

y.mean()
```

    51.82823529411765

```python
# What if we guessed this number for every election?
guesses = [y.mean()] * len(y)
```

```python
# How far off would this be on average?

mean_absolute_error(y, guesses)
```

    4.463806228373702

Plot the "residuals" (errors)

```python
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_error, r2_score

def regression_residuals(df, feature, target, m, b):
    """
    Visualize linear regression, with residual errors,
    in 2D: 1 feature + 1 target.

    Use the m & b parameters to "fit the model" manually.

    df : Pandas DataFrame
    feature : string, feature column in df
    target : string, target column in df
    m : numeric, slope for linear equation
    b : numeric, intercept for linear requation
    """

    # Plot data
    df.plot.scatter(feature, target)

    # Make predictions
    x = df[feature]
    y = df[target]
    y_pred = m*x + b

    # Plot predictions
    plt.plot(x, y_pred)

    # Plot residual errors
    for x, y1, y2 in zip(x, y, y_pred):
        plt.plot((x, x), (y1, y2), color='grey')

    # Print regression metrics
    mae = mean_absolute_error(y, y_pred)
    r2 = r2_score(y, y_pred)
    print('Mean Absolute Error:', mae)
    print('R^2:', r2)

```

```python
feature = 'Average Recent Growth in Personal Incomes'
regression_residuals(df, feature, target, m=0, b=y.mean())
```

## Challenge

# Understand how ordinary least squares regression minimizes the sum of squared errors

## Overview

But Ordinary Least Squares Regression *doesn't* directly minimize MAE or R^2...

1. Guess & Check
2. Linear Algebra

## Follow Along

### Guess & Check

```python
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

def regression_squared_errors(df, feature, target, m, b):
    """
    Visualize linear regression, with squared errors,
    in 2D: 1 feature + 1 target.

    Use the m & b parameters to "fit the model" manually.

    df : Pandas DataFrame
    feature : string, feature column in df
    target : string, target column in df
    m : numeric, slope for linear equation
    b : numeric, intercept for linear requation
    """

    # Plot data
    fig = plt.figure(figsize=(7,7))
    ax = plt.axes()
    df.plot.scatter(feature, target, ax=ax)

    # Make predictions
    x = df[feature]
    y = df[target]
    y_pred = m*x + b

    # Plot predictions
    ax.plot(x, y_pred)

    # Plot squared errors
    xmin, xmax = ax.get_xlim()
    ymin, ymax = ax.get_ylim()
    scale = (xmax-xmin)/(ymax-ymin)
    for x, y1, y2 in zip(x, y, y_pred):
        bottom_left = (x, min(y1, y2))
        height = abs(y1 - y2)
        width = height * scale
        ax.add_patch(Rectangle(xy=bottom_left, width=width, height=height, alpha=0.1))

    # Print regression metrics
    mse = mean_squared_error(y, y_pred)
    rmse = np.sqrt(mse)
    mae = mean_absolute_error(y, y_pred)
    r2 = r2_score(y, y_pred)
    print('Mean Squared Error:', mse)
    print('Root Mean Squared Error:', rmse)
    print('Mean Absolute Error:', mae)
    print('R^2:', r2)
```

```python
regression_squared_errors(df, feature, target, m=3, b=46)
```

```python
from ipywidgets import interact, fixed

interact(
regression_squared_errors,
    df=fixed(df),
    feature=fixed(feature),
    target=fixed(target),
    m=(-5, 5, 0.5),
    b=(40, 60, 0.6)
);
```

    interactive(children=(FloatSlider(value=0.0, description='m', max=5.0, min=-5.0, step=0.5), FloatSlider(value=…

### Linear Algebra

The same result that is found by minimizing the sum of the squared errors can be also found through a linear algebra process known as the "Least Squares Solution:"

\begin{align}
\hat{\beta} = (X^{T}X)^{-1}X^{T}y
\end{align}

Before we can work with this equation in its linear algebra form we have to understand how to set up the matrices that are involved in this equation.

### The $\beta$ vector

The $\beta$ vector represents all the parameters that we are trying to estimate, our $y$ vector and $X$ matrix values are full of data from our dataset. The $\beta$ vector holds the variables that we are solving for: $\beta_0$ and $\beta_1$

Now that we have all of the necessary parts we can set them up in the following equation:

\begin{align}
y = X \beta + \epsilon
\end{align}

Since our $\epsilon$ value represents **random** error we can assume that it will equal zero on average.

\begin{align}
y = X \beta
\end{align}

The objective now is to isolate the $\beta$ matrix. We can do this by pre-multiplying both sides by "X transpose" $X^{T}$.

\begin{align}
X^{T}y =  X^{T}X \beta
\end{align}

Since anything times its transpose will result in a square matrix, if that matrix is then an invertible matrix, then we should be able to multiply both sides by its inverse to remove it from the right hand side. (We'll talk tomorrow about situations that could lead to $X^{T}X$ not being invertible.)

\begin{align}
(X^{T}X)^{-1}X^{T}y =  (X^{T}X)^{-1}X^{T}X \beta
\end{align}

Since any matrix multiplied by its inverse results in the identity matrix, and anything multiplied by the identity matrix is itself, we are left with only $\beta$ on the right hand side:

\begin{align}
(X^{T}X)^{-1}X^{T}y = \hat{\beta}
\end{align}

We will now call it "beta hat" $\hat{\beta}$ because it now represents our estimated values for $\beta_0$ and $\beta_1$

### Lets calculate our $\beta$ coefficients with numpy!

```python
# This is NOT an objective you'll be tested on. It's just a demo.

# X is a matrix. Add constant for the intercept.
from statsmodels.api import add_constant
X = add_constant(df[feature].values)
print('X')
print(X)

# y is a column vector
y = df[target].values[:, np.newaxis]
print('y')
print(y)

# Least squares solution in code
X_transpose = X.T
X_transpose_X = X_transpose @ X
X_transpose_X_inverse = np.linalg.inv(X_transpose_X)
X_transpose_y = X_transpose @ y
beta_hat = X_transpose_X_inverse @ X_transpose_y

print('Beta Hat')
print(beta_hat)
```

    X
    [[ 1.    2.4 ]
     [ 1.    2.89]
     [ 1.    0.85]
     [ 1.    4.21]
     [ 1.    3.02]
     [ 1.    3.62]
     [ 1.    1.08]
     [ 1.   -0.39]
     [ 1.    3.86]
     [ 1.    2.27]
     [ 1.    0.38]
     [ 1.    1.04]
     [ 1.    2.36]
     [ 1.    1.72]
     [ 1.    0.1 ]
     [ 1.    0.95]
     [ 1.    0.1 ]]
    y
    [[44.6 ]
     [57.76]
     [49.91]
     [61.34]
     [49.6 ]
     [61.79]
     [48.95]
     [44.7 ]
     [59.17]
     [53.94]
     [46.55]
     [54.74]
     [50.27]
     [51.24]
     [46.32]
     [52.  ]
     [48.2 ]]
    Beta Hat
    [[46.49920976]
     [ 2.97417709]]

```python
# Scikit-learn gives the exact same results
from sklearn.linear_model import LinearRegression
model = LinearRegression()
features = ['Average Recent Growth in Personal Incomes']
target = 'Incumbent Party Vote Share'
X = df[features]
y = df[target]
model.fit(X, y)
model.intercept_, model.coef_
```

    (46.499209757741625, array([2.97417709]))

## Challenge

# Explain why overfitting is a problem. Do train/test split

## Overview

#### Jake VanderPlas, [_Python Data Science Handbook,_ Chapter 5.3](https://jakevdp.github.io/PythonDataScienceHandbook/05.03-hyperparameters-and-model-validation.html#The-Bias-variance-trade-off)

Fundamentally, the question of "the best model" is about finding a sweet spot in the tradeoff between bias and variance. Consider the following figure, which presents two regression fits to the same dataset:

The model on the left attempts to find a straight-line fit through the data. Because the data are intrinsically more complicated than a straight line, the straight-line model will never be able to describe this dataset well. Such a model is said to _underfit_ the data: that is, it does not have enough model flexibility to suitably account for all the features in the data; another way of saying this is that the model has high _bias_.

The model on the right attempts to fit a high-order polynomial through the data. Here the model fit has enough flexibility to nearly perfectly account for the fine features in the data, but even though it very accurately describes the training data, its precise form seems to be more reflective of the particular noise properties of the data rather than the intrinsic properties of whatever process generated that data. Such a model is said to _overfit_ the data: that is, it has so much model flexibility that the model ends up accounting for random errors as well as the underlying data distribution; another way of saying this is that the model has high _variance_.

From the scores associated with these two models, we can make an observation that holds more generally:

- For high-bias models, the performance of the model on the validation set is similar to the performance on the training set.

- For high-variance models, the performance of the model on the validation set is far worse than the performance on the training set.

If we imagine that we have some ability to tune the model complexity, we would expect the training score and validation score to behave as illustrated in the following figure:

The diagram shown here is often called a validation curve, and we see the following essential features:

- The training score is everywhere higher than the validation score. This is generally the case: the model will be a better fit to data it has seen than to data it has not seen.
- For very low model complexity (a high-bias model), the training data is under-fit, which means that the model is a poor predictor both for the training data and for any previously unseen data.
- For very high model complexity (a high-variance model), the training data is over-fit, which means that the model predicts the training data very well, but fails for any previously unseen data.
- For some intermediate value, the validation curve has a maximum. This level of complexity indicates a suitable trade-off between bias and variance.

The means of tuning the model complexity varies from model to model.

## Follow Along

Wrangle New York City property sales data

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

```python
# Read New York City property sales data, from first 4 months of 2019.
# Dataset has 23040 rows, 21 columns.
df = pd.read_csv('../data/NYC_Citywide_Rolling_Calendar_Sales.csv')
assert df.shape == (23040, 21)

# Change column names. Replace spaces with underscores
df.columns = [col.replace(' ', '_') for col in df]

# Remove symbols from SALE_PRICE string, convert to integer
df['SALE_PRICE'] = (
    df['SALE_PRICE']
    .str.replace('$','')
    .str.replace('-','')
    .str.replace(',','')
    .astype(int)
)

# Keep subset of rows:
# Tribeca neighborhood, Condos - Elevator Apartments,
# 1 unit, sale price more than $1, less than $35 million
mask = (
    (df['NEIGHBORHOOD'].str.contains('TRIBECA')) &
    (df['BUILDING_CLASS_CATEGORY'] == '13 CONDOS - ELEVATOR APARTMENTS') &
    (df['TOTAL_UNITS'] == 1) &
    (df['SALE_PRICE'] > 0) &
    (df['SALE_PRICE'] < 35000000)
)
df = df[mask]

# Data now has 90 rows, 21 columns
assert df.shape == (90, 21)

# Convert SALE_DATE to datetime
df['SALE_DATE'] = pd.to_datetime(df['SALE_DATE'], infer_datetime_format=True)

from ipywidgets import interact
import pandas as pd
from sklearn.linear_model import LinearRegression

# Read New York City property sales data, from first 4 months of 2019.
# Dataset has 23040 rows, 21 columns.
df = pd.read_csv('../data/NYC_Citywide_Rolling_Calendar_Sales.csv')
assert df.shape == (23040, 21)

# Change column names. Replace spaces with underscores
df.columns = [col.replace(' ', '_') for col in df]

# Remove symbols from SALE_PRICE string, convert to integer
df['SALE_PRICE'] = (
    df['SALE_PRICE']
    .str.replace('$','')
    .str.replace('-','')
    .str.replace(',','')
    .astype(int)
)

# Keep subset of rows:
# Tribeca neighborhood, Condos - Elevator Apartments,
# 1 unit, sale price more than $1, less than $35 million
mask = (
    (df['NEIGHBORHOOD'].str.contains('TRIBECA')) &
    (df['BUILDING_CLASS_CATEGORY'] == '13 CONDOS - ELEVATOR APARTMENTS') &
    (df['TOTAL_UNITS'] == 1) &
    (df['SALE_PRICE'] > 0) &
    (df['SALE_PRICE'] < 35000000)
)
df = df[mask]

# Data now has 90 rows, 21 columns
assert df.shape == (90, 21)

# Convert SALE_DATE to datetime
df['SALE_DATE'] = pd.to_datetime(df['SALE_DATE'], infer_datetime_format=True)

# Arrange X features matrix & y target vector
features = ['GROSS_SQUARE_FEET']
target = 'SALE_PRICE'
X = df[features]
y = df[target]
```

Do random train/test spliit

```python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=11)
```

Repeatedly fit increasingly complex models, and keep track of the scores

```python
from IPython.display import display, HTML
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# Credit: Jake VanderPlas, Python Data Science Handbook, Chapter 5.3
# https://jakevdp.github.io/PythonDataScienceHandbook/05.03-hyperparameters-and-model-validation.html#Validation-curves-in-Scikit-Learn
def PolynomialRegression(degree=2, **kwargs):
    return make_pipeline(PolynomialFeatures(degree),
                         LinearRegression(**kwargs))

polynomial_degrees = range(1, 10, 2)
train_r2s = []
test_r2s = []

for degree in polynomial_degrees:
    model = PolynomialRegression(degree)
    display(HTML(f'Polynomial degree={degree}'))

    model.fit(X_train, y_train)
    train_r2 = model.score(X_train, y_train)
    test_r2 = model.score(X_test, y_test)
    display(HTML(f'<b style="color: blue">Train R2 {train_r2:.2f}</b>'))
    display(HTML(f'<b style="color: red">Test R2 {test_r2:.2f}</b>'))

    plt.scatter(X_train, y_train, color='blue', alpha=0.5)
    plt.scatter(X_test, y_test, color='red', alpha=0.5)
    plt.xlabel(features)
    plt.ylabel(target)

    x_domain = np.linspace(X.min(), X.max())
    curve = model.predict(x_domain)
    plt.plot(x_domain, curve, color='blue')
    plt.show()
    display(HTML('<hr/>'))

    train_r2s.append(train_r2)
    test_r2s.append(test_r2)

display(HTML('Validation Curve'))
plt.plot(polynomial_degrees, train_r2s, color='blue', label='Train')
plt.plot(polynomial_degrees, test_r2s, color='red', label='Test')
plt.xlabel('Model Complexity (Polynomial Degree)')
plt.ylabel('R^2 Score')
plt.legend()
plt.show()
```

## Challenge

# Review

# Sources

Jake VanderPlas, [_Python Data Science Handbook,_ Chapter 5.3](https://jakevdp.github.io/PythonDataScienceHandbook/05.03-hyperparameters-and-model-validation.html#The-Bias-variance-trade-off)

#### Douglas Hibbs, [Background Information on the ‘Bread and Peace’ Model of Voting in Postwar US Presidential Elections](https://douglas-hibbs.com/background-information-on-bread-and-peace-voting-in-us-presidential-elections/)

> Aggregate two-party vote shares going to candidates of the party holding the presidency during the postwar era are well explained by just two fundamental determinants:

> (1) Positively by weighted-average growth of per capita real disposable personal income over the term.
> (2) Negatively by cumulative US military fatalities (scaled to population) owing to unprovoked, hostile deployments of American armed forces in foreign wars.

#### Data sources
- 1952-2012: Douglas Hibbs, [2014 lecture at Deakin University Melbourne](http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf), Slide 40
- 2016, Vote Share: [The American Presidency Project](https://www.presidency.ucsb.edu/statistics/elections)
- 2016, Recent Growth in Personal Incomes: [The 2016 election economy: the "Bread and Peace" model final forecast](https://angrybearblog.com/2016/11/the-2016-election-economy-the-bread-and-peace-model-final-forecast.html)
- 2016, US Military Fatalities: Assumption that Afghanistan War fatalities in 2012-16 occured at the same rate as 2008-12

> Fatalities denotes the cumulative number of American military fatalities per millions of US population the in Korea, Vietnam, Iraq and Afghanistan wars during the presidential terms preceding the 1952, 1964, 1968, 1976 and 2004, 2008 and 2012 elections. —[Hibbs](http://www.douglas-hibbs.com/HibbsArticles/HIBBS-PRESVOTE-SLIDES-MELBOURNE-Part1-2014-02-26.pdf), Slide 33


# VQE を利用しての分子シミュレーションを行う

このチュートリアルでは、Variational Quantum Eigensolver (VQE)とその利用の狙いを紹介し、必要な理論を説明します。また、分子の基底状態エネルギーを見つけるための実装例を紹介します。

## 目次
1. [はじめに](#introduction)
2. [量子力学での変分法](#varmethod)
    1. [数学的背景](#backgroundmath)
    2. [基底状態の下限](#groundstate)
3. [Variational Quantum Eigensolver](#vqe)
    1. [変分フォーム](#varforms)
    2. [単純な変分フォーム](#simplevarform)
    3. [パラメーター最適化](#optimization)
    4. [単一量子ビットの変分フォームの例](#example)
    5. [一般的な変分フォームの構造](#commonvarforms)
4. [Qiskit での VQE 実装](#implementation)
    1. [VQE を状態ベクトルシミュレーターで実行する](#implementationstatevec)
    2. [VQE をノイズのあるシミュレーターで実行する](#implementationnoisy)
5. [問題](#problems)
6. [参考文献](#references)

## はじめに
多くのアプリケーションでは、行列固有値の最小値を見つけるのが重要です。例えば、化学では、分子を特徴付けるエルミート行列の最小固有値は、そのシステムの基底状態のエネルギーになります。将来的には、最小固有値を見つけるのに量子位相推定アルゴリズムが使われる見込みですが、実用的な応用問題の実装に必要な回路の深さが、NISQ時代に利用可能なハードウェアの限界を超えています。それゆえに、2014年にPeruzzoらが、浅い回路を利用した分子の基底状態エネルギーを推定する VQE を提案しました[1]。

形式的に述べると、最小値が不明な固有値　$\lambda_{min}$ と関連する固有状態  $|\psi_{min}\rangle$ をもったエルミート行列 $H$ が与えられた場合、VQEは $\lambda_{min}$ を下に有界として、$\lambda_{\theta}$ を推定します(下限):

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

ここで、$|\psi(\theta)\rangle$ は、$\lambda_{\theta}$ に関係する固有状態です。適当な状態 $|\psi\rangle$ から開始し、$U(\theta)$ で表現されるパラメータ化された回路を適用することで、このアルゴリズムにより、$|\psi_{min}\rangle$ に対する推定 $U(\theta)|\psi\rangle \equiv |\psi(\theta)\rangle$ が得られます。パラメータ $\theta$ を期待値 $\langle \psi(\theta) |H|\psi(\theta) \rangle$ が最小になるように古典的制御を繰り返し実施しながら推定します。

## 量子力学における変分法
### 数学的背景

VQEは、量子力学の変分法アプリケーションです。変分法をよりよく理解するために、基礎的な数学的背景を説明します。行列 $A$ の固有ベクトル $|\psi_i\rangle$ は、$A$ による変換でスカラー乗数($A$ の固有値$\lambda_i$)分の違いだけで変化しません。すなわち、

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

です。また、行列 $H$ が随伴行列(転置と複素共役)の場合は、エルミートと呼ばれます。

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

スペクトル定理によると、エルミート行列の固有値は実数になります。したがって、$H$ の任意の固有値は $\lambda_i = \lambda_i^*$ を満たします。測定できる量は実数である必要があるため、量子システムのハミルトニアンを記述するために、エルミート行列が適切です。さらには、$H$ は以下のように表現できます。

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

ここで、各 $\lambda_i$ は、対応する固有ベクトル $|\psi_i\rangle$ の固有値です。また、任意の量子状態に対するオブザーバブル $H$ の期待値は、以下の式で求められます。

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

$H$ を重み付きの固有ベクトルの和として代入すると、次のようになります。

\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}

最後の式は、任意の状態に対するオブザーバブルの期待値は、$H$ に関係する固有値を利用して重みのついた線形結合として示せることを現しています。さらには、$| \langle \psi_i | \psi\rangle |^2 \ge 0$ であるため、線型結合における重みは 0以上の数値になるので、次は自明です。

\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}

上記の式が**変分法**と呼ばれるものです(テキストによっては変分原理と呼んでいます）[2]。この式の結果から、任意の波動関数の期待値は、最小でも $H$ に関係する最小固有値にしかならないことがわかります。また、$|\psi_{min}\rangle$ 状態の期待値は、$\langle \psi_{min}|H|\psi_{min}\rangle = \langle \psi_{min}|\lambda_{min}|\psi_{min}\rangle = \lambda_{min}$ となり、期待通り $\langle H \rangle_{\psi_{min}}=\lambda_{min}$ になります。

### 基底状態の下限
系のハミルトニアンがエルミート行列 $H$ で表現されている場合、系の基底状態(ground state)エネルギー $E_{gs}$ は $H$ に関係する最小の固有値になります。*ansatz* と呼ばれる適当な波動関数 $|\psi \rangle$ を $|\psi_{min}\rangle$ に近似させるため初期推測をして、期待値 $\langle H \rangle_{\psi}$ を計算します。波動関数を繰り返し更新することで、ハミルトニアンの基底状態エネルギーに近い下限を得ることができます。

## Variational Quantum Eigensolver
### 変分フォーム
量子コンピューター上で変分法を実装するには、系統的に ansatz を変更する方法が必要です。VQE はこれを固定フォームのパラメータ化された回路の利用を通じて行います。この回路は、しばしば *変分フォーム(variational form)* と呼ばれ、その実行は線形変換 $U(\theta)$ で表現されます。変分フォームは開始状態 $|\psi\rangle$ (例えば、真空 $|0\rangle$ あるいは、Hartree Fock 状態）に適用され、出力状態 $U(\theta)|\psi\rangle\equiv |\psi(\theta)\rangle$ を生成します。$|\psi(\theta)\rangle$ に対して繰り返し最適化を適用することで、期待値 $\langle \psi(\theta)|H|\psi(\theta)\rangle \approx E_{gs} \equiv \lambda_{min}$ が出力されることを狙います。理想的には、$|\psi(\theta)\rangle$ は $|\psi_{min}\rangle$ に近くなるでしょう（ここで、'近く' というのは、状態フィデリティー、もしくはマンハッタン距離(Manhattan distance)で特徴付けられます）。実際には、この場合でなくても、$E_{gs}$ に限りなく近い有効値が得られます。

さらには、多項式パラメータ数をもった固定変分フォームは、指数関数サイズのヒルベルト空間内の全ての状態を、多項式サイズの部分空間へのみ変換します。その結果として、様々な変分フォームがあります。いくつか、例えば、Ry や RyRz は目的とするドメインを考慮せず、いわば発見的に設計されたものがあります。その他、UCCSD などは、問題の構造に基づいて近似を生成するために、特定のドメイン知識を利用しています。よく利用される変分フォームの構造は、このドキュメントの後半で詳しく説明します。

### 単純な変分フォーム
変分フォームを構築する際には、２つの相反する目的のバランスを考える必要があります。理想的には、$n$ 量子ビットの変分フォームは、$|\psi\rangle \in \mathbb{C}^N$ かつ $N=2^n$の任意の状態 $|\psi\rangle$ を生成できます。しかしながら、可能な限り少ないパラメータで変分フォームを構築したいでしょう。ここでは、簡単に、最初のゴールを満たし、２つめのゴールを満たさない変分フォームの構築を見てみたいと思います。

$n=1$ の場合を考えます。U3 ゲートは３つのパラメータ、$\theta, \phi$ 及び $\lambda$ を受け取り、以下の変換を表現します:

\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}

グローバル位相を除いて、３つのパラメータを適切に設定して実装することで、どのような単一量子ビットへも変換が行えます。その結果、単一量子ビットの場合、以下の回路で、どのような状態も生成できる変分フォームを作成できます:

その上、このユニバーサルな'変分フォーム'は３つしかパラメータがないため、効率的に最適化できます。強調すべきは、任意の状態を生成できるということは、変分フォームが生成できるのは、$H$ が取りうる期待値上での達成可能な状態のセットに限定されないということです。理想的には、最小の期待値を求める部分は、古典計算の最適化の能力だけに依存します。

あまり自明でないユニバーサル変分フォームは、２量子ビットの場合から導出できます。この場合、2体相互作用があり、従って、ユニバーサルを達成するためには、エンタングルメントを考慮に入れる必要があります。*Shende et al.* [3] らによる調査に従うと、以下の例がパラメータ化された、ユニバーサル２量子ビット回路になります:

上記回路で実行される変換を $U(\theta)$ とします。変分を利用して最適化した場合、$H$ の期待値は、$U(\theta)|\psi\rangle \equiv |\psi(\theta)\rangle \approx |\psi_{min}\rangle$ の時に、最小化されます。形式から、$U(\theta)$ は任意の可能な状態に変換するので、この変分フォームは2量子ビットの基底エネルギーに強く関係したものを得ることができるでしょう。制限は、古典最適化の能力だけになります。

### パラメーター最適化
効率的にパラメータ化された変分フォームを選択したら、変分法に従って、そのパラメータをターゲットとなるハミルトニアンの期待値を最小化するように最適化する必要があります。パラメータの最適化のプロセスには様々な課題があります。例えば、量子ハードウェアには様々なタイプのノイズがあり、目的関数の評価(エネルギーの計算）は実際の目的関数を反映しないかも知れません。また、いくつかのオプティマイザーはパラメーター集合の濃度(パラメーターの数)に依存して、目的関数の評価を数多く実施します。アプリケーションの要求を考慮しながら、最適なオプティマイザーを選択する必要があります。

もっとも一般的な最適化戦略は、エネルギーの変化が極大になるような方向にパラメータを更新する最急降下法です。結果として、評価の数は、最適化すべきパラメータの数に依存します。これにより、探索スペースにおいてローカル最適値をクイックに発見するアルゴリズムとなります。しかしながら、この最適化方法はしばしば局所最適時に止まることがあり、実施される回路評価数によっては比較的時間がかかります。直感的な最適化戦略ですが、VQEで利用するにはお勧めできません。

ノイズのある目的関数を最適化する適切なオプティマイザーとして、*Simultaneous Perturbation Stochastic Approximation* オプティマイザー (SPSA)があります。SPSAは２回の測定だけで、目的関数の勾配を近似します。最急降下法では各パラメータを独立に摂動させるのに対して、全てのパラメータを同時にランダムに摂動させます。VQEをノイズ込みのシミュレーター、もしくは実ハードウェアで利用する場合には、古典オプティマイザーとして SPSA が推奨されます。

コスト関数の評価にノイズがない場合（例えば、VQEを状態ベクトルシミュレーターで利用する場合など）は、多様な古典オプティマイザーを利用できます。Qiskit Aqua でサポートされている２つのオプティマイザーは、*Sequential Least Squares Programming* オプティマイザー (SLSQP) と *Constrained Optimization by Linear Approximation* オプティマイザー (COBYLA) です。COBYLAでは、目的関数の評価を最適化の繰り返しで１回のみ実施（つまり評価の数はパラメータセットの濃度には依存しない）ということに着目します。従って、目的関数がノイズがない場合、及び評価の数を最小化したい場合は、COBYLAの利用がお勧めです。

### 単一量子ビットの変分フォームの例
ではここで、基底エネルギー推定問題と似たような単一量子ビットの変分フォームを利用してみましょう。問題は、ランダムな確率ベクトルが$\vec{x}$ が与えられており、出力の確率分布が $\vec{x}$ に近くなるように、可能な単一量子ビット変分フォームのパラメーターを決定します（ここで近くは２つの確率ベクトル間のマンハッタン距離によって定義します）。

最初に 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)
```

次に、単一の U3 変分フォームの３つのパラメーターを引数として受け取り、対応する量子回路をリターンする関数を定義します:

```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
```

また、変分フォームのパラメータのリストを入力とし、パラメータに対応したコストを計算する目的関数を定義します:

```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
```

最後に、COBYLA オプティマイザーのインスタンスを作成し、アルゴリズムを実行します。出力は実行の度に異なることに注意してください。また、近いとはいえ、得られた分布はターゲットの分布とは完全に同じではありません。しかしながら、ショットの数を増やすことで出力の確度を向上させることができるでしょう。

```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])
```

    Target Distribution: [0.51357006 0.48642994]
    Obtained Distribution: [0.5182, 0.4818]
    Output Error (Manhattan Distance): 0.0001401187388391789
    Parameters Found: [1.59966854 0.66273002 0.28432001]

### 一般的な変分フォームの構造
既に説明したように、多項式パラメーターの変分フォームでは、任意の状態への変換を生成できません。変分フォームはこの制限をどのように扱うかによって２つのカテゴリーに分類されます。変分フォームの最初のカテゴリーはドメイン、あるいはアプリケーション固有の知識を生かして可能な出力状態のセットを制限するというものです。２つ目は、ドメインやアプリケーション固有の知識なしに、発見的な回路を利用するアプローチです。

最初のカテゴリーの変分フォームは、必要となる変換のセットを制限するために、課題ドメインの特徴を流用します。例えば、分子の基底状態エネルギーの計算には、粒子の数は*前もって*知られているとします。従って、正しい粒子数をもった開始状態を利用し、変分フォームを粒子数を保存する変換にのみ制限することで、新しい変換の部分空間への展開に必要なパラメーター数をを大きく削減することができます。実際に、結合クラスター(Coupled-Cluster)理論と同じような情報を利用して、変分フォームの UCCSD は、Hartree Fock状態から開始する場合には、分子の基底状態エネルギー値を非常に正確に推定することが出来ます。ドメイン固有の知識を利用する別の例として、実量子ハードウェアで実現可能な回路を考慮するというものが挙げられます。超電導型量子ビットに基づくような現存する量子コンピューターは量子ビット間接続に制限があります。つまり、(スワップゲートを挟み込むことなく）任意の量子ビットペアに基づく、２量子ビットゲートを実装することが出来ません。与えられた利用可能な接続性とゲートを最大限活用した回路により特定の量子コンピューターに特化した変分フォームの構築がなされています。このような変分フォームにより 2017年にIBM量子コンピューター上で、 BeH$_2$ までの大きさをもつ分子の基底状態エネルギーの推定をするVQE実装に成功しました[4]。

２番目のアプローチでは、広範囲の状態に対してよい近似が得られるようにゲートがレイヤー化されます。Qiskit Aqua では、３つのこのような変分フォーム: RyRz、Ry、及び SwapRzをサポートします（ここでは最初の２つのみ説明します）。これらの変分フォームはすべて、複数のユーザー指定の構成を受け付けます。３つの不可欠な構成は、システム上の量子ビット数、深さの設定、及びエンタグルメント設定です。変分フォームの一つのレイヤーでは、特定のパターンの単一量子ビットの回転と CX ゲートを指定します。深さの設定では、例えば、変分フォームがこのパターンを何回繰り返すかを指定します。深さの設定を増やすことで、最適化すべきパラメーター数を増やすことで生じるコストが上がりますが、変分フォームが生成できる状態のセットも増加します。最後に、エンタングルメント設定は、CXゲートの構成、内部的には数、を選択します。例えば、エンタングルメント設定が linear の場合、CXゲートは順番に直近の量子ビットペアに適用されます（したがって$n-1$ CX ゲートがレイヤーに追加されます）。エンタングルメント設定が full の場合、CXゲートは、各レイヤーのそれぞれの量子ビットペアに適用されます。`entanglement="full"` と `entanglement="linear"`の場合の、RxRz回路は以下のコードを実行することで確認できます:

```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()
```

    =============Linear Entanglement:=============
            ┌───────────┐┌───────┐                      ┌───────────┐  ┌───────┐                »
    q_0: |0>┤ U3(0,0,0) ├┤ U1(0) ├──────────────────■───┤ U3(0,0,0) ├──┤ U1(0) ├────────────────»
            ├───────────┤├───────┤┌──────────────┐┌─┴─┐┌┴───────────┴─┐└───────┘ ┌───────────┐  »
    q_1: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├┤ X ├┤ U2(0,3.1416) ├────■─────┤ U3(0,0,0) ├──»
            ├───────────┤├───────┤├──────────────┤└───┘└──────────────┘  ┌─┴─┐  ┌┴───────────┴─┐»
    q_2: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├───────────────────────┤ X ├──┤ U2(0,3.1416) ├»
            ├───────────┤├───────┤├──────────────┤                       └───┘  └──────────────┘»
    q_3: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├──────────────────────────────────────────────»
            └───────────┘└───────┘└──────────────┘                                              »
    «                                                     ░
    «q_0: ────────────────────────────────────────────────░─
    «     ┌───────┐                                       ░
    «q_1: ┤ U1(0) ├───────────────────────────────────────░─
    «     └───────┘ ┌───────────┐    ┌───────┐            ░
    «q_2: ────■─────┤ U3(0,0,0) ├────┤ U1(0) ├────────────░─
    «       ┌─┴─┐  ┌┴───────────┴─┐┌─┴───────┴─┐┌───────┐ ░
    «q_3: ──┤ X ├──┤ U2(0,3.1416) ├┤ U3(0,0,0) ├┤ U1(0) ├─░─
    «       └───┘  └──────────────┘└───────────┘└───────┘ ░

    =============Full Entanglement:=============
            ┌───────────┐┌───────┐                                                               »
    q_0: |0>┤ U3(0,0,0) ├┤ U1(0) ├──────────────────■────■────────────────────■──────────────────»
            ├───────────┤├───────┤┌──────────────┐┌─┴─┐  │  ┌──────────────┐  │                  »
    q_1: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├┤ X ├──┼──┤ U2(0,3.1416) ├──┼──────────────────»
            ├───────────┤├───────┤├──────────────┤└───┘┌─┴─┐└──────────────┘  │  ┌──────────────┐»
    q_2: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├─────┤ X ├──────────────────┼──┤ U2(0,3.1416) ├»
            ├───────────┤├───────┤├──────────────┤     └───┘                ┌─┴─┐└──────────────┘»
    q_3: |0>┤ U3(0,0,0) ├┤ U1(0) ├┤ U2(0,3.1416) ├──────────────────────────┤ X ├────────────────»
            └───────────┘└───────┘└──────────────┘                          └───┘                »
    «      ┌───────────┐     ┌───────┐                                                              »
    «q_0: ─┤ U3(0,0,0) ├─────┤ U1(0) ├──────────────────────────────────────────────────────────────»
    «      └───────────┘     └───────┘                          ┌───────────┐     ┌───────┐         »
    «q_1: ───────────────────────■──────────■───────────────────┤ U3(0,0,0) ├─────┤ U1(0) ├─────────»
    «     ┌──────────────┐     ┌─┴─┐        │  ┌──────────────┐ └───────────┘     └───────┘         »
    «q_2: ┤ U2(0,3.1416) ├─────┤ X ├────────┼──┤ U2(0,3.1416) ├──────────────────────────────────■──»
    «     ├──────────────┤┌────┴───┴─────┐┌─┴─┐└──────────────┘┌──────────────┐┌──────────────┐┌─┴─┐»
    «q_3: ┤ U2(0,3.1416) ├┤ U2(0,3.1416) ├┤ X ├────────────────┤ U2(0,3.1416) ├┤ U2(0,3.1416) ├┤ X ├»
    «     └──────────────┘└──────────────┘└───┘                └──────────────┘└──────────────┘└───┘»
    «                                            ░
    «q_0: ───────────────────────────────────────░─
    «                                            ░
    «q_1: ───────────────────────────────────────░─
    «      ┌───────────┐    ┌───────┐            ░
    «q_2: ─┤ U3(0,0,0) ├────┤ U1(0) ├────────────░─
    «     ┌┴───────────┴─┐┌─┴───────┴─┐┌───────┐ ░
    «q_3: ┤ U2(0,3.1416) ├┤ U3(0,0,0) ├┤ U1(0) ├─░─
    «     └──────────────┘└───────────┘└───────┘ ░

深さの設定を $d$ とします。この時、RyRz は $n\times (d+1)\times 2$ 個のパラメーターを持ち、linear エンタングルメントの Ry は $2n\times(d + \frac{1}{2})$ 個のパラメーター、また Ryで full エンタングルメントの場合は、$d\times n\times \frac{(n + 1)}{2} + n$ 個のパラメーターを持つようになります。

## Qiskit での VQE 実装
このセクションでは、プログラミングアプローチでのVQEの実装例を示します。Qiskit Aqua では宣言的な実装もできますが、この場合動作しているアルゴリズムの情報が表面に出てきません。このコードは、特に量子ビット操作の準備は、Qiskit チュートリアルリポジトリに基づいています(リポジトリは 2019年7月の時点で https://github.com/Qiskit/qiskit-tutorials　にあります）。

次のライブラリを最初にインポートします。

```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
```

### VQEを状態ベクトルシミュレーターで実行する
様々な原子間距離での LiH の基底エネルギーの計算をデモンストレーションしてみます。各距離での分子ドライバーを作成する必要があります。この実験では、利用する量子ビットの数を減らすため、内核固定(frozen core)し、２つの非占有起動を削除していることに注意してください。最初に、原子間距離を引数とし、適切な量子ビット演算子 $H$ と演算子に関するその他の情報を返却する関数を定義します。

```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
```

最初に、正確な基底状態エネルギーを、量子ビット演算子及び古典的固有値計算法(ExactEigensolver)を使って計算します。次に、初期状態 $|\psi\rangle$ を作成し、これを使って VQE が最終的 ansatz $\min_{\theta}(|\psi(\theta)\rangle)$ を生成するようにします。各原子間距離での正確な値と VQEの結果を記録します。`vqe.run(backend)['energy'] + shift` で与えられる結果と $\min_{\theta}\left(\langle \psi(\theta)|H|\psi(\theta)\rangle\right)$ 量は同じであることに着目してください。ここでは、最小はかならずしももっとも最小ではありません。

`VQE(qubitOp, var_form, optimizer, 'matrix')` で VQE インスタンスを初期化する時には、$|\psi(\theta)\rangle$ に対する $H$ の期待値は行列の掛け算で直接計算しています。しかしながら、実量子デバイスや、`VQE(qubitOp, var_form, optimizer, 'paulis')` を使った `qasm_simulator`などの実シミュレーターの場合は、期待値の計算はより複雑になります。ハミルトニアンはパウリストリング (Pauli strings - パウリ行列のテンソル積)の足し算で表現され、使用したマッピングで指定されたパウリ項が、量子ビットに適用されます。$|\psi(\theta)\rangle$ に対応した回路の後に、各パウリストリングに対応する回路が追加されます。結果的に、これらの回路が実行されると、$|\psi(\theta)\rangle$ に対する $H$ の期待値の決定に利用できます。以下の例では、VQE のインスタンスを `matrix` モードで初期化しているので、期待値の計算には行列の掛け算を通じて直接計算しています。

注意： 以下のコードは完了までしばらく時間がかかります。

```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.039710219020506 Exact Energy: -7.039732521635202
    Interatomic Distance: 0.6 VQE Result: -7.313344302334236 Exact Energy: -7.313345828761008
    Interatomic Distance: 0.7 VQE Result: -7.500921095743192 Exact Energy: -7.500922090905936
    Interatomic Distance: 0.8 VQE Result: -7.630976914468914 Exact Energy: -7.630978249333209
    Interatomic Distance: 0.9 VQE Result: -7.7208107952020795 Exact Energy: -7.720812412134773
    Interatomic Distance: 1.0 VQE Result: -7.782240655298441 Exact Energy: -7.782242402637011
    Interatomic Distance: 1.1 VQE Result: -7.823597493320795 Exact Energy: -7.82359927636281
    Interatomic Distance: 1.2 VQE Result: -7.850696622934822 Exact Energy: -7.850698377596024
    Interatomic Distance: 1.3 VQE Result: -7.867561602181376 Exact Energy: -7.867563290110052
    Interatomic Distance: 1.4 VQE Result: -7.876999876757721 Exact Energy: -7.877001491818373
    Interatomic Distance: 1.5 VQE Result: -7.8810141736656405 Exact Energy: -7.881015715646992
    Interatomic Distance: 1.6 VQE Result: -7.881070662952161 Exact Energy: -7.88107204403092
    Interatomic Distance: 1.7 VQE Result: -7.878267162143656 Exact Energy: -7.878268167584993
    Interatomic Distance: 1.8 VQE Result: -7.873440112155302 Exact Energy: -7.873440293132828
    Interatomic Distance: 1.9 VQE Result: -7.86723366674701 Exact Energy: -7.8672339648160285
    Interatomic Distance: 2.0 VQE Result: -7.860152327529411 Exact Energy: -7.86015320737878
    Interatomic Distance: 2.1 VQE Result: -7.852595105536979 Exact Energy: -7.852595827876738
    Interatomic Distance: 2.2 VQE Result: -7.844878726366329 Exact Energy: -7.844879093009722
    Interatomic Distance: 2.3 VQE Result: -7.837257439448259 Exact Energy: -7.8372579676155025
    Interatomic Distance: 2.4 VQE Result: -7.829935045088515 Exact Energy: -7.829937002623394
    Interatomic Distance: 2.5 VQE Result: -7.823070191557451 Exact Energy: -7.82307664213409
    Interatomic Distance: 2.6 VQE Result: -7.816782591999657 Exact Energy: -7.816795150472929
    Interatomic Distance: 2.7 VQE Result: -7.8111534373726 Exact Energy: -7.811168284803366
    Interatomic Distance: 2.8 VQE Result: -7.806218299266321 Exact Energy: -7.806229560089845
    Interatomic Distance: 2.9 VQE Result: -7.801962397475152 Exact Energy: -7.8019736023325486
    Interatomic Distance: 3.0 VQE Result: -7.798352412318197 Exact Energy: -7.7983634309151295
    Interatomic Distance: 3.1 VQE Result: -7.795326815750017 Exact Energy: -7.795340451637537
    Interatomic Distance: 3.2 VQE Result: -7.792800698225245 Exact Energy: -7.792834806738612
    Interatomic Distance: 3.3 VQE Result: -7.790603799019874 Exact Energy: -7.790774009971014
    Interatomic Distance: 3.4 VQE Result: -7.788715354695274 Exact Energy: -7.789088897991478
    Interatomic Distance: 3.5 VQE Result: -7.787215781080283 Exact Energy: -7.787716973466144
    Interatomic Distance: 3.6 VQE Result: -7.786080393658009 Exact Energy: -7.786603763673838
    Interatomic Distance: 3.7 VQE Result: -7.785203497342158 Exact Energy: -7.785702912499886
    Interatomic Distance: 3.8 VQE Result: -7.7844795319924325 Exact Energy: -7.784975591698873
    Interatomic Distance: 3.9 VQE Result: -7.783853361693722 Exact Energy: -7.7843896116723315
    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()
```

VQEの結果は、正確な値と非常に近いことに着目してください。そのため、正確な値のカーブは VQEカーブに隠れてしまっています。

### VQE をノイズのあるシミュレーターで実行する

ここでは、H$_2$ の基底状態エネルギーの計算に、ノイズのあるシミュレーターとエラー軽減を利用してみます。

最初に、分子のハミルトニアンを表現する量子ビット演算子を用意します:

```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)
```

次に、デバイスのカップリングマップとノイズモデルを IBMQ プロバイダーからロードし、量子インスタンスを作成し、エラー軽減を有効にします:

```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)
```

最後に、オプティマイザー、変分フォーム、および VQE インスタンスを構成します。２量子ビット回路の深さが増すにつれて、ノイズの効果が増加するため、ここでは UCCSD ではなく、発見的な変分フォーム (RYRZ)を利用します。RYRZは、UCCSDに比べ浅い回路で、実質的により少ない２量子ビットゲート数となります。

注意：以下のコードは完了までしばらく時間がかかります。

```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.86712097834127
    VQE Result: -1.8220854070067132

たとえ結果が化学有効値(正しい結果に対して 0.0016 Hartree と定義されている)になっていませんが、ノイズ軽減が有効な場合、正確な値にかなり近いと言えるでしょう。

## 問題
1. 基底状態が $n$ 量子ビットの最大エンタングルに近いことが約束されている、ハミルトニアン $H$ が与えられているとする。どの変分フォームが、$H$ の基底状態エネルギーを効率的かつ正確に与えるかを説明せよ。ここでは自作の変分フォームを使ってもよい。その場合、このハミルトニアンになぜそれが適切かを説明せよ。

2. COPYLAオプティマイザーと 1000ショットの `qasm_simulator`、および 60パウリストリングをもったハミルトニアンを使う場合、１回の最適化の際に行われる回路評価数を計算せよ。

3. 原子間距離が $1.3$Å の BeH$_2$ の基底状態エネルギーを推定せよ。`get_qubit_op(dist)` 関数を再利用する場合、`atom="Li .0 .0 .0; H .0 .0 " + str(dist)` を、`atom="Be .0 .0 .0; H .0 .0 -" + str(dist) + "; H .0 .0 " + str(dist)` で置き換え、`get_qubit_op(1.3)` を実行する。この分子の場合、非占有軌道の削除をすると、化学的な正確性が失われることに注意すること。しかしながら、必要となる量子ビット数を6まで下げる（多くのラップトップで効果的なシミュレーションができるようにする）場合は、このロスは許容範囲である。興味のある読者は、必要する量子ビット数を 7 までにして化学正確性を失わないように試みてもよい。

## 参考文献
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__
```


# 2.5 非参数方法

之前我们的方法都是预先假设数据服从一个特定的概率分布，然后对对这个分布的参数进行估计。这种方法叫做参数估计。

但是这种方法的缺陷在于，我们选择的分布可能与实际的分布相差很大：如果数据是个多峰的分布，那么我们用一个高斯分布很难得到一个较好的结果。

非参数方法与参数估计不同，非参数方法对于数据分布的假设通常是很少的。

我们先从直方图来入手。考虑一个连续变量 $x$，直方图方法将 $x$ 划分为 $n$ 个长度为 $\Delta_i$ 的区间，每个区间内观察到的数据点数为 $n_i$，总的数据点为 N，那么我们可以认为，在第 $i$ 个区间的概率密度可以近似为：

$$
p_i = \frac{n_i}{N\Delta_i}
$$

即我们认为在每个区间它的概率密度是相同的。然后我们显然有 $\int p(x) dx = 1$。在实际应用中，我们通常让这些区间等长即 $\Delta_i = \Delta$。

```python
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
from scipy.stats import norm

%matplotlib inline

_, axes = plt.subplots(3, 1, figsize=(7,6))

xx = np.zeros(50)

n = 15
xx[:n] = norm.rvs(size=(n,), loc=0.25, scale=0.1)
xx[n:] = norm.rvs(size=(50-n,), loc=0.75, scale=0.1)

dlt = [0.04, 0.08, 0.25]
tt = np.linspace(0, 1, 300)
tp = 0.3 * norm.pdf(tt, loc=0.25, scale=0.1) + 0.7 * norm.pdf(tt, loc=0.75, scale=0.1)

for i in range(3):
    axes[i].hist(xx, bins=np.arange(0,1.01,dlt[i]), normed=True, color='r')
    axes[i].plot(tt, tp, 'g')
    axes[i].set_ylim(0, 5)
    axes[i].set_yticks([0, 5])
    axes[i].set_xticks([0, .5, 1])
    axes[i].text(0.1, 3, '$\Delta={}$'.format(dlt[i]), fontsize='x-large')

plt.show()
```

上图是两个高斯分布的混合，绿色为实际的概率分布，我们看到，当我们将 $\Delta$ 设的很小时，很多区域的信息被丢失了，当 $\Delta$ 设的很大时，又不能很好的反映出多峰的性质。

在实际应用中，直方图方法只能用在一维或者二维的情况，当维数多的时候并不适合。

我们使用直方图方法的原因基于两个考虑，第一，对于某个位置的概率密度，我们通常会考虑该点附近某个区域内的数据点的情况，直方图的区间宽度控制了这个区域的大小；第二，直方图的区间宽度选择事实上控制了我们模型的复杂度。考虑这两个因素，我们引入两种广泛使用的非参数估计方法

## 2.5.1 核密度估计量

考虑估计 $D$ 维空间的一个未知概率分布 $p(\mathbf x)$ 的问题，我们考虑 $\bf x$ 附近包含它的一个小区域 $\mathcal R$，这个小区域内的概率质量为：

$$
P=\int_{\mathcal R} p(\mathbf x) d\mathbf x
$$

假设现在我们有 $N$ 个观测点；对于这 N 个点来说，它有 $P$ 的概率落在区域 $\mathbf R$ 中，所以有 $K$ 个数据点落在 $\mathbf R$ 中的概率是一个二项分布，概率为

$$
\mathrm{Bin}(K| N, P)=\frac{N!}{K!(N-K)!}P^{K}(1-P)^{N-K}
$$

利用二项分布的性质，我们知道
$\mathbb E[K/N] = P$
和
$\mathrm{var}[K/N]=P(1-P)/N$，当 $N$ 足够大时，我们有：

$$K\simeq NP$$

当区域 $\mathcal R$ 足够小时，我们有：

$$P\simeq p(\mathbf x)V$$

其中 $V$ 是区域的体积，这样我们就得到估计值：

$$
p(\mathbf x) = \frac{K}{NV}
$$

对于 $\mathcal R$ 的选择，我们要让它足够小，从而 $p(\mathbf x)$ 可以认为是一个常数；但是要足够大，使得落在这个区域的次数 $K$ 能在均值附近出现峰值。

当我们固定 $K$，然后决定适合的 $V$ 时，对应的是 $K$ 近邻的方法，当我们固定 $V$ 找 $K$ 的时候，对应的是核方法。

当 $N\to\infty$ 的时候，这两种方法在 $V, K$ 随 $N$ 增大而分别减小和增大的情况下都会收敛到真实分布。

### 核函数方法

我们将区域 $\mathcal R$ 限制为以 $\bf x$ 为中心的一个小立方体。为了计算落在这个区域的数据点数 $K$ 我们定义这样的函数：

$$
k(\mathbf u) = \left\{
\begin{aligned}
1, &~|u_i| \leq 1/2, i=1,\dots,D \\
0, &~{\rm otherwise}
\end{aligned}
\right.
$$

它表示数据点是否落在以原点为中心的单位立方体。

$k(\mathbf u)$ 就是一个核函数的例子，在这个问题上，更确切的说法应该是 Parzen 窗函数。

从上面的式子可以看出落在，$k(\frac{\mathbf{x-x}_n}{h})$ 表示数据点 ${\bf x}_n$ 是否以 $\mathbf x$ 为中心，边长为 $h$ 的区域。因此，落在这个区域的数据点总数为：

$$
K = \sum_{n=1}^N k(\frac{\mathbf{x-x}_n}{h})
$$

从而概率密度的估计值为

$$
p({\bf x})=\frac{1}{N} \sum_{n=1}^N \frac{1}{h^D} k(\frac{\mathbf{x-x}_n}{h})
$$

与直方图法一样，这种方法面临的一个问题是在边界上的不连续性。我们可以考虑一个更加光滑的核函数，比如说最常见的高斯核函数，考虑上式的形式，我们给出一个类似的表达式：

$$
p({\bf x})=\frac{1}{N} \sum_{n=1}^N \frac{1}{(2\pi h^2)^{1/2}} \exp(-\frac{\|\mathbf{x-x}_n\|}{2h^2})
$$

这里 $h$ 对应的是高斯成分的标准差。

当我们更换了核函数的时候，为了保证概率密度是归一的，求和的部分通常也要满足概率密度的归一性。

通常，核函数满足的条件为

$$
\begin{align}
k(\mathbf u) & \geq 0 \\
\int k(\mathbf u)d\mathbf u & = 1
\end{align}
$$

如图所示为高斯核函数在不同 $h$ 下的拟合结果。

```python
_, axes = plt.subplots(3, 1, figsize=(7,6))

h = 0.005, 0.07, 0.2

for i in range(3):
    tq = np.zeros_like(tp)
    for x in xx:
        tq += norm.pdf(tt, loc=x, scale=h[i]) / len(tt)
    axes[i].plot(tt, tp, 'g')
    axes[i].plot(tt, tq, 'b')
    axes[i].set_ylim(0, 4)
    axes[i].set_yticks([0, 4])
    axes[i].set_xticks([0, .5, 1])
    axes[i].text(0.1, 3, '$h={}$'.format(h[i]), fontsize='x-large')

plt.show()
```

## 2.5.2 近邻方法

在核方法中，一旦我们选定了 $h$，我们就相当于固定了体积 $V$（分别对应于上一节的 $h^D, (2\pi h^2)^{1/2}$）。

现在我们考虑固定 $K$ 的情况，此时我们需要找到一个合适的 $V$ 使得落在体积 $V$ 中的点正好为 $K$ 个。

为了方便，我们考虑点 $\bf x$ 的一个球域，这个球域正好包含 $K$ 个数据点。此时，球的体积由离 $\bf x$ 第 $K$ 近的点决定。

该点的密度近似为：

$$
p(\mathbf x)=\frac{K}{NV}
$$

```python
_, axes = plt.subplots(3, 1, figsize=(7,6))

K = 1, 5, 30
N = 50.0

for i in range(3):
    tq = np.zeros_like(tt)
    for j, t in enumerate(tt):
        dd = np.abs(xx - t)
        dd.sort()
        tq[j] = K[i] / N / (2 * dd[K[i]-1])
    axes[i].plot(tt, tp, 'g')
    axes[i].plot(tt, tq, 'b')
    axes[i].set_ylim([0, 5])
    axes[i].set_yticks([0, 5])
    axes[i].set_xticks([0, .5, 1])
    axes[i].text(0.1, 3, '$K={}$'.format(K[i]), fontsize='x-large')

plt.show()
```

不同 $K$ 的拟合结果如上图所示。

### 拓展到分类问题

假设我们有 N 个数据点，第 $\mathcal C_k$ 类有 $N_k$ 个，从而 $\sum_{k} N_k = N$。

对于一个新数据点 $\bf x$，我们找到包含 K 个点的体积为 $V$ 的区域，从而总的概率密度近似为：

$$
p(\mathbf x)=\frac{K}{NV}
$$

在这个区域中，第 $\mathcal C_k$ 类有 $K_k$ 个，条件概率密度近似为：

$$
p(\mathbf x|\mathcal C_k)=\frac{K_k}{N_kV}
$$

而类别的先验为：

$$
p(\mathcal C_k) = \frac{N_k}{N}
$$

于是贝叶斯定理给出：

$$
p(\mathcal C_k|\mathbf x) = \frac{p(\mathbf x|\mathcal C_k)p(\mathcal C_k)}{p(\mathbf x)} = \frac{K_k}{K}
$$

最小错误率决策给出，我们要将 $\bf x$ 标记为 $p(\mathcal C_k|\mathbf x)$ 即 $K_k / K$ 最大的那个类。

所以为了对新数据点进行决策，我们首先找到离它最近的 $K$ 个点，然后将数据点划分为 $K$ 个点中最多的那个类；数目相同则随机选取一类。

当 $K = 1$ 时，这对应于最近邻规则，即新数据点的标签为离它最近的数据点的类别。

对于最近邻规则，当 $N\to\infty$ 时，它的错误里不会超过使用真实分布进行判断的最小错误率的两倍。

当然，核方法和近邻法的一个缺点在于我们需要存储所有的训练数据。