# Homework 1

**For exercises in the week 22-28.10.19**

**Points: 7 + 2 bonus point**

Please solve the problems at home and bring to class a [declaration form](http://ii.uni.wroc.pl/~jmi/Dydaktyka/misc/kupony-klasyczne.pdf) to indicate which problems you are willing to present on the blackboard.

## Problem 1 (McKay 4.1) [1p]

You are given a set of 12 balls in which:
- 11 balls are equal
- 1 ball is different (either heavier or lighter).

You have a two-pan balance. How many weightings you must use to detect the odd ball?

*Hint:* A weighting can be seen as a random event. You can design them to carry the most information, i.e. to maximize the entropy of their outcome.

## Problem 2 [1p]

Bayes' theorem allows to reason about conditional probabilities of causes and their effects:

\begin{equation}
p(A,B)=p(A|B)p(B)=p(B|A)p(A)
\end{equation}

\begin{equation}
p(A|B) = \frac{p(B|A)p(A)}{p(B)}
\end{equation}

Bayes' theorem allows us to reason about probabilities of causes, when
we observe their results.  Instead of directly answering the hard
question $p(\text{cause}|\text{result})$ we can instead separately
work out the marginal probabilities of causes $p(\text{cause})$ and
carefully study their effects $p(\text{effect}|\text{cause})$.

Solve the following using Bayes' theorem.

1. There are two boxes on the table: box \#1 holds two
  black balls and eight red ones, box \#2 holds 5 black ones and
  5 red ones. We pick a box at random (with equal probabilities),
  and then a ball from that box.
  1. What is the probability, that the
  ball came from box \#1 if we happened to pick a red ball?

1. The government has started a preventive program of
  mandatory tests for the Ebola virus. Mass testing method is
  imprecise, yielding 1% of false positives (healthy, but the test
  indicates the virus) and 1% of false negatives
  (having the virus but healthy according to test results).
  As Ebola is rather infrequent, lets assume that it occurs in
  one in a million people in Europe.
  1. What is the probability,
  that a random European, who has been tested positive for Ebola
  virus, is indeed a carrier?
  2. Suppose we have an additional information, that the person has just
  arrived from a country where one in a thousand people is a carrier.
  How big will be the increase in probability?
  3. How accurate should be the test, for a 80% probability of true
  positive in a European?

## Problem 3 [1.5p]

Given observations $x_1,\ldots,x_n$
  coming from a certain distribution,
  prove that MLE of a particular parameter of that distribution is equal to the sample mean $\frac{1}{n}\sum_{i=1}^n x_i$:
1. Bernoulli distribution with success probability $p$ and MLE $\hat{p}$,
2. Gaussian distribution $\mathcal{N}(\mu,\sigma)$ and MLE $\hat{\mu}$,
3. Poisson distribution $\mathit{Pois}(\lambda)$ and MLE $\hat{\lambda}$.

## Problem 4 [1.5p]

1D Gaussian manipulation for Kalman filters.

A [1D Kalman filter](https://en.wikipedia.org/wiki/Kalman_filter) tracks the location of an object given imprecise measurements of its location. At its core it performs an update of the form:

$$
      p(x|m) = \frac{p(m|x)p(x)}{p(m)} = \frac{p(m|x)p(x)}{Z},
$$

where:

- $p(x|m)$ is the updated belief about the location,
- $p(x) = \mathcal{N}(\mu=\mu_x, \sigma=\sigma_x)$ is the belief about the location,
- $p(m|x) = \mathcal{N}(\mu=x, \sigma=\sigma_m)$ is the noisy measurement, centered on the location of the object,
- $Z = p(m) =\int p(m|x)p(x) dx$ is a normalization constant not dependent on $x$.

Compute $p(x|m)$.

*Hint:* The product $\mathcal{N}(x;\mu_1, \sigma_1)\mathcal{N}(x;\mu_2, \sigma_2)$ resembles an unnormalized probability distribution, which one? Can you normalize it by computing the mean and standard deviation and fitting it to a known PDF?

## Problem 5 (Murphy, 2.17) [1p]

Expected value of the minimum.

Let $X, Y$ be sampled uniformly on the interval $[0,1]$. What is the expected value of $\min(X,Y)$?

## Problem 6 (Kohavi) [1p]

The failure of leave-one-out evaluation.

Consider a binary classification dataset in which the labels are assigned completely at random, with 50% probability given to either class. Assume you have a collected a dataset with 100 records in which exactly 50 of them belong to class 0 and 50 to class 1.

What will be the leave-one-out accuracy of the majority voting classifier?

NB: To avoid similar effects, sometimes it is useful to equalize the number of classes in each fold of cross-validation, e.g. using the [StratifiedKFold](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html) implementation from SKlearn. Somewhat obviously, this will not work with leave-one-out validation – but it will with leave-two-out already, at least in the binary case.

## Problem 7 [1pb]
Do Problem 7a from [Assignment 1](https://github.com/janchorowski/ml_uwr/blob/fall2019/assignment1/Assignment1.ipynb).

## Problem 8 [1bp]

Many websites ([Reddit](https://reddit.com), [Wykop](https://wykop.pl), [StackOverflow](https://stackoverflow.com)) provide sorting of comments based on user votes. Discuss what are the implications when sorting by:
- difference between up- and down-votes
- mean score
- lower or upper confidence bound of the score

At least for Reddit the sorting algorithm can be found online, what is it?


```python
%load_ext autoreload
%autoreload 2

%matplotlib inline
```

## Basic setup

Install SymPy library
<br>
```bash
conda install sympy
```

# Initialize symbols

SymPy is the library for symbolic calculations

```python
from sympy import *
```

```python
a11, a12, a13, a21, a22, a23, a31, a32, a33 = symbols(
    "a11 a12 a13 a21 a22 a23 a31 a32 a33"
)
```

```python
a11, a12, a13, a21, a22, a23, a31, a32, a33
```

    (a11, a12, a13, a21, a22, a23, a31, a32, a33)

Initialize matrix with symbols

```python
M = Matrix([[a11, a12, a13], [a21, a22, a23], [a31, a32, a33]])
```

```python
M
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{12} & a_{13}\\a_{21} & a_{22} & a_{23}\\a_{31} & a_{32} & a_{33}\end{matrix}\right]$

```python
M.det()
```

$\displaystyle a_{11} a_{22} a_{33} - a_{11} a_{23} a_{32} - a_{12} a_{21} a_{33} + a_{12} a_{23} a_{31} + a_{13} a_{21} a_{32} - a_{13} a_{22} a_{31}$

```python
M.T
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{21} & a_{31}\\a_{12} & a_{22} & a_{32}\\a_{13} & a_{23} & a_{33}\end{matrix}\right]$

Define second other simbolsm and other matrix

```python
b11, b12, b13, b21, b22, b23, b31, b32, b33 = symbols(
    "b11 b12 b13 b21 b22 b23 b31 b32 b33"
)
```

```python
M1 = M
M2 = Matrix([[b11, b12, b13], [b21, b22, b23], [b31, b32, b33]])
```

```python
M2
```

$\displaystyle \left[\begin{matrix}b_{11} & b_{12} & b_{13}\\b_{21} & b_{22} & b_{23}\\b_{31} & b_{32} & b_{33}\end{matrix}\right]$

```python
M1 * M2
```

$\displaystyle \left[\begin{matrix}a_{11} b_{11} + a_{12} b_{21} + a_{13} b_{31} & a_{11} b_{12} + a_{12} b_{22} + a_{13} b_{32} & a_{11} b_{13} + a_{12} b_{23} + a_{13} b_{33}\\a_{21} b_{11} + a_{22} b_{21} + a_{23} b_{31} & a_{21} b_{12} + a_{22} b_{22} + a_{23} b_{32} & a_{21} b_{13} + a_{22} b_{23} + a_{23} b_{33}\\a_{31} b_{11} + a_{32} b_{21} + a_{33} b_{31} & a_{31} b_{12} + a_{32} b_{22} + a_{33} b_{32} & a_{31} b_{13} + a_{32} b_{23} + a_{33} b_{33}\end{matrix}\right]$

Inverse matrix of $M \in \mathbb{R}^{n \times n}$, is the matrix $M^{-1} \in \mathbb{R}^{n \times n}$ for which $M^{-1}M = I$

```python
M3 = Matrix([[1, 2, 3], [4.5, 5, 6], [a11, 7, 8]])
```

```python
M3 * M3
```

$\displaystyle \left[\begin{matrix}3 a_{11} + 10.0 & 33 & 39\\6 a_{11} + 27.0 & 76.0 & 91.5\\9 a_{11} + 31.5 & 2 a_{11} + 91 & 3 a_{11} + 106\end{matrix}\right]$

```python
a, b, c = symbols("a, b, c")
```

```python
(a ** 2 - b ** 2) / (a - b)
```

$\displaystyle \frac{a^{2} - b^{2}}{a - b}$

```python
invM = M ** -1
```

```python
invM
```

$\displaystyle \left[\begin{matrix}\frac{a_{11} a_{22} \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right) - \left(- a_{12} \left(a_{11} a_{23} - a_{13} a_{21}\right) + a_{13} \left(a_{11} a_{22} - a_{12} a_{21}\right)\right) \left(a_{21} \left(a_{11} a_{32} - a_{12} a_{31}\right) - a_{31} \left(a_{11} a_{22} - a_{12} a_{21}\right)\right)}{a_{11} \left(a_{11} a_{22} - a_{12} a_{21}\right) \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right)} & \frac{- a_{11} a_{12} \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right) + a_{11} \left(a_{11} a_{32} - a_{12} a_{31}\right) \left(- a_{12} \left(a_{11} a_{23} - a_{13} a_{21}\right) + a_{13} \left(a_{11} a_{22} - a_{12} a_{21}\right)\right)}{a_{11} \left(a_{11} a_{22} - a_{12} a_{21}\right) \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right)} & - \frac{- a_{12} \left(a_{11} a_{23} - a_{13} a_{21}\right) + a_{13} \left(a_{11} a_{22} - a_{12} a_{21}\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)}\\\frac{- a_{21} \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{21} \left(a_{11} a_{32} - a_{12} a_{31}\right) - a_{31} \left(a_{11} a_{22} - a_{12} a_{21}\right)\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right)} & \frac{a_{11} \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right) + a_{11} \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)\right)} & - \frac{a_{11} \left(a_{11} a_{23} - a_{13} a_{21}\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)}\\\frac{a_{21} \left(a_{11} a_{32} - a_{12} a_{31}\right) - a_{31} \left(a_{11} a_{22} - a_{12} a_{21}\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)} & - \frac{a_{11} \left(a_{11} a_{32} - a_{12} a_{31}\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)} & \frac{a_{11} \left(a_{11} a_{22} - a_{12} a_{21}\right)}{\left(a_{11} a_{22} - a_{12} a_{21}\right) \left(a_{11} a_{33} - a_{13} a_{31}\right) - \left(a_{11} a_{23} - a_{13} a_{21}\right) \left(a_{11} a_{32} - a_{12} a_{31}\right)}\end{matrix}\right]$

```python
# M * invM * M * invM
```

Identity matrix

```python
I = eye(3)
```

```python
I
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]$

```python
P = 2 * I
```

```python
I
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]$

```python
I * M
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{12} & a_{13}\\a_{21} & a_{22} & a_{23}\\a_{31} & a_{32} & a_{33}\end{matrix}\right]$

```python
M * I
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{12} & a_{13}\\a_{21} & a_{22} & a_{23}\\a_{31} & a_{32} & a_{33}\end{matrix}\right]$

```python
P * M
```

$\displaystyle \left[\begin{matrix}2 a_{11} & 2 a_{12} & 2 a_{13}\\2 a_{21} & 2 a_{22} & 2 a_{23}\\2 a_{31} & 2 a_{32} & 2 a_{33}\end{matrix}\right]$

```python
M * P
```

$\displaystyle \left[\begin{matrix}2 a_{11} & 2 a_{12} & 2 a_{13}\\2 a_{21} & 2 a_{22} & 2 a_{23}\\2 a_{31} & 2 a_{32} & 2 a_{33}\end{matrix}\right]$

```python
simplify(M * invM)
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]$

```python
simplify((a ** 2 - b ** 2) / (a - b))
```

$\displaystyle a + b$

```python
n = symbols("n")
```

```python
simplify((a ** 3 - b ** 3) / (a - b))
```

$\displaystyle \frac{a^{3} - b^{3}}{a - b}$

```python
simplify((a - b) ** 3, inverse=False)
```

$\displaystyle \left(a - b\right)^{3}$

```python
simplify(b ^ 20)
```

$\displaystyle \neg b$


# 1. Introduction

Collateral Reblance Pool (CRP) dynamically rebalances Collateral to ensure the ayToken minted (i.e. the loan) remains solvent, especially in an adverse market environment (i.e. the value of the loan does not exceed the value of Collateral). This dynamic rebalancing, together with a careful choice of the key parameters (including loan to Loan-to-Value (LTV) and volatility assumption) allows ALEX to eliminate the liquidation needs. Any residual gap risk (which CRP cannot address entirely) is addressed through maintaining a strong reserve fund. When a Borrower mints ayToken by providing appropriate Collateral, the Collateral is converted into a basket of Collateral and Token, with the weights determined by CRP.

In this notebook, we will help you understand the key attributes of the CRP pool by answering:
1.  How does CRP achieve the dynamic rebalances with weights determined by CRP?
2.  How does CRP perform in different market environments?
3.  How does power arbitrageur play a role in the dynamic rebalances and the bring rebate back to the pool.
3.  In which parameters space (including LTV, volatility assumption, and power arbitrageur functions) and the CRP would maintain a low default risk and a high pool-value level to a collateral ratio (PVCR)?

Given there is no close form of CPR performance, we use simulation to show the results based on the predicted future scenarios.

# 2. CRP dynamic rebalance mechanism

The following diagram illustrates how CRP dynamic rebalances with the weights determined by CRP.  Once the pool updates new weights based on the Black-Scholes option-pricing model, the pool will be rebalanced by Power Arbitrageur to bring the spot price back to the market price. We leave the mathematical formula of the weight to session 5.

# 3.  CRP performance by Simulations

In this session, we want to simulate how CRP performs in different market environments.  Basically, a CRP would serve as an agent (bot) response to the actual market environment by updating the pool weight based on current token price $p$, actual price volatility $\eta$, and estimated price volatility $\sigma$.  We simplify the market environment and let the token price change follow a linear growth trend with variation, formally named the Geometric Brownian Motion (https://en.wikipedia.org/wiki/Geometric_Brownian_motion).  By setting up different growth rates $r$ and volatility $\eta$, we can approximately mimic different market environments.

Two metrics a liquidity provider (LP) would be very interested to know are 1) the chance of default, i.e., when the LtV >1 at any time point, and 2) Impermanent loss.  We can empirically estimate the default risk and confidence intervals of impermanent loss for any given parameters by conducting Monte Carlo simulations.

For simplicity, token APY are not considered for now. We set initial weights to be 50/50 and loan lifetime equals 91 days, and all key parameters various in the following parameters space:
### Pool parameters

   (1) Initial LTV: range from 0.7 to 0.95

   (2) Black-scholes volatility: $\sigma$ range from 0.1 - 1.0

   (3) Rebate rate of the power arbitargeur: 0 - 100%

   (4) Fee paid based on percentage to weights change: 0% - 1%

   (5) Moving average (MA) of rebalnacing weights: 1 - 30

### Market enviroment
   (6) Growth rate: $r$ range from $[-2, 2]$ (corresponding to 25% or 200% of the initial price)

   (7) Growth rate volatility: $\eta$ range from 0.1 - 1.0

### Episode

For each parameter setup (1) to (7), we activate the CRP and monitor its performance during the whole loan life term (called an episode). At each scheduled time point t (e.g. daily), we rebalance Collateral and token according to section 5, and record and plot:

1. Token price at time t
2. Reblanced weight at time t
3. LTV (pool value includes rebate and fee)
4. PVTC (pool value includes rebate and fee)
5. Scatter plot of BTC weight change and impermanent loss ratio.

Each episode will be one realization of the CRP based on the selected parameters.

```python
# plot of liquity
import scipy
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.widgets import Slider
import seaborn as sns
import numpy as np
import random
import pandas as pd
from ipywidgets import *
from scipy.stats import norm

#Import simulation function and class
%run rbpool_env_v2.ipynb
```

```python
# an episode example
t =  np.linspace(91,0,92)/365
Real_vol = 0.5
Growth_rate = 1
LTV0 = 0.8
bs_vol = 0.5
y_price_init = 50000
Collateral = 10000000
pool_init_x = 5000000
pool_init_y = 100
pool_init_wx = 0.5
fee_rate = 0.0015
rebate=0.8
set_random_seed = True

example = get_episode_full(t,y_price_init, bs_vol, Growth_rate, Real_vol, Collateral, LTV0, fee_rate, rebate,
                      pool_init_x, pool_init_y, pool_init_wx, ma_window=3)
example.head(2)
```

      t
      y_price
      weights_ytoken
      slippage_rebalance
      x_locked
      y_locked
      coll_with_rebate
      btc_bal
      usd_bal
      collateral_value
      splippage_check
      ltv_rebalance
      ltv_with_rebate
      wt_chg
      pvtc_rebate

      0
      0.249315
      50000.000000
      0.549670
      0.000000
      4.503296e+06
      5.496704e+06
      1.000000e+07
      5.496704e+06
      4.503296e+06
      1.000000e+07
      0.000000
      0.800000
      0.800000
      0.000000
      1.000000

      1
      0.246575
      50173.968817
      0.554931
      -0.000056
      4.459184e+06
      5.559908e+06
      1.001909e+07
      5.559556e+06
      4.459024e+06
      1.001858e+07
      -0.000039
      0.798516
      0.798476
      0.005261
      0.998435

```python
 # fix random seed for rw.
# plot key metrics
episode_plot(['y_price', 'weights_ytoken', 'ltv_with_rebate', 'pvtc_rebate', 'slippage_rebalance', 'wt_chg'])
```

    <IPython.core.display.Javascript object>

    C:\Users\jings\Anaconda3\lib\site-packages\ipykernel_launcher.py:127: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.

# 4 Simulation findings

Example 1: In the market, when BTC price goes up (above fig), with 50% rebate on power arbitrageur profit, the CRP maintains LTV lower than 0.8 and PVTC is more than 80% of the initial Collateral.

Example 2: In the market, when BTC price goes down to more than 50%, with 50% rebate on power arbitrageur profit, the CRP maintains LTV < 1 and PVTC greater than Collateral.

# 5 How CRP determine the weights

Let $P_0$ and $P_t$ be the token price at time 0 and time $t$, respectively. CRP determines the weights based on the prevailing LTV and uses the following formula:

\begin{equation}
   \begin{cases}
      w_{Token} = N(d_1) \\
      w_{Collateral} = (1 - w_{Token}) \\
      d_1 = \frac{1}{\sigma \sqrt{(t)}} [ln (\frac{P_t}{P_0}) + t \cdot \frac{\sigma^2}{2}]
    \end{cases}
    \label{eq:CRP} \tag{1}
\end{equation}

Where $N()$ is cdf of standard normal distribution, some readers may note the similarity of the above formula to the Black & Scholes delta because it is. CRP essentially implements a delta replicating strategy of a call option on Token / Collateral, buying more Token when LTV moves higher and vice versa.

Token price changes follow:
$$ log(P_t) = log(P_0) + (r - \frac{\eta^2}{2}) \cdot \Delta t + \eta W_t $$
where $W_t$ is a Brownian motion.

notebooks/ALEX_issue16-simulation-Vectorization - updates - spotprice - rebate.ipynb


### Pipeline that checks if a number is Non-Prime and Outputs the Prime Factors

You can find the full README at:

https://github.com/ydataai/academy/blob/master/tutorials/Pipelines/README.md

### Input variables

```python
# The python packages that will be installed in the multiple pipeline steps
pip_packages_to_install = [
    "sympy==1.5.1"
]

# Pipeline params
enter_number = 100

# Kfp
pipeline_name = "Checks if a number is Non-Prime and Outputs the Prime Factors"
pipeline_description = "Checks if a number is Non-Prime and Outputs the Prime Factors"
experiment_name = "Prime number experiment"
run_name = "Sample Run"

```

```python
import kfp
import typing

```

#### Read the Number Input and Pass it to the next Pipeline Block

```python
from kfp.components import *

# This is the Function definition which will run inside your first Pipeline block
# Input Parameters are defined just like a normal Python function definition

def import_number(numb: int) -> int:  # Single Output Parameters are defined as shown
    num = numb
    return num  # return the output Parameter

```

```python
# kfp.components subclass is used to create Pipeline components from Python definitions/functions
# func_to_container_op packages the python function into a Pipeline Block which runs as a Kubernetes Pod

kfp_import_number = kfp.components.func_to_container_op(
    func=import_number,
    output_component_file="./imp_num.yaml",
    # this yaml component file is the Kubernetes pod definition for your Pipeline block
    packages_to_install=pip_packages_to_install,
)

# kfp uses a Docker Tensorflow container image
# list the packages that are not inherently installed into that container
# image over here, so that they are installed at the time of
# container creation. Be sure to also include the correct package version if it
# is not the most recent one.

```

#### Check if Number is a Prime or a Consonant

```python
from typing import NamedTuple
from kfp.components import *

def is_prime(numb: int) -> NamedTuple("S_outs", [("num", int), ("result", bool)]):
    # Multiple Output Parameters are defined using Named Tuple

    # Note that it is also necessary to import the packages in addition to listing them in the
    # func_to_container_op definition

    import sympy

    num = numb
    if sympy.isprime(num):
        str_isprime = "{} is a Prime Number".format(num)
    else:
        str_isprime = "{} is a Consonant".format(num)

    print(
        str_isprime
    )  # Printed Outputs can be viewed in the Pipeline Block's 'Logs' tab

    return (num, sympy.isprime(num))  # Return the Multiple Parameters as a Tuple

```

```python
kfp_is_prime = kfp.components.func_to_container_op(
    func=is_prime,
    output_component_file="./is_prime.yaml",
    packages_to_install=pip_packages_to_install,
)

```

#### Conditional Block - Only executed if the Number is Prime

```python
def prime_print(numb: int) -> str:
    s = "{} is Prime".format(numb)
    print(s)
    return s

```

```python
kfp_prime_print = kfp.components.func_to_container_op(
    func=prime_print, output_component_file="./prime_print.yaml"
)

```

#### Consonant Number's Prime Factors are Returned

```python
from typing import NamedTuple
from kfp.components import *

def prime_fact(
    numb: int,
) -> NamedTuple("S_outs", [("num", int), ("prime_factors", list)]):
    import sympy

    num = numb
    l_prime = sympy.primefactors(num)
    print("Factors of {} are: ".format(num))
    for x in l_prime:
        print("{} ".format(x), end=" ")
    return (num, l_prime)

```

```python
kfp_prime_fact = kfp.components.func_to_container_op(
    func=prime_fact,
    output_component_file="./prime_fact.yaml",
    packages_to_install=pip_packages_to_install,
)

```

#### Exit Handler

```python
# The exit Handler function is the Pipeline-block that will execute regardless of successful or failed execution
# of the Pipeline

def exit_handle(msg: str):
    print(msg)

```

```python
kfp_exit_handle = kfp.components.func_to_container_op(
    func=exit_handle, output_component_file="./ext_hdle.yaml"
)

```

#### Defining the Pipeline Execution Sequence and Input-Output scheme

```python
# kfp.dsl is used to create the Pipeline Definition and the Sequence of Execution for the Pipeline Blocks
import kfp.dsl as dsl

@dsl.pipeline(
    name=pipeline_name,
    description=pipeline_description,
)
def Primefact_func(
    in_enter_number: int = enter_number,
):  # The input parameter(s) defined here will be accessible to the User running
    # the pipeline from the YData Pipelines Dashboard

    exit_task = kfp_exit_handle(
        "Operation completed"
    )  # Exit task is defined first - this is the task

    # It is important to follow the correct sequence in defining the Operational sequence of the Pipeline Blocks
    # Keeping in mind their relative Input-Output schemes
    # dsl.ExitHandler is used to create an Unconditionally Executable Block - with the

    with dsl.ExitHandler(exit_task):
        imp_num_task = kfp_import_number(in_enter_number)
        is_prime_task = kfp_is_prime(imp_num_task.output)

        # dsl.Condition is used to create conditional execution of a Pipeline Block
        # If Number is Prime, only this block will execute, and Print output 'Prime'
        with dsl.Condition(is_prime_task.outputs["result"] == True):
            prime_print_task = kfp_prime_print(is_prime_task.outputs["num"])

        # If Number is non-Prime, this block will execute, and return the Prime Factors of the Number
        with dsl.Condition(is_prime_task.outputs["result"] == False):
            prime_fact_task = kfp_prime_fact(is_prime_task.outputs["num"])

# For an operation with a single return value, the output reference can be accessed using `task.output` or `task.outputs['output_name']` syntax
# For an operation with a multiple return values, the output references can be accessed using `task.outputs['output_name']` syntax

```

#### Compiling the Pipeline

```python
pipeline_func = Primefact_func
pipeline_filename = pipeline_func.__name__ + ".pipeline.tar.gz"

import kfp.compiler as comp

comp.Compiler().compile(pipeline_func, pipeline_filename)

```

#### Running the Pipeline

```python
import json
import uuid
from random import randrange

client = kfp.Client()

# check if pipeline already exists -> if not, create a new one
filter = json.dumps(
    {
        "predicates": [
            {"key": "name", "op": 1, "string_value": "{}".format(pipeline_name)}
        ]
    }
)
pipeline = client.pipelines.list_pipelines(filter=filter)

if pipeline.pipelines is None:
    print("Creating a new pipeline: " + pipeline_name)
    pipeline = client.pipeline_uploads.upload_pipeline(
        pipeline_filename, name=pipeline_name, description=pipeline_description
    )
else:
    print("Pipeline already exists: " + pipeline_name)
    pipeline = pipeline.pipelines[0]

pipeline_version = str(uuid.uuid4())

print("Creating a new pipeline version: " + pipeline_name + str(" [" + pipeline_version + "]"))
client.pipeline_uploads.upload_pipeline_version(
    pipeline_filename,
    name=pipeline_name + str(" [" + pipeline_version + "]"),
    pipelineid=pipeline.id,
)

# create a new experiment or use an existing one
print("Creating a new experiment or use an existing one: " + experiment_name)
experiment = client.create_experiment(name=experiment_name)

# create a new run with a random identifier
run_random_id = str(randrange(1000))
print("Creating a new run: " + run_name + " [" + run_random_id + "]")
new_run = client.run_pipeline(
    experiment.id, run_name + " [" + run_random_id + "]", pipeline_id=pipeline.id
)

```


# Neuromatch Academy: Week 1, Day 5, Tutorial 1
# Dimensionality Reduction: Geometric view of data

---

Tutorial objectives
In this notebook we'll explore how multivariate data can be represented in different orthonormal bases. This will help us build intuition that will be helpful in understanding PCA in the following tutorial.

Steps:
 1. Generate correlated multivariate data.
 2. Define an arbitrary orthonormal basis.
 3. Project data onto new basis.

---

```python
#@title Video: Geometric view of data
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="emLW0F-VUag", width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

```

    Video available at https://youtube.com/watch?v=emLW0F-VUag

# Setup
Run these cells to get the tutorial started.

```python
#library imports
import time                        # import time
import numpy as np                 # import numpy
import scipy as sp                 # import scipy
import math                        # import basic math functions
import random                      # import basic random number generator functions

import matplotlib.pyplot as plt    # import matplotlib
from IPython import display

```

```python
#@title Figure Settings
%matplotlib inline
fig_w, fig_h = (8, 8)
plt.rcParams.update({'figure.figsize': (fig_w, fig_h)})
plt.style.use('ggplot')
%config InlineBackend.figure_format = 'retina'
```

```python
#@title Helper functions

def get_data(cov_matrix):
  """
  Returns a matrix of 1000 samples from a bivariate, zero-mean Gaussian

  Note that samples are sorted in ascending order for the first random variable.

  Args:
    cov_matrix (numpy array of floats): desired covariance matrix

  Returns:
    (numpy array of floats) : samples from the bivariate Gaussian, with
                              each column corresponding to a different random variable
  """
  mean = np.array([0,0])
  X = np.random.multivariate_normal(mean,cov_matrix,size = 1000)
  indices_for_sorting = np.argsort(X[:,0])
  X = X[indices_for_sorting,:]
  return X

def plot_data(X):
  """
  Plots bivariate data. Includes a plot of each random variable, and a scatter
  plot of their joint activity. The title indicates the sample correlation
  calculated from the data.

  Args:
    X (numpy array of floats):   Data matrix
                                  each column corresponds to a different random variable

  Returns:
    Nothing.
  """
  fig = plt.figure(figsize=[8,4])
  gs = fig.add_gridspec(2,2)
  ax1 = fig.add_subplot(gs[0,0])
  ax1.plot(X[:,0],color='k')
  plt.ylabel('Neuron 1')
  plt.title('Sample var 1: {:.1f}'.format(np.var(X[:,0])))
  ax1.set_xticklabels([])
  ax2 = fig.add_subplot(gs[1,0])
  ax2.plot(X[:,1],color='k')
  plt.xlabel('Sample Number')
  plt.ylabel('Neuron 2')
  plt.title('Sample var 2: {:.1f}'.format(np.var(X[:,1])))
  ax3 = fig.add_subplot(gs[:, 1])
  ax3.plot(X[:,0],X[:,1],'.',markerfacecolor=[.5,.5,.5], markeredgewidth=0)
  ax3.axis('equal')
  plt.xlabel('Neuron 1 activity')
  plt.ylabel('Neuron 2 activity')
  plt.title('Sample corr: {:.1f}'.format(np.corrcoef(X[:,0],X[:,1])[0,1]))

def plot_basis_vectors(X,W):
  """
  Plots bivariate data as well as new basis vectors.

  Args:
    X (numpy array of floats):   Data matrix
                                  each column corresponds to a different random variable
    W (numpy array of floats):   Square matrix representing new orthonormal basis
                                  each column represents a basis vector

  Returns:
    Nothing.
  """
  plt.figure(figsize=[4,4])
  plt.plot(X[:,0],X[:,1],'.',color=[.5,.5,.5],label='Data')
  plt.axis('equal')
  plt.xlabel('Neuron 1 activity')
  plt.ylabel('Neuron 2 activity')
  plt.plot([0,W[0,0]],[0,W[1,0]],color='r',linewidth=3,label = 'Basis vector 1')
  plt.plot([0,W[0,1]],[0,W[1,1]],color='b',linewidth=3,label = 'Basis vector 2')
  plt.legend()

def plot_data_new_basis(Y):
  """
  Plots bivariate data after transformation to new bases. Similar to plot_data but
  with colors corresponding to projections onto basis 1 (red) and basis 2 (blue).
  The title indicates the sample correlation calculated from the data.

  Note that samples are re-sorted in ascending order for the first random variable.

  Args:
    Y (numpy array of floats):   Data matrix in new basis
                                  each column corresponds to a different random variable

  Returns:
    Nothing.
  """
  fig = plt.figure(figsize=[8,4])
  gs = fig.add_gridspec(2,2)
  ax1 = fig.add_subplot(gs[0,0])
  ax1.plot(Y[:,0],'r')
  plt.xlabel
  plt.ylabel('Projection \n basis vector 1')
  plt.title('Sample var 1: {:.1f}'.format(np.var(Y[:,0])))
  ax1.set_xticklabels([])
  ax2 = fig.add_subplot(gs[1,0])
  ax2.plot(Y[:,1],'b')
  plt.xlabel('Sample number')
  plt.ylabel('Projection \n basis vector 2')
  plt.title('Sample var 2: {:.1f}'.format(np.var(Y[:,1])))
  ax3 = fig.add_subplot(gs[:, 1])
  ax3.plot(Y[:,0],Y[:,1],'.',color=[.5,.5,.5])
  ax3.axis('equal')
  plt.xlabel('Projection basis vector 1')
  plt.ylabel('Projection basis vector 2')
  plt.title('Sample corr: {:.1f}'.format(np.corrcoef(Y[:,0],Y[:,1])[0,1]))

```

# Generate correlated multivariate data

```python
#@title Video: Multivariate data
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="YOan2BQVzTQ", width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video
```

    Video available at https://youtube.com/watch?v=YOan2BQVzTQ

To study multivariate data, first we generate it. In this exercise we generate data from a *bivariate normal distribution*. This is an extension of the one-dimensional normal distribution to two dimensions, in which each $x_i$ is marginally normal with mean $\mu_i$ and variance $\sigma_i^2$:

\begin{align}
x_i \sim \mathcal{N}(\mu_i,\sigma_i^2)
\end{align}

Additionally, the joint distribution for $x_1$ and $x_2$ has a specified correlation coefficient $\rho$. Recall that the correlation coefficient is a normalized version of the covariance, and ranges between -1 and +1.

\begin{align}
\rho = \frac{\text{cov}(x_1,x_2)}{\sqrt{\sigma_1^2 \sigma_2^2}}
\end{align}

For simplicity, we will assume that the mean of each variable has already been subtracted, so that $\mu_i=0$. The remaining parameters can be summarized in the covariance matrix:
\begin{equation*}
{\bf \Sigma} =
\begin{pmatrix}
 \text{var}(x_1) & \text{cov}(x_1,x_2) \\
 \text{cov}(x_1,x_2) &\text{var}(x_2)
\end{pmatrix}
\end{equation*}
Note that this is a symmetric matrix with the variances $\text{var}(x_i) = \sigma_i^2$ on the diagonal, and the covariance on the off-diagonal.

### Exercise
We have provided code to draw random samples from a zero-mean bivariate normal distribution. These samples could be used to simulate changes in firing rates for two neurons. Fill in the function below to calculate the covariance matrix given the desired variances and correlation coefficient. The covariance can be found by rearranging the equation above:

\begin{align}
\text{cov}(x_1,x_2) = \rho \sqrt{\sigma_1^2 \sigma_2^2}
\end{align}

Use these functions to generate and plot data while varying the parameters. You should get a feel for how changing the correlation coefficient affects the geometry of the simulated data.

**Suggestions**
* Fill in the function `calculate_cov_matrix` to calculate the covariance.
* Generate and plot the data for $\sigma_1^2 =1$, $\sigma_1^2 =1$, and $\rho = .8$. Try plotting the data for different values of the correlation coefficent: $\rho = -1, -.5, 0, .5, 1$.

```python
help(plot_data)
help(get_data)
```

    Help on function plot_data in module __main__:

    plot_data(X)
        Plots bivariate data. Includes a plot of each random variable, and a scatter
        plot of their joint activity. The title indicates the sample correlation
        calculated from the data.

        Args:
          X (numpy array of floats):   Data matrix
                                        each column corresponds to a different random variable

        Returns:
          Nothing.

    Help on function get_data in module __main__:

    get_data(cov_matrix)
        Returns a matrix of 1000 samples from a bivariate, zero-mean Gaussian

        Note that samples are sorted in ascending order for the first random variable.

        Args:
          cov_matrix (numpy array of floats): desired covariance matrix

        Returns:
          (numpy array of floats) : samples from the bivariate Gaussian, with
                                    each column corresponding to a different random variable

```python

def calculate_cov_matrix(var_1,var_2,corr_coef):
  """
  Calculates the covariance matrix based on the variances and correlation coefficient.

  Args:
    var_1 (scalar):    variance of the first random variable
    var_2 (scalar):    variance of the second random variable
    corr_coef (scalar):      correlation coefficient

  Returns:
    (numpy array of floats) : covariance matrix
  """
  ###################################################################
  ## Insert your code here to:
  ##                calculate the covariance from the variances and correlation
  # cov = ...
  cov_matrix = np.array([[var_1,cov],[cov,var_2]])

  #uncomment once you've filled in the function
  raise NotImplementedError("Student excercise: calculate the covariance matrix!")
  ###################################################################
  return cov

###################################################################
## Insert your code here to:
##                generate and plot bivariate Gaussian data with variances of 1
##                      and a correlation coefficients of: 0.8
##                repeat while varying the correlation coefficient from -1 to 1
###################################################################
variance_1 = 1
variance_2 = 1
corr_coef = 0.8

#uncomment to test your code and plot
#cov_matrix = calculate_cov_matrix(variance_1,variance_2,corr_coef)
#X = get_data(cov_matrix)
#plot_data(X)
```

```python
# to_remove solution

def calculate_cov_matrix(var_1,var_2,corr_coef):
  """
  Calculates the covariance matrix based on the variances and correlation coefficient.

  Args:
    var_1 (scalar):    variance of the first random variable
    var_2 (scalar):    variance of the second random variable
    corr_coef (scalar):      correlation coefficient

  Returns:
    (numpy array of floats) : covariance matrix
  """
  cov = corr_coef * np.sqrt(var_1 * var_2)
  cov_matrix = np.array([[var_1,cov],[cov,var_2]])
  return cov_matrix

cov_matrix = calculate_cov_matrix(variance_1,variance_2,corr_coef)
X = get_data(cov_matrix)

with plt.xkcd():
  plot_data(X)

```

# Define a new orthonormal basis

```python
#@title Video: Orthonormal bases
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="dK526Nbn2Xo", width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video
```

    Video available at https://youtube.com/watch?v=dK526Nbn2Xo

Next, we will define a new orthonormal basis of vectors ${\bf u} = [u_1,u_2]$ and ${\bf w} = [w_1,w_2]$. As we learned in the video, two vectors are orthonormal if:

1.   They are orthogonal (i.e., their dot product is zero):
\begin{equation}
{\bf u\cdot w} = u_1 w_1 + u_2 w_2 = 0
\end{equation}
2.   They have unit length:
\begin{equation}
||{\bf u} || = ||{\bf w} || = 1
\end{equation}

In two dimensions, it is easy to make an arbitrary orthonormal basis. All we need is a random vector ${\bf u}$, which we have normalized. If we now define the second basis vector to be ${\bf w} = [-u_2,u_1]$, we can check that both conditions are satisfied:
\begin{equation}
{\bf u\cdot w} = - u_1 u_2 + u_2 u_1 = 0
\end{equation}
and
\begin{equation}
{|| {\bf w} ||} = \sqrt{(-u_2)^2 + u_1^2} = \sqrt{u_1^2 + u_2^2} = 1,
\end{equation}
where we used the fact that ${\bf u}$ is normalized. So, with an arbitrary input vector, we can define an orthonormal basis, which we will write in matrix by stacking the basis vectors horizontally:

\begin{equation}
{{\bf W} } =
\begin{pmatrix}
 u_1 & w_1 \\
 u_2 & w_2
\end{pmatrix}.
\end{equation}

### Exercise

In this exercise you will fill in the function below to define an orthonormal basis, given a single arbitrary 2-dimensional vector as an input.

**Suggestions**
* Modify the function `define_orthonormal_basis` to first normalize the first basis vector $\bf u$.
* Then complete the function by finding a basis vector $\bf w$ that is orthogonal to $\bf u$.
* Test the function using initial basis vector ${\bf u} = [3,1]$. Plot the resulting basis vectors on top of the data scatter plot using the function `plot_basis_vectors`. (For the data, use  $\sigma_1^2 =1$, $\sigma_1^2 =1$, and $\rho = .8$).

```python
help(plot_basis_vectors)
```

    Help on function plot_basis_vectors in module __main__:

    plot_basis_vectors(X, W)
        Plots bivariate data as well as new basis vectors.

        Args:
          X (numpy array of floats):   Data matrix
                                        each column corresponds to a different random variable
          W (numpy array of floats):   Square matrix representing new orthonormal basis
                                        each column represents a basis vector

        Returns:
          Nothing.

```python
def define_orthonormal_basis(u):
  """
  Calculates an orthonormal basis given an arbitrary vector u.

  Args:
    u (numpy array of floats):    arbitrary 2-dimensional vector used for new basis

  Returns:
    (numpy array of floats) : new orthonormal basis
                                columns correspond to basis vectors
  """
  ###################################################################
  ## Insert your code here to:
  ##                normalize vector u
  ##                calculate vector w that is orthogonal to w

  #u = ....
  #w = ...
  #W = np.column_stack((u,w))

  #comment this once you've filled the function
  raise NotImplementedError("Student excercise: implement the orthonormal basis function")
  ###################################################################
  return W

variance_1 = 1
variance_2 = 1
corr_coef = 0.8

cov_matrix = calculate_cov_matrix(variance_1,variance_2,corr_coef)
X = get_data(cov_matrix)
u = np.array([3,1])

#uncomment and run below to plot the basis vectors
##define_orthonormal_basis(u)
#plot_basis_vectors(X,W)
```

```python
# to_remove solution

def define_orthonormal_basis(u):
  """
  Calculates an orthonormal basis given an arbitrary vector u.

  Args:
    u (numpy array of floats):    arbitrary 2-dimensional vector used for new basis

  Returns:
    (numpy array of floats) : new orthonormal basis
                                columns correspond to basis vectors
  """
  u = u / np.sqrt(u[0]**2 + u[1]**2)
  w = np.array([-u[1],u[0]])
  W = np.column_stack((u,w))
  return W

W = define_orthonormal_basis(u)

with plt.xkcd():
  plot_basis_vectors(X,W)
```

# Project data onto new basis

```python
#@title Video: Change of basis
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="5MWSUtpbSt0", width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video
```

    Video available at https://youtube.com/watch?v=5MWSUtpbSt0

Finally, we will express our data in the new basis that we have just found. Since $\bf W$ is orthonormal, we can project the data into our new basis using simple matrix multiplication :

\begin{equation}
{\bf Y = X W}.
\end{equation}

We will explore the geometry of the transformed data $\bf Y$ as we vary the choice of basis.

#### Exercise
In this exercise you will fill in the function below to define an orthonormal basis, given a single arbitrary vector as an input.

**Suggestions**
* Complete the function `change_of_basis` to project the data onto the new basis.
* Plot the projected data using the function `plot_data_new_basis`.
* What happens to the correlation coefficient in the new basis? Does it increase or decrease?
* What happens to variance?

```python
def change_of_basis(X,W):
  """
  Projects data onto new basis W.

  Args:
    X (numpy array of floats) : Data matrix
                                each column corresponding to a different random variable
    W (numpy array of floats):  new orthonormal basis
                                columns correspond to basis vectors

  Returns:
    (numpy array of floats) : Data matrix expressed in new basis
  """
  ###################################################################
  ## Insert your code here to:
  ##                project data onto new basis described by W

  #Y = ...
  #comment this once you've filled the function
  raise NotImplementedError("Student excercise: implement change of basis")
  ###################################################################
  return Y

## Unomment below to transform the data by projecting it into the new basis
## Plot the projected data
# Y = change_of_basis(X,W)
# plot_data_new_basis(Y)
# disp(...)
```

```python
# to_remove solution
def change_of_basis(X,W):
  """
  Projects data onto a new basis.

  Args:
    X (numpy array of floats) : Data matrix
                                each column corresponding to a different random variable
    W (numpy array of floats):  new orthonormal basis
                                columns correspond to basis vectors

  Returns:
    (numpy array of floats) : Data matrix expressed in new basis
  """
  Y = np.matmul(X,W)
  return Y

Y = change_of_basis(X,W)

with plt.xkcd():
  plot_data_new_basis(Y)

```

#### Exercise
To see what happens to the correlation as we change the basis vectors, run the cell below. The parameter $\theta$ controls the angle of $\bf u$ in degrees. Use the slider to rotate the basis vectors.

**Questions**
* What happens to the projected data as you rotate the basis?
* How does the correlation coefficient change? How does the variance of the projection onto each basis vector change?
* Are you able to find a basis in which the projected data is uncorrelated?

```python
###### MAKE SURE TO RUN THIS CELL VIA THE PLAY BUTTON TO ENABLE SLIDERS ########

import ipywidgets as widgets

def refresh(theta = 0):
  u = [1,np.tan(theta * np.pi/180.)]
  W = define_orthonormal_basis(u)
  Y = change_of_basis(X,W)
  plot_basis_vectors(X,W)
  plot_data_new_basis(Y)

_ = widgets.interact(refresh,
  theta = (0, 90, 5))
```


```python
import numpy as np
import tensorflow as tf
import scipy.stats as spst
```

```python
from sif.models.sigmoid import sigmoid
```

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

# Introduction

In this notebook, I will explore preconditioning of Langevin dynamics. The Langevin equation can be preconditioned and maintain the same stationary distribution through the following modification.
\begin{align}
\mathop{d}\theta_t = -\mathbf{M}\nabla_\theta V(\theta_t) + \sqrt{2\mathbf{M}} \mathop{d}B_t
\end{align}
where $\mathbf{M}$ is a preconditioning matrix. In this notebook, rather than define the potential well via numpy, I will specify the log-posterior ($V(\theta_t)$) directly in TensorFlow and exploit automatic differentiation in order to obtain the gradient ($\nabla_\theta V(\theta_t)$).

```python
def inference(x_ph, n_feats, n_classes):
    if n_classes == 2:
        n_classes -= 1
    with tf.name_scope("linear"):
        weights = tf.Variable(tf.zeros([n_feats, n_classes], name='weights'))
    logits = tf.matmul(x_ph, weights)
    return logits, weights
```

```python
def loss(labels_ph, logits, weights, l2_reg):
    if logits.get_shape()[1].value == 1:
        labels = tf.to_float(labels_ph)
        nll = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(
            labels=labels, logits=tf.squeeze(logits)))
    else:
        labels = tf.to_int64(labels_ph)
        nll = tf.losses.sparse_softmax_cross_entropy(
            labels=labels, logits=logits)
    reg = l2_reg * tf.reduce_sum(tf.square(weights))
    return nll + reg
```

Define constants.

```python
n_feats = 2
n_classes = 2
```

Define placeholders.

```python
x_ph = tf.placeholder(tf.float32, [None, n_feats])
labels_ph = tf.placeholder(tf.int32, [None])
```

Define potential well.

```python
logits, weights = inference(x_ph, n_feats, n_classes)
V = loss(labels_ph, logits, weights, 0.5)
```

Compute gradient of the potential well.

```python
grad_V = tf.gradients(V, [weights])
opt = tf.train.GradientDescentOptimizer(learning_rate=1e-3)
```

```python
grads_and_vars = opt.compute_gradients(V, [weights])
langevin_grads_and_vars = [
    (g + np.sqrt(2) * tf.random.normal(g.get_shape(), stddev=1/np.sqrt(opt._learning_rate)), v)
    for g, v in grads_and_vars]
train_op = opt.apply_gradients(langevin_grads_and_vars)
```

With the TensorFlow computational graph defined, we can proceed to applying the usual (un-preconditioned) Langevin dynamics equation to sample from this distribution.

```python
np.random.seed(0)
n = 500
x = np.random.normal(scale=2, size=(n, n_feats))
theta_star = np.array([2, -1])
y = np.random.binomial(1, sigmoid(x.dot(theta_star)))
```

```python
sess = tf.Session()
sess.run(tf.global_variables_initializer())
```

```python
n_samples = 5000
n_thin = 10
Theta = np.zeros((n_samples, n_feats))
```

```python
sample_index = 0
for i in range(n_samples*n_thin):
    sess.run(train_op, {x_ph: x, labels_ph: y})
    if (i+1) % n_thin == 0:
        Theta[sample_index] = sess.run(weights).ravel()
        sample_index += 1
```

```python
kernel = spst.gaussian_kde(Theta.T)
bounds_min, bounds_max = Theta.min(axis=0), Theta.max(axis=0)
n_grid = 30
Theta_1_grid, Theta_2_grid = np.meshgrid(
    np.linspace(bounds_min[0], bounds_max[0], n_grid),
    np.linspace(bounds_min[1], bounds_max[1], n_grid))
Theta_grid = np.vstack([Theta_1_grid.ravel(), Theta_2_grid.ravel()])
f_grid = np.reshape(kernel(Theta_grid), Theta_1_grid.shape)
```

```python
plt.figure(figsize=(7, 7))
plt.grid()
plt.contourf(Theta_1_grid, Theta_2_grid, f_grid, cmap='Blues')
cset = plt.contour(Theta_1_grid, Theta_2_grid, f_grid, colors="k")
plt.clabel(cset, inline=1, fontsize=14)
plt.xlabel("$\\theta_1$", fontsize=18)
plt.ylabel("$\\theta_2$", fontsize=18)
plt.title("Stationary Distribution of Stochastic Dynamics", fontsize=18)
plt.show()
```

```python
Theta
```

    array([[ 1.72888517, -0.67463535],
           [ 1.7779882 , -0.69420904],
           [ 1.7275157 , -0.70228869],
           ...,
           [ 2.068645  , -0.74573219],
           [ 2.02805161, -0.8651185 ],
           [ 1.94896173, -0.72809547]])

```python
# sess.close()
```

```python

```

```python

```

```python

```


  Finite Element Analysis (1-D Elements)

  Pramod Kumar Yadav
  @iampramodyadav

---

# Natural Coordinate: Shape Funtion and its derivative

Pramod Kumar Yadav (@iampramodyadav)

---

- This programme calculates the values of Shape functions and their derivatives.
- plot the Shape funtions

## Function for Shape function
- [Nptel Reference](https://nptel.ac.in/content/storage2/courses/112104116/lecture-11/11_10.htm)
- [SymPy Documentation](https://docs.sympy.org/latest/tutorial/basic_operations.html)

- **Shape Functions in Natural Coordinate**
  -
  -Here,Approximation of Order p,
  - physical coordinate ($x$) and natural coordinate $\xi$ (here, z)

```python
from sympy import *
import numpy as np
```

```python
z=Symbol('z')
```

```python
def Shape(p):
  '''
  =====================================================
  Shape(p)
  p:  order (p) of approximation
  z:  value of natural coordinate
  This function return Shape funtions values at given p
  =====================================================
  '''
  z=Symbol('z')
  n=[]

  for i in range(0, p+1):
    point=-1
    point=point+2*i/p
    n.append(point)

  shape=[1]*(p+1)

  for i in range(0,p+1):
    for j in range(0,p+1):
      if i!=j:
        shape[i]=shape[i]*((z-n[j])/(n[i]-n[j]))

  return shape
```

```python
print(Shape.__doc__)
```

      =====================================================
      Shape(p)
      p:  order (p) of approximation
      z:  value of natural coordinate
      This function return Shape funtions values at given p
      =====================================================

```python
Shape(2)
```

    [-1.0*z*(0.5 - 0.5*z), (1.0 - 1.0*z)*(1.0*z + 1.0), 1.0*z*(0.5*z + 0.5)]

---

## Function for derivative of Shape function
- [SymPy Documentation](https://docs.sympy.org/latest/tutorial/calculus.html)
- [Reference](https://stackoverflow.com/questions/47998930/differentiation-of-a-list-of-functions-with-respect-to-a-list-of-variables)

```python
def dShape(p):
  '''
  =====================================================
  dShape(p)
  p:  order (p) of approximation

  z:  natural coordinate variable
  This function return derivative of Shape funtions values at given p
  =====================================================
  '''
  z=Symbol('z')
  f=Shape(p)
  z=symbols('z')
  return [simplify(diff(f1)) for f1 in f]
```

```python
print(dShape.__doc__)
```

      =====================================================
      dShape(p)
      p:  order (p) of approximation

      z:  natural coordinate variable
      This function return derivative of Shape funtions values at given p
      =====================================================

---

### Examples

```python
df=dShape(3)
print('Derivative of Shape function:\n',df)
```

    Derivative of Shape function:
     [-1.6875*z**2 + 1.125*z + 0.0625000000000001, 5.0625*z**2 - 1.125*z - 1.6875, -5.0625*z**2 - 1.125*z + 1.6875, 1.6875*z**2 + 1.125*z - 0.0624999999999999]

```python
f=Shape(3)
print('Shape funtion:\n',f ,'\n')

print('Simplified Shape funtion:\n',[simplify(f1) for f1 in f])
```

    Shape funtion:
     [(0.25 - 0.75*z)*(0.5 - 0.5*z)*(-1.5*z - 0.5), (0.5 - 1.5*z)*(0.75 - 0.75*z)*(1.5*z + 1.5), (1.5 - 1.5*z)*(0.75*z + 0.75)*(1.5*z + 0.5), (0.5*z + 0.5)*(0.75*z + 0.25)*(1.5*z - 0.5)]

    Simplified Shape funtion:
     [-0.5625*z**3 + 0.5625*z**2 + 0.0625*z - 0.0625, 1.125*(z - 1)*(z + 1)*(1.5*z - 0.5), -1.125*(z - 1)*(z + 1)*(1.5*z + 0.5), 0.5*(0.75*z + 0.25)*(z + 1)*(1.5*z - 0.5)]

```python
simplify(f[0]) #first term
```

$\displaystyle - 0.5625 z^{3} + 0.5625 z^{2} + 0.0625 z - 0.0625$

```python
df[0] #deruvative of first term
```

$\displaystyle - 1.6875 z^{2} + 1.125 z + 0.0625000000000001$

---

## Visualization: Plotting Shape functions

```python
import seaborn as sns
```

```python
def plotShape(p):
  '''
  =====================================================
  plotShape(p)

  p: order (p) of approximation
  z:value of natural coordinate
  This function return plot of Shape funtions (Natural Coordinate)
  =====================================================
  '''
  sns.set()
  sns.set_style("whitegrid", {'grid.linestyle': '--'})

  z=Symbol('z')
  f=Shape(p)
  p1=plot(0,(z,-1,1),show=False)
  for i in f:
    p2=plot(i,(z,-1,1),show=False)
    p1.append(p2[0])

  return p1.show()
```

```python
print(plotShape.__doc__)
```

      =====================================================
      plotShape(p)

      p: order (p) of approximation
      z:value of natural coordinate
      This function return plot of Shape funtions (Natural Coordinate)
      =====================================================

```python
plotShape(1)
```

```python
plotShape(2)
```

```python
plotShape(3)
```

```python
plotShape(4)
```

```python
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = 14, 8

plotShape(5)
```

---

# Gauss-Legendre Quadrature implementation in Python
Pramod Kumar Yadav (@iampramodyadav)

---

  Important Link and Formula Used

* [Gaussian quadrature](https://en.wikipedia.org/wiki/Gaussian_quadrature)

* [Legendre polynomials](https://en.wikipedia.org/wiki/Legendre_polynomials#Recurrence_relations)

* [Newton–Raphson method](https://en.wikipedia.org/wiki/Newton%27s_method)

* **Gaussian quadrature Rule**

$${\displaystyle \int _{-1}^{1}f(x)\,dx\approx \sum _{i=1}^{n}w_{i}f(x_{i})}$$

* **Bonnet’s recursion formula for Legendre Polynomial**

    * $$\boxed{\displaystyle (n+1)P_{n+1}(x)=(2n+1)xP_{n}(x)-nP_{n-1}(x)}$$

    * $${\displaystyle P_{0}(x)=1\,}$$

    * $${\displaystyle\quad P_{1}(x)=x}$$

* Recursive equation for derivative of Legendre Polynomial:
  * $$\boxed{\displaystyle {\frac {x^{2}-1}{n}}{\frac {d}{dx}}P_{n}(x)=xP_{n}(x)-P_{n-1}(x)\,}$$

* The $i-th$ Gauss node, $x_i$, is the $i-th$ root of $P_n$ and the weights are given by the formula (Abramowitz & Stegun 1972)

$$\boxed{\displaystyle w_{i}={\frac {2}{\left(1-x_{i}^{2}\right)\left[P'_{n}(x_{i})\right]^{2}}}.}$$
* Newton–Raphson method

$${\displaystyle x_{1}=x_{0}-{\frac {f(x_{0})}{f'(x_{0})}}}$$

* The first guess ${\displaystyle x_{0}}$ for the ${\displaystyle i}$-th root of a ${\displaystyle n}$-order polynomial ${\displaystyle P_{n}}$ can be given by

$${\displaystyle x_{0}=\cos \left({\frac {\pi (i-1/4)}{n+1/2}}\right)}$$

## Legendre Polynomial (symbolic)

[SymPy Documentation](https://docs.sympy.org/latest/tutorial/basic_operations.html)

**Bonnet’s recursion formula for Legendre Polynomial**

  *  $$\boxed{\displaystyle (n+1)P_{n+1}(x)=(2n+1)xP_{n}(x)-nP_{n-1}(x)}$$

  * $${\displaystyle P_{0}(x)=1\,}$$

  * $${\displaystyle\quad P_{1}(x)=x}$$

```python
from sympy import *
import numpy as np
```

```python
x=symbols('x')
```

```python
def Legendre(n):
  """
  =====================================================
  n: Order of polynomial
  x: Variable
  This function print Legendre polynomial of order n (Symbolic)
  =====================================================
  """

  x=symbols('x')
  if (n==0):
    return x*0+1.0
  elif (n==1):
    return x
  else:
    return ((2.0*n-1.0)*x*Legendre(n-1)-(n-1)*Legendre(n-2))/n
```

```python
print(Legendre.__doc__)
```

      =====================================================
      n: Order of polynomial
      x: Variable
      This function print Legendre polynomial of order n (Symbolic)
      =====================================================

**Example:**

```python
Legendre(2)
```

$\displaystyle 1.5 x^{2} - 0.5$

```python
Legendre(5)
```

$\displaystyle - 1.33333333333333 x \left(1.5 x^{2} - 0.5\right) + 1.8 x \left(- 1.125 x^{2} + 1.75 x \left(1.66666666666667 x \left(1.5 x^{2} - 0.5\right) - \frac{2 x}{3}\right) + 0.375\right) + \frac{8 x}{15}$

```python
simplify(Legendre(5))
```

$\displaystyle x \left(7.875 x^{4} - 8.75 x^{2} + 1.875\right)$

## Derivative of the Legendre polynomials
[Reference](https://en.wikipedia.org/wiki/Legendre_polynomials#Recurrence_relations)

$$\boxed{\displaystyle {\frac {x^{2}-1}{n}}{\frac {d}{dx}}P_{n}(x)=xP_{n}(x)-P_{n-1}(x)\,}$$

```python
def dLegendre(n):
  """
  =====================================================
  n: Order of polynomial
  x: Variable
  This function print Derivative of Legendre polynomial of order n  (Symbolic)
  =====================================================
  """
  x=symbols('x')
  if (n==0):
    return x*0
  elif (n==1):
    return x*0+1.0
  else:
    return (n/(x**2-1.0))*(x*Legendre(n)-Legendre(n-1))
```

```python
print(dLegendre.__doc__)
```

      =====================================================
      n: Order of polynomial
      x: Variable
      This function print Derivative of Legendre polynomial of order n  (Symbolic)
      =====================================================

**Example:**

```python
simplify(dLegendre(2))
```

$\displaystyle 3.0 x$

```python
dLegendre(5)
```

$\displaystyle \frac{5 \left(1.125 x^{2} - 1.75 x \left(1.66666666666667 x \left(1.5 x^{2} - 0.5\right) - \frac{2 x}{3}\right) + x \left(- 1.33333333333333 x \left(1.5 x^{2} - 0.5\right) + 1.8 x \left(- 1.125 x^{2} + 1.75 x \left(1.66666666666667 x \left(1.5 x^{2} - 0.5\right) - \frac{2 x}{3}\right) + 0.375\right) + \frac{8 x}{15}\right) - 0.375\right)}{x^{2} - 1.0}$

```python
simplify(dLegendre(5))
```

$\displaystyle \frac{0.333333333333333 \left(118.125 x^{6} - 196.875 x^{4} + 84.375 x^{2} - 5.625\right)}{x^{2} - 1}$

### Visualization: Plot Legendre Polynomial

```python
f=Legendre(2)
```

```python
from matplotlib import style
import matplotlib.pyplot as plt

#plt.rcParams['figure.figsize'] = 14, 8
style.use('ggplot')
plot(f,(x,-1,1),legend=True)
```

```python

```

```python
f1=Legendre(1)
f2=simplify(Legendre(3))
```

```python
pl=plot(f1,f2,(x,-1,1),legend=True,show=False)
pl[0].line_color = 'red'
pl[1].line_color = 'green'
pl.show()
```

```python
plot(Legendre(1),Legendre(2),Legendre(3),Legendre(4),Legendre(5),(x,-1,1))
```

```python
import seaborn as sns
sns.set()
plt.rcParams['figure.figsize'] = 14, 8
sns.set_style("whitegrid", {'grid.linestyle': '--'})
p=plot(Legendre(1),Legendre(2),Legendre(3),Legendre(4),Legendre(5),(x,-1,1),title='Legendre PolyNomial',show=False);
p[0].line_color = 'red'
p[1].line_color = 'green'
p[2].line_color = 'yellow'
p[3].line_color = ['b','r']

p.show()
```

## Legedre Roots

* The first guess ${\displaystyle x_{0}}$ for the ${\displaystyle i}$-th root of a ${\displaystyle n}$-order polynomial ${\displaystyle P_{n}}$ can be given by
$${\displaystyle x_{0}=\cos \left({\frac {\pi (i-1/4)}{n+1/2}}\right)}$$

* Newton–Raphson method

$${\displaystyle x_{1}=x_{0}-{\frac {f(x_{0})}{f'(x_{0})}}}$$

```python
def LegendreRoots(polyorder,tolerance=1e-20):
  """
  =====================================================
  LegendreRoots(polyorder,tolerance=1e-20)

  polyorder: Order of polynomial(>2)
  tolerance: tolerance of error (default: 1e-20)
  Output: [roots, err]
  This function return Roots of Legendre polynomial
  =====================================================
  """
  if polyorder<2:
    err=1

  else:
    roots=[]
		                                                  # The polynomials are alternately even and odd functions.
                                                      # So we evaluate only half the number of roots.
    for i in range(1,int((polyorder)/2) +1):
      x1=cos(pi*(i-0.25)/(polyorder+0.5))
      error=10*tolerance
      iters=0

      while (error>tolerance) and (iters<1000):
        dx=-Legendre(polyorder)/dLegendre(polyorder)
        dx=N(dx.subs(x,x1))
        x1=N(x1+dx)
        iters=iters+1
        error=abs(dx)

      #print(roots)
      roots.append(x1)
      #print(roots)
		                                                  # Use symmetry to get the other roots
    roots=np.array(roots)
    if polyorder%2==0:
      roots=np.concatenate( (-1.0*roots, roots[::-1]) )

    else:
      roots=np.concatenate( (-1.0*roots, [0.0], roots[::-1]) )

    err=0
                                                    # successfully determined roots
  return [roots, err]
```

```python
print(LegendreRoots.__doc__)
```

      =====================================================
      LegendreRoots(polyorder,tolerance=1e-20)

      polyorder: Order of polynomial(>2)
      tolerance: tolerance of error (default: 1e-20)
      Output: [roots, err]
      This function return Roots of Legendre polynomial
      =====================================================

**Examples**

```python
[Xis, err]=LegendreRoots(8)
print(Xis)
```

    [-0.960289856497536 -0.796666477413627 -0.525532409916329
     -0.183434642495650 0.183434642495650 0.525532409916329 0.796666477413627
     0.960289856497536]

```python
[Xis, err]=LegendreRoots(2)
print(Xis)
```

    [-0.577350269189626 0.577350269189626]

```python
[Xis, err]=LegendreRoots(4)
print(Xis)
```

    [-0.861136311594053 -0.339981043584856 0.339981043584856 0.861136311594053]

```python
[Xis, err]=LegendreRoots(5)
print(Xis)
```

    [-0.906179845938664 -0.538469310105683 0.0 0.538469310105683
     0.906179845938664]

## Weight Coefficient

* the $i-th$ Gauss node, $x_i$, is the $i-th$ root of $P_n$ and the weights are given by the formula (Abramowitz & Stegun 1972)

$${\displaystyle w_{i}={\frac {2}{\left(1-x_{i}^{2}\right)\left[P'_{n}(x_{i})\right]^{2}}}.}$$

```python
def GaussLegendreWeights(polyorder):
  """
  =====================================================
  GaussLegendreWeights(polyorder)

  polyorder: Order of polynomial
  Output: [W, xis, err]
  This function return Weights(W) and Roots(xis) of Legendre polynomial
  =====================================================
  """
  W=[]
  [xis,err]=LegendreRoots(polyorder)
  xis=list(xis)
  if err==0:
    for x1 in xis:
      w=2.0/( (1.0-x**2)*(dLegendre(polyorder)**2) )
      w=w.subs(x,x1)
      W.append(w)
      err=0
  else:
    err=1 # could not determine roots - so no weights
  return [W, xis, err]
```

```python
print(GaussLegendreWeights.__doc__)
```

      =====================================================
      GaussLegendreWeights(polyorder)

      polyorder: Order of polynomial
      Output: [W, xis, err]
      This function return Weights(W) and Roots(xis) of Legendre polynomial
      =====================================================

**Example**

```python
[W, xis, err]=GaussLegendreWeights(2)
print(W)
```

    [1.00000000000000, 1.00000000000000]

```python
[W, xis, err]=GaussLegendreWeights(5)
print(W)
```

    [0.236926885056189, 0.478628670499366, 0.568888888888889, 0.478628670499366, 0.236926885056189]

## Function Plot Legendre Polynomial

```python
def PlotLegendre(polyorder):
  """
  =====================================================
  PlotLegendre(polyorder)

  This function plot legendre plynomial
  =====================================================
  """
  sns.set()
  sns.set_style("whitegrid", {'grid.linestyle': '--'})
  plot(Legendre(polyorder),(x,-1,1))

```

```python
PlotLegendre(4)
```

```python
PlotLegendre(2)
```

## Testing Final Result

```python
for Polyorder in range(2,7):
  plt.rcParams['figure.figsize'] = 6.4, 4.8
  plot(Legendre(Polyorder),(x,-1,1))

  [W, xis, err]=GaussLegendreWeights(Polyorder)
  print("order:", Polyorder)
  print("Weights:", W)
  print ("Roots    : ",xis)

  print('='*110)
  print('\n\n')
  print('='*110)
```

---

# Values of Shape Function at Gauss Points (Legendre root)

Pramod Kumar Yadav (iampramodyadav)

Flow chart for Gauss-Legendre Quadrature use in FEA

---

```python
def ShapeLegRoot(p):
  """
  =====================================================
  ShapeLegRoot(p)

  p : order of approximation (integer)

  Output: [n, W, xis, ShapeValueXi, dShapeValueXi]
  Here,
  n-    Order of gauss legendre polynomial
  W-    Legendre Weights
  xis-  Gauss point coordinates (real)
  ShapeValueXi-   Value of Shape function at  at all the  Xi (real)(Legendre Roots)
  ShapeValueXi-   Value of derivative of Shape function at Gauss points Xi(Legendre Roots)
  =====================================================
  """

  p=int(p)

  pbar=max(2*p, p+2)
  if pbar%2!=0:                                   #if odd
    n=(pbar+1)/2
  else:
    n=(pbar+2)/2

  n=int(n)

  #print("Order of gauss legendre polynomial:",n)

  [W, xis, err]=GaussLegendreWeights(n)           #order: Polyorder(n), Weights:  W, Roots: xis

  ShapeValueXi=np.zeros((n, p+1))
  dShapeValueXi=np.zeros((n, p+1))

  #print('ndofel(p+1):',p+1)
  #print('Shape of Shape value matrix:',ShapeValueXi.Shape)

  s=Shape(p)
  ds=dShape(p)

  for i in range(0,n):
    for j in range(0,p+1):

      ShapeValueXi[i][j]=s[j].subs(z,xis[i])          #w.subs(x,x1)
      dShapeValueXi[i][j]=ds[j].subs(z,xis[i])

  return [n, W, xis, ShapeValueXi, dShapeValueXi]
```

```python
print(ShapeLegRoot.__doc__)
```

      =====================================================
      ShapeLegRoot(p)

      p : order of approximation (integer)

      Output: [n, W, xis, ShapeValueXi, dShapeValueXi]
      Here,
      n-    Order of gauss legendre polynomial
      W-    Legendre Weights
      xis-  Gauss point coordinates (real)
      ShapeValueXi-   Value of Shape function at  at all the  Xi (real)(Legendre Roots)
      ShapeValueXi-   Value of derivative of Shape function at Gauss points Xi(Legendre Roots)
      =====================================================

---

**Example**

```python
[n, W,xis,ShapeValueXi,dShapeValueXi]=ShapeLegRoot(1)
print('Shape Function values at Legendre roots:\n',ShapeValueXi)
print('Derivative of Shape Function values at Legendre roots\n:',dShapeValueXi)
```

    Shape Function values at Legendre roots:
     [[0.78867513 0.21132487]
     [0.21132487 0.78867513]]
    Derivative of Shape Function values at Legendre roots
    : [[-0.5  0.5]
     [-0.5  0.5]]

```python
[n, W,xis,ShapeValueXi,dShapeValueXi]=ShapeLegRoot(5)
print('Shape Function values at Legendre roots:\n',ShapeValueXi)
print('Derivative of Shape Function values at Legendre roots\n:',dShapeValueXi)
```

    Shape Function values at Legendre roots:
     [[ 0.66464722  0.6750085  -0.61277567  0.39633694 -0.14644321  0.02322621]
     [ 0.04145294  1.14719867 -0.30450086  0.16307144 -0.05567618  0.008454  ]
     [-0.00517461  0.05451099  1.0201782  -0.08982382  0.02349007 -0.00318084]
     [-0.00318084  0.02349007 -0.08982382  1.0201782   0.05451099 -0.00517461]
     [ 0.008454   -0.05567618  0.16307144 -0.30450086  1.14719867  0.04145294]
     [ 0.02322621 -0.14644321  0.39633694 -0.61277567  0.6750085   0.66464722]]
    Derivative of Shape Function values at Legendre roots
    : [[-4.2710757   7.68824036 -5.97290352  3.67209091 -1.32305421  0.20670216]
     [-0.87306492 -2.03347341  4.8542633  -2.76385889  0.96414612 -0.14801221]
     [ 0.14181715 -1.57319431 -0.20311928  2.13898231 -0.58491528  0.08042941]
     [-0.08042941  0.58491528 -2.13898231  0.20311928  1.57319431 -0.14181715]
     [ 0.14801221 -0.96414612  2.76385889 -4.8542633   2.03347341  0.87306492]
     [-0.20670216  1.32305421 -3.67209091  5.97290352 -7.68824036  4.2710757 ]]

# Element Calculation

* Here linear mapping function used between the physical coordinate (x) and the natural coordinate $\xi$ (here, z) :

  *

  * Hence, $x_1^k$ (xL) and $x_{p+1}^k$ (xR) are the coordinates of the end nodes of the element $k$ and $p$ denotes the order of approximation.

* Expressions for Element Stiffness Matrix and Force Vector
  * $$\boxed{\displaystyle k_{i j}^{k}=\int_{-1}^{1} \hat{E}(\xi) \hat{A}(\xi) \frac{d \hat{N}_{i}(\xi)}{d \xi} \frac{d \hat{N}_{j}(\xi)}{d \xi} \frac{2}{h_{k}} d \xi \quad \text { for } i, j=1,2, \ldots, p+1}$$

  * $$\boxed{\displaystyle f_{i}^{k}=\int_{-1}^{1} \hat{f}(\xi) \hat{N}_{i}(\xi) \frac{h_{k}}{2} d \xi \quad i=1,2, \ldots, p+1}$$

## Function for Local $K_{ij}$, & $f_i$ if 1-D Bar Elements

```python
def ElemKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0):
  """
  =====================================================
  ElemKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

  k :   element number (integer)
  p :   order of approximation (integer)
  xL :  x -coordinate of the left end of the element (real)
  xR :  x -coordinate of the right end of the element (real)
  E :   Young's modules ( E ) of the bar material which is taken as constant (real)
  a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
  f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

  Output: [Ke, fe]
  Ke : element stiffness matrix (real)
  fe : element force vector (real)
  =====================================================
  """

  Ke=np.zeros((p+1,p+1))  # size of element matrix
  h=xR - xL               # h : element length (real)

  [n, W,xis,ShapeValueXi,dShapeValueXi]=ShapeLegRoot(p)
  fe=np.zeros(p+1)

  for m in range(0,n):
    xi=xis[m]
    x = xL*(1 - z)/2 + xR*(1 + z)/2

    EA = E*(a0 + a1*x+a2*(x**2))
    EA = EA.subs(z,xi)

    f = f0 + f1*x + f2*x*x
    f=f.subs(z,xi)

    for i in range(0, p+1):
      fe[i] = fe[i] + f*ShapeValueXi[m][i]*(h/2)*W[m]

      for j in range(0,p+1):
        Ke[i][j] = Ke[i][j]+EA*dShapeValueXi[m][i]*dShapeValueXi[m][j]*(2/h)*W[m]

  return [Ke, fe]
```

```python
print(ElemKfi.__doc__)
```

      =====================================================
      ElemKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

      k :   element number (integer)
      p :   order of approximation (integer)
      xL :  x -coordinate of the left end of the element (real)
      xR :  x -coordinate of the right end of the element (real)
      E :   Young's modules ( E ) of the bar material which is taken as constant (real)
      a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
      f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

      Output: [Ke, fe]
      Ke : element stiffness matrix (real)
      fe : element force vector (real)
      =====================================================

---

### Example:1

```python
[ke,fe]=ElemKfi(k=1,p=1,xL=0,xR=10,a0=6,a1=-0.1,a2=0,E=30,f0=0.1296,f1=-0.00216)
print("Local Stiffness Matrix: \n\n", ke,"\n\n")
print("Lobal Force Vector (due to distributed force):\n\n ", fe)
```

    Local Stiffness Matrix:

     [[ 16.5 -16.5]
     [-16.5  16.5]]

    Lobal Force Vector (due to distributed force):

      [0.612 0.576]

```python
[ke,fe]=ElemKfi(k=1,p=1,xL=10,xR=20,a0=6,a1=-0.1,E=30,f0=0.1296,f1=-0.00216)
print("Local Stiffness Matrix: \n\n", ke,"\n\n")
print("Lobal Force Vector (due to distributed force):\n\n ", fe)
```

    Local Stiffness Matrix:

     [[ 13.5 -13.5]
     [-13.5  13.5]]

    Lobal Force Vector (due to distributed force):

      [0.504 0.468]

```python
[ke,fe]=ElemKfi(k=1,p=1,xL=20,xR=30,a0=6,a1=-0.1,E=30,f0=0.1296,f1=-0.00216)
print("Local Stiffness Matrix: \n\n", ke,"\n\n")
print("Lobal Force Vector (due to distributed force):\n\n ", fe)
```

    Local Stiffness Matrix:

     [[ 10.5 -10.5]
     [-10.5  10.5]]

    Lobal Force Vector (due to distributed force):

      [0.396 0.36 ]

### Example:2
- book:
Fundamentals of Finite Element Analysis
Linear Finite Element Analysis-Ioannis Koutromanos
- example-33.2
- page-87/729

**Stiffeness and Force**

```python
[ke,fe]=ElemKfi(k=1,p=1,xL=0,xR=5,E=5000,a0=1,a1=0,a2=1/400,f0=2) #for element-1
print("Local Stiffness Matrix: \n\n", ke,"\n\n")
print("Lobal Force Vector (due to distributed force):\n\n ", fe)
```

    Local Stiffness Matrix:

     [[ 1020.83333333 -1020.83333333]
     [-1020.83333333  1020.83333333]]

    Lobal Force Vector (due to distributed force):

      [5. 5.]

```python
[ke,fe]=ElemKfi(k=1,p=1,xL=5,xR=20,E=5000,a0=1,a1=0,a2=1/400,f0=2) #for element-2
print("Local Stiffness Matrix: \n\n", ke,"\n\n")
print("Lobal Force Vector (due to distributed force):\n\n ", fe)
```

    Local Stiffness Matrix:

     [[ 479.16666667 -479.16666667]
     [-479.16666667  479.16666667]]

    Lobal Force Vector (due to distributed force):

      [15. 15.]

##  Function for Global $K_{ij}$, & $f_i$ if 1-D Bar Devided in Equal Size of Elements

```python
def GlobalKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0):

  """
  =====================================================
    Note: For elements of equal length

    GlobalKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

    k :   number of elements (integer)
    p :   order of approximation (integer)
    xL :  x -coordinate of the left end of the element (real)
    xR :  x -coordinate of the right end of the element (real)
    E :   Young's modules ( E ) of the bar material which is taken as constant (real)
    a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
    f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

    Output: [Kg, fg]
    Kg : Global stiffness matrix (real)
    fg : Global force vector (real)
  =====================================================
  """

  Kg=np.zeros((k*p+1,k*p+1))   #size of element matrix
  h=xR - xL

  [n, W,xis,ShapeValueXi,dShapeValueXi]=ShapeLegRoot(p)
  fg=np.zeros(k*p+1)

  for e in range(0,k*p,p):
    #xL=N[e]
    #xR=N[e+1]
    for m in range(0,n):
      xi=xis[m]
      x = (xL+h*int(e/p))*(1 - z)/2 + (xR+h*int(e/p))*(1 + z)/2

      EA = E*(a0 + a1*x+a2*(x**2))
      EA = EA.subs(z,xi)

      f = f0 + f1*x + f2*x*x
      f=f.subs(z,xi)

      for i in range(0, p+1):
        fg[i+e] = fg[i+e] + f*ShapeValueXi[m][i]*(h/2)*W[m]

        for j in range(0,p+1):
          Kg[i+e][j+e] = Kg[i+e][j+e]+EA*dShapeValueXi[m][i]*dShapeValueXi[m][j]*(2/h)*W[m]

  return [Kg, fg]
```

```python
print(GlobalKfi.__doc__)
```

      =====================================================
        Note: For elements of equal length

        GlobalKfi(k,p,xL,xR,E,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

        k :   number of elements (integer)
        p :   order of approximation (integer)
        xL :  x -coordinate of the left end of the element (real)
        xR :  x -coordinate of the right end of the element (real)
        E :   Young's modules ( E ) of the bar material which is taken as constant (real)
        a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
        f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

        Output: [Kg, fg]
        Kg : Global stiffness matrix (real)
        fg : Global force vector (real)
      =====================================================

### **Example:1**

**Solution:**
Local Stiffeness Matrices (alreday evaluated above)

Global Stiffeness Matrix
1. Equal Elements Length (Methode-1)

```python
[Kij, fi]=GlobalKfi(k=3,p=1,xL=0,xR=10,a0=6,a1=-0.1,a2=0,E=30,f0=0.1296,f1=-0.00216)
print("Global Stiffness Matrix: \n\n", Kij,"\n\n")
print("Global Force Vector (due to distributed force):\n\n ", fi)
```

    Global Stiffness Matrix:

     [[ 16.5 -16.5   0.    0. ]
     [-16.5  30.  -13.5   0. ]
     [  0.  -13.5  24.  -10.5]
     [  0.    0.  -10.5  10.5]]

    Global Force Vector (due to distributed force):

      [0.612 1.08  0.864 0.36 ]

---

##  Function for Global $K_{ij}$, & $f_i$ if 1-D Bar Devided in Unequal Size of Elements

*   NodesList=Nodes X- Cordinate as list
*   k=Number of element
*   here,K+1=length(NodesList)

```python
def GlobalKfiU(k,p,E,NodesList,a0,a1=0,a2=0,f0=0,f1=0,f2=0):

  """
  =====================================================
    Note: For elements of unequal (or equal) length

    GlobalKfiU(k,p,E,NodesList,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

    k :   number of elements (integer)
    p :   order of approximation (integer)
    NodesList:  [x0,x1,x2....xk] (X coordinates of nodes)
    E :   Young's modules ( E ) of the bar material which is taken as constant (real)
    a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
    f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

    Output: [Kg, fg]
    Kg : Globel stiffness matrix (real)
    fg : Globel force vector (real)
  =====================================================
  """

  Kg=np.zeros((k*p+1,k*p+1))                        #size of element matrix

  [n, W,xis,ShapeValueXi,dShapeValueXi]=ShapeLegRoot(p)
  fg=np.zeros(k*p+1)

  for e in range(0,k*p,p):
    xL=NodesList[int(e/p)]
    xR=NodesList[int(e/p)+1]
    h=xR - xL

    for m in range(0,n):
      xi=xis[m]
      x = (xL)*(1 - z)/2 + (xR)*(1 + z)/2

      EA = E*(a0 + a1*x+a2*(x**2))
      EA = EA.subs(z,xi)

      f = f0 + f1*x + f2*x*x
      f=f.subs(z,xi)

      for i in range(0, p+1):
        fg[i+e] = fg[i+e] + f*ShapeValueXi[m][i]*(h/2)*W[m]

        for j in range(0,p+1):
          Kg[i+e][j+e] = Kg[i+e][j+e]+EA*dShapeValueXi[m][i]*dShapeValueXi[m][j]*(2/h)*W[m]

  return [Kg, fg]
```

```python
print(GlobalKfiU.__doc__)
```

      =====================================================
        Note: For elements of unequal (or equal) length

        GlobalKfiU(k,p,E,NodesList,a0,a1=0,a2=0,f0=0,f1=0,f2=0)

        k :   number of elements (integer)
        p :   order of approximation (integer)
        NodesList:  [x0,x1,x2....xk] (X coordinates of nodes)
        E :   Young's modules ( E ) of the bar material which is taken as constant (real)
        a0,a1,a2 : coefficients in (A=a0 + a1*x + a2*(x**2)) for the area of cross-section ( A ) of the bar (real)
        f0,f1,f2 : coefficients in (f = f0 + f1*x + f2*x*x) for the distributed force ( f ) acting on the bar (real)

        Output: [Kg, fg]
        Kg : Globel stiffness matrix (real)
        fg : Globel force vector (real)
      =====================================================

### **Example:2**
**

**Solution**

```python
[Kij,fi]=GlobalKfiU(k=2,p=1,NodesList=[0,5,20],E=5000,a0=1,a1=0,a2=1/400,f0=2)
print("Global Stiffness Matrix: \n\n", Kij,"\n\n")
print("Global Force Vector (due to distributed force):\n\n ", fi)
```

    Global Stiffness Matrix:

     [[ 1020.83333333 -1020.83333333     0.        ]
     [-1020.83333333  1500.          -479.16666667]
     [    0.          -479.16666667   479.16666667]]

    Global Force Vector (due to distributed force):

      [ 5. 20. 15.]

---

**Example-1 By this methode**(metode-2)

```python
[Kij,fi]=GlobalKfiU(k=3,p=1,NodesList=[0,10,20,30],a0=6,a1=-0.1,a2=0,E=30,f0=0.1296,f1=-0.00216) #global K,f
print("Global Stiffness Matrix: \n\n", Kij,"\n\n")
print("Global Force Vector (due to distributed force):\n\n ", fi)
```

    Global Stiffness Matrix:

     [[ 16.5 -16.5   0.    0. ]
     [-16.5  30.  -13.5   0. ]
     [  0.  -13.5  24.  -10.5]
     [  0.    0.  -10.5  10.5]]

    Global Force Vector (due to distributed force):

      [0.612 1.08  0.864 0.36 ]

---

---


```python
import pulp
import numpy as np
import networkx as nx
from numpy import genfromtxt
import pandas as pd
import matplotlib.pyplot as plt
import time
import seaborn as sns
from numpy import genfromtxt
from numpy import linalg as LA
import scipy as sp
import sympy
import sys
import networkx.algorithms.approximation as nxaa
```

```python
mydata = genfromtxt('1hmk.csv', delimiter=',')
mydata = mydata[1:,1:]
m = len(mydata)
print(m)
```

    121

```python
for i in range(0,m):
    for j in range(0,m):
        if i ==j:
            mydata[i][j] = 1

print(mydata)
```

    [[1. 0. 0. ... 0. 0. 0.]
     [0. 1. 0. ... 0. 0. 0.]
     [0. 0. 1. ... 0. 0. 0.]
     ...
     [0. 0. 0. ... 1. 0. 0.]
     [0. 0. 0. ... 0. 1. 0.]
     [0. 0. 0. ... 0. 0. 1.]]

```python
plt.figure(figsize=(12,8))
plt.imshow(mydata, cmap='spring', interpolation='nearest')
plt.show()
```

### creat the graph using networkX

```python
G = nx.Graph(mydata)
```

## removing isolated nodes

```python
"""
for component in list(nx.connected_components(G)):
    if len(component)<= 3:
        for node in component:
            G.remove_node(node)
m = len(G.nodes)
print(m)
"""
```

    96

# MIP package

```python
from mip import Model, xsum, maximize, BINARY
from itertools import product
from sys import stdout as out
from mip import Model, xsum, minimize, BINARY
```

```python
m = Model("minimum_dominating_set")

I = range(len(G.nodes()))

x = [m.add_var(var_type=BINARY) for i in I]

m.objective = minimize(xsum(x[i] for i in I))

for j in G.nodes():

    m += x[j]+ xsum(x[u] for u in G.neighbors(j)) >= 1

m.optimize()
selected=[]
number_of_mds=0
for i in I:
    if x[i].x >= 0.99:
        selected = np.append(selected,i)
        number_of_mds += 1

print("selected items: {}".format(selected))
print(number_of_mds)
```

    selected items: [  1.   7.   8.  10.  14.  19.  20.  22.  23.  26.  31.  32.  34.  36.
      39.  45.  47.  48.  49.  51.  57.  58.  60.  62.  64.  68.  70.  74.
      78.  80.  82.  84.  87.  89.  90.  95.  96.  97. 101. 102. 104. 108.
     109. 112. 113. 114. 115. 116. 117. 119. 120.]
    51

# PuLp package

```python
# define the problem
prob = pulp.LpProblem("minimum_dominating_set", pulp.LpMinimize)

# define the variables
x = pulp.LpVariable.dicts("x", G.nodes(), cat=pulp.LpBinary)

# define the objective function
start_time = time.time()

for (v,u) in G.edges():

    prob += pulp.lpSum(x)

# define the constraints
for v in G.nodes():
       prob += x[v] + pulp.lpSum([x[u] for u in G.neighbors(v)]) >= 1

color_map = []
# solve
prob.solve()
end_time = time.time()
print("time = %s seconds" % (end_time - start_time))

# display solution
for v in G.nodes():
    if pulp.value(x[v]) > 0.99:

        color_map.append('red')

        print(v,end=',')

    else:
        color_map.append('dodgerblue')

plt.figure(3,figsize=(10,10))
nx.draw(G, node_color=color_map, node_size=25,width=0.55, with_labels=True,font_size=8)
plt.savefig("1b9c_center.pdf")
plt.show()

```

```python
i = 0
for v in prob.variables():
    if v.varValue == 1:
        #print(v.name, "=", v.varValue)
        i = i+1
print(i)
```

    51

```python
def nodes_connected(u, v):

    return u in G.neighbors(v)
```

```python
nodes_connected(40,213)
```

    True

```python

```


# Casing design affects frac geometry and economics
_Ohm Devani_

NOTE: This project will focus on relative economic uplift between 2 casing designs in similar geology.

## Contents
1. Model inputs
2. Base case
    - define per-frac endpoints (min rate defined by dfit initiation rate, max rate defined by hhp of fleet, water volume/frac)
    - economic constants (pump charge, water cost
3. Economic sensitivities
    1. Frac surface area (PKN) vs rate; 2 series for different casing sizes
        - \$ cost/surface area vs rate vs (y-axis 2) surface area
        - Talk about impact of other variables: stress shadow, additional height growth in absence of frac barriers, better proppant/fluid distribution between clusters
    2. Frac pump time vs rate vs $ cost/stage (y2)

### Model parameters

0.7 psi/ft frac gradient
1.0 connate water SG
Horizontal well
Midpoint lateral TVD 10,000 ft

Scenario A:
7" 32# T-95 intermediate casing to 9000 ft (6.094" ID)
4 1/2" 11.6# P-110 liner to 20000 ft (4" ID)

Scenario B:
5 1/2" 17# P-110 casing to 20000 ft (4.892" ID)

60 bbl/ft
ignore proppant
7000 psi target surface pressure
2000 psi target NWB friction

### Pipe friction estimates

Hazen-Williams for pipe friction
SPE 146674 correlation and adjustment for friction reduction

```python
def hwfriclosspsi(cfactor,ratebpm,pipeidinch,lengthft):
    hyddiam = 4*(3.14159*(pipeidinch/2)**2)/(2*3.14169*pipeidinch/2)
    hwfriclosspsi = 0.2083*((100/cfactor)**1.852)*((ratebpm*42)**1.852)/(hyddiam**4.8655)*.433*lengthft/100
    return hwfriclosspsi

def freduction(pipeidinch,ratebpm):
    velocftpersec = (5.615*144/60)*ratebpm/(3.14159*(pipeidinch/2)**2)
    fr = 6.2126*velocftpersec**0.5858
    if fr<25:
        fr=25
    elif fr>85:
        fr=85
    return fr
```

```python
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

bpmrange=list(range(0, 150))
adjfac=0.1
#sceAfric = [hwfriclosspsi(120,i,6.094,9000)*(1-freduction(6.094,i)/100)*adjfac+hwfriclosspsi(120,i,4,11000)*(1-freduction(4,i)/100)*adjfac for i in bpmrange]
#sceBfric = [hwfriclosspsi(120,i,4.892,20000)*(1-freduction(4.892,i)/100)*adjfac for i in bpmrange]

sceAfric = [hwfriclosspsi(120,i,6.094,9000)*adjfac+hwfriclosspsi(120,i,4,11000)*adjfac for i in bpmrange]
sceBfric = [hwfriclosspsi(120,i,4.892,20000)*adjfac for i in bpmrange]
```

### BHTP & Net Fracture Pressure

Ozesen, Ahsen. ANALYSIS OF INSTANTANEOUS SHUT-IN PRESSURE IN SHALE OIL AND GAS RESERVOIRS. 2017. Penn State U, Masters Thesis. https://etda.libraries.psu.edu/files/final_submissions/15314.

```python
surfpress=7000
tvd=10000
nwbfriction=1000
fracgradient=0.70

def nfp(bhtp,nwbfric,fracgrad,tvd):
    nfp = bhtp-nwbfric-fracgrad*tvd
    return nfp

bhtpA = [surfpress-i+0.433*tvd for i in sceAfric]
bhtpB = [surfpress-i+0.433*tvd for i in sceBfric]

nfpA = [nfp(i,nwbfriction,fracgradient,tvd) for i in bhtpA]
nfpB = [nfp(i,nwbfriction,fracgradient,tvd) for i in bhtpB]

plt.plot(bpmrange, bhtpA, label = "4 1/2\" liner", linestyle="-.")
plt.plot(bpmrange, bhtpB, label = "5 1/2\" casing", linestyle=":")
plt.ylim(bottom=0)
plt.grid(color='k', linestyle='-', linewidth=0.25)

plt.xlabel('Pumping Rate [bpm]')
plt.ylabel('BHP [psi]')

plt.legend()
plt.show()

plt.plot(bpmrange, nfpA, label = "4 1/2\" liner", linestyle="-.")
plt.plot(bpmrange, nfpB, label = "5 1/2\" casing", linestyle=":")
plt.ylim(bottom=0)
plt.grid(color='k', linestyle='-', linewidth=0.25)

plt.xlabel('Pumping Rate [bpm]')
plt.ylabel('Pressure available to fracture [psi]')

plt.legend()
plt.show()

#print(nfpA)
#print(nfpB)
#print(nfpA.index(57.58718507850335))
#print(nfpB.index(21.654318992093977))
```

Under these constraints, the 5 1/2" casing designs supports a maximum pump rate of 107 bpm, which is 25 bpm higher than the 4 1/2" liner design's max pump rate of 82 bpm.

Below, I calculate the time and cost savings from this added rate over a range of pumped volumes.
HHP (hp) = Surface Treating Pressure (psi) x Rate (bpm) / 40.8
Indicative pricing estimates for a 45,000 hhp frac spread, and typical pricing are $6-8,000/hr.

Plot below shows a typical frac spread will have more HHP than is required at even the highest rate.

https://www.danielep.com/news-observations/3-14-21-dep-industry-observations-frac-engine-supply-frac-newbuilds-sand-thoughts-and-q4-earnings/
https://markets.businessinsider.com/news/stocks/fracking-fluid-end-market-size-to-reach-us-678-million-in-2025-says-stratview-research-1029144444

```python
def hhp(stp,rate):
    hhp=stp*rate/40.8
    return hhp

plt.plot(bpmrange, [hhp(surfpress,i) for i in bpmrange], label = "Required HHP")
plt.ylim(bottom=0, top=50000)
plt.axhline(y=45000, color='r', linestyle='--', label = "Available HHP")
plt.grid(color='k', linestyle='-', linewidth=0.25)

plt.xlabel('Pumping Rate [bpm]')
plt.ylabel('HHP required')
plt.legend()
plt.show()
```

For simplicity, I'm only considering the hourly pumping cost and neglecting other costs like diesel.

The plot below shows pumping cost vs total volume for the two casing designs pumping at max rates which retain a few hundred psi of net frac pressure (80 vs 105).

```python
volumerange=list(range(400, 800))
pumpcost=8000 #$8000/hr
amaxrate=80
bmaxrate=105

acost=[pumpcost*1000*i/(amaxrate*60) for i in volumerange]
bcost=[pumpcost*1000*i/(bmaxrate*60) for i in volumerange]

fig, ax = plt.subplots()
ax.plot(volumerange,acost, label = "4 1/2\" liner", linestyle="-.")
ax.plot(volumerange,bcost, label = "5 1/2\" casing", linestyle=":")
ax.plot(volumerange, [a_i - b_i for a_i, b_i in zip(acost, bcost)], label = "Cost Savings", linestyle="-.", color='g')

plt.ylim(bottom=0)
ax.grid(color='k', linestyle='-', linewidth=0.25)

plt.xlabel('Total Volume [MBBL]')
plt.ylabel('Pumping Cost [$]')

fmt = '${x:,.0f}'
tick = mtick.StrMethodFormatter(fmt)
ax.yaxis.set_major_formatter(tick)

plt.legend()
plt.show()

```

### Economic sensitivities
5 1/2" max rate is approx. 30% greater than 4 1/2" liner's max rate.
Fracture surface area as a function of rate is a convolved problem requiring a simulator; I suppose a range of 0%-15% oil IP30 uplift for the range of 105-80bpm, respectively.

Economic variables:
- 75% NRI
- $50 flat net oil price
- Harmonic decline
- 800 MBBL water pumped

Production for 4 1/2" scenario:
- 800 bopd IP (24.3 MBO/mo)
- 800 MBO EUR over 30 years
- Ignoring water, gas

Production for 5 1/2" scenario:
- Variable IP
- 800 MBO EUR over 30 years
- Ignoring water, gas

```python
import numpy as np
#from sympy.solvers import solve
#from sympy import Symbol
from sympy import log

#def solvedi(np,qi,t):
#    di = Symbol('di',real=True)
#    solvedi=solve(np/log(qi/(qi/(1+di*t)))-(qi/di),di)
#    return solvedi

#adeclinerate_monthly = solvedi(800000,36500,360)
#print(N(adeclinerate_monthly[0]))

#print(N(solvedi(1000000,30000,600)))

ip=800
eur=800000
months=360

def brutedi(np,qi,t):
    calcerror=-1
    di=0.01
    while calcerror<0:
        calcerror=np-((qi/di)*log(qi/(qi/(1+di*t))))
        if calcerror>0:
            break
        else:
            di=di+0.0001
    #print(di)
    return di

#adeclinerate_monthly = brutedi(800000,36500,360)

ipuplift=np.linspace(0,0.15,num=16)
col2=(ipuplift+1)*ip
col3=np.zeros(col2.size)

i=0
while i<col2.size:
    col3[i]=brutedi(eur,col2[i]*30.4,months)
    i+=1

#print(col2)
#print(col3)
```

```python
def harmonictotal(qi,di,t):
    harmonictotal=((qi/di)*log(qi/(qi/(1+di*t))))
    return harmonictotal

def pv30yr(netoilprice,nri,ip,initd,discrate):
    cumarray=np.zeros(360)
    casharray=np.zeros(360)
    ipmonth=ip*30.4
    discratemonth=discrate/12
    j=0
    while j<casharray.size:
        if j==0:
            cumarray[j]=harmonictotal(ipmonth,initd,1)
            casharray[j]=cumarray[j]*netoilprice*nri
        else:
            cumarray[j]=harmonictotal(ipmonth,initd,j+1)
            casharray[j]=(cumarray[j]-cumarray[j-1])*netoilprice*nri
        j+=1
    #print(cumarray)
    #print(casharray)
    #print(cumarray[cumarray.size-1])
    pv30yr=np.npv(discratemonth,casharray)
    return pv30yr
```

```python
netprice=50
netri=0.75
discountrate=0.10
totalpumpedvolume=800000

#print(pv30yr(netprice,netri,920,.137,0))
col4=np.zeros(col2.size)
colincpv=np.zeros(col2.size)

i=0
while i<col4.size:
    col4[i]=pv30yr(netprice,netri,col2[i],col3[i],discountrate)
    i+=1

i=0
while i<colincpv.size:
    colincpv[i]=col4[i]-col4[0]
    i+=1
#print(colincpv)
```

    C:\Users\User\Anaconda3\lib\site-packages\ipykernel_launcher.py:22: DeprecationWarning: numpy.npv is deprecated and will be removed from NumPy 1.20. Use numpy_financial.npv instead (https://pypi.org/project/numpy-financial/).

```python
pumpratearray=np.linspace(105,80,num=16)
pumpsavings=np.zeros(pumpratearray.size)

i=0
while i<pumpsavings.size:
    if pumpratearray[i] == amaxrate:
        pumpsavings[i]=0
    else:
        pumpsavings[i]=pumpcost*(-(totalpumpedvolume/(pumpratearray[i]*60))+(totalpumpedvolume/(amaxrate*60)))
    i+=1

#print(pumpsavings)
```

    [317460.31746032 301075.2688172  284153.00546448 266666.66666667
     248587.57062147 229885.05747126 210526.31578947 190476.19047619
     169696.96969697 148148.14814815 125786.16352201 102564.1025641
      78431.37254902  53333.33333333  27210.88435374      0.        ]

```python
farray = np.column_stack((ipuplift, col2)) #ip bopd
farray = np.column_stack((farray, col3)) #di
farray = np.column_stack((farray, colincpv))
farray = np.column_stack((farray, pumpratearray))
finalarray = np.column_stack((farray, pumpsavings))

#print(finalarray[:,5])

finaldf = pd.DataFrame(finalarray, columns = ['IP_uplift','IP_BOPD','Di_monthly','Acceration_PV','Pump_Rate','Pump_Savings'])
finaldf['Total_PV'] = finaldf['Acceration_PV'] + finaldf['Pump_Savings']

#print(finaldf)
```

```python
fig, ax = plt.subplots()
ax.plot(finaldf['Pump_Rate'],finaldf['Total_PV'], label = "5 1/2\" casing", linestyle=":", color='g')

plt.ylim(bottom=0, top=500000)
ax.grid(color='k', linestyle='-', linewidth=0.25)

plt.xlabel('Total Volume [MBBL]')
plt.ylabel('Pumping Cost [$]')

fmt = '${x:,.0f}'
tick = mtick.StrMethodFormatter(fmt)
ax.yaxis.set_major_formatter(tick)

plt.legend()
plt.show()
```

```python

```