# Select suitable MDL test

# Purpose
Find suitable roll decay tests from MDL, to reproduce in Motions later on.

# Methodology
Look at the results from the KLVCC2 ship.
* Load model tests from MDL
* Determine damping

# WIP - improvements
(WORK IN PROGRESS)
Use this section only if the notebook is not final.

Notable TODOs:
* todo 1
* todo 2
* todo 3

## Results
Describe and comment the most important results.

# Suggested next steps
State suggested next steps, based on results obtained in this notebook.

# Setup

```python
# %load imports.py
"""
These is the standard setup for the notebooks.
"""

%matplotlib inline
%load_ext autoreload
%autoreload 2

from jupyterthemes import jtplot
jtplot.style(theme='onedork', context='notebook', ticks=True, grid=False)

import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
from bqplot import pyplot as plt2
import ipywidgets as widgets
#plt.style.use('paper')

#import data
import copy

from sklearn.pipeline import Pipeline
from rolldecayestimators.transformers import CutTransformer, LowpassFilterDerivatorTransformer, ScaleFactorTransformer, OffsetTransformer
from rolldecayestimators.direct_estimator_cubic import EstimatorQuadraticB, EstimatorCubic
from rolldecayestimators.ikeda_estimator import IkedaQuadraticEstimator
import rolldecayestimators.equations as equations
import rolldecayestimators.lambdas as lambdas
from rolldecayestimators.substitute_dynamic_symbols import lambdify
import rolldecayestimators.symbols as symbols
import sympy as sp

from sklearn.metrics import r2_score
from src.data import database
from mdldb import tables
from mdldb.run import Run

```

    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 461 ('figure.figsize   : 5, 3   ## figure size in inches')
    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 462 ('figure.dpi       : 100        ## figure dots per inch')

```python
from mdldb.hdf5_file import Hdf5File
from rolldecayestimators.direct_estimator_cubic import EstimatorCubic,EstimatorQuadraticB
```

```python
db = database.get_db()
```

```python
sql = """
SELECT * from run
WHERE model_number='M5057-01-A' and test_type='roll decay' and project_number=40178362;
"""
df_rolldecays = pd.read_sql(sql=sql, con=db.engine, index_col='id')

df_rolldecays['ship_speed'].fillna(0, inplace=True)

```

```python
df_rolldecays
```

      project_number
      series_number
      run_number
      test_number
      model_number
      ship_name
      loading_condition_id
      ascii_name
      ship_speed
      comment
      file_path_ascii
      file_path_ascii_temp
      file_path_log
      file_path_hdf5
      date
      test_type
      facility
      angle1
      angle2
      Körfallstyp

      id

      21337
      40178362
      1
      94
      1
      M5057-01-A
      M5057-01-A
      166
      94.0
      0.0
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-03
      roll decay
      MDL
      None
      None
      None

      21338
      40178362
      1
      95
      1
      M5057-01-A
      M5057-01-A
      166
      95.0
      0.0
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-03
      roll decay
      MDL
      None
      None
      None

      21339
      40178362
      1
      96
      1
      M5057-01-A
      M5057-01-A
      166
      96.0
      0.0
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-11-28
      roll decay
      MDL
      None
      None
      None

      21340
      40178362
      1
      97
      1
      M5057-01-A
      M5057-01-A
      166
      97.0
      15.5
      Roll decay, 15.5 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-04
      roll decay
      MDL
      None
      None
      None

```python
df_rolldecays[['date','ship_speed']]
```

      date
      ship_speed

      id

      21337
      2018-04-03
      0.0

      21338
      2018-04-03
      0.0

      21339
      2018-11-28
      0.0

      21340
      2018-04-04
      15.5

```python
fig,axes=plt.subplots(nrows=len(df_rolldecays))

for (id, row),ax in zip(df_rolldecays.iterrows(),axes):

    run = db.session.query(Run).get(int(id))
    run = database.load_run(run, save_as_example=False, prefer_hdf5=True)

    run.df.plot(y='phi', ax=ax)
    ax.set_ylabel(f"roll [{run.units['phi']}]")
    ax.grid(True)
    ax.set_ylim([-0.2,0.2])

plt.tight_layout()
```

```python
fig,axes=plt.subplots(ncols=len(df_rolldecays))

for (id, row),ax in zip(df_rolldecays.iterrows(),axes):

    run = db.session.query(Run).get(int(id))
    run = database.load_run(run, save_as_example=False, prefer_hdf5=True)

    run.track_plot(ax)

plt.tight_layout()
```

## Analyze the damping

```python
#run = db.session.query(Run).get(21338)
#run = db.session.query(Run).get(21339)
run = db.session.query(Run).get(21337)

#run = db.session.query(Run).get(21340)

run = database.load_run(run, save_as_example=False, prefer_hdf5=True)
```

    c:\dev\evaluation\signal_lab\mdl_to_evaluation.py:106: UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access
      df_.units = units

```python
lowpass_filter = LowpassFilterDerivatorTransformer(cutoff=1, minimum_score=0.999)
cutter = CutTransformer(phi_max=np.deg2rad(15), phi_min=np.deg2rad(1), phi1d_start_tolerance=0.015)
offset_transformer = OffsetTransformer()
```

```python
#steps = [
##    ('filter',lowpass_filter),
#    ('cutter', cutter),
##    ('offset_transformer',offset_transformer),
#]
#
#df = run.df.copy()
#df['phi1d']=np.gradient(df['phi'],df.index)
#df['phi2d']=np.gradient(df['phi1d'],df.index)
#
#
#preprosessor = Pipeline(steps=steps)
#preprosessor.fit(X=df[['phi']])
#X = preprosessor.transform(df[['phi','phi1d','phi2d']])
#X-=X['phi'].mean()

steps = [
    ('filter',lowpass_filter),
    ('cutter', cutter),
#    ('offset_transformer',offset_transformer),
]

df = run.df.copy()

preprosessor = Pipeline(steps=steps)
preprosessor.fit(X=df[['phi']])
X = preprosessor.transform(df[['phi']])
X-=X['phi'].mean()

```

```python
fig,ax=plt.subplots()
run.df.plot(y='phi', ax=ax)
X.plot(y='phi', ax=ax)
```

```python
estimator_quadratic_b = EstimatorQuadraticB(fit_method='derivation')
```

```python
estimator_quadratic_b.fit(X=X)
estimator_quadratic_b.score(X=X)
```

    0.9964378071236195

```python
estimator_cubic = EstimatorCubic(fit_method='derivation', maxfev=1000, ftol=1e-15)
estimator_cubic.fit(X=X)
estimator_cubic.score(X=X)
```

    0.9991335936625841

```python
estimator_quadratic_b
```

    EstimatorQuadraticB(B_1A:0.016, B_2A:0.060, C_1A:6.108,)

```python
estimator_cubic
```

    EstimatorCubic(B_1A:0.026, B_2A:-0.033, B_3A:0.190, C_1A:6.078, C_3A:2.383, C_5A:33.349,)

```python
fig,ax=plt.subplots()
estimator_quadratic_b.plot_fit(ax=ax)
estimator_cubic.plot_fit(ax=ax, model_test=False)
```

```python
estimator_quadratic_b.calculate_amplitudes_and_damping()
estimator_cubic.calculate_amplitudes_and_damping()

fig,ax=plt.subplots()
X['phi_deg']=np.rad2deg(X['phi'])
X.plot(y='phi_deg', ax=ax)
estimator_quadratic_b.plot_amplitude(ax=ax)
estimator_cubic.plot_amplitude(ax=ax, include_model_test=False)

fig,ax=plt.subplots()
estimator_quadratic_b.plot_damping(ax=ax)
estimator_cubic.plot_damping(ax=ax, include_model_test=False)

fig,ax=plt.subplots()
estimator_quadratic_b.plot_error(ax=ax)
estimator_cubic.plot_error(ax=ax)
```

```python
fig,ax=plt.subplots()
X_amplitudes = estimator_quadratic_b.X_amplitudes.copy()
X_amplitudes['omega0_model'] = estimator_quadratic_b.omega0
X_amplitudes['omega0_diff'] = X_amplitudes['omega0_model']/X_amplitudes['omega0']
X_amplitudes.plot(y=['omega0_diff'], ax=ax)
```

## Investigating the saw-signal...

```python
from rolldecayestimators import measure

#X_pred = estimator_quadratic_b.predict(X=X)
t = np.linspace(X.index[0],X.index[-1],1*len(X))
X_pred =estimator_quadratic_b.simulate(t=t, phi0=X.iloc[0]['phi'], phi1d0=X.iloc[0]['phi1d'])

X_amplitudes_pred = measure.calculate_amplitudes_and_damping(X=X_pred)
X_amplitudes_ = measure.calculate_amplitudes_and_damping(X=X)

fig,ax=plt.subplots()
X_amplitudes_pred.plot(y='B_n',style='-', ax=ax)
X_amplitudes_.plot(y='B_n',style='.', ax=ax)
#ax.set_xlim([50,55])

fig,ax=plt.subplots()
X_amplitudes_pred.plot(x='phi',y='B_n',style='-', ax=ax)
```

```python
key = 'phi1d'
phi1d = np.array(X[key])

index = np.arange(0, len(X.index))
index_later = np.roll(index, shift=-1)
index_later[-1] = index[-1]
mask = (
        ((phi1d[index] > 0) &
         (phi1d[index_later] < 0)) |
        ((phi1d[index] < 0) &
         (phi1d[index_later] > 0))
)

index_first = index[mask]
index_second = index[mask]+1

# y = m + k*x
# k = (y2-y1)/(x2-x1)
# m = y1 - k*x1
# y = 0 --> x = -m/k
X_1 = X.iloc[index_first].copy()
X_2 = X.iloc[index_second].copy()
rows,cols = X_1.shape

x1 = np.array(X_1.index)
x2 = np.array(X_2.index)
y1 = np.array(X_1['phi1d'])
y2 = np.array(X_2['phi1d'])
k = (y2-y1)/(x2-x1)
m = y1 - k*x1
x = -m/k

X_1 = np.array(X_1)
X_2 = np.array(X_2)

factor = (x-x1)/(x2-x1)
factor = np.tile(factor,[cols,1]).T
X_zero = X_1 + (X_2-X_1)*factor

X_zerocrossings = pd.DataFrame(data=X_zero, columns=X.columns, index=x)
```

```python
fig,ax=plt.subplots()
X.plot(y='phi1d', ax=ax, style='o-')
X.iloc[index_first].plot(y='phi1d', style='g.', ax=ax)
X.iloc[index_second].plot(y='phi1d', style='r.', ax=ax)

X_zerocrossings.plot(y='phi1d', style='m.',ax=ax)
ax.set_xlim(10.58,10.62)
ax.set_ylim(-0.05,0.05)
ax.grid(True)
```

```python
X_zerocrossings.head()
```

      phi
      phi_filtered
      phi1d
      phi2d
      phi_deg

      27.447465
      -0.156722
      -0.156557
      -5.741935e-16
      0.967098
      -8.979520

      28.713872
      0.152637
      0.152594
      -1.821460e-17
      -0.937192
      8.745480

      29.981708
      -0.148606
      -0.148794
      -2.499086e-15
      0.914050
      -8.514520

      31.250208
      0.145394
      0.145446
      4.659359e-17
      -0.890826
      8.330480

      32.518927
      -0.141878
      -0.141948
      -2.962040e-15
      0.871692
      -8.128986

```python
fig,ax=plt.subplots()
X_zerocrossings = measure.get_peaks(X=X_pred)
X_pred.plot(y='phi1d', ax=ax)
X_zerocrossings.plot(y='phi1d', style='r.',ax=ax)
ax.set_xlim(10.5,11)
ax.grid(True)
```

```python
fig,ax=plt.subplots()
X_pred.plot(y='phi1d', ax=ax)
X_zerocrossings.plot(y='phi1d', style='r.',ax=ax)
ax.set_xlim(10.5,13)
ax.grid(True)
```

```python
X_zerocrossings.head()
```

      phi
      phi1d
      phi2d

      27.453592
      -0.157486
      2.406495e-15
      0.961943

      28.724366
      0.154017
      1.124968e-15
      -0.940756

      29.995219
      -0.150624
      -8.847090e-17
      0.920032

      31.265974
      0.147301
      3.870168e-15
      -0.899732

      32.536743
      -0.144133
      -2.555248e-15
      0.880384

```python
fig,ax=plt.subplots()
ax.plot(np.diff(X_zerocrossings.index),'.-')
```

```python
fig,ax=plt.subplots()
ax.plot(np.diff(run.df.index))
```

```python
size = 100
np.random.seed(0)
x_data = np.arange(size)
y_data = np.cumsum(np.random.randn(size)  * 100.0)

plt2.figure(title='My First Plot')
plt2.plot(x_data, y_data)
plt2.show()
```

    VBox(children=(Figure(axes=[Axis(scale=LinearScale()), Axis(orientation='vertical', scale=LinearScale())], fig…

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```


# Coase, Property rights and the 'Coase Theorem'

Coase, R. H. 1960. “The Problem of Social Cost.” *The Journal of Law and Economics* 3:1–44.

Coase, Ronald H. 1937. “The Nature of the Firm.” *Economica* 4 (16):386–405.

**Note:** Most of the code to generate visuals and results below is in a [code section](#codesection) at the end of this notebook.  To run interactive widgets or modify content, navigate to the code section for instructions.

**Slideshow mode**: this notebook can be viewed as a slideshow by pressing Alt-R if run on a server.

## Coase (1960)

### A rancher and wheat farmer.

Both are utilizing adjacent plots of land. There is no fence separating the lands.

**The Wheat Farmer**

The wheat farm choose a production method the delivers a maximum profit of $\Pi_W =8$.
- to keep this simple suppose this is the farmer's only production choice.

**The Rancher**

Chooses herd size $x$ to maximize profits:

$$\Pi_C(x) = P \cdot F(x) - c \cdot x$$

where $P$ is cattle price and $c$ is the cost of feeding each animal.

To simplify we allow decimal levels (but conclusions would hardly change if restrictedto integers).

First-order necessary condition for herd size $x^*$ to max profits:

$$P \cdot F'(x^*) = c$$

**Example:* $P_c=4$, $F(x) = \sqrt{x}$ and $c=1$

The FOC are $\frac{4}{2\sqrt x*} = 1$

And the rancher's privately optimal herd size:

$$x^* = 4$$

**The external cost**

No effective barrier exists between the fields so cattle sometimes strays into the wheat farmer's fields, trampling crops and reducing wheat farmer's profits.

Specifically, if rancher keeps a herd size $x$ net profits in wheat are reduced to :

$$\Pi_W(x) = \Pi_W - d \cdot x^2$$

**The external cost**

Suppose $d=\frac{1}{2}$

At rancher's private optimum herd size of $x*=4$ the farmer's profit is reduced from 8 to zero:

$$\begin{align}
\Pi_W(x) &= \Pi_W - d \cdot x^2  \\
 & = 8 - \frac{1}{2} \cdot 4^2 = 0
 \end{align}$$

```python
Pc = 4
Pw = 8
c = 1/2
d = 1/2

CE, TE = copt(),topt()
CE, TE
```

    (4.0, 2.0)

If the rancher chose his private optimum he'd earn \$8 but drive the farmer's earnings to zero.

```python
coaseplot1()
```

Private and social marginal benefits and costs can be plotted to see deadweight loss (DWL) differently:

```python
coaseplot2()
```

## The assignment of property rights (liability)

**Scenario 1:** Farmer has right to enjoin cattle herding (prohibit via an injunction).

Rancher now earns \$0. Farmer \$8.

This is not efficient either.

If rancher herded just 2 would earn \$6. Could offer \$2 compensation to the wheat farmer and capture \$6-2 =$4.

...or they could bargain to divide the gains to trade of \$4 in other ways.

**Scenario 2:**  Rancher has right to graze with impunity.

Farmer earns \$0 if rancher herds private optimal of 4 cattle.  Farmer could offer to pay \$2 to have rancher reduce herd to 2 which would leave rancher as well off and take the farmer from \$0 to \$4 (= 6-2).

...or they could bargain to divide the gains to trade of \$4 in other ways.

### With zero transactions costs

- **The initial assignment of property rights does not matter:** The parties bargain to an efficient outcome either way.

- However, like any scarce resource, legal rights are valuable, so **the initial allocation will affect the distribution of benefits and incomes between parties*

- **The emergence of property rights**: Even if there is no initial assignment of property rights, with zero transactions costs it should be in the interests of the parties to negotiate to an efficient outcome.

## With positive transactions costs

- The initial distribution of property rights typically will matter.

- It's not so clear from this example but suppose that we had a situation with one wheat farmer and many ranchers.  It might be difficult to get the ranchers

## Coase and the development of a land market

Suppose there is an open field.  In the absence of a land market whoever gets to the land first (possibly the more powerful in the the village) will prepare/clear land until the marginal value product of the last unit of land is equal to the clearing cost.  We contrast two situations:

(1) Open frontier: where land is still abundant

(2) Land Scarcity.

There will be a misallocation in (2) shown by DWL in the diagram... but also an incentive for the parties to bargain to a more efficient outcome.  A well functionining land market would also deliver that outcome.

Abundant land environment

$\bar T$ units of land and $N$=2 households.

Land clearing cost $c$.  Frontier land not yet exhausted.

Maximize profits at $P \cdot F_T(T) = c$

```python
landmarket(P=5, cl = 3, title = 'Open Frontier')
```

```python
landmarket(P=8, cl = 3, title = 'Land Scarcity')
```

### The 'Coase Theorem'

Costless bargaining between the parties will lead to an efficient outcome regardless of which party is awarded the rights?

### Coase Theorem: True, False or Tautology?

Tautology?:  "if there are no costs to fixing things, then things will be fixed."

Like the First Welfare Theorem (complete competitive markets will lead to efficient allocations, regardless of initial allocation of property rights).  The Coase Theorem makes legal entitlements tradable

## More useful reading of Coase result

When transactions costs the initial allocation of property rights will matter for the efficiency of the outcome.

## Further notes on Coase (incomplete)

Coase can be seen as generalizing the neo-classical propositions about the exchange of goods (i.e. 1st Welfare Theorem) to the exchange of legal entitlements (Cooter, 1990). "The initial allocation of legal enttilements dos not matter foram an efficiency perspetive so long as they can be freely exchanged...'

Suggests insuring efficiency of law is matter or removing impediments to free exchange of legal entitlements...... define entitlements clearly and enforce private contracts for their exchagne...

But conditions needed for efficient resource allocation...

Nice discussion [here](https://afinetheorem.wordpress.com/2013/09/03/on-coases-two-famous-theorems/):

Tautology: "Costless bargaining is efficient tautologically; if I assume people can agree on socially efficient bargains, then of course they will"
"The fact that side payments can be agreed upon is true even when there are no property rights at all."
" In the absence of property rights, a bargain *establishes* a contract between parties with novel rights that needn’t exist ex-ante."

"The interesting case is when transaction costs make bargaining difficult. What you should take from Coase is that social efficiency can be enhanced by institutions (including the firm!) which allow socially efficient bargains to be reached by removing restrictive transaction costs, and particularly that the assignment of property rights to different parties can either help or hinder those institutions."

Transactions cost: time and effort to carry out a transaction.. any resources needed to negotiate and enforce contracts...

Coase: initial allocation of legal entitlements does not matter from an efficiency perspective so long as transaction costs of exchange are nil...

Like frictionless plane in Phyisics... a logical construction rather than something encountered in real life..

Legal procedure to 'lubricate' exchange rather than allocate legal entitlement efficiently in the first place...

As with ordinary goods the gains from legal

### The Political Coase Theorem

Acemoglu, Daron. 2003. “Why Not a Political Coase Theorem? Social Conflict, Commitment, and Politics.” Journal of Comparative Economics 31 (4):620–652.

## Incomplete contracts

- Hard to think of all contingencies
- Hard to negotiate all contingencies
- Hard to write contracts to cover all contingencies

Incomplete contracts
- silent about parties' obligations in some states or state these only coarsely or ambiguosly

- Incomplete contracts will be revised and renegotiated as future unfolds...This implies
    - ex-post costs  (my fail to reach agreement..).
    - ex-ante costs

Relationship-specific investments.. Party may be reluctant to invest because fears expropriation by the other party at recontracting stage..
  - hold up:  after tenant has made investment it is sunk, landlord may hike rent to match higher value of property (entirely due to tenant investment)... Expecting this tenant may not invest..

  "Ownership – or power – is distributed among the parties to maximise their investment incentives. Hart and Moore show that complementarities between the assets and the parties have important implications. If the assets are so complementary that they are productive only when used together, they should have a single owner. Separating such complementary assets does not give power to anybody, while when the assets have a single owner, the owner has power and improved incentives."

## Code Section
**Note:** To re-create or modify any content go to the 'Cell' menu above run all code cells below by choosing 'Run All Below'.  Then 'Run all Above' to recreate all output above (or go to the top and step through each code cell manually).

```python
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve
%matplotlib inline
```

Default parameter values:

```python
Pc = 4
Pw = 8
c = 1/2
d = 1/2
```

```python
def F(x,P=Pc,c=c):
    '''Cattle Profit'''
    return P*x - c*x**2

def AG(x, P=Pw):
    '''Wheat farm profit before crop damage'''
    return P*(x**0)   # to return an array of len(x)

def AGD(x,P=Pw,d=d):
    return AG(x,P) - d*x**2
```

```python
def copt(P=Pc,c=c):
    '''rancher private optimum'''
    return P/(2*c)

def topt(P=Pc,c=c, d=d):
    '''Social effient optimum'''
    return P/(2*(c+d))
```

```python
CE, TE = copt(),topt()
CE, TE
```

    (4.0, 2.0)

```python
xx = np.linspace(0,6,100)
```

```python
def coaseplot1():
    fig = plt.subplots(figsize=(12,8))
    plt.plot(xx, F(xx), label = 'Rancher Profit' )
    plt.plot(xx, AG(xx), '--', label = 'Farmer w/ no cattle' )
    plt.plot(xx, AGD(xx), label = 'Farmer w/ cattle')
    plt.plot(xx, F(xx) + AGD(xx),label='Sum of both activities')
    plt.scatter(copt(),F(copt()))
    plt.scatter(topt(),F(topt()) + AGD(topt()))
    plt.grid()
    plt.ylim(0,14)
    plt.xlabel('x -- head of cattle', fontsize=18)
    plt.ylabel('Benefits/Profit', fontsize=18)
    plt.legend(fontsize=14);
```

```python
coaseplot1()
```

Let's plot a standard 'external cost' diagram

```python
def MC(x,c=1/2):
    '''Cattle MC'''
    return 2*c*x

def excost(x,d=1/2):
    return 2*d*x

```

```python
def coaseplot2(Pw=Pw, Pc=Pc):
    fig = plt.subplots(figsize=(12,8))
    plt.axhline(Pc);
    plt.plot(xx, MC(xx), label = 'Rancher PMC' )
    plt.plot(xx, MC(xx)+excost(xx), label = 'SMC')
    plt.fill_between(xx, MC(xx)+excost(xx),Pc*xx**0, where=((MC(xx)<=Pc*xx**0) & (xx>2)),
                     facecolor='green', alpha=0.2, label='DWL')

    plt.text(3,5,'DWL' )
    plt.text(5,3.5,r'$SMB = P_C$')
    plt.text(5,5.5, r'$PMC$')
    plt.text(5,10.5, r'$SMC$')
    #plt.scatter(topt(),G(topt()) + AGD(topt()))
    plt.grid()
    plt.ylim(0,13)
    plt.xlabel('x -- head of cattle')
    plt.ylabel('Benefits/Profit')
    plt.legend();
```

#### Code for land example

```python
A=1
```

```python
def F(T, A=A):
    return A*np.sqrt(T)
```

```python
def MVPT(P,T,A=A):
    return A*P/T**(1/2)

def LD(P,r,A=A):
    return (P*A/r)**2
```

```python
A=1
Tbar  = 10  # Total land endowment
P = 5.5     # Price of output
cl = 3      # cost of clearing land
```

Land demand for each farmer is given by $P\cdot F_T(T_i) = r$. So for this production  $P \frac{1}{\sqrt T_i} = r$ or $P \frac{1}{\sqrt T_i} = cl$  so we can write

$$T^*_i(r) = (P/r)^2$$

If there is an open frontier the sum or demands falls short of total land supply and the marginal cost of land is the cost of clearing $r=c_l$. 'Land scarcity' results when there is an equilibrium price of land and$r>c_l$ where $r$ is found from

$$\sum T^*_i(r) = \bar T$$

```python
def req(P,cl, Tb=Tbar, N=2, A=A):
    '''equilibrium rental rate'''
    def landemand(r):
        return N*(A*P/r)**2 - Tb
    return fsolve(landemand, 1)[0]

```

```python
P, cl, req(P,cl)
```

    (5.5, 3, 2.4596747752497685)

```python
LD(P, req(P,cl))*2, Tbar
```

    (10.000000000000002, 10)

```python
def mopt(P,cl,A=A):
    '''Optimum land use for each i at the P*MPT = max(cl,r)'''
    r = req(P,cl)
    ru = max(cl, r)
    return (A*P/ru)**2
```

Farmer A will demand

```python
mopt(P,cl), MVPT(P, mopt(P,cl) )
```

    (3.3611111111111107, 3.0)

```python
#plt.style.use('bmh')

def landmarket(P, cl, title, A=A):
    t = np.linspace(0.1,Tbar-0.1, 2*Tbar)
    fig = plt.subplots(figsize=(12,8))
    x0 = mopt(P,cl,A=A)
    plt.ylim(0,5)
    #plt.axhline(cl,linestyle=':')
    plt.axhline(max(cl,req(P,cl,A=A)),linestyle='--')
    plt.axhline(cl,linestyle=':')
    plt.plot(t,MVPT(P,t))
    plt.text(8, MVPT(P,8),r'$P \cdot F_T(T)$', fontsize=18)
    plt.text(1, MVPT(P,Tbar-1),r'$P \cdot F_T(\bar T - T)$', fontsize=18)
    plt.xlabel('T -- land use', fontsize=18)
    plt.ylabel('MVPT', fontsize=18)

    plt.scatter(x0, MVPT(P,x0))
    plt.scatter(Tbar-mopt(P,cl),MVPT(P,x0))
    plt.plot([x0,x0],[0,MVPT(P,x0)],':')
    plt.plot([Tbar-x0,Tbar-x0],[0,MVPT(P,x0)],':')
    plt.plot(t,MVPT(P,Tbar - t))
    plt.plot(t,MVPT(P,Tbar-t))
    plt.title(title)
    plt.xlim(0,Tbar);

```

```python
landmarket(P=5.5, cl = 3, title = 'Open Frontier')
```

```python
landmarket(P=8, cl = 3, title = 'Land Scarcity')
```


```python
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Class 5: Managing Data with Pandas

Pandas is a Python library for managing datasets. Documentation and examples are available on the website for Pandas: http://pandas.pydata.org/.

In this Notebook, we'll make use of a dataset containing long-run averages of inflation, money growth, and real GDP. The dataset is available here: https://raw.githubusercontent.com/letsgoexploring/economic-data/master/quantity-theory/csv/quantity_theory_data.csv (Python code to generate the dataset: https://github.com/letsgoexploring/economic-data). Recall that the quantity theory of money implies the following linear relationship between the long-run rate of money growth, the long-run rate of inflation, and the long-run rate of real GDP growth in a country:

\begin{align}
\text{inflation} & = \text{money growth} - \text{real GDP growth},
\end{align}

Generally, we treat real GDP growth and money supply growth as exogenous so this is a theory about the determination of inflation.

### Import Pandas

```python
# Import the Pandas module as pd
import pandas as pd
```

### Import data from a csv file

Pandas has a function called `read_csv()` for reading data from a csv file into a Pandas `DataFrame` object.

```python
# Import quantity theory data into a Pandas DataFrame called 'df' with country names as the index.

# Directly from internet
df = pd.read_csv('https://raw.githubusercontent.com/letsgoexploring/economic-data/master/quantity-theory/csv/quantity_theory_data.csv')

# From current working directory
# df = pd.read_csv('qtyTheoryData.csv')
```

```python
# Print the first 5 rows
print(df.head())
```

                   country iso code  observations  inflation  money growth  \
    0              Albania      ALB            21    0.05453       0.12879
    1              Algeria      DZA            51    0.10913       0.16370
    2               Angola      AGO            20    0.88679       1.08457
    3  Antigua and Barbuda      ATG            38    0.04052       0.09903
    4            Argentina      ARG            54    0.74972       0.79170

       gdp growth
    0     0.04829
    1     0.03896
    2     0.08592
    3     0.03673
    4     0.02468

```python
# Print the last 10 rows
print(df.tail(10))
```

                      country iso code  observations  inflation  money growth  \
    168               Ukraine      UKR            23    0.61867       0.58537
    169  United Arab Emirates      ARE            40    0.03659       0.14033
    170         United States      USA            54    0.03442       0.05753
    171               Uruguay      URY            54    0.37658       0.41383
    172               Vanuatu      VUT            36    0.03611       0.08343
    173         Venezuela, RB      VEN            53    0.20726       0.27581
    174    West Bank and Gaza      PSE            17    0.03864       0.12528
    175           Yemen, Rep.      YEM            24    0.14770       0.13852
    176                Zambia      ZMB            27    0.22850       0.24470
    177              Zimbabwe      ZWE            32   -0.00128      -0.13307

         gdp growth
    168    -0.01070
    169     0.04811
    170     0.03140
    171     0.02294
    172     0.02972
    173     0.02793
    174     0.03389
    175     0.04008
    176     0.01208
    177     0.00839

```python
# Print the type of variable 'df'
print(type(df))
```

    <class 'pandas.core.frame.DataFrame'>

### Properties of `DataFrame` objects

Like entries in a spreadsheet file, elements in a `DataFrame` object have row (or *index*) and column coordinates. Column names are always strings. Index elements can be integers, strings, or dates.

```python
# Print the columns of df
print(df.columns)
```

    Index(['country', 'iso code', 'observations', 'inflation', 'money growth',
           'gdp growth'],
          dtype='object')

```python
# Create a new variable called 'money' equal to the 'money growth' column and print
money = df['money growth']
print(money)
```

    0      0.12879
    1      0.16370
    2      1.08457
    3      0.09903
    4      0.79170
            ...
    173    0.27581
    174    0.12528
    175    0.13852
    176    0.24470
    177   -0.13307
    Name: money growth, Length: 178, dtype: float64

```python
# Print the type of the variable money
print(type(money))
```

    <class 'pandas.core.series.Series'>

A Pandas `Series` stores one column of data. Like a `DataFrame`, a `Series` object has an index. Note that `money` has the same index as `df`. Instead of having a column, the `Series` has a `name` attribute.

```python
# Print the name of the 'money' variable
print(money.name)
```

    money growth

Select multiple columns of a `DataFrame` by puting the desired column names in a set a of square brackets (i.e., in a `list`).

```python
# Print the first 5 rows of just the inflation, money growth, and gdp growth columns
print(df[['inflation','money growth','gdp growth']].head())
```

       inflation  money growth  gdp growth
    0    0.05453       0.12879     0.04829
    1    0.10913       0.16370     0.03896
    2    0.88679       1.08457     0.08592
    3    0.04052       0.09903     0.03673
    4    0.74972       0.79170     0.02468

As mentioned, the set of row coordinates is the index. Unless specified otherwise, Pandas automatically assigns an integer index starting at 0 to rows of the `DataFrame`.

```python
# Print the index of 'df'
print(df.index)
```

    RangeIndex(start=0, stop=178, step=1)

Note that in the index of the `df` is the numbers 0 through 177. We could have specified a different index when we imported the data using `read_csv()`. For example, suppose we want to the country names to be the index of `df`. Since country names are in the first column of the data file, we can pass the argument `index_col=0` to `read_csv()`

```python
# Import quantity theory data into a Pandas DataFrame called 'df' with country names as the index.
df = pd.read_csv('https://raw.githubusercontent.com/letsgoexploring/economic-data/master/quantity-theory/csv/quantity_theory_data.csv',index_col=0)

# Print first 5 rows of df
print(df.head())
```

                        iso code  observations  inflation  money growth  \
    country
    Albania                  ALB            21    0.05453       0.12879
    Algeria                  DZA            51    0.10913       0.16370
    Angola                   AGO            20    0.88679       1.08457
    Antigua and Barbuda      ATG            38    0.04052       0.09903
    Argentina                ARG            54    0.74972       0.79170

                         gdp growth
    country
    Albania                 0.04829
    Algeria                 0.03896
    Angola                  0.08592
    Antigua and Barbuda     0.03673
    Argentina               0.02468

Use the `loc` attribute to select rows of the `DataFrame` by index *values*.

```python
# Create a new variable called 'usa_row' equal to the 'United States' row and print
usa_row = df.loc['United States']
print(usa_row)
```

    iso code            USA
    observations         54
    inflation       0.03442
    money growth    0.05753
    gdp growth       0.0314
    Name: United States, dtype: object

Use `iloc` attribute to select row based on integer location (starting from 0).

```python
# Create a new variable called 'third_row' equal to the third row in the DataFrame and print
third_row = df.iloc[2]
print(third_row)
```

    iso code            AGO
    observations         20
    inflation       0.88679
    money growth    1.08457
    gdp growth      0.08592
    Name: Angola, dtype: object

There are several ways to return a single element of a Pandas `DataFrame`. For example, here are three that we want to return the value of inflation for the United States from the DataFrame `df`:

1. `df.loc['United States','inflation']`
2. `df.loc['United States']['inflation']`
3. `df['inflation']['United States']`

The first method points directly to the element in the `df` while the second and third methods return *copies* of the element. That means that you can modify the value of inflation for the United States by running:

    df.loc['United States','inflation'] = new_value

But running either:

    df.loc['United States']['inflation'] = new_value

or:

    df['inflation']['United States'] = new_value

will return an error.

```python
# Print the inflation rate of the United States  (By index and column together)
print('Long-run average inflation in US: ',df.loc['United States','inflation'])
```

    Long-run average inflation in US:  0.03442

```python
# Print the inflation rate of the United States (first by index, then by column)
print('Long-run average inflation in US: ',df.loc['United States']['inflation'])
```

    Long-run average inflation in US:  0.03442

```python
# Print the inflation rate of the United States  (first by column, then by index)
print('Long-run average inflation in US: ',df['inflation']['United States'])
```

    Long-run average inflation in US:  0.03442

New columns are easily created as functions of existing columns.

```python
# Create a new column called 'difference' equal to the money growth column minus
# the inflation column and print the modified DataFrame
df['difference'] = df['money growth'] - df['inflation']
print(df['difference'])
```

    country
    Albania                0.07426
    Algeria                0.05457
    Angola                 0.19778
    Antigua and Barbuda    0.05851
    Argentina              0.04198
                            ...
    Venezuela, RB          0.06855
    West Bank and Gaza     0.08664
    Yemen, Rep.           -0.00918
    Zambia                 0.01620
    Zimbabwe              -0.13179
    Name: difference, Length: 178, dtype: float64

```python
# Print the average difference between money growth and inflation
print(df.difference.mean())
```

    0.06339837078651682

```python
# Remove the following columns from the DataFrame: 'iso code','observations','difference'
df = df.drop(['iso code','observations','difference'],axis=1)

# Print the modified DataFrame
print(df)
```

                         inflation  money growth  gdp growth
    country
    Albania                0.05453       0.12879     0.04829
    Algeria                0.10913       0.16370     0.03896
    Angola                 0.88679       1.08457     0.08592
    Antigua and Barbuda    0.04052       0.09903     0.03673
    Argentina              0.74972       0.79170     0.02468
    ...                        ...           ...         ...
    Venezuela, RB          0.20726       0.27581     0.02793
    West Bank and Gaza     0.03864       0.12528     0.03389
    Yemen, Rep.            0.14770       0.13852     0.04008
    Zambia                 0.22850       0.24470     0.01208
    Zimbabwe              -0.00128      -0.13307     0.00839

    [178 rows x 3 columns]

### Methods

A Pandas `DataFrame` has a bunch of useful methods defined for it. `describe()` returns some summary statistics.

```python
# Print the summary statistics for 'df'
print(df.describe())
```

            inflation  money growth  gdp growth
    count  178.000000    178.000000  178.000000
    mean     0.128568      0.191966    0.037594
    std      0.172497      0.170733    0.022616
    min     -0.001280     -0.133070   -0.025240
    25%      0.042493      0.102785    0.024382
    50%      0.070050      0.140935    0.037745
    75%      0.132060      0.210498    0.048245
    max      1.277050      1.224430    0.165480

The `corr()` method returns a `DataFrame` containing the correlation coefficients of the specified `DataFrame`.

```python
# Create a variable called 'correlations' containg the correlation coefficients for columns in 'df'
correlations = df.corr()

# Print the correlation coefficients
print(correlations)
```

                  inflation  money growth  gdp growth
    inflation      1.000000      0.971200   -0.041515
    money growth   0.971200      1.000000    0.088061
    gdp growth    -0.041515      0.088061    1.000000

```python
# Print the correlation coefficient for inflation and money growth
print('corr of inflation and money growth: ',round(correlations.loc['inflation','money growth'],4))

# Print the correlation coefficient for inflation and real GDP growth
print('corr of inflation and gdp growth:   ',round(correlations.loc['inflation','gdp growth'],4))

# Print the correlation coefficient for money growth and real GDP growth
print('corr of money growth and gdp growth:',round(correlations.loc['money growth','gdp growth'],4))
```

    corr of inflation and money growth:  0.9712
    corr of inflation and gdp growth:    -0.0415
    corr of money growth and gdp growth: 0.0881

`sort_values()` returns a copy of the original `DataFrame` sorted along the given column. The optional argument `ascending` is set to `True` by default, but can be changed to `False` if you want to print the lowest first.

```python
# Print rows for the countries with the 10 lowest inflation rates
print(df.sort_values('inflation').head(10))
```

                          inflation  money growth  gdp growth
    country
    Zimbabwe               -0.00128      -0.13307     0.00839
    Djibouti                0.00035       0.07940     0.01683
    Germany                 0.01120       0.07852     0.01233
    Hong Kong SAR, China    0.01452       0.10295     0.03778
    Ireland                 0.01467       0.13223     0.03552
    France                  0.01560       0.07038     0.01316
    Kosovo                  0.01659       0.09355     0.03622
    Greece                  0.01736       0.01533    -0.00487
    Switzerland             0.01754       0.06243     0.01750
    Austria                 0.01757       0.08010     0.01543

```python
# Print rows for the countries with the 10 highest inflation rates
print(df.sort_values('inflation',ascending=False).head(10))
```

                      inflation  money growth  gdp growth
    country
    Congo, Dem. Rep.    1.27705       1.22443    -0.00239
    Brazil              0.88891       0.92023     0.04208
    Angola              0.88679       1.08457     0.08592
    Argentina           0.74972       0.79170     0.02468
    Nicaragua           0.62762       0.67775     0.02522
    Belarus             0.62519       0.65668     0.05171
    Ukraine             0.61867       0.58537    -0.01070
    Peru                0.48310       0.54905     0.03510
    Armenia             0.46680       0.48970     0.05726
    Azerbaijan          0.45091       0.52949     0.05724

Note that `sort_values` and `sort_index` return *copies* of the original `DataFrame`. If, in the previous example, we had wanted to actually modify `df`, we would have need to explicitly overwrite it:

    df = df.sort_index(ascending=False)

```python
# Print first 10 rows with the index sorted in descending alphabetical order
print(df.sort_index(ascending=False).head(10))
```

                          inflation  money growth  gdp growth
    country
    Zimbabwe               -0.00128      -0.13307     0.00839
    Zambia                  0.22850       0.24470     0.01208
    Yemen, Rep.             0.14770       0.13852     0.04008
    West Bank and Gaza      0.03864       0.12528     0.03389
    Venezuela, RB           0.20726       0.27581     0.02793
    Vanuatu                 0.03611       0.08343     0.02972
    Uruguay                 0.37658       0.41383     0.02294
    United States           0.03442       0.05753     0.03140
    United Arab Emirates    0.03659       0.14033     0.04811
    Ukraine                 0.61867       0.58537    -0.01070

### Quick plotting example

Construct a graph that visually confirms the quantity theory of money by making a scatter plot with average money growth on the horizontal axis and average inflation on the vertical axis. Set the marker size `s` to 50 and opacity (`alpha`) 0.25. Add a 45 degree line, axis labels, and a title. Lower and upper limits for the horizontal and vertical axes should be -0.2 and 1.2.

```python
# Create data for 45 degree line
x45 = [-0.2,1.2]
y45 = [-0.2,1.2]

# Create figure and axis
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

# Plot 45 degree line and create legend in lower right corner
ax.plot(x45,y45,'-r',label = '$45^{\circ}$')
ax.legend(loc='lower right')

# Scatter plot of data inflation against money growth
ax.scatter(df['money growth'],df['inflation'],s=50,alpha = 0.25)
ax.set_xlim([-0.2,1.2])
ax.set_ylim([-0.2,1.2])
ax.set_xlabel('money growth')
ax.set_ylabel('inflation')
ax.set_title('Average inflation against average money growth \nfor '+str(len(df.index))+' countries.')
ax.grid()
```

### Exporting a `DataFrame` to csv

Use the DataFrame method `to_csv()` to export DataFrame to a csv file.

```python
# Export the DataFrame 'df' to a csv file called 'modified_data.csv'.
df.to_csv('modified_data.csv')
```


## 1. Module Import, Select Device, and Download MNIST Data

### 1-1. Setup & Module Import

```python
!pip install pennylane pennylane-ionq qiskit[visualization]
```

    Requirement already satisfied: pennylane in /usr/local/lib/python3.7/dist-packages (0.16.0)
    Requirement already satisfied: pennylane-ionq in /usr/local/lib/python3.7/dist-packages (0.16.0)
    Requirement already satisfied: qiskit[visualization] in /usr/local/lib/python3.7/dist-packages (0.27.0)
    Requirement already satisfied: autograd in /usr/local/lib/python3.7/dist-packages (from pennylane) (1.3)
    Requirement already satisfied: appdirs in /usr/local/lib/python3.7/dist-packages (from pennylane) (1.4.4)
    Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from pennylane) (1.19.5)
    Requirement already satisfied: autoray in /usr/local/lib/python3.7/dist-packages (from pennylane) (0.2.5)
    Requirement already satisfied: toml in /usr/local/lib/python3.7/dist-packages (from pennylane) (0.10.2)
    Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from pennylane) (1.4.1)
    Requirement already satisfied: networkx in /usr/local/lib/python3.7/dist-packages (from pennylane) (2.5.1)
    Requirement already satisfied: semantic-version==2.6 in /usr/local/lib/python3.7/dist-packages (from pennylane) (2.6.0)
    Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pennylane-ionq) (2.23.0)
    Requirement already satisfied: python-dateutil in /usr/local/lib/python3.7/dist-packages (from pennylane-ionq) (2.8.1)
    Requirement already satisfied: qiskit-terra==0.17.4 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.17.4)
    Requirement already satisfied: qiskit-ignis==0.6.0 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.6.0)
    Requirement already satisfied: qiskit-ibmq-provider==0.14.0 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.14.0)
    Requirement already satisfied: qiskit-aqua==0.9.2 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.9.2)
    Requirement already satisfied: qiskit-aer==0.8.2 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.8.2)
    Requirement already satisfied: seaborn>=0.9.0; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.11.1)
    Requirement already satisfied: pylatexenc>=1.4; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (2.10)
    Requirement already satisfied: pydot; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (1.3.0)
    Requirement already satisfied: pygments>=2.4; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (2.6.1)
    Requirement already satisfied: pillow>=4.2.1; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (7.1.2)
    Requirement already satisfied: ipywidgets>=7.3.0; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (7.6.3)
    Requirement already satisfied: matplotlib>=2.1; extra == "visualization" in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (3.2.2)
    Requirement already satisfied: future>=0.15.2 in /usr/local/lib/python3.7/dist-packages (from autograd->pennylane) (0.16.0)
    Requirement already satisfied: decorator<5,>=4.3 in /usr/local/lib/python3.7/dist-packages (from networkx->pennylane) (4.4.2)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->pennylane-ionq) (3.0.4)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->pennylane-ionq) (1.24.3)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pennylane-ionq) (2021.5.30)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pennylane-ionq) (2.10)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil->pennylane-ionq) (1.15.0)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (5.4.8)
    Requirement already satisfied: fastjsonschema>=2.10 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (2.15.1)
    Requirement already satisfied: retworkx>=0.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (0.9.0)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (1.7.1)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (0.3.4)
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (2.6.0)
    Requirement already satisfied: python-constraint>=1.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (1.4.0)
    Requirement already satisfied: ply>=3.10 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (3.11)
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ignis==0.6.0->qiskit[visualization]) (57.0.0)
    Requirement already satisfied: requests-ntlm>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.1.0)
    Requirement already satisfied: websocket-client>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.1.0)
    Requirement already satisfied: h5py in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (3.1.0)
    Requirement already satisfied: quandl in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (3.6.1)
    Requirement already satisfied: dlx<=1.0.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (1.0.4)
    Requirement already satisfied: docplex<=2.20.204; sys_platform != "darwin" in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (2.20.204)
    Requirement already satisfied: fastdtw<=0.3.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (0.3.4)
    Requirement already satisfied: yfinance in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (0.1.59)
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (1.1.5)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (0.22.2.post1)
    Requirement already satisfied: pybind11>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.8.2->qiskit[visualization]) (2.6.2)
    Requirement already satisfied: pyparsing>=2.1.4 in /usr/local/lib/python3.7/dist-packages (from pydot; extra == "visualization"->qiskit[visualization]) (2.4.7)
    Requirement already satisfied: traitlets>=4.3.1 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.0.5)
    Requirement already satisfied: jupyterlab-widgets>=1.0.0; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.0.0)
    Requirement already satisfied: nbformat>=4.2.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.1.3)
    Requirement already satisfied: ipython>=4.0.0; python_version >= "3.3" in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.5.0)
    Requirement already satisfied: ipykernel>=4.5.1 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (4.10.1)
    Requirement already satisfied: widgetsnbextension~=3.5.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (3.5.1)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1; extra == "visualization"->qiskit[visualization]) (0.10.0)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1; extra == "visualization"->qiskit[visualization]) (1.3.1)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-terra==0.17.4->qiskit[visualization]) (1.2.1)
    Requirement already satisfied: ntlm-auth>=1.0.2 in /usr/local/lib/python3.7/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.5.0)
    Requirement already satisfied: cryptography>=1.3 in /usr/local/lib/python3.7/dist-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (3.4.7)
    Requirement already satisfied: cached-property; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from h5py->qiskit-aqua==0.9.2->qiskit[visualization]) (1.5.2)
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.2->qiskit[visualization]) (8.8.0)
    Requirement already satisfied: inflection>=0.3.1 in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.2->qiskit[visualization]) (0.5.1)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from yfinance->qiskit-aqua==0.9.2->qiskit[visualization]) (0.0.9)
    Requirement already satisfied: lxml>=4.5.1 in /usr/local/lib/python3.7/dist-packages (from yfinance->qiskit-aqua==0.9.2->qiskit[visualization]) (4.6.3)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->qiskit-aqua==0.9.2->qiskit[visualization]) (2018.9)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.2->qiskit[visualization]) (1.0.1)
    Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.7/dist-packages (from traitlets>=4.3.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.2.0)
    Requirement already satisfied: jupyter-core in /usr/local/lib/python3.7/dist-packages (from nbformat>=4.2.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (4.7.1)
    Requirement already satisfied: pexpect; sys_platform != "win32" in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (4.8.0)
    Requirement already satisfied: pickleshare in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.7.5)
    Requirement already satisfied: prompt-toolkit<2.0.0,>=1.0.4 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.0.18)
    Requirement already satisfied: simplegeneric>0.8 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.8.1)
    Requirement already satisfied: jupyter-client in /usr/local/lib/python3.7/dist-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.3.5)
    Requirement already satisfied: tornado>=4.0 in /usr/local/lib/python3.7/dist-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.1.1)
    Requirement already satisfied: notebook>=4.4.1 in /usr/local/lib/python3.7/dist-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.3.1)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.14.5)
    Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.7/dist-packages (from pexpect; sys_platform != "win32"->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.7.0)
    Requirement already satisfied: wcwidth in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.2.5)
    Requirement already satisfied: pyzmq>=13 in /usr/local/lib/python3.7/dist-packages (from jupyter-client->ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (22.1.0)
    Requirement already satisfied: Send2Trash in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.5.0)
    Requirement already satisfied: nbconvert in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.6.1)
    Requirement already satisfied: jinja2 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (2.11.3)
    Requirement already satisfied: terminado>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.10.1)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (2.20)
    Requirement already satisfied: defusedxml in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.7.1)
    Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.4.3)
    Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.8.4)
    Requirement already satisfied: testpath in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.5.0)
    Requirement already satisfied: bleach in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (3.3.0)
    Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.3)
    Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from jinja2->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (2.0.1)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (20.9)
    Requirement already satisfied: webencodings in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.5.1)

```python
import matplotlib.pyplot as plt
%matplotlib inline

import os
import pennylane as qml
from pennylane import numpy as np
from pennylane.templates import RandomLayers
from pennylane_ionq import ops
import matplotlib.pyplot as plt

import qiskit
from qiskit import transpile, assemble
from qiskit.visualization import *
from qiskit.circuit.random import random_circuit

import torch
from torch.autograd import Function
from torchvision import datasets, transforms
import torch.optim as optim
import torch.nn as nn
import torch.nn.functional as F
```

### 1-2. Select Device

```python
use_cuda = torch.cuda.is_available()
if use_cuda:
    DEVICE = torch.device('cuda')
    print('cuda index:', torch.cuda.current_device())
    print('GPU:', torch.cuda.get_device_name())
else:
    DEVICE = torch.device('cpu')

print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)
```

    Using PyTorch version: 1.9.0+cu102  Device: cpu

### 1-3. Setting of the main hyper-parameters of the model

```python
BATCH_SIZE = 16
n_epochs = 30   # Number of optimization epochs
n_layers = 1    # Number of random layers
n_train = 128    # Size of the train dataset
n_test = 32     # Size of the test dataset

SAVE_PATH = "quanvolution_pqc/" # Data saving folder
PREPROCESS = True           # If False, skip quantum processing and load data from SAVE_PATH
seed = 47
np.random.seed(seed)        # Seed for NumPy random number generator
torch.manual_seed(seed)     # Seed for TensorFlow random number generator

os.environ['IONQ_API_KEY']='1LCa7Dvzz5P35g6B3gwoxzFO04n7gWYH'   # IonQ Token
#dev = qml.device("ionq.qpu", wires=9)
dev = qml.device("default.qubit", wires=9)
```

### 1-4. Data Loading

```python
from torch.utils.data import SubsetRandomSampler
train_dataset = datasets.MNIST(root = "./mnist_data",
                               train = True,
                               download = True,
                               transform = transforms.Compose([
                                               transforms.Resize(size=14),
                                               transforms.ToTensor()]))

test_dataset = datasets.MNIST(root = "./mnist_data",
                              train = False,
                              transform = transforms.Compose([
                                              transforms.Resize(size=14),
                                              transforms.ToTensor()]))

# train_dataset = Subset(train_dataset,range(n_train))
# test_dataset = Subset(test_dataset,range(n_test))

train_sampler = SubsetRandomSampler(range(n_train))
test_sampler = SubsetRandomSampler(range(n_test))

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = False,
                                           sampler = train_sampler)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False,
                                          sampler = test_sampler)
```

    /usr/local/lib/python3.7/dist-packages/torchvision/datasets/mnist.py:498: UserWarning: The given NumPy array is not writeable, and PyTorch does not support non-writeable tensors. This means you can write to the underlying (supposedly non-writeable) NumPy array using the tensor. You may want to copy the array to protect its data or make it writeable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at  /pytorch/torch/csrc/utils/tensor_numpy.cpp:180.)
      return torch.from_numpy(parsed.astype(m[2], copy=False)).view(*s)

```python
for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break
```

    X_train: torch.Size([16, 1, 14, 14]) type: torch.FloatTensor
    y_train: torch.Size([16]) type: torch.LongTensor

```python
pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(14, 14), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))
```

## 2. Construct Quantum Circuit

### 2-1. Create a 'Quantum Class' with Qiskit

```python
# Random circuit parameters

@qml.qnode(dev, interface='torch')
def pqc(data,params,circuit_type):
    n_qubits = 9
    for j in range(n_qubits):
        qml.RY(np.pi * data[j], wires=j)

    if circuit_type == 2:     # len(params) == 18
        # PQC
        for i in range(n_qubits):
            qml.RX(params[i], wires=i)
        for i in range(n_qubits):
            qml.RX(params[i+n_qubits], wires=i)
        for i in range(n_qubits-1,0,-1):
            qml.CNOT(wires=[i,i-1])
    elif circuit_type == 3:   # len(params) == 26
        # PQC
        for i in range(n_qubits):
            qml.RX(params[i], wires=i)
        for i in range(n_qubits):
            qml.RX(params[i+n_qubits], wires=i)
        for k,i in enumerate(range(n_qubits-1,0,-1)):
            qml.CRZ(params[k+2*n_qubits], wires=[i,i-1])
    elif circuit_type == 9:   # len(params) == 9
        # PQC
        for i in range(n_qubits):
            qml.Hadamard(wires=i)
        for i in range(n_qubits-1,0,-1):
            qml.CZ(wires=[i,i-1])
        for i in range(n_qubits):
            qml.RX(params[i],wires=i)
    elif circuit_type == 10:  # len(params) == 18
        for i in range(n_qubits):
            qml.RY(params[i],wires=i)
        for i in range(n_qubits-1,0,-1):
            qml.CZ(wires=[i,i-1])
            qml.CZ(wires=[0,n_qubits-1])
        for i in range(n_qubits):
            qml.RY(params[i+n_qubits],wires=i)
    elif circuit_type == 'random':
        # Random quantum circuit
        rand_params = np.random.uniform(high=2 * np.pi, size=(n_layers, n_qubits))
        RandomLayers(rand_params, wires=list(range(4)))
    else:
        print("Invalid circuit_type")
        raise NotImplementedError
    # Measurement producing 9 classical output values
    return qml.expval(qml.PauliZ(0))
```

```python
print(pqc(torch.tensor([1,0,2,1,2,3,1,2,0]),torch.tensor([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]), 'random'))
```

    tensor(0.5221, dtype=torch.float64)

```python
class QuanvNet(torch.nn.Module):
    def __init__(self,qc=True,opt=True,circuit_type=9):
        super().__init__()
        self.qc = qc
        self.opt = opt
        self.n_qubits = 9
        self.filters = 1
        self.conv_opt = torch.nn.Conv2d(in_channels=self.filters,out_channels=self.filters,kernel_size=3)
        self.conv1 = torch.nn.Conv2d(in_channels=self.filters,out_channels=8,kernel_size=3,padding='same')
        self.pre_pool = torch.nn.AvgPool2d(2)
        self.pool = torch.nn.MaxPool2d(2)
        self.conv2 = torch.nn.Conv2d(in_channels=8,out_channels=8,kernel_size=3)
        self.fc1 = torch.nn.Linear(200,16)
        self.fc2 = torch.nn.Linear(16,10)
        self.dropout = torch.nn.Dropout(0.4)
        if circuit_type == 2:
          self.num_q_params = 2*self.n_qubits
        elif circuit_type == 3:
          self.num_q_params = 3*self.n_qubits - 1
        elif circuit_type == 9:
          self.num_q_params = self.n_qubits
        elif circuit_type == 10:
          self.num_q_params = 2*self.n_qubits
        self.q_params = torch.nn.Parameter(2*np.pi*torch.rand(self.filters,self.num_q_params))
        self.q_params.requires_grad = True
        if self.qc:
          self.circuit_type = circuit_type
        else:
          self.circuit_type = 0

    def forward(self, inputs):
        if self.qc == True:
            out = torch.zeros((BATCH_SIZE,self.filters,12,12))
            for bat, image in enumerate(inputs):
                for j in range(1, 13, 1):
                    for k in range(1, 13, 1):
                        for i in range(self.filters):
                            window = image[0, j-1:j+2, k-1:k+2].squeeze()
                            window_flatten = window.reshape(self.n_qubits)
                            q_results = pqc(
                                window_flatten,
                                self.q_params[i],
                                circuit_type=self.circuit_type
                            )
                            out[bat,i,j - 1, k - 1] = F.relu(q_results.cuda())
        else:
          out = torch.zeros((BATCH_SIZE,self.filters,14,14)).cuda()
          out = inputs
        # print(out.size())
        if self.opt==True:
          out = F.relu(self.conv_opt(inputs))
        # print("out", out.type)
        x = self.pool(F.relu(self.conv1(out)))
        # print("x", x.type)
        # print(x.size())
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x,1)
        # print(x.size())
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.softmax(self.fc2(x))
        return x.cuda()
```

```python
from torchsummary import summary
criterion = torch.nn.CrossEntropyLoss()
model = QuanvNet(qc=False,opt=True)
if use_cuda:
    model.cuda()
opt = optim.Adam(model.parameters(),lr=0.01)
summary(model,(1,14,14))
```

```python
for p in model.parameters():
    print(p.shape)
```

```python
losses = []
accs = []
for epoch in range(10000):
  train_loss = 0
  acc = 0
  for (x_train, y_train) in train_loader:
    # print(y_train)
    opt.zero_grad()
    outputs = model(x_train.cuda() if use_cuda else x_train)
    _, preds = torch.max(outputs,1)
    acc += (preds==(y_train.cuda() if use_cuda else y_train)).sum().item()
    loss = criterion(outputs,(y_train.cuda() if use_cuda else y_train))
    loss.backward()
    opt.step()
    train_loss += loss
    del loss
  acc /= n_train
  train_loss /= (n_train/BATCH_SIZE)
  if epoch%10 == 9:
    if epoch%100 == 99:
      print(epoch+1,train_loss)
    losses.append(train_loss.item())
  if epoch%10 == 9:
    accs.append(acc)
  del acc
```

```python
from matplotlib.pyplot import plot
x = np.arange(0,10000,10)
plot(x,losses)
```

```python
x = np.arange(0,10000,10)
plot(x,accs)
```

```python
file = pd.DataFrame(data={'train_loss': losses,
                          'train_acc': accs})
file.to_pickle(file_name)
```


Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the MIT License.

# Bilateral Variational Autoencoder (BiVAE)

This notebook serves as a tutorial on Bilateral Variational Autoencoder (BiVAE) model for collaborative filtering. The research paper of BiVAE [1] is presented at WSDM'21 conference. For all experiments related to BiVAE model, please refer to [this repository](https://github.com/PreferredAI/bi-vae).

The implementation of the model is from [Cornac](https://github.com/PreferredAI/cornac) [2], which is a framework for multimodal recommender systems focusing on models that utilize auxiliary data (e.g., item descriptive text and image, social network, etc).

## 0 Global Settings and Imports

```python
import sys
import os
import torch
import cornac
import papermill as pm
import scrapbook as sb
import pandas as pd
from reco_utils.dataset import movielens
from reco_utils.dataset.python_splitters import python_random_split
from reco_utils.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k
from reco_utils.recommender.cornac.cornac_utils import predict_ranking
from reco_utils.common.timer import Timer
from reco_utils.common.constants import SEED

print("System version: {}".format(sys.version))
print("PyTorch version: {}".format(torch.__version__))
print("Cornac version: {}".format(cornac.__version__))
```

    System version: 3.6.11 | packaged by conda-forge | (default, Nov 27 2020, 18:57:37)
    [GCC 9.3.0]
    PyTorch version: 1.4.0
    Cornac version: 1.11.0

```python
# Select MovieLens data size: 100k, 1m, 10m, or 20m
MOVIELENS_DATA_SIZE = '100k'

# top k items to recommend
TOP_K = 10

# Model parameters
LATENT_DIM = 50
ENCODER_DIMS = [100]
ACT_FUNC = "tanh"
LIKELIHOOD = "pois"
NUM_EPOCHS = 500
BATCH_SIZE = 128
LEARNING_RATE = 0.001
```

## 1 Theory behind BiVAE

### 1.1 Motivation from Dyadic Data

Preference data in collaborative filtering (CF) typically consists of a set of users, a set of items, and a set of interactions, e.g., ratings, clicks, purchases between some user-item pairs.  Most of the time, preference data is being represented as an interaction matrix between user and item.  Generally, it is a form of dyadic data, with measurements associated with pairs of elements arising from two discrete sets of objects.  Naturally, there are two ways to view such data, by users (row-wise) and by items (column-wise).

Ultimately, we would like to seek for representations for both sides of dyadic data (users and items) whose combination would be capable of explaining user-item affinities. To tackle this objective, latent factor or matrix factorization models are predominant in the context of CF. The latter owe their success mainly to their simplicity, efficiency, effectiveness, and extensibility. Nevertheless, this category of models is also known to suffer from a limited modeling capacity as it can
only capture linear patterns both in the data and latent spaces.

To go beyond this limitation, there has recently been a surge of interest in using non-linear neural-based approaches. Notably, Variational Autoencoder (VAE) [3] model has been recently applied to CF with strong performance improvements over several competitive approaches. One plausible explanation for the good results achieved by VAE on the CF task is its probabilistic nature. Indeed, the key difference of this model with neural networks is that VAE does not seek to learn deterministic representations, but rather learns distributions over these representations, thereby allowing it to account for uncertainty in the latent space. That property is particularly beneficial when dealing with sparse data where few observations are available.

Despite its remarkable performance, VAE was originally designed for vector based-data, and thus is not in complete fidelity to the two-way nature of dyadic data. Specifically, in the User-VAE, only users are explicitly represented while items are treated as features in a vector space of users, and similarly for Item-VAE. In consequence of this mismatch between VAE and the two-way nature of preference data, it is not clear how one would extend such model on the item side in a principled way, for example to represent side information such as item textual descriptions, images, etc.

### 1.2 BiVAE Formulation

As remedy to the drawback of VAE discussed earlier, Bilateral Variational Autoencoder (BiVAE) is proposed. It consists of a generative model of user-item interactions (or dyads), and a pair of inference models (user- and item-based respectively) parameterized using multilayer neural networks, all combined together in a unified framework to auto-encode dyadic preference data. As opposed to the vanilla VAE, the proposed BiVAE is “bilateral” in that it treats users and items symmetrically, making it more apt for two-way or dyadic data. In particular, BiVAE can capture uncertainty on both sides of dyadic data, which would improve its robustness and performance on sparse preference data, compared to classical one-sided variational autoencoders.

**Notation.** The data that we seek to learn from is the user-item preference matrix, of size $U\times I$, denoted $\mathbf{R} = (r_{ui})$, where $r_{ui}$ is the interaction, e.g., integer rating, between user $u$ and item $i$. We use the notation $\mathbf{r}_{u*}$ to refer to the row in $\mathbf{R}$ corresponding to user $u$. Similarly, $\mathbf{r}_{*i}$ refers to the $i$th column of $\mathbf{R}$. The latent variables are the per user and item representations denoted respectively $\mathbf{\theta}_{u}$, $\mathbf{\beta}_i \in \mathbb{R}^{K}$.

**Generative Model.** Figure above depicts BiVAE generative model, as compared to VAE, in plate notations. The latent variables are drawn from prior distributions. Without loss of generality, the Gaussian priors with diagonal covariance matrices are being used. BiVAE further adopts the standard multivariate isotropic Gaussian as the prior over all user/item latent variables. That is, $p(\bf{\theta}_u) = \mathcal{N}(\bf{0},\bf{I})$  and $p(\bf{\beta}_i) = \mathcal{N}(\bf{0},\bf{I})$, $\forall i,u$.

Conditional on the latent variables, the observations are drawn from a univariate exponential family,

$$
p(r_{ui}|\bf{\theta}_u,\bf{\beta}_i) = \mathrm{EXPFAM}(r_{ui}; \eta(\bf{\theta}_u; \bf{\beta}_i;\omega)) = h(r_{ui})\exp\{\eta(\bf{\theta}_u;\bf{\beta}_i;\omega)r_{ui} - a(\eta(\bf{\theta}_u;\bf{\beta}_i;\omega))\}
$$

where $h(\cdot)$, $\eta(\cdot)$ and $a(\cdot)$ denote respectively the base measure, natural parameter and log-normalizer of the exponential family. For simplicity, $r_{ui}$ is assumed to be the sufficient statistic by itself. This form of the exponential family still encompasses many popular univariate distributions, including the Poisson, Bernoulli, Gaussian with unit variance, Gamma with fixed shape parameter, etc. Therefore, BiVAE framework can accommodate various types of preference data, such as counts, binary, continuous, etc. The conditional likelihood is further parameterized in such a way that,

$$
\mathbb{E}(r_{ui}|\bf{\theta}_u,\bf{\beta}_i) = \frac{d a(\eta)}{d \eta} = g_{\omega}(\bf{\theta}_u;\bf{\beta}_i)
$$

where $g_\omega(\cdot)$ is some differentiable function (e.g., inner product, neural network, etc.) parameterized by $\omega$, combining the latent representations to output the mean of the observation $r_{ui}$.

Given some $\bf{R}$, the goal is to find the values of the parameters $\omega$ that would most likely have generated the observations, and to infer the posterior over the latent variables $p(\bf{\theta}_{1:U},\bf{\beta}_{1:I}|\bf{R})$. The latter will allow us to make predictions about unknown preferences and form recommendations. However, the posterior and likelihood are intractable and thereby, exact inference and learning are infeasible.  Therefore, BiVAE relies on variational Bayes (VB), a popular and efficient approach to deal with complex probabilistic models.

**Inference Model.**
The starting point of VB is to introduce a tractable inference model $q$, governed by a set of *variational parameters* $\nu$, which will be used as a proxy for the true but intractable posterior. A variational distribution, which breaks the coupling between $\beta$ and $\theta$ - a main source of intractability in our model, is chosen as:

$$
q(\bf{\theta}_{1:U},\bf{\beta}_{1:I}|\bf{R}) = q(\bf{\theta}_{1:U}|\bf{R})q(\bf{\beta}_{1:I}|\bf{R})
$$

with

$$
\begin{align}
q(\bf{\theta}_{1:U}|\bf{R}) &= \prod_{u} q(\bf{\theta}_u|\bf{R}_{u*})\nonumber\\
q(\bf{\beta}_{1:I}|\bf{R})  &= \prod_{i} q(\bf{\beta}_i|\bf{R}_{*i})\nonumber
\end{align}
$$

Without loss of generality, the following forms are adopted:

$$
\begin{align}
q(\bf{\theta}_u|\bf{R}_{u*}) &= \mathcal{N}(\tilde{\bf{\mu}}_{\tilde\psi}(\bf{R}_{u*}),\tilde{\bf\sigma}_{\tilde\psi}(\bf{R}_{u*}))\nonumber\\
q(\bf{\beta}_i|\bf{R}_{*i}) &= \mathcal{N}(\tilde{\bf\mu}_{\tilde\phi}(\bf{R}_{*i}),\tilde{\bf\sigma}_{\tilde\phi}(\bf{R}_{*i}))\nonumber
\end{align}
$$

where $\nu=\{\tilde\phi,\tilde\psi\}$, $\tilde{\bf\mu}(\cdot)$ and $\tilde{\bf\sigma}(\cdot)$ are vector-valued functions (e.g., multilayer perceptrons) parameterized by $\tilde\phi$/$\tilde\psi$, outputting respectively the mean and covariance parameters of the variational distributions.

With $q$ in place, we can proceed with approximate inference by optimizing the Evidence Lower BOund (ELBO), w.r.t. the model $\omega$ and variational $\nu$ parameters, given in our case by,

$$
\mathcal{L} = \sum_{u,i}\mathbb{E}_{q(\bf{\theta}_u|\bf{R}_{u*})}\mathbb{E}_{q(\bf{\beta}_i|\bf{R}_{*i})}[\log p(r_{ui}|\bf{\theta}_u,\bf{\beta}_i)]
- \sum_{u} \mathrm{KL}(q(\bf{\theta}_u|\bf{R}_{u*})|| p(\bf{\theta}_u)) - \sum_{i} \mathrm{KL}(q(\bf{\beta}_i|\bf{R}_{*i})|| p(\bf{\beta}_i))
$$

### 1.3 Optimization

In practice, stochastic optimization is used to fit BiVAE to observations. While the KL terms in the ELBO are available analytically, the expectations over the conditional log-likelihood are intractable and thereby, the direct optimization of ELBO is not possible. To overcome this difficulty, the *reparameterization trick* is used to build an unbiased Monte Carlo estimator of the ELBO:

$$
\tilde{\mathcal{L}} = \sum_{u,i} \log p(r_{ui} | \tilde{\bf\theta}_u,\tilde{\bf\beta}_i) - \sum_{u} \mathrm{KL}(q(\bf{\theta}_u|\bf{r}_{u*}) ||  p(\bf{\theta}_u)) - \sum_{i} \mathrm{KL}(q(\bf{\beta}_i|\bf{r}_{*i})|| p(\bf{\beta}_i))
$$

where

$$
\begin{align}
\tilde{\bf\theta}_u &= \mathcal{T}(\bf\epsilon,\tilde{\bf\psi})= \tilde{\bf\mu}_{\tilde\psi}(\bf{r}_{u*}) + \tilde{\bf\sigma}_{\tilde\psi}(\bf{r}_{u*})\odot\bf\epsilon \nonumber\\
\tilde{\bf\beta}_{i} &= \mathcal{T}(\bf\epsilon,\tilde{\bf\phi})= \tilde{\bf\mu}_{\tilde\phi}(\bf{r}_{*i}) + \tilde{\bf\sigma}_{\tilde\phi}(\bf{r}_{*i})\odot\bf\epsilon \nonumber
\end{align}
$$

with $\bf\epsilon \sim \mathcal{N}(\bf{0},\bf{I})$.

Now all the quantities involved are tractable. However, performing unbiased stochastic optimization over the above objective is not convenient, due to the mixing between $r_{ui}$, $\bf{r}_{u*}$ and $\bf{r}_{*i}$. To overcome this difficulty and ease subsampling of observations, the two-way nature of BiVAE model is exploited to perform alternate optimization in a Gauss-Seidel fashion. Precisely, the parameters are organized into two blocks consisting of user-related and item-related parameters respectively, then alternate the optimization of each block while holding the other one fixed.  For more details on the optimization procedure, please refer to the original paper [1].

## 2 Cornac implementation of BiVAE

BiVAE is implemented in the Cornac framework as part of the [model collections](https://github.com/PreferredAI/cornac#models).
* Detailed documentations of the BiVAE model in Cornac can be found [here](https://cornac.readthedocs.io/en/latest/models.html#module-cornac.models.bivaecf.recom_bivaecf).
* Source codes of the BiVAE implementation is available on [Cornac](https://github.com/PreferredAI/cornac/tree/master/cornac/models/bivaecf).
* For all experiments related to BiVAE, please refer to [this repository](https://github.com/PreferredAI/bi-vae).

## 3 Experiments on MovieLens

### 3.1 Load and split data

To evaluate the performance of item recommendation, we adopted the provided `python_random_split` tool for the consistency.  Data is randomly split into training and test sets with the ratio of 75/25.

Note that Cornac also cover different [built-in schemes](https://cornac.readthedocs.io/en/latest/eval_methods.html) for model evaluation.

```python
data = movielens.load_pandas_df(
    size=MOVIELENS_DATA_SIZE,
    header=["userID", "itemID", "rating"]
)

data.head()
```

    100%|██████████| 4.81k/4.81k [00:01<00:00, 2.42kKB/s]

      userID
      itemID
      rating

      0
      196
      242
      3.0

      1
      186
      302
      3.0

      2
      22
      377
      1.0

      3
      244
      51
      2.0

      4
      166
      346
      1.0

```python
train, test = python_random_split(data, 0.75)
```

### 3.2 Cornac Dataset

To work with models implemented in Cornac, we need to construct an object from [Dataset](https://cornac.readthedocs.io/en/latest/data.html#module-cornac.data.dataset) class.

Dataset Class in Cornac serves as the main object that the models will interact with.  In addition to data transformations, Dataset provides a bunch of useful iterators for looping through the data, as well as supporting different negative sampling techniques.

```python
train_set = cornac.data.Dataset.from_uir(train.itertuples(index=False), seed=SEED)

print('Number of users: {}'.format(train_set.num_users))
print('Number of items: {}'.format(train_set.num_items))
```

    Number of users: 943
    Number of items: 1642

### 3.3 Train the BiVAE model

The BiVAE has a few important parameters that we need to consider:

- `k`: dimension of the latent space (i.e. the size of $\bf{\theta}_u$  and  $\bf{\beta}_i$ ).
- `encoder_structure`: dimension(s) of hidden layer(s) of the user and item encoders.
- `act_fn`: non-linear activation function used in the encoders.
- `likelihood`: choice of the likelihood function being optimized.
- `n_epochs`: number of passes through training data.
- `batch_size`: size of mini-batches of data during training.
- `learning_rate`: step size in the gradient update rules.

To train the model, we simply need to call the `fit()` method.

```python
bivae = cornac.models.BiVAECF(
    k=LATENT_DIM,
    encoder_structure=ENCODER_DIMS,
    act_fn=ACT_FUNC,
    likelihood=LIKELIHOOD,
    n_epochs=NUM_EPOCHS,
    batch_size=BATCH_SIZE,
    learning_rate=LEARNING_RATE,
    seed=SEED,
    use_gpu=torch.cuda.is_available(),
    verbose=True
)

with Timer() as t:
    bivae.fit(train_set)
print("Took {} seconds for training.".format(t))
```

      0%|          | 0/500 [00:00<?, ?it/s]

    Took 69.1472 seconds for training.

### 3.4 Prediction and Evaluation

Now that our model is trained, we can produce the ranked lists for recommendation.  Every recommender models in Cornac provide `rate()` and `rank()` methods for predicting item rated value as well as item ranked list for a given user.  To make use of the current evaluation schemes, we will through `predict()` and `predict_ranking()` functions inside `cornac_utils` to produce the predictions.

Let's measure recommendation performance of the model using top-K ranking metrics.

```python
with Timer() as t:
    all_predictions = predict_ranking(bivae, train, usercol='userID', itemcol='itemID', remove_seen=True)
print("Took {} seconds for prediction.".format(t))
```

    Took 1.7215 seconds for prediction.

```python
eval_map = map_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)
eval_ndcg = ndcg_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)
eval_precision = precision_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)
eval_recall = recall_at_k(test, all_predictions, col_prediction='prediction', k=TOP_K)

print("MAP:\t%f" % eval_map,
      "NDCG:\t%f" % eval_ndcg,
      "Precision@K:\t%f" % eval_precision,
      "Recall@K:\t%f" % eval_recall, sep='\n')
```

    MAP:	0.146552
    NDCG:	0.474124
    Precision@K:	0.412527
    Recall@K:	0.225064

```python
# Record results with papermill for tests
sb.glue("map", eval_map)
sb.glue("ndcg", eval_ndcg)
sb.glue("precision", eval_precision)
sb.glue("recall", eval_recall)
```

## 4 Discussion

BiVAE is a new variational autoencoder tailored for dyadic data, where observations consist of measurements associated with two sets of objects, e.g., users, items and corresponding ratings.  The model is symmetric, which makes it easier to extend axiliary data from both sides of users and items.  In addition to preference data, the model can be applied to other types of dyadic data such as documentword matrices, and other tasks such as co-clustering.

In the paper, there is also a discussion on Constrained Adaptive Priors (CAP), a proposed method to build informative priors to mitigate the well-known posterior collapse problem. We have left out that part purposely, not to distract the audiences.  Nevertheless, it is very interesting and worth taking a look.

[This repository](https://github.com/PreferredAI/bi-vae) will provide you a more comprehensive set of experiments related to BiVAE.

## References

1. Quoc-Tuan Truong, Salah, Aghiles, and Hady W. Lauw. "Bilateral Variational Autoencoder for Collaborative Filtering." Proceedings of the 14th ACM International Conference on Web Search and Data Mining. 2021. https://dl.acm.org/doi/pdf/10.1145/3437963.3441759
2. Salah, Aghiles, Quoc-Tuan Truong, and Hady W. Lauw. "Cornac: A Comparative Framework for Multimodal Recommender Systems." Journal of Machine Learning Research 21.95 (2020): 1-5. https://cornac.preferred.ai
3. Liang, Dawen, et al. "Variational autoencoders for collaborative filtering." Proceedings of the 2018 World Wide Web Conference. 2018.


## 3. Feladatsor

,,Ny name is Gear.''---,,pardon?"

,,Gear, dshii, ii, ay, are."---,,Mr. Jiea?"

(In a hotel of Paris)

##  Backward differentiation formulas (BDF módszerek)

### BDF módszerek reprezentálása

$$y_{i+1}=\sum_{j=1}^ka_jy_{i+1-j}+hb_0f_{i+1}$$
Jelölések:
+ $k$ lépések száma
+ $p=k$ módszer rendje
+ lokális csonkolási hibában $C_{p+1}=\frac{1}{k+1}$

$k$
$b_0$
$a_1$
$a_2$
$a_3$
$a_4$
$a_5$
$a_6$

$$1$$
$$1$$
$$1$$

$$2$$
$$\frac{2}{3}$$
$$-\frac{1}{3}$$

$$3$$
$$\frac{6}{11}$$
$$\frac{18}{11}$$
$$-\frac{9}{11}$$
$$\frac{2}{11}$$

$$4$$
$$\frac{12}{25}$$
$$\frac{48}{25}$$
$$-\frac{36}{25}$$
$$\frac{16}{25}$$
$$-\frac{3}{25}$$

$$5$$
$$\frac{60}{137}$$
$$\frac{300}{137}$$
$$-\frac{300}{137}$$
$$\frac{200}{137}$$
$$-\frac{75}{137}$$
$$\frac{12}{137}$$

$$6$$
$$\frac{60}{147}$$
$$\frac{360}{147}$$
$$-\frac{450}{147}$$
$$\frac{400}{147}$$
$$-\frac{225}{147}$$
$$\frac{72}{147}$$
$$-\frac{10}{147}$$

1.feladat. Számoljuk ki $k=1,2,3$ esetekben a $$\sum_{j=1}^k\frac{1}{j}\nabla^jy_{n+1}=hf_{n+1}$$
felíráson alapuló BDF módszereket!

2.feladat. Írjunk programot az előző feladatra általános $k$ esetre!

```octave
BDFcoefficients(2)
```

    ans =         3/2         -2        1/2

### A-, $A(\alpha)$- és L-stabilitás

3.feladat. Írjunk programot, mely kiszámolja a BDF3 módszer $A(\alpha)$ stabilitási tartományának szögét!

```octave
LW = 'LineWidth'; lw = 1;
clr = [221 221 221]/255;
% Az egysegkor definialasa
N = 1000;
theta = linspace(0,2*pi,N);
xi = exp(1i*theta);

% Az egyes karakterisztikus polinomok (\pi(xi)=0 egyenletre vonatkozoan)
BDF1=@(xi)(1-1./xi);
BDF2=@(xi)(3*xi.^2-4*xi+1)./(2*xi.^2);
BDF3=@(xi)(11*xi.^3-18*xi.^2+9*xi-2)./(6*xi.^3);
BDF4=@(xi)(25*xi.^4-48*xi.^3+36*xi.^2-16*xi+3)./(12*xi.^4);
BDF5=@(xi)(137*xi.^5-300*xi.^4+300*xi.^3-200*xi.^2+75*xi+-12)./(60*xi.^5);
BDF6=@(xi)(147*xi.^6-360*xi.^5+450*xi.^4-400*xi.^3+225*xi.^2-72*xi+10)./(60*xi.^6);
z1=BDF1(xi);
z2=BDF2(xi);
z3=BDF3(xi);
z4=BDF4(xi);
z5=BDF5(xi);
z6=BDF6(xi);
plot(z1,'k-',LW,lw), hold on
plot(z2,'k-',LW,lw),
plot(z3,'k-',LW,lw),
plot(z4,'k-',LW,lw),
plot(z5,'k-',LW,lw),
plot(z6,'k-',LW,lw),
%fill(real(z),imag(z),clr)
%plot([min(real(z)) max(real(z))],[0 0],'b--',LW,lw)
%plot([0 0],[min(imag(z)) max(imag(z))],'b--',LW,lw)
axis tight, axis equal, hold off
title('A BDF1-6 modszerek abszolut stabilitasi tartomanyai')

```

4.feladat. Tekintsük az alábbi rendszert

$u'(t)=\left[ \begin{array}{cc}
-2 & 1 \\ 998 & -999
\end{array} \right]u(t)+\left[ \begin{array}{c}
2\sin(t) \\  999[\cos(t)-\sin(t)]
\end{array} \right]$,
$u(0)$=$\left( \begin{array}{c}
0 \\
0
\end{array} \right)$

A feladat pontos megoldása

$u(t)=-\frac{1}{999}\exp(-t)\left( \begin{array}{c}
1 \\
1
\end{array} \right)+\frac{1}{999}\exp(-1000t)\left( \begin{array}{c}
1 \\
-998
\end{array} \right)+\left( \begin{array}{c}
\sin(t) \\
\cos(t)
\end{array} \right)$

Alkalmazzuk a rendszer megoldására az IE és Trapéz módszereket és pontos megoldással együtt ábrázolva magyarázzuk meg az $L$-stabilitás jelenségét!

```octave

```

### BDF módszerek implementálása

5.feladat. Tekintsük az alábbi kezdetiérték-problémát:

\begin{cases}
x'(t)= x(t)+t&\\
x(0)=2, &
\end{cases}

ahol $t\in [0,1]$. Számoljuk ki a feladat közelítő megoldását BDF2 módszer segítségével és mérjük a konvergencia rendet maximum normában finom rács technikával.

```octave

```

6.feladat. [Two-Mass and Spring system] Tekintsük az előző feladatsorról megismert kéttömegű lengőendszer vertikális kitérését leíró rendszert:
\begin{align}
u_1^{'}&=u_2\\
u_2^{'}&=-\frac{s_1}{m_1}u_1+\frac{s_2}{m_1}(u_3-u_1)\\
u_3^{'}&=u_4\\
u_4^{'}&=-\frac{s_2}{m_2}(u_3-u_1)
\end{align}
ahol $m_1=10$kg és $m_2=2$kg adott tömegek, $s_1=100$ és $s_2=120$ adott rugóhoz tartozó állandók. A feladathoz tartozó kezdeti érték vektora
$$u_1(0)=0.5,\quad u_2(0)=0,\quad u_3(0)=0.25,\quad u_4(0)=0.$$

A kitérést a tömeg egyensúlyi helyzetétől mérve pozitív irányítással lefelé értjük. A kilengést és a hosszt magában foglaló ábárzoláshoz szükséges paraméterek értékei $r_1=10$ és $r_2=15$.

A feladat megoldására a BDF4 módszert alkalmazzuk. Vizsgáljuk meg, hogy mekkora a kitérés mértéke az egyes tömegekre nézve az első 5 másodpercben ha $N=100$. Miben tér el ez a módszer az Adams-Moulton és Milne-Simpson prediktor-korrektor típusú módszerektől? Finomrács technika segítségével mérjük meg a módszer becsült rendjét kettes normában!

```octave

```

```octave

```

    ans =

           4   65537

    ratekomp1 =

       3.9916   3.9736   3.9864

    ratekomp2 =

       3.9672   3.9649   3.9835

    ratekomp3 =

       3.9926   3.9744   3.9868

    ratekomp4 =

       3.9668   3.9648   3.9834

    a =

       3.9864   3.9835   3.9868   3.9834

    ans =  3.9834

### Explicit Gear módszerekről

$$y_{i+1}=\sum_{j=1}^ka_jy_{i-j}+hb_1f_{i}$$

$k$
$b_1$
$a_1$
$a_2$
$a_3$
$a_4$
$a_5$
$a_6$

$$1$$
$$1$$
$$1$$

$$2$$
$$2$$
$$0$$
$$1$$

$$3$$
$$3$$
$$-\frac{3}{2}$$
$$\frac{6}{2}$$
$$-\frac{1}{2}$$

$$4$$
$$4$$
$$-\frac{10}{3}$$
$$\frac{18}{3}$$
$$-\frac{6}{3}$$
$$\frac{1}{3}$$

$$5$$
$$5$$
$$-\frac{65}{12}$$
$$\frac{120}{12}$$
$$-\frac{60}{12}$$
$$\frac{20}{12}$$
$$-\frac{3}{12}$$

$$6$$
$$6$$
$$-\frac{77}{10}$$
$$\frac{150}{10}$$
$$-\frac{100}{10}$$
$$\frac{50}{10}$$
$$-\frac{15}{10}$$
$$\frac{2}{10}$$

Az implementálás a már tanult AB módszerekhez hasonló. A gyakorlatban ritkán használják explicit merev megoldóként a kis abszolút stabilitási tartománya miatt.

```octave

```