# Density estimation
Use dataset

```
%matplotlib inline
import theano
import pymc3 as pm
import sklearn
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from warnings import filterwarnings
filterwarnings('ignore')
sns.set_style('white')
from sklearn import datasets
from sklearn.preprocessing import scale
from sklearn.model_selection import train_test_split
import theano.tensor as tt
from theano import shared
import pandas as pd
import scipy as sp
```

    /Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/h5py/__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
      from ._conv import register_converters as _register_converters

## Load dataset
Peek into the data - curiosity helps build intuition
Intuition yields a prior (required for probabilistic modeling).

```
df = pd.read_csv('./pb_asc_x1x5_x6.csv', header=None, names=['x1', 'x2', 'x3', 'x4', 'x5', 'x6'])
df.head()
```

      x1
      x2
      x3
      x4
      x5
      x6

      0
      16
      30
      41
      48
      53
      16

      1
      1
      26
      41
      50
      57
      11

      2
      23
      33
      42
      46
      59
      4

      3
      2
      18
      19
      22
      63
      19

      4
      10
      16
      40
      52
      55
      17

```
df['x1'].hist()
```

### The Model
Assume x1 is given for x2, and so on
For x1, we estimate the density and pick the MAP

```latex
%%latex
\begin{align}
\alpha \sim \Gamma(1,1) \\
\beta_1...\beta_K \sim \beta(1, \alpha) \\
w_i = \beta_i \prod_{j=1}^{i-1}(1-\beta_j) \\
a_i \sim U(0,50) \\
b_i \sim U(0,50) \\
\lambda_i = a_i * x_1 + b_i \\
x_2 \sim \sum_{}^{}w_i Pois(\lambda_i)\\
\end{align}
```

\begin{align}
\alpha \sim \Gamma(1,1) \\
\beta_1...\beta_K \sim \beta(1, \alpha) \\
w_i = \beta_i \prod_{j=1}^{i-1}(1-\beta_j) \\
a_i \sim U(0,50) \\
b_i \sim U(0,50) \\
\lambda_i = a_i * x_1 + b_i \\
x_2 \sim \sum_{}^{}w_i Pois(\lambda_i)\\
\end{align}

```
SEED=5132290
K = 30
#x1 = df['x1'].values
def stick_breaking(beta):
    portion_remaining = tt.concatenate([[1], tt.extra_ops.cumprod(1-beta)[:-1]])
    return beta * portion_remaining

```

```

with pm.Model() as model:
    alpha = pm.Gamma('alpha', 1, 1)
    beta = pm.Beta('beta', 1, alpha, shape=K)
    w = pm.Deterministic('w', stick_breaking(beta))
    #a = pm.Uniform('a', 0, 50, shape=K)
    #b = pm.Uniform('b', 0, 50, shape=K)
    mu = pm.Uniform('mu', 0, 50, shape=K)
    bp = [pm.Bound(pm.Poisson, lower=1, upper=70).dist(mu=mu[i]) for i in range(K)]
    #x = pm.Mixture('obs', w, pm.Poisson.dist(mu), observed=df['x1'].values)
    x = pm.Mixture('obs', w, bp, observed=df['x1'].values)

```

```
with model:
    trace = pm.sample(1000, step=pm.Metropolis(), random_seed=SEED)
```

    Multiprocess sampling (2 chains in 2 jobs)
    CompoundStep
    >Metropolis: [mu_interval__]
    >Metropolis: [beta_logodds__]
    >Metropolis: [alpha_log__]
    100%|██████████| 1500/1500 [00:21<00:00, 68.62it/s]
    INFO (theano.gof.compilelock): Refreshing lock /Users/jlao/.theano/compiledir_Darwin-17.4.0-x86_64-i386-64bit-i386-3.5.1-64/lock_dir/lock
    The gelman-rubin statistic is larger than 1.4 for some parameters. The sampler did not converge.
    The estimated number of effective samples is smaller than 200 for some parameters.

```
with model:
    pm.traceplot(trace, varnames=['alpha'])
```

```
plot_w = np.arange(K) + 1
fig, ax = plt.subplots(figsize=(8,6))
#ax.hist(df['x2'].values, alpha=0.2, color='blue', normed=True)
ax.plot(plot_w-0.5, trace['w'].mean(axis=0) )
ax.plot(plot_w-0.5, np.cumsum(trace['w'].mean(axis=0)))
ax.set_xlim(.5, K)
ax.set_ylabel('Mixture weight')
```

```
np.cumsum(trace['w'].mean(axis=0))
```

    array([0.28275004, 0.65136045, 0.8528166 , 0.87689402, 0.98910066,
           0.99605705, 0.99795251, 0.99882794, 0.9992389 , 0.99994005,
           0.99994665, 0.99996554, 0.99996971, 0.99999206, 0.99999281,
           0.99999345, 0.99999685, 0.99999733, 0.9999981 , 0.9999982 ,
           0.99999916, 0.99999955, 0.99999977, 0.99999981, 0.99999983,
           1.        , 1.        , 1.        , 1.        , 1.        ])

```
x_plot = np.arange(50) #Range of values of x1
#Create the set of Poisson distributions with means 'mu'
pmf_contribs = sp.stats.poisson.pmf(np.atleast_3d(x_plot), trace['mu'][:, np.newaxis, :])

#Multiply each distribution by its weight
wt_pmfs = (trace['w'][:, np.newaxis, :] * pmf_contribs).sum(axis=-1)
#trace['mu'].shape, x_plot.shape,np.atleast_3d(x_plot).shape, pmf_contribs.shape, pmfs.shape
wt_pmfs.shape, trace['mu'].shape, wt_pmfs[::200].shape, pmf_contribs.mean(axis=1).shape
```

    ((2000, 50), (2000, 30), (10, 50), (2000, 30))

```
fig, ax = plt.subplots(figsize=(8,6))
ax.hist(df['x1'].values, bins=54, alpha=0.2,normed=True)
#ax.plot(x_plot, wt_pmfs[0])
ax.plot(x_plot, wt_pmfs.mean(axis=0))
#ax.plot(x_plot, wt_pmfs[::200].T, color='orange')
```

```
pmf_contribs.mean(axis=1).shape, wt_pmfs.mean(axis=1).shape, trace['mu'].shape
```

    ((2000, 30), (2000,), (2000, 30))

```
with model:
    ppc = pm.sample_ppc(trace, 1000)
    fig, ax = plt.subplots(figsize=(8,6))
    ax.hist(ppc['obs'], bins=50, normed=True, alpha=0.3, color='green', label='Posterior predictions')
    ax.hist(df['x1'], bins=50, normed=True, alpha=0.3, label='Training data')
    ax.legend(loc=1)
```

```
ppc['obs'].shape, df['x1'].shape
```

    ((1000,), (2022,))

### Try dependent distribution for x2
Based on x1

```
#std_range = df.std_range.values[:, np.newaxis]
#shared(std_range, broadcastable=(False, True))
#x1_shift = ppc['obs'][:K]
x1_shift = shared(df['x1'].values[:, np.newaxis], broadcastable=(False, True))
x2obs = df['x2'].values[:, np.newaxis]
#x2obs = df['x2'].values
```

```
df['x1'].values[:, np.newaxis]
```

    array([[16],
           [ 1],
           [23],
           ...,
           [ 7],
           [ 2],
           [22]])

```
from pymc3.math import logsumexp
with pm.Model() as x2model:
    alpha = pm.Gamma('alpha', 1, 1)
    beta = pm.Beta('beta', 1, alpha, shape=K)
    w = pm.Deterministic('w', stick_breaking(beta))
    eta = pm.Uniform('eta', 0, 20, shape=K)
    delta = pm.Uniform('delta', 0, 10., shape=K)
    mu = pm.Deterministic('mu', eta + delta * x1_shift)
    # use a potential to add the mixture logp to the model
    pm.Potential('logp', logsumexp(tt.log(w) +
                                   pm.Poisson.dist(mu).logp(x2obs), axis=-1).sum())
```

```
from scipy.stats import poisson
wpost = x2trace['w']
mupost = x2trace['mu']
ndraws = wpost.shape[0]
nppc = 100
ppc = np.zeros((nppc, len(x2obs)))
for n in range(nppc):
    idx = np.random.randint(ndraws)
    pchoice = np.random.choice(K, len(x2obs), p=wpost[idx, :])
    ppc[n, : ] = poisson.rvs(mupost[10, :, :])[range(len(x2obs)), pchoice]
```

```
fig, ax = plt.subplots(figsize=(8,6))
ax.hist(ppc.flatten(), bins=50, normed=True, alpha=0.3, color='green', label='Posterior predictions')
ax.hist(df['x2'], bins=50, normed=True, alpha=0.3, label='Training data')
ax.legend(loc=1)
```

```
df['x2'].values.shape
```

    (2022,)

```
with x2model:
    x2trace = pm.sample(1000, step=pm.Metropolis(), n_init=1000, random_seed=SEED)

```

    INFO (theano.gof.compilelock): Refreshing lock /Users/jlao/.theano/compiledir_Darwin-17.4.0-x86_64-i386-64bit-i386-3.5.1-64/lock_dir/lock
    INFO:theano.gof.compilelock:Refreshing lock /Users/jlao/.theano/compiledir_Darwin-17.4.0-x86_64-i386-64bit-i386-3.5.1-64/lock_dir/lock
    Multiprocess sampling (2 chains in 2 jobs)
    INFO:pymc3:Multiprocess sampling (2 chains in 2 jobs)
    CompoundStep
    INFO:pymc3:CompoundStep
    >Metropolis: [delta_interval__]
    INFO:pymc3:>Metropolis: [delta_interval__]
    >Metropolis: [eta_interval__]
    INFO:pymc3:>Metropolis: [eta_interval__]
    >Metropolis: [beta_logodds__]
    INFO:pymc3:>Metropolis: [beta_logodds__]
    >Metropolis: [alpha_log__]
    INFO:pymc3:>Metropolis: [alpha_log__]
    100%|██████████| 1500/1500 [00:45<00:00, 42.64it/s]
    INFO (theano.gof.compilelock): Refreshing lock /Users/jlao/.theano/compiledir_Darwin-17.4.0-x86_64-i386-64bit-i386-3.5.1-64/lock_dir/lock
    INFO:theano.gof.compilelock:Refreshing lock /Users/jlao/.theano/compiledir_Darwin-17.4.0-x86_64-i386-64bit-i386-3.5.1-64/lock_dir/lock
    The gelman-rubin statistic is larger than 1.4 for some parameters. The sampler did not converge.
    ERROR:pymc3:The gelman-rubin statistic is larger than 1.4 for some parameters. The sampler did not converge.
    The estimated number of effective samples is smaller than 200 for some parameters.
    ERROR:pymc3:The estimated number of effective samples is smaller than 200 for some parameters.

```
x2trace.varnames
ax.hist(x2trace['w'].mean(axis=0))
```

    (array([25.,  0.,  0.,  1.,  1.,  0.,  0.,  0.,  1.,  2.]),
     array([3.84127949e-11, 2.75111123e-02, 5.50222246e-02, 8.25333369e-02,
            1.10044449e-01, 1.37555562e-01, 1.65066674e-01, 1.92577786e-01,
            2.20088898e-01, 2.47600011e-01, 2.75111123e-01]),
     )

```
plot_x = np.arange(K) + 1
fig, ax = plt.subplots(figsize=(8,6))
#ax.hist(df['x2'].values, alpha=0.2, color='blue', normed=True)
ax.plot(plot_x-0.5, x2trace['w'].mean(axis=0), label='w')
ax.plot(plot_x-0.5, np.cumsum(x2trace['w'].mean(axis=0)))
#ax.plot(plot_x-0.5, x2trace['eta'].mean(axis=0), label='eta')
#ax.plot(plot_x-0.5, x2trace['delta'].mean(axis=0), label='delta')
ax.set_xlim(0, K)
ax.set_ylabel('Mixture weight')
```

```
sns.distplot(x2trace['w'].mean(axis=0))
x2trace.varnames
```

```
with x2model:
    x2ppc = pm.sample_ppc(x2trace, 1000)

fig, ax = plt.subplots(figsize=(8,6))
ax.hist(x2ppc['obsx2'], bins=50, normed=True, alpha=0.3, color='green', label='Posterior predictions')
ax.hist(df['x2'], bins=50, normed=True, alpha=0.3, label='Training data')
ax.legend(loc=1)
```

```
fig, ax = plt.subplots(figsize=(8,6))
x2ppc['obsx2'].min(), x2ppc['obsx2'].max(), x2ppc['obsx2'][:20], x1_shift[:20], x2ppc['obsx2'][:-5]
```

```
from scipy.stats import wasserstein_distance, energy_distance
energy_distance(ppc['obs'], df['x1']), wasserstein_distance(ppc['obs'], df['x1'])
```

### Trials and tribulations
Do a conda install pydot, graphviz
Then, Add the path to dot.exe under graphviz (under Anaconda) to your system PATH variable
Then the following SVG call might work

```
import pydot, theano
ax = theano.tensor.scalar()
print(ax)
theano.pprint(ax)
theano.printing.debugprint(ax)
from IPython.display import SVG
SVG(theano.printing.pydotprint(ax, return_image=True, format='svg'))

ay = 3*(ax**2) + 1
ay.eval({ax : 1})
theano.pprint(ay)
theano.printing.debugprint(ay)
SVG(theano.printing.pydotprint(ay, return_image=True, format='svg'))

```

```
from sklearn.model_selection import train_test_split
df = df.sample(frac=1).reset_index(drop=True)
X_train, X_test, y_train, y_test = train_test_split(df['x6'], df['x1'], test_size=0.1)
X_train = np.reshape(X_train, (-1, 1))
y_train = np.reshape(y_train, (-1, 1))
X_test = np.reshape(X_test, (-1,1))
y_test = np.reshape(y_test, (-1, 1))

X_train, y_train, X_test, y_test
```

```
fig, ax = plt.subplots(9, 5, figsize=(12, 20), sharey=True)
for i in range(0, 42):
    sns.distplot(df.loc[df['x6']==i+1]['x1'], ax=ax[i//5, i%5])

```

```
rf_reg = RandomForestRegressor(max_depth=10, n_estimators=20)
rf_reg.fit(X_train, y_train)
y_ = np.round(rf_reg.predict(X_test))
y_ = np.reshape(y_, (-1, 1))
y_.shape, X_test.shape, y_test.shape, X_train.shape, y_train.shape,

```

```
#Within an epsilon distance
epsilon = 1
sum(np.sum((abs(y_ - y_test) <= epsilon), axis=1)) * 100./y_.shape[0]
```

```
X_train.max(), X_train.min(), y_train.max(), y_train.min()
```

### Log transformed in and out
Transform x6 and x1 by np.log

```
X_train, y_train, X_test, y_test = np.log(X_train), np.log(y_train), \
    np.log(X_test), np.log(y_test)

```

```
sns.distplot(y_train)
```

```
y_train.min()
#sns.distplot(np.log(y_train))
```

```

```

```

rf_reg = RandomForestRegressor(max_depth=10, n_estimators=200)
rf_reg.fit(X_train, y_train)
y_ = rf_reg.predict(X_test)
y_ = np.reshape(y_, (-1, 1))
#y_.shape, X_test.shape, y_test.shape, X_train.shape, y_train.shape,

```

```
import math
#Within an epsilon distance
epsilon = math.e
sum(np.sum((abs(y_ - y_test) <= epsilon), axis=1)) * 100./y_.shape[0]
```

```
sns.distplot(np.exp(y_), color='b')
sns.distplot(np.exp(y_test))
```

```
#np.sqrt(np.sum(np.sum((y_ - y_test)**2, axis=1)))
#np.sum((y_ - y_test)**2, axis=1)
np.mean(y_ - y_test), np.var(y_ - y_test), np.max(y_ - y_test), np.min(y_ - y_test)
```

### Prepare the data
Training data consists of X1, X2 pairs
Also split the data into train and test

```
from sklearn.cross_validation import train_test_split
def prepare_data(df):
    X_data = df[['x1', 'x2']]

    #Shuffle
    X_data = X_data.sample(frac=1).reset_index(drop=True)

    #split - train, test
    X_train, X_test, y_train, y_test = train_test_split(X_data['x1'], X_data['x2'], test_size=0.01)
    X_train = np.reshape(X_train, (-1, 1))
    X_test = np.reshape(X_test, (-1, 1))
    y_train = np.reshape(y_train, (-1, 1))
    y_test = np.reshape(y_test, (-1, 1))
    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = prepare_data(df_train)
```

```

```

### Plot the training data (X2)

```
fig, ax = plt.subplots()
ax.scatter(X_train, y_train, color='g', alpha=0.3)
plt.xlabel('X1'); plt.ylabel('X2')
```

_This makes sense of course, since X2 >= X1_

```
sns.distplot(y_train)
plt.legend()

```

```
xrange = np.arange(X_train.min(), X_train.max() + 1)
fig, ax = plt.subplots()
ax.plot(X_train[arange])
```

### Define a model for X2
Assuming BetaBinomial, discrete distribution with support over positive integers
The model for X2 will be a Bayesian Neural Net conditioned on the value of X1
The prior for the X2's support \{0..n\}  and it's alpha and beta are dependent on X1

```
ntrain = len(X_train)

#Construct the NN
def construct_nn(nn_in, nn_out):
    n_hidden = 5
    init_1 = np.random.randn(1, n_hidden).astype(theano.config.floatX)
    init_2 = np.random.randn(n_hidden, n_hidden).astype(theano.config.floatX)
    init_3 = np.random.randn(n_hidden, 1).astype(theano.config.floatX)

    with pm.Model() as bnn:
        a = pm.Uniform('a', lower=0.4, upper=5.)
        b = pm.Uniform('b', lower=2., upper=8.)
        c = pm.Uniform('c', lower=1., upper=60.)

        weights_1 = pm.Normal('w_1', mu=0, sd=1, shape=(1, n_hidden), testval=init_1)
        weights_2 = pm.Normal('w_2', mu=0, sd=1, shape=(n_hidden, n_hidden), testval=init_2)
        weights_3 = pm.Normal('w_3', mu=0, sd=1, shape=(n_hidden, 1), testval=init_3)

        #Activations
        act_1 = pm.math.tanh(pm.math.dot(nn_in, weights_1))
        act_2 = pm.math.tanh(pm.math.dot(act_1, weights_2))
        act_3 = pm.math.sigmoid(pm.math.dot(act_2, weights_3))

        print("{}, {}, {},".format(act_3.tag.test_value.shape, act_3.shape, type(act_3)))
        print("{}".format(act_3.tag.test_value[:,0].mean()))
#         print("{}".format(act_3.tag.test_value[:,1].mean()))
#         print("{}".format(act_3.tag.test_value[:,2].mean()))
#         a = pm.Deterministic('a', act_3.tag.test_value[:,0].mean())
#         b = pm.Deterministic('b', act_3.tag.test_value[:,1].mean())
#         c = pm.Deterministic('c', act_3.tag.test_value[:,2].mean())
        out = pm.BetaBinomial('x2pred',
                              alpha=a,
                              beta=b,
                              n=tt.round(c),
                              observed=nn_out,
                              total_size=ntrain)

    return bnn
```

### Fit the model to data (Inference)
Uses ADVI to compute the posterior distribution of parameters

```
nn_in = theano.shared(X_train)
nn_out = theano.shared(y_train)
bnn = construct_nn(nn_in, nn_out)
with bnn:
    # inference = pm.ADVI()
    approx = pm.sample(1000)
```

```
with bnn:
    pm.plot_posterior(approx)

```

```

```

```

```

# Miscellaneous
## A Zero Truncated Poisson
__Custom Probability Distribution in PyMC3__

```
import theano.tensor as tt
from theano import function
from theano import printing
import pymc3 as pm

class ZeroTruncatedPoisson(pm.Discrete):
    '''
    Similar to a Poisson distribution, but the support is over positive integes
    and so, excludes zero. An example, number of items in your grocery shopping cart
    at checkout
    '''
    def __init__(self, mu, *args, **kwargs):
        super(ZeroTruncatedPoisson, self).__init__(*args, **kwargs)
        self.mu = tt.as_tensor_variable(mu)

    def logp(self, value):
        '''
        compute total_log_probability
        '''
        return tt.sum(value * tt.log(self.mu) - tt.log(tt.exp(self.mu) -1) - tt.gammaln(value))

    def random(self, point=None, size=None):
        '''
        Generates a random sample from Zero Truncated Poisson
        '''
        k = 1
        mu = self.mu.tag.test_value
        t = tt.exp(-self.mu)/(1 - tt.exp(-self.mu))/(1. * self.mu)
        tn = t.tag.test_value
        s = tn
        u = np.random.uniform()
        while s < u:
            k = k + 1
            tn = tn * mu / k
            s = s + tn
            u = np.random.uniform()
        return k
```

```
# If using a ZeroTruncatedPoisson
# obs = df_train['x1']
# with pm.Model() as zmodel:
#     mu = pm.HalfNormal('mu', sd=10.)
#     zp_poisson = ZeroTruncatedPoisson('zp', mu=mu, observed=obs)
#     ztrace = pm.sample(1000)
#     pm.plot_posterior(ztrace)

```

```
import numpy as np
import theano.tensor as tt
from theano import function
#tt.config.compute_test_value =
x = tt.dscalar('x')
y = tt.dscalar('y')
z = x + y
tt.pprint(z)
add = function([x, y], z)
n1 = add(402., 1)
type(n1)
#n1
with pm.Model() as mmodel:
    a1 =
    w1 = pm.Normal('w1', mu=0., sd=1., shape=(1, 2))
    a2 = pm.math.dot(a1, w1)

```

```
k = np.array([1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,48,1])
import theano.tensor as tt
def ztp_logp(k):
    return tt.sum(k * tt.log(lam) - tt.log(tt.exp(lam) - 1) - tt.gammaln(k))

ztp_logp(k)

```

```
vm1 = np.random.randn(1, 5).astype(theano.config.floatX)
type(vm1)
#vm1.shape
vm2= halfnorm.rvs(size=(1,5)).astype(theano.config.floatX)
type(vm2)
vm2.shape
X_train[0].shape
nn_in.shape

```

### Define the neural network

### Plot the posterior parameter distribution from 5000 samples

```
with bnn:
    trace = approx.sample(draws=5000)
    pm.traceplot(trace)
```

### Plot the learnt boundary against training data

```
with bnn:
    rpc = pm.sample_ppc(trace, 500)
    rpred = rpc['Category'].mean(axis=0)
    #rproba = rpc['Category'].std(axis=0)
    fig, ax = plt.subplots()
    ax.scatter(X_train[rpred > 0.5], rpred[rpred > 0.5], color='b', alpha=0.01)
    ax.scatter(X_train[rpred <= 0.5], rpred[rpred <= 0.5], color='r', alpha=0.01)
    ax.scatter(X_train[y_train], y_train[y_train], color='g', alpha=0.01)
    ax.scatter(X_train[~y_train], y_train[~y_train], color='k', alpha=0.01)
```

#### Plot the Evidence Lower Bound (ELBO) maximization history

```
with bnn:
    plt.plot(-inference.hist, label='ADVI', color='b', alpha=0.4)
    plt.xlabel('Iteration')
    plt.ylabel('ELBO')
```

**__What is the functional form of the neural net?__**

The neural net computes the probability parameter of a Bernoulli distribution based on a series of non-linearities and affine transformations of the input

```
bnn.Category.distribution
```

### Now time to predict (Posterior Predictive Check)

```
nn_in.set_value(X_test)
nn_out.set_value(y_test)
with bnn:
    ppc = pm.sample_ppc(trace, 500)
    pred = ppc['Category'].mean(axis=0)
    proba = ppc['Category'].std(axis=0)
```

### Plot the predictions

```
fig, ax = plt.subplots()
ax.scatter(X_test[pred > 0.5], pred[pred > 0.5])
ax.scatter(X_test[pred <= 0.5], pred[pred <= 0.5], color='r', alpha=0.2)
ax.scatter(X_test[y_test], y_test[y_test], color='c', alpha=0.1)
ax.scatter(X_test[~y_test], y_test[~y_test], color='c', alpha=0.1)

print('Accuracy: {}'.format( ( y_test == (pred > 0.5) ).mean() * 100 ))
```

### Confusion Matrix

```
from sklearn.metrics import confusion_matrix
cm = [ 1 if x > 0.5 else 0 for x in pred ]
cnf = confusion_matrix(cm, y_test)
sns.heatmap(cnf, annot=True, fmt='d', cmap='PiYG')
```

```
cnf
```

### Now the uncertainty in prediction

```
fig, ax = plt.subplots(figsize=(10, 5))
ax.errorbar(X_test[pred > 0.5], pred[pred > 0.5], yerr = proba[pred > 0.5], fmt='o', ecolor='g', capthick=2, alpha=0.1)
ax.errorbar(X_test[pred <= 0.5], pred[pred <= 0.5], yerr = proba[pred <= 0.5],
            fmt='o', ecolor='r', color='y', capthick=2, alpha=0.1)
```

## Comparison to a Gaussian Process classifier

```
%%time
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF

gp_clf = GaussianProcessClassifier(1.0 * RBF(1.0))
nsize=1000
gp_clf.fit(X_train[:nsize,:], y_train[:nsize,:])
#clf.fit(X_train, y_train)
```

**__Phew.... that took for ever. Especially in comparison to BNN__**

```
%%time
print gp_clf.score(X_test, y_test)
xx = np.arange(1,70)
pp = gp_clf.predict_proba(xx.reshape(-1,1))
fig, ax = plt.subplots()
ax.scatter(xx, pp[:,1], color='r')
```

## Compare to a random forest classifier

```
%%time
from sklearn.ensemble import RandomForestClassifier

rf_clf = RandomForestClassifier(max_depth=5, n_estimators=10, max_features=1)
rf_clf.fit(X_train, y_train)
```

```
%%time
print (rf_clf.score(X_test, y_test))
xx = np.arange(1,70)
pp = rf_clf.predict_proba(xx.reshape(-1,1))
fig, ax = plt.subplots()
ax.scatter(xx, pp[:,1], color='r')
```

## Compare to AdaBoosted Decision Trees

```
%%time
from sklearn.ensemble import AdaBoostClassifier
ad_clf = AdaBoostClassifier()
ad_clf.fit(X_train, y_train)
```

```
%%time
print (ad_clf.score(X_test, y_test))
xx = np.arange(1, 70)
pp = ad_clf.predict_proba(xx.reshape(-1,1))
fig, ax = plt.subplots()
ax.scatter(xx, pp[:,1], color='r')
```

## Miscellaneous

```
with pm.Model():
    bs = pm.BetaBinomial('betasample', alpha=3., beta=4., n=50)
    bstrace = pm.sample(10000)
    pm.plot_posterior(bstrace)
```

```
sns.distplot(df_train['x6'])
```


# `GiRaFFE_NRPy`: Basic Equations

## Authors: Patrick Nelson &

In this tutorial, we will introduce the basic GRFFE equations that we will wish to solve.

Throughout this section and beyond, we will work in geometrized units such that $G=c=1$. As is standard in NRPy+,
* Greek indices refer to four-dimensional quantities where the zeroth component indicates temporal (time) component,
* and Latin indices refer to three-dimensional quantities. This is somewhat counterintuitive since Python always indexes its lists starting from 0. As a result, the zeroth component of three-dimensional quantities will necessarily indicate the first *spatial* direction.
As a corollary, any expressions involving mixed Greek and Latin indices will need to offset one set of indices by one: A Latin index in a four-vector will be incremented and a Greek index in a three-vector will be decremented. For further detail and examples, see [this notebook](../Tutorial-GRFFE_Equations-Cartesian.ipynb).

To describe spacetime, we will use a standard 3+1 decomposition in which the line element is given as
$$
ds^2 = -\alpha^2 dt^2 + \gamma_{ij} \left( dx^i + \beta^i dt \right) \left( dx^j + \beta^j dt \right).
$$
where $\alpha$ is the lapse function, $\beta^i$ is the shift vector, $\gamma_{ij}$ is the spatial three metric. Given two adjacent spatial hypersurfaces with coordinate times $t_0$ and $t_0+dt$, $\alpha dt$ is the proper time interval between the two hypersurfaces, $\beta^i$ represents the magnitude of the spatial coordinate shift between them, and $\gamma_{ij}$ is the spatial three metric within a hypersurface.

We will use the pure electromagnetic stress energy tensor,
$$
T^{\mu\nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{1}{2} b^2 g^{\mu\nu} - b^\mu b^\nu,
$$
where
\begin{align}
\sqrt{4\pi} b^0 = B^0_{\rm (u)} &= \frac{u_j B^j}{\alpha} \\
\sqrt{4\pi} b^i = B^i_{\rm (u)} &= \frac{B^i + (u_j B^j) u^i}{\alpha u^0},\\
\end{align}
and $u^\mu$ is the four-velocity. We will not couple this stress-energy tensor to the Einstein Tensor because the plasma we are

**Derivation from F^{\mu\nu}, including the constraints of GRFFE**

In place of the magnetic field $B^i$, we choose to evolve the vector potential $A_i$. By doing so, we guarantee that the magnetic field is divergenceless to round-off level. We also evolve the Poynting flux $\tilde{S}_i$, which in GRFFE, represents the momentum flux in the plasma, and $\psi^6 \Phi$, where $\psi^6=\sqrt{\gamma}$ and $\gamma$ is the determinant of the three metric.

We thus arrive at the following set of equations to evolve:
\begin{align}
\partial_t \tilde{S}_i + \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) &= \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} \\
\partial_t [\sqrt{\gamma} \Phi] &= -\partial_j (\alpha\sqrt{\gamma}A^j - \beta^j [\sqrt{\gamma} \Phi]) - \xi \alpha [\sqrt{\gamma} \Phi] \\
\partial_t A_i &= \epsilon_{ijk} v^j B^k - \partial_i (\alpha \Phi - \beta^j A_j) \\
\end{align}

Here, the four velocity $u^\mu$ is the velocity of the plasma as measured by a normal observer. There are several possible choices of three-velocity that can be made; the ones which we will use here are the drift velocity $v^i$ and the Valencia three-velocity $\bar{v}^i$. While we have chosen to use the Valencia three-velocity in this version of `GiRaFFE`, we have also frequently made use of a relationship expressing this in terms of the drift velocity, which was used in the original `GiRaFFE`. The usefulness of this relationship to drift velocity extends beyond merely translating the original code. As discussed in [Paschalidis, et al.](https://arxiv.org/pdf/1310.3274.pdf), Sec. III.A (just above Eq. 45, with a proof in Appendix A), there is a one-parameter family of velocity definitions that fulfill the GRFFE conditions. The drift velocity sets this parameter to 0, which minimizes the Lorentz factor and *guarantees* that the four-velocity and magnetic fields are orthogonal to each other. This simplifies the form of $b^\mu$ and quantities that depend on it.

This must be taken into account in developing this unit test, because NRPy+'s GRFFE module defaults to using a definition of $b^\mu$ that does not assume that this criterion is met, while the original `GiRaFFE` code assumes this in its C2P and P2C solvers. So, if we do not guarantee that our test data fulfills this criterion, these two different routines will produce different results. We will now go through the derivation of the equation used by `GiRaFFE` from first principles to show where this extra term appears.

This is the equation used by `GiRaFFE`, pulled from Eqs. 47 and 85 of [this paper](https://arxiv.org/abs/1310.3274):
$$\tilde{S}_i = \gamma_{ij} \frac{\bar{v}^j \sqrt{\gamma}B^2}{4 \pi},$$
or $$\tilde{S}_i = \gamma_{ij} \frac{(v^j + \beta^j) \sqrt{\gamma}B^2}{4 \pi \alpha},$$
where $\bar{v}^j$ is the Valencia 3-velocity and $v^j$ is the drift velocity.

In IllinoisGRMHD (IGM), the expression used is $\tilde{S}_i = \alpha \sqrt{\gamma} T^0_{{\rm EM}i},$
where
\begin{align}
T^{\mu\nu}_{\rm EM} &= b^2 u^{\mu} u^{\nu} + \frac{1}{2} b^2 g^{\mu\nu} - b^\mu b^\nu \\
b^0 &= \frac{u_j B^j}{\sqrt{4\pi} \alpha} \\
b^i &= \frac{B^i + (u_j B^j) u^i}{\sqrt{4\pi} \alpha u^0} \\
u^i &= u^0 v^i.
\end{align}
**The above has been pulled from the C2P unit test. This is probably a better permanent home for this discussion.**

We also use the Valencia three-velocity $\bar{v}^i$ and the magnetic field $B^i$ as primitives, which can be defined in terms of the conservative variables:
\begin{align}
B^i &= \epsilon^{ijk} \partial_j A_k \\
\bar{v}^i &= 4 \pi \frac{\gamma^{ij} {\tilde S}_j}{\sqrt{\gamma} B^2} \\
\end{align}

```python

```


# Question 1

Considérons le système d'inégalités $Ax \geq b$, $x \geq 0$, où $b \geq 0$. Nous transformons ce
système sous une forme standard en introduisant m variables de surplus $y$ pour
obtenir le système suivant $Ax-y = b$, $x \geq 0$, $y \geq 0$ où $b \geq 0$. Dénotons
\begin{gather*}
b_k = \max_{1 \leq 1 \leq m} \lbrace b_i \rbrace,
\end{gather*}
et considérons le nouveau problème sous forme standard obtenu du précédent en
additionnant la $k$ième ligne à toutes les autres lignes après avoir changé leur signe.

Indiquez pourquoi il suffit d'introduire une seule variable artificielle dans une phase I pour obtenir
une solution de base réalisable initiale.

Illustrer cette procédure sur le problème suivant
\begin{align*}
x_1 + 2x_2 + x_3 &\geq 4 \\
2x_1 + x_2 + x_3 &\geq 5 \\
2x_1 + 3x_2 + 2x_3 &\geq 6 \\
x_j \geq 0,\ j &= 1,2,3.
\end{align*}

## Solution question 1

On introduit les variables $y_1$, $y_2$ et $y_3$ dans le problème pour le mettre sous forme standard
\begin{align*}
x_1 + 2x_2 + x_3 - y_1 & =  4 \\
2x_1 + x_2 + x_3 - y_2 & = 5 \\
2x_1 + 3x_2 + 2x_3 - y_3 & = 6 \\
x_j \geq 0,\ j &= 1,2,3,\\
y_j \geq 0,\ j &= 1,2,3.
\end{align*}

On utilise l'indication, on choisit
\begin{gather*}
b_k = \max_{1 \leq 1 \leq 3} \lbrace b_i \rbrace = b_3
\end{gather*}
et on effectue les opérations comme dans l'énoncé
\begin{align*}
x_1 + x_2 + x_3 - y_3 + y_1 & =  2 \\
2x_2 + x_3 - y_3 + y_2 & = 1 \\
2x_1 + 3x_2 + 2x_3 - y_3 & = 6 \\
x_j \geq 0,\ j &= 1,2,3,\\
y_j \geq 0,\ j &= 1,2,3,\\
t_1 \ge 0.
\end{align*}

On obtient ainsi un système totalement équivalent au premier sur lequel on peut appliquer la phase I qui prend la forme suivante

\begin{align*}
&\max t_1\\
&\begin{aligned}
\text{tel que } \quad x_1 + x_2 + x_3 - y_3 + y_1 & =  2 \\
2x_2 + x_3 - y_3 + y_2 & = 1 \\
2x_1 + 3x_2 + 2x_3 - y_3 + t_1 & = 6 \\
t_1,\ x_j, y_j \geq 0,\ j &= 1,2,3.
\end{aligned}
\end{align*}

De manière générale, on aurait

Pour $i=k$,
\begin{align*}
\sum_{j=1}^{n} a_{kj} x_j - y_k + t_1 = b_k \ge 0
\end{align*}

Pour $i = 1, \ldots, m$ et $i \ne k$,
\begin{align*}
\sum _{j=1}^{n} ( a_{kj} - a_{ij} ) x_j - y_k + y_i = b_k - b_i \ge 0
\end{align*}
Le système est équivalent par construction et il est réalisable puisque les
termes de droites sont non-négatifs.
Pour $i = 1, \ldots, m$ et $i \ne k$, on maintenant que $y_i$ est une variable de base.
Il ne manque plus qu'à déterminer une dernière variable de base et pour ce faire on
fait une phase I en ajoutant la variable $t_1$ à la kième équation.

# Question 2

Question 5 de l'examen intra A2011.

On a le problème suivant
\begin{align*}
\begin{array}{l c c c c c c c}
  \min\  z  = &  -3x_1    & - &  x_2   & - &  3x_3  &  &   \\
  \text{tel que }   &  2x_1     & + &  1x_2  & + &  x_3   &  \leq  & 2  \\
            &  x_1      & + &  2x_2  & + &  3x_3  &  \leq  & 5  \\
            &  2x_1     & + &  2x_2  & + &  x_3   &  \leq  & 6  \\
            & x_1 && x_2 && x_3 & \geq & 0      \\
\end{array}
\end{align*}

**(a)** Transformer le problème sous forme standard

**(b)** À une itération du simplexe révisé on obtient
\begin{equation}
\begin{array}{ccccccc}
x_1 &\frac{1}{2} & 0 & 0 & 1 \\
x_5 &\frac{-1}{2} & 1 & 0 & 4 \\
x_6 &-1 & 0 & 1 & 4 \\
-z &\frac{3}{2} & 0 & 0 & 3 \\
\end{array}
\end{equation}
où dans la dernière ligne on a mis **l'opposé du vecteur des multiplicateurs** $\lambda^t$ $\left(\lambda^t=c^t_B B^{-1} \right)$.

Quelle est la base associée à cette itération?

**(c)** En partant du tableau du simplexe révisé en **(b)**, continuer l'algorithme du simplexe révisé jusqu'à l'optimum.

**(d)**
Soit le nouveau coût initial $\hat{c}_2 = c_2+\delta$, où $c_2=-1$ est le coût initial associé à $x_2$. Pour quelle valeur de $\delta$ la solution trvouvée en $\textbf{(b)}$ demeure-t-elle optimale?

## Solution question 2

**(a)** Pour transformer le problème sous forme standard, on introduit des variables d'écarts $x_4$, $x_5$ et $x_6$.

\begin{align*}
\begin{array}{l c c c c c c c c c c c c c}
  \min  z  = &  -3x_1    & - &  x_2   & - &  3x_3  &   &       &   &     &  &      &  &   \\
  \text{tel que }    &  2x_1     & + &  x_2  & + &  x_3   & + &  x_4  &   &     &  &      &= & 2  \\
            &  x_1      & + &  2x_2  & + &  3x_3  &   &       & + & x_5 &  &      &= & 5  \\
            &  2x_1     & + &  2x_2  & + &  x_3   &   &       &   &     & +&  x_6 &= & 6  \\
            &  x_1, && x_2, && x_3, && x_4, && x_5, && x_6 & \geq & 0
\end{array}
\end{align*}

**(b)**
La base B est composée des colonnes associées aux variables $x_1$, $x_5$ et $x_6$.
$$
B = \begin{pmatrix}
    2 & 0 & 0 \\
    1 & 1 & 0 \\
    2 & 0 & 1
\end{pmatrix}
$$

**(c)**
Pour savoir si nous devons poursuivre la résolution du problème, nous
calculons d'abord les coûts réduits:

Nous disposons dans le tableau de
l'inverse de la base, et du vecteur multiplicateur $\lambda$: $\lambda^t = \left(\frac{-3}{2}, 0, 0 \right)$.

Pour calculer les coûts réduits :

$\bar c^t = c^t - \lambda^t A $

$\bar c^t$ = (0, $\frac{1}{2}$, $\frac{-3}{2}$, $\frac{3}{2}$, 0 0)

$\bar c_3 < 0$ la base courante n'est pas optimale. $x_3$ rentre dans la base.

Déterminons la variable de sortie:

$y_3 = B^{-1}A_3$

$$
y_3 = \begin{pmatrix}
      1/2  \\
      5/2  \\
      0
\end{pmatrix}
$$
De plus, $\min \left\{ \frac{1}{1/2}, \frac{4}{5/2} \right\} = 8/5$.
Ainsi $x_5$ sort de la base.

\begin{align*}
\begin{array} { c c c c c|  c}
        &    &   &   &   &  y_3  \\
\hline
 x_1    & 1/2 & 0 & 0 & 1 & 1/2  \\
 x_5    & -1/2 & 1 & 0 & 4 & 5/2 \\
 x_6    & -1  & 0 & 1 & 4 &  0 \\
\hline
 -z     & 3/2 & 0 & 0 & 3 & -3/2 \\
\hline
\end{array}
\end{align*}

Les opérations sur les lignes se résument ainsi:
$L'_1 \leftarrow L_1 - \frac{1}{2} L'_2$ \\
$L'_2 \leftarrow \frac{2}{5} L_2$ \\
$L'_3 \leftarrow L_3$ \\
$L'_4 \leftarrow L_4 + \frac{3}{2} L' _2$

Le nouveau tableau du simplexe révisé est donc le suivant:

\begin{align*}
\begin{array}{|c| c c c |c|}
\hline
 x_1  & 3/5  & -1/5 & 0 & 1/5  \\
 x_3  & -1/5 & 2/5  & 0 & 8/5  \\
 x_6  & -1   & 0    & 1 & 4    \\
\hline
 -z   & 6/5  & 3/5  & 0 & 27/5 \\
\hline
\end{array}
\end{align*}

Dès lors,
$$
B^{-1} = \begin{pmatrix}
    3/5 & -1/5 & 0 \\
    -1/5 & 2/5 & 0 \\
    -1 & 0 & 1
\end{pmatrix}
$$
et
$$
\lambda^t = c_b^t B^{-1} = (-6/5 , -3/5, 0).
$$
et nous pouvons calculer les coûts réduits $\bar c^t = c^t - \lambda^tA$:
$$
\bar c^t = (0, 7/5, 0, 6/5, 3/5, 0).
$$
Tous les coûts réduits sont positifs ou nuls. La base courante est donc optimale.
Une solution optimale est donnée par :
$x_1^* = 1/5$, $x_2^* = 0$, $x_3^* = 8/5$, $x_4^* = 0$, $x_5^* = 0$, $x_6^* = 4$, de valeur optimale -27/5.

**(d)**
La solution demeure optimale tant que $(-1 + \delta) - \lambda A_2 \geq 0$.
Alors la solution demeure réalisable pour tout $\delta \geq -7/5$.

### Question supplémentaire : Dualité

**(e)**
Formuler le dual du problème et vérifier que $\lambda = (-1.2, 0.6, 0)$ est une solution réalisable de ce problème.

Le dual de (P) est:

\begin{align*}
\begin{array}{c c c c c c c c}
\max    & 2 \lambda_1  &  + & 5 \lambda_2  &  +  & 6 \lambda_3  &        &    \\
  \text{tel que }   & 2 \lambda_1  & +  &   \lambda_2  &  +  & 2 \lambda_3  &  \leq  & -3 \\
       &  \lambda_1   & +  & 2 \lambda_2  &  +  & 2 \lambda_3  &  \leq  & -1 \\
       & \lambda_1  & +  & 3 \lambda_2  &  +  &  \lambda_3   &  \leq  & -3 \\
       &  \lambda_1   &    &            &     &            &  \leq  & 0 \\
       &            &    &   \lambda_2  &     &            &  \leq  & 0 \\
       &            &    &            &     & \lambda_3    &  \leq  & 0 \\
\end{array}
\end{align*}

Le vecteur $\lambda = (-1.2, 0.6, 0)$ est égal au vecteur des multiplicateurs
optimaux (-6/5, -3/5, 0).  Le vecteur des multiplicateurs étant solution
réalisable et optimale du problème dual, on a vérifié que $\lambda$ est
réalisable dans le dual.

## Question 3, \#4 examen Intra 2018

Soit le problème de programmation linéaire suivant
\begin{align*}
\min_x \ &z = –8x_1 – 9x_2 – 7x_3  – 6x_4  – 8x_5 – 9x_6 \\
\mbox{sujet à } & x_1          + x_3               +x_5           = 4 \\
& x_2 + x_4 + x_6 = 2 \\
& x_1 + x_4 = 2 \\
& x_2 + x_5 = 1 \\
& x_j \geq 0,\ j = 1,2,\ldots,6.
\end{align*}

Utilisez la phase I de la méthode à deux phases pour trouver une solution réalisable au problème.

La forme révisée du simplexe est utilisée pour résoudre le problème, et à une certaine itération nous avons le tableau réduit suivant
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2\\
x_2 & 0 & 0 & 0 & 1 & 1\\
x_3 & 1 & 0 & -1 & 0 & 2\\
x_6 & 0 & 1 & 0 & -1 & 1\\
& 7 & 9 & 1 & 0 & 48
\end{matrix}
$$
La dernière ligne contient l'opposé des multiplicateurs, correspondant à $c_B^T B^{-1}$.

Que vaut $B$? Et $B^{-1}$? La solution de base actuelle est-elle optimale? Si oui, justifier votre réponse. Si non, poursuivre l'application de la forme révisée du simplexe pour déterminer une solution optimale.

De quelle quantité faut-il modifier le coût associé à la variable $x_2$ dans la fonction économique (i.e. son coefficient dans la fonction objectif) pour que le coût réduit de cette variable soit nul relativement à la base optimale obtenue au point précédent. Déterminer alors une solution de base optimale alternative.

# Solution question 3

Remarquons tout d'abord que $x_3$ et $x_6$ sont isolées, aussi nous n'avons que deux variables artificielles à ajouter, aux troisième et quatrième contraintes, donnant le problème artificiel suivant:
\begin{align*}
\min_{x} \ & x_7 + x_8 \\
\mbox{sujet à } & x_1 + x_3               +x_5           = 4 \\
& x_2 + x_4 + x_6 = 2 \\
& x_1 + x_4 + x_7 = 2 \\
& x_2 + x_5 + x_8 = 1 \\
& x_j \geq 0,\ j = 1,2,\ldots,8.
\end{align*}

Ceci mène à la séquence suivante d'itérations de l'algorithme du simplexe:
$$
\begin{matrix}
1 & 0 & 1 & 0 & 1 & 0 & 0 & 0 & 4 \\
0 & 1 & 0 & 1 & 0 & 1 & 0 & 0 & 2 \\
1 & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 2 \\
0 & 1 & 0 & 0 & 1 & 0 & 0 & 1 & 1 \\
0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 0
\end{matrix}
$$
Après annulation des coûts réduits, nous avons
$$
\begin{matrix}
1 & 0 & 1 & 0 & 1 & 0 & 0 & 0 & 4 \\
0 & 1 & 0 & 1 & 0 & 1 & 0 & 0 & 2 \\
1 & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 2 \\
0 & 1 & 0 & 0 & 1 & 0 & 0 & 1 & 1 \\
-1 & -1 & 0 & -1 & -1 & 0 & 0 & 0 & -3
\end{matrix}
$$
Faisons entrer $x_1$ dans la base. $x_7$ sort, donnant
$$
\begin{matrix}
0 & 0 & 1 & -1 & 1 & 0 & -1 & 0 & 2 \\
0 & 1 & 0 & 1 & 0 & 1 & 0 & 0 & 2 \\
1 & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 2 \\
0 & 1 & 0 & 0 & 1 & 0 & 0 & 1 & 1 \\
0 & -1 & 0 & 0 & -1 & 0 & 2 & 1 & -1
\end{matrix}
$$
Nous pouvons à présent faire entrer $x_1$ dans la base, en faisant sortir $x_8$, ce qui donne
$$
\begin{matrix}
0 & 0 & 1 & -1 & 1 & 0 & -1 & 0 & 2 \\
0 & 0 & 0 & 1 & -1 & 1 & 0 & -1 & 1 \\
1 & 0 & 0 & 1 & 0 & 0 & 1 & 0 & 2 \\
0 & 1 & 0 & 0 & 1 & 0 & 0 & 1 & 1 \\
0 & 0 & 0 & 0 & 0 & 0 & 2 & 2 & 0
\end{matrix}
$$
Dès lors, une solution réalisable au problème initial est $x = (2, 1, 2, 0, 0, 1)$.

La forme révisée du simplexe est utilisée pour résoudre le problème, et à une certaine itération nous avons le tableau réduit suivant
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2\\
x_2 & 0 & 0 & 0 & 1 & 1\\
x_3 & 1 & 0 & -1 & 0 & 2\\
x_6 & 0 & 1 & 0 & -1 & 1\\
& 7 & 9 & 1 & 0 & 48
\end{matrix}
$$
La dernière ligne contient l'opposé des multiplicateurs, correspondant à $c_B^T B^{-1}$.

Que vaut $B$? Et $B^{-1}$? La solution de base actuelle est-elle optimale? Si oui, justifier votre réponse. Si non, poursuivre l'application de la forme révisée du simplexe pour déterminer une solution optimale.

Les variables de base selon le dictionnaire sont $x_1$, $x_2$, $x_3$ et $x_6$. En se référant à la matrice $A$ initiale, incluant les variables artificielles, nous en déduisons
$$
B =
\begin{pmatrix}
1 & 0 & 1 & 0 \\
0 & 1 & 0 & 1 \\
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0
\end{pmatrix}
$$
La base initiale est la matrice identité. Dès lors, les colonnes du simplexe révisé donne immédiatement $B^-1$:
$$
B^{-1} =
\begin{pmatrix}
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 0 & -1 & 0 \\
0 & 1 & 0 & -1
\end{pmatrix}
$$
Nous pouvons facilement vérifier sous Julia qu'il s'agit bien l'inverse de $B$.

```julia
B = [ 1 0 1 0 ; 0 1 0 1 ; 1 0 0 0 ; 0 1 0 0 ]
invB = [ 0 0 1 0 ; 0 0 0 1 ; 1 0 -1 0 ; 0 1 0 -1 ]
B*invB
```

    4×4 Matrix{Int64}:
     1  0  0  0
     0  1  0  0
     0  0  1  0
     0  0  0  1

Pour voir si cette base est optimale, nous devons calculer les coûts réduits. Rappelons que les coûts réduits sont obtenus par
$$
r_D^T = c_D^T - c_B^TB^{-1}D = c_D^T - \lambda^TD,
$$
et de l'énoncé,
$$
\lambda^T = - \begin{pmatrix} 7 & 9 & 1 & 0 \end{pmatrix}.
$$
Nous pouvons le vérifier:
$$
\lambda^T = - \begin{pmatrix} 8 & 9 & 7 & 9 \end{pmatrix}
\begin{pmatrix}
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 0 & -1 & 0 \\
0 & 1 & 0 & -1
\end{pmatrix}
= - \begin{pmatrix} 7 & 9 & 1 & 0 \end{pmatrix}.
$$

Dès lors,
$$
r_D^T = - \begin{pmatrix} 6 & 8 \end{pmatrix} + \begin{pmatrix} 7 & 9 & 1 & 0 \end{pmatrix}
\begin{pmatrix} 0 & 1 \\ 1 & 0 \\ 1 & 0 \\ 0 & 1 \end{pmatrix}
= \begin{pmatrix} 4 & -1 \end{pmatrix}.
$$
Il persiste un coût réduit négatif. La solution actuelle n'est donc pas optimale.
Note: on a ignoré les deux dernières composantes de $\lambda$, qui correspondent aux variables artificielles.

Nous faisons entrer dans la base $x_5$.
Exprimons $y_5 = B^{-1}a_5$:
$$
y_5 = \begin{pmatrix}
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 0 & -1 & 0 \\
0 & 1 & 0 & -1
\end{pmatrix}
\begin{pmatrix}
1 \\ 0 \\ 0 \\ 1
\end{pmatrix}
= \begin{pmatrix}
0 \\ 1 \\ 1 \\ -1
\end{pmatrix}
$$

Pivotage
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2 & 0 \\
x_2 & 0 & 0 & 0 & 1 & 1 & 1 \\
x_3 & 1 & 0 & -1 & 0 & 2 & 1 \\
x_6 & 0 & 1 & 0 & -1 & 1 & -1 \\
-z & 7 & 9 & 1 & 0 & 48 & -1 \\
\end{matrix}
$$
Note: nous avons inséré le coût réduit associé à $y_5$ dans la dernière, comme nous devrons l'annuler.
La règle du pivotage nous donne $x_2$ comme variable sortante, ce qui donne
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2 \\
x_5 & 0 & 0 & 0 & 1 & 1 \\
x_3 & 1 & 0 & -1 & -1 & 1 \\
x_6 & 0 & 1 & 0 & 0 & 2 \\
-z & 7 & 9 & 1 & 1 & 49  \\
\end{matrix}
$$

Nous retrouvons les coûts réduits à la dernière ligne, que nous pouvons vérifier:
$$
-\lambda^T = -c_B^TB^{-1} = \begin{pmatrix} 8 & 8 & 7 & 9 \end{pmatrix}
\begin{pmatrix} 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ 1 & 0 & -1 & -1 \\ 0 & 1 & 0 & 0 \end{pmatrix}
= \begin{pmatrix} 7 & 9 & 1 & 1 \end{pmatrix}
$$
et
$$
r_D^T = c_D^T-\lambda^TD = - \begin{pmatrix} 9 & 6 \end{pmatrix} + \begin{pmatrix} 7 & 9 & 1 & 1 \end{pmatrix}
\begin{pmatrix} 0 & 0 \\ 1 & 1 \\ 0 & 1 \\ 1 & 0 \end{pmatrix}
= \begin{pmatrix} 1 & 4 \end{pmatrix}
$$

Tous les coûts réduits sont strictement positifs; nous sommes à l'optimalité. La solution optimale est $(2, 0, 1, 0, 1, 2)$ et la valeur optimale, $-49$.

Vérifions-le avec JuMP

```julia
using JuMP
using GLPK
```

```julia
c = [ -8 -9 -7 -6 -8 -9 ]
A = [ 1 0 1 0 1 0 ; 0 1 0 1 0 1 ; 1 0 0 1 0 0 ; 0 1 0 0 1 0 ]
b = [ 4 2 2 1 ]
```

    1×4 Matrix{Int64}:
     4  2  2  1

```julia

m = Model(with_optimizer(GLPK.Optimizer))

@variable(m, 0 <= x[1:6])

for i = 1:4
    @constraint(m, sum(A[i,j]*x[j] for j = 1:6) == b[i])
end

@objective(m, Min, sum(c[i]*x[i] for i = 1:6))

print(m)
```

```julia
optimize!(m)
println("Objective value: ", objective_value(m))
println("x = ", value.(x))
```

De quelle quantité faut-il modifier le coût associé à la variable $x_2$ dans la fonction économique (i.e. son coefficient dans la fonction objectif) pour que le coût réduit de cette variable soit nul relativement à la base optimale obtenue au point précédent. Déterminer alors une solution de base optimale alternative.

Nous devons donc calculer le coût réduit associé à $x_2$ et déterminer $\Delta c_2$, la quantité à ajouter à $c_2$, le coefficient de $x_2$ dans la fonction objectif.
Il n'est pas nécessaire de calculer tous les coûts réduits, seul celui associé à $x_2$ importe:
$r_2 = c'_2 - \lambda^T a_2$, où $c'_2$ est le coût modifié dans la fonction objectif.
Nous voulons annuler $r_2$, ce qui donne l'équation
$$
0 = c'_2 - \lambda^T a_2.
$$
On en déduit immédiatement qu'il faudrait avoir $c'_2 = \lambda^T a_2$, soit
$$
c'_2 = - \begin{pmatrix} 7 & 9 & 1 & 1 \end{pmatrix} \begin{pmatrix} 0 & 1 & 0 & 1 \end{pmatrix} = -10.
$$
De là
$$
\Delta c_2 = c'_2 - c_2 = -10 + 9 = -1.
$$

Comme seul $c_2$ change, mais pas les autres éléments du problème, les autres coûts réduits sont inchangés, et la solution précédemment obtenue reste optimale. Néanmoins, nous pouvons à présent faire entrer $x_2$ dans la base optimale sans changer la valeur de la fonction objectif.

Nous devons déterminer la variable entrante. Pour ce faire, calculons tout d'abord
$$
y_2 =
\begin{pmatrix}
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 0 & -1 & -1 \\
0 & 1 & 0 & 0
\end{pmatrix}
\begin{pmatrix}
0 \\ 1 \\ 0 \\ 1
\end{pmatrix}
=
\begin{pmatrix}
0 \\ 1 \\ -1 \\ 1
\end{pmatrix}
$$

Procédons au pivotage
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2 & 0 \\
x_5 & 0 & 0 & 0 & 1 & 1 & 1 \\
x_3 & 1 & 0 & -1 & -1 & 1 & -1 \\
x_6 & 0 & 1 & 0 & 0 & 2 & 1\\
-z & 7 & 9 & 1 & 1 & 49 & 0 \\
\end{matrix}
$$
On voit que $x_5$ sort de la base, pour donner
$$
\begin{matrix}
x_1 & 0 & 0 & 1 & 0 & 2 \\
x_2 & 0 & 0 & 0 & 1 & 1 \\
x_3 & 1 & 0 & -1 & 0 & 2 \\
x_6 & 0 & 1 & 0 & -1 & 1 \\
-z & 7 & 9 & 1 & 1 & 49 \\
\end{matrix}
$$
La nouvelle solution optimale est $(2, 1, 2, 0, 0, 1)$, associée à la même valeur optimale $-49$.

Note: comme la solution avant pivot était optimale, il n'est pas nécessaire de recalculer les nouveaux coûts réduits pour vérifier l'optimalité.

On va néanmoins s'en convaincre:
$$
r_D^T = c_D^T - c_B^TB^{-1}D
= \begin{pmatrix} -6 & -8 \end{pmatrix} + \begin{pmatrix} 7 & 9 & 1 & 1 \end{pmatrix}
\begin{pmatrix} 0 & 1 \\ 1 & 0 \\ 1 & 0 \\ 0 & 1 \end{pmatrix}
= \begin{pmatrix} 4 & 0 \end{pmatrix}
$$

```julia

```


# WT operating under yaw misalignment

In case the wind turbine rotor is not perpendicular to the inflow, its operation and effects on the flow field will be different. In general it is a quite complicated process. In PyWake the effects are divided into four subeffects that are handled invididually:

1. Change of operation due to reduced inflow wind speed
2. Reduced deficit, $deficit_{normal}$, due to reduced inflow wind speed
3. Reduced deficit, $deficit_{downwind}$, due to misalignment between thrust and downwind direction
4. Deflection of wake deficit, $deficit_{deflected}$, due to traversal thrust component reaction

```python
# Install PyWake if needed
try:
    import py_wake
except ModuleNotFoundError:
    !pip install git+https://gitlab.windenergy.dtu.dk/TOPFARM/PyWake.git
```

```python
import numpy as np
import matplotlib.pyplot as plt
from py_wake.examples.data.hornsrev1 import V80
from py_wake.flow_map import HorizontalGrid
from py_wake.tests.test_files import tfp
from py_wake.wind_farm_models import PropagateDownwind
from py_wake.deficit_models import FugaYawDeficit
from py_wake.deflection_models import FugaDeflection
from py_wake.examples.data.hornsrev1 import Hornsrev1Site
from py_wake.flow_map import XYGrid

from ipywidgets import interact
from ipywidgets import IntSlider
wt = V80()
site = Hornsrev1Site()
```

## Change of operation due to reduced inflow wind speed

The inflow perpendicular to the rotor is reduced by $cos\theta$.

This means that the pitch and rotor speed setting of the WT will be different. In PyWake the WT operation (pitch and rotor speed settings) is modeled in terms of the Power and CT curves which must be looked up for the reduced wind speed.

Note, that below rated rotor speed, the CT curve is typically rather flat and reducing the wind speed has therefore limited effect on the CT value in this region. The power, on the other hand, is limited by the maximum power and therefore only affected in the region below rated wind speed (rated wind speed will increase with yaw misalignment)

```python
u = np.arange(3,25,.1)
ax1,ax2 = plt.subplots(1,2, figsize=(15,5))[1]
for yaw in [0,15,30]:
    ax1.plot(u,wt.ct(u*np.cos(np.deg2rad(yaw))), label='%d deg yaw misalignment'%yaw)
    ax2.plot(u,wt.power(u*np.cos(np.deg2rad(yaw))), label='%d deg yaw misalignment'%yaw)
for ax in (ax1, ax2):
    ax.legend()
    ax.set_xlabel('Wind speed [m/s]')
ax1.set_ylabel('CT')
ax2.set_ylabel('Power [W]')
```

Furthermore, misalignemnt between the thrust force (axial induction) and the inflow leads to higher wind speed at the rotor plane which also affects the WT operation. This effect, however, is not considered in PyWake

## Reduced deficit due to reduced inflow wind speed

The wake deficit is caused by a reaction to the thrust force which slows down the inflow.

In non-aligned inflow, the thrust force is reduced as the wind speed component normal to the rotor is reduced

\begin{align}
T & =\frac{1}{2}\rho C_T A (U cos\theta)^2
\end{align}

The engineering models, however, scale their deficit with the inputs, U and CT. The reduction in deficit is therefore modeled by applying a reduced $C_T$ value, $C_{T,yaw}$, that gives the same reduction in thrust, i.e. it satisfies:

\begin{align}
\frac{1}{2}\rho C_{T,yaw} A U^2 & = \frac{1}{2}\rho C_T A (U cos\theta)^2 \\
C_{T,yaw} & = C_T cos^2\theta\\
\end{align}

The `WindTurbines` object is responsible for computing the reduced $C_T$ values and the default model, just multipy the normal ct function value with $cos^2\theta$

## Reduced deficit due to misalignment between thrust and downwind direction

Most engineering models calculate the deficit normal to the rotor plane, while the deficit impacting downstream WT is the deficit in the downstream direction.

In case of yaw misalignment, the deficit is therefore mapped to downstream direction by

$deficit_{downwind} = deficit_{normal} cos \theta$

### FugaYawDeficit

The `FugaYawDeficit` model implicitly models this effect. This model requires two sets of look-up tabels, namely `UL` and `UT`. These tables describes the normalized deficit in the downwind, `U` direction caused by a unit forcing in the longitudinal, `L` and transveral, `T` directions.

```python
fuga = FugaYawDeficit(tfp + 'fuga/2MW/Z0=0.00014617Zi=00399Zeta0=0.00E+0/')
D = 80
R = D/2
x0,x1,y = 1,10,2 #upstream, downstream and crosswind regions to load [D]
xi0 = int(512-x0/(fuga.dx/D))
xi1 = int(512+x1/(fuga.dx/D))+1
yi = int(y/(fuga.dy/D))+1
UL, UT = [fuga.mirror(v,anti_symmetric=a) for v, a in zip(fuga.load_luts()[:2,0,:yi, xi0:xi1], (False,True))]
```

```python
axes = plt.subplots(2,1, figsize=(15,12))[1]
X, Y = np.meshgrid(fuga.x[xi0:xi1], fuga.mirror(fuga.y[:yi], anti_symmetric=True))
wtL = np.array([[0,0],[-D/2,D/2]])
for ax, v, l in zip(axes.flatten(), [UL, UT], ['UL','UT']):
    c = ax.contourf(X, Y, v)
    plt.colorbar(c, ax=ax, label=r'Downwind deficit $\left[\frac{1}{U CT}\right]$')
    s = (slice(None,None,2),slice(None,None,5))
    ax.quiver(X[s], Y[s], v[s]*(l[0]=='U'), v[s]*(l[0]=='V'))
    ax.plot([0,0],[-D/2,D/2],'r')
    if l[1]=='L':
        ax.arrow(-10,0,-20,0,color='r', width=10,head_length=30)
        ax.arrow(60,0,-20,0,color='r', width=10,head_length=30)
    else:
        ax.arrow(-30,-25,0,20,color='r', width=10,head_length=30)
        ax.arrow(30,-25,0,20,color='r', width=10,head_length=30)

    ax.axis('equal')
    ax.set_xlim([-D,10*D])
    ax.set_ylim([-D,D])
    ax.set_title(l)
```

As Fuga is a linear model, the deficit in the downstream direction can simply be calculated by the linear superposition of `UL` and `UT`:

$deficit = (UL \cdot cos\theta + UT \cdot sin\theta) \cdot U \cdot CT$

You can move the slider below to see the deficit fields for different yaw-misalignment angles

```python
def plot(yaw):
    plt.figure(figsize=(18,5))
    theta = np.deg2rad(yaw)
    co, si = np.cos(theta), np.sin(theta)
    deficit = co**2*(UL*co + UT*si)
    c = plt.contourf(X, Y, deficit, levels=np.arange(-.4,.1,.05))
    plt.colorbar(c, label=r'Downwind deficit $\left[\frac{1}{U CT}\right]$')
    s = (slice(None,None,2),slice(None,None,5))
    plt.quiver(X[s], Y[s], deficit[s], deficit[s]*0, width=.002, scale=2)
    plt.plot([-si*R, si*R],[-co*R,co*R],'w',label='Rotor',lw=3)
    plt.arrow(-0,0,-100*co,0,color='r', width=5,head_length=10,length_includes_head=True)
    plt.arrow(-0,-0,0,100*si,color='r', width=5,head_length=10,length_includes_head=True, zorder=32)
    plt.axis('equal')
    plt.xlim([-D,10*D])
    plt.ylim([-D,D])
    ax.set_title(l)
_ = interact(plot, yaw=IntSlider(min=-90, max=90, step=1, value=20, continuous_update=False))
```

    interactive(children=(IntSlider(value=20, continuous_update=False, description='yaw', max=90, min=-90), Output…

## Wake deflection due to traversal thrust component reaction

Wake deflection is modeled by a `DeflectionModel` which modifies the downwind, horizontal crosswind and vertical distance between wind turbines.

### FugaDeflection

FugaDeflection is capable of modeling the wake deflection if the `VL` and `VT` is present. These tables describes the normalized deficit in the crosswind, `V` direction caused by a unit forcing in the longitudinal, `L` and transveral, `T` directions.

```python
fugaDeflection = FugaDeflection(tfp + 'fuga/2MW/Z0=0.00014617Zi=00399Zeta0=0.00E+0/')
D = 80
R = D/2
x0,x1,y = 1,10,2 #upstream, downstream and crosswind regions to load [D]
xi0 = int(512-x0/(fugaDeflection.dx/D))
xi1 = int(512+x1/(fugaDeflection.dx/D))+1
yi = int(y/(fugaDeflection.dy/D))+1

# VL is antisymmetric, VT is symmetric
VL, VT = [fugaDeflection.mirror(v,anti_symmetric=a) for v, a in zip(fugaDeflection.load_luts()[2:4,0,:yi, xi0:xi1], (True, False))]
```

```python
axes = plt.subplots(2,1, figsize=(15,12))[1]
X, Y = np.meshgrid(fugaDeflection.x[xi0:xi1], fugaDeflection.mirror(fugaDeflection.y[:yi], anti_symmetric=True))
for ax, v, l in zip(axes.flatten(), [VL, -VT], ['VL','VT']):
    c = ax.contourf(X, Y, v)
    plt.colorbar(c, ax=ax, label=r'$WS_{Crosswind} \left[\frac{1}{U CT}\right]$')
    s = (slice(None,None,10),slice(None,None,1))
    ax.quiver(X[s], Y[s], 0, v[s], scale=(.3,3)[l[1]=='T'])
    ax.plot([0,0],[-D/2,D/2],'r')
    if l[1]=='L':
        ax.arrow(-10,0,-20,0,color='r', width=10,head_length=30)
        ax.arrow(60,0,-20,0,color='r', width=10,head_length=30)
    else:
        ax.arrow(-30,-25,0,20,color='r', width=10,head_length=30)
        ax.arrow(30,-25,0,20,color='r', width=10,head_length=30)

    ax.axis('equal')
    ax.set_xlim([-D,10*D])
    ax.set_ylim([-D,D])
    ax.set_title(l)
```

As Fuga is a linear model, the effects in the crosswind direction can simply be calculated by the linear superposition of `VL` and `VT`:

$WS_{crosswind} = cos(VL \cdot cos\theta + VT \cdot sin\theta) \cdot U \cdot CT$

You can move the slider below to see the crosswind wind speed field for different yaw-misalignment angles. Futhermore, the integrated deflection is shown with solid lines

```python
def plot(yaw):
    plt.figure(figsize=(18,5))
    theta = np.deg2rad(yaw)
    co, si = np.cos(theta), np.sin(theta)
    deficit = co**2*(VL*co + -VT*si)
    X, Y = np.meshgrid(fugaDeflection.x[xi0:xi1], fugaDeflection.mirror(fugaDeflection.y[:yi], anti_symmetric=True))
    c = plt.contourf(X, Y, deficit, levels=np.arange(-.09,.1,.01))
    plt.colorbar(c, label=r'$WS_{crosswind} \left[\frac{1}{U CT}\right]$')
    s = (slice(None,None,6),slice(None,None,1))
    plt.quiver(X[s], Y[s], deficit[s]*0, -deficit[s], width=.002, scale=2)

    # plot deflection lines
    fL, fT = fugaDeflection.fLT.V.T[:,256-yi:255+yi, xi0:xi1]
    lambda1 = co**2 * (fL * co + fT * si)
    Yp = Y + lambda1
    plt.plot(X[yi, :], Y[yi, :], 'grey', lw=3)
    for x, y, yp in zip(X[1::4], Y[1::4], Yp[1::4]):
        plt.plot(x, y, 'grey', lw=1, zorder=-32)
        plt.plot(x, yp, 'k', lw=1)
    plt.plot(X[yi, :], Yp[yi, :], 'k', lw=3)

    plt.plot([si*R, -si*R],[-co*R,co*R],'w',label='Rotor',lw=3)
    plt.arrow(-0,0,-50*co,0,color='r', width=5,head_length=10,length_includes_head=True, zorder=32)
    plt.arrow(-0,-0,0,-50*si,color='r', width=5,head_length=10,length_includes_head=True, zorder=32)
    plt.axis('equal')
    plt.xlim([-D,10*D])
    plt.ylim([-D,D])
    ax.set_title(l)
_ = interact(plot, yaw=IntSlider(min=-90, max=90, step=1, value=20, continuous_update=False))
```

    interactive(children=(IntSlider(value=20, continuous_update=False, description='yaw', max=90, min=-90), Output…

Finally, all effects are put together. You can move the slider below to see the effects on the deficit behind a WT

```python
path = tfp + 'fuga/2MW/Z0=0.00014617Zi=00399Zeta0=0.00E+0/'
wfm = PropagateDownwind(site,wt,FugaYawDeficit(path), deflectionModel=FugaDeflection(path))
def plot(yaw):
    plt.figure(figsize=(18,5))
    fm = wfm([0],[0],ws=10,wd=270, yaw_ilk=[[[yaw]]]).flow_map(XYGrid(x=np.arange(-100,800,10), y=np.arange(-150,150,10)))
    fm.plot_wake_map()
    max_deficit_line = fm.min_WS_eff(x=np.arange(10,800,10))
    max_deficit_line.plot(color='k', label='Max deficit line')
    plt.axhline(0, label='Center line')
    plt.legend()

_ = interact(plot, yaw=IntSlider(min=-90, max=90, step=1, value=20, continuous_update=False))
```

    interactive(children=(IntSlider(value=20, continuous_update=False, description='yaw', max=90, min=-90), Output…


### Electromechanical differential equations
\begin{eqnarray}
f_1 &=& \dot \delta = \Omega_b \left( \omega - \omega_s  \right) \\
f_2 &=& \dot \omega = \frac{1}{2H} \left( p_m - p_e - D   \left( \omega - \omega_s \right) \right)
\end{eqnarray}

### Electric rotor differential equations
\begin{eqnarray}
f_3 &=& \dot e_q' =  \frac{1}{T'_{d0}} \left( -e'_q - \left(X_d - X'_d \right) i_d + v_f^\star \right) \\
f_4 &=& \dot e'_d = \frac{1}{T'_{q0}} \left( -e'_d - \left(X_q - X'_q \right) i_q \right)
\end{eqnarray}

### AVR/Exitation dynamic
\begin{eqnarray}
f_5 &=& \dot v_c =   (v_t - v_c)/T_e
\end{eqnarray}

### Park transform
\begin{eqnarray}
g_1 &=&-v_d + v_t \sin\left(\delta - \theta_t\right) \\
g_2 &=&-v_q + v_t \cos\left(\delta - \theta_t\right)
\end{eqnarray}

### Stator equations
\begin{eqnarray}
g_3 &=& v_q + R_a i_q + X'_d i_d - e'_q\\
g_4 &=& v_d + R_a i_d - X'_q i_q - e'_d\\
\end{eqnarray}

### Powers
\begin{eqnarray}
g_5 &=& -p_e + \left( v_q + R_a i_q  \right) i_q + \left( v_d + R_a i_d  \right) i_d \\
g_6 &=&  i_d v_d + i_q v_q - p_t \\
g_7 &=&  i_d v_q - i_q v_d - q_t
\end{eqnarray}

### Network equations
\begin{eqnarray}
g_8 &=&   p_t - \left(v_t V_0 \sin\left(\theta_t - \theta_0\right)\right)/X_l\\
g_9 &=&   q_t + \left(v_t V_0 \cos\left(\theta_t - \theta_0\right)\right)/X_l - v_t^2/X_l
\end{eqnarray}

### AVR algebraic equations
\begin{eqnarray}
g_{10} &=&   K_a (v^\star - v_c + v_s) - v_f
\end{eqnarray}

```python
import numpy as np
import sympy as sym
import numba
import pydae.build as db
```

## System definition

```python
params_dict = {'X_d':1.81,'X1d':0.3, 'T1d0':8.0,  # synnchronous machine d-axis parameters
               'X_q':1.76,'X1q':0.65,'T1q0':1.0,  # synnchronous machine q-axis parameters
               'R_a':0.003,'X_l': 0.1,
               'H':3.5,'D':0.0,
               'Omega_b':2*np.pi*50,'omega_s':1.0,
               'v_0':1.0,'theta_0':0.0,
               'K_a':100, 'T_e':0.1, 'v_pss':0.0}

u_ini_dict = {'p_t':0.8,'v_t':1.0}  # for the initialization problem
u_run_dict = {'p_m':0.8,'v_ref':1.0}  # for the running problem (here initialization and running problem are the same)

x_list = ['delta','omega','e1q','e1d','v_c']    # dynamic states
y_ini_list = ['v_d','v_q','i_d','i_q','p_e','p_m','q_t','v_ref','theta_t','v_f']
y_run_list = ['v_d','v_q','i_d','i_q','p_e','p_t','q_t','v_t','theta_t','v_f']

sys_vars = {'params':params_dict,
            'u_list':u_run_dict,
            'x_list':x_list,
            'y_list':y_run_list}

exec(db.sym_gen_str())  # exec to generate the required symbolic varables and constants
```

```python
ddelta = Omega_b*(omega - omega_s)
domega = 1/(2*H)*(p_m - p_e - D*(omega - omega_s))
de1q = 1/T1d0*(-e1q - (X_d - X1d)*i_d + v_f)
de1d = 1/T1q0*(-e1d + (X_q - X1q)*i_q)
dv_c =   (v_t - v_c)/T_e

g_1 = -v_d + v_t*sin(delta - theta_t)
g_2 = -v_q + v_t*cos(delta - theta_t)
g_3 = v_q + R_a*i_q + X1d*i_d - e1q
g_4 = v_d + R_a*i_d - X1q*i_q - e1d
g_5 = -p_e + i_d*(v_d + R_a*i_d) + i_q*(v_q + R_a*i_q)
g_6 = i_d*v_d + i_q*v_q - p_t
g_7 = i_d*v_q - i_q*v_d - q_t
g_8 = p_t - (v_t*v_0*sin(theta_t - theta_0))/X_l
g_9 = q_t + (v_t*v_0*cos(theta_t - theta_0))/X_l - v_t**2/X_l
g_10 = K_a*(v_ref - v_c + v_pss) - v_f

h_1 = p_m

sys = {'name':'smib_milano_ex8p1_4ord_avr',
       'params_dict':params_dict,
       'f_list':[ddelta,domega,de1q,de1d,dv_c],
       'g_list':[g_1,g_2,g_3,g_4,g_5,g_6,g_7,g_8,g_9,g_10],
       'x_list':x_list,
       'y_ini_list':y_ini_list,
       'y_run_list':y_run_list,
       'u_run_dict':u_run_dict,
       'u_ini_dict':u_ini_dict,
       'h_dict':{'p_m':p_m}}

sys = db.system(sys)
db.sys2num(sys)

```

    jacobians respect u = 0

```python

```

```python

```


# Simulating Molecules using VQE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### Example with a Single Qubit Variational Form

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

We first create the random probability vector in python:

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

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

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

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

```python
from qiskit import Aer, execute
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
    result = execute(qc, backend, shots=NUM_SHOTS).result()
    # Obtain the counts for each measured state, and convert those counts into a probability vector
    output_distr = get_probability_distribution(result.get_counts(qc))
    # Calculate the cost as the distance between the output distribution and the target distribution
    cost = sum([np.abs(output_distr[i] - target_distr[i]) for i in range(2)])
    return cost
```

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

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

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

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

# Obtain the output distribution using the final parameters
qc = get_var_form(ret[0])
counts = execute(qc, backend, shots=NUM_SHOTS).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])

```

    /usr/local/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:6: DeprecationWarning: The QuantumCircuit.u3 method is deprecated as of 0.16.0. It will be removed no earlier than 3 months after the release date. You should use QuantumCircuit.u instead, which acts identically. Alternatively, you can decompose u3 in terms of QuantumCircuit.p and QuantumCircuit.sx: u3(ϴ,φ,λ) = p(φ+π) sx p(ϴ+π) sx p(λ) (2 pulses on hardware).

    /usr/local/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:6: DeprecationWarning: The QuantumCircuit.u3 method is deprecated as of 0.16.0. It will be removed no earlier than 3 months after the release date. You should use QuantumCircuit.u instead, which acts identically. Alternatively, you can decompose u3 in terms of QuantumCircuit.p and QuantumCircuit.sx: u3(ϴ,φ,λ) = p(φ+π) sx p(ϴ+π) sx p(λ) (2 pulses on hardware).

    Target Distribution: [0.51357006 0.48642994]
    Obtained Distribution: [0.5191, 0.4809]
    Output Error (Manhattan Distance): 0.007340118738839108
    Parameters Found: [1.55025378 1.03771732 0.51467324]

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

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

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

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

    =============Linear Entanglement:=============

    =============Full Entanglement:=============

Assume the depth setting is set to $d$. Then, RyRz has $n\times (d+1)\times 2$ parameters, Ry with linear entanglement has $2n\times(d + \frac{1}{2})$ parameters, and Ry with full entanglement has $d\times n\times \frac{(n + 1)}{2} + n$ parameters.

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

The following libraries must first be imported.

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

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

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

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

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

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

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

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

    Interatomic Distance: 0.5 VQE Result: -7.039673441537582 Exact Energy: [-7.03973252]
    Interatomic Distance: 0.6 VQE Result: -7.313308879733899 Exact Energy: [-7.31334583]
    Interatomic Distance: 0.7 VQE Result: -7.500887034646628 Exact Energy: [-7.50092209]
    Interatomic Distance: 0.8 VQE Result: -7.63094541057222 Exact Energy: [-7.63097825]
    Interatomic Distance: 0.9 VQE Result: -7.720781099437151 Exact Energy: [-7.72081241]
    Interatomic Distance: 1.0 VQE Result: -7.782211599012616 Exact Energy: [-7.7822424]
    Interatomic Distance: 1.1 VQE Result: -7.82356808182339 Exact Energy: [-7.82359928]
    Interatomic Distance: 1.2 VQE Result: -7.850666132010801 Exact Energy: [-7.85069838]
    Interatomic Distance: 1.3 VQE Result: -7.867529587097838 Exact Energy: [-7.86756329]
    Interatomic Distance: 1.4 VQE Result: -7.876966253743829 Exact Energy: [-7.87700149]
    Interatomic Distance: 1.5 VQE Result: -7.880979507189623 Exact Energy: [-7.88101572]
    Interatomic Distance: 1.6 VQE Result: -7.881036616988421 Exact Energy: [-7.88107204]
    Interatomic Distance: 1.7 VQE Result: -7.87823697183363 Exact Energy: [-7.87826817]
    Interatomic Distance: 1.8 VQE Result: -7.873345219347113 Exact Energy: [-7.87344029]
    Interatomic Distance: 1.9 VQE Result: -7.867137507633879 Exact Energy: [-7.86723396]
    Interatomic Distance: 2.0 VQE Result: -7.8600683797083235 Exact Energy: [-7.86015321]
    Interatomic Distance: 2.1 VQE Result: -7.852535225154712 Exact Energy: [-7.85259583]
    Interatomic Distance: 2.2 VQE Result: -7.8448393837634836 Exact Energy: [-7.84487909]
    Interatomic Distance: 2.3 VQE Result: -7.837212700738681 Exact Energy: [-7.83725797]
    Interatomic Distance: 2.4 VQE Result: -7.829853243487846 Exact Energy: [-7.829937]
    Interatomic Distance: 2.5 VQE Result: -7.822930298730153 Exact Energy: [-7.82307664]
    Interatomic Distance: 2.6 VQE Result: -7.816588435964219 Exact Energy: [-7.81679515]
    Interatomic Distance: 2.7 VQE Result: -7.810934374084946 Exact Energy: [-7.81116828]
    Interatomic Distance: 2.8 VQE Result: -7.806014817320134 Exact Energy: [-7.80622956]
    Interatomic Distance: 2.9 VQE Result: -7.801736628994531 Exact Energy: [-7.8019736]
    Interatomic Distance: 3.0 VQE Result: -7.798083540282849 Exact Energy: [-7.79836343]
    Interatomic Distance: 3.1 VQE Result: -7.795060179153879 Exact Energy: [-7.79534045]
    Interatomic Distance: 3.2 VQE Result: -7.792367416894701 Exact Energy: [-7.79283481]
    Interatomic Distance: 3.3 VQE Result: -7.7896861836248705 Exact Energy: [-7.79077401]
    Interatomic Distance: 3.4 VQE Result: -7.7873754424150725 Exact Energy: [-7.7890889]
    Interatomic Distance: 3.5 VQE Result: -7.78562745363149 Exact Energy: [-7.78771697]
    Interatomic Distance: 3.6 VQE Result: -7.784375116829985 Exact Energy: [-7.78660376]
    Interatomic Distance: 3.7 VQE Result: -7.7834959894908415 Exact Energy: [-7.78570291]
    Interatomic Distance: 3.8 VQE Result: -7.782885673586908 Exact Energy: [-7.78497559]
    Interatomic Distance: 3.9 VQE Result: -7.782466682215736 Exact Energy: [-7.78438961]
    All energies have been calculated

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

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

### Running VQE on a Noisy Simulator

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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