```python
import numpy as np
import sympy as smp
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from matplotlib import animation
from matplotlib.animation import PillowWriter
```

```python
t, m, g, k = smp.symbols('t m g k')
```

```python
the1, the2, r1, r2 = smp.symbols(r'\theta_1, \theta_2, r_1, r_2', cls=smp.Function)

# theta1
the1 = the1(t)
the1_d = smp.diff(the1, t)
the1_dd = smp.diff(the1_d, t)

the2 = the2(t)
the2_d = smp.diff(the2, t)
the2_dd = smp.diff(smp.diff(the2, t), t)

r1 = r1(t)
r1_d = smp.diff(r1, t)
r1_dd = smp.diff(smp.diff(r1, t), t)

r2 = r2(t)
r2_d = smp.diff(r2, t)
r2_dd = smp.diff(smp.diff(r2, t), t)
```

```python
x1, y1, x2, y2 = smp.symbols('x_1, y_1, x_2, y_2', cls=smp.Function)
x1= x1(the1, r1)
y1= y1(the1, r1)
x2= x2(the1, r1, the2, r2)
y2= y2(the1, r1, the2, r2)
```

```python
x1 = (1+r1)*smp.cos(the1)
y1 = -(1+r1)*smp.sin(the1)
x2 = (1+r1)*smp.cos(the1) + (1+r2)*smp.cos(the2)
y2 = -(1+r1)*smp.sin(the1)-(1+r2)*smp.sin(the2)
```

```python
T = 1/2 * m * (smp.diff(x1, t)**2 + smp.diff(y1, t)**2 + \
               smp.diff(x2, t)**2 + + smp.diff(y2, t)**2)
V = m*g*y1 + m*g*y2 + 1/2 * k * r1**2 + 1/2 * k * r2**2
L = T-V
```

```python
LE1 = smp.diff(L, the1) - smp.diff(smp.diff(L, the1_d), t)
LE1 = LE1.simplify()

LE2 = smp.diff(L, the2) - smp.diff(smp.diff(L, the2_d), t)
LE2 = LE2.simplify()

LE3 = smp.diff(L, r1) - smp.diff(smp.diff(L, r1_d), t)
LE3 = LE3.simplify()

LE4 = smp.diff(L, r2) - smp.diff(smp.diff(L, r2_d), t)
LE4 = LE4.simplify()
```

```python
LE1
```

$\displaystyle m \left(2.0 g \operatorname{r_{1}}{\left(t \right)} \cos{\left(\theta_{1}{\left(t \right)} \right)} + 2.0 g \cos{\left(\theta_{1}{\left(t \right)} \right)} - 2.0 \operatorname{r_{1}}^{2}{\left(t \right)} \frac{d^{2}}{d t^{2}} \theta_{1}{\left(t \right)} - 1.0 \operatorname{r_{1}}{\left(t \right)} \operatorname{r_{2}}{\left(t \right)} \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \left(\frac{d}{d t} \theta_{2}{\left(t \right)}\right)^{2} - 1.0 \operatorname{r_{1}}{\left(t \right)} \operatorname{r_{2}}{\left(t \right)} \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta_{2}{\left(t \right)} - 1.0 \operatorname{r_{1}}{\left(t \right)} \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \left(\frac{d}{d t} \theta_{2}{\left(t \right)}\right)^{2} + 1.0 \operatorname{r_{1}}{\left(t \right)} \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \operatorname{r_{2}}{\left(t \right)} - 2.0 \operatorname{r_{1}}{\left(t \right)} \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d}{d t} \theta_{2}{\left(t \right)} \frac{d}{d t} \operatorname{r_{2}}{\left(t \right)} - 1.0 \operatorname{r_{1}}{\left(t \right)} \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta_{2}{\left(t \right)} - 4.0 \operatorname{r_{1}}{\left(t \right)} \frac{d}{d t} \theta_{1}{\left(t \right)} \frac{d}{d t} \operatorname{r_{1}}{\left(t \right)} - 4.0 \operatorname{r_{1}}{\left(t \right)} \frac{d^{2}}{d t^{2}} \theta_{1}{\left(t \right)} - 1.0 \operatorname{r_{2}}{\left(t \right)} \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \left(\frac{d}{d t} \theta_{2}{\left(t \right)}\right)^{2} - 1.0 \operatorname{r_{2}}{\left(t \right)} \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta_{2}{\left(t \right)} - 1.0 \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \left(\frac{d}{d t} \theta_{2}{\left(t \right)}\right)^{2} + 1.0 \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \operatorname{r_{2}}{\left(t \right)} - 2.0 \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d}{d t} \theta_{2}{\left(t \right)} \frac{d}{d t} \operatorname{r_{2}}{\left(t \right)} - 1.0 \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d^{2}}{d t^{2}} \theta_{2}{\left(t \right)} - 4.0 \frac{d}{d t} \theta_{1}{\left(t \right)} \frac{d}{d t} \operatorname{r_{1}}{\left(t \right)} - 2.0 \frac{d^{2}}{d t^{2}} \theta_{1}{\left(t \right)}\right)$

```python
sols = smp.solve([LE1, LE2, LE3, LE4], (the1_dd, the2_dd, r1_dd, r2_dd),
                simplify=False, rational=False)
```

```python
sols[the1_dd]
```

$\displaystyle - \frac{0.00390625 g m \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \sin{\left(\theta_{2}{\left(t \right)} \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} + \frac{0.00390625 g m \cos{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \cos{\left(\theta_{2}{\left(t \right)} \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} - \frac{0.0078125 g m \cos{\left(\theta_{1}{\left(t \right)} \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} + \frac{0.00390625 k \operatorname{r_{2}}{\left(t \right)} \sin{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} - \frac{0.0078125 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d}{d t} \theta_{1}{\left(t \right)} \frac{d}{d t} \operatorname{r_{1}}{\left(t \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} - \frac{0.0078125 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} \frac{d}{d t} \theta_{1}{\left(t \right)} \frac{d}{d t} \operatorname{r_{1}}{\left(t \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m} + \frac{0.015625 m \frac{d}{d t} \theta_{1}{\left(t \right)} \frac{d}{d t} \operatorname{r_{1}}{\left(t \right)}}{0.00390625 m \operatorname{r_{1}}{\left(t \right)} \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \operatorname{r_{1}}{\left(t \right)} \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m \operatorname{r_{1}}{\left(t \right)} + 0.00390625 m \sin^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} + 0.00390625 m \cos^{2}{\left(\theta_{1}{\left(t \right)} - \theta_{2}{\left(t \right)} \right)} - 0.0078125 m}$

```python
dw1dt_f = smp.lambdify((m, k ,g, the1, the2, r1, r2, the1_d, the2_d, r1_d, r2_d), sols[the1_dd])
dthe1dt_f = smp.lambdify(the1_d, the1_d)

dw2dt_f = smp.lambdify((m, k ,g, the1, the2, r1, r2, the1_d, the2_d, r1_d, r2_d), sols[the2_dd])
dthe2dt_f = smp.lambdify(the2_d, the2_d)

dv1dt_f = smp.lambdify((m, k ,g, the1, the2, r1, r2, the1_d, the2_d, r1_d, r2_d), sols[r1_dd])
dr1dt_f = smp.lambdify(r1_d, r1_d)

dv2dt_f = smp.lambdify((m, k ,g, the1, the2, r1, r2, the1_d, the2_d, r1_d, r2_d), sols[r2_dd])
dr2dt_f = smp.lambdify(r2_d, r2_d)
```

```python
def dSdt(S, t):
    the1, w1, the2, w2, r1, v1, r2, v2 = S
    return [
        dthe1dt_f(w1),
        dw1dt_f(m,k,g,the1,the2,r1,r2,w1,w2,v1,v2),
        dthe2dt_f(w2),
        dw2dt_f(m,k,g,the1,the2,r1,r2,w1,w2,v1,v2),
        dr1dt_f(v1),
        dv1dt_f(m,k,g,the1,the2,r1,r2,w1,w2,v1,v2),
        dr2dt_f(v2),
        dv2dt_f(m,k,g,the1,the2,r1,r2,w1,w2,v1,v2),
    ]
```

```python
t = np.linspace(0, 20, 1000)
g = 9.81
m=1
k=10
ans = odeint(dSdt, y0=[np.pi/2,0,(3/2)*np.pi/2,0,0,5,0,5], t=t)
```

```python
plt.plot(ans.T[0])
plt.show()
```

```python
def get_x1y1x2y2(the1, the2, r1, r2):
    return ((1+r1)*np.cos(the1),
            -(1+r1)*np.sin(the1),
            (1+r1)*np.cos(the1) + (1+r2)*np.cos(the2),
            -(1+r1)*np.sin(the1)-(1+r2)*np.sin(the2)
    )
```

```python
x1, y1, x2, y2 = get_x1y1x2y2(ans.T[0], ans.T[2], ans.T[4], ans.T[6])
```

```python
plt.plot(y1)
plt.show()
```

```python
def animate(i):
    ln1.set_data([0, x1[i], x2[i]], [0, y1[i], y2[i]])

fig, ax = plt.subplots(1,1, figsize=(8,8))
ax.grid()
ln1, = plt.plot([], [], 'ro--', lw=3, markersize=8)
ax.set_ylim(-10, 10)
ax.set_xlim(-10,10)
ani = animation.FuncAnimation(fig, animate, frames=1000, interval=50)
## ani.save('pen.gif',writer='pillow',fps=50)
```

```python
from IPython.display import HTML
HTML(ani.to_html5_video())
```

```python

```


## Full formulation
### Formulation as described in O'Brien et al. (2013)
$$
\begin{align}
\max_{v,\mu} \ & \mu = c^T v \\
\mathrm{s.t.} \ & Sv = 0 \\
& v_{dilution,ribo} \geq \sum_{i \in Peptide}\left( \frac{l_{p,i}}{c_{ribo}\kappa_{\tau}} v_{translation,i} (\mu+r_0\kappa_{\tau}) \right) \\
& v_{dilution,RNAP} \geq \sum_{i \in TU}\left( \frac{l_{TU,i}}{3 c_{ribo}\kappa_{\tau}} v_{transcription,i} (\mu+r_0\kappa_{\tau}) \right) \\
& v_{dilution,j} \geq \frac{1}{k_{deg,j}} \cdot \mu \cdot v_{degradation,j}, & \forall j \in mRNA \\
& v_{degradation,j} \geq \frac{3 k_{deg,j}}{\kappa_{\tau} c_{mRNA}} \cdot \frac{\mu + \kappa_{\tau} r_0}{\mu} v_{translation,j}  , & \forall j \in mRNA \\
& v_{dilution,j} \geq \frac{1}{\kappa_{\tau} c_{tRNA,j}} (\mu + \kappa_{\tau} r_0)  v_{charging,j} , & \forall j \in tRNA \\
& v_{dilution,j} \geq \mu \sum_{i} \left( \frac{1}{k_{eff,i}} v_{usage,i} \right), & \forall j \in Enzyme \\
& v^{L} \leq v \leq v^{U} \\
& \mu \leq v_{biomass\_dilution} \leq \mu
\end{align}
$$

where $\mu$ is the growth rate, $v$ is the vector of all fluxes, $l_{p,i}$ is the length of peptide $i$, $l_{TU,i}$ is the length of transcription unit $i$; $c_{ribo}$, $\kappa_{\tau}$, $r_0$, $k_{deg,j}$, $k_{eff,j}$, $c_{mRNA,j}$, $c_{tRNA,j}$ are constants; and $Peptide$, $TU$, $mRNA$, $tRNA$, $Enzyme$ are index sets of $v$ representing peptides, transcription units, mRNA, tRNA, and metabolic enzymes, respectively.

# For ME modeling overview figure

$$
\begin{pmatrix}
& Met  & Trans & Charg & Transcr \\
met1 & -1 & . & . & .\\
met2 & 1 & . & . & .\\
enz1 & -\frac{\mu}{k_{eff}} & 1 & . & .\\
mRNA & . &-\frac{\mu + K_{\tau}r_0}{K_{\tau}c_{mRNA}}& . & 1\\
ribo & . &-\frac{l_{p,i}(\mu + K_{\tau}r_0)}{c_{ribo}K_{\tau}}& . & .\\
tRNA\_char & . & -1 & 1 & .\\
tRNA & . & . & -(1 + \frac{\mu + K_{\tau}r_0}{K_{\tau}c_{tRNA,j}}) & .\\
aa & . & . & -(1 + \frac{\mu + K_{\tau}r_0}{K_{\tau}c_{tRNA,j}}) & . \\
synth & . & . & -\frac{\mu}{k_{eff}} & . \\
nuc & . & . & . & -1\\
RNAP & . & . & . & - \frac{l_{TU,i}(\mu + K_{\tau}r_0)}{3c_{ribo}K_{\tau}}
\end{pmatrix}
$$

$$
\begin{align*}
v_{degradation,j} \cdot \mu &\geq \frac{3 k_{deg,j} (\mu + \kappa_\tau r_0)}{\kappa_\tau c_{mRNA}} v_{translation,j}
\end{align*}
$$

$$
\begin{align*}
mRNA &: v_{transc} - v_{degradation} - v_{dilution} - v_{translation} = 0 \\
& v_{transc} - v_{degradation} - v_{dilution + translation}= 0 \\
Deg-constraint &: v_{degradation,j} \geq \frac{3 k_{deg,j}}{\kappa_{\tau} c_{mRNA}} \cdot \frac{\mu + \kappa_{\tau} r_0}{\mu} v_{translation,j} \\
& v_{degradation,j} \cdot \mu \geq \frac{3 k_{deg,j} (\mu + \kappa_\tau r_0)}{\kappa_\tau c_{mRNA}} v_{translation,j}
\end{align*}
$$

$$
\begin{align*}
v_{degradation,j} &\geq \frac{\mu}{k_{eff,deg}} v_{translation,j} \\
k_{mRNA,deg} &= \frac{\mu \kappa_{\tau} c_{mRNA,j}}{3 k_{deg,j}(1 + \kappa_{\tau} r_0/\mu)} \\
k_{mRNA,cat} &= \frac{c_{mRNA} \kappa_\tau \mu}{\mu + r_0 \kappa_\tau} \\
v_{dil,mRNA} &\geq \frac{\mu}{k_{mRNA,cat}} v_{translation,j} \\
v_{deg,mRNA} &\geq \frac{k_{deg,mRNA}}{k_{mRNA,cat}} v_{translation,j} \\
\end{align*}
$$

$$
\begin{align*}
\frac{k_{mRNA,deg}}{k_{mRNA,cat}} &= \frac{\mu + r_0 \kappa_\tau}{3k_{deg,j}(1+\kappa_\tau r_0/\mu)}
\end{align*}
$$

$$
\begin{align*}
\frac{k_{mRNA,deg}}{k_{mRNA,cat}} &= \frac{\mu + r_0 \kappa_\tau}{3k_{deg,j}(1+\kappa_\tau r_0/\mu)}
\end{align*}
$$

### Constants
$$
\begin{align*}
\frac{R}{P} &= \frac{\mu + \kappa_{\tau} r_0}{\kappa_{\tau}}\\
c_{ribo} &= \frac{m_{rr,i}}{m_{aa} f_{rRNA}} =  \frac{\sim 1700 \mathrm{\ kDa}}{0.109 \mathrm{\ kDa} \cdot 0.86} \approx 18135 \\
\kappa_{\tau} &= 4.5 \mathrm{\ h}^{-1} \\
r_0 &= 0.087 \\
c_{tRNA,j} &= \frac{m_{tRNA,j}}{m_{aa} f_{tRNA}} = \frac{\sim 25 \mathrm{\ kDa}}{0.109 \mathrm{\ kDa} \cdot 0.12} \approx 1911 \\
k_{eff,tRNA charging} &= \frac{\kappa_\tau c_{tRNA,j}\mu}{\mu + \kappa_\tau r_0} = \frac{P m_{tRNA}\mu}{Rf_{tRNA}m_{aa} } \\
\end{align*}
$$

```python
kt = 4.5
r0 = 0.087
c_ribo = 1700/0.109/0.86
m_tRNA = 25    # kDa
m_aa = 0.109   # kDa
k_deg = 1.0/5 * 60.0  # 1/5 1/min 60 min/h # h-1
f_tRNA = 0.12
c_tRNA = m_tRNA/(m_aa * f_tRNA)
# tRNA charging keff
keff_tRNA_charging = kt * c_tRNA
# Translation keff
keff_transl_per_length = c_ribo*kt
print 'c_tRNA * kt = ',c_tRNA * kt
print c_ribo
print c_tRNA
print keff_transl_per_length
print keff_tRNA_charging
print k_deg
```

    c_tRNA * kt =  8600.91743119
    18135.2677619
    1911.31498471
    81608.7049285
    8600.91743119
    12.0

```python
m_nt = 324./1000.
f_mRNA = .02
c_mRNA = m_nt/(f_mRNA*m_aa)
print c_mRNA * kt / 3600.
```

    0.185779816514

```python
r0 * kt
```

    0.39149999999999996

```python
c_tRNA * kt/3600.
```

    2.38914373088685

```python
c_tRNA
```

    1911.31498470948

```python
f = lambda x: ((x / kt) + r0)
```

```python
f(0.924162)
```

    0.29236933333333337

```python
%matplotlib inline
x = linspace(0, 1)
plot(x, f(x))
```

```python
mu = 1.0
mu * 22.7 / (mu + 0.391)
```

    16.319194823867722

### Generalized formulation
$$
\begin{align}
\max_{v,\mu} \ & \mu = c^T v = v_{j:j=growth}\\
\mathrm{s.t.} \ & S v = 0 \\
& \mu \sum_{j=1}^{n} a_{ij} v_j + \sum_{j=1}^{n} b_{ij} v_j \leq 0, & i=1,\dots,q \\
& v^L \leq v \leq v^U
\end{align}
$$
where $q$ is the number of bilinear constraints, and $a_{ij}$ and $b_{ij}$ are constants.

### Piecewise McCormick relaxation formulation of BLP
$$
\begin{align}
\min_{x,w,y} \ & c^T x \\
\mathrm{s.t.} \ & Ax = b \\
& w_\textit{ij} \geq \sum_n(\hat{x}_\textit{ijn} \cdot x^L_\textit{jn} + x^L_i \cdot \hat{x}_\textit{jn} - x^L_i \cdot x^L_\textit{jn} \cdot y_\textit{jn})\\
& w_\textit{ij} \geq \sum_n(\hat{x}_\textit{ijn} \cdot x^U_\textit{jn} + x^U_i \cdot \hat{x}_\textit{jn} - x^U_i \cdot x^U_\textit{jn} \cdot y_\textit{jn}) \\
& w_\textit{ij} \leq \sum_n(\hat{x}_\textit{ijn} \cdot x^L_\textit{jn} + x^U_i \cdot \hat{x}_\textit{jn} - x^U_i \cdot x^L_\textit{jn} \cdot y_\textit{jn}) \\
& w_\textit{ij} \leq \sum_n(\hat{x}_\textit{ijn} \cdot x^U_\textit{jn} + x^L_i \cdot \hat{x}_\textit{jn} - x^L_i \cdot x^U_\textit{jn} \cdot y_\textit{jn}) \\
& x_i = \sum_n \hat{x}_\textit{ijn} \\
& x_j = \sum_n \hat{x}_\textit{jn} \\
& x_i^L y_\textit{jn} \leq \hat{x}_\textit{ijn} \leq x_i^U y_\textit{jn} \\
& x_\textit{jn}^L y_\textit{jn} \leq \hat{x}_\textit{jn} \leq x_\textit{jn}^U y_\textit{jn} \\
& \sum_n y_\textit{jn} = 1 \\
& x^L_\textit{jn} = x^L_j+ \frac{(x^U_j-x^L_j) \cdot (n-1)}{N} \\
& x^L_\textit{jn} = x^L_j+ \frac{(x^U_j-x^L_j) \cdot n}{N} \\
\end{align}
$$

### Piecewise McCormick relaxation formulation of tiny-ME
$$
\begin{align}
\max_{v,\mu,\hat{v},\hat{\mu},w,y} \ & \mu = c^T v \\
\mathrm{s.t.} \ & Sv = 0 \\
& w_\textit{i} \geq \sum_n(\hat{v}_\textit{in} \cdot \mu^L_\textit{n} + v^L_i \cdot \hat{\mu}_n - v^L_i \cdot \mu^L_\textit{n} \cdot y_\textit{n})\\
& w_\textit{i} \geq \sum_n(\hat{v}_\textit{in} \cdot \mu^U_\textit{n} + v^U_i \cdot \hat{\mu}_n - v^U_i \cdot \mu^U_\textit{n} \cdot y_\textit{n}) \\
& w_\textit{i} \leq \sum_n(\hat{v}_\textit{in} \cdot \mu^L_\textit{n} + v^U_i \cdot \hat{\mu}_n - v^U_i \cdot \mu^L_\textit{n} \cdot y_\textit{n}) \\
& w_\textit{i} \leq \sum_n(\hat{v}_\textit{in} \cdot \mu^U_\textit{n} + v^L_i \cdot \hat{\mu}_n - v^L_i \cdot \mu^U_\textit{n} \cdot y_\textit{n}) \\
& v^L_i y_n \leq \hat{v}_\textit{in} \leq v^U_i y_n \\
& \mu^L_n y_n \leq \hat{\mu}_n \leq \mu^U_n y_n \\
& v_i = \sum_{n}\hat{v}_\textit{in} \\
& \mu = \sum_{n}\hat{\mu}_n \\
& \sum_n y_\textit{n} = 1 \\
& \mu^L_\textit{n} = \mu^L+ \frac{(\mu^U-\mu^L) \cdot (n-1)}{N} \\
& \mu^L_\textit{n} = \mu^L+ \frac{(\mu^U-\mu^L) \cdot n}{N} \\
\end{align}
$$

### Radix-based discretization of BLP
$$
\begin{align}
\max_{x} \ & c^T x \\
\mathrm{s.t.} \ & Ax = b \\
& \sum_i \sum_j a_\textit{ij} x_i x_j = 0 \\
\end{align}
$$

can be approximate with specified precision by

$$
\begin{align}
\max_{x,w,\hat{x},z} \ & c^T x \\
\mathrm{s.t.} \ & Ax = b \\
& \sum_i \sum_j a_\textit{ij} w_\textit{ij} = 0 \\
& w_\textit{ij} = \sum_{l=p}^P \sum_{k=0}^9 10^l \cdot k \cdot \hat{x}_\textit{ijkl} \\
& x_j = \sum_{l=p}^P \sum_{k=0}^9 10^l \cdot k \cdot z_\textit{jkl} \\
& x_i^L \cdot z_\textit{jkl} \leq \hat{x}_\textit{ijkl} \leq x_i^U \cdot z_\textit{jkl} \\
& \sum_{k=0}^9 \hat{x}_\textit{ijkl} = x_i \\
& \sum_{k=0}^9 z_\textit{jkl} = 1 \\
& z_\textit{jkl} \in \{0,1\}
\end{align}
$$

### Radix-based discretization of tiny-ME
$$
\begin{align}
\max_{v,\mu,w,\hat{v},z} \ & \mu = c^T v\\
\mathrm{s.t.} \ & S v = 0 \\
& \sum_j a_\textit{i,j} w_j + \sum_j b_\textit{i,j} v_j = 0 \\
& w_j = \sum_{l=p}^P \sum_{k=0}^9 10^l \cdot k \cdot \hat{v}_\textit{jkl} \\
& \mu = \sum_{l=p}^P \sum_{k=0}^9 10^l \cdot k \cdot z_\textit{kl} \\
& v_j^L \cdot z_\textit{kl} \leq \hat{v}_\textit{jkl} \leq v_j^U \cdot z_\textit{kl} \\
& \sum_{k=0}^9 \hat{v}_\textit{jkl} = v_j, & \forall l \in L, \forall j \in Coupling \\
& \sum_{k=0}^9 z_\textit{kl} = 1, & \forall l \in L \\
& z_\textit{kl} \in \{0,1\}
\end{align}
$$

#### Additional tightening via McCormick envelopes
$$
\begin{align}
& w_\textit{j} \geq v_\textit{j} \cdot \mu^L + v^L_j \cdot \mu - v^L_j \cdot \mu^L \\
& w_\textit{j} \geq v_\textit{j} \cdot \mu^U + v^U_j \cdot \mu - v^U_j \cdot \mu^U \\
& w_\textit{j} \leq v_\textit{j} \cdot \mu^L + v^U_j \cdot \mu - v^U_j \cdot \mu^L \\
& w_\textit{j} \leq v_\textit{j} \cdot \mu^U + v^L_j \cdot \mu - v^L_j \cdot \mu^U \\
\end{align}
$$

### Additional nonlinear constraints in GSM ME
$$
20 \cdot \textrm{average_aa_mw} \cdot \textrm{proportion_of_rna_that_is_mrna}
$$

```python
from sympy import init_printing, symbols, latex, simplify
from sympy.printing.mathml import print_mathml
from sympy.parsing.sympy_parser import parse_expr
expr = "1058.0*average_aa_mw*proportion_of_rna_that_is_rrna*(0.222222222222222*growth_rate_in_per_hour + 1.087)*(0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - 6.53086343855605e-9*(5731824.61240473*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 2614423.4000092*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589 - (0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - 6.53086343855605e-9*(5731824.61240473*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 2614423.4000092*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589)/(0.222222222222222*growth_rate_in_per_hour + 1.087))/(mass_rrna_per_ribosome*(0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - 6.53086343855605e-9*(5731824.61240473*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 2614423.4000092*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589))"
#expr = "(22039.3155555555*growth_rate_in_per_hour + 107805.31204)*(0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - (0.0374337637973698*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0170744421960255*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589 - (0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - (0.0374337637973698*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0170744421960255*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589)/(0.222222222222222*growth_rate_in_per_hour + 1.087))/(65351.451*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - 1700000.0*(0.0374337637973698*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0170744421960255*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 1560066.013)"
expr = "0.0184129956861297*growth_rate_in_per_hour*unmodeled_protein_proportion_of_proteome*(0.03844203*growth_rate_in_per_hour**3.90364139/(growth_rate_in_per_hour**3.90364139 + 0.11685874) - 6.53086343855605e-9*(5731824.61240473*2**(0.231049057876158*growth_rate_in_per_hour)*(-0.45612411*2**(0.231049057876158*growth_rate_in_per_hour) + 3.9*2**(0.231049060186648*growth_rate_in_per_hour)) + 2614423.4000092*2**(0.462098115752316*growth_rate_in_per_hour))/(0.052012300930823*2**(0.462098115752316*growth_rate_in_per_hour)*(-1.43295615310118*2**(0.231049057876158*growth_rate_in_per_hour) + 12.2522113490002*2**(0.231049060186648*growth_rate_in_per_hour)) + 0.0372656733278866*2**(0.693147173628473*growth_rate_in_per_hour)) + 0.91768589)/(0.222222222222222*growth_rate_in_per_hour + 1.087)"
#init_printing()   # Doesn't work...
expr = expr.replace('growth_rate_in_per_hour','mu')

sexpr = parse_expr(expr)

#sexpr = simplify(sexpr)

l = latex(sexpr)
print l
#print_mathml(sexpr)

import sympy as sp
sp.N(sexpr,subs={'mu' : 0.1, 'unmodeled_protein_proportion_of_proteome' : 0.45})

```

    0.0184129956861297 \frac{\mu unmodeled_{protein proportion of proteome} \left(0.03844203 \frac{\mu^{3.90364139}}{\mu^{3.90364139} + 0.11685874} - \frac{0.0374337637973698 \times 2^{0.231049057876158 \mu} \left(- 0.45612411 \times 2^{0.231049057876158 \mu} + 3.9 \times 2^{0.231049060186648 \mu}\right) + 0.0170744421960255 \times 2^{0.462098115752316 \mu}}{0.052012300930823 \times 2^{0.462098115752316 \mu} \left(- 1.43295615310118 \times 2^{0.231049057876158 \mu} + 12.2522113490002 \times 2^{0.231049060186648 \mu}\right) + 0.0372656733278866 \times 2^{0.693147173628473 \mu}} + 0.91768589\right)}{0.222222222222222 \mu + 1.087}

    0.000506667522842899

$$
0.0184129956861297 \frac{\mu unmodeled_{protein proportion of proteome} \left(0.03844203 \frac{\mu^{3.90364139}}{\mu^{3.90364139} + 0.11685874} - \frac{0.0374337637973698 \times 2^{0.231049057876158 \mu} \left(- 0.45612411 \times 2^{0.231049057876158 \mu} + 3.9 \times 2^{0.231049060186648 \mu}\right) + 0.0170744421960255 \times 2^{0.462098115752316 \mu}}{0.052012300930823 \times 2^{0.462098115752316 \mu} \left(- 1.43295615310118 \times 2^{0.231049057876158 \mu} + 12.2522113490002 \times 2^{0.231049060186648 \mu}\right) + 0.0372656733278866 \times 2^{0.693147173628473 \mu}} + 0.91768589\right)}{0.222222222222222 \mu + 1.087}
$$

```python
r0 = 0.087
kt = 4.5
kdeg = 1/5 * 60
fmRNA = 0.02
mnt = 324
maa = 109
cmRNA = mnt / fmRNA / maa

print 1.087/0.0292550617283951
print 0.22222222/0.00598079561042524

```

    37.1559633028
    37.1559629312

```python
'CPLX0-222_TU_p_4325087_4328261'
```

    'CPLX0-222_TU_p_4325087_4328261'

### Minimal proteome identification procedure
#### Version A: fewer differentially expressed genes (DEGs) than non-DEGs
$$
\begin{align}
\min_{v,\ y} \ & {\sum_{i} y_i} \\
\mathrm{s.t.} \ & Sv=0 \\
& v_{biomass} \geq \mu^{min} \\
& v_j^L \leq v_j \leq v_j^U, & \forall j \in NDEGs \\
& y_j v_j^L \leq v_j \leq y_j v_j^U, & \forall j \in DEGs\\
\end{align}
$$
where the sets $DEGs$ and $NDEGs$ are determined using meta-analysis of differential gene expression data sets across strains and/or conditions. Also, $\mu^{min}$ is the minimum required growth rate for the cell to be considered viable.


# Instructor Feedback

#### Thank you for taking the time to fill out the feedback form!! Some trends I observed were:
- Asking for more worked examples
- Readings to be posted again
- Details about timing of CP
- Pacing (sometimes fast, sometimes slow)***

-----

#### What I will try to do is:
- Compliment more of lectures with hand derivations (starting today)
- Make my jupyter notebooks more interactive, so the presentation style is dynamic
- Update the reading list
- A few explanations
  * Sometimes I do spend time answering questions in lecture because the concept asked about is important. However, I will note if something is a "deeper" question and suggest you come to office hours if it will interfere with other topics being presented
  -----

#### Updates about CPs
- CP2 you will get on Friday. It will be due 2 weeks from Friday.
- CP3 you will get the last week of classes

----
#### Other
- If you and your colleagues have issues with the same topic, please email me and I will add a worked problem to lecture moving forward.

```python
%matplotlib inline

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

plt.rcParams["figure.figsize"] = (12, 9)
plt.rcParams["font.size"] = 18
```

# Transport Fundamentals

Some fundamentals beyond the life cycle of the fission neutron.

## Learning Objectives

- Calculate the dynamic impact of reactivity feedback on criticality.
- Recognize the impact of moderator material choice on reactor behavior.
- Analyze reactor behavior.
- Calculate the impact of geometry on criticality.
- State Fick's Law.
- Apply diffusion to neutron transport.
- Establish the one group diffusion equation.
- Derive the multigroup diffusion equations.
- State the definition and meaning of the transport cross section.

### Reactivity is deviation from k

\begin{align}
\rho &= \frac{k-1}{k}
\end{align}

```python
def rho(k):
    """reactivity, rho
    :param k: multiplication factor
    """
    return (k-1)/k

# Plot reactivity as a function of k
import numpy as np
k_vals = np.arange(0.7, 1.0, 0.01)
r = [rho(k) for k in k_vals]
plt.plot(k_vals, r)
plt.ylabel(r'reactivity $\rho$ $[\frac{\delta k}{k}]$',fontsize=20,)
plt.xlabel(r'multiplication factor $k$ $[-]$',fontsize=20,)
plt.title(r'$\rho = \frac{k-1}{k}$', fontsize=20, y=1.02)
```

## Neutron Transport Fundamentals

### Fast Homogenous Reactor

Consider a reactor that is:

- fast
- critical
- the fuel and coolant are a homogeneous mixture
- the reactor has only one region, no reflector ("bare" reactor)

This reactor can be described by the one group diffusion equation:

\begin{align}
D\nabla^2\phi-\Sigma_a\phi + s &= \frac{1}{v}\frac{\partial \phi}{\partial t}\\
D &= \mbox{ the diffusion coefficient}\\
\phi &= \mbox{ flux}\\
v &= \mbox{ neutron speed}
\end{align}

If the fission source, $s$ does not balance neutron absorption and leakage, then the right hand side of the one-group diffusion equation is nonzero and the power may increase or decrease with time.

#### Steady state

The multiplication factor, k, can be used to adjust the source strength and reach a steady state diffusion equation:

\begin{align}
D\nabla^2\phi-\Sigma_a\phi + \frac{1}{k}\nu\Sigma_f\phi &= 0\\
\end{align}

## Diffusion Solution

```python
## This code example was adapted from
## https://github.com/marort91/AlphaEigenvalue/blob/master/RadiationTransportCoding/NeutronDiffusion_Python/NDE_CriticalityEigenvalue.ipynb

def diff(sig_tr):
    return 1.0/(3.0*sig_tr)

def sig_tr(e):
    sig_t(e) - mu*sig_s(e)

D = diff(0.1)
nusigf = 0.70
siga = 0.066

Lx = np.pi*((nusigf-siga)/D)**(-0.5)

N = 50;
h = Lx/(N-1)

x = np.zeros(N)

for i in range(N-1):
    x[i+1] = x[i] + h

L = np.zeros((N,N))
A = np.zeros((N,N))
M = np.zeros((N,N))

for i in range(N):
    L[i][i] = L[i][i] + (-2*(-D/(h**2)))

for i in range(1,N):
    L[i][i-1] = L[i][i-1] + (-D/h**2)

for i in range(N-1):
    L[i][i+1] = L[i][i+1] + (-D/h**2)

for i in range(N):
    A[i][i] = A[i][i] + siga

M = L + A

M[0][0] = 1
M[0][1] = 0
M[N-1][N-1] = 1
M[N-1][N-2] = 0

phi0 = np.ones((N,1))
phi0[0] = 0
phi0[N-1] = 0

tol = 1e-15
k = 1.00

def is_converged(k_old, k, tol):
    return np.abs(k - k_old) <= tol

for i in range(100):
    # update k
    k_old = k
    # solve for psi
    psi = np.linalg.solve(M, nusigf*phi0)

    # solve for k
    k = sum(nusigf*psi)/sum(nusigf*phi0)

    # solve for phi
    phi0 = (1/k)*psi
    phi0[0] = 0
    phi0[N-1] = 0

    # determine convergence
    if is_converged(k_old, k, tol):
        break

plt.plot(x, phi0)
plt.xlabel('Slab (cm)')
plt.ylabel('Neutron Flux')
plt.grid()

print("k-effective = ", k)

print(" approx alpha = ", rho(k) * sum(nusigf*phi0)/sum(phi0))
```

### One Group Reactor Equation

We can define a quantity, geometric bucking, as:

\begin{align}
B^2 &= \frac{1}{D}\left(\frac{\nu}{k}\Sigma_f - \Sigma_a\right)\\
\end{align}

Next, we can simplify the previous equation using this definition, so that the one-group reactor equation becomes:

\begin{align}
\nabla^2\phi + B^2\phi &= 0\\
\end{align}

To find the criticality of a reactor with a specific geometry, then, we can solve the geometric buckling equation for k:

\begin{align}
k &= \frac{\nu\Sigma_f}{DB^2 + \Sigma_a}\\
\end{align}

The buckling, B, is used to help describe the geometry of the system. The solutions of the one group reactor equation for boundary conditions corresponding to canonical shapes provide both flux and buckling formulae for each canonical shape:

#### Slab
Thickness a:
\begin{align}
\phi &= cos\left(\frac{\pi x}{a}\right)\\
B^2 &= \frac{\pi^2}{a^2}
\end{align}

#### Cylinder
Height H, Radius R:
\begin{align}
\phi &= J_0\left(\frac{\nu_0r}{R}\right)cos\left(\frac{\pi z}{H}\right)\\
B^2 &= \frac{\nu_0^2}{R^2} + \frac{\pi^2}{H^2}
\end{align}

#### Sphere
Radius R:
\begin{align}
\phi &= \left(\frac{1}{r}\right)sin\left(\frac{\pi r}{R}\right)\\
B^2 &= \frac{\pi^2}{R^2}
\end{align}

```python
def k(nusigf, diff, bsq, siga):
    return nusigf/(diff*bsq + siga)

def bsq_sphere(r):
    return (np.pi**2)/(r**2)

nusigf = 0.3
diff = 1.1
siga =0.01

fig, ax1 = plt.subplots()
radii = np.arange(0.1, 10.0, 0.01)
ax1.plot(radii, k(nusigf, diff, bsq_sphere(radii), siga), 'b-', label='k')
ax1.set_xlabel('radius (r)')
ax1.set_ylabel('k', color='b')
ax1.set_title('Criticality and Geometric Buckling of a Sphere')
for tl in ax1.get_yticklabels():
    tl.set_color('b')

ax2 = ax1.twinx()

ax2.plot(radii,  bsq_sphere(radii), 'darkorange', label=r'$B^2$')
ax2.set_ylabel(r'$B^2$', color='darkorange')
for tl in ax2.get_yticklabels():
    tl.set_color('darkorange')

ax1.legend()
ax2.legend(loc=2)
```

### Multigroup and beyond

To capture the variation of neutron energies in the diffusion equation, we can discretize the flux into energy groups.

\begin{align}
\phi &= \sum_{g=1}^{g=G}\phi_g\\
\phi_g &= \int_{E_g}^{E_{g-1}}\phi(E)dE\rvert_{g = 1,G}
\end{align}

The diffusion coefficient also needs to be individually evaluated for each energy group such that we have a $D_g$ for each group g. Also, it is important to consider possible paths of demise for potential fission neutrons.

The derivation, based on integration, can be found in Lamarsh Ch. 5. It is based on the following integration:

\begin{align}
\int_{E_g}^{E_{g-1}}dE\left[-\nabla D(\vec{r},E)\nabla\phi(\vec{r},E)+ \Sigma_t(\vec{r},E)\phi(\vec{r},E)\right] &= \int_{E_g}^{E_{g-1}}dE\left[\int_{E'}\Sigma_{s}(\vec{r},E'\rightarrow E)\phi(\vec{r},E') + S(\vec{r},E)\right]\\
\end{align}

Once this integration is completed for a set of group boundaries, $g\in[1,G] \rightarrow E_g\in[E_1,E_G]$, one can generically write:

\begin{align}
-\nabla D_g\nabla\phi_g+ \Sigma_{t,g}\phi_g &= \sum_{g'=1}^{g'=G}\Sigma_{s}^{g'\rightarrow g}\phi_{g'} + S\int_{E_g}^{E_{g-1}}\chi(E)dE\\
\end{align}

Recall, however, that the fission spectrum is a probability density function, so one can also write the following identities:

\begin{align}
\int_0^\infty\chi(E) &= 1\\
\chi_g &= \int_{E_g}^{E_{g-1}}\chi(E)dE\\
\sum_{g=1}^{g=G}\chi_g &= 1\\
\end{align}

This simplifies the diffusion equation:

\begin{align}
-\nabla D_g\nabla^2\phi_g+ \Sigma_{t,g}\phi_g &= \sum_{g'=1}^{g'=G}\Sigma_{s}^{g'\rightarrow g}\phi_{g'} + \chi_gS\\
\end{align}

where

\begin{align}
S &= \sum_{g'=1}^{g'=G}(\nu\Sigma_f)_{g'}\phi_{g'}
\end{align}

#### Group Removal Cross Section

The right hand side summation of the scattering cross section is confusing though. Most of the scattering is from other groups into this one, but some of the scattering is from this group into itself. Keeping that term, $\Sigma_s^{g'\rightarrow g}$ on the right hand side is misleading. It's not a source of new neutrons. So, we have a change of variables that can clean this up, the group removal cross section.

\begin{align}
\Sigma_{R,g} &= \Sigma_{t,g} - \Sigma_{s}^{g'\rightarrow g}
\end{align}

If we use the group removal cross section, then we arrivae at the following form of the multigroup equation.

\begin{align}
-\nabla D_g\nabla^2\phi_g+ \Sigma_{R,g}\phi_g &= \sum_{g'\ne G}^{g'=G}\Sigma_{s}^{g'\rightarrow g}\phi_{g'} + \chi_g\sum_{g'=1}^{g'=G}(\nu\Sigma_f)_{g'}\phi_{g'}\\
\end{align}

### Two Group Diffusion

Let's define just two groups, fast and thermal. Let's also state that all prompt neutrons are born fast and that the diffusion coefficient and cross sections do not vary in space. With these assumptions, we arrive at the following two equations:

\begin{align}
-D_1\nabla^2\phi_1 + \Sigma_{R,1}\phi_1 &= \Sigma_{s}^{2\rightarrow 1}\phi_{2} + \left[(\nu\Sigma_f)_{1}\phi_{1} + (\nu\Sigma_f)_{2}\phi_{2}\right]\\
\end{align}

\begin{align}
- D_2\nabla^2\phi_2+ \Sigma_{R,2}\phi_2 &= \Sigma_{s}^{1\rightarrow 2}\phi_{1}\\
\end{align}

**Discussion: What happened to the prompt fission spectrum, $\chi_g$ in the above equations?**

**Discussion: If we neglect upscattering, which term or terms in the above two equations will disappear?**

### Criticality calculation

For criticality calculations, one might normalize the prompt fission spectrum with k as we have done before. For simplicity, one can move the scattering term to the left hand side, as if to say "diffusion, group removal, and scattering are balanced at criticality."

\begin{align}
-\nabla D_g\nabla\phi_g+ \Sigma_{R,g}\phi_g - \sum_{g'\ne G}^{g'=G}\Sigma_{s}^{g'\rightarrow g}\phi_{g'} &= \frac{1}{k}\chi_g\sum_{g'=1}^{g'=G}(\nu\Sigma_f)_{g'}\phi_{g'}\\
\end{align}

This change propagates to the explicit equations for two groups thus:

\begin{align}
- D_1\nabla^2\phi_1 + \Sigma_{R,1}\phi_1 - \Sigma_{s}^{2\rightarrow 1}\phi_{2} &= \frac{1}{k}\chi_1\left[(\nu\Sigma_f)_{1}\phi_{1} + (\nu\Sigma_f)_{2}\phi_{2}\right]\\
\end{align}

\begin{align}
- D_2\nabla^2\phi_2+ \Sigma_{R,2}\phi_2 - \Sigma_{s}^{1\rightarrow 2}\phi_{1} &= 0\\
\end{align}

## Fick's Law

Backing up, let's review diffusion in the context of Fick's law. In one dimension:

\begin{align}
J_x = -D\frac{d\phi}{dx}
\end{align}

For multiple spatial variables, we use the grad symbol $\nabla$ to indicate the gradient in all spatial dimensions:

\begin{align}
\vec{J} = -D\nabla\phi
\end{align}

J is the neutron current density (or, just the neutron current) and is a *vector quantity*. We can use it to describe the flow of neutrons into and out of a volume. If $\hat{n}$ is the unit normal vector pointing out of a surface, then,

\begin{align}
\vec{J}\cdot\hat{n} = \frac{\mbox{neutrons passing outward through the surface}}{cm^2s}\\
\end{align}

# Steady State 2D Multi-Group

Steady-state 2D multi-group neutron diffusion equation

\begin{align}
\nabla\cdot J_g(x,y) &+\Sigma_{r,g}(x,y)\phi_g(x,y) = S_g(x,y)
\end{align}
where

\begin{align}J_g(x,y) &= -D_g(x,y)\nabla\phi_g(x, y)\\
\Sigma_{r,g}(x,y) &= \Sigma_{a,g}(x,y) + \sum_{g'\ne g}\Sigma_{s,g\rightarrow g'}(x,y)\\
S_g(x,y) &= \frac{\chi_g(x,y)}{k}\sum_{g'}\nu\Sigma_{f,g'}\phi_{g'}(x,y) + \sum_{g'\ne g}\Sigma_{s,g'\rightarrow g}(x,y)\phi_{g'}\\
D_g(x,y) &= \frac{1}{3\Sigma_{tr,g}(x,y)}
\end{align}

### Nomenclature

#### Subscripts

 \begin{align}
  g &= \mbox{energy group (discrete energy interval)}
 \end{align}

#### Variables

 \begin{align}
  \phi_g(x,y) &= \mbox{neutron flux}\\
  J_g(x,y) &= \mbox{neutron current (or neutron current density)}
  \end{align}

#### Coefficients (cross-sections, material properties)

 \begin{align}
\Sigma_{r,g}(x,y) &= \mbox{removal cross-section, sum of absorption and out-scattering (scattering from group g to group g’)}\\
D{g}(x,y) &= \mbox{diffusion coefficient (analogous to conductivity in heat conduction)}\\
\Sigma_{a,g}(x,y)&= \mbox{absorption cross-section}\\
\Sigma_{s,g\rightarrow g'}(x,y)&= \mbox{scattering cross-section from group g to group g’}\\
\Sigma_{tr,g}(x,y)&= \mbox{transport cross-sections, approximately equivalent to total cross-section}\\
&\mbox{(sum of absorption, out-scattering and self-scattering) corrected by transport effect}\\
\nu\Sigma_{f,g}(x,y)&= \mbox{fission cross-section multiplied by number of neutrons per fission}\\
\chi_{g}(x,y)&= \mbox{fission spectrum, fraction of all fission neutrons born in group g}\\
\end{align}

### Addition of chemical shim

Together let us consider the impact of a chemical shim, some absorber introduced to the coolant.

**Discussion: In the two group equations, which parameter will change?**

\begin{align}
- D_1\nabla^2\phi_1 + \Sigma_{R,1}\phi_1 - \Sigma_{s}^{2\rightarrow 1}\phi_{2} &= \frac{1}{k}\chi_1\left[(\nu\Sigma_f)_{1}\phi_{1} + (\nu\Sigma_f)_{2}\phi_{2}\right]\\
\end{align}

\begin{align}
- D_2\nabla^2\phi_2+ \Sigma_{R,2}\phi_2 - \Sigma_{s}^{1\rightarrow 2}\phi_{1} &= 0\\
\end{align}

In the thermal group, group 2, the removal cross section will change, because it involves absorption. The amount of chemical shim will impact the absorption cross section thus:

\begin{align}
\Sigma_{a',2} &= \Sigma_{a,2} + \Sigma_{shim,2} \\
\Sigma_{shim, 2} &= \Sigma_{a',2} - \Sigma_{a,2}\\
&= \rho_{shim}\frac{N_{avo}}{A_{shim}}\sigma_{a,shim,2}\\
\end{align}

## Transport Cross Section

I have said above that D is given approximately as :

\begin{align}
D &= \frac{1}{3\Sigma_{tr}}
\end{align}

where

\begin{align}
\Sigma_{tr} = \mbox{the macroscopic transport cross section}
\end{align}

This can also be stated:

\begin{align}
D &= \frac{\lambda_{tr}}{3}
\end{align}

where

\begin{align}
\lambda_{tr} = \mbox{the transport mean free path}
\end{align}

### Think Pair Share

Based on the above definitions, what are the units of D, the diffusion coefficent?

----

**In a purely scattering (no absorption) medium,** we can describe the transport mean free path thus:

\begin{align}
\lambda_{tr} &= \frac{1}{\Sigma_{tr}}\\
&= \frac{1}{\Sigma_s\left(1-\overline{\mu}\right)}
\end{align}

**In a medium with both absorption and scattering,** the transport mean free path is:

\begin{align}
\lambda_{tr} &= \frac{1}{\Sigma_{tr}}\\
&= \frac{1}{\left(\Sigma_t-\overline{\mu}\Sigma_s\right)}
\end{align}

----

In both cases, $\overline{\mu}$ is the average cosine of the neutron scattering angle in the medium.

Without repeating our discussion of elastic scattering angles, note that the cosine of the neutron deflection from a nucleus of mass A is isotropic in the COM system, but **forward scattering is preferred in the laboratory frame of reference.**

\begin{align}
<\cos{\theta}> &= \frac{1}{n}\int_0^\pi \frac{n}{2}\cos{\theta}\sin{\theta}d\theta = 0
\end{align}

We arrive at the **average cosine of the scattering angle** by integrating over all angles $\theta \in [0, \pi]$ and normalizing by degree of anisotropy in COM system (from above, we arrive at n=2).

\begin{align}
\overline{\mu} &= <\cos{\psi}> \\
&= \frac{1}{n}\int_0^\pi \frac{A\cos{\theta} + 1}{\sqrt{A^2 + 2A\cos{\theta} + 1}}\sin{\theta}d\theta\\
&= \frac{1}{2}\int_0^\pi \frac{A\cos{\theta} + 1}{\sqrt{A^2 + 2A\cos{\theta} + 1}}\sin{\theta}d\theta\\ &= \frac{2}{3A}
\end{align}

So, basically, **the transport mean free path ($\lambda_{tr}$) is the average distance a neutron will move in its original direction after infinite number of scattering collisions.**

### Putting it all together

\begin{align}
D &= \frac{1}{3\Sigma_{tr}}\\
\mbox{Purely scattering medium:   } &\\
D &= \frac{1}{3\Sigma_s\left(1-\frac{2}{3A}\right)}\\
\mbox{Scattering medium with absorption:   } &\\
D &= \frac{1}{3\left(\Sigma_t- \frac{2}{3A}\Sigma_s\right)}
\end{align}

## Wrap-up

- Calculate the dynamic impact of reactivity feedback on criticality.
- Recognize the impact of moderator material choice on reactor behavior.
- Analyze reactor behavior.
- Calculate the impact of geometry on criticality.
- Apply Fick's Law to neutron diffusion
- Develop multi-group diffusion equations
- Explore the two group diffusion equations
- Calculate the diffusion coefficient
- Define the transport cross section for non-absorbing and absorbing media
- Recognize the meaning of the transport mean free path

```python

```


```python
# HIDDEN
from datascience import *
from prob140 import *
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
%matplotlib inline
import math
from scipy import stats
from sympy import *
init_printing()
```

## Beta Densities with Integer Parameters ##

In the previous section we learned how to work with joint densities, but many of the joint density functions seemed to appear out of nowhere. For example, we checked that the function

$$
f(x, y) = 120x(y-x)(1-y), ~~~~ 0 < x < y < 1
$$

is a joint density, but there was no clue where it came from. In this section we will find its origin and go on to develop an important family of densities on the unit interval.

### Order Statistics of IID Uniform $(0, 1)$ Variables ###
Let $U_1, U_2, \ldots, U_n$ be i.i.d. uniform on $(0, 1)$. Imagine each $U_i$ as the position of a dart thrown at the unit interval. The graph below shows the positions of five such darts, each shown as a star.

```python
# NO CODE

plt.plot([0, 1], [0, 0], color='k', lw=2)
y = 1 - np.ones(5)
x = stats.uniform.rvs(size=5)
order_stats = np.sort(x)
plt.scatter(x, y, marker='*', color='r', s=100)
plt.text(0, -0.0007, r'0', size=16)
plt.text(0.98, -0.0007, r'1', size=16)
plt.xlim(0, 1)
plt.yticks([])
plt.xticks([])
plt.title('Five IID Uniform (0, 1) Variables');
```

Based on the graph above, can you tell which star corresponds to $U_1$? You can't, because $U_1$ could be any of the five stars. So also you can't identify any of the five variables $U_1, U_2, U_3, U_4, U_5$.

What you *can* see, however, is the list of $U_i$'s *sorted in increasing order*. You can see the value of the minimum, the second on the sorted list, the third, the fourth, and finally the fifth which is the maximum.

These are called the *order statistics* of $U_1, U_2, U_3, U_4, U_5$, and are denoted $U_{(1)}, U_{(2)}, U_{(3)}, U_{(4)}, U_{(5)}$.

Remember that because the $U_i$'s are independent random variables with densities, there can't be ties: the chance that two of them are equal is 0.

```python
# NO CODE

plt.plot([0, 1], [0, 0], color='k', lw=2)
order_stats = np.sort(x)
plt.scatter(x, y, marker='*', color='r', s=100)
u_labels = make_array('$U_{(1)}$', '$U_{(2)}$', '$U_{(3)}$', '$U_{(4)}$', '$U_{(5)}$')
for i in range(5):
    plt.text(order_stats[i], -0.0007, u_labels[i], size=16)
plt.text(0, -0.0007, r'0', size=16)
plt.text(0.98, -0.0007, r'1', size=16)
plt.xlim(0, 1)
plt.yticks([])
plt.xticks([])
plt.title('Order Statistics of the Five IID Uniform (0, 1) Variables');
```

In general for $1 \le k \le n$, the *$k$th order statistic* of $U_1, U_2, \ldots, U_n$ is the $k$th value when the $U_i$'s are sorted in increasing order. This can also be thought of as the $k$th *ranked* value when the minimum has rank 1. It is denoted $U_{(k)}$.

### Joint Density of Two Order Statistics ###
Let $n = 5$ as above and let's try to work out the joint density of $U_{(2)}$ and $U_{(4)}$. That's the joint density of the second and fourth values on the sorted list.

The graph below shows the event $\{U_{(2)} \in dx, U_{(4)} \in dy\}$ for values $x$ and $y$ such that $0 < x < y < 1$.

```python
# NO CODE

plt.plot([0, 1], [0, 0], color='k', lw=2)
y = 1 - np.ones(5)
x = make_array(0.1, 0.3, 0.45, 0.7, 0.9)
plt.scatter(x, y, marker='*', color='r', s=100)
plt.plot([0.28, 0.32], [0, 0], color='gold', lw=2)
plt.text(0.28, -0.0007, r'$dx$', size=16)
plt.plot([0.68, 0.72], [0, 0], color='gold', lw=2)
plt.text(0.68, -0.0007, r'$dy$', size=16)
plt.text(0, -0.0007, r'0', size=16)
plt.text(0.98, -0.0007, r'1', size=16)
plt.xlim(0, 1)
plt.yticks([])
plt.xticks([])
plt.title('$n = 5$; $\{ U_{(2)} \in dx, U_{(4)} \in dy \}$');
```

To find $P(U_{(2)} \in dx, U_{(4)} \in dy)$, notice that:

- One of $U_1, U_2, U_3, U_4, U_5$ must be in $dx$; there are 5 ways to choose this one.
- Of the remaining 4 variables, one of them must be in $dy$; there are 4 ways to choose this one.
- Of the remaining 3, one must be in $(0, x)$.
- Of the remaining 2, one must be in $(x, y)$.
- The last one must be in $(y, 1)$.

Thus

$$
\begin{align*}
P(U_{(2)} \in dx, U_{(4)} \in dy) ~ &\sim ~ 5(1dx) \cdot 4(1dy) \cdot 3x \cdot 2(y-x) \cdot 1(1-y) \\
&\sim ~ 120x(y-x)(1-y)dxdy
\end{align*}
$$

and therefore the joint density of $U_{(2)}$ and $U_{(4)}$ is given by

$$
f(x, y) = 120x(y-x)(1-y), ~~~ 0 < x < y < 1
$$

This solves the mystery of how the formula arises.

But it also does much more. The *marginal* densities of the order statistics of i.i.d. uniform $(0, 1)$ variables form a family that is important in data science.

### The Density of $U_{(k)}$ ###
Let $U_{(k)}$ be the $k$th order statistic of $U_1, U_2, \ldots, U_n$. We will find the density of $U_{(k)}$ by following the same general process that we followed to find the joint density above.

The graph below displays the event $\{ U_{(k)} \in dx \}$. For the event to occur,
- One of the variables $U_1, U_2, \ldots, U_n$ has to be in $dx$.
- Of the remaining $n-1$ variables, $k-1$ must have values in $(0, x)$ and the rest in $(x, 1)$.

```python
# NO CODE

plt.plot([0, 1], [0, 0], color='k', lw=2)
plt.scatter(0.4, 0, marker='*', color='r', s=100)
plt.plot([0.38, 0.42], [0, 0], color='gold', lw=2)
plt.text(0.38, -0.0007, r'$dx$', size=16)
plt.text(0.1, 0.001, '$k-1$ stars', size=16)
plt.text(0.1, 0.0005, 'in $(0, x)$', size=16)
plt.text(0.6, 0.001, '$n-k$ stars', size=16)
plt.text(0.6, 0.0005, 'in $(x, 1)$', size=16)
plt.text(0, -0.0007, r'0', size=16)
plt.text(0.98, -0.0007, r'1', size=16)
plt.xlim(0, 1)
plt.yticks([])
plt.xticks([])
plt.title('$\{ U_{(k)} \in dx \}$');
```

There are $n$ ways to choose the variable that lands in $dx$. Once that is chosen, $k-1$ of the remaining $n-1$ variables must have values in $(0, x)$. Thus

$$
P(U_{(k)} \in dx) \sim n \cdot 1 dx \cdot \binom{n-1}{k-1}x^{k-1}(1-x)^{n-k}
$$

by the binomial formula.

Therefore the density of $U_{(k)}$ is given by

$$
f_{U_{(k)}} (x) = \frac{n!}{(k-1)!(n-k)!} x^{k-1}(1-x)^{n-k}, ~~~ 0 < x < 1
$$

Let's rewrite the exponents slightly:

$$
f_{U_{(k)}} (x) = \frac{n!}{(k-1)!((n-k+1)-1)!} x^{k-1}(1-x)^{(n-k+1)-1}, ~~~ 0 < x < 1
$$

Because $1 \le k \le n$, we know that $n-k+1$ is a positive integer. Since $n$ is an arbitrary positive integer, so is $n-k+1$.

### Beta Densities ###
We have shown that if $r$ and $s$ are any two positive integers, then the function

$$
f(x) ~ = ~ \frac{(r+s-1)!}{(r-1)!(s-1)!} x^{r-1}(1-x)^{s-1}, ~~~ 0 < x < 1
$$

is a probability density function. This is called the *beta density with parameters $r$ and $s$*.

The order statistic $U_{(k)}$ has the beta density with parameters $k$ and $n-k+1$.

The shape of the density is determined by the two factors that involve $x$. All the factorials are just parts of the constant that make the density integrate to 1.

Notice that the uniform $(0, 1)$ density is the same as the beta density with parameters $r = 1$ and $s = 1$. The uniform $(0, 1)$ density is a member of the *beta family*.

The graph below shows some beta density curves. As you would expect, the beta $(3, 3)$ density is symmetric about 0.5.

```python
x = np.arange(0, 1.01, 0.01)
for i in np.arange(1, 7, 1):
    plt.plot(x, stats.beta.pdf(x, i, 6-i), lw=2)
plt.title('Beta $(i, 6-i)$ densities for $1 \leq i \leq 5$');
```

By choosing the parameters appropriately, you can create beta densities that put much of their mass near a prescribed value. That is one of the reasons beta densities are used to model *random proportions*. For example, if you think that the probability that an email is spam is most likely in the 60% to 90% range, but might be lower, you might model your belief by choosing the density that peaks at around 0.75 in the graph above.

The calculation below shows you how to get started on the process of picking parameters so that the beta density with those parameters has properties that reflect your beliefs.

### The Beta Integral ###
The beta density integrates to 1, and hence for all positive integers $r$ and $s$ we have

$$
\int_0^1 x^{r-1}(1-x)^{s-1}dx ~ = ~ \frac{(r-1)!(s-1)!}{(r+s-1)!}
$$

Thus probability theory makes short work of an otherwise laborious integral. Also, we can now find the expectation of a random variable with a beta density.

Let $X$ have the beta $(r, s)$ density for two positive integer parameters $r$ and $s$. Then

$$
\begin{align*}
E(X) &= \int_0^1 x \frac{(r+s-1)!}{(r-1)!(s-1)!} x^{r-1}(1-x)^{s-1}dx \\ \\
&= \frac{(r+s-1)!}{(r-1)!(s-1)!} \int_0^1 x^r(1-x)^{s-1}dx \\ \\
&= \frac{(r+s-1)!}{(r-1)!(s-1)!} \cdot \frac{r!(s-1)!}{(r+s)!} ~~~~~~~ \text{(beta integral for parameters } r+1 \text{and } s\text{)}\\ \\
&= \frac{r}{r+s}
\end{align*}
$$

You can follow the same method to find $E(X^2)$ and hence $Var(X)$.

The formula for the expectation allows you to pick parameters corresponding to your belief about the random proportion being modeled by $X$. For example, if you think the proportion is likely to be somewhere around 0.4, you might start by trying out a beta prior with $r = 2$ and $s = 3$.

You will have noticed that the form of the beta density looks rather like the binomial formula. Indeed, we used the binomial formula to derive the beta density. Later in the course you will see another close relation between the beta and the binomial. These properties make the beta family one of the most widely used families of densities in machine learning.

```python

```


# Resolution of the elastic problem of a sphere in an infinite matrix

```julia
using TensND, LinearAlgebra, SymPy, Tensors, OMEinsum, Rotations
sympy.init_printing(use_unicode=true)
```

## Definition of the coordinate system, base vectors...

```julia
Spherical = coorsys_spherical()
θ, ϕ, r = getcoords(Spherical) # Note the order of coordinates not `r, θ, ϕ` but `θ, ϕ, r` so that the frame `(𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ)` coincides with the canonical frame for null angles
𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ = unitvec(Spherical) # "\bfe<TAB>" to write bold `𝐞` and "\^\theta<TAB><TAB>" to write superscript `ᶿ`
𝐱 = getOM(Spherical) # Defines the current position vector in terms of spherical coordinates (ie `𝐱 = r 𝐞ʳ`)
𝐞₁, 𝐞₂, 𝐞₃ = unitvec(coorsys_cartesian()) ;
```

```julia
𝟏, 𝟙, 𝕀, 𝕁, 𝕂 = init_isotropic() # Defines usual isotropic Tensors
k, μ = symbols("k μ", positive = true)
λ = k -2μ/3 ;
```

## General resolution

### Hydrostatic loading

\begin{equation}
\mathbf{u}\underset{||\mathbf{x}||\to\infty}{\sim}\mathbf{E}\cdot\mathbf{x} \textrm{ with } \mathbf{E}=\frac{1}{3}E_v\mathbf{1}
\end{equation}

The displacement field is naturally searched in a form satisfying the isotropy of the loading ie
\begin{equation}
\mathbf{u}=u_r(r)\,\mathbf{e}_r
\end{equation}

```julia
u = SymFunction("u", real = true)
𝐮ˢᵖʰ = u(r) * 𝐞ʳ  # Note that the vector is in bold font ("\bfu<TAB>") and the component in normal font
𝛆ˢᵖʰ = simplify(SYMGRAD(𝐮ˢᵖʰ, Spherical)) # Strain tensor ("\bfepsilon<TAB>")
```

```julia
𝛔ˢᵖʰ = simplify(λ * tr(𝛆ˢᵖʰ) * 𝟏 + 2μ * 𝛆ˢᵖʰ) # Stress tensor ("\bfsigma<TAB>")
𝐓ˢᵖʰ = simplify(𝛔ˢᵖʰ ⋅ 𝐞ʳ) ;
```

```julia
div𝛔ˢᵖʰ = DIV(𝛔ˢᵖʰ, Spherical) ;
```

```julia
eqˢᵖʰ = factor(simplify(div𝛔ˢᵖʰ ⋅ 𝐞ʳ))
```

```julia
solˢᵖʰ = dsolve(eqˢᵖʰ, u(r)) ;
```

```julia
ûˢᵖʰ = solˢᵖʰ.rhs() ; display(ûˢᵖʰ)
T̂ˢᵖʰ = factor(simplify(subs(𝐓ˢᵖʰ ⋅ 𝐞ʳ, u(r) => ûˢᵖʰ))) ; display(T̂ˢᵖʰ)
```

### Deviatoric loading

\begin{equation}
\mathbf{u}\underset{||\mathbf{x}||\to\infty}{\sim}\mathbf{E}\cdot\mathbf{x} \textrm{ with } \mathbf{E}=E\,(\mathbf{e}_1\otimes\mathbf{e}_1+\mathbf{e}_2\,\otimes\mathbf{e}_2-2\mathbf{e}_3\,\otimes\mathbf{e}_3)=\mathbb{1}-3\mathbf{e}_3\otimes\mathbf{e}_3
\end{equation}

Note that such a macroscopic strain tensor induces a symmetry of revolution of the fields, which means in particular that the displacement field is expect of the form

\begin{equation}
\mathbf{u}=u_\theta(\theta,r)\,\mathbf{e}_\theta+u_r(\theta,r)\,\mathbf{e}_r
\end{equation}

```julia
𝐄 = 𝟏 - 3𝐞₃⊗𝐞₃
# Remote trends in θ of the displacement
fᶿ = simplify(𝐞ᶿ ⋅ 𝐄 ⋅ 𝐞ʳ)
fʳ = simplify(𝐞ʳ ⋅ 𝐄 ⋅ 𝐞ʳ) ;

```

```julia
uᶿ = SymFunction("uᶿ", real = true)
uʳ = SymFunction("uʳ", real = true)
𝐮ᵈᵉᵛ = uᶿ(r)* fᶿ * 𝐞ᶿ + uʳ(r)* fʳ * 𝐞ʳ
𝛆ᵈᵉᵛ = simplify(SYMGRAD(𝐮ᵈᵉᵛ, Spherical)) ;
```

```julia
𝛔ᵈᵉᵛ = simplify(λ * tr(𝛆ᵈᵉᵛ) * 𝟏 + 2μ * 𝛆ᵈᵉᵛ)
𝐓ᵈᵉᵛ = simplify(𝛔ᵈᵉᵛ ⋅ 𝐞ʳ) ;
```

```julia
div𝛔ᵈᵉᵛ = DIV(𝛔ᵈᵉᵛ, Spherical) ;
```

```julia
eqᶿᵈᵉᵛ = factor(simplify(div𝛔ᵈᵉᵛ ⋅ 𝐞ᶿ) / fᶿ) ;
```

```julia
eqʳᵈᵉᵛ = factor(simplify(div𝛔ᵈᵉᵛ ⋅ 𝐞ʳ) / fʳ) ;
```

```julia
α, Λ = symbols("α Λ", real = true)
eqᵈᵉᵛ = simplify.(subs.([eqᶿᵈᵉᵛ,eqʳᵈᵉᵛ], uᶿ(r) => r^α, uʳ(r) => Λ*r^α))
```

```julia
αΛ = solve(eqᵈᵉᵛ, [α, Λ])
```

```julia
ûᶿᵈᵉᵛ = sum([Sym("C$(i+2)") * r^αΛ[i][1] for i ∈ 1:length(αΛ)]) ; display(ûᶿᵈᵉᵛ)
ûʳᵈᵉᵛ = sum([Sym("C$(i+2)") * αΛ[i][2] * r^αΛ[i][1] for i ∈ 1:length(αΛ)]) ; display(ûʳᵈᵉᵛ)
```

```julia
T̂ᶿᵈᵉᵛ = factor(simplify(subs(𝐓ᵈᵉᵛ ⋅ 𝐞ᶿ / fᶿ, uᶿ(r) => ûᶿᵈᵉᵛ, uʳ(r) => ûʳᵈᵉᵛ))) ; display(T̂ᶿᵈᵉᵛ)
T̂ʳᵈᵉᵛ = factor(simplify(subs(𝐓ᵈᵉᵛ ⋅ 𝐞ʳ / fʳ, uᶿ(r) => ûᶿᵈᵉᵛ, uʳ(r) => ûʳᵈᵉᵛ))) ; display(T̂ʳᵈᵉᵛ)

```

```julia

```


+ This notebook is part of lecture 8 *Solving Ax=b, row reduced form* in the OCW MIT course 18.06 by Prof Gilbert Strang [1]
+ Created by me, Dr Juan H Klopper
    + Head of Acute Care Surgery
    + Groote Schuur Hospital
    + University Cape Town
    + Email me with your thoughts, comments, suggestions and corrections

Linear Algebra OCW MIT18.06 IPython notebook [2] study notes by Dr Juan H Klopper is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

+ [1] OCW MIT 18.06
+ [2] Fernando Pérez, Brian E. Granger, IPython: A System for Interactive Scientific Computing, Computing in Science and Engineering, vol. 9, no. 3, pp. 21-29, May/June 2007, doi:10.1109/MCSE.2007.53. URL: http://ipython.org

```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

```python
#import numpy as np
from sympy import init_printing, Matrix, symbols
#import matplotlib.pyplot as plt
#import seaborn as sns
#from IPython.display import Image
from warnings import filterwarnings

init_printing(use_latex = 'mathjax')
%matplotlib inline
filterwarnings('ignore')
```

# Solving A**x**=**b**

* We now want to solve the set of linear equations in the previous lecture (I_8) for any right-hand side **b**
$$ { x }_{ 1 }+2{ x }_{ 2 }+2{ x }_{ 3 }+2{ x }_{ 4 }={ b }_{ 1 }\\ 2{ x }_{ 1 }+4{ x }_{ 2 }+6{ x }_{ 3 }+8{ x }_{ 4 }={ b }_{ 2 }\\ 3{ x }_{ 1 }+6{ x }_{ 2 }+8{ x }_{ 3 }+10{ x }_{ 4 }={ b }_{ 3 }\\ \begin{bmatrix} 1 & 2 & 2 & 2 \\ 2 & 4 & 6 & 8 \\ 3 & 6 & 8 & 10 \end{bmatrix}\begin{bmatrix} { x }_{ 1 } \\ { x }_{ 2 } \\ { x }_{ 3 } \\ { x }_{ 4 } \end{bmatrix}=\begin{bmatrix} { b }_{ 1 } \\ { b }_{ 2 } \\ { b }_{ 3 } \end{bmatrix}\\ { x }_{ 1 }\begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}+{ x }_{ 2 }\begin{bmatrix} 2 \\ 4 \\ 6 \end{bmatrix}+{ x }_{ 3 }\begin{bmatrix} 2 \\ 6 \\ 8 \end{bmatrix}+{ x }_{ 4 }\begin{bmatrix} 2 \\ 8 \\ 10 \end{bmatrix}=\begin{bmatrix} { b }_{ 1 } \\ { b }_{ 2 } \\ { b }_{ 3 } \end{bmatrix} $$

* Since the third row is the addition of rows 1 and 2, the only solution in the right-hand side must be
$$ {b}_{3} = {b}_{1} + {b}_{2} $$

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

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

### So, for which right-hand sides is this set solvable?

* A**x**=**b** is solvable (exactly) when **b** is in the column space of A
    * **b** must be a linear combination of the columns

## Finding the complete solution to the set

### The particular solution: **x**particular

* To find **x**particular
    * Set all free variable to zero (in this example case that would be x2 = x4 = 0, since these are **columns without pivots**)
    * Solve A**x**=**b** for the pivot variables

* So, let's see that in action and calculate **x**particular for
$$ \overline { b } =\begin{bmatrix} 1 \\ 5 \\ 6 \end{bmatrix} $$

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

```python
A_augm.rref() # The video example is not solved to reduced row echelon form
```

* So we are left with a new matrix (called A1 below)

```python
A1 = Matrix([[1, 0, -2] ,[0, 1, (3 / 2)] ,[0, 0, 0]]) # The video example is not solved to reduced row echelon form
A1
```

* Thus we have for xparticular we have:
$$ { \overline { x }  }_{ P }=\begin{bmatrix} -2 \\ 0 \\ \frac { 3 }{ 2 }  \\ 0 \end{bmatrix} $$

### The nullspace solution: **x**nullspace

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

### The full set of solutions

* **x** = **x**particular + **x**nullspace
* This is so because:
$$ A{ \overline { x }  }_{ P }=b\\ A{ \overline { x }  }_{ N }=0\\ \therefore \quad A\left( { \overline { x }  }_{ P }+{ \overline { x }  }_{ N } \right) =b $$

* With the final solution:
$$ \overline { x } ={ \overline { x }  }_{ P }+{ \overline { x }  }_{ N }=\begin{bmatrix} -2 \\ 0 \\ \frac { 3 }{ 2 }  \\ 0 \end{bmatrix}+{ c }_{ 1 }\begin{bmatrix} -2 \\ 1 \\ 0 \\ 0 \end{bmatrix}+{ c }_{ 2 }\begin{bmatrix} -2 \\ 0 \\ -2 \\ 0 \end{bmatrix} $$

## Rank

* For any *m* × *n* matrix we have a rank (number of pivots)
* We cannot have more pivots than rows, ∴ *r* ≤ *m* and *r* ≤ *n*

### The case of full column rank, i.e. *r* = *n*

* This implies that ther are no free variables and the nullspace only has the zero vector (it is a subspace and MUST contain the zero vector)
* Thus **x** = **x**particular ONLY (if it exists)

* Consider this example with 2 columns
* One column is not a linear combination of the other and the rank will be two
* With only 2 unknowns and a rank of two, the nullspace wil only contain the zero vector

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

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

```python
A.rref() # Just to show the reduced row echelon form
```

### The case of full row rank, i.e. *r* = *m*

* Every row has a pivot
* For which **b** will the the set be solvable
    * For ALL **b**
* How many free variable?
    * We are left with *n* - *r* (or *n* - *m*) free variables

```python
A2 = A.transpose() # Creating a new matrix, which is the transpose of A above (just as an example)
A2
```

```python
A2.nullspace() # Showing the two nullspace solutions
```

```python
A2.rref()
```

### The case of full (row and column) rank, i.e. *r* = *m* = *n*

* Let's do an example

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

```python
A.nullspace() # Only the zero vector
```

```python
A.rref()
```

* This is by definition an invertible matrix

* Let's look at an non-invertible matrix
* For this 2 × 2 matrix we will have one free variable (no pivot)

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

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

```python
A.rref()
```

```python

```


```python
%load_ext autoreload
%autoreload 2
import warnings
warnings.simplefilter('ignore')
%matplotlib inline
```

```python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics.pairwise import rbf_kernel
from mliv.dgps import get_data, get_tau_fn, fn_dict
from mliv.rkhs import RKHSIV, RKHSIVCV, ApproxRKHSIV, ApproxRKHSIVCV
```

```python
def plot_est_vs_true(est, ind, T_test, T_train, true_fn, fn_name=None):
    plt.plot(T_test[:, ind], est.predict(T_test), label='est')
    plt.plot(T_test[:, ind], true_fn(T_test), '--', label='true')
    RMSE = np.sqrt(np.mean((est.predict(T_train).flatten() - true_fn(T_train).flatten())**2))
    R2 = 1 - RMSE**2 / np.var(true_fn(T_train).flatten())
    plt.title("RMSE on Train: {:.3f}, "
              "R2 on train: {:.2f}".format(RMSE, R2))
    plt.legend()
    if fn_name is not None:
        plt.savefig('{}_rkhs.png'.format(fn_name))
    plt.show()
```

# Data Generation

```python
n = 1000
n_z = 3
n_t = 3
iv_strength = .6
fname = 'abs'
dgp_num = 5
Z, T, Y, true_fn = get_data(n, n_z, iv_strength, get_tau_fn(fn_dict[fname]), dgp_num)
```

```python
ind = 0
x_grid = np.linspace(np.quantile(T[:, ind], .01), np.quantile(T[:, ind], .99), 100)
T_test = np.zeros((100, T.shape[1])) + np.median(T, axis=0, keepdims=True)
T_test[:, ind] = x_grid
```

```python
plt.figure(figsize=(10,3))
plt.subplot(1, 2, 1)
plt.scatter(Z[:, 0], Y)
plt.subplot(1, 2, 2)
plt.scatter(T[:, 0], Y)
plt.plot(T[np.argsort(T[:, 0]), 0], true_fn(T[np.argsort(T[:, 0])]))
plt.show()
```

# Visualization of Strength of Instrument Based on Kernel

```python
Kf = rbf_kernel(Z, gamma=2)
plt.imshow(Kf)
plt.show()
```

# Vanilla Benchmarks: OLS and 2SLS

```python
LinearRegression().fit(T, Y).coef_
```

```python
LinearRegression().fit(LinearRegression().fit(Z, T).predict(Z), Y).coef_
```

# RKHSIV

```python
kernel = 'rbf'
delta_scale = 'auto'
delta_exp = .4
gamma = .1
alpha_scale = 'auto'
alpha_scales = np.geomspace(1, 10000, 10)
cv = 5
```

```python
est = RKHSIV(kernel=kernel, gamma=gamma, delta_scale=delta_scale,
             delta_exp=delta_exp, alpha_scale=alpha_scale).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

### RKHSIV with CV Estimated Hyperparams

```python
est = RKHSIVCV(kernel=kernel, gamma=gamma, delta_scale=delta_scale,
               delta_exp=delta_exp, alpha_scales=alpha_scales, cv=cv).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

```python
plt.title("Best alpha: {:.3f}".format(est.alpha_scales[np.argmin(est.avg_scores)]))
plt.scatter(est.alpha_scales, est.avg_scores)
plt.show()
```

### Oracle Hyperparameter Tuning

```python
scores = []
for t in alpha_scales:
    est = RKHSIV(kernel, gamma=gamma, delta_scale=delta_scale,
                 delta_exp=delta_exp, alpha_scale=t).fit(Z, T, Y)
    scores.append(np.sqrt(np.mean((est.predict(T) - true_fn(T))**2)))
```

```python
plt.title("Best alpha: {:.3f}".format(alpha_scales[np.argmin(scores)]))
plt.scatter(alpha_scales, scores)
plt.show()
```

```python
est = RKHSIV(kernel=kernel, gamma=gamma, delta_scale=delta_scale,
             delta_exp=delta_exp, alpha_scale=alpha_scales[np.argmin(scores)]).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

# Nystrom Approx

If we assume that $K_z = U U'$ and $K_x= V V'$, then we first we observe that we can express the solution to the inner maximization problem as:
\begin{align}
\psi_n' U \left(\frac{1}{2n\delta^2}U'U + \frac{1}{2H}\right)^{-1} U'\psi_n
\end{align}
Let:
\begin{align}
Q = \left(\frac{1}{2n\delta^2}U'U + \frac{1}{2H} I\right)^{-1}
\end{align}
can show that we can express the solution in terms of $\gamma=V'a$ and such that $\gamma$ must solve:
\begin{align}
VV'\left(\left(U Q U' V + \lambda I) \gamma - UQU'y\right)\right) = 0
\end{align}
Or equivalently:
\begin{align}
V\left(\left(V'U Q U' V + \lambda I) \gamma - V'UQU'y\right)\right) = 0
\end{align}
Letting $A=V'U$, then we have that this is equivalent to:
\begin{align}
\gamma = (A Q A' + \lambda I)^{-1} A Q U'y
\end{align}
Then we can also solve for $a$ via: $V'a = \gamma \implies a = V^+\gamma$.

However, typically we can express $h(x)$ as:
\begin{align}
h(x) = v_x' V'a = v_x' \gamma
\end{align}
where $v_x$ is a vector that corresponds to the feature map of a nystrom approximation for a target $x$, i.e.
\begin{align}
(K(x_1, x), \ldots, K(x_n, x)) =  V \phi(x)
\end{align}
such that $h(x) = \phi(x)' V' a = \phi(x)' \gamma$. All these calculations require time of the order of $n_\text{samples} \times n_\text{nystrom components}^2$

```python
kernel_approx = 'nystrom'
n_components = 500
```

```python
est = ApproxRKHSIV(kernel_approx=kernel_approx, n_components=n_components,
                    kernel=kernel, gamma=gamma, delta_scale=delta_scale,
                    delta_exp=delta_exp, alpha_scale=alpha_scale).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

### Nystrom with CV Hyperparameter Tuning

```python
est = ApproxRKHSIVCV(kernel_approx=kernel_approx, n_components=n_components,
                      kernel=kernel, gamma=gamma, delta_scale=delta_scale,
                      delta_exp=delta_exp, alpha_scales=alpha_scales, cv=cv).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

```python
plt.title("Best alpha scale: {:.3f}".format(est.alpha_scales[np.argmin(est.avg_scores)]))
plt.scatter(est.alpha_scales, est.avg_scores)
plt.show()
```

```python
est.best_alpha
```

### Random Fourier Features Approx

Same as Nystrom, but instead of choosing random points it chooses random fourier features to approximate the kernel

```python
kernel_approx = 'rbfsampler'
n_components = 200
```

```python
est = ApproxRKHSIV(kernel_approx=kernel_approx, n_components=n_components,
                    kernel=kernel, gamma=gamma, delta_scale=delta_scale,
                    delta_exp=delta_exp, alpha_scale=alpha_scale).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

```python
est = ApproxRKHSIVCV(kernel_approx=kernel_approx, n_components=n_components,
                      kernel=kernel, gamma=gamma, delta_scale=delta_scale,
                      delta_exp=delta_exp, alpha_scales=alpha_scales, cv=cv).fit(Z, T, Y)

plot_est_vs_true(est, 0, T_test, T, true_fn)
```

```python

```


# Homework #3 Programming Assignment
CSCI567, Spring 2019
Victor Adamchik
**Due: 11:59 pm, March 3rd 2019**

### Before you start:
On Vocareum, when you submit your homework, it takes around 5-6 minutes to run the grading scripts and evaluate your code. So, please be patient regarding the same.

## Office Hour for Project Assignment 3
Office hours for Anirudh:

February 15th, 2pm - 3pm

February 22nd, 2pm - 3pm

March 1st, 2pm - 4pm

Office hours for Piyush:

February 13th, 2pm - 3pm

February 21nd, 2pm - 3pm

March 4th, 2pm - 4pm

Also, you can post your question on Piazza under pa-3 folder. We will try our best to answer all questions as soon as possible. Please make sure you read previous posts before creating a new post in case your question has been answered before. However, if you have any urgent issue, please feel free to send an email to both of us.

Anirudh, Kashi: kashia@usc.edu

Piyush, Umate: pumate@usc.edu

## Problem 1 Neural Networks (40 points)

For this Assignment, you are asked to implement neural networks. We will be using this neural network to classify MNIST database of handwritten digits (0-9). The architecture of the multi-layer perceptron (MLP, just another term for fully connected feedforward networks we discussed in the lecture) you will be implementing is shown in figure 1. Following MLP is designed for a K-class classification problem.

Let $(x\in\mathbb{R}^D, y\in\{1,2,\cdots,K\})$ be a labeled instance, such an MLP performs the following computations.

$$
\begin{align}
 \textbf{input features}: \hspace{15pt} & x \in \mathbb{R}^D \\
 \textbf{linear}^{(1)}: \hspace{15pt} & u = W^{(1)}x + b^{(1)} \hspace{2em}, W^{(1)} \in \mathbb{R}^{M\times D} \text{ and } b^{(1)} \in \mathbb{R}^{M}  \label{linear_forward}\\
 \textbf{tanh}:\hspace{15pt} & h =\cfrac{2}{1+e^{-2u}}-1 \label{tanh_forward}\\
 \textbf{relu}: \hspace{15pt} & h = max\{0, u\} =
\begin{bmatrix}
\max\{0, u_1\}\\
\vdots \\
\max\{0, u_M\}\\
\end{bmatrix} \label{relu_forward}\\
 \textbf{linear}^{(2)}: \hspace{15pt} & a = W^{(2)}h + b^{(2)} \hspace{2em}, W^{(2)} \in \mathbb{R}^{K\times M} \text{ and } b^{(2)} \in \mathbb{R}^{K} \label{linear2_forward}\\
 \textbf{softmax}: \hspace{15pt} & z = \begin{bmatrix}
\cfrac{e^{a_1}}{\sum_{k} e^{a_{k}}}\\
\vdots \\
\cfrac{e^{a_K}}{\sum_{k} e^{a_{k}}} \\
\end{bmatrix}\\
 \textbf{predicted label}: \hspace{15pt} & \hat{y} = argmax_k z_k.
%& l = -\sum_{k} y_{k}\log{\hat{y_{k}}} \hspace{2em}, \vy \in \mathbb{R}^{k} \text{ and } y_k=1 \text{ if } \vx \text{ belongs to the } k' \text{-th class}.
\end{align}
$$

For a $K$-class classification problem, one popular loss function for training (i.e., to learn $W^{(1)}$, $W^{(2)}$, $b^{(1)}$, $b^{(2)}$) is the cross-entropy loss.
Specifically we denote the cross-entropy loss with respect to the training example $(x, y)$ by $l$:

$$
\begin{align}
  l = -\log (z_y) = \log \left( 1 + \sum_{k\neq y} e^{a_k - a_y} \right)
\end{align}
$$

Note that one should look at $l$ as a function of the parameters of the network, that is, $W^{(1)}, b^{(1)}, W^{(2)}$ and $b^{(2)}$.
For ease of notation, let us define the one-hot (i.e., 1-of-$K$) encoding of a class $y$ as

\begin{align}
y \in \mathbb{R}^K \text{ and }
y_k =
\begin{cases}
1, \text{ if }y = k,\\
0, \text{ otherwise}.
\end{cases}
\end{align}
so that
\begin{align}
l = -\sum_{k} y_{k}\log{z_k} =
-y^T
\begin{bmatrix}
\log z_1\\
\vdots \\
\log z_K\\
\end{bmatrix}
= -y^T\log{z}.
\end{align}

We can then perform error-backpropagation, a way to compute partial derivatives (or gradients) w.r.t the parameters of a neural network, and use gradient-based optimization to learn the parameters.

Submission: All you need to submit is neural_networks.py

### Q1.1 Mini batch Gradient Descent
First, You need to implement mini-batch gradient descent which is a gradient-based optimization to learn the parameters of the neural network.

$$
\begin{align}
\upsilon = \alpha \upsilon - \eta \delta_t\\
w_t = w_{t-1} + \upsilon
\end{align}
$$

You can use the formula above to update the weights using momentum.

Here,
$\alpha$ is the discount factor such that $\alpha \in (0, 1)$

$\upsilon$ is the velocity update

$\eta$ is the learning rate

$\delta_t$ is the gradient

You need to handle with as well without momentum scenario in the ```miniBatchGradientDescent``` function.

* ```TODO 1```
You need to complete ```def miniBatchGradientDescent(model, momentum, _lambda, _alpha, _learning_rate)``` in ```neural_networks.py```

### Q1.2 Linear Layer (10 points)
Second, You need to implement the linear layer of MLP. In this part, you need to implement 3 python functions in ```class linear_layer```. In ```def __init__(self, input_D, output_D)``` function, you need to initialize W with random values using np.random.normal such that the mean is 0 and standard deviation is 0.1. You also need to initialize gradients to zeroes in the same function.

$$
\begin{align}
\text{forward pass:}\hspace{2em} &
u = \text{linear}^{(1)}\text{.forward}(x) = W^{(1)}x + b^{(1)},\\
&\text{where } W^{(1)} \text{ and } b^{(1)} \text{ are its parameters.}\nonumber\\
\nonumber\\
\text{backward pass:}\hspace{2em} &[\frac{\partial l}{\partial x}, \frac{\partial l}{\partial W^{(1)}}, \frac{\partial l}{\partial b^{(1)}}] = \text{linear}^{(1)}\text{.backward}(x, \frac{\partial l}{\partial u}).
\end{align}
$$

You can use the above formula as a reference to implement the ```def forward(self, X)``` forward pass and ```def backward(self, X, grad)``` backward pass in class linear_layer. In backward pass, you only need to return the backward_output. You also need to compute gradients of W and b in backward pass.

* ```TODO 2```
You need to complete ```def __init__(self, input_D, output_D)``` in ```class linear_layer``` of ```neural_networks.py```
* ```TODO 3```
You need to complete ```def forward(self, X)``` in ```class linear_layer``` of  ```neural_networks.py```
* ```TODO 4```
You need to complete ```def backward(self, X, grad)``` in ```class linear_layer``` of  ```neural_networks.py```

### Q1.3 Activation function - tanh (10 points)
Now, you need to implement the activation function tanh. In this part, you need to implement 2 python functions in ```class tanh```. In ```def forward(self, X)```, you need to implement the forward pass and you need to compute the derivative and accordingly implement ```def backward(self, X, grad)```, i.e. the backward pass.
$$
\begin{align}
\textbf{tanh}:\hspace{15pt} & h =\cfrac{2}{1+e^{-2u}}-1\\
\end{align}
$$
You can use the above formula for tanh as a reference.
* ```TODO 5```
You need to complete ```def forward(self, X)``` in ```class tanh``` of  ```neural_networks.py```
* ```TODO 6```
You need to complete ```def backward(self, X, grad)``` in ```class tanh``` of  ```neural_networks.py```

### Q1.4 Activation function - relu (10 points)
You need to implement another activation function called relu. In this part, you need to implement 2 python functions in ```class relu```. In ```def forward(self, X)```, you need to implement the forward pass and you need to compute the derivative and accordingly implement ```def backward(self, X, grad)```, i.e. the backward pass.
$$
\begin{align}
\textbf{relu}: \hspace{15pt} & h = max\{0, u\} =
\begin{bmatrix}
\max\{0, u_1\}\\
\vdots \\
\max\{0, u_M\}\\
\end{bmatrix}
\end{align}
$$
You can use the above formula for relu as a reference.
* ```TODO 7```
You need to complete ```def forward(self, X)``` in ```class relu``` of  ```neural_networks.py```
* ```TODO 8```
You need to complete ```def backward(self, X, grad)``` in ```class relu``` of  ```neural_networks.py```

### Q1.5 Dropout (10 points)
To prevent overfitting, we usually add regularization. Dropout is another way of handling overfitting. In this part, you will initially read and understand ```def forward(self, X, is_train)``` i.e. the forward pass of ```class dropout``` and derive partial derivatives accordingly to implement ```def backward(self, X, grad)``` i.e. the backward pass of ```class dropout```. We define the forward and the backward passes as follows.

\begin{align}
\text{forward pass:}\hspace{2em} &
{s} = \text{dropout}\text{.forward}({q}\in\mathbb{R}^J) = \frac{1}{1-r}\times
\begin{bmatrix}
\textbf{1}[p_1 >= r] \times q_1\\
\vdots \\
\textbf{1}[p_J >= r] \times q_J\\
\end{bmatrix},
\\
\nonumber\\
&\text{where } p_j \text{ is sampled uniformly from }[0, 1), \forall j\in\{1,\cdots,J\}, \nonumber\\
&\text{and } r\in [0, 1) \text{ is a pre-defined scalar named dropout rate}.
\end{align}
\begin{align}
\text{backward pass:}\hspace{2em} &\frac{\partial l}{\partial {q}} = \text{dropout}\text{.backward}({q}, \frac{\partial l}{\partial {s}})=
\frac{1}{1-r}\times
\begin{bmatrix}
\textbf{1}[p_1 >= r] \times \cfrac{\partial l}{\partial s_1}\\
\vdots \\
\textbf{1}[p_J >= r] \times \cfrac{\partial l}{\partial s_J}\\
\end{bmatrix}.
\end{align}

Note that $p_j, j\in\{1,\cdots,J\}$ and $r$ are not be learned so we do not need to compute the derivatives w.r.t. to them. Moreover, $p_j, j\in\{1,\cdots,J\}$ are re-sampled every forward pass, and are kept for the following backward pass. The dropout rate $r$ is set to 0 during testing.

* ```TODO 9```
You need to complete ```def backward(self, X, grad)``` in ```class dropout``` of  ```neural_networks.py```

### Q1.6 Connecting the dots
In this part, you will combine the modules written from question Q1.1 to Q1.5 by implementing TODO snippets in the ```def main(main_params, optimization_type="minibatch_sgd")``` i.e. main function. After implementing forward and backward passes of MLP layers in Q1.1 to Q1.5,now in the main function you will call the forward methods and backward methods of every layer in the model in an appropriate order based on the architecture.

* ```TODO 10```
You need to complete ```main(main_params, optimization_type="minibatch_sgd")``` in ```neural_networks.py```

### Grading
Your code will be graded on Vocareum with autograding script. For your reference, the solution code takes around 5 minutes to execute. As long as your code can finish grading on Vocareum, you should be good. When you finish all ```TODO``` parts, please click submit button on Vocareum. Sometimes you may need to come back to check grading report later.

Your code will be tested on the correctness of modules you have implemented as well as certain custom testcases. 40 points are assigned for Question 1 while 60 points are assigned to custom testcases.

```python

```