Following https://docs.pymc.io/Advanced_usage_of_Theano_in_PyMC3.html

```python
from scipy import optimize, special
import numpy as np
```

```python
def func(mu, theta):
    thetamu = theta * mu
    value = np.log(mu) + np.logaddexp(0, thetamu)
    return value

def jac(mu, theta):
    thetamu = theta * mu
    jac = theta * special.expit(thetamu) + 1 / mu
    return jac

def mu_from_theta(theta):
    return optimize.newton(func, 1, fprime=jac, args=(0.4,))
```

```python
import sympy

mu = sympy.Function('mu')
theta = sympy.Symbol('theta')
R = mu(theta) + mu(theta) * sympy.exp(theta * mu(theta)) - 1
solution = sympy.solve(R.diff(theta), mu(theta).diff(theta))[0]
solution
```

$\displaystyle - \frac{\mu^{2}{\left(\theta \right)} e^{\theta \mu{\left(\theta \right)}}}{\theta \mu{\left(\theta \right)} e^{\theta \mu{\left(\theta \right)}} + e^{\theta \mu{\left(\theta \right)}} + 1}$

```python
import theano
import theano.tensor as tt
import theano.tests.unittest_tools

class MuFromTheta(tt.Op):
    itypes = [tt.dscalar]
    otypes = [tt.dscalar]

    def perform(self, node, inputs, outputs):
        theta, = inputs
        mu = mu_from_theta(theta)
        outputs[0][0] = np.array(mu)

    def grad(self, inputs, g):
        theta, = inputs
        mu = self(theta)
        thetamu = theta * mu
        return [- g[0] * mu ** 2 / (1 + thetamu + tt.exp(-thetamu))]
```

```python
import pymc3 as pm

tt_mu_from_theta = MuFromTheta()

with pm.Model() as model:
    theta = pm.HalfNormal('theta', sigma=1)
    mu = pm.Deterministic('mu', tt_mu_from_theta(theta))
    pm.Normal('y', mu=mu, sigma=0.1, observed=[0.2, 0.21, 0.3])

    trace = pm.sample(1000)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    Multiprocess sampling (4 chains in 4 jobs)
    NUTS: [theta]
    Sampling 4 chains, 0 divergences: 100%|██████████| 6000/6000 [04:14<00:00, 23.59draws/s]
    The chain reached the maximum tree depth. Increase max_treedepth, increase target_accept or reparameterize.
    The rhat statistic is larger than 1.05 for some parameters. This indicates slight problems during sampling.
    The estimated number of effective samples is smaller than 200 for some parameters.

```python
pm.traceplot(trace)
```

```python
pm.plot_posterior(trace)
```

```python
pm.summary(trace)
```

      mean
      sd
      hpd_3%
      hpd_97%
      mcse_mean
      mcse_sd
      ess_mean
      ess_sd
      ess_bulk
      ess_tail
      r_hat

      theta
      0.592
      0.493
      0.000
      1.458
      0.078
      0.056
      40.0
      40.0
      40.0
      74.0
      1.12

      mu
      0.455
      0.000
      0.455
      0.455
      0.000
      0.000
      4000.0
      4000.0
      4000.0
      4000.0
      NaN

```python

```


# Corrugated Shells

## Init symbols for *sympy*

```python
from sympy import *
from sympy.vector import CoordSys3D
N = CoordSys3D('N')
x1, x2, x3 = symbols("x_1 x_2 x_3")
alpha1, alpha2, alpha3 = symbols("alpha_1 alpha_2 alpha3")
R, L, ga, gv = symbols("R L g_a g_v")
init_printing()
```

## Cylindrical coordinates

```python
a1 = pi / 2 + (L / 2 - alpha1)/R

x = R * cos(a1)
y = alpha2
z = R * sin(a1)

r = x*N.i + y*N.j + z*N.k
```

### Mid-surface coordinates is defined with the following vector $\vec{r}=\vec{r}(\alpha_1, \alpha_2)$

```python
r
```

#### Tangent to curve

```python
r1 = r.diff(alpha1)
r2 = r.diff(alpha2)
k1 = trigsimp(r1.magnitude()**3)
k2 = trigsimp(r2.magnitude()**3)
r1 = r1/k1
r2 = r2/k2
```

```python
r1
```

```python
r2
```

#### Normal to curve

```python
n = r1.cross(r2)
n = trigsimp(n.normalize())
n
```

#### Curvature

```python
dr1=r1.diff(alpha1)
k1 = trigsimp(r1.cross(dr1).magnitude()/k1)
k1
```

```python
dr2=r2.diff(alpha2)
k2 = trigsimp(r2.cross(dr2).magnitude()/k2)
k2
```

#### Derivative of base vectors
Let's find
$\frac { d\vec{n} } { d\alpha_1}$
$\frac { d\vec{v} } { d\alpha_1}$
$\frac { d\vec{n} } { d\alpha_2}$
$\frac { d\vec{v} } { d\alpha_2}$

```python
n.diff(alpha1)
```

$ \frac { d\vec{n} } { d\alpha_1} = -\frac {1}{R} \vec{v} = -k \vec{v} $

```python
r1.diff(alpha1)
```

$ \frac { d\vec{v} } { d\alpha_1} = \frac {1}{R} \vec{n} = k \vec{n} $

#### Derivative of vectors

$ \vec{u} = u_v \vec{v} + u_n\vec{n} $

$ \frac { d\vec{u} } { d\alpha_1} =  \frac { d(u_v\vec{v}) } { d\alpha_1} + \frac { d(u_n\vec{n}) } { d\alpha_1} =
 \frac { du_n } { d\alpha_1} \vec{n} + u_n \frac { d\vec{n} } { d\alpha_1} + \frac { du_v } { d\alpha_1} \vec{v} + u_v \frac { d\vec{v} } { d\alpha_1} = \frac { du_n } { d\alpha_1} \vec{n} - u_n k \vec{v} + \frac { du_v } { d\alpha_1} \vec{v} + u_v k \vec{n}$

Then
$ \frac { d\vec{u} } { d\alpha_1} = \left( \frac { du_v } { d\alpha_1} - u_n k \right) \vec{v} + \left( \frac { du_n } { d\alpha_1} + u_v k \right) \vec{n}$

$ \frac { d\vec{u} } { d\alpha_2} = \frac { d(u_n\vec{n}) } { d\alpha_2} + \frac { d(u_v\vec{v}) } { d\alpha_2} =
 \frac { du_n } { d\alpha_2} \vec{n} + u_n \frac { d\vec{n} } { d\alpha_2} + \frac { du_v } { d\alpha_2} \vec{v} + u_v \frac { d\vec{v} } { d\alpha_2} = \frac { du_n } { d\alpha_2} \vec{n} + \frac { du_v } { d\alpha_2} \vec{v} $

### Base Vectors $\vec{R}_1, \vec{R}_2, \vec{R}_3$

```python
R_alpha=r+alpha3*n
R_alpha
```

```python
R1=R_alpha.diff(alpha1)
R2=R_alpha.diff(alpha2)
R3=R_alpha.diff(alpha3)
```

```python
trigsimp(R1)
```

```python
R2
```

```python
R3
```

#### Base Vectors $\vec{R}^1, \vec{R}^2, \vec{R}^3$

```python
eps=trigsimp(R1.dot(R2.cross(R3)))
R_1=simplify(trigsimp(R2.cross(R3)/eps))
R_2=simplify(trigsimp(R3.cross(R1)/eps))
R_3=simplify(trigsimp(R1.cross(R2)/eps))
```

```python
R_1
```

```python
R_2
```

```python
R_3
```

#### Jacobi matrix:

$ A = \left(
\begin{array}{ccc}
\frac{\partial x_1}{\partial \alpha_1} & \frac{\partial x_1}{\partial \alpha_2} & \frac{\partial x_1}{\partial \alpha_3} \\
\frac{\partial x_2}{\partial \alpha_1} & \frac{\partial x_2}{\partial \alpha_2} & \frac{\partial x_3}{\partial \alpha_3} \\
\frac{\partial x_3}{\partial \alpha_1} & \frac{\partial x_3}{\partial \alpha_2} & \frac{\partial x_3}{\partial \alpha_3} \\
\end{array}
\right)$

$ \left[
\begin{array}{ccc}
\vec{R}_1 & \vec{R}_2 & \vec{R}_3
\end{array}
\right] = \left[
\begin{array}{ccc}
\vec{e}_1 & \vec{e}_2 & \vec{e}_3
\end{array}
\right] \cdot  \left(
\begin{array}{ccc}
\frac{\partial x_1}{\partial \alpha_1} & \frac{\partial x_1}{\partial \alpha_2} & \frac{\partial x_1}{\partial \alpha_3} \\
\frac{\partial x_2}{\partial \alpha_1} & \frac{\partial x_2}{\partial \alpha_2} & \frac{\partial x_3}{\partial \alpha_3} \\
\frac{\partial x_3}{\partial \alpha_1} & \frac{\partial x_3}{\partial \alpha_2} & \frac{\partial x_3}{\partial \alpha_3} \\
\end{array}
\right) = \left[
\begin{array}{ccc}
\vec{e}_1 & \vec{e}_2 & \vec{e}_3
\end{array}
\right] \cdot A$

$ \left[
\begin{array}{ccc}
\vec{e}_1 & \vec{e}_2 & \vec{e}_3
\end{array}
\right] =\left[
\begin{array}{ccc}
\vec{R}_1 & \vec{R}_2 & \vec{R}_3
\end{array}
\right] \cdot A^{-1}$

```python
dx1da1=R1.dot(N.i)
dx1da2=R2.dot(N.i)
dx1da3=R3.dot(N.i)

dx2da1=R1.dot(N.j)
dx2da2=R2.dot(N.j)
dx2da3=R3.dot(N.j)

dx3da1=R1.dot(N.k)
dx3da2=R2.dot(N.k)
dx3da3=R3.dot(N.k)

A=Matrix([[dx1da1, dx1da2, dx1da3], [dx2da1, dx2da2, dx2da3], [dx3da1, dx3da2, dx3da3]])
simplify(A)
```

```python
A_inv = trigsimp(A**-1)
simplify(trigsimp(A_inv))
```

```python
trigsimp(A.det())
```

### Metric tensor

${\displaystyle \hat{G}=\sum_{i,j} g^{ij}\vec{R}_i\vec{R}_j}$

```python
g11=R1.dot(R1)
g12=R1.dot(R2)
g13=R1.dot(R3)

g21=R2.dot(R1)
g22=R2.dot(R2)
g23=R2.dot(R3)

g31=R3.dot(R1)
g32=R3.dot(R2)
g33=R3.dot(R3)

G=Matrix([[g11, g12, g13],[g21, g22, g23], [g31, g32, g33]])
G=trigsimp(G)
G
```

${\displaystyle \hat{G}=\sum_{i,j} g_{ij}\vec{R}^i\vec{R}^j}$

```python
g_11=R_1.dot(R_1)
g_12=R_1.dot(R_2)
g_13=R_1.dot(R_3)

g_21=R_2.dot(R_1)
g_22=R_2.dot(R_2)
g_23=R_2.dot(R_3)

g_31=R_3.dot(R_1)
g_32=R_3.dot(R_2)
g_33=R_3.dot(R_3)

G_con=Matrix([[g_11, g_12, g_13],[g_21, g_22, g_23], [g_31, g_32, g_33]])
G_con=trigsimp(G_con)
G_con
```

```python
G_inv = G**-1
G_inv
```

### Derivatives of vectors

#### Derivative of base vectors

```python
dR1dalpha1 = trigsimp(R1.diff(alpha1))
dR1dalpha1
```

$ \frac { d\vec{R_1} } { d\alpha_1} = -\frac {1}{R} \left( 1+\frac{\alpha_3}{R} \right) \vec{R_3} $

```python
dR1dalpha2 = trigsimp(R1.diff(alpha2))
dR1dalpha2
```

```python
dR1dalpha3 = trigsimp(R1.diff(alpha3))
dR1dalpha3
```

$ \frac { d\vec{R_1} } { d\alpha_3} = \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}  \vec{R_1} $

```python
dR2dalpha1 = trigsimp(R2.diff(alpha1))
dR2dalpha1
```

```python
dR2dalpha2 = trigsimp(R2.diff(alpha2))
dR2dalpha2
```

```python
dR2dalpha3 = trigsimp(R2.diff(alpha3))
dR2dalpha3
```

```python
dR3dalpha1 = trigsimp(R3.diff(alpha1))
dR3dalpha1
```

$ \frac { d\vec{R_3} } { d\alpha_1} = \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}  \vec{R_1} $

```python
dR3dalpha2 = trigsimp(R3.diff(alpha2))
dR3dalpha2
```

```python
dR3dalpha3 = trigsimp(R3.diff(alpha3))
dR3dalpha3
```

$ \frac { d\vec{R_3} } { d\alpha_3} = \vec{0} $

#### Derivative of vectors

$ \vec{u} = u^1 \vec{R_1} + u^2\vec{R_2} + u^3\vec{R_3} $

$ \frac { d\vec{u} } { d\alpha_1} =  \frac { d(u^1\vec{R_1}) } { d\alpha_1} + \frac { d(u^2\vec{R_2}) } { d\alpha_1}+ \frac { d(u^3\vec{R_3}) } { d\alpha_1} = \frac { du^1 } { d\alpha_1} \vec{R_1} + u^1 \frac { d\vec{R_1} } { d\alpha_1} + \frac { du^2 } { d\alpha_1} \vec{R_2} + u^2 \frac { d\vec{R_2} } { d\alpha_1} + \frac { du^3 } { d\alpha_1} \vec{R_3} + u^3 \frac { d\vec{R_3} } { d\alpha_1} = \frac { du^1 } { d\alpha_1} \vec{R_1} - u^1 \frac {1}{R} \left( 1+\frac{\alpha_3}{R} \right) \vec{R_3} + \frac { du^2 } { d\alpha_1} \vec{R_2}+ \frac { du^3 } { d\alpha_1} \vec{R_3} + u^3 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}  \vec{R_1}$

Then
$ \frac { d\vec{u} } { d\alpha_1} = \left( \frac { du^1 } { d\alpha_1} + u^3 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}  \right) \vec{R_1} + \frac { du^2 } { d\alpha_1} \vec{R_2} + \left( \frac { du^3 } { d\alpha_1} - u^1 \frac {1}{R} \left( 1+\frac{\alpha_3}{R} \right) \right) \vec{R_3}$

$ \frac { d\vec{u} } { d\alpha_2} =  \frac { d(u^1\vec{R_1}) } { d\alpha_2} + \frac { d(u^2\vec{R_2}) } { d\alpha_2}+ \frac { d(u^3\vec{R_3}) } { d\alpha_2} =  \frac { du^1 } { d\alpha_2} \vec{R_1} + \frac { du^2 } { d\alpha_2} \vec{R_2} + \frac { du^3 } { d\alpha_2} \vec{R_3} $

Then
$ \frac { d\vec{u} } { d\alpha_2} = \frac { du^1 } { d\alpha_2} \vec{R_1} + \frac { du^2 } { d\alpha_2} \vec{R_2} + \frac { du^3 } { d\alpha_2} \vec{R_3} $

$ \frac { d\vec{u} } { d\alpha_3} = \frac { d(u^1\vec{R_1}) } { d\alpha_3} + \frac { d(u^2\vec{R_2}) } { d\alpha_3}+ \frac { d(u^3\vec{R_3}) } { d\alpha_3} =
 \frac { du^1 } { d\alpha_3} \vec{R_1} + u^1 \frac { d\vec{R_1} } { d\alpha_3} + \frac { du^2 } { d\alpha_3} \vec{R_2} + u^2 \frac { d\vec{R_2} } { d\alpha_3} + \frac { du^3 } { d\alpha_3} \vec{R_3} + u^3 \frac { d\vec{R_3} } { d\alpha_3} = \frac { du^1 } { d\alpha_3} \vec{R_1} + u^1 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}  \vec{R_1} + \frac { du^2 } { d\alpha_3} \vec{R_2}+ \frac { du^3 } { d\alpha_3} \vec{R_3} $

 Then
$ \frac { d\vec{u} } { d\alpha_3} = \left( \frac { du^1 } { d\alpha_3}  + u^1 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}} \right) \vec{R_1} + \frac { du^2 } { d\alpha_3} \vec{R_2}+ \frac { du^3 } { d\alpha_3} \vec{R_3}$

### Gradient of vector

$\nabla_1 u^1 = \frac { \partial u^1 } { \partial \alpha_1} + u^3 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}$

$\nabla_1 u^2 = \frac { \partial u^2 } { \partial \alpha_1} $

$\nabla_1 u^3 = \frac { \partial u^3 } { \partial \alpha_1} - u^1 \frac {1}{R} \left( 1+\frac{\alpha_3}{R} \right) $

$\nabla_2 u^1 = \frac { \partial u^1 } { \partial \alpha_2}$

$\nabla_2 u^2 = \frac { \partial u^2 } { \partial \alpha_2}$

$\nabla_2 u^3 = \frac { \partial u^3 } { \partial \alpha_2}$

$\nabla_3 u^1 = \frac { \partial u^1 } { \partial \alpha_3} + u^1 \frac {1}{R} \frac {1}{1+\frac{\alpha_3}{R}}$

$\nabla_3 u^2 = \frac { \partial u^2 } { \partial \alpha_3} $

$\nabla_3 u^3 = \frac { \partial u^3 } { \partial \alpha_3}$

$ \nabla \vec{u} = \left(
\begin{array}{ccc}
\nabla_1 u^1 & \nabla_1 u^2 & \nabla_1 u^3 \\
\nabla_2 u^1 & \nabla_2 u^2 & \nabla_2 u^3 \\
\nabla_3 u^1 & \nabla_3 u^2 & \nabla_3 u^3 \\
\end{array}
\right)$

```python
u1=Function('u^1')
u2=Function('u^2')
u3=Function('u^3')
q=Function('q') # q(alpha3) = 1+alpha3/R
K = Symbol('K') # K = 1/R

u1_nabla1 = u1(alpha1, alpha2, alpha3).diff(alpha1) + u3(alpha1, alpha2, alpha3) * K / q(alpha3)
u2_nabla1 = u2(alpha1, alpha2, alpha3).diff(alpha1)
u3_nabla1 = u3(alpha1, alpha2, alpha3).diff(alpha1) - u1(alpha1, alpha2, alpha3) * K * q(alpha3)

u1_nabla2 = u1(alpha1, alpha2, alpha3).diff(alpha2)
u2_nabla2 = u2(alpha1, alpha2, alpha3).diff(alpha2)
u3_nabla2 = u3(alpha1, alpha2, alpha3).diff(alpha2)

u1_nabla3 = u1(alpha1, alpha2, alpha3).diff(alpha3) + u1(alpha1, alpha2, alpha3) * K / q(alpha3)
u2_nabla3 = u2(alpha1, alpha2, alpha3).diff(alpha3)
u3_nabla3 = u3(alpha1, alpha2, alpha3).diff(alpha3)
# $\nabla_2 u^2 = \frac { \partial u^2 } { \partial \alpha_2}$

grad_u = Matrix([[u1_nabla1, u2_nabla1, u3_nabla1],[u1_nabla2, u2_nabla2, u3_nabla2], [u1_nabla3, u2_nabla3, u3_nabla3]])
grad_u
```

```python
G_s = Matrix([[q(alpha3)**2, 0, 0],[0, 1, 0], [0, 0, 1]])
grad_u_down=grad_u*G_s
expand(simplify(grad_u_down))
```

$
\left(
\begin{array}{c}
\nabla_1 u_1 \\ \nabla_2 u_1 \\ \nabla_3 u_1 \\
\nabla_1 u_2 \\ \nabla_2 u_2 \\ \nabla_3 u_2 \\
\nabla_1 u_3 \\ \nabla_2 u_3 \\ \nabla_3 u_3 \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\left( 1+\frac{\alpha_2}{R} \right)^2  \frac { \partial u^1 } { \partial \alpha_1} + u^3 \frac {\left( 1+\frac{\alpha_3}{R} \right)}{R} \\
\left( 1+\frac{\alpha_2}{R} \right)^2 \frac { \partial u^1 } { \partial \alpha_2} \\
\left( 1+\frac{\alpha_3}{R} \right)^2  \frac { \partial u^1 } { \partial \alpha_3} + u^1 \frac {\left( 1+\frac{\alpha_3}{R} \right)}{R} \\
\frac { \partial u^2 } { \partial \alpha_1} \\
\frac { \partial u^2 } { \partial \alpha_2} \\
\frac { \partial u^2 } { \partial \alpha_3} \\
\frac { \partial u^3 } { \partial \alpha_1} - u^1 \frac {\left( 1+\frac{\alpha_3}{R} \right)}{R} \\
\frac { \partial u^3 } { \partial \alpha_2} \\
\frac { \partial u^3 } { \partial \alpha_3} \\
\end{array}
\right)
$

$
\left(
\begin{array}{c}
\nabla_1 u_1 \\ \nabla_2 u_1 \\ \nabla_3 u_1 \\
\nabla_1 u_2 \\ \nabla_2 u_2 \\ \nabla_3 u_2 \\
\nabla_1 u_3 \\ \nabla_2 u_3 \\ \nabla_3 u_3 \\
\end{array}
\right)
=
B \cdot
\left(
\begin{array}{c}
u^1 \\
\frac { \partial u^1 } { \partial \alpha_1} \\
\frac { \partial u^1 } { \partial \alpha_2} \\
\frac { \partial u^1 } { \partial \alpha_3} \\
u^2 \\
\frac { \partial u^2 } { \partial \alpha_1} \\
\frac { \partial u^2 } { \partial \alpha_2} \\
\frac { \partial u^2 } { \partial \alpha_3} \\
u^3 \\
\frac { \partial u^3 } { \partial \alpha_1} \\
\frac { \partial u^3 } { \partial \alpha_2} \\
\frac { \partial u^3 } { \partial \alpha_3} \\
\end{array}
\right)
$

```python
B = zeros(9, 12)
B[0,1] = (1+alpha3/R)**2
B[0,8] = (1+alpha3/R)/R

B[1,2] = (1+alpha3/R)**2

B[2,0] = (1+alpha3/R)/R
B[2,3] = (1+alpha3/R)**2

B[3,5] = S(1)
B[4,6] = S(1)
B[5,7] = S(1)

B[6,9] = S(1)
B[6,0] = -(1+alpha3/R)/R
B[7,10] = S(1)
B[8,11] = S(1)

B
```

### Deformations tensor

```python
E=zeros(6,9)
E[0,0]=1
E[1,4]=1
E[2,8]=1
E[3,1]=1
E[3,3]=1
E[4,2]=1
E[4,6]=1
E[5,5]=1
E[5,7]=1
E

```

```python
Q=E*B
Q=simplify(Q)
Q
```

### Tymoshenko theory

$u^1 \left( \alpha_1, \alpha_2, \alpha_3 \right)=u\left( \alpha_1 \right)+\alpha_3\gamma \left( \alpha_1 \right) $

$u^2 \left( \alpha_1, \alpha_2, \alpha_3 \right)=0 $

$u^3 \left( \alpha_1, \alpha_2, \alpha_3 \right)=w\left( \alpha_1 \right) $

$ \left(
\begin{array}{c}
u^1 \\
\frac { \partial u^1 } { \partial \alpha_1} \\
\frac { \partial u^1 } { \partial \alpha_2} \\
\frac { \partial u^1 } { \partial \alpha_3} \\
u^2 \\
\frac { \partial u^2 } { \partial \alpha_1} \\
\frac { \partial u^2 } { \partial \alpha_2} \\
\frac { \partial u^2 } { \partial \alpha_3} \\
u^3 \\
\frac { \partial u^3 } { \partial \alpha_1} \\
\frac { \partial u^3 } { \partial \alpha_2} \\
\frac { \partial u^3 } { \partial \alpha_3} \\
\end{array}
\right) = T \cdot
\left(
\begin{array}{c}
u \\
\frac { \partial u } { \partial \alpha_1} \\
\gamma \\
\frac { \partial \gamma } { \partial \alpha_1} \\
w \\
\frac { \partial w } { \partial \alpha_1} \\
\end{array}
\right) $

```python
T=zeros(12,6)
T[0,0]=1
T[0,2]=alpha3
T[1,1]=1
T[1,3]=alpha3
T[3,2]=1

T[8,4]=1
T[9,5]=1
T
```

```python
Q=E*B*T
Q=simplify(Q)
Q
```

### Elasticity tensor(stiffness tensor)

#### General form

```python
from sympy import MutableDenseNDimArray
C_x = MutableDenseNDimArray.zeros(3, 3, 3, 3)

for i in range(3):
    for j in range(3):
        for k in range(3):
            for l in range(3):
                elem_index = 'C^{{{}{}{}{}}}'.format(i+1, j+1, k+1, l+1)
                el = Symbol(elem_index)
                C_x[i,j,k,l] = el

C_x
```

#### Include symmetry

```python
C_x_symmetry = MutableDenseNDimArray.zeros(3, 3, 3, 3)

def getCIndecies(index):
    if (index == 0):
        return 0, 0
    elif (index == 1):
        return 1, 1
    elif (index == 2):
        return 2, 2
    elif (index == 3):
        return 0, 1
    elif (index == 4):
        return 0, 2
    elif (index == 5):
        return 1, 2

for s in range(6):
    for t in range(s, 6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        elem_index = 'C^{{{}{}{}{}}}'.format(i+1, j+1, k+1, l+1)
        el = Symbol(elem_index)
        C_x_symmetry[i,j,k,l] = el
        C_x_symmetry[i,j,l,k] = el
        C_x_symmetry[j,i,k,l] = el
        C_x_symmetry[j,i,l,k] = el
        C_x_symmetry[k,l,i,j] = el
        C_x_symmetry[k,l,j,i] = el
        C_x_symmetry[l,k,i,j] = el
        C_x_symmetry[l,k,j,i] = el

C_x_symmetry
```

#### Isotropic material

```python
C_isotropic = MutableDenseNDimArray.zeros(3, 3, 3, 3)

C_isotropic_matrix = zeros(6)

mu = Symbol('mu')
la = Symbol('lambda')

for s in range(6):
    for t in range(s, 6):
        if (s < 3 and t < 3):
            if(t != s):
                C_isotropic_matrix[s,t] = la
                C_isotropic_matrix[t,s] = la
            else:
                C_isotropic_matrix[s,t] = 2*mu+la
                C_isotropic_matrix[t,s] = 2*mu+la
        elif (s == t):
            C_isotropic_matrix[s,t] = mu
            C_isotropic_matrix[t,s] = mu

for s in range(6):
    for t in range(s, 6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        el = C_isotropic_matrix[s, t]
        C_isotropic[i,j,k,l] = el
        C_isotropic[i,j,l,k] = el
        C_isotropic[j,i,k,l] = el
        C_isotropic[j,i,l,k] = el
        C_isotropic[k,l,i,j] = el
        C_isotropic[k,l,j,i] = el
        C_isotropic[l,k,i,j] = el
        C_isotropic[l,k,j,i] = el

C_isotropic
```

```python
def getCalpha(C, A, q, p, s, t):
    res = S(0)
    for i in range(3):
        for j in range(3):
            for k in range(3):
                for l in range(3):
                    res += C[i,j,k,l]*A[q,i]*A[p,j]*A[s,k]*A[t,l]
    return simplify(trigsimp(res))

C_isotropic_alpha = MutableDenseNDimArray.zeros(3, 3, 3, 3)

for i in range(3):
    for j in range(3):
        for k in range(3):
            for l in range(3):
                c = getCalpha(C_isotropic, A_inv, i, j, k, l)
                C_isotropic_alpha[i,j,k,l] = c

C_isotropic_alpha[0,0,0,0]
```

```python
C_isotropic_matrix_alpha = zeros(6)

for s in range(6):
    for t in range(6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        C_isotropic_matrix_alpha[s,t] = C_isotropic_alpha[i,j,k,l]

C_isotropic_matrix_alpha
```

#### Orthotropic material

```python
C_orthotropic = MutableDenseNDimArray.zeros(3, 3, 3, 3)

C_orthotropic_matrix = zeros(6)

for s in range(6):
    for t in range(s, 6):
        elem_index = 'C^{{{}{}}}'.format(s+1, t+1)
        el = Symbol(elem_index)
        if ((s < 3 and t < 3) or t == s):
            C_orthotropic_matrix[s,t] = el
            C_orthotropic_matrix[t,s] = el

for s in range(6):
    for t in range(s, 6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        el = C_orthotropic_matrix[s, t]
        C_orthotropic[i,j,k,l] = el
        C_orthotropic[i,j,l,k] = el
        C_orthotropic[j,i,k,l] = el
        C_orthotropic[j,i,l,k] = el
        C_orthotropic[k,l,i,j] = el
        C_orthotropic[k,l,j,i] = el
        C_orthotropic[l,k,i,j] = el
        C_orthotropic[l,k,j,i] = el

C_orthotropic
```

#### Orthotropic material in shell coordinates

```python
def getCalpha(C, A, q, p, s, t):
    res = S(0)
    for i in range(3):
        for j in range(3):
            for k in range(3):
                for l in range(3):
                    res += C[i,j,k,l]*A[q,i]*A[p,j]*A[s,k]*A[t,l]
    return simplify(trigsimp(res))

C_orthotropic_alpha = MutableDenseNDimArray.zeros(3, 3, 3, 3)

for i in range(3):
    for j in range(3):
        for k in range(3):
            for l in range(3):
                c = getCalpha(C_orthotropic, A_inv, i, j, k, l)
                C_orthotropic_alpha[i,j,k,l] = c

C_orthotropic_alpha[0,0,0,0]
```

```python
C_orthotropic_matrix_alpha = zeros(6)

for s in range(6):
    for t in range(6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        C_orthotropic_matrix_alpha[s,t] = C_orthotropic_alpha[i,j,k,l]

C_orthotropic_matrix_alpha
```

### Physical coordinates

$u^1=\frac{u_{[1]}}{1+\frac{\alpha_3}{R}}$

$\frac{\partial u^1} {\partial \alpha_3}=\frac{1}{1+\frac{\alpha_3}{R}} \frac{\partial u_{[1]}} {\partial \alpha_3} + u_{[1]} \frac{\partial} {\partial \alpha_3} \left( \frac{1}{1+\frac{\alpha_3}{R}} \right) = =\frac{1}{1+\frac{\alpha_3}{R}} \frac{\partial u_{[1]}} {\partial \alpha_3} - u_{[1]} \frac{1}{R \left( 1+\frac{\alpha_3}{R} \right)^2} $

```python
P=eye(12,12)
P[0,0]=1/(1+alpha3/R)
P[1,1]=1/(1+alpha3/R)
P[2,2]=1/(1+alpha3/R)
P[3,0]=-1/(R*(1+alpha3/R)**2)
P[3,3]=1/(1+alpha3/R)
P
```

```python
Def=simplify(E*B*P)
Def
```

```python
rows, cols = Def.shape
D_p=zeros(rows, cols)
q = 1+alpha3/R
for i in range(rows):
    ratio = 1
    if (i==0):
        ratio = q*q
    elif (i==3 or i == 4):
        ratio = q

    for j in range(cols):
        D_p[i,j] = Def[i,j] / ratio

D_p = simplify(D_p)
D_p
```

#### Stiffness tensor

```python
C_isotropic_alpha_p = MutableDenseNDimArray.zeros(3, 3, 3, 3)
q=1+alpha3/R
for i in range(3):
    for j in range(3):
        for k in range(3):
            for l in range(3):
                fact = 1
                if (i==0):
                    fact = fact*q
                if (j==0):
                    fact = fact*q
                if (k==0):
                    fact = fact*q
                if (l==0):
                    fact = fact*q
                C_isotropic_alpha_p[i,j,k,l] = simplify(C_isotropic_alpha[i,j,k,l]*fact)

C_isotropic_matrix_alpha_p = zeros(6)

for s in range(6):
    for t in range(6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        C_isotropic_matrix_alpha_p[s,t] = C_isotropic_alpha_p[i,j,k,l]

C_isotropic_matrix_alpha_p
```

```python
C_orthotropic_alpha_p = MutableDenseNDimArray.zeros(3, 3, 3, 3)
q=1+alpha3/R
for i in range(3):
    for j in range(3):
        for k in range(3):
            for l in range(3):
                fact = 1
                if (i==0):
                    fact = fact*q
                if (j==0):
                    fact = fact*q
                if (k==0):
                    fact = fact*q
                if (l==0):
                    fact = fact*q
                C_orthotropic_alpha_p[i,j,k,l] = simplify(C_orthotropic_alpha[i,j,k,l]*fact)

C_orthotropic_matrix_alpha_p = zeros(6)

for s in range(6):
    for t in range(6):
        i,j = getCIndecies(s)
        k,l = getCIndecies(t)
        C_orthotropic_matrix_alpha_p[s,t] = C_orthotropic_alpha_p[i,j,k,l]

C_orthotropic_matrix_alpha_p
```

#### Tymoshenko

```python
D_p_T = D_p*T
K = Symbol('K')
D_p_T = D_p_T.subs(R, 1/K)
simplify(D_p_T)
```

## Square of segment

$A=\frac {\theta}{2} \left( R + h_2 \right)^2-\frac {\theta}{2} \left( R + h_1 \right)^2$

```python
theta, h1, h2=symbols('theta h_1 h_2')
square_geom=theta/2*(R+h2)**2-theta/2*(R+h1)**2
expand(simplify(square_geom))
```

${\displaystyle A=\int_{0}^{L}\int_{h_1}^{h_2} \left( 1+\frac{\alpha_3}{R} \right) d \alpha_1 d \alpha_3}, L=R \theta$

```python
square_int=integrate(integrate(1+alpha3/R, (alpha3, h1, h2)), (alpha1, 0, theta*R))
expand(simplify(square_int))
```

## Virtual work

### Isotropic material physical coordinates

```python
simplify(D_p.T*C_isotropic_matrix_alpha_p*D_p)
```

### Isotropic material physical coordinates - Tymoshenko

```python
W = simplify(D_p_T.T*C_isotropic_matrix_alpha_p*D_p_T*(1+alpha3*K)**2)
W
```

```python
h=Symbol('h')
E=Symbol('E')
v=Symbol('nu')
W_a3 = integrate(W, (alpha3, -h/2, h/2))
W_a3 = simplify(W_a3)
W_a3.subs(la, E*v/((1+v)*(1-2*v))).subs(mu, E/((1+v)*2))
```

```python
A_M = zeros(3)
A_M[0,0] = E*h/(1-v**2)
A_M[1,1] = 5*E*h/(12*(1+v))
A_M[2,2] = E*h**3/(12*(1-v**2))
Q_M = zeros(3,6)
Q_M[0,1] = 1
Q_M[0,4] = K
Q_M[1,0] = -K
Q_M[1,2] = 1
Q_M[1,5] = 1
Q_M[2,3] = 1

W_M=Q_M.T*A_M*Q_M
W_M
```


# 方差分析 Analusis of Variance

xyfJASON

## 1 单因素方差分析

考虑一个因素 $A$ 对某关心指标 $X$ 的影响，$A$ 取若干水平，每个水平上作若干试验，且每次试验控制除 $A$ 以外的其他因素不变，因而结果只受 $A$ 和随机因素的影响。我们想根据结果推断因素 $A$ 是否对指标 $X$ 有影响。

> 注：$A$ 的不同水平也可以对应成若干待检测的分布，这样能检验这些分布的**均值**是否相同。

### 1.1 理论

取 $A$ 的若干个水平 $A_1,A_2,\ldots,A_s$，并在水平 $A_i$ 下做 $n_i$ 次独立的试验（设一共做了 $n$ 次试验），观察指标 $X$，设第 $i$ 个水平下的第 $j$ 次试验结果为 $x_{ij}$。

令 $\bar x_i$ 表示第 $i$ 个水平下试验结果的平均值，$\bar x$ 表示所有试验结果的平均值，$T_i$ 表示第 $i$ 个水平下所有试验结果之和，$T$ 表示所有试验结果之和，即：

$$
\begin{align}
&\bar x_i=\frac{1}{n_i}\sum_{j=1}^{n_i}x_{ij}&&\bar x=\frac{1}{n}\sum_{i=1}^s\sum_{j=1}^{n_i}x_{ij}
\end{align}
$$

由于随机因素的影响，我们可以把第 $i$ 个水平的试验结果视为服从正态分布 $N(\mu_i,\sigma^2)$，注意这里假设不同水平试验结果的方差是相同的。我们想看 $A$ 的变化对 $X$ 是否有影响，也即提出原假设：

$$
H_0:\mu_1=\mu_2=\cdots=\mu_s
$$

备择假设 $H_1$ 自然就是 $\mu_1,\mu_2,\ldots,\mu_s$ 不完全相等。

为了检验该假设，我们考虑统计量**总体偏差平方和**：

$$
\begin{align}
S_T&=\sum_{i=1}^s\sum_{j=1}^{n_i}(x_{ij}-\bar x)^2\\
&=\sum_{i=1}^sn_i(\bar x_i-\bar x)^2+\sum_{i=1}^{s}\sum_{j=1}^{n_i}(x_{ij}-\bar x_i)^2\\
&=S_A+S_E
\end{align}
$$

其中 $S_A$ 体现组间偏差平方和，$S_E$ 体现组内偏差平方和。

根据正态分布及其导出分布的知识，可以知道，若原假设 $H_0$ 成立，则统计量

$$
F=\frac{(n-s)S_A}{(s-1)S_E}\sim F(s-1, n-s)
$$

于是，给定显著性水平 $\alpha$，我们可以查表找到 $F(s-1, n-s)$ 分布的 $1-\alpha$ 分位数，若 $F$ 大于该分位数，则拒绝原假设 $H_0$。（或等价地，如果 $\text{p-value}$ 小于 $\alpha$ 就拒绝原假设 $H_0$）

一般而言，取 $\alpha=0.01$ 时拒绝称影响非常显著；取 $\alpha=0.05$ 时拒绝称影响显著；否则称无显著影响。

### 1.2 代码

`scipy.stats.f_oneway` 提供了单因素方差分析的接口，输入若干次试验的数据，返回 $F$ 的值及其 $\text{p-value}$。

Documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.f_oneway.html

## 2 例题

### 2.1 例一

为考察 5 名工人的劳动生产率是否相同，记录了每人 4 天的产量，并算出其平均值，如下表。推断他们的生产率有无显著差别。

|      | $A_1$ | $A_2$ | $A_3$ | $A_4$ | $A_5$ |
| :--: | :---: | :---: | :---: | :---: | :---: |
|  1   |  256  |  254  |  250  |  248  |  236  |
|  2   |  242  |  330  |  277  |  280  |  252  |
|  3   |  280  |  290  |  230  |  305  |  220  |
|  4   |  298  |  295  |  302  |  289  |  252  |

编写代码如下（注意每一**行**一次试验）：

```python
from scipy.stats import f_oneway

x = [[256, 242, 280, 298],
     [254, 330, 290, 295],
     [250, 277, 230, 302],
     [248, 280, 305, 289],
     [236, 252, 220, 252]]
res = f_oneway(*x)
res
```

    F_onewayResult(statistic=2.2617412494461675, pvalue=0.1109133645884241)

可见 $\text{p-value}=0.1109$，高于 $0.05$，故无显著差别。

### 2.2 例二

用4种工艺生产灯泡，从各种工艺制成的灯泡中各抽出了若干个测量其寿命， 结果如下表，试推断这几种工艺制成的灯泡寿命是否有显著差异。

|      | $A_1$ | $A_2$ | $A_3$ | $A_4$ |
| :--: | :---: | :---: | :---: | :---: |
|  1   | 1620  | 1580  | 1460  | 1500  |
|  2   | 1670  | 1600  | 1540  | 1550  |
|  3   | 1700  | 1640  | 1620  | 1610  |
|  4   | 1750  | 1720  |       | 1680  |
|  5   | 1800  |       |       |       |

编写代码如下：

```python
x = [[1620, 1670, 1700, 1750, 1800],
     [1580, 1600, 1640, 1720],
     [1460, 1540, 1620],
     [1500, 1550, 1610, 1680]]
res = f_oneway(*x)
res
```

    F_onewayResult(statistic=4.060762766645119, pvalue=0.03314955139443403)

可见 $\text{p-value}=0.0331$，故有显著差异。

Mathematical Statistics/Analysis of Variance.ipynb


# Shock Tube

> WORK IN PROGRESS !!!

In this example we are going to solve the Euler equations for a one-dimensional shock tube.

\begin{equation}
    \begin{array}{c}
        \rho_t + (\rho u)_x = 0 \\
        (\rho u)_t + (\rho u u + p)_x = 0 \\
        (\rho e)_t + (u ( \rho e + p ))_x = 0 \\
    \end{array}
\end{equation}

The inputs to the network will be the independent variables $x$ and $t$ and the outputs will be the density, $\rho$, the velocity $u$ and the pressure $p$. We can compute the internal energy with the expression $\rho e = 0.5 \rho u u + p/(\gamma-1)$ with $\gamma=1.4$ for air.

```python
# autoreload nangs
%reload_ext autoreload
%autoreload 2
%matplotlib inline
```

```python
# imports
import math
import numpy as np
import matplotlib.pyplot as plt
import torch
```

## Define PDE

First we define our PDE and set the values for training.

```python
# imports
from nangs.pde import PDE
from nangs.bocos import PeriodicBoco, DirichletBoco
from nangs.solutions import MLP

# define our PDE
class MyPDE(PDE):
    def __init__(self, inputs, outputs, params):
        super().__init__(inputs, outputs, params)
    # the loss of our NN is the PDE !
    def computePDELoss(self, grads, inputs, outputs, params):
        drdt = grads['r']['t']
        #dudt = grads['u']['t']
        #dpdt = grads['p']['t']

        r, u, p = outputs['r'], outputs['u'], outputs['p']
        ru = r*u
        drudx = self.computeGrad(ru, 'x')
        drudt = self.computeGrad(ru, 't')
        ruup = r*u*u + p
        druupdx =  self.computeGrad(ruup, 'x')
        re = p/(params['g']-1) + 0.5*r*u*u
        dredt = self.computeGrad(re, 't')
        repu = (re+p)*u
        drepudx = self.computeGrad(repu, 'x')

        return [
            drdt + drudx,
            drudt + druupdx,
            dredt + drepudx
        ]

# instanciate pde with keys for inputs/outputs
pde = MyPDE(inputs=['x', 't'], outputs=['r', 'u', 'p'], params=['g'])

# define input values for training
x = np.linspace(0,1,200)
t = np.linspace(0,0.0001,5)
g = 1.4

pde.setValues({'x': x, 't': t, 'g': np.array([g])})

# define input values for validation
x_v = np.linspace(0,1,25)
t_v = np.linspace(0,1,15)
pde.setValues({'x': x_v, 't': t_v}, train=False)
```

```python
# initial condition (dirichlet for temporal dimension)
rl, pl, rr, pr = 10, 10/g, 1, 1/g
r0 = np.zeros(len(x))
u0 = np.zeros(len(x))
p0 = np.zeros(len(x))
for i, _x in enumerate(x):
    u0[i] = 0.
    if _x < 0.5:
        r0[i] = rl
        p0[i] = pl
    else:
        r0[i] = rr
        p0[i] = pr

boco = DirichletBoco('initial_condition', {'x': x, 't': np.array([0])}, {'r': r0, 'u': u0, 'p': p0})
pde.addBoco(boco)

# dirichlet b.c for the space dimension
x1, x2 = np.array([0]), np.array([1])
ones = np.ones(len(t))
boco = DirichletBoco('boco_left', {'x': x1, 't': t}, {'r': r0[0]*ones, 'u': u0[0]*ones, 'p': p0[0]*ones})
pde.addBoco(boco)
boco = DirichletBoco('boco_rigth', {'x': x2, 't': t}, {'r': r0[len(x)-1]*ones, 'u': u0[len(x)-1]*ones, 'p': p0[len(x)-1]*ones})
pde.addBoco(boco)
```

Now we define a topology for our solution and set the training parameters. Then we can find a solution for our PDE.

```python
# define solution topology
mlp = MLP(pde.n_inputs, pde.n_outputs, 2, 10)
optimizer = torch.optim.Adam(mlp.parameters(), lr=0.01)
pde.compile(mlp, optimizer)
```

```python
# pre-train on initial conditions

if False:
    hist = pde.warm(epochs=1000, bocos=['initial_condition'])

    for h in hist:
        plt.plot(hist[h], label=h)
    plt.legend()
    plt.grid()
    plt.yscale("log")
    plt.show()
```

```python
# visualize initial condition
#pde.evaluate({'x': x, 't': np.array([0])})

plt.plot(x, r0)
plt.plot(x, u0)
plt.plot(x, p0)
#plt.plot(x, pde.outputs['r'])
#plt.plot(x, pde.outputs['u'])
#plt.plot(x, pde.outputs['p'])
plt.legend(['$\\rho$', 'u', 'p'])#, 'NN $\\rho$', 'NN u', 'NN p'])
plt.grid()
plt.xlabel('x')
plt.show()
```

```python
# find the solution
hist = pde.solve(epochs=100)
```

Epoch 1/100 Losses 58.62757
 PDE  [ 0.00017 0.00079 0.00101 ]
 initial_condition 19.77371
 boco_left 38.32866
 boco_rigth 0.52454
 Val [ 0.00003 0.00250 0.00071 ]
Epoch 2/100 Losses 32.60192
 PDE  [ 0.00021 0.00044 0.00089 ]
 initial_condition 10.89428
 boco_left 14.50709
 boco_rigth 7.20003
 Val [ 0.00039 0.00097 0.00239 ]
Epoch 3/100 Losses 27.55633
 PDE  [ 0.13475 0.51051 0.47091 ]
 initial_condition 9.55239
 boco_left 9.41387
 boco_rigth 8.21802
 Val [ 0.14426 0.22699 0.13335 ]
Epoch 4/100 Losses 20.91638
 PDE  [ 0.59189 0.54670 0.54522 ]
 initial_condition 8.09087
 boco_left 7.49855
 boco_rigth 4.76568
 Val [ 0.09757 0.36972 0.60730 ]
Epoch 5/100 Losses 14.36793
 PDE  [ 0.92401 0.99316 0.65038 ]
 initial_condition 6.57188
 boco_left 3.89096
 boco_rigth 3.04924
 Val [ 0.22579 0.67393 0.53616 ]
Epoch 6/100 Losses 11.75697
 PDE  [ 0.72944 0.98192 0.60731 ]
 initial_condition 5.93528
 boco_left 2.54540
 boco_rigth 2.50340
 Val [ 0.38250 0.75508 0.25557 ]
Epoch 7/100 Losses 9.30013
 PDE  [ 0.68976 0.75474 0.65239 ]
 initial_condition 5.02657
 boco_left 1.63487
 boco_rigth 1.93973
 Val [ 0.74502 1.34052 0.96918 ]
Epoch 8/100 Losses 6.01877
 PDE  [ 0.45170 0.43486 0.73594 ]
 initial_condition 3.89424
 boco_left 0.80656
 boco_rigth 0.77713
 Val [ 22.28698 30.03385 114.76870 ]
Epoch 9/100 Losses 4.37701
 PDE  [ 0.26852 0.23836 0.30916 ]
 initial_condition 3.29593
 boco_left 0.20670
 boco_rigth 0.60237
 Val [ 34.05077 49.55556 192.48432 ]
Epoch 10/100 Losses 3.91082
 PDE  [ 0.20454 0.20009 0.32800 ]
 initial_condition 3.00780
 boco_left 0.07443
 boco_rigth 0.58438
 Val [ 32.40826 47.39661 187.33188 ]
Epoch 11/100 Losses 3.82157
 PDE  [ 0.18726 0.19352 0.46258 ]
 initial_condition 2.92799
 boco_left 0.04999
 boco_rigth 0.56246
 Val [ 29.86036 42.46873 165.35007 ]
Epoch 12/100 Losses 3.75934
 PDE  [ 0.13846 0.11630 0.25642 ]
 initial_condition 3.01609
 boco_left 0.03332
 boco_rigth 0.53953
 Val [ 25.82365 37.26537 142.31368 ]
Epoch 13/100 Losses 3.64202
 PDE  [ 0.15047 0.10485 0.29946 ]
 initial_condition 2.91642
 boco_left 0.02267
 boco_rigth 0.51801
 Val [ 23.52652 32.02566 123.88632 ]
Epoch 14/100 Losses 3.37966
 PDE  [ 0.14107 0.09128 0.27520 ]
 initial_condition 2.69817
 boco_left 0.01983
 boco_rigth 0.49247
 Val [ 19.34466 25.46177 96.46943 ]
Epoch 15/100 Losses 3.23022
 PDE  [ 0.14743 0.08331 0.22845 ]
 initial_condition 2.58439
 boco_left 0.02220
 boco_rigth 0.47057
 Val [ 14.54816 18.22783 68.68941 ]
Epoch 16/100 Losses 3.42247
 PDE  [ 0.11593 0.07187 0.22591 ]
 initial_condition 2.81162
 boco_left 0.02418
 boco_rigth 0.44877
 Val [ 14.95706 18.84461 67.21325 ]
Epoch 17/100 Losses 3.06015
 PDE  [ 0.13886 0.06894 0.24783 ]
 initial_condition 2.45247
 boco_left 0.02816
 boco_rigth 0.42764
 Val [ 11.69905 15.63880 52.07896 ]
Epoch 18/100 Losses 3.29844
 PDE  [ 0.15195 0.07502 0.41757 ]
 initial_condition 2.65560
 boco_left 0.03120
 boco_rigth 0.39679
 Val [ 10.91354 15.55521 46.45233 ]
Epoch 19/100 Losses 3.08161
 PDE  [ 0.12215 0.06345 0.22570 ]
 initial_condition 2.51761
 boco_left 0.03875
 boco_rigth 0.38815
 Val [ 9.57644 12.57414 37.81595 ]
Epoch 20/100 Losses 3.02186
 PDE  [ 0.11453 0.04777 0.15852 ]
 initial_condition 2.51020
 boco_left 0.03858
 boco_rigth 0.36615
 Val [ 8.34461 12.14880 34.50414 ]
Epoch 21/100 Losses 3.02113
 PDE  [ 0.13567 0.05849 0.33483 ]
 initial_condition 2.44996
 boco_left 0.04049
 boco_rigth 0.35435
 Val [ 7.40687 10.79924 29.55019 ]
Epoch 22/100 Losses 2.74295
 PDE  [ 0.11321 0.04754 0.25050 ]
 initial_condition 2.23348
 boco_left 0.04417
 boco_rigth 0.32822
 Val [ 8.26181 11.28827 33.16557 ]
Epoch 23/100 Losses 2.86317
 PDE  [ 0.09148 0.04358 0.12016 ]
 initial_condition 2.40882
 boco_left 0.04811
 boco_rigth 0.32117
 Val [ 6.01056 9.20142 23.94022 ]
Epoch 24/100 Losses 2.75518
 PDE  [ 0.13056 0.06061 0.32401 ]
 initial_condition 2.23423
 boco_left 0.05132
 boco_rigth 0.29790
 Val [ 4.66855 7.02458 17.49083 ]
Epoch 25/100 Losses 2.61555
 PDE  [ 0.09440 0.05231 0.22785 ]
 initial_condition 2.15776
 boco_left 0.05507
 boco_rigth 0.27786
 Val [ 4.43495 8.12311 18.71557 ]
Epoch 26/100 Losses 2.53623
 PDE  [ 0.07493 0.04455 0.09584 ]
 initial_condition 2.13325
 boco_left 0.05632
 boco_rigth 0.27488
 Val [ 4.36137 7.96141 19.77276 ]
Epoch 27/100 Losses 2.65963
 PDE  [ 0.07830 0.04215 0.13409 ]
 initial_condition 2.26116
 boco_left 0.05903
 boco_rigth 0.25459
 Val [ 4.27229 8.12998 20.97398 ]
Epoch 28/100 Losses 2.60685
 PDE  [ 0.08611 0.05735 0.17700 ]
 initial_condition 2.19601
 boco_left 0.06097
 boco_rigth 0.24304
 Val [ 3.21378 6.92911 16.15971 ]
Epoch 29/100 Losses 2.38429
 PDE  [ 0.09102 0.05777 0.26542 ]
 initial_condition 1.96584
 boco_left 0.06837
 boco_rigth 0.21201
 Val [ 2.60168 6.60586 16.09286 ]
Epoch 30/100 Losses 2.39654
 PDE  [ 0.05775 0.06403 0.12745 ]
 initial_condition 2.04931
 boco_left 0.07496
 boco_rigth 0.18919
 Val [ 2.68853 6.38241 17.32091 ]
Epoch 31/100 Losses 2.35588
 PDE  [ 0.05153 0.06018 0.13170 ]
 initial_condition 2.01360
 boco_left 0.08096
 boco_rigth 0.18018
 Val [ 3.16707 7.10621 20.60434 ]
Epoch 32/100 Losses 2.41175
 PDE  [ 0.04607 0.07691 0.12715 ]
 initial_condition 2.07236
 boco_left 0.08268
 boco_rigth 0.17334
 Val [ 2.36041 5.80866 17.56447 ]
Epoch 33/100 Losses 2.20654
 PDE  [ 0.07084 0.06738 0.24276 ]
 initial_condition 1.84971
 boco_left 0.08254
 boco_rigth 0.14729
 Val [ 2.32675 5.63999 17.02067 ]
Epoch 34/100 Losses 2.15504
 PDE  [ 0.04553 0.07891 0.15796 ]
 initial_condition 1.83481
 boco_left 0.08390
 boco_rigth 0.14219
 Val [ 2.23051 5.71461 17.79899 ]
Epoch 35/100 Losses 2.17531
 PDE  [ 0.07314 0.08310 0.22017 ]
 initial_condition 1.83815
 boco_left 0.08412
 boco_rigth 0.12757
 Val [ 2.24335 5.87087 18.11379 ]
Epoch 36/100 Losses 2.11642
 PDE  [ 0.04868 0.06227 0.12762 ]
 initial_condition 1.82682
 boco_left 0.08480
 boco_rigth 0.12528
 Val [ 2.39899 6.00796 20.07412 ]
Epoch 37/100 Losses 2.10352
 PDE  [ 0.04851 0.07293 0.13453 ]
 initial_condition 1.81071
 boco_left 0.08342
 boco_rigth 0.12407
 Val [ 2.62157 7.13560 23.56316 ]
Epoch 38/100 Losses 2.12140
 PDE  [ 0.09289 0.07741 0.33674 ]
 initial_condition 1.74180
 boco_left 0.08327
 boco_rigth 0.12732
 Val [ 2.75848 5.48728 21.40059 ]
Epoch 39/100 Losses 2.27177
 PDE  [ 0.13440 0.12169 0.48833 ]
 initial_condition 1.83292
 boco_left 0.08493
 boco_rigth 0.10578
 Val [ 2.81949 5.62105 20.15824 ]
Epoch 40/100 Losses 2.02635
 PDE  [ 0.07877 0.05562 0.24089 ]
 initial_condition 1.70238
 boco_left 0.08231
 boco_rigth 0.11657
 Val [ 2.75796 7.13013 23.72144 ]
Epoch 41/100 Losses 1.98110
 PDE  [ 0.03834 0.05672 0.08735 ]
 initial_condition 1.72431
 boco_left 0.08527
 boco_rigth 0.11071
 Val [ 2.81312 7.30902 24.96267 ]
Epoch 42/100 Losses 2.02166
 PDE  [ 0.04583 0.07228 0.14125 ]
 initial_condition 1.74048
 boco_left 0.07823
 boco_rigth 0.11649
 Val [ 2.97387 7.61456 26.84585 ]
Epoch 43/100 Losses 2.07075
 PDE  [ 0.05042 0.06866 0.14156 ]
 initial_condition 1.79200
 boco_left 0.07693
 boco_rigth 0.11495
 Val [ 2.84285 6.54112 24.48310 ]
Epoch 44/100 Losses 2.02772
 PDE  [ 0.07555 0.08088 0.21039 ]
 initial_condition 1.72488
 boco_left 0.07844
 boco_rigth 0.10214
 Val [ 3.12047 8.25672 29.91480 ]
Epoch 45/100 Losses 2.49880
 PDE  [ 0.23987 0.12557 0.81046 ]
 initial_condition 1.92633
 boco_left 0.08051
 boco_rigth 0.09999
 Val [ 3.25057 7.92977 27.29096 ]
Epoch 46/100 Losses 2.14696
 PDE  [ 0.11282 0.07877 0.36014 ]
 initial_condition 1.78885
 boco_left 0.08299
 boco_rigth 0.09122
 Val [ 3.37766 8.04798 28.45337 ]
Epoch 47/100 Losses 2.03686
 PDE  [ 0.07926 0.06336 0.21892 ]
 initial_condition 1.74102
 boco_left 0.07908
 boco_rigth 0.09625
 Val [ 3.34624 6.99033 26.74650 ]
Epoch 48/100 Losses 2.04773
 PDE  [ 0.08623 0.08313 0.27177 ]
 initial_condition 1.72508
 boco_left 0.07677
 boco_rigth 0.09884
 Val [ 3.60577 8.59732 32.22918 ]
Epoch 49/100 Losses 1.81119
 PDE  [ 0.05147 0.06297 0.15212 ]
 initial_condition 1.55264
 boco_left 0.07287
 boco_rigth 0.09683
 Val [ 3.80179 9.43613 35.24068 ]
Epoch 50/100 Losses 1.82044
 PDE  [ 0.05142 0.06225 0.15195 ]
 initial_condition 1.56192
 boco_left 0.07358
 boco_rigth 0.09640
 Val [ 3.76400 8.87561 34.17486 ]
Epoch 51/100 Losses 1.89998
 PDE  [ 0.04178 0.06522 0.09870 ]
 initial_condition 1.66172
 boco_left 0.07206
 boco_rigth 0.09763
 Val [ 4.05041 9.67704 37.43223 ]
Epoch 52/100 Losses 1.78421
 PDE  [ 0.07808 0.07620 0.22505 ]
 initial_condition 1.49876
 boco_left 0.07389
 boco_rigth 0.08512
 Val [ 4.16211 9.93122 38.32412 ]
Epoch 53/100 Losses 2.05185
 PDE  [ 0.21661 0.10974 0.80387 ]
 initial_condition 1.50814
 boco_left 0.06688
 boco_rigth 0.10009
 Val [ 3.81445 8.47682 32.24683 ]
Epoch 54/100 Losses 2.65390
 PDE  [ 0.24291 0.18226 0.62046 ]
 initial_condition 2.11191
 boco_left 0.08911
 boco_rigth 0.10433
 Val [ 3.98866 8.71927 30.69905 ]
Epoch 55/100 Losses 1.96813
 PDE  [ 0.12638 0.06988 0.40971 ]
 initial_condition 1.58916
 boco_left 0.08386
 boco_rigth 0.09313
 Val [ 3.92734 8.71091 31.25188 ]
Epoch 56/100 Losses 1.93414
 PDE  [ 0.06002 0.05823 0.16190 ]
 initial_condition 1.67241
 boco_left 0.07018
 boco_rigth 0.09816
 Val [ 4.25900 10.10029 36.31034 ]
Epoch 57/100 Losses 1.81709
 PDE  [ 0.13063 0.08004 0.44058 ]
 initial_condition 1.43778
 boco_left 0.06736
 boco_rigth 0.09487
 Val [ 4.82920 12.55960 43.34531 ]
Epoch 58/100 Losses 1.89534
 PDE  [ 0.15892 0.11981 0.41166 ]
 initial_condition 1.52778
 boco_left 0.06297
 boco_rigth 0.07445
 Val [ 4.26292 9.61121 35.45389 ]
Epoch 59/100 Losses 1.81396
 PDE  [ 0.05855 0.05677 0.16404 ]
 initial_condition 1.57521
 boco_left 0.06192
 boco_rigth 0.08370
 Val [ 4.36262 10.10860 37.40589 ]
Epoch 60/100 Losses 1.62238
 PDE  [ 0.04430 0.07325 0.10149 ]
 initial_condition 1.40832
 boco_left 0.06333
 boco_rigth 0.07772
 Val [ 4.80222 10.61305 40.71122 ]
Epoch 61/100 Losses 1.72416
 PDE  [ 0.05430 0.05378 0.17887 ]
 initial_condition 1.49086
 boco_left 0.06002
 boco_rigth 0.07764
 Val [ 5.23020 11.66663 44.39259 ]
Epoch 62/100 Losses 1.70273
 PDE  [ 0.06130 0.06490 0.17533 ]
 initial_condition 1.46434
 boco_left 0.06163
 boco_rigth 0.07625
 Val [ 5.11438 11.04286 43.35766 ]
Epoch 63/100 Losses 1.66678
 PDE  [ 0.05989 0.08762 0.16400 ]
 initial_condition 1.43255
 boco_left 0.05637
 boco_rigth 0.07402
 Val [ 5.62054 12.42272 47.42074 ]
Epoch 64/100 Losses 1.73590
 PDE  [ 0.04794 0.06622 0.15847 ]
 initial_condition 1.51570
 boco_left 0.06167
 boco_rigth 0.06765
 Val [ 6.20785 13.80032 52.03949 ]
Epoch 65/100 Losses 1.65663
 PDE  [ 0.04583 0.07276 0.14740 ]
 initial_condition 1.44176
 boco_left 0.06210
 boco_rigth 0.06411
 Val [ 5.69001 12.00050 47.59212 ]
Epoch 66/100 Losses 1.64226
 PDE  [ 0.06420 0.07153 0.17795 ]
 initial_condition 1.41956
 boco_left 0.05139
 boco_rigth 0.06674
 Val [ 5.92765 11.77481 48.87643 ]
Epoch 67/100 Losses 1.57423
 PDE  [ 0.02950 0.06419 0.06873 ]
 initial_condition 1.39806
 boco_left 0.05697
 boco_rigth 0.06505
 Val [ 6.42228 12.81247 52.29058 ]
Epoch 68/100 Losses 1.77624
 PDE  [ 0.16268 0.07686 0.51504 ]
 initial_condition 1.40467
 boco_left 0.05840
 boco_rigth 0.06164
 Val [ 6.41190 11.55645 49.36082 ]
Epoch 69/100 Losses 1.79605
 PDE  [ 0.12183 0.09430 0.43338 ]
 initial_condition 1.48502
 boco_left 0.05375
 boco_rigth 0.04077
 Val [ 6.24482 11.30763 45.84729 ]
Epoch 70/100 Losses 2.00527
 PDE  [ 0.28075 0.10004 0.97726 ]
 initial_condition 1.45333
 boco_left 0.05694
 boco_rigth 0.04232
 Val [ 6.77437 11.21210 46.52089 ]
Epoch 71/100 Losses 2.29310
 PDE  [ 0.35436 0.28953 1.28334 ]
 initial_condition 1.53410
 boco_left 0.08107
 boco_rigth 0.03553
 Val [ 5.07388 10.62836 35.26531 ]
Epoch 72/100 Losses 1.76553
 PDE  [ 0.03345 0.05319 0.07847 ]
 initial_condition 1.60169
 boco_left 0.08149
 boco_rigth 0.02732
 Val [ 5.93250 11.78754 42.43485 ]
Epoch 73/100 Losses 1.57149
 PDE  [ 0.02774 0.05794 0.07159 ]
 initial_condition 1.42572
 boco_left 0.05728
 boco_rigth 0.03608
 Val [ 6.41756 13.95773 48.44322 ]
Epoch 74/100 Losses 1.44620
 PDE  [ 0.01876 0.07450 0.04463 ]
 initial_condition 1.31162
 boco_left 0.05550
 boco_rigth 0.03312
 Val [ 6.99677 14.86023 52.36290 ]
Epoch 75/100 Losses 1.56222
 PDE  [ 0.06665 0.08531 0.24627 ]
 initial_condition 1.34130
 boco_left 0.05449
 boco_rigth 0.03369
 Val [ 6.83484 14.60527 52.47204 ]
Epoch 76/100 Losses 1.60871
 PDE  [ 0.09051 0.06942 0.38202 ]
 initial_condition 1.34744
 boco_left 0.04880
 boco_rigth 0.03182
 Val [ 7.46585 14.96465 54.55783 ]
Epoch 77/100 Losses 1.46127
 PDE  [ 0.02309 0.05481 0.07008 ]
 initial_condition 1.32558
 boco_left 0.05120
 boco_rigth 0.03516
 Val [ 8.20398 16.56351 59.83820 ]
Epoch 78/100 Losses 1.45139
 PDE  [ 0.05312 0.07213 0.14251 ]
 initial_condition 1.27894
 boco_left 0.05093
 boco_rigth 0.03227
 Val [ 9.32996 18.71577 66.16993 ]
Epoch 79/100 Losses 2.08752
 PDE  [ 0.36528 0.18455 1.39393 ]
 initial_condition 1.36983
 boco_left 0.04568
 boco_rigth 0.02410
 Val [ 6.48952 10.80496 44.11295 ]
Epoch 80/100 Losses 2.02542
 PDE  [ 0.22583 0.10042 0.70581 ]
 initial_condition 1.57321
 boco_left 0.08706
 boco_rigth 0.02114
 Val [ 6.67902 13.10379 43.92239 ]
Epoch 81/100 Losses 1.63713
 PDE  [ 0.03103 0.08341 0.08933 ]
 initial_condition 1.47901
 boco_left 0.07602
 boco_rigth 0.01419
 Val [ 7.99134 17.47052 54.14668 ]
Epoch 82/100 Losses 1.42230
 PDE  [ 0.03713 0.06542 0.12808 ]
 initial_condition 1.27888
 boco_left 0.05140
 boco_rigth 0.01514
 Val [ 8.67312 19.10093 58.78460 ]
Epoch 83/100 Losses 1.33981
 PDE  [ 0.03647 0.07251 0.09950 ]
 initial_condition 1.20503
 boco_left 0.04469
 boco_rigth 0.02060
 Val [ 9.51117 20.22787 64.53285 ]
Epoch 84/100 Losses 1.51709
 PDE  [ 0.09972 0.12906 0.35870 ]
 initial_condition 1.25484
 boco_left 0.04442
 boco_rigth 0.02201
 Val [ 8.58791 18.05443 60.72419 ]
Epoch 85/100 Losses 1.50592
 PDE  [ 0.10747 0.08968 0.39217 ]
 initial_condition 1.24612
 boco_left 0.04604
 boco_rigth 0.01732
 Val [ 9.21322 20.55821 62.11214 ]
Epoch 86/100 Losses 1.30977
 PDE  [ 0.05238 0.07668 0.19729 ]
 initial_condition 1.14349
 boco_left 0.04436
 boco_rigth 0.01313
 Val [ 9.13457 18.52682 60.55412 ]
Epoch 87/100 Losses 1.40643
 PDE  [ 0.06567 0.08672 0.29242 ]
 initial_condition 1.19693
 boco_left 0.04479
 boco_rigth 0.01644
 Val [ 9.26379 18.54006 63.29062 ]
Epoch 88/100 Losses 1.59058
 PDE  [ 0.19703 0.10794 0.68744 ]
 initial_condition 1.20589
 boco_left 0.03786
 boco_rigth 0.01604
 Val [ 8.72673 19.04762 57.39573 ]
Epoch 89/100 Losses 1.61102
 PDE  [ 0.11646 0.07138 0.41341 ]
 initial_condition 1.34720
 boco_left 0.04872
 boco_rigth 0.01468
 Val [ 8.98995 19.26807 59.29590 ]
Epoch 90/100 Losses 1.29740
 PDE  [ 0.03669 0.06261 0.12344 ]
 initial_condition 1.16349
 boco_left 0.04697
 boco_rigth 0.01270
 Val [ 10.34559 21.61817 65.27143 ]
Epoch 91/100 Losses 1.28085
 PDE  [ 0.04311 0.06822 0.16952 ]
 initial_condition 1.13151
 boco_left 0.04138
 boco_rigth 0.01433
 Val [ 10.80692 22.60905 69.97147 ]
Epoch 92/100 Losses 1.39583
 PDE  [ 0.04886 0.07115 0.17432 ]
 initial_condition 1.23848
 boco_left 0.04031
 boco_rigth 0.01894
 Val [ 11.36550 22.31971 72.89676 ]
Epoch 93/100 Losses 1.33501
 PDE  [ 0.07412 0.07790 0.17175 ]
 initial_condition 1.17533
 boco_left 0.03662
 boco_rigth 0.01513
 Val [ 10.34801 21.02680 67.26522 ]
Epoch 94/100 Losses 1.36491
 PDE  [ 0.06559 0.07161 0.19404 ]
 initial_condition 1.20529
 boco_left 0.03415
 boco_rigth 0.01505
 Val [ 11.40020 22.24954 71.22702 ]
Epoch 95/100 Losses 1.51936
 PDE  [ 0.17043 0.09362 0.72927 ]
 initial_condition 1.13967
 boco_left 0.03619
 boco_rigth 0.01240
 Val [ 10.39404 18.00435 63.04786 ]
Epoch 96/100 Losses 1.72550
 PDE  [ 0.27045 0.14641 0.99615 ]
 initial_condition 1.20920
 boco_left 0.03312
 boco_rigth 0.01218
 Val [ 8.81375 17.47530 53.20095 ]
Epoch 97/100 Losses 1.50807
 PDE  [ 0.08102 0.10223 0.23902 ]
 initial_condition 1.29878
 boco_left 0.06074
 boco_rigth 0.00780
 Val [ 9.67853 18.94920 60.06963 ]
Epoch 98/100 Losses 1.34452
 PDE  [ 0.02996 0.05660 0.06977 ]
 initial_condition 1.24221
 boco_left 0.04241
 boco_rigth 0.00778
 Val [ 11.76865 22.72171 68.20542 ]
Epoch 99/100 Losses 1.16967
 PDE  [ 0.02597 0.05583 0.07095 ]
 initial_condition 1.07085
 boco_left 0.03648
 boco_rigth 0.01142
 Val [ 12.40874 23.43733 73.38071 ]
Epoch 100/100 Losses 1.06549
 PDE  [ 0.01664 0.05914 0.03403 ]
 initial_condition 0.97879
 boco_left 0.03836
 boco_rigth 0.01174
 Val [ 12.74897 24.57459 76.30487 ]

    /home/sebastianm/.local/lib/python3.7/site-packages/numpy/core/fromnumeric.py:3257: RuntimeWarning: Mean of empty slice.
      out=out, **kwargs)
    /home/sebastianm/.local/lib/python3.7/site-packages/numpy/core/_methods.py:161: RuntimeWarning: invalid value encountered in double_scalars
      ret = ret.dtype.type(ret / rcount)

```python
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5))
ax1.plot(hist['train_loss'], label="train_loss")
ax1.plot(hist['val_loss'], label="val_loss")
ax1.grid(True)
ax1.legend()
ax1.set_yscale("log")
for boco in pde.bocos:
    ax2.plot(hist['bocos'][boco.name], label=boco.name)
ax2.legend()
ax2.grid(True)
ax2.set_yscale("log")
plt.show()
```

Finally, we can evaluate our solution.

```python
# evaluate the solution
x = np.linspace(0,1,100)
t = np.linspace(0,0.0001,20)
r, u, p = [], [], []
for _t in t:
    pde.evaluate({'x': x, 't': np.array([_t])})
    r.append(pde.outputs['r'])
    u.append(pde.outputs['u'])
    p.append(pde.outputs['p'])

from matplotlib import animation, rc
rc('animation', html='html5')

def plot(x, r, u, p, t):
    ax.clear()
    tit = ax.set_title(f"t = {t:.4f}", fontsize=14)
    ax.plot(x, r, label="$\\rho$")
    ax.plot(x, u, label="u")
    ax.plot(x, p, label="p")
    ax.set_xlabel("x", fontsize=14)
    ax.legend(loc="upper right")
    ax.grid(True)
    ax.set_xlim([0, 1])
    ax.set_ylim([-0.2, 10.2])
    return [tit]

def get_anim(fig, ax, x, r, u, p, t):
    def anim(i):
        return plot(x, r[i], u[i], p[i], t[i])
    return anim

fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(111, autoscale_on=False)
animate = get_anim(fig, ax, x, r, u, p, t)
anim = animation.FuncAnimation(fig, animate, frames=len(t), interval=100, blit=True)
```

```python
anim
```

```python

```


Logistic Regression From Scratch

This notebook is in Active state of development!
 Code on GitHub!

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

from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_blobs
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
```

Part 1 📓

# What is Logistic Regression

Logistic regression uses an equation as the representation, very much like linear regression. Input values $X$ are combined linearly using weights or coefficient values to predict an output value $Y$

A key difference from linear regression is that the output value being modeled is a binary value (0 or 1) rather than a numeric value.

Linear Regression models always map a set of $x$ values to a resulting $y$ value on a continuous range. This means that the $y$ value can be -5, 0.4 or 768. How would we use such a Regression model if our yy value is categorical such as a binary value which is either 0 or 1? Is there a way to define a threshold so that a value such as 768 is assigned to the category 1 while a small value such as -5 gets assigned to the category 0?

That's where Logistic Regression comes into play. With Logistic Regression we can map any resulting $y$ value, no matter its magnitude to a value between 0 and 1.

# Why don't we use linear regression for class separation?

(Pic. 1) On axes $x$ feature, and on axes $y$ label of the class. We can see that line doing pretty good on separating these two classes. For example we could say that if value of y > 0.5, then class is positive.

(Pic. 2) Let's add data, that definetely relates to the positive class. And our line will change dramatically. Now, some points that are definetelly has different classes, are different classes with probability of around 0.5. But it is not true!

That's happens, since linear model does not have purpose to separate data. Its main purpose is to minimize MSE, and to minimize MSE it needs to minimize sum of margins between data and line.

One more problem here is that for input with value 100 for example we get output of 2 which is weird since our class has labels of 1 and 0 only.

**We need to fit our output data in range between 0 and 1. And sigmoid function will help us!**

```python
fig, ax = plt.subplots(1, 2, figsize=(15,5))

X = np.array([[1],[2],[3],[4],[8],[9],[10],[11]])
Y = np.array([0, 0, 0, 0, 1, 1, 1, 1])
model = LinearRegression()
model.fit(X, Y)
x_values = np.linspace(0, 12, 100)
y_values = [model.intercept_ + x * model.coef_[0] for x in x_values]
ax[0].plot(x_values, y_values)
ax[0].scatter(X, Y, c='b');
ax[0].title.set_text('Рис. 1')

X = np.array([[1],[2],[3],[4],[8],[9],[10],[11], [18], [19], [20], [21], [22], [23], [24]])
Y = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
model = LinearRegression()
model.fit(X, Y)
x_values = np.linspace(0, 25, 100)
y_values = [model.intercept_ + x * model.coef_[0] for x in x_values]
ax[1].plot(x_values, y_values)
ax[1].scatter(X, Y, c='b');
ax[1].title.set_text('Рис. 2')
```

# Sigmoid function

We are using sigmoid for interpretating probability. We need to tune sigmoid that way so that sigmoid can descrive data very well.

The Sigmoid Function squishes all its inputs (values on the x-axis) between 0 and 1 as we can see on the y-axis in the graph below.

Assume that class is positive if sigmoid is > 0.5, this happend when x > 0.
Is sigmoid < 0.5, class is negative and x < 0.

We can see that as $x$ increases towards positive infinity the output gets closer to 1, and as $x$ decreases towards negative infinity the output gets closer to 0.

```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

fig, ax = plt.subplots(1, 2, figsize=(15,5))

x_values = np.linspace(-5, 5, 100)
y_values =  [sigmoid(x) for x in x_values]

ax[0].plot(x_values, y_values);

X = np.array([[-1],[-2],[-3],[-4],[-8],[-9],[5],[8], [12], [13], [14], [15]])
Y = np.array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1])

x_values = np.linspace(-10, 25, 100)
y_values =  [sigmoid(x) for x in x_values]

ax[1].plot(x_values, y_values);
ax[1].scatter(X, [sigmoid(x) for x in X], c='r');
```

## Loss function

Let's create some rule to penalize our model for error!

We know, that $y$ can be 0 or 1. And our $\hat{y}$ - predicted value is between 0 and 1. If $y$ is similar to $\hat{y}$ the less error we want to have, and the more $\hat{y}$ differ from $y$ the more erorr we want to have. Well, it's easy to find a function for that task!

$$\large
Cost(h_{\theta}(x),y) =  \begin{equation}
    \begin{cases}
         -log(h_{\theta}(x)) \text{ if } y = 1 \\
         -log(1 - h_{\theta}(x)) \text{ if } y = 0
    \end{cases}\,
\end{equation}
$$

When y = 1, and true class = 1 then the error is 0, because they are the same (that means that we predicted corrrectly). Otherwise, if sigmoid output tends to zero while our true class is 1, the error tends to plus infinity. Pretty logical, by the way.

When y = 0, and true class = 0, then the error is 0, because they are the same (that means that we predicted corrrectly). Otherwise, if sigmoid output tends to 1 while our true class is 0, the error tends to plus infinity.

If you don't get idea, look at this plot. It shows how error would grow!

```python
# hx - sigmoid values between (0, 1)

fig, ax = plt.subplots(1, 2, figsize=(15,5))

def if_y_1(hx):
    return -np.log(hx)

x_values = np.linspace(0.001, 1, 100)
y_values = [if_y_1(hx) for hx in x_values]

ax[0].plot(x_values, y_values)
ax[0].title.set_text('If y = 1');
ax[0].set_xlabel('h(x) - sigmoid output')
ax[0].set_ylabel('Cost')

def if_y_0(hx):
    return -np.log(1 - hx)

x_values = np.linspace(0, 0.999, 100)
y_values = [if_y_0(hx) for hx in x_values]

ax[1].plot(x_values, y_values)
ax[1].title.set_text('If y = 0');
ax[1].set_xlabel('h(x) - sigmoid output')
ax[1].set_ylabel('Cost');
```

We will rewrite loss function in something more understandble.

$$\large
Cost(h_{\theta}(x),y) = -[y=1]log(h_{\theta}(x) - [y=0]log(1 - h_{\theta}(x))
$$

We can't differentiate that function so let's do 1 more transformation:

$$\large
Cost(h_{\theta}(x),y) = -y \cdot log(h_{\theta}(x) - (y-1) \cdot log(1 - h_{\theta}(x))
$$

Why and how does it work?
* y is always 1 or 0
* If y = 1, then function will look like $-log(h_{\theta}(x))$
* If y = 0, then function will look like $-log(1 - h_{\theta}(x))$

# Gradient of loss function

$$\large
h_{\theta}(x) = \frac{1}{1 + \exp(-x)}
$$

$\text{}$

$$\large
Cost(h_{\theta}(x),y) = -y \cdot log(h_{\theta}(x) - (y-1) \cdot log(1 - h_{\theta}(x))
$$

Particial derivative with respect to $w_i$ = $(h_{\theta}(x_i) - y_i)x_i$
Particial derivative with respect to $bias$ = $h_{\theta}(x_i) - y_i$

# Implementaion Logistic Regression

```python
class LogisticRegression:

    def __init__(self,lr=0.1,n_iters=1000):
        self.lr = lr
        self.n_iters = n_iters
        self.weights = None
        self.bias = None

    def fit(self,X,y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0

        for _ in range(self.n_iters):
            linear_model = X @ self.weights + self.bias
            hx = self._sigmoid(linear_model)

            dw = (X.T * (hx - y)).T.mean(axis=0)
            db = (hx - y).mean(axis=0)

            self.weights -= self.lr * dw
            self.bias -= self.lr * db

    def predict(self,X):
        linear_model = np.dot(X,self.weights) + self.bias
        y_predicted = self._sigmoid(linear_model)
        y_predicted_cls = [1 if i > 0.5 else 0 for i in y_predicted]
        return y_predicted_cls

    def _sigmoid(self,x):
        return(1/(1+np.exp(-x)))
```

# Use model

```python
X, y = make_blobs(n_samples=100, centers=2, n_features=2, random_state=0)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
```

```python
fig, ax = plt.subplots(1, 2, figsize=(15,5))

color = ['blue' if l == 0 else 'green' for l in y_train]
ax[0].scatter(X_train[:, 0], X_train[:, 1], c=color, label='1')
ax[0].title.set_text('Train sample')

color = ['blue' if l == 0 else 'green' for l in y_test]
ax[1].scatter(X_test[:, 0], X_test[:, 1], c=color, label='1');
ax[1 ].title.set_text('Test sample')
```

Let's fit our model to the data and then predicts its values for test data!

```python
model = LogisticRegression()
model.fit(X_train, y_train);
```

```python
fig, ax = plt.subplots(1, 2, figsize=(15,5))

x_values = np.linspace(X_train[:, 0].min(), X_train[:, 0].max(), 100)
y_values = [(-model.bias - model.weights[0]*x) / model.weights[1] for x in x_values]
color = ['blue' if l == 0 else 'green' for l in y_train]
ax[0].scatter(X_train[:, 0], X_train[:, 1], c=color, label='1')
ax[0].plot(x_values, y_values)
ax[0].title.set_text('Train sample, accuracy: {}'.format(accuracy_score(y_train, model.predict(X_train))))

x_values = np.linspace(X_test[:, 0].min(), X_test[:, 0].max(), 100)
y_values = [(-model.bias - model.weights[0]*x) / model.weights[1] for x in x_values]
color = ['blue' if l == 0 else 'green' for l in y_test]
ax[1].scatter(X_test[:, 0], X_test[:, 1], c=color, label='1');
ax[1].plot(x_values, y_values)
ax[1].title.set_text('Test sample, accuracy: {}'.format(accuracy_score(y_test, model.predict(X_test))))
```


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

```python
from sympy import init_session
init_session()
```

    IPython console for SymPy 1.5.1 (Python 3.6.10-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at https://docs.sympy.org/1.5.1/

No solutions will be provided,  but you can check your solution using `sympy` or `numpy.linalg`

**1**.

Let

$$A = \pmatrix{1 & 3\\2 & 6}$$

* Find a set of basis vectors for $C(A), N(A), C(A^T), N(A^T)$
* Find which subspaces are orthogonal to each other (i.e. all vectors in the first subspace are orthogonal to all vectors in the second subspace)
* Express $A$ as the product of two vectors
* If $Ax = b$, describe the geometric constraints on $b$ for there to be a solution $x$
* Check your results using `sympy` and `numpy.linalg`

```python
A = Matrix([[1, 3], [2, 6]])
A
```

```python
A.columnspace()
```

```python
A.nullspace()
```

```python
A.T.columnspace()
```

```python
A.T.nullspace()
```

```python
A1 = Matrix([1, 2])
A1
```

```python
A2 = Matrix([[1, 3]])
A2
```

```python
A1 * A2
```

**2**.

Let

$$A = \pmatrix{1 & 1 & 1\\2& 3 & 5\\4 & 6 & 8}$$

* Find $A^{-1}$ by Gaussian elimination
* Find the LU and LDU decomposition of $A$ by hand
* Find the Cholesky decomposition of $A^TA$ by hand
* Check your results using `sympy` and `numpy.linalg`

```python
A = Matrix([[1, 1, 1], [2, 3, 5], [4, 6, 8]])
A
```

```python
A.inv()
```

```python
A.LUdecomposition()
```

```python
(A.T * A).cholesky()
```

**3**.

A population of immortal being grows as follows every year

\begin{eqnarray}
\text{child} &\to& \text{adult} \\
\text{adult} &\to& \text{child} + \text{adult}
\end{eqnarray}

That is, if you are a child in Year 1, you will be an adult in Year 2. If you are an adult in Year 1, you remain an adult in Year 2 and you produce a child.

* Express the number of adults and children in Year(n+1) as function of the number of adults and children in Year(n) as a matrix equation
* Find the eigenvalues and eigenvectors of the resulting matrix by hand
* Check your results using `sympy` and `numpy.linalg`
* Explain geometrically why the ratio of adults to children as $n \to \infty$ approaches the larger eigenvalue
* Using `numpy`, evaluate the 10 years of population growth starting from a single child

```python
A = Matrix([[1, 1], [1, 0]])
A
```

```python
A.eigenvals()
```

```python
A.eigenvects()
```

```python
years = 10
growth = np.zeros(shape = (2, years + 1))
growth
```

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

```python
growth[:, 0].reshape(-1, 1)
```

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

```python
years = 10
growth = np.zeros(shape = (2, years + 1))
growth[:, 0] = np.array([0, 1])
growth
```

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

```python
# Initialize arrays
A = np.array([1, 1, 1, 0]).reshape(2, 2)
years = 10
growth = np.zeros(shape = (2, years + 1))
growth[:, 0] = np.array([0, 1])

# Show 10 years of growth
for year in range(1, years + 1):
    growth[:, year] = A @ growth[:, year - 1]

# Display result
growth
```

    array([[ 0.,  1.,  1.,  2.,  3.,  5.,  8., 13., 21., 34., 55.],
           [ 1.,  0.,  1.,  1.,  2.,  3.,  5.,  8., 13., 21., 34.]])

**4**.

A vector $x$ has coordinates $\pmatrix{1 \\ 1}$  with basis vectors $\left\{ \pmatrix{1 \\ 0}, \pmatrix{1 \\ 1} \right\}$.

* What will its coordinates be with basis vectors $\left\{ \pmatrix{1 \\ 2}, \pmatrix{0 \\ 1} \right\}$?
* Do this manually using your understanding of coordinates as coefficients for the linear combination of basis vectors
* Do this by matrix multiplication using the change of basis matrix

Hint: Try doing the change of basis in two steps, using the standard basis $\left\{ \pmatrix{1 \\ 0}, \pmatrix{0 \\ 1} \right\}$ as an intermediate

*See handwritten notes*


```python
import random
import numpy as np
import pandas as pd
import sympy as sp
from sklearn import datasets
from matplotlib import pyplot as plt
```

```python
diabetes = datasets.load_diabetes()
df = pd.DataFrame(diabetes.data, columns=diabetes.feature_names)
df['target'] = pd.Series(diabetes.target)
df.head()
```

      age
      sex
      bmi
      bp
      s1
      s2
      s3
      s4
      s5
      s6
      target

      0
      0.038076
      0.050680
      0.061696
      0.021872
      -0.044223
      -0.034821
      -0.043401
      -0.002592
      0.019908
      -0.017646
      151.0

      1
      -0.001882
      -0.044642
      -0.051474
      -0.026328
      -0.008449
      -0.019163
      0.074412
      -0.039493
      -0.068330
      -0.092204
      75.0

      2
      0.085299
      0.050680
      0.044451
      -0.005671
      -0.045599
      -0.034194
      -0.032356
      -0.002592
      0.002864
      -0.025930
      141.0

      3
      -0.089063
      -0.044642
      -0.011595
      -0.036656
      0.012191
      0.024991
      -0.036038
      0.034309
      0.022692
      -0.009362
      206.0

      4
      0.005383
      -0.044642
      -0.036385
      0.021872
      0.003935
      0.015596
      0.008142
      -0.002592
      -0.031991
      -0.046641
      135.0

```python
y = df['target'].values.reshape(-1,1)
x = df[['age', 'bmi', 'sex', 'bp']]
y = np.transpose(y)
x = np.transpose(x).values
```

```python
m, n = np.shape(x)
numIterations= 400
alpha = 0.05
theta = np.ones(m).reshape(1, m)
```

```python
counter_it = list()
counter_cost = list()

def gradientDescent(x, y, theta, alpha, m, numIterations):
    xTrans = x.transpose()

    for i in range(0, numIterations):
        hypothesis = (theta @ x).reshape(1,n)
        loss = (hypothesis - y)
        cost = np.sum(loss ** 2) / (2 * m)
        if i % 50 == 0:
            print("Iteration %d | Cost: %f" % (i, cost))

        counter_it.append(i)
        counter_cost.append(cost)

        gradient = (loss @ xTrans) / m
        theta = theta - alpha * gradient

    plt.scatter(counter_it, counter_cost)
    return theta
```

```python
theta = gradientDescent(x, y, theta, alpha, m, numIterations)
print(theta)
```

```python

```


```python
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns
import pandas as pd
from scipy.stats import norm, uniform, expon, t
from scipy.integrate import quad
from sympy.solvers import solve
from sympy import Symbol
import numpy as np
from pandas import Series, DataFrame
```

```python
x = np.loadtxt(r"oldfaithful.txt")
n = np.size(x)
nboot = 1000
tmpdata = np.random.choice(x, n*nboot, replace=True)
bootstrapsample = np.reshape(tmpdata, (n, nboot))
xbarstar = np.mean(bootstrapsample, axis=0)
d = np.percentile(xbarstar, q=[2.5, 97.5])
print('Vertrauensintervall:',d)
```

    Vertrauensintervall: [201.57693015 217.44365809]

```python
x = np.loadtxt(r"oldfaithful.txt")
n = np.size(x)
nboot = 1000
tmpdata = np.random.choice(x, n*nboot, replace=True)
bootstrapsample = np.reshape(tmpdata, (n, nboot))
xbarstar = np.median(bootstrapsample, axis=0)
d = np.percentile(xbarstar, q=[2.5, 97.5])
print('Vertrauensintervall:',d)
```

    Vertrauensintervall: [229.5 246.5]

```python
x = np.loadtxt(r"oldfaithful.txt")
n = x.size
nboot = 1000
tmpdata = np.random.choice(x, n*nboot, replace=True)
bootstrapsample = np.reshape(tmpdata, (n, nboot))
xbarstar = np.mean(bootstrapsample, axis=0) - np.mean(x)
l = 0
u = np.sum(np.abs(xbarstar) > 5)
ratio = (l+u)/nboot
print(ratio)
```

    0.232

```python
np.sqrt(100/12)
```

    2.886751345948129

```python
from scipy.stats import uniform
import matplotlib.pyplot as plt
import numpy as np
n = 60 # Anzahl Stichproben
# X_1,...,X_n simulieren und in einer
# n-spaltigen Matrix (mit 200 Zeilen) anordnen
sim = uniform.rvs(size=200*n, loc=0, scale=10)
sim = sim.reshape(200, n)
#In jeder Matrixzeile Mittelwert berechnen
sim_mean = sim.mean(axis=1)
plt.axhline(y=5.73, linewidth=1, color='b')
plt.axhline(y=4.27, linewidth=1, color='b')
plt.plot(np.arange(1,201,1),sim_mean)
```

```python
from scipy.stats import uniform
import matplotlib.pyplot as plt
import numpy as np
n = 60
sim = uniform.rvs(size=200*n, loc=0, scale=10)
sim = sim.reshape(200, n)
d = np.sum(sim_mean>5.73) + np.sum(sim_mean<4.27)
print(d)

```

    11

```python
from scipy.stats import probplot
probplot(sim_mean, plot=plt)
```

```python

```

    0.252

```python
norm.ppf(q=[0.005, 0.995], loc=31, scale=6/np.sqrt(1000))
```

    array([30.51127075, 31.48872925])

```python
t.interval(alpha=0.95, loc=-403, scale=3.127/np.sqrt(9), df=8)
```

    (-404.93826890444467, -401.06173109555533)

# 7.5

```python
x = Series([520, 512, 499, 524, 505])
print(x.mean())
t.ppf(q=0.95, df=x.size-1, loc=500, scale=x.std()/np.sqrt(x.size))

t.ppf(q=0.95, loc=500, df=4, scale=x.std()/np.sqrt(5))
```

    512.0

    509.8388828578604

```python
t.interval(alpha=0.95, loc=x.mean(), scale=x.std()/np.sqrt(x.size), df=x.size-1)
```

    (499.1861719188852, 524.8138280811148)

```python

```


# Valuación de opciones con barrera

## 1. Recorderis de valuación usando simulación...

```python
#importar los paquetes que se van a usar
import pandas as pd
import pandas_datareader.data as web
import numpy as np
from sklearn.neighbors import KernelDensity
import datetime
from datetime import datetime, timedelta
import scipy.stats as stats
import scipy as sp
import scipy.optimize as optimize
import scipy.cluster.hierarchy as hac
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
#algunas opciones para Python
pd.set_option('display.notebook_repr_html', True)
pd.set_option('display.max_columns', 6)
pd.set_option('display.max_rows', 10)
pd.set_option('display.width', 78)
pd.set_option('precision', 3)
```

```python
def get_historical_closes(ticker, start_date, end_date=None):
    #closes = web.YahooDailyReader(ticker, start_date, end_date).read().sort_index('major_axis')
    closes = web.YahooDailyReader(symbols=ticker, start=start_date, end=end_date).read()
    closes.set_axis(closes.loc['date',:,ticker[0]].values, axis=1, inplace=True)
    closes = closes.loc['adjclose'].sort_index().dropna()
    closes = pd.DataFrame(np.array(closes.as_matrix(), dtype=np.float64), columns=ticker, index=closes.index)
    closes.index.name = 'Date'
    #return closes.loc['Adj Close']
    return closes
####### Calculation of log-returns
def calc_daily_ret(closes):
    return np.log(closes/closes.shift(1)).dropna()
```

```python
closes_aapl = get_historical_closes(['AAPL'], '2017-01-01')
```

```python
closes_aapl.plot(figsize=(8,6))
```

```python
ret_aapl = calc_daily_ret(closes_aapl)
```

### 1.1 Modelo normal de rendimientos

```python
mu = ret_aapl.mean().AAPL
sigma = ret_aapl.std().AAPL
r = 0.0160/252
mu, sigma, r
```

    (0.0014979727368957033, 0.012018070762626952, 6.349206349206349e-05)

```python
K = 180
ndays = (datetime(2018,6,15).date()-datetime(2018,3,14).date()).days
nscen = 100000
dates = pd.date_range('2018-03-15',periods=ndays).astype('str')
sim_ret = pd.DataFrame(sigma*np.random.randn(ndays,nscen)+r,index=dates)
sim_closes = (closes_aapl.iloc[-1].AAPL)*np.exp(sim_ret.cumsum())
```

```python
strike = pd.DataFrame(K*np.ones(ndays*nscen).reshape((ndays,nscen)),index=dates)
call=pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()},index=dates)
call.plot();
```

```python
call.iloc[-1]
```

    Prima    8.652
    Name: 2018-06-15, dtype: float64

```python
sigma_est = sim_closes.iloc[-1].std()
(call.iloc[-1].Prima-1.96*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen),call.iloc[-1].Prima+1.96*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen))
```

    (8.522631329418223, 8.7818191077035248)

### 1.2 Histograma de rendimientos

```python
freq, values = np.histogram(ret_aapl-mu+r, bins=250)
freq = freq.astype('float32')
prob = freq/np.sum(freq)
```

```python
K = 180
ndays = (datetime(2018,6,15).date()-datetime(2018,3,14).date()).days
nscen = 100000
dates = pd.date_range('2018-03-15',periods=ndays).astype('str')
ret = np.random.choice(values[1:], (ndays,nscen), p = prob)
sim_ret_hist = pd.DataFrame(ret,index=dates)
sim_closes_hist = (closes_aapl.iloc[-1].AAPL)*np.exp(sim_ret_hist.cumsum())
```

```python
strike = pd.DataFrame(K*np.ones(ndays*nscen).reshape((ndays,nscen)), index=dates)
call_hist = pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes_hist-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()}, index=dates)
call_hist.plot();
```

```python
call_hist.iloc[-1]
```

    Prima    10.645
    Name: 2018-06-15, dtype: float64

```python
sigma_est = sim_closes_hist.iloc[-1].std()
(call_hist.iloc[-1].Prima-4.5*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen),call_hist.iloc[-1].Prima+4.5*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen))
```

    (10.342849495593608, 10.94763673256945)

### 1.3 Distribución empírica con kernel

```python
kde = KernelDensity(kernel='gaussian', bandwidth=0.001).fit(ret_aapl-mu+r)
```

```python
K = 180
ndays = (datetime(2018,6,15).date()-datetime(2018,3,14).date()).days
nscen = 100000
dates = pd.date_range('2018-03-15',periods=ndays).astype('str')
kde = KernelDensity(kernel='gaussian', bandwidth=0.001).fit(ret_aapl-mu+r)
ret = kde.sample(n_samples=ndays*nscen, random_state=None)
sim_ret_kernel = pd.DataFrame(ret.reshape((ndays,nscen)),index=dates)
sim_closes_kernel = (closes_aapl.iloc[-1].AAPL)*np.exp(sim_ret_kernel.cumsum())
```

```python
strike = pd.DataFrame(K*np.ones(ndays*nscen).reshape((ndays,nscen)), index=dates)
call_kernel = pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes_kernel-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()}, index=dates)
call_kernel.plot();
```

```python
call_kernel.iloc[-1]
```

    Prima    8.679
    Name: 2018-06-15, dtype: float64

```python
sigma_est = sim_closes_kernel.iloc[-1].std()
(call_kernel.iloc[-1].Prima-4.5*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen),call_kernel.iloc[-1].Prima+4.5*sigma_est*np.exp(-r*ndays)/np.sqrt(nscen))
```

    (8.3808795306568467, 8.9773825101152003)

## 2. Aspectos teóricos

Para un activo con precio $S_t$, se considera una opción de la siguiente forma
\begin{equation}
    V_T=u(t)F_T
\end{equation}
para $0\leq t\leq T$. En este caso, $F_T=\max(S_T-K,0)$ para una opción tipo call o $F_T=\max(K-S_T,0)$ para una opción tipo put. La función $u(t)$ est\'a definida en $0\leq t\leq T$, toma el valor de uno sí se cumple cierta condición y el valor de cero en otra caso.

Por otra lado, para $B>0$ y $0<t<T$ se dice que $S_t$ ha superado la barrera $B$ sí $S_0B$ para algún tiempo $t$ en el periodo considerado. Similarmente, se dice que $S_t$ ha caído por debajo de la barrera $B$ sí $S_0>B$ y $S_t<B$ para alg\'un tiempo $t$ en el periodo considerado.

Finalmente, para un enunciado $x$, se define la función indicadora $I(x)$ como $I(x)=1$ si $x$ se cumple e $I(x)=0$ en otro caso.

Se esta forma, pueden definirse las siguientes acciones

* Up-and-out: Sí $S_0<B$, entonces $u(t)=I(S_tB$, entonces $u(t)=I(S_t>B)$.
* Up-and-in: Sí $S_0B)$.
* Down-and-in: Sí $S_0>B$, entonces $u(t)=I(S_t<B)$.

Así se se pueden definirse ocho nuevos tipos de opciones.

* Up-and-out call: Sí $S_0<B$, $C_t^{uo}=\max\{S_T-K,0\}I(S_tB$, $C_t^{do}=\max\{S_T-K,0\}I(S_t>B)$
* Up-and-in call: Sí $S_0B)$
* Down-and-in call: Sí $S_0>B$, $C_t^{di}=\max\{S_T-K,0\}I(S_t<B)$
* Up-and-out put: Sí $S_0<B$, $P_t^{uo}=\max\{K-S_T,0\}I(S_tB$, $P_t^{do}=\max\{K-S_T,0\}I(S_t>B)$
* Up-and-in put: Sí $S_0B)$
* Down-and-in put: Sí $S_0>B$, $P_t^{di}=\max\{K-S_T,0\}I(S_t<B)$

A las del tipo *up-and-out* y *down-and-out* se les conoce como opciones *knock-out*. De la misma forma, a las del tipo *up-and-in* y *down-and-in* se les conoce como opciones *knock-in*.

Note que estas son **opciones dependientes de la trayectoria del precio**, dado que $u(t)$ depende de $S_t$.

## 3. Valuación de una opción up and in

### 3.1 Modelo normal de rendimientos

```python
mu = ret_aapl.mean().AAPL
sigma = ret_aapl.std().AAPL
r = 0.0160/252
mu, sigma, r
```

    (0.0014979727368957033, 0.012018070762626952, 6.349206349206349e-05)

```python
K = 180
B = 190
ndays = (datetime(2018,6,15).date()-datetime(2018,3,14).date()).days
nscen = 100000
dates = pd.date_range('2018-03-15',periods=ndays).astype('str')
sim_ret = pd.DataFrame(sigma*np.random.randn(ndays,nscen)+r,index=dates)
sim_closes = (closes_aapl.iloc[-1].AAPL)*np.exp(sim_ret.cumsum())
```

Hasta la simulación, todo normal... falta crear la función indicadora

```python
barrier = pd.DataFrame(B*np.ones((ndays, nscen)),index=dates)
I = (sim_closes>barrier).cumsum()>0
I
```

      0
      1
      2
      ...
      99997
      99998
      99999

      2018-03-15
      False
      False
      False
      ...
      False
      False
      False

      2018-03-16
      False
      False
      False
      ...
      False
      False
      False

      2018-03-17
      False
      False
      False
      ...
      False
      False
      False

      2018-03-18
      False
      False
      False
      ...
      False
      False
      False

      2018-03-19
      False
      False
      False
      ...
      False
      False
      False

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      2018-06-11
      False
      False
      True
      ...
      False
      True
      True

      2018-06-12
      False
      False
      True
      ...
      False
      True
      True

      2018-06-13
      False
      False
      True
      ...
      False
      True
      True

      2018-06-14
      False
      False
      True
      ...
      False
      True
      True

      2018-06-15
      False
      False
      True
      ...
      False
      True
      True

93 rows × 100000 columns

```python
strike = pd.DataFrame(K*np.ones((ndays, nscen)),index=dates)
call = pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()},index=dates)
callui = pd.DataFrame({'Prima':np.exp(-r*ndays)*(I*np.fmax(sim_closes-strike,np.zeros((ndays,nscen)))).T.mean()},index=dates)
plt.plot(call.index, call.Prima, callui.index, callui.Prima)
#callui.plot();
#call.plot();
```

```python
call.iloc[-1]
```

    Prima    8.502
    Name: 2018-06-15, dtype: float64

```python
callui.iloc[-1]
```

    Prima    8.304
    Name: 2018-06-15, dtype: float64

### 3.2 Distribución empírica con Kernel

```python
kde = KernelDensity(kernel='gaussian', bandwidth=0.001).fit(ret_aapl-mu+r)
```

```python
K = 180
B = 190
ndays = (datetime(2018,6,15).date()-datetime(2018,3,14).date()).days
nscen = 100000
dates = pd.date_range('2018-03-15',periods=ndays).astype('str')
kde = KernelDensity(kernel='gaussian', bandwidth=0.001).fit(ret_aapl-mu+r)
ret = kde.sample(n_samples=ndays*nscen, random_state=None)
sim_ret_kernel = pd.DataFrame(ret.reshape((ndays,nscen)),index=dates)
sim_closes_kernel = (closes_aapl.iloc[-1].AAPL)*np.exp(sim_ret_kernel.cumsum())
```

```python
barrier = pd.DataFrame(B*np.ones((ndays, nscen)),index=dates)
I = (sim_closes_kernel>barrier).cumsum()>0
I
```

      0
      1
      2
      ...
      99997
      99998
      99999

      2018-03-15
      False
      False
      False
      ...
      False
      False
      False

      2018-03-16
      False
      False
      False
      ...
      False
      False
      False

      2018-03-17
      False
      False
      False
      ...
      False
      False
      False

      2018-03-18
      False
      False
      False
      ...
      False
      False
      False

      2018-03-19
      False
      False
      False
      ...
      False
      False
      False

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      2018-06-11
      True
      False
      True
      ...
      False
      False
      True

      2018-06-12
      True
      False
      True
      ...
      False
      False
      True

      2018-06-13
      True
      False
      True
      ...
      False
      False
      True

      2018-06-14
      True
      False
      True
      ...
      False
      False
      True

      2018-06-15
      True
      False
      True
      ...
      False
      False
      True

93 rows × 100000 columns

```python
strike = pd.DataFrame(K*np.ones((ndays,nscen)), index=dates)
call_kernel = pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes_kernel-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()},index=dates)
callui_kernel = pd.DataFrame({'Prima':np.exp(-r*ndays)*(I*np.fmax(sim_closes_kernel-strike,np.zeros((ndays,nscen)))).T.mean()},index=dates)
call_kernel.plot();
callui_kernel.plot();
```

```python
call_kernel.iloc[-1]
```

    Prima    8.689
    Name: 2018-06-15, dtype: float64

```python
callui_kernel.iloc[-1]
```

    Prima    8.482
    Name: 2018-06-15, dtype: float64

**Tarea.** Montar la simulación para una opción put down-and-in.

Created with Jupyter by Esteban Jiménez Rodríguez.


# Calculate watermass transformation
From Groeskamp et al. (2019), equation 4:
$$ G(\lambda) = \frac{\partial}{\partial \lambda}\iiint_{\lambda'\leq\lambda}\rho\dot{\lambda}dV $$

For general vertical coordinate $s$, the conservation equation for tracer $C$ integrated over a layer is given by:
$$\int^{z_{k-1}}_{z_k}\rho \dot{C} dz = \partial_t(\rho C h) + \nabla\cdot (\rho C \mathbf{u} h) + \Delta_k(\rho C w^{(s)}) $$
where $\rho$ is the _in situ_ density, $\rho_l$ is the locally referenced potential density, $\mathbf{u}$ and $w^{(s)}$ are the along-layer and dia-surface residual velocities respectively, $\Delta_k$ is a discrete difference operator in the vertical dimension, and $h$ is the layer thickness.

Noting that $\rho\dot{\rho_l} = \rho_{l,\theta}\rho\dot{\theta} + \rho_{l,S}\rho\dot{S}$, transformation across the locally referenced potential density surface can be written:
$$ \begin{align}
G(\rho_l) &= \frac{\partial}{\partial \rho_l} \iint dA \sum_{\rho_l'\leq \rho_l} \int^{z_{k-1}}_{z_k} \rho \dot{\rho_l'} dz \\
          &= \frac{\partial}{\partial \rho_l} \iint dA \sum_{\rho_l'\leq \rho_l} \big( \rho_{l,\theta}[\partial_t(\rho \theta h) + \nabla\cdot (\rho \theta \mathbf{u} h) + \Delta_k(\rho \theta w^{(s)})] + \rho_{l,S}[\partial_t(\rho S h) + \nabla\cdot (\rho S \mathbf{u} h) + \Delta_k(\rho S w^{(s)})] \big)
\end{align}$$
where we have made the asssumption that $\rho_{l,\theta}$ and $\rho_{l,S}$ are constant within each layer.

```python
import xarray as xr
from matplotlib import pyplot as plt
import budgetcalcs as bc
```

```python
rootdir = '/archive/gam/ESM4/DECK/ESM4_piControl_D/gfdl.ncrc4-intel16-prod-openmp/history/'
filename = '08500101.ocean_month_rho2.nc'
ds = xr.open_dataset(rootdir+filename)

filename_native = '08500101.ocean_month.nc'
ds_native = xr.open_dataset(rootdir+filename_native)

# Correct MOM6 tendencies to account for mass in cell
# i.e. convert from [mol kg^-1 m s^-1] to [mol m^-2 s^-1]
rho0=ds_native.rhozero.values
ds['o2h_tendency'] *= rho0
ds['o2h_tendency_vert_remap'] *= rho0
ds['o2_advection_xy'] *= rho0
ds['o2_dfxy_cont_tendency'] *= rho0
ds['o2_vdiffuse_impl'] *= rho0
# Remove bottom boundary flux from jo2_plus_btm (already included in o2_vdiffuse_impl)
#jo2_btm = xr.zeros_like(ds.jo2_plus_btm)
#jo2_btm.loc[dict(zl=ds.zl.max())]=ds.o2_btf
#jo2 = ds.jo2_plus_btm - jo2_btm
#jo2.name = 'jo2'
#ds = xr.merge([ds,jo2])
```

    /nbhome/gam/miniconda/envs/mom6/lib/python3.7/site-packages/xarray/coding/times.py:419: SerializationWarning: Unable to decode time axis into full numpy.datetime64 objects, continuing using cftime.datetime objects instead, reason: dates out of range
      self.use_cftime)

```python
# Calculate material derivative of heat
termsLHS = ['opottemptend','T_advection_xy','Th_tendency_vert_remap']
signsLHS = [-1,1,1]
termsRHS = ['boundary_forcing_heat_tendency','internal_heat_tend_3d','opottempdiff','opottemppmdiff','frazil_heat_tendency']
signsRHS = [1,1,1,1,1]
Dheat, dheat, error_heat = bc.calc_materialderivative(ds,termsLHS,signsLHS,termsRHS,signsRHS,vertc='rho2_l',plot=False)
```

```python
# Calculate material derivate for salt
termsLHS = ['osalttend','S_advection_xy','Sh_tendency_vert_remap']
signsLHS = [-1,1,1]
termsRHS = ['boundary_forcing_salt_tendency','osaltdiff','osaltpmdiff']
signsRHS = [1,1,1]
Dsalt, dsalt, error_salt = bc.calc_materialderivative(ds,termsLHS,signsLHS,termsRHS,signsRHS,plot=False)
```

```python
# Calculate material derivative for density
Dheat = -1*Dheat
Dsalt = -1*Dsalt
Drho = -(ds.alpha/ds_native.rhozero)*Dheat/ds_native.C_p+(ds.beta/ds_native.rhozero)*Dsalt
drho = -(ds.alpha/ds_native.rhozero)*dheat/ds_native.C_p+(ds.beta/ds_native.rhozero)*dsalt
rhoDrho = ds.rhopot2*Drho
rhodrho = ds.rhopot2*drho
```

```python
# Integrate tendencies in each layer
# First, multiply density tendency by grid cell area
area3d = ds_native.areacello*xr.ones_like(ds.temp) # Expand areas to 3D
rhoDrho_cell = rhoDrho*area3d
rhodrho_cell = rhodrho*area3d
# Sum up in each layer
rhoDrho_int = rhoDrho_cell.sum(dim=['xh','yh']).squeeze()
rhodrho_int = rhodrho_cell.sum(dim=['xh','yh']).squeeze()
```

```python
# Divide by density interval
# (and by Boussinesq reference density to convert to m3s-1)
# (and by 1E6 to convert to Sv)
dr = ds.rho2_i.diff(dim='rho2_i').rename({'rho2_i':'rho2_l'}).assign_coords(rho2_l=ds.rho2_l)
G_D = rhoDrho_int/dr/rho0/1E6
G_d = rhodrho_int/dr/rho0/1E6
```

```python
G_D.plot(label='G_D')
G_d.plot(label='G_d')
plt.legend()
```

### Layer-wise budgets for oxygen
From Groeskamp et al. (2019), the change of globally integrated tracer content ($[C]$) between two $\lambda$ contours is given by:
$$ \frac{d[C]}{dt}= \Delta_{\lambda} E_C(\rho_l) + [F^{mass}_C] + \Delta_{\lambda}[M^{dia}_C(\rho2_l)] + [F^{tracer}_C] + [B_C] $$
where
$$ E_C = \frac{\partial}{\partial \lambda}\iiint \rho c \dot{\lambda} dV $$
is the flux of tracer due to water mass transformation, where $c$ is the local tracer concentration. $F$'s represent the flux of tracer at the surface due to mass fluxes and direct tracer fluxes (e.g. air-sea gas exchange), $M^{dia}_C$ represents diffusive tracer fluxes across the bounding contours, $B_C$ respresents sources or sinks due to biogeochemical processes. $\Delta_\lambda$'s denote the difference in that flux between the two bounding contours, square brackets denote the integral of that flux between the bounding contours.

```python
# Take the cell-integrated rhodrho term, multiply it by the local oxygen concentration, and integrate globally
crhodrho_int = (ds.o2*rhodrho_cell).sum(dim=['xh','yh']).squeeze()
crhoDrho_int = (ds.o2*rhoDrho_cell).sum(dim=['xh','yh']).squeeze()
# Divide by the density difference
E_d = crhodrho_int/dr
E_D = crhoDrho_int/dr
```

```python
# Interpolate E onto layer interfaces, and take the difference
E_d = E_d.interp(rho2_l=ds.rho2_i,method='cubic').diff('rho2_i').assign_coords(rho2_i = ds.rho2_l.values).drop('rho2_l').rename({'rho2_i':'rho2_l'})
E_D = E_D.interp(rho2_l=ds.rho2_i,method='cubic').diff('rho2_i').assign_coords(rho2_i = ds.rho2_l.values).drop('rho2_l').rename({'rho2_i':'rho2_l'})
```

```python
# Integrate up all of the other terms
# o2_vdiffuse_impl includes both diffusion across layer interfaces and the air-sea gas exchange and the bottom boundary layer input
MF = (ds.o2_vdiffuse_impl*area3d).sum(dim=['xh','yh'])
# jo2_plus_btm includes all of the biogeochemistry sources and sinks. Unfortunately it will double-count the bottom boundary exchange.
B = (ds.jo2_plus_btm*area3d).sum(dim=['xh','yh'])
# Also sum up the o2h_tendency
C = (ds.o2h_tendency*area3d).sum(dim=['xh','yh'])
```

```python
E_d.plot(label = '\Delta E')
MF.plot(label = '[M+F]')
B.plot(label = '[B]')
(E_d+MF+B).plot(label='sum')
C.plot(label = '[C]')
plt.legend()
```

```python
# Just sum up the tendencies in each layer directly
termsLHS = ['o2h_tendency','o2_advection_xy','o2h_tendency_vert_remap']
termsRHS = ['o2_dfxy_cont_tendency','o2_vdiffuse_impl','jo2_plus_btm']
area3d = ds_native.areacello*xr.ones_like(ds.o2)
ds_int = xr.ones_like(ds_native.areacello)
for var in termsLHS:
    term_int = (ds[var]*area3d).sum(['xh','yh'])
    term_int.name = var
    ds_int = xr.merge([ds_int,term_int])
for var in termsRHS:
    term_int = (ds[var]*area3d).sum(['xh','yh'])
    term_int.name = var
    ds_int = xr.merge([ds_int,term_int])
signsLHS = [-1,1,1]
signsRHS = [1,1,1]
Do2, do2, errors = bc.calc_materialderivative(ds_int.squeeze(),termsLHS,signsLHS,termsRHS,signsRHS,vertc='rho2_l',plot=True)
```

```python
bc.plot_budgetterms(ds_int.squeeze(),termsLHS+termsRHS,vertc='rho2_l')
```

```python
E_d.plot(label='E_d')
ds_int.o2h_tendency_vert_remap.plot(label='vert_remap');
plt.legend()
```

```python

```


# Connecting Qiskit Pulse with Qiskit Dynamics

This tutorial shows a simple model of a qubit used to demonstrate how Qiskit Dynamics can simulate the time evolution of a pulse schedule. The qubit is modeled by the drift hamiltonian

\begin{align}
\hat H_\text{drift} = \frac{\omega}{2} Z
\end{align}

to which we apply the drive

\begin{align}
\hat H_\text{drive}(t) = \frac{r\,\Omega(t)}{2} X
\end{align}

Here, $\Omega(t)$ is the drive signal which we will create using Qiskit pulse. The factor $r$ is the strength with which the drive signal drives the qubit. We begin by creating a pulse schedule with a `sx` gate followed by a phase shift on the drive so that the following pulse creates a `sy` rotation. Therefore, if the qubit begins in the ground state we expect that this second pulse will not have any effect on the qubit. This situation is simulated with the following steps:

1. Create the pulse schedule
2. Convert the pulse schedule to a Signal
3. Create the system model
4. Simulate the pulse schedule using the model

## 1. Create the pulse schedule

First, we use the pulse module in Qiskit to create a pulse schedule.

```python
import numpy as np

import qiskit.pulse as pulse
```

```python
# Strength of the Rabi-rate in GHz.
r = 0.1

# Frequency of the qubit transition in GHz.
w = 5.

# Sample rate of the backend in ns.
dt = 0.222

# Define gaussian envelope function to have a pi rotation.
amp = 1.
area = 1
sig = area*0.399128/r/amp
T = 4*sig
duration = int(T / dt)
beta = 2.0

# The 1.75 factor is used to approximately get a sx gate.
# Further "calibration" could be done to refine the pulse amplitude.
with pulse.build(name="sx-sy schedule") as xp:
    pulse.play(pulse.Drag(duration, amp / 1.75, sig / dt, beta), pulse.DriveChannel(0))
    pulse.shift_phase(np.pi/2, pulse.DriveChannel(0))
    pulse.play(pulse.Drag(duration, amp / 1.75, sig / dt, beta), pulse.DriveChannel(0))

xp.draw()
```

## 2. Convert the pulse schedule to a Signal

To use Qiskit Dynamics we must convert the pulse schedule to instances of `Signal`. This is done using the pulse instruction to signal converter `InstructionToSignals`. This converter needs to know the sample rate of the arbitrary waveform generators creating the signals, i.e. `dt`, as well as the carrier frequency of the signals, i.e. `w`. The plot below shows the envelopes and the signals resulting from this conversion. The dashed line shows the time at which the virtual `Z` gate is applied.

```python
from matplotlib import pyplot as plt

from qiskit_dynamics.pulse import InstructionToSignals

plt.rcParams["font.size"] = 16
```

```python
converter = InstructionToSignals(dt, carriers=[w])

signals = converter.get_signals(xp)
fig, axs = plt.subplots(1, 2, figsize=(14, 4.5))
for ax, title in zip(axs, ["envelope", "signal"]):
    signals[0].draw(0, 2*T, 2000, title, axis=ax)
    ax.set_xlabel("Time (ns)")
    ax.set_ylabel("Amplitude")
    ax.set_title(title)
    ax.vlines(T, ax.get_ylim()[0], ax.get_ylim()[1], "k", linestyle="dashed")
```

## 3. Create the system model

We now setup the Hamiltonian with the signals as drives, enter the rotating frame and perform the rotating wave approximation.

```python
from qiskit.quantum_info.operators import Operator
from qiskit_dynamics import Solver
```

```python
# construct operators
X = Operator.from_label('X')
Z = Operator.from_label('Z')

drift = 2 * np.pi * w * Z/2
operators = [2 * np.pi * r * X/2]

# construct the solver
hamiltonian_solver = Solver(
    drift=drift,
    hamiltonian_operators=operators,
    hamiltonian_signals=signals,
    rotating_frame=drift,
    rwa_cutoff_freq=2*w
)
```

## 4. Simulate the pulse schedule using the model

In the last step we perform the simulation and plot the results.

```python
from qiskit.quantum_info.states import Statevector
```

```python
# Start the qubit in its ground state.
y0 = Statevector([1., 0.])

%time sol = hamiltonian_solver.solve(t_span=[0., 2*T], y0=y0, atol=1e-10, rtol=1e-10)
```

    CPU times: user 24.6 s, sys: 1.82 ms, total: 24.6 s
    Wall time: 24.6 s

```python
def plot_populations(sol):
    pop0 = [psi.probabilities()[0] for psi in sol.y]
    pop1 = [psi.probabilities()[1] for psi in sol.y]

    fig = plt.figure(figsize=(8, 5))
    plt.plot(sol.t, pop0, lw=3, label="Population in |0>")
    plt.plot(sol.t, pop1, lw=3, label="Population in |1>")
    plt.xlabel("Time (ns)")
    plt.ylabel("Population")
    plt.legend(frameon=False)
    plt.ylim([0, 1.05])
    plt.xlim([0, 2*T])
    plt.vlines(T, 0, 1.05, "k", linestyle="dashed")
```

The plot below shows the population of the qubit as it evolves during the pulses. The vertical dashed line shows the time of the virtual Z rotation which was induced by the `shift_phase` instruction in the pulse schedule. As expected, the first pulse moves the qubit to an eigenstate of the `Y` operator. Therefore, the second pulse, which drives around the `Y`-axis due to the phase shift, has hardley any influence on the populations of the qubit.

```python
plot_populations(sol)
```

```python

```


```python
import numpy as np
import sympy
sympy.init_printing(use_unicode=True)
from sympy import symbols,simplify,diff,latex,Piecewise
from sympy.solvers import solve
from IPython.display import display
from typing import Callable

%matplotlib inline
import matplotlib.pyplot as plt

def simplified(exp, title=None):
    simp = simplify(exp)
    if simplified.LOG:
        if title: display(title,simp)
        else: display(simp)
    return simp
simplified.LOG = False

def firstOrderCondition(exp, var):
    diffExp = simplified(diff(exp, var))
    solutions = solve(diffExp, var)
    if firstOrderCondition.LOG:
        display(solutions)
    return solutions
firstOrderCondition.LOG = False

class Result(object):   # a class for holding results of calculations
    def __repr__(self): return self.__dict__.__repr__()
    def display(self):
        for k,v in sorted(self.__dict__.items()):
            display(k,v)
```

```python
a,p,r,b,vmax,bmin,bmax,beta = symbols('a p r b v_{\max} b_{\min} b_{\max} \\beta', positive=True)
w,T,D,L,n,Supply = symbols('w T \\Delta L n S', positive=True)
D
```

```python
def exactCostPerDay(T):
    return (a*p + w*b*( (1+r)**T - 1 )) / T

def approxCostPerDay(T):
    return a*p/T + w*b*r

def symmetricLifetime(w):
    return w**2/4/L

def asymmetricLifetime(w):
    return w / D

def uniformCDF(v):
    return v / vmax

def powerlawCDF(v):
    return (1 - 1 / v)

uniformPDF = Piecewise( (1 / bmax , b<bmax), (0, True) )
powerlawPDF =  Piecewise( (0 , b<bmin), (bmin / b**2, True) )

#uniformPDF(b)
display(sympy.integrate(uniformPDF, (b, 0, np.inf)))  # should be 1
display(sympy.integrate(powerlawPDF, (b, 0, np.inf))) # should be 1
```

```python
def calculateMarketEquilibrium(costPerDay:Callable,channelLifetime:Callable,valueCDF:Callable,wSolutionIndex:int):
    T = simplified(channelLifetime(w), "T")
    CPD = simplified(costPerDay(T), "CPD")
    optimal = Result()
    optimal.w = simplified(firstOrderCondition(CPD,w)[wSolutionIndex], "Optimal channel funding (w)")
    optimal.T   = simplified(T.subs(w,optimal.w), "optimal channel lifetime (T)")
    optimal.CPD = simplified(CPD.subs(w,optimal.w), "Cost-per-day")
    optimal.RPD = simplified(a / optimal.T, "Potential records per day")
    optimal.C   =   simplified(optimal.CPD*optimal.T, "Cost between resets")
    optimal.V   =   simplified(optimal.T*L*beta*b, "Value between resets")
    optimal.VCR1 = 1
    optimal.VCR2 = simplified(optimal.V / optimal.C, "Value/Cost Ratio of lightning")
    optimal.VCR3 = simplified(beta*b / p, "Value/Cost Ratio of blockchain")
    optimal.b12  = simplified(solve(optimal.VCR1-optimal.VCR2,b)[0],"b of indifference between nop and lightning")
    optimal.b13  = simplified(solve(optimal.VCR1-optimal.VCR3,b)[0],"b of indifference between nop and blockchain")
    optimal.b23  = simplified(solve(optimal.VCR2-optimal.VCR3,b)[0],"b of indifference between blockchain and lightning")
    optimal.DemandLightningCondition = simplified(optimal.T*L - optimal.C/p,"demand lightning condition")
    optimal.thresholdPrice = simplified(solve(optimal.DemandLightningCondition, p)[0], "p")
    optimal.DemandLightning = simplified(n*(1-valueCDF(2*a*p / optimal.T/L)) * (a / optimal.T),"demand lightning")
    optimal.eqPriceLightning = simplified(solve(optimal.DemandLightning-Supply, p)[0], "p")
    optimal.DemandBlockchain = simplified(n*(1-valueCDF(p))*L, "demand blockchain")
    optimal.eqPriceBlockchain = simplified(solve(optimal.DemandBlockchain-Supply, p)[0], "p")
    return optimal
```

```python
simplified.LOG = False
firstOrderCondition.LOG = False
asymmetricSolution = calculateMarketEquilibrium(approxCostPerDay,asymmetricLifetime,uniformCDF,wSolutionIndex=0)
```

```python
asymmetricSolution.display()
```

```python
params = {
    L: 10,           # total transfers per day
    D: 4,            # delta transfers per day
    beta: 0.01,      # value as fraction of tx size
    r: 0.5/100/365,  # interest rate per day
    a: 1.1,          # records per reset tx
}
(asymmetricSolution.b12.subs(params),
asymmetricSolution.b13.subs(params),
asymmetricSolution.b23.subs(params))
```

```python
sympy.integrate(asymmetricSolution, )
```

    <function sympy.integrals.integrals.integrate>

```python
simplified.LOG = False
firstOrderCondition.LOG = False
symmetricSolution = calculateMarketEquilibrium(approxCostPerDay,symmetricLifetime,uniformCDF,wSolutionIndex=0)
```

```python
symmetricSolution.display()
```

```python
(symmetricSolution.b12.subs(params),
symmetricSolution.b13.subs(params),
symmetricSolution.b23.subs(params))
```

```python

```