# 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

```


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


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

```


# Project 3 - Promoter Regions

Etudiants:
- ORTIZ Angelo
- HANOUTI Célina

```python
# Les imports nécessaires pour ce mini-projet
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

%load_ext autoreload
%autoreload 2
import utils
import project
```

## 2 Préliminaires : données et lecture des fichiers

```python
cerevisae, chromos_flattened, counts, freqs = \
    utils.read_file("sequences/yeast_s_cerevisae_genomic_chr1-4.fna")
print("S. cerevisae contient", len(cerevisae), "chromosones et", \
      len(chromos_flattened), "nucléotides")
print("Voici les nombres d'occurrence et les fréquences d'apparition de chaque nucléotide :")
pd.DataFrame({'Occurrences': counts, 'Fréquence': freqs}, index=utils.nucleotide.keys())
```

    S. cerevisae contient 4 chromosones et 2515853 nucléotides
    Voici les nombres d'occurrence et les fréquences d'apparition de chaque nucléotide :

      Occurrences
      Fréquence

      A
      754342
      0.299835

      C
      505943
      0.201102

      G
      503163
      0.199997

      T
      752405
      0.299066

## 3.1 Description empirique

Q2) D'après l'hypothèse d'indépendance des occurrences des lettres dans le génome, pour un mot $w = w_1.w_2\dots w_k$ de longueur $k$ donné, on connaît les fréquences des lettres $w_i$ le composant.
Donc, la probabilité de trouver ce mot est donnée par
$$P(w) = \prod_{i=1}^{k} P(w_i).$$

On peut alors calculer le nombre d'occurrences du mot $w$ dans une séquence de longueur $l$ :
$$\text{comptage_attendu} = (l - k + 1)\cdot P(w),$$
où $l-k+1$ est le nombre de positions des occurrences du mot $w$.

Q3) Dans les histogrammes ci-dessous, les écarts entre les nombres d’occurrences attendus et ceux observés sont repérés avec la droite $\mathcal{L}: y = x$.
En effet,  plus la distance entre un point et cette droite est grande, plus l'écart est important.

On remarque dans les histogrammes obtenus la présence de mots qui détiennent une fonction spéciale, étant donné que l'écart entre le comptage observé et le comptage attendu augmente avec la taille des n-grammes, notamment pour les longueurs 6 et 8.

Ceci correspond bien à l'hypothèse sur la taille des région promoteurs, qui comptent souvent entre 6 et 10 nucléobases.
On trouve ainsi 3 mots pour la séquence MET, 1 mot pour PHO, et 1 mot pour GAL, qui sont susceptibles d'appartenir à une région promoteur.

```python
files = ["sequences/regulatory_seqs_MET.fasta", "sequences/regulatory_seq_PHO.fasta", \
         "sequences/regulatory_seqs_GAL.fasta"]
ks = [2, 4, 6, 8]

 # la séquence de nucléotides des séquences de contrôle
chromos_list = project.plot_scatter(files, ks)

# la distribution de probabilité des nucléotides sur la concaténation des 3 fichiers
probas = utils.nucleotide_frequency(chromos_list)
```

## 3.2 Simulation de séquences aléatoires

Q3) On estime la probabilité d'observer un mot un certain nombre $n$ de fois par le proportion de séquences simulées où le mot est apparu au moins $n$ fois, c'est-à-dire
$$P(N \geq n) \approx P_{emp}(N \geq n) = \frac{1}{S}\cdot \sum_{i=1}^{S} \unicode{x1D7D9}_{N \geq n},$$
où $S$ est le nombre de simulations effectuées.

Q4) Vous trouverez dans la cellule ci-dessous les histogrammes des distributions du comptage d'occurrences $P_{emp}(N \geq n)$ pour les mots **ATCTGC**, **ATATAT**, **TTTAAA** et **AAAAAA**.

On remarque que les mots ATATAT et TTTAAA ont la même probabilité d'apparaître, puisque la probabilité empirique d'un nombre d'occurrences nul est zéro.
Cependant, leurs distributions de comptage d'occurrences est différente.
En effet, la distribution du mot TTTAAA decroît légèrement plus vite que celle du mot ATATAT.
Ceci peut s'expliquer par le fait que le mot ATATAT chevauche avec lui-même, ce qui donne lieu a plus d'occurrences.
Pour observer ce comportement, il suffit de regarder le nombre d'occurrences maximal des deux mots, et leur comportement entre 15 et 20 occurrences.

```python
project.plot_histogram(chromos_list, probas, 1000)
```

Q5) On peut calculer un intervalle de confiance pour la probabilité d'observer un mot avec sa probabilité empirique :
$$p_{emp} \sim \mathcal{N}\left(p, \sqrt \frac{p\cdot (1-p)}{n}\right),$$
où $n$ est le nombre (suffisamment grand) de simulations effectuées.

Pour un risque de 5 % ($z_{2.5\%} = 1.96$), on obtient l'intervalle
$$\left[p_{emp} - 1.96 \cdot \sqrt \frac{p_{emp}\cdot (1-p_{emp})}{n},
p_{emp} - 1.96 \cdot \sqrt \frac{p_{emp}\cdot (1-p_{emp})}{n} \right]$$

## 3.3 Modèles de dinucléotides et trinucléotides

Q1) On prend $\mathcal{X} = \{A, C, G, T\}$. Le modèle décrit correspond à une chaîne de Markov homogène d'ordre 1 car il remplit les 3 conditions :
- c'est un processus stochastique à temps discret avec les v.a. $X_i$ correpondant à la nucléobase à la position $i$ dans un génome ;
- l'ensemble des états $\mathcal{X}$ est fini ;
- la propriété de markov d'ordre 1 est vérifiée : $\forall i \geq 0, P(X_{i+1}|X_0,\dots, X_i) = P(X_{i+1}|X_i)$. Autrement dit, la loi du prochain nucléotide de la séquence ne dépend que du nucléotide précédent.

De plus, cette chaîne de Markov est homogène car les probabilités de transition ne dépendent pas d'une variable temporelle.

Par ailleurs, la probabilité stationnaire de cette chaîne correspond aux proportions des nucléobases dans un génome auxquelles on s'attend.

Q5) De la même manière que dans la question 3.1.2), le nombre d'occurrences attendu est
$$\text{comptage_attendu} = (l - k + 1)\cdot P(w),$$
sauf que, dans ce cas, la probabilité d'apparition du mot est calculée différemment. En effet, on utilise la propriété de Markov d'ordre 1 pour obtenir le résultat suivant :
$$
\begin{align}
    P(w) &= P(w_1)\cdot \prod_{i=2}^{k} P(w_i|w_1, \dots, w_{i-1}) \\
    P(w) &= P(w_1)\cdot \prod_{i=2}^{k} P(w_i|w_{i-1}).
\end{align}
$$

Pour la probabilité du premier nucléotide, à savoir $P(w_1)$, on a utilisé $\pi^\ast$, i.e. la distribution stationnaire de la chaîne de Markov, pour le calcul de la probabilité ci-dessus.
On a néanmoins remarqué lors du calcul de cette distribution que les $\pi^{(n)}$ convergent très rapidement.
C'est pourquoi il aurait été aussi sensé d'utiliser $\pi^{(0)}$.

Q6) Dans la cellule ci-dessous, on s'aperçoit que le modèle des dinucléotides est plus représentatif des séquences de génome utilisées que le modèle des nucléotides.

Cependant, cet effet s'attenue au fur et à mesure que la longueur des n-grammes augmente.

On pourra donc dire que le modèle des **nucléotides** reste utile pour la recherche des mots promoteurs, puisque leur longueur varie entre 6 et 10.

```python
project.plot_counts(chromos_list, probas)
```

## 3.4 Probabilité de mots

Q1) En faisant l'hypothèse d'indépendance des positions d'occurrences d'un mot **non chevauchant** $w$, on peut approcher le comptage du nombre d'occurrences dans une séquence de longueur $l$ avec la loi $Bin(P(w),\ l-k+1)$, où $P(w)$ sera calculée au moyen des modèles de nucléotides et de dinucléotides.

Q2) Etant donné que la longueur des séquences est au moins de l'ordre de 10 000, le deuxième paramètre de ladite loi binomiale est beaucoup plus grand que la probabilité $P(w)$.

De ce fait, on peut approcher cette loi avec la loi $Pois(P(w)\cdot (l-k+1))$.

Q3) Les figures ci-dessous montrent les distributions de probabilité empirique des mots ATCTGC, ATATAT, TTTAAA et AAAAAA obtenues en simulant 1000 séquences aléatoires avec le modèle des nucléotides.
Nous avons représenté également un nuage de points représentant le complémentaire de la répartition de la loi de Poisson avec $P(w)$ obtenu suivant les modèles de nucléotides et dinucléotides (par chaîne de Markov).

**N.B. On fera dans la suite une analyse par rapport à la courbe de la loi de Poisson utilisant le modèle des chaînes de Markov.**

On remarque que l'histogramme représentant la probabilité empirique du mot ATCTGC, qui ne se chevauche pas, est très proche de la loi de Poisson. Malgré le fait que le mot ATATAT n'est pas chevauchant avec lui même, L’histogramme de la probabilité empirique de ce mot est quelque peu eloigné de la loi de Poisson correspondante.

En revanche, étant chavauchant avec lui même, l’histogramme de la probabilité empirique du mot AAAAAA s’écarte beaucoup de la loi de Poisson.

De ces faits, l’hypothèse d'indépendance en les différentes positions d'occurrences d'un mot et l'utilisation de la loi binomiale pour le comptage ne sont pas pértinents pour les mots qui chevauchent avec eux-mêmes.

```python
project.plot_histogram(chromos_list, probas, 1000, expected=True)
```

Q5) Un mot qui  apparaît significativement plus qu'attendu est un mot dont la probabilité empirique de dépasser le comptage observé est faible. Pour cela, nous avons trié les mots par ordre croissant de cette probabilité. Puis, on a fixé un seuil de coupure $\epsilon$ tel que les mots les plus inattendus sont ceux dont la probabilité empirique est inférieure à ce seuil.
$$P(N_{w} \geq n_{w}) < \epsilon$$
On fixe $\epsilon$ à 0.02

### Modèle de nucléotides
Pour $k = 2$, les mots les plus inattendus sont :

'GC', 'TT'.

Pour $k = 4$, les mots les plus inattendus sont :

'AAAA', 'TTTT', 'CAGC'.

Pour $n = 6$, les mots les plus inattendus sont :

'AAAAAA', 'CACGTG', 'TCATCT', 'CAAGAA', 'GCAGCA', 'TGCCAA', 'TTTTTT', 'CGTGGG'.

#### Conclusion
Avec ce modèle, les séquences 'AAAAAA', 'TTTTTT', 'GCAGCA' se trouvent dans les sites de fixation.

### Modèle de dinucléotides

Pour $k = 2$, il n'y a pas de mot inattendu.

Pour $k = 4$, les mots les plus inattendus sont :

'ACGT', 'AAAA', 'CGTG'

Pour $n = 6$, les mots les plus inattendus sont :

'AAAAAA', 'TATATA', 'TCATCT', 'CACGTG', 'ACGTGG', 'CGTGGG', 'GAAGAA', 'CAAGAA', 'ACGTGC', 'TGCCAA'.

#### Conclusion
Avec ce modèle, les séquences 'AAAAAA', 'ACGTGG', 'ACGTGC', 'CACGTG' se trouvent dans les sites de fixation.
Mis à part la séquence 'AAAAAA' qui appartient forcément à un site de fixation, le modèle des dinucléotides ne permet pas de trouver les mots chevauchant avec eux-mêmes susceptibles de se trouver dans une région promoteur.

```python
_, chromos_PHO, counts_PHO, freqs_PHO = utils.read_file(files[1])
nt, dint = project.sort_unexpected_words(chromos_PHO, freqs_PHO, [2, 4, 6 ,8], 0.002)
```

```python
print("Nucléotides")
print("===========")
print("k = 2 :", nt[0])
print("k = 4 :", nt[1][:3])
print("k = 6 :", nt[2][:8])
```

    Nucléotides
    ===========
    k = 2 : [('GC', 7.69693389768289e-06), ('TT', 0.0002337053950783874)]
    k = 4 : [('AAAA', 2.5964341787698686e-11), ('TTTT', 1.0729857979896451e-07), ('CAGC', 6.06612669363793e-05)]
    k = 6 : [('AAAAAA', 0.0), ('CACGTG', 1.6920897301098137e-06), ('TCATCT', 2.195556734641002e-06), ('CAAGAA', 4.8302813431266856e-06), ('GCAGCA', 5.856189141728585e-06), ('TGCCAA', 6.05191253999493e-06), ('TTTTTT', 1.312040984391416e-05), ('CGTGGG', 1.8057310173680285e-05)]

```python
print("Dinucléotides")
print("=============")
print("k = 2 :", dint[0])
print("k = 4 :", dint[1][:3])
print("k = 6 :", dint[2][:10])
```

    Dinucléotides
    =============
    k = 2 : []
    k = 4 : [('ACGT', 1.378394142981243e-05), ('AAAA', 6.324769583387901e-05), ('CGTG', 7.843519915196051e-05)]
    k = 6 : [('AAAAAA', 2.220446049250313e-16), ('TATATA', 8.609168137274636e-07), ('TCATCT', 1.1803502472096383e-06), ('CACGTG', 1.4190981796335933e-06), ('ACGTGG', 5.357991372800797e-06), ('CGTGGG', 7.022354704400158e-06), ('GAAGAA', 2.4193239793968147e-05), ('CAAGAA', 4.516515199359805e-05), ('ACGTGC', 5.3097112990396766e-05), ('TGCCAA', 7.366812330045214e-05)]

```python
project.unexpected_words(chromos_PHO, freqs_PHO, 6, 0.002)
```

    Convergence after 3 iterations
    word: AAAAAA		Occurrences: 43		P(N >= 43) = 0.0000
    word: AAACGT		Occurrences: 7		P(N >= 7) = 0.0004
    word: AAGAAA		Occurrences: 11		P(N >= 11) = 0.0009
    word: AAGAAG		Occurrences: 8		P(N >= 8) = 0.0004
    word: ACCTTG		Occurrences: 4		P(N >= 4) = 0.0005
    word: ACGTAT		Occurrences: 5		P(N >= 5) = 0.0003
    word: ACGTGC		Occurrences: 5		P(N >= 5) = 0.0001
    word: ACGTGG		Occurrences: 5		P(N >= 5) = 0.0000
    word: ACTACT		Occurrences: 4		P(N >= 4) = 0.0011
    word: AGAAGA		Occurrences: 6		P(N >= 6) = 0.0019
    word: AGGGCA		Occurrences: 4		P(N >= 4) = 0.0020
    word: ATCTCT		Occurrences: 4		P(N >= 4) = 0.0015
    word: ATGCCA		Occurrences: 5		P(N >= 5) = 0.0009
    word: CAAGAA		Occurrences: 10		P(N >= 10) = 0.0000
    word: CACGTG		Occurrences: 6		P(N >= 6) = 0.0000
    word: CATCTC		Occurrences: 4		P(N >= 4) = 0.0009
    word: CCACGT		Occurrences: 3		P(N >= 3) = 0.0013
    word: CCCGTT		Occurrences: 3		P(N >= 3) = 0.0007
    word: CCGCAC		Occurrences: 3		P(N >= 3) = 0.0016
    word: CGTATA		Occurrences: 4		P(N >= 4) = 0.0017
    word: CGTGCG		Occurrences: 3		P(N >= 3) = 0.0011
    word: CGTGGG		Occurrences: 4		P(N >= 4) = 0.0000
    word: CTCATC		Occurrences: 5		P(N >= 5) = 0.0001
    word: CTCGTA		Occurrences: 4		P(N >= 4) = 0.0003
    word: CTCTAC		Occurrences: 3		P(N >= 3) = 0.0017
    word: CTGCAC		Occurrences: 4		P(N >= 4) = 0.0007
    word: GAAACG		Occurrences: 5		P(N >= 5) = 0.0011
    word: GAAGAA		Occurrences: 9		P(N >= 9) = 0.0000
    word: GAGATC		Occurrences: 4		P(N >= 4) = 0.0006
    word: GATAAG		Occurrences: 5		P(N >= 5) = 0.0015
    word: GCACGT		Occurrences: 5		P(N >= 5) = 0.0001
    word: GCAGCA		Occurrences: 6		P(N >= 6) = 0.0014
    word: GGAAGA		Occurrences: 5		P(N >= 5) = 0.0003
    word: GTATAT		Occurrences: 5		P(N >= 5) = 0.0006
    word: GTGCAG		Occurrences: 4		P(N >= 4) = 0.0012
    word: TATACG		Occurrences: 4		P(N >= 4) = 0.0014
    word: TATATA		Occurrences: 10		P(N >= 10) = 0.0000
    word: TCATCT		Occurrences: 8		P(N >= 8) = 0.0000
    word: TCCTCT		Occurrences: 3		P(N >= 3) = 0.0012
    word: TCTTTG		Occurrences: 5		P(N >= 5) = 0.0011
    word: TGAGCT		Occurrences: 4		P(N >= 4) = 0.0017
    word: TGATGT		Occurrences: 4		P(N >= 4) = 0.0011
    word: TGCCAA		Occurrences: 7		P(N >= 7) = 0.0001
    word: TGGCAC		Occurrences: 4		P(N >= 4) = 0.0007
    word: TGTGGC		Occurrences: 3		P(N >= 3) = 0.0017
    word: TTATCA		Occurrences: 7		P(N >= 7) = 0.0008
    word: TTGGCA		Occurrences: 5		P(N >= 5) = 0.0020
    word: TTTATC		Occurrences: 6		P(N >= 6) = 0.0017
    word: TTTCCT		Occurrences: 5		P(N >= 5) = 0.0006
    word: TTTTCG		Occurrences: 6		P(N >= 6) = 0.0013

```python

```


# Transformação de Fontes

Jupyter Notebook desenvolvido por [Gustavo S.S.](https://github.com/GSimas)

**Transformação de fontes é o processo de substituir uma fonte de tensão
vs em série com um resistor R por uma fonte de corrente is em paralelo com
um resistor R, ou vice-versa.**

Assim como na transformação estrela-triângulo, uma
transformação de fontes não afeta a parte remanescente do circuito.

Portanto, a transformação de fontes requer que

\begin{align}
{\Large v_s = i_sR}
\\
\\{\Large i_s = \frac{v_s}{R}}
\end{align}

A transformação de fontes também se aplica a fontes dependentes, desde
que tratemos adequadamente a variável dependente.

**Exemplo 4.6**

Use transformação de fontes para determinar vo no circuito da Figura 4.17.

```python
print("Exemplo 4.6")
#trasforma fonte 1 (corrente -> tensao)
#vs1 = is*R = 12V

#Req em serie entre 4 e 2
#Req1 = 4 + 2 = 6

#transforma fonte 2 (tensao -> corrente)
#is2 = 12/3 = 4A

#transforma fonte 1 (tensao -> corrente)
#is1 = 12/6 = 2A

#Req paralelo entre 6 e 3
#Req2 = 6*3/(6 + 3) = 2

#fonte resultante
#ir = is2 - is1 = 4 - 2 = 2A

#transforma fonte 2 (corrente -> tensao)
#vs2 = Req2*ir = 2 * 2 = 4V

#divisor tensao
#v0 = vs2*8/(8 + Req2)
v0 = 4*8/(8 + 2)

print("Tensao v0",v0,"V")
```

    Exemplo 4.6
    Tensao v0 3.2 V

**Problema Prático 4.6**

Determine io no circuito da Figura 4.19 usando transformação de fontes.

```python
print("Problema Prático 4.6")
#Req serie 4 e 1 = 5
#Req paralelo 6 e 3 = 2

#transforma fonte 1 (corrente -> tensao)
#vs1 = R*is1 = 5*2 = 10V

#soma fonte 1 e 2 = 5 + 10 = 15V

#transforma fonte soma (tensao -> corrente)
#is = 15/2 = 7,5A

#Req paralelo 5 e 2 = 10/7

#soma fonte corrente = 7,5 + 3 = 10,5 A

#divisor corrente
i0 = 10.5*(10/7)/((10/7) + 7)

print("Corrente i0:",i0,"A")
```

    Problema Prático 4.6
    Corrente i0: 1.7796610169491525 A

**Exemplo 4.7**

Determine vx na Figura 4.20 usando transformação de fontes.

```python
print("Exemplo 4.7")

#transforma fonte 1 (tensao -> corrente)
#is1 = 6/2 = 3 A

#transforma fonte dep. (corrente -> tensao)
#vs_dep = 0.25Vx * 4 = Vx

#soma fonte dep. e fonte 2 = 18 + Vx

#Req paralelo 2 e 2 = 1

#transforma fontes soma (tensao -> corrente)
#is_soma = 18/4 + Vx/4

#soma fontes = 18/4 + Vx/4 + 3 = 30/4 + Vx/4 = (30 + Vx)/4

#transforma fontes soma (corrente -> tensao)
#fonte resultante = ((30 + Vx)/4)*4 = 30 + Vx

#LKT
#(30 + Vx) - 4*ix - Vx = 0
#ix = (30 + Vx)/5 = 6 + Vx/5
#30 - 24 - 4Vx/5 = 0
vx = 6*5/4
print("Tensão Vx",vx,"V")
```

    Exemplo 4.7
    Tensão Vx 7.5 V

**Problema Prático 4.7**

Use transformação de fontes para determinar ix no circuito exposto na Figura 4.22.

```python
print("Problema Prático 4.7")

#transforma fonte dep. (tensao -> corrente)
#is_dep = 2ix/5

#soma fontes = 0.024 - 2ix

#divisor corrente
#ix = (24m - 2ix)*5/(5 + 10)
    #ix = (0.12 - 10ix)/15
    #ix + 2ix/3 = 0.008
    #5ix/3 = 0.008
ix = 0.008*3/5
print("Corrente ix:",ix,"A")
```

    Problema Prático 4.7
    0.008

# Teorema de Thèvenin

**O teorema de Thévenin afirma que um circuito linear de dois terminais
pode ser substituído por um circuito equivalente formado por uma fonte
de tensão VTh em série com um resistor RTh, onde VTh é a tensão de circuito
aberto nos terminais e RTh, a resistência de entrada ou equivalente nos
terminais quando as fontes independentes forem desativadas.**

O teorema de Thévenin é muito importante na análise de circuitos, porque
ajuda a simplificar um circuito, e um circuito grande pode ser substituído por
uma única fonte de tensão independente e um único resistor.

Para tanto, suponha
que os dois circuitos da Figura 4.23 sejam equivalentes – dois circuitos são
ditos equivalentes se tiverem a mesma relação tensão-corrente em seus terminais. Se
os terminais a-b forem tornados um circuito aberto (eliminando-se a carga),
nenhuma corrente fluirá e, portanto, a tensão nos terminais a-b da Figura 4.23a
terá de ser igual à fonte de tensão VTh da Figura 4.23b, já que os dois circuitos
são equivalentes. Logo:

\begin{align}
{\Large V_{Th} = v_{oc}}
\end{align}

A resistência de entrada (ou
resistência equivalente) do circuito inativo nos terminais a-b da Figura 4.23a
deve ser igual a RTh da Figura 4.23b, pois os dois circuitos são equivalentes.
Portanto, RTh é a resistência de entrada nos terminais quando as fontes
independentes forem desligadas. Logo:

\begin{align}
{\Large R_{Th} = R_{oc}}
\end{align}

- **Caso 1:** Se a rede não tiver fontes dependentes, **desligamos todas as fontes independentes**. RTh é a resistência de entrada da rede, olhando-se entre os terminais a e b.
- **Caso 2:** Se a rede tiver fontes dependentes, **desligamos todas as fontes independentes**. As fontes dependentes não devem ser desligadas, pois elas são controladas por variáveis de circuito. Aplicamos uma tensão vo aos terminais a e b, e determinamos a corrente resultante io. Então, RTh = vo/io. De forma alternativa, poderíamos inserir uma fonte de corrente io nos terminais a e b, como na Figura 4.25b, e encontrar a tensão entre os terminais vo. Chegamos novamente a RTh = vo/io. Qualquer um dos dois métodos leva ao mesmo resultado. Em ambos os métodos, podemos supor qualquer valor de vo e io. Poderíamos usar, por exemplo, vo = 1 V ou io = 1 A, ou até mesmo valores não especificados de vo ou io.

Muitas vezes, pode ocorrer de RTh assumir um valor negativo; nesse caso,
a resistência negativa (v = –iR) implica o fato de o circuito estar **fornecendo
energia.**

**Exemplo 4.8**

Determine o circuito equivalente de Thévenin do circuito mostrado na Figura 4.27,
à esquerda dos terminais a-b. Em seguida, determine a corrente através de RL = 6 Ω,
16 Ω e 36 Ω.

```python
print("Exemplo 4.8")

#Req1 = 4*12/(4 + 12) = 48/16 = 3
#Rth = 3 + 1 = 4

#transforma fonte 1 (tensao -> corrente)
#is1 = 32/4 = 8 A

#soma fontes = 8 + 2 = 10 A

#ix = 10*4/(4 + 12) = 40/16 = 5/2
#Vab = 12*(5/2) = 30 = Vth

Vth = 30
Rth = 4

Rl = 6
Il = Vth/(Rl + Rth)
print("Para RL = 6, Corrente:",Il,"A")

Rl = 16
Il = Vth/(Rl + Rth)
print("Para RL = 6, Corrente:",Il,"A")

Rl = 36
Il = Vth/(Rl + Rth)
print("Para RL = 6, Corrente:",Il,"A")
```

    Exemplo 4.8
    Para RL = 6, Corrente: 3.0 A
    Para RL = 6, Corrente: 1.5 A
    Para RL = 6, Corrente: 0.75 A

**Problema Prático 4.8**

Usando o teorema de Thévenin, determine o circuito equivalente à esquerda dos terminais do circuito da Figura 4.30. Em seguida, determine I.

```python
print("Problema Prático 4.8")

#Req1 = 6 + 6 = 12
#Rth = Req1*4/(Req1 + 4) = 48/16 = 3
Rth = 3

#Superposicao Vsource
#Vab1 = Vs*4/(4 + 6 + 6) = 12*4/16 = 3V

#Superposicao Csource
#Iab = Is*6/(4 + 6 + 6) = 2*6/16 = 3/4
#Vab2 = Iab*4 = 3V

#Vth = Vab1 + Vab2
Vth = 6

I = Vth/(Rth + 1)
print("Tensao Vth:",Vth,"V")
print("Resistencia Rth:",Rth)
print("Corrente I:",I,"A")
```

    Problema Prático 4.8
    Tensao Vth: 6 V
    Resistencia Rth: 3
    Corrente I: 1.5 A

**Exemplo 4.9**

Determine o equivalente de Thévenin do circuito da Figura 4.31.

```python
print("Exemplo 4.9")

import numpy as np

#Descobrir Rth - desliga fontes indep., nao se alteram fontes dep.
#Aplicar tensao vo arbitraria entre terminais a b
#vo = 1 V
#Analise de malhas

#-2Vx + 2(i1 - i2) = 0
    #Vx = i1 - i2
    #Vx = -4i2
    #i1 + 3i2 = 0
#-Vx + 2(i2 - i1) + 6(i2 - i3) = 0
    #2i2 - 2i1 + 6i2 - 6i3 = Vx
    #-3i1 + 9i2 - 6i3 = 0
    #-i1 + 3i2 - 2i3 = 0
#Vo + 6(i3 - i2) + 2i3 = 0
    #6i3 - 6i2 + 2i3 = -1
    #-6i2 + 8i3 = -1

coef = np.matrix("1 3 0;-1 3 -2;0 -6 8")
res = np.matrix("0;0;-1")
I = np.linalg.inv(coef)*res

#i3 = -i0
io = -I[2]
#Rth = Vo/io
Rth = 1/io
print("Resistencia Rth:",float(Rth))

#Descobrir Vth
#Analise de tensao em terminais a b
#Analise de Malhas

#i1 = 5 A
#-2Vx + 2(i2 - i3) = 0
    #Vx = i2 - i3
    #Vx = 4(5 - i3) = 20 - 4i3
    #i2 + 3i3 = 20
#4(i3 - 5) + 2(i3 - i2) + 6i3 = 0
    #4i3 +2i3 - 2i2 + 6i3 = 20
    #-2i2 + 12i3 = 20
    #-i2 + 6i3 = 10

coef = np.matrix("1 3;-1 6")
res = np.matrix("20;10")
I = np.linalg.inv(coef)*res
Vth = 6*I[1]

print("Tensão Vth:",float(Vth),"V")
```

    Exemplo 4.9
    Resistencia Rth: 6.0
    Tensão Vth: 20.0 V

**Problema Prático 4.9**

Determine o equivalente de Thévenin do circuito da Figura 4.34 à esquerda dos terminais.

```python
print("Problema Prático 4.9")

#Descobrir Rth
#Vo = 1V
#Analise Nodal

#i1 - Ix/2 = 0
    #v1/5 - Ix/2 = 0
    #Ix = (v1 - 1)/3
    #v1/5 - (v1 - 1)/6 = 0
    #v1/5 - v1/6 = -1/6
    #v1/30 = -1/6
    #v1 = -5
#Ix = (v1 - 1)/3 = -6/3 = -2 A
#i2 = 1/4 A
#io = -Ix + i2 = 9/4 A

#Rth = 1/(9/4) = 4/9
Rth = 4/9
print("Resistencia Rth:",Rth)

#Descobrir Vth
#Analise de Malhas
#-6 + 5i1 + 3Ix + 4Ix = 0
    #5i1 + 7Ix = 6
#3Ix/2 + i1 = Ix
    #Ix/2 + i1 = 0
    #2i1 + Ix = 0

coef = np.matrix("5 7;2 1")
res = np.matrix("6;0")
I = np.linalg.inv(coef)*res
Ix = float(I[1])
Vth = 4*Ix
print("Tensão Vth:",Vth,"V")
```

    Problema Prático 4.9
    Resistencia Rth: 0.4444444444444444
    Tensão Vth: 5.333333333333333 V

**Exemplo 4.10**

Determine o equivalente de Thévenin do circuito da Figura 4.35a nos terminais a-b.

```python
print("Exemplo 4.10")
#vab = -vo = -1 V
#i1 = 1/4 A
#ix = 1/2 A
#i0 = 2ix - ix - i1 = 1 - 1/2 - 1/4 = 1/4 A
#Rth = -1/(1/4) = -4
Rth = -4
print("Resistencia Rth:", Rth)
print("Tensao Vth:",0,"V")
```

    Exemplo 4.10
    Resistencia Rth: -4
    Tensao Vth: 0 V

**Problema Prático 4.10**

Obtenha o equivalente de Thévenin do circuito da Figura 4.36.

```python
print("Problema Prático 4.10")
#iab = 1 A
#-vx + 10i1 + 4vx + 15(i1 - iab) = 0
    #3vx + 25i1 - 15iab = 0
    #vx = -5i1
    #-15i1 + 25i1 = 15
    #10i1 = 15
    #i1 = 1,5 A = 3/2 A
#vx = -5i1 = -7,5 V = -15/2 V
#vdep = 4*vx = -30V

#vab = vo = 15(i1 - iab) = 15/2 = 7,5V
#Rth = vo/(-iab) = -7,5
Rth = -7.5
print("Tensao Vth:",0,"V")
print("Resistencia Rth",Rth)
```

    Problema Prático 4.10
    Tensao Vth: 0 V
    Resistencia Rth -7.5