# Homework 2
## BIOST 558 Spring 2020
### Juan Solorio

```python
# needed libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn import linear_model
import statsmodels.api as sm

%matplotlib inline
plt.style.use('ggplot')
```

### Exercise 1
#### Gradient Descent - Theory (part 1 answers)

We have the objective equation

\begin{equation}
F(\beta) = \frac{1}n\sum_{i=1}^n(y_i - x^T_i\beta)^2 + \lambda\|\beta\|^2_2
\end{equation}

or expanded:

\begin{equation}
F(\beta) = \frac{1}n\sum_{i=1}^n(y_i - \sum_{j=1}^dx_{ij}\beta_j)^2 + \lambda\sum_{j=1}^d\beta_j^2
\end{equation}

which we want to minimize with respect to $\beta$, meaning we want to take the derivative of $F(\beta)$ and set it equal to zero.

The derivative for n=1 and d=1 yields:
\begin{equation}
\frac{dF}{d\beta} = \frac{2}n(y - x\beta) + 2\lambda\beta
\end{equation}

In Matrix terms, $F(\beta)$ can be interpreted as:
\begin{equation}
F(\beta) = (Y - X\beta)^T(Y-X\beta) + \lambda\beta^T\beta
\end{equation}
\begin{equation}
= y^Ty - \beta^TX^Ty-y^TX\beta+\beta^TX^TX\beta+\lambda\beta^T\beta
\end{equation}
\begin{equation}
= y^Ty - \beta^TX^Ty-y^TX\beta+\beta^TX^TX\beta+\beta^T\lambda I\beta
\end{equation}
\begin{equation}
= y^Ty - 2\beta^TX^Ty + \beta^T(X^TX + \lambda I)\beta
\end{equation}

Taking the derivative of the matrix form of $F(\beta)$ leads us to the following
\end{equation}
\begin{equation}
\frac{d}{d\beta}= - 2X^Ty + 2(X^TX + \lambda I)\beta
\end{equation}

#### Gradient Descent - Algorithm Code

```python
# Load the data
hitters = pd.read_csv('https://raw.githubusercontent.com/selva86/datasets/master/Hitters.csv',
                      sep=',', header=0)
hitters = hitters.dropna()
```

```python
# Creating matrix X with the predictors and
# and vector y with the response
X = hitters.drop('Salary', axis=1)
X = pd.get_dummies(X, drop_first=True)
y = hitters.Salary
```

```python
# Dividing the data into train and test sets.
# By default,  it is a 75-25 split between train-test
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# Standarizing the data
X_train = np.array((X_train-X_train.mean())/(X_train.max()-X_train.min()))
y_train = np.array((y_train-y_train.mean())/(y_train.max()-y_train.min()))
```

```python
def betas(X,y,lamda=0.05):
    '''
    Function to calculate betas from the matrix solution
    '''
    I = np.identity(len(X[0]))
    return np.dot(np.linalg.inv(np.dot(X.T,X) + lamda*I),np.dot(X.T,y))
```

```python
def Fx_ridge(X,y,beta, lamda):
    '''
    # RSS = (y-XB).T(y-XB)+lamda*B.T*B
    This is the objective function for Ridge regression
    '''
    n = len(y)
    lin = y - (X @ beta)
    reg = lamda*(beta.T @ beta)
    return (1/n *(lin.T @ lin) + reg)
```

```python
def computegrad(X, y, beta,lamda):
    '''
    Function for the gradient of the ridge objective function
    '''
    n = len(X[1])
    I = np.identity(len(X_train[1]))
    lin = (X_train.T @ y_train)/n
    reg = ((X_train.T @ X_train)/n + lamda*I) @ beta
    return 2*(reg-lin)
```

```python
def graddescent(X,y,t,beta,lamda,max_iter=1000):
    '''
    Gradient descent function that takes in X,y,t,beta,lamda,max_iter
    returns list of betas, grandient, objective fx values
    '''
    grad = computegrad(X,y,beta,lamda)
    fx = Fx_ridge(X,y,beta,lamda)
    fx_vals = [fx]
    beta_vals = [beta]
    grad_vals = [np.linalg.norm(grad)]
    for i in range(max_iter):
        beta = beta - t*grad
        grad = computegrad(X,y,beta,lamda)
        fx = Fx_ridge(X,y,beta,lamda)
        beta_vals.append(beta)
        grad_vals.append(np.linalg.norm(grad))
        fx_vals.append(fx)
    return [beta_vals,grad_vals,fx_vals]

```

```python
def graddescent_eps(X,y,t,beta,lamda,eps,max_iter=1000):
    '''
    Gradient descent function that takes in X,y,t,beta,lamda,max_iter
    returns list of betas, grandient, objective fx values
    '''
    grad = computegrad(X,y,beta,lamda)
    fx = Fx_ridge(X,y,beta,lamda)
    fx_vals = [fx]
    beta_vals = [beta]
    grad_vals = [grad]
    iter_val = 0
    while np.linalg.norm(grad) > eps and iter_val < max_iter:
        beta = beta - t*grad
        grad = computegrad(X,y,beta,lamda)
        fx = Fx_ridge(X,y,beta,lamda)
        beta_vals.append(beta)
        grad_vals.append(grad)
        fx_vals.append(fx)
    return [beta_vals,grad_vals,fx_vals]

```

##### Checking the Gradient Descent and Objective Ridge Fx for $\lambda = -5.00$

```python
n=len(X_train[1])
beta = np.zeros(n)
t=0.05
lamda=-5

grad_desc_n5 = graddescent(X_train, y_train,t,beta,lamda)
ts = np.array(range(len(grad_desc_n5[1])))
plt.scatter(x=ts,y=np.array(grad_desc_n5[2]))
plt.show()
```

From the figure we can observe that with $\lambda < 0$, the Ridge objective function diverges to $-\infty$, which was to be expected given that it would cause the gradient descent to become more of a gradient 'ascent'.

##### Checking the Gradient Descent and Objective Ridge Fx for $\lambda = 0.05$

```python
n=len(X_train[1])
beta = np.zeros(n)
t=0.05
lamda=0.05

grad_desc_05 = graddescent(X_train, y_train,t,beta,lamda)
```

```python
df_fx = pd.DataFrame(grad_desc_05[2])
df_grad = pd.DataFrame(grad_desc_05[1])
df_beta = pd.DataFrame(grad_desc_05[0])
df_fx.plot(legend=False,figsize=(8,6),title='Ridge Objective Fx')
plt.xlabel("t")
plt.show()
```

```python
df_beta.plot(subplots=True,layout=(4,5),legend=False,figsize=(14,10), title=r"$\beta$ Individial Predictors' Gradient Descent")
plt.subplots_adjust(hspace=0.35)
plt.show()
```

As can be observed in the $"Ridge\ Objective\ Fx"$ chart and the $"\beta\ Individial\ Predictors'\ Gradient\ Descent"$ charts, for the $\lambda = 0.05$, we can see each of these values converging, in the case of the $\beta s$ converge to some number and for the $F(\beta)$ to about 0.032.

#### Comparing sklearn.linear_model.Ridge $\beta$ to those values from the defined functions

```python
# sklearn Ridge regression betas
ridge = linear_model.Ridge(alpha=0.05)
model = ridge.fit(X_train,y_train)
model.coef_
```

    array([-0.49423255,  0.61521257,  0.14279993, -0.12541456,  0.0084655 ,
            0.31010389, -0.08027074, -0.36881245,  0.56366317, -0.05996115,
            0.52130315,  0.05585199, -0.4589716 ,  0.14773404,  0.06993996,
           -0.08699545,  0.01999316, -0.0504822 , -0.00686063])

```python
# betas calculated through my betas function
B = betas(X_train,y_train)
B
```

    array([-0.49423255,  0.61521257,  0.14279993, -0.12541456,  0.0084655 ,
            0.31010389, -0.08027074, -0.36881245,  0.56366317, -0.05996115,
            0.52130315,  0.05585199, -0.4589716 ,  0.14773404,  0.06993996,
           -0.08699545,  0.01999316, -0.0504822 , -0.00686063])

```python
print("sklearn: %f \t Vectorize Ridge: %f \t Grad Desc: %f"%(
    Fx_ridge(X_train,y_train,model.coef_,lamda),Fx_ridge(X_train,y_train,B,lamda),np.array(grad_desc_05[2])[-1]))
```

    sklearn: 0.107533 	 Vectorize Ridge: 0.107533 	 Grad Desc: 0.031999

The values calculated by the vectorize Ridge function are quite close to those from the sklearn Ridge function.
However the values calculated by my gradient descent were lower than these two previous values.

#### Adding a stopping criteria $\|\nabla F(\beta)\| <$ $\epsilon = 0.005$

```python
n=len(X_train[1])
beta = np.zeros(n)
t=0.2
lamda=0.05
eps = 0.005
grad_desc_eps = graddescent_eps(X_train, y_train,t,beta,lamda,eps)

df_fx = pd.DataFrame(grad_desc_eps[2])
df_grad = pd.DataFrame(grad_desc_eps[1])
df_fx.plot(legend=False,figsize=(8,6),title='Ridge Objective Fx')
plt.xlabel("t")
plt.show()
```

The new criteria allows for the gradient descent process to stop sooner as it converges without the need to continue iterating.

### Exercise 2

```python
# (a) Read in the dataset. The data can be downloaded from this url: http://www-bcf.usc.
# edu/~gareth/ISL/Auto.csv When reading in the data use the option na values=’?’.
# Then drop all NaN values using dropna().

# Load the data
Auto = pd.read_csv("http://faculty.marshall.usc.edu/gareth-james/ISL/Auto.csv",
                      sep=',', header=0, na_values='?')
Auto = Auto.dropna()
Auto['constant']=1
```

```python
# (b) Use the OLS function from the statsmodels package to perform a simple linear regression with mpg as the response and weight as the predictor. Be sure to include an
# intercept. Use the summary() attribute to print the results.

X = Auto.loc[:,['constant','weight']]
Y = Auto.mpg
lm_mpg_wgt = sm.OLS(Y,X)

lm_mpg_wgt_model = lm_mpg_wgt.fit()
print(lm_mpg_wgt_model.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                    mpg   R-squared:                       0.693
    Model:                            OLS   Adj. R-squared:                  0.692
    Method:                 Least Squares   F-statistic:                     878.8
    Date:                Fri, 17 Apr 2020   Prob (F-statistic):          6.02e-102
    Time:                        20:56:37   Log-Likelihood:                -1130.0
    No. Observations:                 392   AIC:                             2264.
    Df Residuals:                     390   BIC:                             2272.
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    constant      46.2165      0.799     57.867      0.000      44.646      47.787
    weight        -0.0076      0.000    -29.645      0.000      -0.008      -0.007
    ==============================================================================
    Omnibus:                       41.682   Durbin-Watson:                   0.808
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):               60.039
    Skew:                           0.727   Prob(JB):                     9.18e-14
    Kurtosis:                       4.251   Cond. No.                     1.13e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 1.13e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

- i) From the summary statistics we can observe there is a strong relation between Weight and mpg.

- ii) From the summary we see that the p-value suggest a very strong statistical significance for the linear relation between MPG and Weight. With a correlation of -0.83

- iii) The relationship between MPG and Weight is negative.

```python
fig, ax = plt.subplots()
fig = sm.graphics.plot_fit(lm_mpg_wgt_model, 1, ax=ax)
ax.set_xlabel("Weight")
ax.set_ylabel("mpg")
ax.set_title("Linear Regression")
plt.show()
```

```python
# (d) Plot the residuals vs. fitted values. Comment on any problems you see with the fit.
fig, ax = plt.subplots()
lm_mpg_wgt_fitted = lm_mpg_wgt_model.fittedvalues
lm_mpg_wgt_res = lm_mpg_wgt_model.resid
fig = sns.residplot(lm_mpg_wgt_fitted,Auto.columns[0],data=Auto,
             lowess=True,
             scatter_kws={'alpha':0.5},
             line_kws={'color': 'red', 'lw': 1, 'alpha': 0.8})
ax.set_xlabel("Fitted")
ax.set_ylabel("Residuals")
ax.set_title("Residuals vs Fitted")
plt.show()
```

The red lines across the data shows a "bow-shape" or curve which indicates we are not capturing some of the non-linear aspects of the model.

### Exercise 3

```python
# (a) Produce a scatterplot matrix which includes all of the variables in the data set using
# pandas.plotting.scatter matrix
pd.plotting.scatter_matrix(Auto, alpha=0.5, figsize=(14,14))
plt.show()
```

```python
# (b) Compute the matrix of correlations between the variables using the corr() attribute
# in Pandas.

corrMatrix = Auto.corr()
sns.heatmap(corrMatrix,annot=True)
plt.show()
```

```python
# (c) Use the OLS function from the statsmodels package to perform a multiple linear
# regression with mpg as the response and all other variables except name as the predictors.
# Be sure to include an intercept.
Xmlt = Auto.loc[:,['constant','cylinders','displacement','horsepower','weight','acceleration','year','origin']]
lm_mpg_mlt = sm.OLS(Y,Xmlt)

lm_mpg_mlt_model = lm_mpg_mlt.fit()
print(lm_mpg_mlt_model.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                    mpg   R-squared:                       0.821
    Model:                            OLS   Adj. R-squared:                  0.818
    Method:                 Least Squares   F-statistic:                     252.4
    Date:                Fri, 17 Apr 2020   Prob (F-statistic):          2.04e-139
    Time:                        20:57:02   Log-Likelihood:                -1023.5
    No. Observations:                 392   AIC:                             2063.
    Df Residuals:                     384   BIC:                             2095.
    Df Model:                           7
    Covariance Type:            nonrobust
    ================================================================================
                       coef    std err          t      P>|t|      [0.025      0.975]
    --------------------------------------------------------------------------------
    constant       -17.2184      4.644     -3.707      0.000     -26.350      -8.087
    cylinders       -0.4934      0.323     -1.526      0.128      -1.129       0.142
    displacement     0.0199      0.008      2.647      0.008       0.005       0.035
    horsepower      -0.0170      0.014     -1.230      0.220      -0.044       0.010
    weight          -0.0065      0.001     -9.929      0.000      -0.008      -0.005
    acceleration     0.0806      0.099      0.815      0.415      -0.114       0.275
    year             0.7508      0.051     14.729      0.000       0.651       0.851
    origin           1.4261      0.278      5.127      0.000       0.879       1.973
    ==============================================================================
    Omnibus:                       31.906   Durbin-Watson:                   1.309
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):               53.100
    Skew:                           0.529   Prob(JB):                     2.95e-12
    Kurtosis:                       4.460   Cond. No.                     8.59e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 8.59e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

```python
# (d) Plot the residuals vs. fitted values. Comment on any problems you see with the fit.
fig, ax = plt.subplots()
lm_mpg_mlt_fitted = lm_mpg_mlt_model.fittedvalues
lm_mpg_mlt_res = lm_mpg_mlt_model.resid
fig = sns.residplot(lm_mpg_mlt_fitted,Auto.columns[0],data=Auto,
             lowess=True,
             scatter_kws={'alpha':0.5},
             line_kws={'color': 'red', 'lw': 1, 'alpha': 0.8})
ax.set_xlabel("Fitted")
ax.set_ylabel("Residuals")
ax.set_title("Residuals vs Fitted")
plt.show()

```

Even from the multivariate model, the Residuals vs Fitted plot is still showing some aspects of the model that are not accounted for. This might mean we could be overlooking some sort of transformation to possibly allow for a better fit.

```python
# (e) Statsmodels allows you to fit models using R-style formulas. See http://www.statsmodels.
# org/dev/example_formulas.html. Use the * and : symbols to fit linear regression
# models with interaction effects. Do any interactions appear to be statistically significant?

import statsmodels.formula.api as smf
Auto_x = Auto.drop(['name','constant'],axis=1)
lm_mpg_mult = smf.ols(formula = 'mpg ~ cylinders * displacement * horsepower * weight * acceleration * year * origin', data=Auto_x)
res_mult = lm_mpg_mult.fit()
print(res_mult.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                    mpg   R-squared:                       0.931
    Model:                            OLS   Adj. R-squared:                  0.906
    Method:                 Least Squares   F-statistic:                     38.51
    Date:                Fri, 17 Apr 2020   Prob (F-statistic):          1.49e-123
    Time:                        20:57:10   Log-Likelihood:                -838.23
    No. Observations:                 392   AIC:                             1880.
    Df Residuals:                     290   BIC:                             2286.
    Df Model:                         101
    Covariance Type:            nonrobust
    =====================================================================================================================================
                                                                            coef    std err          t      P>|t|      [0.025      0.975]
    -------------------------------------------------------------------------------------------------------------------------------------
    Intercept                                                            -0.0172      0.395     -0.043      0.965      -0.795       0.761
    cylinders                                                            -0.9849      3.107     -0.317      0.751      -7.100       5.130
    displacement                                                         15.6423     26.104      0.599      0.549     -35.735      67.019
    cylinders:displacement                                                9.9087      7.405      1.338      0.182      -4.666      24.483
    horsepower                                                            3.4947     23.826      0.147      0.883     -43.399      50.388
    cylinders:horsepower                                                  0.3706      8.546      0.043      0.965     -16.450      17.191
    displacement:horsepower                                              -0.2288      0.317     -0.722      0.471      -0.852       0.395
    cylinders:displacement:horsepower                                    -0.1061      0.094     -1.128      0.260      -0.291       0.079
    weight                                                               -0.5506      0.852     -0.647      0.518      -2.227       1.125
    cylinders:weight                                                     -0.3717      0.284     -1.309      0.192      -0.931       0.187
    displacement:weight                                                  -0.0100      0.022     -0.455      0.649      -0.053       0.033
    cylinders:displacement:weight                                         0.0016      0.004      0.389      0.698      -0.007       0.010
    horsepower:weight                                                     0.0024      0.011      0.224      0.823      -0.019       0.024
    cylinders:horsepower:weight                                           0.0040      0.004      1.084      0.279      -0.003       0.011
    displacement:horsepower:weight                                    -1.781e-05      0.000     -0.081      0.935      -0.000       0.000
    cylinders:displacement:horsepower:weight                           2.426e-05   4.09e-05      0.593      0.553   -5.62e-05       0.000
    acceleration                                                        -24.8380     46.061     -0.539      0.590    -115.494      65.818
    cylinders:acceleration                                                2.7590     24.784      0.111      0.911     -46.020      51.538
    displacement:acceleration                                            -1.0383      1.458     -0.712      0.477      -3.908       1.832
    cylinders:displacement:acceleration                                  -0.7078      0.511     -1.384      0.167      -1.714       0.299
    horsepower:acceleration                                               0.0149      1.697      0.009      0.993      -3.326       3.355
    cylinders:horsepower:acceleration                                    -0.1210      0.636     -0.190      0.849      -1.373       1.131
    displacement:horsepower:acceleration                                  0.0271      0.024      1.153      0.250      -0.019       0.073
    cylinders:displacement:horsepower:acceleration                        0.0049      0.006      0.767      0.444      -0.008       0.018
    weight:acceleration                                                   0.0331      0.062      0.534      0.594      -0.089       0.155
    cylinders:weight:acceleration                                         0.0279      0.018      1.549      0.123      -0.008       0.063
    displacement:weight:acceleration                                     -0.0004      0.001     -0.275      0.784      -0.003       0.002
    cylinders:displacement:weight:acceleration                            0.0002      0.000      0.759      0.449      -0.000       0.001
    horsepower:weight:acceleration                                     7.089e-05      0.001      0.083      0.934      -0.002       0.002
    cylinders:horsepower:weight:acceleration                             -0.0003      0.000     -1.293      0.197      -0.001       0.000
    displacement:horsepower:weight:acceleration                        5.365e-06   1.68e-05      0.319      0.750   -2.77e-05    3.85e-05
    cylinders:displacement:horsepower:weight:acceleration             -2.933e-06   3.04e-06     -0.965      0.335   -8.91e-06    3.05e-06
    year                                                                 -7.7709      5.452     -1.425      0.155     -18.502       2.960
    cylinders:year                                                       -2.2045      1.821     -1.210      0.227      -5.789       1.380
    displacement:year                                                    -0.1631      0.344     -0.473      0.636      -0.841       0.515
    cylinders:displacement:year                                          -0.1110      0.097     -1.140      0.255      -0.303       0.081
    horsepower:year                                                       0.0346      0.322      0.107      0.915      -0.600       0.669
    cylinders:horsepower:year                                             0.0331      0.121      0.275      0.784      -0.204       0.270
    displacement:horsepower:year                                          0.0018      0.004      0.411      0.681      -0.007       0.010
    cylinders:displacement:horsepower:year                                0.0013      0.001      1.038      0.300      -0.001       0.004
    weight:year                                                           0.0100      0.015      0.674      0.501      -0.019       0.039
    cylinders:weight:year                                                 0.0061      0.004      1.670      0.096      -0.001       0.013
    displacement:weight:year                                           7.838e-05      0.000      0.297      0.767      -0.000       0.001
    cylinders:displacement:weight:year                                -2.061e-05   4.85e-05     -0.425      0.671      -0.000    7.49e-05
    horsepower:weight:year                                            -2.278e-05      0.000     -0.142      0.887      -0.000       0.000
    cylinders:horsepower:weight:year                                   -8.25e-05   4.81e-05     -1.715      0.087      -0.000    1.22e-05
    displacement:horsepower:weight:year                                7.245e-07   2.65e-06      0.274      0.784   -4.48e-06    5.93e-06
    cylinders:displacement:horsepower:weight:year                     -2.609e-07   4.95e-07     -0.527      0.598   -1.23e-06    7.13e-07
    acceleration:year                                                     0.8048      0.649      1.240      0.216      -0.472       2.082
    cylinders:acceleration:year                                           0.1015      0.330      0.308      0.759      -0.547       0.750
    displacement:acceleration:year                                        0.0271      0.021      1.284      0.200      -0.014       0.069
    cylinders:displacement:acceleration:year                              0.0042      0.007      0.628      0.531      -0.009       0.017
    horsepower:acceleration:year                                         -0.0057      0.024     -0.236      0.813      -0.053       0.041
    cylinders:horsepower:acceleration:year                               -0.0005      0.009     -0.055      0.956      -0.018       0.017
    displacement:horsepower:acceleration:year                            -0.0004      0.000     -1.253      0.211      -0.001       0.000
    cylinders:displacement:horsepower:acceleration:year               -2.828e-05   8.87e-05     -0.319      0.750      -0.000       0.000
    weight:acceleration:year                                             -0.0008      0.001     -0.799      0.425      -0.003       0.001
    cylinders:weight:acceleration:year                                   -0.0004      0.000     -1.703      0.090      -0.001    6.22e-05
    displacement:weight:acceleration:year                              3.713e-06    1.6e-05      0.232      0.817   -2.78e-05    3.53e-05
    cylinders:displacement:weight:acceleration:year                   -1.157e-06   2.54e-06     -0.456      0.649   -6.15e-06    3.84e-06
    horsepower:weight:acceleration:year                                9.533e-08   1.19e-05      0.008      0.994   -2.32e-05    2.34e-05
    cylinders:horsepower:weight:acceleration:year                      5.662e-06   3.23e-06      1.751      0.081   -7.03e-07     1.2e-05
    displacement:horsepower:weight:acceleration:year                  -6.705e-08   2.04e-07     -0.328      0.743   -4.69e-07    3.35e-07
    cylinders:displacement:horsepower:weight:acceleration:year         2.691e-08   3.69e-08      0.728      0.467   -4.58e-08    9.96e-08
    origin                                                               -1.6028      3.091     -0.518      0.605      -7.687       4.481
    cylinders:origin                                                     -0.7671      2.486     -0.309      0.758      -5.660       4.126
    displacement:origin                                                  10.2550     21.860      0.469      0.639     -32.769      53.279
    cylinders:displacement:origin                                       -11.6548     12.294     -0.948      0.344     -35.851      12.542
    horsepower:origin                                                     2.9976     21.387      0.140      0.889     -39.095      45.090
    cylinders:horsepower:origin                                          -1.6308     11.010     -0.148      0.882     -23.300      20.039
    displacement:horsepower:origin                                       -0.1666      0.266     -0.626      0.532      -0.690       0.357
    cylinders:displacement:horsepower:origin                              0.1478      0.152      0.975      0.330      -0.151       0.446
    weight:origin                                                        -0.3470      0.728     -0.477      0.634      -1.779       1.085
    cylinders:weight:origin                                               0.4401      0.414      1.063      0.289      -0.375       1.255
    displacement:weight:origin                                            0.0092      0.020      0.461      0.645      -0.030       0.048
    cylinders:displacement:weight:origin                                 -0.0017      0.005     -0.366      0.714      -0.011       0.008
    horsepower:weight:origin                                              0.0054      0.013      0.428      0.669      -0.019       0.030
    cylinders:horsepower:weight:origin                                   -0.0046      0.006     -0.830      0.407      -0.015       0.006
    displacement:horsepower:weight:origin                              6.378e-05      0.000      0.321      0.748      -0.000       0.000
    cylinders:displacement:horsepower:weight:origin                   -2.918e-05    4.8e-05     -0.608      0.544      -0.000    6.53e-05
    acceleration:origin                                                 -23.8165     44.972     -0.530      0.597    -112.329      64.696
    cylinders:acceleration:origin                                         6.8451     20.638      0.332      0.740     -33.774      47.464
    displacement:acceleration:origin                                     -0.6671      1.193     -0.559      0.576      -3.015       1.680
    cylinders:displacement:acceleration:origin                            0.7792      0.746      1.044      0.297      -0.690       2.248
    horsepower:acceleration:origin                                        0.0620      1.564      0.040      0.968      -3.016       3.140
    cylinders:horsepower:acceleration:origin                              0.0674      0.738      0.091      0.927      -1.384       1.519
    displacement:horsepower:acceleration:origin                           0.0011      0.017      0.065      0.948      -0.033       0.035
    cylinders:displacement:horsepower:acceleration:origin                -0.0075      0.009     -0.818      0.414      -0.026       0.011
    weight:acceleration:origin                                            0.0212      0.055      0.385      0.701      -0.087       0.130
    cylinders:weight:acceleration:origin                                 -0.0292      0.028     -1.032      0.303      -0.085       0.026
    displacement:weight:acceleration:origin                               0.0005      0.001      0.395      0.693      -0.002       0.003
    cylinders:displacement:weight:acceleration:origin                    -0.0002      0.000     -0.661      0.509      -0.001       0.000
    horsepower:weight:acceleration:origin                                -0.0006      0.001     -0.636      0.525      -0.002       0.001
    cylinders:horsepower:weight:acceleration:origin                       0.0004      0.000      0.929      0.354      -0.000       0.001
    displacement:horsepower:weight:acceleration:origin                -9.374e-06   1.57e-05     -0.596      0.552   -4.03e-05    2.16e-05
    cylinders:displacement:horsepower:weight:acceleration:origin       3.341e-06   3.28e-06      1.019      0.309   -3.11e-06    9.79e-06
    year:origin                                                          -6.1203      4.912     -1.246      0.214     -15.789       3.548
    cylinders:year:origin                                                 4.3981      2.511      1.752      0.081      -0.544       9.340
    displacement:year:origin                                             -0.1078      0.289     -0.373      0.709      -0.677       0.461
    cylinders:displacement:year:origin                                    0.1233      0.162      0.761      0.447      -0.196       0.442
    horsepower:year:origin                                                0.0188      0.289      0.065      0.948      -0.551       0.588
    cylinders:horsepower:year:origin                                     -0.0343      0.153     -0.224      0.823      -0.335       0.267
    displacement:horsepower:year:origin                                   0.0028      0.004      0.792      0.429      -0.004       0.010
    cylinders:displacement:horsepower:year:origin                        -0.0018      0.002     -0.879      0.380      -0.006       0.002
    weight:year:origin                                                    0.0076      0.011      0.699      0.485      -0.014       0.029
    cylinders:weight:year:origin                                         -0.0079      0.005     -1.559      0.120      -0.018       0.002
    displacement:weight:year:origin                                   -9.245e-05      0.000     -0.397      0.692      -0.001       0.000
    cylinders:displacement:weight:year:origin                          2.569e-05   5.68e-05      0.452      0.651   -8.61e-05       0.000
    horsepower:weight:year:origin                                        -0.0001      0.000     -0.995      0.321      -0.000       0.000
    cylinders:horsepower:weight:year:origin                            9.742e-05   6.83e-05      1.427      0.155   -3.69e-05       0.000
    displacement:horsepower:weight:year:origin                        -1.063e-06   2.32e-06     -0.457      0.648   -5.64e-06    3.51e-06
    cylinders:displacement:horsepower:weight:year:origin               2.911e-07   5.94e-07      0.490      0.625   -8.78e-07    1.46e-06
    acceleration:year:origin                                              0.6899      0.627      1.101      0.272      -0.544       1.923
    cylinders:acceleration:year:origin                                   -0.3570      0.283     -1.263      0.208      -0.914       0.200
    displacement:acceleration:year:origin                                -0.0086      0.018     -0.489      0.625      -0.043       0.026
    cylinders:displacement:acceleration:year:origin                      -0.0045      0.010     -0.457      0.648      -0.024       0.015
    horsepower:acceleration:year:origin                                  -0.0030      0.020     -0.151      0.880      -0.043       0.037
    cylinders:horsepower:acceleration:year:origin                         0.0022      0.010      0.218      0.827      -0.018       0.022
    displacement:horsepower:acceleration:year:origin                   6.517e-05      0.000      0.272      0.786      -0.000       0.001
    cylinders:displacement:horsepower:acceleration:year:origin         5.796e-05      0.000      0.464      0.643      -0.000       0.000
    weight:acceleration:year:origin                                      -0.0003      0.001     -0.400      0.689      -0.002       0.001
    cylinders:weight:acceleration:year:origin                             0.0005      0.000      1.353      0.177      -0.000       0.001
    displacement:weight:acceleration:year:origin                      -3.585e-06    1.4e-05     -0.255      0.799   -3.12e-05     2.4e-05
    cylinders:displacement:weight:acceleration:year:origin             9.195e-07      3e-06      0.306      0.760   -4.99e-06    6.83e-06
    horsepower:weight:acceleration:year:origin                         1.063e-05   1.09e-05      0.978      0.329   -1.08e-05     3.2e-05
    cylinders:horsepower:weight:acceleration:year:origin              -6.546e-06   4.75e-06     -1.377      0.170   -1.59e-05    2.81e-06
    displacement:horsepower:weight:acceleration:year:origin            1.052e-07   1.88e-07      0.560      0.576   -2.65e-07    4.75e-07
    cylinders:displacement:horsepower:weight:acceleration:year:origin -3.037e-08   4.04e-08     -0.751      0.453    -1.1e-07    4.92e-08
    ==============================================================================
    Omnibus:                       45.623   Durbin-Watson:                   1.752
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):              129.382
    Skew:                           0.532   Prob(JB):                     8.04e-29
    Kurtosis:                       5.605   Cond. No.                     3.52e+16
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 3.52e+16. This might indicate that there are
    strong multicollinearity or other numerical problems.

```python
# (f) Try a few different transformations of the variables, such as log(X), 1/X,
# sqrt(X), X2. Comment on your findings.
Auto_log = np.log(Auto_x.drop(['year','origin'],axis=1))
Auto_inv = 1/Auto_x.drop(['year','origin'],axis=1)
Auto_sqrt= np.sqrt(Auto_x.drop(['year','origin'],axis=1))

```

```python
lm_log = smf.ols(formula = 'mpg ~ cylinders * displacement * horsepower * weight * acceleration', data=Auto_log)
lm_log_model = lm_log.fit()
print(lm_log_model.summary())

fig, ax = plt.subplots()
lm_log_fitted = lm_log_model.fittedvalues
lm_log_res = lm_log_model.resid
fig = sns.residplot(lm_log_fitted,Auto_log.columns[0], data=Auto_log,
             lowess=True,
             scatter_kws={'alpha':0.5},
             line_kws={'color': 'red', 'lw': 1, 'alpha': 0.8})
ax.set_xlabel("Fitted")
ax.set_ylabel("Residuals")
ax.set_title("Residuals vs Fitted")
plt.show()
```

By conducting a Log transformation on the data, we can see that the residuals now present a much better fit for the Fitted values. We even see from the $p-values$ that each predictor and the interactions show statistical significance in their relation to mpg.


```python
import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import itertools
import scipy.special
from decimal import *
```

```python
import sys
sys.version
```

    '3.7.1 (default, Nov  7 2018, 15:44:27) \n[GCC 6.3.0 20170516]'

# In-band P2W attack evaluation

We base the attack evaluation of the in-band variant on the evaluation in the whale attack paper.
Therefore, we start by looking at their evaluation in detail.

## 1. Reproduce whale attack to estimate tx exclusion attack costs i.e., revert one block

According to the [Whale attack paper](https://www.cs.umd.edu/~jkatz/papers/whale-txs.pdf), the value for the additional bribing cost $ \epsilon $ (in the original paper denoted as $ \delta $) i.e., the bribe, is estimated as follows.

* Let $ \alpha $ be the hashrate of the attacker that works on the attack branch unconditionally
* Let $ \beta $ be the hashrate of the other miners, some of which beeing rational i.e., $ \alpha + \beta = 1 $
* Let $ \beta_m $ be the hashrate of an individual miner which has to decide if he should follow the attack branch or not
* Let $ z $ be the number of blocks that attack chain is behind the attack chain when the race phase starts

Expected reward for $ \beta_m $ if **not joining** the attack, for **one new** block on the main chain if *attack fails*. Value in number of block rewards (normalized to $ 1 $):
$$
\begin{align}
  \frac{ \big( 1 - \big(
                        \frac{\alpha}
                             {\beta}
                   \big)^{z+1}
         \big) \cdot \beta_m}
       {\beta}
\end{align}
$$

Expected reward for $ \beta_m $ if **joining** the attack, for **one new** block on the attack chain if *attack succeeds*. Value in number of block rewards (normalized to $ 1 $):
\begin{align}
  \frac{ \big( \frac{\alpha + \beta_m}
                    {\beta - \beta_m}
         \big)^{z+1} \cdot \beta_m}
       {\alpha + \beta_m}
       \cdot ( \epsilon + 1 )
\end{align}

Solved for $ \epsilon $:
$$
\begin{align}
  \epsilon > \frac{ \big( 1 - \big(\frac{\alpha}{\beta}\big)^{z+1} \big)}
                  {\beta}
             \cdot
             \frac{ \beta + \beta_m}
                  { \big(\frac{\alpha + \beta_m}{\beta - \beta_m}\big)^2}
             -1
\end{align}
$$

```python
def whale_attack_bribe(alpha,beta_m,z,beta=None):
    if beta is None:
        beta = 1 - alpha
        #beta = 1 - (alpha + beta_m)
    return ( (1-(alpha/beta)**(z+1))/beta ) * ( (alpha + beta_m)/ ( (alpha+beta_m)/(beta - beta_m) )**(z+1) ) - 1
```

### Compare values with paper
See table 1 in [Whale attack paper](https://www.cs.umd.edu/~jkatz/papers/whale-txs.pdf) for values

```python
assert math.isclose(whale_attack_bribe(alpha=0.1,beta_m=0.05,z=6.),31270.040)
assert round(whale_attack_bribe(alpha=0.05,beta_m=0,z=1),2) == 17.95
assert round(whale_attack_bribe(alpha=0.05,beta_m=0.05,z=1),2) == 7.5
```

```python
t1_alpha=0.15
t1_beta_m=0.10
t1_beta= 1 - t1_alpha
assert round(whale_attack_bribe(t1_alpha,beta_m=t1_beta_m,z=1,beta=t1_beta),2) == 1.56
```

```python
t1_alpha=0.15
t1_beta_m=0.10
t1_beta= 1 - (t1_alpha + t1_beta_m)
round(whale_attack_bribe(t1_alpha,beta_m=t1_beta_m,z=1,beta=t1_beta),2)
```

    1.16

```python
whale_attack_bribe(alpha=0.2,beta_m=0.05,z=6) # compare with table 682.4
```

    682.3957862854006

```python
whale_attack_bribe(alpha=0.05,beta_m=0.05,z=1) # compare with 7.5
```

    7.502697186178741

```python
whale_attack_bribe(alpha=0.05,beta_m=0.05,z=1) # compare with 7.5
```

    7.502697186178741

### Provide some more examples

```python
whale_attack_bribe(alpha=0.05,beta_m=0,z=1) # small potential bribeable attacker hashrate, no one joins
```

    17.94736842105263

```python
whale_attack_bribe(alpha=0,beta_m=0.05,z=1)
```

    17.049999999999997

```python
whale_attack_bribe(alpha=0,beta_m=0.1,z=1)
```

    7.1

```python
whale_attack_bribe(alpha=0,beta_m=0.15,z=1)
```

    3.8166666666666655

```python
whale_attack_bribe(alpha=0,beta_m=0.2,z=1)
```

    2.2

```python
whale_attack_bribe(alpha=0.3,beta_m=0.03,z=1)
```

    0.5863592190122804

### Detour

```python
whale_attack_bribe(alpha=0,beta_m=0.382,z=1) # 38.2 % it is feasable without any additional reward
```

    -0.00019895287958116015

Interestingly the bribe gets negative, i.e., it is more profitable trying to catch-up than switching the chain, if you are just one block behind hand have more than $ 38.196601125010515 \%$ of the hashrate.
Which is exactly:
$$
\frac{1}{\phi^2}
$$

```python
phi = (1 + 5 ** 0.5) / 2
phi
```

    1.618033988749895

```python
(1/phi**2)
```

    0.38196601125010515

```python
whale_attack_bribe(alpha=0,beta_m=0.3819660112501051,z=1) # small bribe needed
```

    4.440892098500626e-16

```python
whale_attack_bribe(alpha=0,beta_m=0.3819660112501052,z=1) # no bribe needed
```

    -4.440892098500626e-16

$ 38.196601125010515 \%$ is also the threshold when the probability to catch-up one block becomes greater than the hashrate:

General catch up condition $ a_z $ from:
* https://arxiv.org/pdf/1402.2009.pdf
* https://bitcoin.org/bitcoin.pdf

Classical parameter names:
* $p$ = hashrate of the honest network
* $q$ = hashrate of the attacker
$$
p + q = 1
$$
* $n$ = number of confirmation blocks, including and on top of the block which includes the respective transaction which needs to be confirmed or double-spend respectively

The probability to **ever** catch-up if $ z $ blocks behind, where one block is added to $z$ when the honest miners find a block and one block is substracted from $ z $ when the attacker finds a block i.e.,

The catch-up probability is a simplification of the *recurrance relation*, where $ q = \alpha $ and $ p = \beta $, given as:
$$
\begin{align}
    a_z &= p \cdot a_{z+1} + q \cdot a_{z-1} \\
        &= min(q/q,1)^{max(z+1,0)}
\end{align}
$$

```python
def catch_up(q,z):
    if q >= 0.5:
        return 1.0
    return ( q/(1-q) )**(z+1)
```

```python
catch_up(0.38,1)
```

    0.3756503642039542

```python
catch_up(q=0.381966011250105,z=1) # success probability lower than hashrate
```

    0.38196601125010465

```python
catch_up(q=0.38196601125010515,z=1) # success probability equal to hashrate
```

    0.3819660112501051

```python
catch_up(q=0.3819660112501052,z=1) # success probability higher than hashrate
```

    0.3819660112501054

### Visualize the barrier when the bribe gets negative compared to hashrate

```python
def fig_bribing_costs_tx_exclusion():
    fig, ax = plt.subplots(figsize=(16, 9))

    # https://matplotlib.org/api/markers_api.html
    marker = itertools.cycle((',','v', '+', 's','.', 'o', '*','1','D','x','^'))

    alpha   = [ 0.00, 0.01, 0.05, 0.1, 0.15, 0.2, 0.3,  ]
    beta_m = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.382 ]
    z = 1

    for o in alpha:
        plt.plot(beta_m, [ whale_attack_bribe(alpha=o,beta_m=o_m,z=z) for o_m in beta_m ] , marker=next(marker), label=" $ \\beta - \\beta_m$=" + str(o))

    # tidy up the figure
    ax.grid(True)
    #ax.legend(loc='center right', bbox_to_anchor=(0.8, 0.57))
    ax.legend(loc='center right', bbox_to_anchor=(0.90, 0.57))
    #ax.set_title("Attack success probability")
    ax.set_xlabel("Hash rate of potentially bribable miner $ \\beta_m $")
    ax.set_ylabel("Attack costs in times of block reward")
    ax.set_ylim([-1, 15])
    ax.set_xlim([0,0.4])

    # draw line at 0
    #plt.plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
    plt.plot([0, 100], [0, 0], color='k', linestyle='--', linewidth=2)

    plt.yticks(np.arange(-1, 15, step=1))

    #plt.yscale('log')
    plt.rcParams.update({'font.size': 17})
    plt.rc('xtick', labelsize=17)
    plt.rc('ytick', labelsize=17)
    #plt.savefig("figures/tx_exclusion_costs_z=1.png", dpi=100) # sage image
    plt.show()

fig_bribing_costs_tx_exclusion()
```

## 2. Adapt whale attack to fit our model

### Rename variable with respect to our Nomenclature

We can adapt the whale attack to fit our model for (in-band) tx exclusion.
The whale attack rewritten in our Nomenclature looks as follows:

* Let $ \alpha = 0 $ be the hashrate of the attacker.
* Let $ \omega_\alpha $ be the estimated hashrate of the other bribable miners which are assumed to work on the attacker branch
* Let $ \beta $ be the hashrate of honest miners which will never accept a bribe to work on the attacker branch - but they will work on the attack branch if it becomes the longest branch.
* Let $ \omega_m $ be the hashrate of an individual rational/bribable miner which has to decide if he should follow the attack branch or not $ \omega_m + \omega_\alpha + \alpha + \beta = 1 $
* Let $ z $ be the number of blocks that attack chain is behind the attack chain when the race phase starts.
In our case $ z = 1 $

Expected reward in number of block rewards (normalized to $ 1 $) for **one new** block on the main chain if *attack fails*:
$$
\begin{align}
  \frac{ \big( 1 - \big(
                        \frac{\alpha + \omega_\alpha}
                             {\beta + \omega_m}
                   \big)^{z+1}
         \big) \cdot \omega_m}
       {\beta + \omega_m}
\end{align}
$$

Expected reward in number of block rewards (normalized to $ 1 $) for **one new** block on the attack chain if *attack succeeds*:
$$
\begin{align}
  \frac{ \big( \frac{\alpha + \omega_\alpha + \omega_m}
                    {\beta}
         \big)^{z+1} \cdot \omega_m}
       {\alpha + \omega_\alpha + \omega_m}
       \cdot ( \epsilon + 1 )
\end{align}
$$

The equations for the expected reward if the attack succeeds has to be larger than the equation for the expected rewards when the attack fails. Solve this inequality for $ \epsilon $:
$$
\begin{align}
  \epsilon > \frac{ \big( 1 - \big(\frac{\alpha + \omega_\alpha}{\beta + \omega_\beta + \omega_m}\big)^{z+1} \big)}
                  {\beta + \omega_m}
             \cdot
             \frac{ \alpha + \omega_\alpha + \omega_m}
                  { \big(\frac{\alpha + \omega_\alpha + \omega_m}{\beta + \omega_\beta}\big)^2}
             -1
\end{align}
$$

```python
def txexclusion_attack_bribe(omega_a,omega_m,alpha=0,beta=None,z=1):
    if beta is None:
        beta = 1 - (alpha + omega_a + omega_m)
    assert math.isclose(alpha + beta + omega_a + omega_m, 1)

    if beta == 0:
        return 0
    if beta == 1.0:
        return None

    bribe = (
             ( ( 1 - ( ( alpha + omega_a)  / (beta + omega_m) )**(z+1) ) / (beta + omega_m) ) *
             ( (alpha + omega_a + omega_m) /
               ( (alpha + omega_a + omega_m)/ ( beta ) )**(z+1) )
              - 1
            )
    if bribe <= 0.0:
        return 0
    else:
        return bribe

    #return bribe
```

```python
whale_attack_bribe(alpha=0.1,beta_m=0.05,z=1)
```

    4.285779606767259

```python
txexclusion_attack_bribe(omega_a=0.1,omega_m=0.05,alpha=0,beta=None,z=1)
```

    4.285779606767259

### Visualize the attack costs whithout negative bribe

```python
def fig_bribing_costs_tx_exclusion():
    fig, ax = plt.subplots(figsize=(16, 9))

    # https://matplotlib.org/api/markers_api.html
    marker = itertools.cycle((',','v', '+', 's','.', 'o', '*','1','D','x','^'))

    omega_a = [ 0.0, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.33, 0.382 ]
    omega_m = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.33, 0.382 ]
    z = 1

    for o_a in omega_a:
        plt.plot(omega_m, [ txexclusion_attack_bribe(omega_a=o_a,omega_m=o_m,z=z) for o_m in omega_m ] , marker=next(marker), label=" $\\omega_\\alpha$=" + str(o_a))

    # tidy up the figure
    ax.grid(True)
    #ax.legend(loc='center right', bbox_to_anchor=(0.8, 0.57))
    ax.legend(loc='center right', bbox_to_anchor=(0.90, 0.57))
    #ax.set_title("Attack success probability")
    ax.set_xlabel("Hash rate of potentially bribable miner $ \\omega_m $")
    ax.set_ylabel("Attack costs in times of block reward")
    ax.set_ylim([-0.1, 8])
    ax.set_xlim([0,0.4])

    plt.yticks(np.arange(0, 8, step=1))

    # draw line at 0
    #plt.plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
    #plt.plot([0, 100], [0, 0], color='k', linestyle='--', linewidth=2)

    #plt.yscale('log')
    plt.rcParams.update({'font.size': 17})
    plt.rc('xtick', labelsize=17)
    plt.rc('ytick', labelsize=17)
    #plt.savefig("figures/.png", dpi=100) # sage image
    plt.show()

fig_bribing_costs_tx_exclusion()
```

### Calculate attack success probabilities and expected profit

This basically wraps the `catch_up()` function with the right parameters defined above.

In this case $ \omega_m $ joins the attacker.

```python
def P_attack_success_with_m(omega_a,omega_m,z=1,alpha=0,beta=None):
    if beta is None:
        beta = 1 - ( alpha + omega_a + omega_m  )
    assert math.isclose(alpha + beta + omega_a + omega_m, 1)

    return catch_up(( alpha + omega_a + omega_m ),z)
```

```python
P_attack_success_with_m(omega_a=0.0,omega_m=0.382) # 38.2 % attacker
```

    0.3820760151234277

```python
P_attack_success_with_m(omega_a=0.09,omega_m=0.40) # 49 % attacker
```

    0.923106497500961

```python
P_attack_success_with_m(omega_a=0.3,omega_m=0.40)
```

    1.0

Again wrapping the `catch_up()` function with the right parameters defined above.

In this case $ \omega_m $ joins the defender.

```python
def P_attack_success_without_m(omega_a,omega_m,z=1,alpha=0,beta=None):
    if beta is None:
        beta = 1 - ( alpha + omega_a + omega_m )
    assert math.isclose(alpha + beta + omega_a + omega_m, 1)

    return catch_up(( alpha + omega_a ),z)
```

```python
P_attack_success_without_m(omega_a=0.4,omega_m=0.382)
```

    0.44444444444444453

```python
P_attack_success_without_m(omega_a=0.3,omega_m=0.40)
```

    0.18367346938775514

```python
P_attack_success_without_m(omega_a=0.1,omega_m=0.90)
```

    0.01234567901234568

Calculate the expected profit for the next block in terms of block reward (normalized to 1).
The profit for mining on the **main chain** is given by the success probability that the attack fails,
times the probability that $ m $ finds the next block on the main chain.

```python
def profit_main_chain(alpha=0,beta=None,omega_a=0,omega_m=0,z=1):
    if beta is None:
        beta = 1 - (alpha + omega_a + omega_m)

    assert math.isclose(alpha + beta + omega_a  + omega_m, 1)

    P_attack_success = P_attack_success_without_m(omega_a=omega_a,
                                                  omega_m=omega_m,
                                                  z=z)
    return (
              ( 1 - P_attack_success ) *
              ( omega_m / ( beta + omega_m ) )
           )
```

```python
profit_main_chain(omega_a=0.0,omega_m=0.382)
```

    0.382

```python
profit_main_chain(omega_a=0.4,omega_m=0.1)
```

    0.09259259259259259

Calculate the expected profit for the next block in terms of block reward (normalized to 1).
The profit for mining on the **attack chain** is given by the success probability that the attack is successful, times the probability that $ m $ finds the next block on the attack chain, multiplied by the bribe $ \epsilon $.

```python
def profit_attack_chain(alpha=0,beta=None,omega_a=0,omega_m=0,z=1,epsilon=0):
    if beta is None:
        beta = 1 - (alpha + omega_a + omega_m)
    assert math.isclose(alpha + beta + omega_a + omega_m, 1)

    P_attack_success = P_attack_success_with_m(omega_a=omega_a,
                                               omega_m=omega_m,
                                               z=z)
    return (
              P_attack_success  *
              ( omega_m / ( alpha + omega_a + omega_m ) )
           ) * ( epsilon + 1 )
```

```python
profit_attack_chain(omega_a=0.0,omega_m=0.382,epsilon=0)
```

    0.3820760151234277

```python
profit_attack_chain(omega_a=0.1,omega_m=0.382,epsilon=0)
```

    0.6862002653508444

```python
profit_attack_chain(omega_a=0.1,omega_m=0.382,epsilon=1)
```

    1.3724005307016889

```python
profit_attack_chain(omega_a=0.1,beta=0.0,omega_m=0.90)
```

    0.9

### Visualize the expected reward

Note that the reward on the main chain slightly increases with low attacker hash rates,
since less hash power is mining on the main chain. This chances when the attackers pass the $ 38.2 \% $ threshold.

```python
def fig_profit_main_chain():
    fig, ax = plt.subplots(figsize=(16, 9))

    # https://matplotlib.org/api/markers_api.html
    marker = itertools.cycle(('*',',','v', '+', 's','.', 'o', '*','1','D','x','^'))

    omega_a = [  0.1, 0.2, 0.3, 0.382, 0.4, 0.45, 0.5, 0.0 ]
    #omega_a = [ 0.0, 0.1, 0.2, 0.3, 0.4, ]
    omega_m = [ 0.01, 0.05, 0.1,0.2, 0.33, 0.382, 0.4, 0.5, 0.6 ]
    z = 1

    for o_a in omega_a:
        profit = [ profit_main_chain(omega_a=o_a,omega_m=o_m) for o_m in omega_m ]
        plt.plot(omega_m, profit, marker=next(marker), label=" $\\omega_\\alpha$=" + str(o_a) )
        #for e in epsilon:
        #    plt.plot(omega_m, [ profit_attack_chain(omega_a=o_a,omega_b=0,omega_m=o_m,epsilon=e) for o_m in omega_m ] , marker=next(marker), label=" $\\omega_\\alpha$ =" + str(o_a) + " $ \\epsilon $= " + str(e))

    # tidy up the figure
    ax.grid(True)
    #ax.legend(loc='center right', bbox_to_anchor=(0.8, 0.57))
    ax.legend(loc='center right', bbox_to_anchor=(0.30, 0.70))
    ax.set_title("Expected profit on main chain in case of attacking miner with hashrate $ \\omega_\\alpha $")
    ax.set_xlabel("Hash rate of potentially bribable miner $ \\omega_m $")
    ax.set_ylabel("Profit in times of block reward")
    ax.set_ylim([-0.1, 1])
    ax.set_xlim([0,0.5])

    plt.yticks(np.arange(-0.1, 1, step=0.10))

    # draw line at 0
    #plt.plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
    plt.plot([0, 100], [0, 0], color='k', linestyle='--', linewidth=2)

    #plt.yscale('log')
    plt.rcParams.update({'font.size': 17})
    plt.rc('xtick', labelsize=17)
    plt.rc('ytick', labelsize=17)
    #plt.savefig("figures/.png", dpi=100) # sage image
    plt.show()

fig_profit_main_chain()
```

Note that the expected reward is highest when the network is split evenly.

```python
def fig_profit_attack_chain(epsilon=[ 0 ]):
    fig, ax = plt.subplots(figsize=(16, 9))

    # https://matplotlib.org/api/markers_api.html
    marker = itertools.cycle((',','v', '+', 's','.', 'o', '*','1','D','x','^'))

    #omega_a = [ 0.0, 0.1, 0.33, 0.382 ]
    omega_a = [ 0.0, 0.05, 0.1, 0.2, 0.3, 0.382, 0.4, 0.45, 0.5, ]
    #omega_a = [ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, ]
    omega_m = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.33, 0.35, 0.382, 0.4, 0.45, 0.5, 0.6 ]
    z = 1

    for o_a in omega_a:
        #plt.plot(omega_m, [ profit_main_chain(omega_a=o_a,omega_b=0,omega_m=o_m) for o_m in omega_m ] , marker=next(marker), label=" $\\omega_\\alpha$=" + str(o_a))
        for e in epsilon:
            plt.plot(omega_m, [ profit_attack_chain(omega_a=o_a,omega_m=o_m,epsilon=e) for o_m in omega_m ] , marker=next(marker), label=" $\\omega_\\alpha$ =" + str(o_a) + " $ \\epsilon $= " + str(e))

    # tidy up the figure
    ax.grid(True)
    #ax.legend(loc='center right', bbox_to_anchor=(0.8, 0.57))
    ax.legend(loc='center right', bbox_to_anchor=(0.30, 0.70))
    ax.set_title("Expected profit on attack chain in case of attacking miner with hashrate $ \\omega_\\alpha $")
    ax.set_xlabel("Hash rate of potentially bribable miner $ \\omega_m $")
    ax.set_ylabel("Profit in times of block reward")
    ax.set_ylim([-0.1, 1])
    ax.set_xlim([0.1,0.5])

    plt.yticks(np.arange(-0.1, 1, step=0.1))

    # draw line at 0
    #plt.plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
    plt.plot([0, 100], [0, 0], color='k', linestyle='--', linewidth=2)

    #plt.yscale('log')
    plt.rcParams.update({'font.size': 17})
    plt.rc('xtick', labelsize=17)
    plt.rc('ytick', labelsize=17)
    #plt.savefig("figures/.png", dpi=100) # sage image
    plt.show()

fig_profit_attack_chain()
```

### Print everything as table

```python
z=1
alpha=0
omega_b=0
beta=None

omega_a=0
omega_m=0

print("""
\\renewcommand{\\arraystretch}{1.3} % increase space between lines
\\begin{table*}[!htpb]
\\centering
\\scriptsize
\\resizebox{0.7\\linewidth}{!}{   % 2 colum view
\\begin{tabular}{c|c|c|c|c|c|c|c|c|}
""",end="")

O_m = [0.05, 0.10, 0.20, 0.30, 0.33, 0.382, 0.40, ]
O_a = [0.0, 0.05, 0.10, 0.20, 0.30, 0.33, 0.382, 0.40, ]

for i in O_m:
    print("\t& $ \\omega_m = $ {} ".format(str(i)),end="")
#print("\\ \\hline")

for omega_a in O_a:
    print("\\\\ \\hline")
    print("$ \\omega_\\alpha = {:.2f} $".format(omega_a),end="")
    for omega_m in O_m:
        if 1 - (omega_a + omega_m) < 0:
            continue

        proift_main_with_m = profit_main_chain(omega_a=omega_a,omega_m=omega_m)
        prob_success_attack_without_m = P_attack_success_without_m(omega_a=omega_a,omega_m=omega_m)

        min_cost = txexclusion_attack_bribe(omega_a=omega_a,omega_m=omega_m,z=1)

        if min_cost is not None:
            profit_attack_with_m = profit_attack_chain(omega_a=omega_a,omega_m=omega_m,epsilon=min_cost)
        else:
            min_cost = float("NaN")
            profit_attack_with_m = 0

        prob_success_attack_with_m = P_attack_success_with_m(omega_a=omega_a,omega_m=omega_m)
        """
        if profit_attack_with_m > proift_main_with_m:
            mark = "X"
        else:
            mark = " "
        print("o_a = {:4.2f}  b = {:4.2f}  o_m = {:4.2f}  r_main = {:.3f}  p_ma = {:.2f}  e = {:.2f}  r_att = {:.3f}  p_at = {:.3f} {}".format(
                                                     omega_a,
                                                     1 - (omega_a + omega_m),
                                                     omega_m,
                                                     proift_main_with_m,
                                                     1 - prob_success_attack_without_m,
                                                     min_cost,
                                                     profit_attack_with_m,
                                                     prob_success_attack_with_m,
                                                     mark))
        """
        print("""\t& \\makecell[l]{{ $\\beta = {:.3f} $ \\\\ $\\rho= {:.3f}$ \\\\ $\\epsilon = {:.3f}$ \\\\ $\\rho'= {:.3f}$ \\\\ $P = {:.3f}$ }} """.format(
                                                        1 - (omega_a + omega_m),
                                                        proift_main_with_m,
                                                        min_cost,
                                                        profit_attack_with_m,
                                                        prob_success_attack_with_m),end="")

print("""
\\end{tabular}
}
\\caption{Comparison of minimum bribing attack costs $ \\epsilon $ for certain attack hashrates $ \\omega_\\alpha $ and undecided individual miners $ \\omega_m $.
The table also shows the expected reward of $ m $ if $ \\omega_m $ would be directed towards the attack chain $ \\rho' $, as well as the expected reward $ \\rho $ if $ \\omega_m $ would be directed towards the main chain.}
\\label{tab:txexclib}
\\end{table*}
""")
```

    \renewcommand{\arraystretch}{1.3} % increase space between lines
    \begin{table*}[!htpb]
    \centering
    \scriptsize
    \resizebox{0.7\linewidth}{!}{   % 2 colum view
    \begin{tabular}{c|c|c|c|c|c|c|c|c|}
    	& $ \omega_m = $ 0.05 	& $ \omega_m = $ 0.1 	& $ \omega_m = $ 0.2 	& $ \omega_m = $ 0.3 	& $ \omega_m = $ 0.33 	& $ \omega_m = $ 0.382 	& $ \omega_m = $ 0.4 \\ \hline
    $ \omega_\alpha = 0.00 $	& \makecell[l]{ $\beta = 0.950 $ \\ $\rho= 0.050$ \\ $\epsilon = 17.050$ \\ $\rho'= 0.050$ \\ $P = 0.003$ } 	& \makecell[l]{ $\beta = 0.900 $ \\ $\rho= 0.100$ \\ $\epsilon = 7.100$ \\ $\rho'= 0.100$ \\ $P = 0.012$ } 	& \makecell[l]{ $\beta = 0.800 $ \\ $\rho= 0.200$ \\ $\epsilon = 2.200$ \\ $\rho'= 0.200$ \\ $P = 0.062$ } 	& \makecell[l]{ $\beta = 0.700 $ \\ $\rho= 0.300$ \\ $\epsilon = 0.633$ \\ $\rho'= 0.300$ \\ $P = 0.184$ } 	& \makecell[l]{ $\beta = 0.670 $ \\ $\rho= 0.330$ \\ $\epsilon = 0.360$ \\ $\rho'= 0.330$ \\ $P = 0.243$ } 	& \makecell[l]{ $\beta = 0.618 $ \\ $\rho= 0.382$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.382$ \\ $P = 0.382$ } 	& \makecell[l]{ $\beta = 0.600 $ \\ $\rho= 0.400$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.444$ \\ $P = 0.444$ } \\ \hline
    $ \omega_\alpha = 0.05 $	& \makecell[l]{ $\beta = 0.900 $ \\ $\rho= 0.052$ \\ $\epsilon = 7.503$ \\ $\rho'= 0.052$ \\ $P = 0.012$ } 	& \makecell[l]{ $\beta = 0.850 $ \\ $\rho= 0.105$ \\ $\epsilon = 4.056$ \\ $\rho'= 0.105$ \\ $P = 0.031$ } 	& \makecell[l]{ $\beta = 0.750 $ \\ $\rho= 0.210$ \\ $\epsilon = 1.362$ \\ $\rho'= 0.210$ \\ $P = 0.111$ } 	& \makecell[l]{ $\beta = 0.650 $ \\ $\rho= 0.315$ \\ $\epsilon = 0.267$ \\ $\rho'= 0.315$ \\ $P = 0.290$ } 	& \makecell[l]{ $\beta = 0.620 $ \\ $\rho= 0.346$ \\ $\epsilon = 0.062$ \\ $\rho'= 0.346$ \\ $P = 0.376$ } 	& \makecell[l]{ $\beta = 0.568 $ \\ $\rho= 0.401$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.512$ \\ $P = 0.578$ } 	& \makecell[l]{ $\beta = 0.550 $ \\ $\rho= 0.420$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.595$ \\ $P = 0.669$ } \\ \hline
    $ \omega_\alpha = 0.10 $	& \makecell[l]{ $\beta = 0.850 $ \\ $\rho= 0.055$ \\ $\epsilon = 4.286$ \\ $\rho'= 0.055$ \\ $P = 0.031$ } 	& \makecell[l]{ $\beta = 0.800 $ \\ $\rho= 0.110$ \\ $\epsilon = 2.512$ \\ $\rho'= 0.110$ \\ $P = 0.062$ } 	& \makecell[l]{ $\beta = 0.700 $ \\ $\rho= 0.219$ \\ $\epsilon = 0.792$ \\ $\rho'= 0.219$ \\ $P = 0.184$ } 	& \makecell[l]{ $\beta = 0.600 $ \\ $\rho= 0.329$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.333$ \\ $P = 0.444$ } 	& \makecell[l]{ $\beta = 0.570 $ \\ $\rho= 0.362$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.437$ \\ $P = 0.569$ } 	& \makecell[l]{ $\beta = 0.518 $ \\ $\rho= 0.419$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.686$ \\ $P = 0.866$ } 	& \makecell[l]{ $\beta = 0.500 $ \\ $\rho= 0.439$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.800$ \\ $P = 1.000$ } \\ \hline
    $ \omega_\alpha = 0.20 $	& \makecell[l]{ $\beta = 0.750 $ \\ $\rho= 0.059$ \\ $\epsilon = 1.637$ \\ $\rho'= 0.059$ \\ $P = 0.111$ } 	& \makecell[l]{ $\beta = 0.700 $ \\ $\rho= 0.117$ \\ $\epsilon = 0.914$ \\ $\rho'= 0.117$ \\ $P = 0.184$ } 	& \makecell[l]{ $\beta = 0.600 $ \\ $\rho= 0.234$ \\ $\epsilon = 0.055$ \\ $\rho'= 0.234$ \\ $P = 0.444$ } 	& \makecell[l]{ $\beta = 0.500 $ \\ $\rho= 0.352$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.600$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.470 $ \\ $\rho= 0.387$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.623$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.418 $ \\ $\rho= 0.448$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.656$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.400 $ \\ $\rho= 0.469$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.667$ \\ $P = 1.000$ } \\ \hline
    $ \omega_\alpha = 0.30 $	& \makecell[l]{ $\beta = 0.650 $ \\ $\rho= 0.058$ \\ $\epsilon = 0.408$ \\ $\rho'= 0.058$ \\ $P = 0.290$ } 	& \makecell[l]{ $\beta = 0.600 $ \\ $\rho= 0.117$ \\ $\epsilon = 0.050$ \\ $\rho'= 0.117$ \\ $P = 0.444$ } 	& \makecell[l]{ $\beta = 0.500 $ \\ $\rho= 0.233$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.400$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.400 $ \\ $\rho= 0.350$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.500$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.370 $ \\ $\rho= 0.385$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.524$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.318 $ \\ $\rho= 0.445$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.560$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.300 $ \\ $\rho= 0.466$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.571$ \\ $P = 1.000$ } \\ \hline
    $ \omega_\alpha = 0.33 $	& \makecell[l]{ $\beta = 0.620 $ \\ $\rho= 0.057$ \\ $\epsilon = 0.144$ \\ $\rho'= 0.057$ \\ $P = 0.376$ } 	& \makecell[l]{ $\beta = 0.570 $ \\ $\rho= 0.113$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.132$ \\ $P = 0.569$ } 	& \makecell[l]{ $\beta = 0.470 $ \\ $\rho= 0.226$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.377$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.370 $ \\ $\rho= 0.339$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.476$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.340 $ \\ $\rho= 0.373$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.500$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.288 $ \\ $\rho= 0.432$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.537$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.270 $ \\ $\rho= 0.452$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.548$ \\ $P = 1.000$ } \\ \hline
    $ \omega_\alpha = 0.38 $	& \makecell[l]{ $\beta = 0.568 $ \\ $\rho= 0.050$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.067$ \\ $P = 0.578$ } 	& \makecell[l]{ $\beta = 0.518 $ \\ $\rho= 0.100$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.180$ \\ $P = 0.866$ } 	& \makecell[l]{ $\beta = 0.418 $ \\ $\rho= 0.200$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.344$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.318 $ \\ $\rho= 0.300$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.440$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.288 $ \\ $\rho= 0.330$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.463$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.236 $ \\ $\rho= 0.382$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.500$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.218 $ \\ $\rho= 0.400$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.512$ \\ $P = 1.000$ } \\ \hline
    $ \omega_\alpha = 0.40 $	& \makecell[l]{ $\beta = 0.550 $ \\ $\rho= 0.046$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.074$ \\ $P = 0.669$ } 	& \makecell[l]{ $\beta = 0.500 $ \\ $\rho= 0.093$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.200$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.400 $ \\ $\rho= 0.185$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.333$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.300 $ \\ $\rho= 0.278$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.429$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.270 $ \\ $\rho= 0.306$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.452$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.218 $ \\ $\rho= 0.354$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.488$ \\ $P = 1.000$ } 	& \makecell[l]{ $\beta = 0.200 $ \\ $\rho= 0.370$ \\ $\epsilon = 0.000$ \\ $\rho'= 0.500$ \\ $P = 1.000$ }
    \end{tabular}
    }
    \caption{Comparison of minimum bribing attack costs $ \epsilon $ for certain attack hashrates $ \omega_\alpha $ and undecided individual miners $ \omega_m $.
    The table also shows the expected reward of $ m $ if $ \omega_m $ would be directed towards the attack chain $ \rho' $, as well as the expected reward $ \rho $ if $ \omega_m $ would be directed towards the main chain.}
    \label{tab:txexclib}
    \end{table*}

Get the success probability for catching up $ k $ blocks within $ N $ total blocks from the out-of-band analysis:

```python
# Attack probability of catching-up k blocks within N total blocks
def attack_prob(k,N,omega,beta=None):
    if beta is None:
        beta = 1 - omega
    p = 0
    #for i in range(0,N-k):
    i = 0
    while i <= N-k-1:
        p += ( scipy.special.binom(k+2*i, i) - scipy.special.binom(k+2*i, i-1) ) * omega**(k+i+1) * beta**i
        i += 1
    return p
```

```python
# Get number of required total blocks s.t. attack success probability is met
def attack_N(k,omega,success_prob):
        N = 0
        prob = 0
        while prob < success_prob:
            #prob = probCalc_optimized.compute_prob(k, N, omega,beta=1-omega)
            prob = attack_prob(k, N, omega,beta=1-omega)
            N += 1
        return N - 1
```

```python
# calculate expected number of blocks in which attack is successful i.e., target sucess probability is reached with this amount of N
def attack_expected(k,N,omega,beta=None):
    if beta is None:
        beta = 1 - omega
    P = 0
    p = 0
    e = 0
    i = 0
    while i <= N-k-1:
        # Current probability:
        p = ( scipy.special.binom(k+2*i, i) - scipy.special.binom(k+2*i, i-1) ) * ( ( omega / (omega + beta) )**(k+i+1) ) * ( beta / (omega + beta ) )**i
        # Overall probability:
        P += p
        # Expected value:
        y = i+k+1 # i+k+1 == y == N
        #print(P,e,y,p)
        if math.isnan(p):
            #return (P,e)
            return e
        e += p * y
        i += 1
        p = 0
    #return (P,e)
    return e
```

```python
# Probability of attack success depending on teams
for o in [0.1,0.2,0.3,0.33,0.4,0.5]:
    print()
    for om in [0.05,0.1,0.2,0.25,0.3,0.33,0.4,0.49]:
        if o + om <= 0.51:
            continue

        # probability that the attack fails if m does not help
        P_fail = (1 - P_attack_success_without_m(o,om,1) )*100
        # probability that the attack is successful if m helps
        P_success = P_attack_success_with_m(o,om,1)*100
        # number of blocks to reach target success probability with given hashrate
        N_success = attack_N(k=1,omega=o + om,success_prob=0.99)
        # expected number of blocks number of blocks
        N_expected = math.ceil(attack_expected(k=1,N=N_success,omega=o + om))
        print("𝜔 = {:4.2f}   𝜔_𝑚 = {:4.2f}   total = {:4.2f}  P_s = {:7.2f} %  P_f = {:7.2f} %   N_s = {:4}   N_e = {:4}".format(o,
                                                                                om,
                                                                                o + om,
                                                                                P_success,
                                                                                P_fail,
                                                                                N_success,
                                                                                N_expected
                                                                                ))
```

    𝜔 = 0.10   𝜔_𝑚 = 0.49   total = 0.59  P_s =  100.00 %  P_f =   98.77 %   N_s =   47   N_e =    6

    𝜔 = 0.20   𝜔_𝑚 = 0.33   total = 0.53  P_s =  100.00 %  P_f =   93.75 %   N_s =  228   N_e =   14
    𝜔 = 0.20   𝜔_𝑚 = 0.40   total = 0.60  P_s =  100.00 %  P_f =   93.75 %   N_s =   40   N_e =    6
    𝜔 = 0.20   𝜔_𝑚 = 0.49   total = 0.69  P_s =  100.00 %  P_f =   93.75 %   N_s =   15   N_e =    4

    𝜔 = 0.30   𝜔_𝑚 = 0.25   total = 0.55  P_s =  100.00 %  P_f =   81.63 %   N_s =  110   N_e =   10
    𝜔 = 0.30   𝜔_𝑚 = 0.30   total = 0.60  P_s =  100.00 %  P_f =   81.63 %   N_s =   40   N_e =    6
    𝜔 = 0.30   𝜔_𝑚 = 0.33   total = 0.63  P_s =  100.00 %  P_f =   81.63 %   N_s =   27   N_e =    5
    𝜔 = 0.30   𝜔_𝑚 = 0.40   total = 0.70  P_s =  100.00 %  P_f =   81.63 %   N_s =   14   N_e =    4
    𝜔 = 0.30   𝜔_𝑚 = 0.49   total = 0.79  P_s =  100.00 %  P_f =   81.63 %   N_s =    8   N_e =    3

    𝜔 = 0.33   𝜔_𝑚 = 0.20   total = 0.53  P_s =  100.00 %  P_f =   75.74 %   N_s =  228   N_e =   14
    𝜔 = 0.33   𝜔_𝑚 = 0.25   total = 0.58  P_s =  100.00 %  P_f =   75.74 %   N_s =   55   N_e =    7
    𝜔 = 0.33   𝜔_𝑚 = 0.30   total = 0.63  P_s =  100.00 %  P_f =   75.74 %   N_s =   27   N_e =    5
    𝜔 = 0.33   𝜔_𝑚 = 0.33   total = 0.66  P_s =  100.00 %  P_f =   75.74 %   N_s =   20   N_e =    4
    𝜔 = 0.33   𝜔_𝑚 = 0.40   total = 0.73  P_s =  100.00 %  P_f =   75.74 %   N_s =   11   N_e =    4
    𝜔 = 0.33   𝜔_𝑚 = 0.49   total = 0.82  P_s =  100.00 %  P_f =   75.74 %   N_s =    7   N_e =    3

    𝜔 = 0.40   𝜔_𝑚 = 0.20   total = 0.60  P_s =  100.00 %  P_f =   55.56 %   N_s =   40   N_e =    6
    𝜔 = 0.40   𝜔_𝑚 = 0.25   total = 0.65  P_s =  100.00 %  P_f =   55.56 %   N_s =   22   N_e =    5
    𝜔 = 0.40   𝜔_𝑚 = 0.30   total = 0.70  P_s =  100.00 %  P_f =   55.56 %   N_s =   14   N_e =    4
    𝜔 = 0.40   𝜔_𝑚 = 0.33   total = 0.73  P_s =  100.00 %  P_f =   55.56 %   N_s =   11   N_e =    4
    𝜔 = 0.40   𝜔_𝑚 = 0.40   total = 0.80  P_s =  100.00 %  P_f =   55.56 %   N_s =    8   N_e =    3
    𝜔 = 0.40   𝜔_𝑚 = 0.49   total = 0.89  P_s =  100.00 %  P_f =   55.56 %   N_s =    5   N_e =    3

    𝜔 = 0.50   𝜔_𝑚 = 0.05   total = 0.55  P_s =  100.00 %  P_f =    0.00 %   N_s =  110   N_e =   10
    𝜔 = 0.50   𝜔_𝑚 = 0.10   total = 0.60  P_s =  100.00 %  P_f =    0.00 %   N_s =   40   N_e =    6
    𝜔 = 0.50   𝜔_𝑚 = 0.20   total = 0.70  P_s =  100.00 %  P_f =    0.00 %   N_s =   14   N_e =    4
    𝜔 = 0.50   𝜔_𝑚 = 0.25   total = 0.75  P_s =  100.00 %  P_f =    0.00 %   N_s =   10   N_e =    3
    𝜔 = 0.50   𝜔_𝑚 = 0.30   total = 0.80  P_s =  100.00 %  P_f =    0.00 %   N_s =    8   N_e =    3
    𝜔 = 0.50   𝜔_𝑚 = 0.33   total = 0.83  P_s =  100.00 %  P_f =    0.00 %   N_s =    6   N_e =    3
    𝜔 = 0.50   𝜔_𝑚 = 0.40   total = 0.90  P_s =  100.00 %  P_f =    0.00 %   N_s =    5   N_e =    3
    𝜔 = 0.50   𝜔_𝑚 = 0.49   total = 0.99  P_s =  100.00 %  P_f =    0.00 %   N_s =    3   N_e =    3

## Tx exclusion in-band compared to `censorshipcon`

* http://fc18.ifca.ai/bitcoin/papers/bitcoin18-final14.pdf

> Once included in the blockchain, the uncle block’s miner is sent
an uncle block reward of $ c_{uncle} = (1−\frac{\delta}{8}) \cdot c_{block} $,
where $ \delta $ is the distance (number of blocks) between the competing main block
$ b^{i}_{A,main} $ and the publisher block $ b^{i+\delta}_{publ} $.
As  of  January  2018,  the  full  block  reward  is  $ 3 $  ETH  and  the  maximum
distance permitted for an uncle block to be included in the blockchain is $ 6 $.

> His uncle block reward is the subsidy provided  by  the  network  for  the  briber,  and  his  final  payout  is  calculated  as $ c_{payout} = c_{bribe} + c_{block} − c_{uncle} $.

> Subsidy  and  expectation  values: Alice can maximise her subsidy by minimising
the number of blocks $ \delta $ it takes until a bribed block is included in the blockchain
as  an  uncle  block.  Ideally,  she  should  include  uncle  blocks  in  her  next  block.
otherwise $ \delta $ may  range  between  1  and  6  if  an  uncle  is  delayed  entry.
Before highlighting the impact of this subsidy for Alice, recall that she also receives an
additional  publisher  reward $ c_{pub} = \frac{1}{32} \cdot c_{block} $ for  each  uncle  block  she  includes.
Hence for one uncle, Alice only has to pay Bob $ c_{payout} > \frac{4\delta -1}{32} \cdot c_{block} $ to guarantee him a higher payout than if he had won the full block reward.
If she includes two uncle blocks, **she has to pay** $ c_{payout} > \frac{4\delta -1}{16} \cdot c_{block} $ to ensure both blocks have a higher payout.

```python
def epsilon_censorshipcon(delta,num_uncles_included=2,blockreward=1):
    assert delta > 0 and delta < 7, "Delta for uncle block inclusion not wihtin valid range"
    assert num_uncles_included > 0 and num_uncles_included < 3, "Invalid number of uncles per block for this attack"
    if num_uncles_included == 2:
        return ( (4 * delta - 1)/16 ) * blockreward
    if num_uncles_included == 1:
        return ( (4 * delta - 1)/32 ) * blockreward
```

```python
epsilon_censorshipcon(1,1) # cheapest scenario, 1 block delta and one unlce block included
```

    0.09375

```python
epsilon_censorshipcon(1,2)
```

    0.1875

```python
epsilon_censorshipcon(3,2) # somewhere in the middle
```

    0.6875

```python
epsilon_censorshipcon(6,1)
```

    0.71875

```python
epsilon_censorshipcon(6,2) # most expansive scenario, 6 block delta and two uncle blocks included
```

    1.4375

### Costs of owning substantial amounts of attacker hashrate $ \alpha \geq 0.33 $

* https://www.crypto51.app/

#### Ethereum
1 h 51% attack = $90,388 USD

1 ETH = 158 USD

$r_e$ = 2 ETH + fees $\approx$ 2.05 ETH

```python
funds_51_attack_USD = 90388
eth = 158
blockrewads_eth = 2.05
eth_block_interval = 15
```

```python
funds_51_attack_ETH = funds_51_attack_USD / eth
funds_51_attack_ETH
```

    572.0759493670886

```python
funds_33_attack_ETH = funds_51_attack_ETH * (0.33/0.51)
funds_33_attack_ETH
```

    370.1667907669397

```python
blockrewards_33_attack_ETH = funds_33_attack_ETH / blockrewads_eth
blockrewards_33_attack_ETH
```

    180.56916622777547

```python
costs_of_tx_exclusion_attack_ib_per_block_in_times_of_block_reward = 17
```

```python
number_of_blocks_tx_exclusion_ib_can_be_executed = int(blockrewards_33_attack_ETH / costs_of_tx_exclusion_attack_ib_per_block_in_times_of_block_reward)
number_of_blocks_tx_exclusion_ib_can_be_executed
```

    10

```python
time_tx_exlcusion_ib_can_be_executed = (number_of_blocks_tx_exclusion_ib_can_be_executed * eth_block_interval)/60
time_tx_exlcusion_ib_can_be_executed
```

    2.5

## Probability of mining a sequence of $ n = 2,3,4,... $ blocks in a row with hashrate $ p $ within $ N $ total blocks

```python
# probability to NOT mine one of the next n blocks with hashrate q
q = 0.33
n = 1
(1-q)**n
```

    0.6699999999999999

```python
q = 0.33
n = 6
(1-q)**n
```

    0.09045838216899994

```python
# probability to mine one of the next n blocks with hashrate q
q = 0.33
n = 1
1-( (1-q)**n )
```

    0.33000000000000007

```python
q = 0.33
n = 6
1-( (1-q)**n )
```

    0.9095416178310001

We can use a finita Markov chain to calculate the exact probability of mining $ n $
blocks in a row within the next $ N $ total blocks, having $ \omega $ hashrate ($\beta = 1 - \omega$).

```python
M_t = np.zeros(shape=(5,3))
M_t
```

    array([[0., 0., 0.],
           [0., 0., 0.],
           [0., 0., 0.],
           [0., 0., 0.],
           [0., 0., 0.]])

```python
M_t[0][2] = 1
M_t
```

    array([[0., 0., 1.],
           [0., 0., 0.],
           [0., 0., 0.],
           [0., 0., 0.],
           [0., 0., 0.]])

```python
def gen_matrix(n,N,q):
    q = q
    p = 1 - q

    M = np.zeros(shape=(N,N))
    M_init = np.zeros(shape=(1,N))

    return M,M_init

gen_matrix(7,8,0.8)
```

    (array([[0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0., 0., 0.]]),
     array([[0., 0., 0., 0., 0., 0., 0., 0.]]))

```python
# probability to mine at least n squential blocks of the next N blocks with hahsrate q
def P_seq_of_n_within_N(n,N,q,pprint=False):
    if N < n:
        return 0

    q = Decimal(q)
    p = Decimal( 1 - q )

    # start state
    M_init = np.zeros(shape=(1,n+1))
    M_init[0][0] = 1

    # transition matrix of markov chain
    M = np.zeros(shape=(n+1,n+1))

    # start state self reference
    M[0][0] = p

    # final failed state S_n
    M[n][n] = 1 # stay in final state
    M[n-1][n] = q # transition in final state

    # All other states and their propabilities:
    for s in range(1,n):
        M[s-1][s] = q
        M[s][0] = p

    # convert array to matrix
    M = np.asmatrix(M)
    M_init = np.asmatrix(M_init)

    P = (M_init * M**int(N))

    if pprint:
        print(M)
        print(M_init)
        print(P)

    return P.item(n)

P_seq_of_n_within_N(2,6,0.33,True)
```

    [[0.67 0.33 0.  ]
     [0.67 0.   0.33]
     [0.   0.   1.  ]]
    [[1. 0. 0.]]
    [[0.45570497 0.16475797 0.37953706]]

    0.37953705923099995

```python
P_seq_of_n_within_N(2,100,0.1,True)
```

    [[0.9 0.1 0. ]
     [0.9 0.  0.1]
     [0.  0.  1. ]]
    [[1. 0. 0.]]
    [[0.36470365 0.03680779 0.59848855]]

    0.598488553518309

```python
P_seq_of_n_within_N(6,100,2/3,True)
```

    [[0.33333333 0.66666667 0.         0.         0.         0.
      0.        ]
     [0.33333333 0.         0.66666667 0.         0.         0.
      0.        ]
     [0.33333333 0.         0.         0.66666667 0.         0.
      0.        ]
     [0.33333333 0.         0.         0.         0.66666667 0.
      0.        ]
     [0.33333333 0.         0.         0.         0.         0.66666667
      0.        ]
     [0.33333333 0.         0.         0.         0.         0.
      0.66666667]
     [0.         0.         0.         0.         0.         0.
      1.        ]]
    [[1. 0. 0. 0. 0. 0. 0.]]
    [[0.00958356 0.00663177 0.00458915 0.00317566 0.00219754 0.00152069
      0.97230163]]

    0.9723016287695403

```python
P_seq_of_n_within_N(6,100,1/3,True)
```

    [[0.66666667 0.33333333 0.         0.         0.         0.
      0.        ]
     [0.66666667 0.         0.33333333 0.         0.         0.
      0.        ]
     [0.66666667 0.         0.         0.33333333 0.         0.
      0.        ]
     [0.66666667 0.         0.         0.         0.33333333 0.
      0.        ]
     [0.66666667 0.         0.         0.         0.         0.33333333
      0.        ]
     [0.66666667 0.         0.         0.         0.         0.
      0.33333333]
     [0.         0.         0.         0.         0.         0.
      1.        ]]
    [[1. 0. 0. 0. 0. 0. 0.]]
    [[0.61116662 0.20390971 0.06803246 0.02269836 0.00757308 0.00252668
      0.08409307]]

    0.08409307467149553

```python

```


$\newcommand{\RR}{\mathbb{R}}$
$\newcommand{\Nn}{\mathcal{N}}$
$\newcommand{\EE}{\mathbb{E}}$

```python
%matplotlib inline
from ipywidgets import interactive
import numpy as np
from numpy import linalg as la
import matplotlib.pyplot as plt
```

```python
# Uncoment the pip command below if you are using Google Colab.
# In case of errors, use the option --upgrade --force-reinstall

#!pip install git+https://github.com/Guillaume-Garrigos/invprob.git  # For Google Colab
import sys  # For local launch
sys.path.append('..')  # For local launch
import invprob.sparse as sparse
```

```python
np.random.seed(seed=78) # Seed for np.random
dpi = 100 # Resolution used for plotting (230 for small screen, 100 for large one)
```

# Notes on Inverse Problems
(c) Guillaume Garrigos. This notebook is rendered as a static page on [nbviewer](https://nbviewer.jupyter.org/github/Guillaume-Garrigos/invprob/blob/master/notebook/lasso_1-regularisation_path.ipynb), and as an interactive notebook on [Google Colab](https://colab.research.google.com/github/Guillaume-Garrigos/invprob/blob/master/notebook/lasso_1-regularisation_path.ipynb)

## I. Sparse Regularisation with the L1 norm (LASSO) : the regularization path

We try here to recover a ground truth signal $x_0\in \RR^N$, being given a finite number of linear measurments:

\begin{equation}
y = \Phi x
\end{equation}

where $\Phi \in \RR^{M\times N}$ and $y \in \RR^M$.

To do so, we are going to exploit a prior assumption on $x_0$, which is that it is a *sparse* vector, in the sense that it has numerous zero entries.
More precisely, we assume that $x_0$ is the solution of the following optimisation problem

\begin{equation}
\min\limits_{y = \Phi x} \ \Vert x \Vert_1 , \hspace{1cm} \text{ where } \Vert x \Vert_1 := \sum_{i=1}^N \vert x_i \vert.
\tag{$P_0$}
\end{equation}

### 1. Presentation of the problem

We define $\Phi$, $x_0$ and $y$.

```python
# We start by defining the characteristics of the problem: dimensions, sparsity level, etc.
data_size = 100
data_number = round(data_size/2)
sparsity_level = 10

# We define the main components of our problem
Phi = np.random.randn(data_number,data_size)
x0 = np.sign(sparse.randn(data_size,1,sparsity_level))
y = Phi@x0
```

Let's compare the ground truth $x_0$ with the pseudo inverse solution of the linear system $\Phi x = y$, that is, $x^\dagger := \Phi^\dagger y$.

```python
x_pinv = la.lstsq(Phi, y, rcond=None)[0]
_ = plt.figure(dpi=dpi)
sparse.stem(x0, "C0", "ground truth")
sparse.stem(x_pinv, "C1", "pinv solution")
plt.show()
```

We see that even if $x^\dagger$ is a solution of the linear system, it is far from $x_0$, and doesn't share at all the same sparse structure.
One could argue here that $x^\dagger$ has high intensities on the support of $x_0$, and so that it would be sufficient to pass it trough a high-pass filter.
But this is not true everywhere, and $x^\dagger$ might have quite high intensities elsewhere, implying that setting up the right threshold would be a hard problem.

To recover $x_0$ from $y$ we would need to solve ($P_0$), but this is a hard optimisation problem: it consists in minimising a nonsmooth function over a constraint onto which it is expensive to project on.

Instead, we are going to look for an approximation of $x_0$, considering solutions coming from a *regularisation method*.

### 2. The penalised problem

In this example, we are going to consider a variational regularisation method, based on *penalisation*, like the Tikhonov regularisation method.
The idea is quite simple: instead of solving ($P_0$), we are going to solve an other problem which can be seen as a perturbation of ($P_0$).
More concretely, we are going to relax the constraint $\Phi x= y$ and replace it by a penalisation term:

\begin{equation}
\min\limits_{x \in \RR^N} f_\lambda(x):= \Vert x \Vert_1 + \frac{1}{2 \lambda}\Vert \Phi x - y \Vert^2.
\tag{$P_\lambda$}
\end{equation}

The rationale behind this is that, when $\lambda \sim 0$, the solutions of ($P_\lambda$) must verify $\Phi x_\lambda \sim y$  "first", and also having a low $\ell^1$ norm, which makes them a good approximation of $x_0$.
This problem is called the [LASSO](https://en.wikipedia.org/wiki/Lasso_(statistics)) in certain comunities, in particular in statistics and compressed sensing.

What we need now is to find a solution of ($P_\lambda$), for a given (small) parameter $\lambda$.
For this, we are going to use the [Forward-Backward algorithm](https://en.wikipedia.org/wiki/Forward%E2%80%93backward_algorithm).
Given an initialization $x^0 \in \RR^N$ and a stepsize $0 < \tau < \frac{2}{\Vert \Phi^* \Phi \Vert}$, the Forward-Backward algorithm writes as follows:

\begin{equation}
x^{k+1} = \text{Soft}_{\lambda \tau}(x^k - \tau \Phi^*(\Phi x^k - y))
\end{equation}

where $\text{Soft}_{\alpha} : \RR^N \rightarrow \RR^N$ is the Soft-Thresholding operator, defined for all $\alpha > 0$ by

\begin{equation}
\text{Soft}_{\alpha}(x) := \left(\text{soft}_{\alpha}(x_i)\right)_{i=1,..,N}, \quad \text{ where } \text{soft}_{\alpha}(x_i) =
\begin{cases}
0 & \text{ if } \vert x_i \vert \leq \alpha \\
x - \text{sgn}(x_i) \lambda & \text{ if } \vert x_i \vert \geq \lambda
\end{cases}
\end{equation}

```python
def fb_lasso(A, y, reg_param, iter_nb, x_ini=None, verbose=False):
    ''' Use the Forward-Backward algorithm to find a minimizer of:
             reg_param*norm(x,1) + 0.5*norm(Ax-y,2)**2
        Eventually outputs the functional values and support of the iterates
        while running the method
    '''
    # Manage optional input/output
    if verbose:  # Optional output
        regret = np.zeros(iter_nb)
        support = np.zeros(iter_nb)
        details = {
            "function_value": regret,
            "iterate_support": support
        }
    if x_ini is not None:
        x = x_ini
    else:
        x = np.zeros((A.shape[1], 1))

    # The core of the algorithm
    stepsize = 0.8 * 2/la.norm(A, 2)**2
    for k in range(iter_nb):
        x = x - stepsize * A.T@(A@x - y)
        x = sparse.soft_thresholding(x, reg_param*stepsize)
        if verbose:
            regret[k] = 0.5*la.norm(A@x-y, 2)**2 + reg_param*la.norm(x, 1)
            support[k] = sparse.norm0(x)

    # Output
    if verbose:
        return x, details
    else:
        return x
```

```python
reg_param = 0.1  # We take lambda close to zero. Try to change it!
iter_nb = 4000
x_reg, details = fb_lasso(Phi, y, reg_param, iter_nb, verbose=True)

_ = plt.figure(dpi=dpi)
sparse.stem(x0, "C0", "ground truth")
sparse.stem(x_reg, "C1", "reg solution")
plt.show()
```

We hardly see the difference between $x_\lambda$ and $x_0$ when $\lambda =$0.1.
The only noticeable difference is the apparition of spikes having a negligeable amplitude.

We can look more into details to iterates generated by the algorithm.
In particular, we see that the support of the iterates is essentially decreasing (not monotonically though), until a point where it reaches a stable support.

```python
plt.figure(dpi=dpi, figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.title(r"Evolution of $f_\lambda(x^k)$")
plt.semilogy(details.get("function_value"))

plt.subplot(1, 2, 2)
plt.title(r"Evolution of #supp$(x^k)$")
plt.plot(details.get("iterate_support"))
plt.show()
```

### 3. The regularisation path

The set

$$\{x_\lambda \ | \ x_\lambda \text{ is a solution of } (P_\lambda), \lambda > 0 \}$$

is called the *regularization path*, and $\lambda$ is generally referred to as the *regularisation parameter*.
As we saw before, $x_\lambda \to x_0$ when $\lambda \to 0$.
Here we will try to visualise the set of regularised solutions.

First, here is a precalculated animation showing $x_\lambda$ for varying values of $\lambda$.
See how $x_\lambda$ starts from zero (when $\lambda$ is large), and then converges to $x_0$ by progressively getting spikes "growing".
See also how additional spikes appear and then tend to vanish along time.

Now, if you want to compute yourself these solutions, you just need to use the algorithm introduced above.
Here is some code to do so.

```python
def compute_solution(reg_param):
    ''' Given a regularisation parameter, we compute the corresponding
        regularized solution with the FB algorithm. The number of
        iterations to use is customly chosen.
    '''
    reg_param = round(reg_param,1)  # For display purposes

    # Choice of the stopping criterion
    if reg_param > 10:
        iter_nb = 200
    else:
        if reg_param > 1:
            iter_nb = 1000
        else:
            iter_nb = 3000
    # We get the solution
    x_reg = fb_lasso(Phi, y, reg_param, iter_nb)
    # We plot it
    _ = plt.cla()
    _ = plt.title(r"Regularisation parameter $\lambda=$" + str(reg_param))
    sparse.stem(x0, "C0","ground truth")
    sparse.stem(x_reg, "C1", "reg solution")
    _ = plt.show()
```

```python
_ = plt.figure(dpi=dpi, figsize=(12, 6))
interactive(compute_solution,reg_param=(1,100,1))
```

    interactive(children=(IntSlider(value=50, description='reg_param', min=1), Output()), _dom_classes=('widget-in…

    <Figure size 1200x600 with 0 Axes>

```python

```


# Visualizing stationary paths of a  functional

Last revised: 02-Feb-2019 by Dick Furnstahl [furnstahl.1@osu.edu]

Consider the functional

$\begin{align}
  S = \int_{x_1}^{x_2} f[y(x), y'(x), x] \, dx
\end{align}$

with $y_1 = y(x_1)$ and $y_2 = y(x_2)$ fixed.  We denote by $y^*(x)$ the path that minimizes $S$ (or, more generally, makes it stationary).  Then we consider the class of candidate paths $y(x)$ given by

$\begin{align}
  y(x) = y^*(x) + \alpha \eta(x)
\end{align}$

where $\eta(x)$ is some function that vanishes at the endpoints: $\eta(x_1) = \eta(x_2) = 0$.  We can derive the Euler-Lagrange equations by minimizing $S(\alpha)$ with respect to $\alpha$.

Here we visualize this problem by considering a particular $S$, choosing among some possible $\eta(x)$ definitions, and seeing how $S$ is minimized with respect to $\alpha$.  We will also allow for an incorrect determination of $y^*(x)$, in which case we expect that the minimum alpha will give us a reasonable reproduction of the true $y^*(x)$.  The variation of $\alpha$ and the choice of functions will be made using widgets from `ipywidgets`.

## Looking at a plot of the functional evaluation versus $\alpha$

\[We'll use `%matplotlib notebook` so that we can modify figures without redrawing them.\]

```python
%matplotlib notebook
```

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

import ipywidgets as widgets
from IPython.display import display

```

### Functional from Taylor problem 6.9

This problem states: "Find the equation of the path from the origin $O$ to the point $P(1,1)$ in the $xy$ plane that makes the integral $\int_O^P (y'^2 + y y' + y^2)$ stationary.  The answer from solving the Euler-Lagrange equation is $y^*(x) = \sinh(x)/\sinh(1)$.

```python
def y_star(x):
    """Path that minimizes the functional in Taylor problem 6.9."""
    return np.sinh(x) / np.sinh(1.)
```

```python
delta_x = 0.001
x_pts = np.arange(0., 1., delta_x)

fig = plt.figure(figsize=(6,3),
                 num='Visualizing stationary paths of a functional')
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)

def setup_figure():
    ax1.set_title('Show paths')
    ax1.plot(x_pts, y_star(x_pts), color='black', lw=2)
    ax1.set_xlabel('x')
    ax1.set_ylabel('y(x)')

    ax2.set_title('Evaluate functional')
    ax2.set_xlabel(r'$\alpha$')
    ax2.set_ylabel('functional')
    ax2.set_xlim(-0.4, 0.4)
    ax2.set_ylim(1.5, 3.)
    #ax2.axvline(0., color='black', alpha=0.3)
    ax2.axhline(evaluate_functional(x_pts, y_star(x_pts)),
                                    color='black', alpha=0.3)

    fig.tight_layout()

def evaluate_functional(x_pts, y_pts):
    """Given arrays of x and y points, evaluate the functional from 6.9."""
    # The numpy gradient function takes the derivative of an array y_pts
    #  that is a function of points x in x_pts.
    y_deriv_pts = np.gradient(y_pts, x_pts)
    f = y_deriv_pts**2 + y_pts * y_deriv_pts + y_pts**2

    # Use the numpy trapezoid rule (trapz) to do the integral over f.
    return np.trapz(f, x_pts)

def make_path(alpha, ax1_passed, ax2_passed,
              base_function='exact', eta_function='sine'):
    """Given a base function, which may be the exact y^*(x) or a guess that
       is not correct, generate and plot the path corresponding to adding
       alpha*eta(x) to the base function, with eta(x) chosen among some
       functions that vanish at the endpoints in x.
    """
    # map x_pts to zero to 1 (it may already be there)
    x_mapped_pts = (x_pts - x_pts[0]) / (x_pts[-1] - x_pts[0])

    # Choices for the base function
    if (base_function == 'exact'):
        base = lambda x : y_star(x)
    elif (base_function == 'guess 1'):
        base = lambda x : np.sinh(2.*x) / np.sinh(2.)
    elif (base_function == 'guess 2'):
        base = lambda x : x**3

    if (eta_function == 'sine'):
        eta = lambda x : np.sin(np.pi * x)
    elif (eta_function == 'parabola'):
        eta = lambda x : 4. * x * (1. - x)

    y_new_pts = base(x_pts) + alpha * eta(x_mapped_pts)

    ax1_passed.plot(x_pts, y_new_pts, color='red', lw=1)
    ax2_passed.plot(alpha, evaluate_functional(x_pts, y_new_pts), '.',
                    color='red')

def reset_graph(event):
    ax1.clear()
    ax2.clear()
    setup_figure()

button = widgets.Button(
    description='reset graph'
)
button.on_click(reset_graph)

widgets.interact(make_path,
                 alpha=widgets.FloatSlider(min=-1., max=1., step=.05,
                                           value=0.0, description=r'$\alpha$',
                                           continuous_update=False),
                 ax1_passed=widgets.fixed(ax1),
                 ax2_passed=widgets.fixed(ax2),
                 base_function=widgets.Dropdown(options=['exact', 'guess 1',
                                                         'guess 2'],
                                                value='exact',
                                                description='base function'),
                 eta_function=widgets.Dropdown(options=['sine', 'parabola'],
                                               value='sine',
                                               description=r'$\eta(x)$')
                )

setup_figure()
button
```

    <IPython.core.display.Javascript object>

    interactive(children=(FloatSlider(value=0.0, continuous_update=False, description='$\\alpha$', max=1.0, min=-1…

    Button(description='reset graph', style=ButtonStyle())

```python

```