```python
# Header starts here.
from sympy.physics.units import *
from sympy import *

# Rounding:
import decimal
from decimal import Decimal as DX
from copy import deepcopy
def iso_round(obj, pv, rounding=decimal.ROUND_HALF_EVEN):
    import sympy
    """
    Rounding acc. to DIN EN ISO 80000-1:2013-08
    place value = Rundestellenwert
    """
    assert pv in set([
        # place value   #  round to:
        1,              #  1
        0.1,            #  1st digit after decimal
        0.01,           #  2nd
        0.001,          #  3rd
        0.0001,         #  4th
        0.00001,        #  5th
        0.000001,       #  6th
        0.0000001,      #  7th
        0.00000001,     #  8th
        0.000000001,    #  9th
        0.0000000001,   # 10th
        ])
    objc = deepcopy(obj)
    try:
        tmp = DX(str(float(objc)))
        objc = tmp.quantize(DX(str(pv)), rounding=rounding)
    except:
        for i in range(len(objc)):
            tmp = DX(str(float(objc[i])))
            objc[i] = tmp.quantize(DX(str(pv)), rounding=rounding)
    return objc

# LateX:
kwargs = {}
kwargs["mat_str"] = "bmatrix"
kwargs["mat_delim"] = ""
# kwargs["symbol_names"] = {FB: "F^{\mathsf B}", }

# Units:
(k, M, G ) = ( 10**3, 10**6, 10**9 )
(mm, cm) = ( m/1000, m/100 )
Newton = kg*m/s**2
Pa     = Newton/m**2
MPa    = M*Pa
GPa    = G*Pa
kN     = k*Newton
deg    = pi/180

half = S(1)/2

# Header ends here.
#
# https://colab.research.google.com/github/kassbohm/wb-snippets/blob/master/ipynb/HTM_03/Uebungen/3.3_cc.ipynb

from mpmath import radians, degrees, pi

l1, l2 = 3*m/10, 6*m/10
p1 = radians(30)
prec = 0.001

s, p2 = var("s, φ₂")

c1, s1 = cos(p1), sin(p1)
c2, s2 = cos(p2), sin(p2)

xdev = ( l1*c1 - l2*c2 - s ) / m
ydev = ( l1*s1 - l2*s2 ) / m
xdev = xdev.simplify()
ydev = ydev.simplify()

xi = var("xi")
xdev = xdev.subs(s/m, xi)

J11, J12 = diff(xdev, p2), diff(xdev, xi)
J21, J22 = diff(ydev, p2), diff(ydev, xi)

# Jacobian:
J = Matrix([[J11, J12],[J21, J22]])
# pprint(J)

pprint("\n--- Numerical Solution using SymPy's nsolve ---")

# Initial Values:
pprint("\nInitial (φ₂ / deg, ξ):")
p2i, xii = radians(150), 0.8

sol = nsolve( [xdev, ydev], [p2, xi], [p2i, xii], dict=True )
sol = sol[0]
sympy_sol = sol
tmp =  Matrix([degrees(sol[p2]), sol[xi]])
pprint("\nSolution:")
pprint("(φ₂ / deg, ξ):")
pprint(iso_round(tmp,prec))

pprint("\n--- Step-by-Step Newton-Solution ---")

pprint("\n--- Step-1 ---")

pprint("\nJacobian in terms of (φ₂, ξ):")
pprint(J)

pprint("\n1: Inital (φ₂, ξ):")
p = Matrix([p2i, xii])
pprint(iso_round(p, prec))
pprint("Note: φ₂ / deg:")
tmp = degrees(p[0])
pprint(iso_round(tmp, prec))

F = Matrix([xdev, ydev])

sub_list = [
    (p2, p2i),
    (xi, xii),
    ]

pprint("\n2: Jacobian:")
Jn = J.subs(sub_list)
# pprint(Jn)
pprint(iso_round(Jn, prec))

pprint("\n3: Deviation Phi:")
Fn = F.subs(sub_list)
pprint(iso_round(Fn, prec))

# Newton-Step:
pprint("\n4: Increment (δφ₂, ξ):")
dp2, dxi = var("δφ₂, δξ")
dp = Matrix([dp2, dxi])
eq = Eq(Jn*dp, -Fn)
sol = solve(eq,[dp2, dxi], dict=True)
sol = sol[0]
dp = Matrix([sol[dp2], sol[dxi]])
pprint(iso_round(dp, prec))
p += dp
pprint("\n5: At end of iteration step:")
pprint("(φ₂, ξ):")
pprint(iso_round(p, prec))
pprint("Note: φ₂ / deg:")
tmp = degrees(p[0])
pprint(iso_round(tmp, prec))

# ---

pprint("\n--- Step-2 ---")
pprint("\n1: Using (φ₂, ξ) from end of last step.")

sub_list = [
    (p2, p[0]),
    (xi, p[1]),
    ]

pprint("\n2: Jacobian:")
Jn = J.subs(sub_list)
pprint(iso_round(Jn, prec))

pprint("\n3: Deviation Phi:")
Fn = F.subs(sub_list)
pprint(iso_round(Fn, prec))

# Newton-Step:
pprint("\n4: Increment (δφ₂, ξ):")
dp2, dxi = var("δφ₂, δξ")
dp = Matrix([dp2, dxi])
eq = Eq(Jn*dp, -Fn)
sol = solve(eq,[dp2, dxi], dict=True)
sol = sol[0]
dp = Matrix([sol[dp2], sol[dxi]])
pprint(iso_round(dp, prec))
p += dp
pprint("\n5: At end of iteration step:")
pprint("(φ₂, ξ):")
pprint(iso_round(p, prec))
pprint("Note: φ₂ / deg:")
tmp = degrees(p[0])
pprint(iso_round(tmp, prec))

pprint("\nPart d):")
sub_list = [
    (p2, sympy_sol[p2]),
    (xi, sympy_sol[xi]),
    ]
pprint("\nJ for solution of part c):")
J = J.subs(sub_list)
pprint(J)

# Unknowns:
p2p, xip = var("φ₂', ξ'")
unk = Matrix([p2p, xip])
rhs = l1/m * 1 / s * Matrix([s1, -c1])

# Linear System:
eq = Eq(J*unk, rhs)
sol = solve(eq,[p2p, xip], dict=True)
pprint("\nSolution:")
pprint(sol[0])

# --- Numerical Solution using SymPy's nsolve ---
#
# Initial (φ₂ / deg, ξ):
#
# Solution:
# (φ₂ / deg, ξ):
# ⎡165.522⎤
# ⎢       ⎥
# ⎣ 0.841 ⎦
#
# --- Step-by-Step Newton-Solution ---
#
# --- Step-1 ---
#
# Jacobian in terms of (φ₂, ξ):
# ⎡ 3⋅sin(φ₂)      ⎤
# ⎢ ─────────    -1⎥
# ⎢     5          ⎥
# ⎢                ⎥
# ⎣-0.6⋅cos(φ₂)  0 ⎦
#
# 1: Inital (φ₂, ξ):
# ⎡2.618⎤
# ⎢     ⎥
# ⎣ 0.8 ⎦
# Note: φ₂ / deg:
# 150.000
#
# 2: Jacobian:
# ⎡0.3   -1.0⎤
# ⎢          ⎥
# ⎣0.52  0.0 ⎦
#
# 3: Deviation Phi:
# ⎡-0.021⎤
# ⎢      ⎥
# ⎣-0.15 ⎦
#
# 4: Increment (δφ₂, ξ):
# ⎡0.289⎤
# ⎢     ⎥
# ⎣0.066⎦
#
# 5: At end of iteration step:
# (φ₂, ξ):
# ⎡2.907⎤
# ⎢     ⎥
# ⎣0.866⎦
# Note: φ₂ / deg:
# 166.540
#
# --- Step-2 ---
#
# 1: Using (φ₂, ξ) from end of last step.
#
# 2: Jacobian:
# ⎡0.14   -1.0⎤
# ⎢           ⎥
# ⎣0.584  0.0 ⎦
#
# 3: Deviation Phi:
# ⎡-0.023⎤
# ⎢      ⎥
# ⎣ 0.01 ⎦
#
# 4: Increment (δφ₂, ξ):
# ⎡-0.018⎤
# ⎢      ⎥
# ⎣-0.025⎦
#
# 5: At end of iteration step:
# (φ₂, ξ):
# ⎡2.889⎤
# ⎢     ⎥
# ⎣0.841⎦
# Note: φ₂ / deg:
# 165.525
#
# Part d):
#
# J for solution of part c):
#
# Solution:
# ⎧    -0.217082039324994        -0.447213595499958 ⎫
# ⎨ξ': ───────────────────, φ₂': ───────────────────⎬
# ⎩             s                         s         ⎭

```


```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import pandas as pd
import numpy.random as rnd
from scipy.integrate import odeint,quad
from scipy.stats import kde,beta
import seaborn as sns
%matplotlib inline
from importlib import reload
pi=np.pi

from numpy import linalg as LA
from scipy.linalg import expm
from scipy.optimize import brentq

```

```python
import stablecompoper
sns.set()
plt.rcParams.update({
  "text.usetex": True
})

plt.rc('text', usetex=False)
```

## The monotype linear birth and death process in a periodic environment

This process $(X(t),t\ge 0)$ with values in $\mathbb{N}$ is described by its
time varying generator
\begin{equation}
  L_t f(x) = x\left[\lambda(t)(f(x+1)-f(x)) + \mu(t) (f(x-1)-f(x))\right]\,,
\end{equation}
where $\lambda,\mu$ are non negative $T$-periodic functions.

Let $Z(t)$ be the  point measure on $S$ describing the set of states
(i.e. phases) of the individuals born before $t$ and still alive at
time $t$ : if $Z(t) = \sum_i \delta_{s_i}$ then $<Z(t), f>=
\sum_i f(s_i)$. We have the convergence in $L^1$, when we start with $X(s)=1$ one individual of phase $s$,
\begin{equation}
  \lim_{n\to +\infty} e^{-\alpha(nT-s)} <Z(nT),f> = h(s) \int_S f(t)\, d\pi(t)\,,
\end{equation}
where the reproductive value of phase $s$ is the periodic function for $T=1$
\begin{equation}
  h(s) = e^{\alpha s -\varphi(s)}\,,
\end{equation}
and the measure $\pi$ is the stable composition law
\begin{equation}
  \boxed{\pi(dt) = \frac1{e^{A(T)} -1} \lambda(t) e^{A(t)}\, 1_{t\in(0,T)}\, dt\,.}
\end{equation}

The process is one dimensional, and the death rate is constant
$\mu(t)=\mu_0$ and the birth rate is
\begin{equation}
  \lambda(t) = \lambda_0 (1 + c \cos(2\pi t/T))\,.
\end{equation}
The stable composition law is thus
\begin{equation}
\pi(dt) = \frac1{e^{A(T)} -1} \lambda(t) e^{A(t)}\, 1_{t\in(0,T)}\,
dt\,,
\end{equation}
with
\begin{equation}
  A(t) = \lambda_0 (t + \frac{ c T}{2 \pi} \sin(2\pi t/T) )
\end{equation}
We perform a simulation of the linear birth and death process for $N$
periods, and we keep the phase, the birth dates modulo $T$, of the
living individuals at time $N T$. We wait until the first non extinct
population, and then we plot its histogram against the true density
$\pi$ and against the birth rate $\lambda(t)$

#### Remarque
Il ne faut pas partir avec un nombre de périodes $N$ trop grand, ni une période $T$ trop grande, sinon la taille de l'échantillon est beaucoup trop grande. Pour avoir une bonne estimation de la densité par l'histogramme, il suffit d'avoir une taile d'échantillon au dessus de 2000.En considerant le cas constant, on voit qu'il faut à peu près prendre $e^{N T (\lambda_0 -\mu_0)}\simeq 2000$ ce qui donne $NT \simeq 7.6/(\lambda_0 -\mu_0) \simeq 12$

Pour obtenir le cas où les taux de naissance et de mort sont constants,
i l suffit de prendre c=0.

```python
from ipywidgets import GridspecLayout,Layout,Button, AppLayout,TwoByTwoLayout,interactive_output
import ipywidgets as widgets
def create_expanded_button(description, button_style):
    return Button(description=description, button_style=button_style, layout=Layout(height='auto', width='auto'))
grid = GridspecLayout(3, 3)
blzero=widgets.FloatSlider(min=0.0, max=4.0, step=0.1, value=0.8, continuous_update=False,description=r'$\lambda_0$')
bmuzero=widgets.FloatSlider(min=0.0, max=2.0, step=0.1, value=0.1, continuous_update=False,description=r'$\mu_0$')
bT=widgets.IntSlider(min=1, max=10, step=1, value=2, continuous_update=False,description='T')
bN=widgets.IntSlider(min=1, max=20, step=1, value=8, continuous_update=False,description='N')
bcoeff=widgets.FloatSlider(min=0.0, max=2.0, step=0.1, value=0.5, continuous_update=False,description='c')

grid[0,0]=blzero
grid[0,1]=bmuzero
grid[0,2]=bcoeff
grid[1,0]=bT
grid[1,1]=bN
w=interactive_output(stablecompoper.nsestimdenszchi,{'lzero':blzero,'muzero':bmuzero,'T':bT,'N':bN,'coeff':bcoeff})
display(grid,w)
#grid

```

```python
reload(stablecompoper)
plt.rcParams['figure.figsize']=(14,6)

stablecompoper.stablerepro(coeff=0.5,image=True)
```

```python

```

```python

```


# 気象学における重要な数式: Important mathematical formulas in meteorology

数式の記号の意味

$
    P ...圧力（気圧）\qquad V ...体積 \qquad T ...絶対温度 \qquad \rho ...空気の密度 \\
    g ...重力加速度 \qquad \theta ...温位 \qquad \theta_{e} ...相当温位
$

## 大気の熱力学に関する数式

* 気体の状態方程式

\begin{equation}
PV = mRT \tag{3.1}
\end{equation}

（nは物質量mol, Rは気体定数 (gas sonctant)）
Rの気体定数は機体の種類によって異なる。乾燥空気では、$287 m^2s^{-2}K^{-1} $である

* 気体の密度

\begin{equation}
\rho= \frac{m}{V}\\
\end{equation}

* 比容

\begin{equation}
\alpha = \frac{1}{\rho}\\
\end{equation}

従って気体の状態方程式は、
\begin{equation}
P =  \rho R T \tag{3.2}
\end{equation}

\begin{equation}
P\alpha = RT \tag{3.3}
\end{equation}

となる。

\begin{equation}
\frac{pV}{T} = mR = MR = R^* \tag{3.5}
\end{equation}

$R^*$ は一般気体定数
\begin{equation}
R^* = 8.3143 × 10^3\ JK^{-1}kmol^{-1}
\end{equation}

$\ 1kg\ $の乾燥空気に対する気体定数: $\ R_d \ $

乾燥空気の分子量: $\ M_d\ $

\begin{eqnarray}
R_d &=& \frac{R^*}{M_d} \nonumber \\
&=& \frac{8.3143 * 10^3}{28.9} \nonumber \\
&≒& 287\ JK^{-1}kg^{-1} \nonumber \\
\end{eqnarray}

## 静力学平衡の式

\begin{equation}
\Delta p = - g \rho \Delta Z  \tag{3.12}
\end{equation}

乾燥空気の状態方程式より、

\begin{equation}
\frac{\Delta p}{\Delta z} = - \frac{p g}{R_d T}
\end{equation}

## 熱力学第一法則の式

* Qは熱量、Wは仕事量、Uは内部エネルギー

\begin{equation}
Q = W + \Delta U
\end{equation}

## 温位と相当温位の関係

* $\theta$ は温位、$\theta_e$ は相当温位、$w$ は混合比

\begin{equation}
\theta_e = \theta + 2.8w
\end{equation}

## 比熱

* 1gの物質の温度を1K上昇させるのに必要な熱量のことを**比熱**といい、Cとする。単位は $\ JK^{-1}g^{-1}\ $

\begin{equation}
Q = C m \Delta T
\end{equation}

## 定容比熱と定圧比熱

* 定積変化の比熱を**定容比熱（定積比熱）**といい、$\ C_v\ $という。単位は $\ JK^{-1}kg^{-1}\ $
* 定圧変化の比熱を**定圧比熱**といい、$\ C_p\ $という。単位は $\ JK^{-1}kg^{-1}\ $
* 乾燥空気の定容比熱$\ C_v\ = 717\ JK^{-1}kg^{-1}\ $
* 乾燥空気の定圧比熱$\ C_p\ = 1004\ JK^{-1}kg^{-1}\ $

\begin{equation}
C_p - C_v = R \qquad （Rは気体定数）
\end{equation}

## 水蒸気量の表現に関する数式

* 混合比と水蒸気圧の関係

$W$は混合比、$e$は水蒸気圧

\begin{equation}
W \approx 0.622 \frac{e}{p}
\end{equation}

* 相対湿度の式 $I$

\begin{equation}
(相対湿度) = \frac{空気塊内の水蒸気量}{そのときの温度の飽和水蒸気量} * 100
\end{equation}

* 相対湿度の式 $II$

\begin{equation}
(相対湿度) = \frac{空気塊内の水蒸気圧}{そのときの温度の飽和水蒸気圧} * 100
\end{equation}

* 混合比

\begin{equation}
(混合比) = \frac{水蒸気の質量}{乾燥空気の質量}
\end{equation}

* 比湿

\begin{equation}
(比湿) = \frac{水蒸気の質量}{湿潤空気全体の質量}
\end{equation}

```python

```

90_Important mathematical formulas in meteorology.ipynb


# 11 Pressure Measurement
Pressure can be measured in many ways.  Here we will see:
- Mechanical
- Electronic

## Absolute vs Gage Pressure

\begin{align}
P_{gage} = P_{abs}-P_{atm}
\end{align}

## Dynamic Response

https://www.grc.nasa.gov/www/k-12/airplane/tunpsm.html

\begin{align}
\left| \frac{P}{P_0} \right| = \frac{1}{\sqrt{\left( 1 - \left( \frac{f}{f_n} \right)^2  \right)^2 + 4h^2 \left( \frac{f}{f_n} \right)^2 }}
\end{align}
with $f_n$ natural frequency, $h$ damping ratio
\begin{align}
f_n = \sqrt{\frac{3 \pi r^2 c^2}{4L V}} \quad h = \frac{2 \mu}{\rho c r^3} \sqrt{3LV}{\pi}
\end{align}
with the for the fluid in the pressure transmitting tube: $c$ speed of sound, $\mu$ dynamic viscosity, $\rho$ density.

Phase angle:
\begin{align}
\phi = \tan^{-1} \left( \frac{-2 h (f/f_n)}{1-(f/f_n)^2} \right)
\end{align}

_example_

A pressure transducer is connected to measurement test section through a thin tube.  The tube has radius $r=0.25$ mm and length $L=75$ mm.  It connects to a cavity of volume $V=3500$ mm$^3$.  The gas is air at 1 atm and 293 K.
- Calculate natural frequency and damping ratio of this system
- Calculate the attenuation for a 100 Hz pressure wave.

```python
import numpy
r = 0.25E-3 #m
c = 340 #m/s
L = 1 #m
V = 3500E-9 #m3
print(V)
```

    3.5e-06

```python
fn = numpy.sqrt((3*numpy.pi* r**2 * c**2)/(4*L *V))
print('f_n = ',fn,' Hz')
```

    f_n =  69.74136954622249  Hz

## Mechanical pressure measurement

### Manometer

\begin{align}
P_0 = \rho \text{g} \Delta z - \rho_0 \text{g} h + P_{atm}
\end{align}

The sensitivity of a manometer is defined as: $ K = \frac{\Delta z}{P_0 - P_{atm}}$.  It can be increased by changing $\rho$ or also by inclining leg of manometer on which we do pressure measurement to increase readibility of $\Delta z$.

### Mechanical pressure gages

## Electronic pressure transducers

### Strain gage

### Capacitance

### LVDT

### Optical

## Piezoelectric pressure transducers

## Examples
### Pitot-Static Probe

A Pitot-Static Probe is placed in an air jet to measure air speed at sea level, $15^\circ$C, and 1 atmosphere (101,325 Pa), $\rho_{air} = 1.225$ kg/m$^3$.  The differential pressure is measured with a U-tube manometer that uses water ($\rho_{water}=1002$ kg/m$^3$).  The height difference in the manometer is 1.00 m.

- Calculate the air speed measured by the Pitot-static tube at sea level.

- What would be the effect of altitude on velocity measurements with a Pitot-static tube?  Would your reading under- or over-estimate the air speed?
- Remember from your aerodynamics class that the troposphere region of the earth atmosphere (0-11 km) follows a temperature gradient regime with $dT/dh = a = -6.5$ K/km.  You can show (do it at home) that the air density varies as:

\begin{align}
\frac{\rho}{\rho_\circ} = \left( \frac{T}{T_\circ} \right)^{-\left( \frac{g}{a R_s} +1 \right)} \quad \textrm{where}\, R_s = 287 \frac{\mathrm{J}}{\mathrm{kg} \cdot \mathrm{K}} \textrm{air specific gas constant}
\end{align}

The height difference in the manometer is still 1 m.  Calculate the air speed at a 3,000 m altitude.  Neglect the effect of gravity on altitude, and assume the water in the manometer is still at $15^\circ$ C.

```python
rho_h20 = 1002 # kg/m3
rho_infty = 1.225 #kg/m3
DeltaZ = 1.0 #m
g = 9.81 # m/s2

U_infty = numpy.sqrt(2*rho_h20/rho_infty*DeltaZ*g)
print('U_infty = ',U_infty,' m/s')
```

    U_infty =  126.68211864218829  m/s

```python
T_0 = 288 # K
T_3 = T_0-6.5*3
rho_3 = rho_infty*(T_3/T_0)**(-(g/287*(-6.5))+1)
U_3 = numpy.sqrt(2*rho_h20/rho_3*DeltaZ*g)
print('T_3 = ', T_3, ' K',', ', 'rho_3 = ', rho_3,' kg/m3',', ', 'U_3 = ',U_3,' m/s')
```

    T_3 =  268.5  K ,  rho_3 =  1.124405549570362  kg/m3 ,  U_3 =  132.22752593523535  m/s

### Strain Gage Pressure Measurement

One entrepreneurial MAE-3120 student decides to create a pressure transducer by mounting a strain gage to the side wall of a soda can.  The soda can has 66.0 mm-diameter, 0.105 mm-wall thickness (side walls), and is made of aluminum with $E = 70.0$ GPa Young's modulus, and $\nu=0.35$ Poisson ratio.  The strain gage has resistance $120\,\Omega$ and $S = 2.05$ strain-gage factor.

- What circuit should the student use to measure the can deformation?
- The can opening is connected to a pressure line that the student want to monitor.  What kind of pressure will be recorded, i.e. $P_{abs}, P_{gage}$?
- Remember that the Hoop Stress, $\sigma_H$, on a thin wall cylinder that is pressurized is given by:
\begin{align}
\sigma_H &= \frac{P_g D}{2t}, \quad \textrm{where}\; P_g = P_{in}-P_{atm}, \quad D:\textrm{diameter},\; \quad t:\textrm{wall thickness}\\
\sigma_L &= \frac{P_g D}{4t}, \quad \textrm{where}\; \sigma_L : \textrm{Longitudinal Stress}
\end{align}
Remember also that stress and strain are related by:
\begin{align}
\epsilon_H &= \frac{1}{E} (\sigma_H - \nu \sigma_L)\\
\epsilon_L &= \frac{1}{E} (\sigma_L - \nu \sigma_H)
\end{align}
Assume he/she used a quarter-wave Wheatstone bridge with 5.00 V excitation and that the bridge is balanced at no load.  The student measures a voltage of 3.95 mV, calculate the applied gage pressure in kPa.

\begin{align}
\epsilon_H & = \frac{P_g D}{2E t }\cdot \left( 1-\frac{\nu}{2} \right)\\
P_g &= \frac{\epsilon_H 2E t}{D(1-\nu /2)}
\end{align}
Remember for 1/4 Wheatstone bridge
\begin{align*}
\frac{V_o}{V_s} = \pm \frac{1}{4} \epsilon_H S
\end{align*}

```python
V_o = 3.95E-3; V_s = 5; S = 2.05
eps_H = 4*V_o/V_s/S
print('eps_H = ',eps_H*1E6, ' micro-strain')
```

    eps_H =  1541.4634146341468  micro-strain

```python
E = 70E9; t = 0.105E-3; D = 66E-3; nu = 0.35
P_g = 2* eps_H*E*t/(D*(1-nu/2))
print('P_g = ', P_g/1000, ' kPa', ' = ', P_g/101325 , ' atm')
```

    P_g =  416.1526573943427  kPa  =  4.107107400881744  atm

```python

```


## Devito CFD Tutorial series

The following series of notebook tutorials will demonstrate the use of Devito and it's SymPy-based API to solve a set of classic examples from Computational Fluid Dynamics (CFD). The tutorials are based on the excellent tutorial series _CFD Python: 12 steps to Navier-Stokes_ by Lorena Barba and focus on the implementation with Devito rather than pure CFD or finite difference theory. For a refresher on how to implement 2D finite difference solvers for CFD problems, please see the original tutorial series here: <br>
http://lorenabarba.com/blog/cfd-python-12-steps-to-navier-stokes/

### Example 1: Linear convection in 2D

Lets start with a simple 2D convection example - step 5 in the original blog. This will already allow us to demonstrate a lot about the use of Devito's symbolic data objects and how to use them to build a simple operator directly from the symbolic notation of the equation. The governing equation we will implement in this tutorial is:

$$\frac{\partial u}{\partial t}+c\frac{\partial u}{\partial x} + c\frac{\partial u}{\partial y} = 0$$

In order to implement this equation we first discretize it using forward differences in time and backward differences in space. Just as the original tutorial, we will use $u_{i,j}^n$ to denote a finite difference stencil point with $i$ and $j$ denoting spatial indices and $n$ denoting the time index. So, after re-arragning the discretized equation for the forward stencil point in time we get

$$u_{i,j}^{n+1} = u_{i,j}^n-c \frac{\Delta t}{\Delta x}(u_{i,j}^n-u_{i-1,j}^n)-c \frac{\Delta t}{\Delta y}(u_{i,j}^n-u_{i,j-1}^n)$$

Using this, we can start deriving the computational stencil for this equation. Let's first look at the original _pure Python_ implementation of the linear convection flow - but first we import our tools and define some parameters:

```python
from examples.cfd import plot_field, init_hat
import numpy as np
%matplotlib inline

# Some variable declarations
nx = 81
ny = 81
nt = 100
c = 1.
dx = 2. / (nx - 1)
dy = 2. / (ny - 1)
print("dx %s, dy %s" % (dx, dy))
sigma = .2
dt = sigma * dx
```

    dx 0.025, dy 0.025

**A small note on style:** Throughout this tutorial series we will use utility functions to plot the various 2D functions and data sets we deal with. These are all taken from the original tutorial series, but have been slightly modified for our purposes. One of the differences readers might find is that the original series uses _(y, x)_ indexing for 2d data arrays, whereas many of the examples have been adapted to use _(x, y)_ notation in our tutorials.

So, let's start by creating a simple 2D function and initialising it with a "hat function". We will use that initialisation function a lot, so it comes from our utility scripts:

```python
#NBVAL_IGNORE_OUTPUT

# Create field and assign initial conditions
u = np.empty((nx, ny))
init_hat(field=u, dx=dx, dy=dy, value=2.)

# Plot initial condition
plot_field(u)
```

Now we can repeat the _pure NumPy_ solve from the original tutorial, where we use NumPy array operations to speed up the computation. Note that we skip the derivation of the stencil used to implement our convection equation, as we are going to walk though this process using the Devito API later in this tutorial.

```python
# Repeat initialisation, so we can re-run the cell
init_hat(field=u, dx=dx, dy=dy, value=2.)

for n in range(nt + 1):
    # Copy previous result into a new buffer
    un = u.copy()

    # Update the new result with a 3-point stencil
    u[1:, 1:] = (un[1:, 1:] - (c * dt / dx * (un[1:, 1:] - un[1:, :-1])) -
                              (c * dt / dy * (un[1:, 1:] - un[:-1, 1:])))

    # Apply boundary conditions
    u[0, :] = 1.
    u[-1, :] = 1.
    u[:, 0] = 1.
    u[:, -1] = 1.
```

```python
#NBVAL_IGNORE_OUTPUT

# A small sanity check for auto-testing
assert (u[45:55, 45:55] > 1.8).all()
u_ref = u.copy()

plot_field(u)
```

Hooray, the wave moved! Now, this little example is already very concise from a notational point of view and it teaches us quite a few key points about how to perform finite difference stencil computation via NumPy:
* Due to the backward differencing scheme in space (more later) we use only three stencil point in this example:  $u^{n}_{i, j}$, $u^{n}_{i-1, j}$ and $u^{n}_{i, j-1}$. These can be identified in the code through the array indices and correspond to `un[1:, 1:]`, `un[:-1, 1:]` and `un[1:, :-1]` respectively.
* Two buffers for array data are used throughout: `un[...]` is read from, while `u[...]` is updated, where the line `un = u.copy()` performs a deep copy of the field to switch buffers between timesteps. Note that in some other finite difference tutorials the cost of this copy operation is sometimes amortised by using two pre-allocated buffers and switching the indices of them explicitly.
* The final four lines within the loop code show us how to implmement simple Dirichlet boundary condition by simply setting a value on the outermost rows and columns of our cartesian grid.

You may have noticed that the hat has not only moved to a different location, but has also changed its shape into a smooth bump. This is a little surprising, as the correct solution of the convection equation would be movement without shape change. The smooth shape is caused by numerical diffusion, a well-known limitation of low-order finite difference schemes. We will discuss this issue and some solutions later in this tutorial.

#### Devito implementation
Now we want to re-create the above example via a Devito operator. To do this, we can start by defining our computational grid and creating a function `u` as a symbolic `devito.TimeFunction`. The core thing to note here is that this is one of Devito's symbolic function, which have a dual role in the creation of finite difference solvers:
* They behave symbolically like `sympy.Function` objects, so that we can construct derivatives and use them in symbolic expressions, thus inheriting all the power of automated symbolic manipulation that SymPy provides.
* They act as containers for user data by providing a `.data` property that wraps automatically allocated memory space in a neat NumPy array.

The particular `TimeFunction` type that we will declare our variable $u$ as in this case is aware of the fact that we will want to implement a timestepping algorithm with it. So the object `u` will declare two buffers of shape `(nx, ny)` for us, as defined by the `Grid` object, and present them as `u.data[0]` and `u.data[1]`. Let's fill the initial buffer with some data and look at it:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Grid, TimeFunction

grid = Grid(shape=(nx, ny), extent=(2., 2.))
u = TimeFunction(name='u', grid=grid)
init_hat(field=u.data[0], dx=dx, dy=dy, value=2.)

plot_field(u.data[0])
```

Nice. Now we can look at deriving our 3-point stencil using the symbolic capabilities given to our function $u$ by SymPy. For this we will first construct our derivative terms in space and time. For the forward derivative in time we can easily use Devito's shorthand notation `u.dt` to denote the first derivative in time and `u.dxl` and `u.dyl` to denote the space derivatives. Note that the `l` means were using the "left" or backward difference here to adhere to the discretization used in the original tutorials.

From the resulting terms we can then create a `sympy.Equation` object that contains the fully discretised equation, but from a neat high-level notation, as shown below.

```python
from devito import Eq, INTERIOR

# Specify the INTERIOR attribute so the stencil is only
# applied to the interior of the domain.
eq = Eq(u.dt + c*u.dxl + c*u.dyl, region=INTERIOR)

print(eq)
```

    Eq(1.0*u(t, x, y)/h_y - 1.0*u(t, x, y - h_y)/h_y + 1.0*u(t, x, y)/h_x - 1.0*u(t, x - h_x, y)/h_x - u(t, x, y)/dt + u(t + dt, x, y)/dt, 0)

The above step resulted in a fully discretised version of our equation, which includes place-holder symbols for the spacing in time (`s`) and space (`h`). These symbols are based on an internal convention and will later be replaced when we build an operator.

But before we can build an operator, we first need to change our discretised expression so that we are updating the forward stencil point in our timestepping scheme - Devito provides another short-hand notation for this: `u.forward`. For the actual symoblic reordering, SymPy comes to the rescue with the `solve` utility that we can use to re-organise our equation.

```python
from sympy import solve

stencil = solve(eq, u.forward)[0]

print(stencil)
```

    (dt*h_x*(-u(t, x, y) + u(t, x, y - h_y)) + dt*h_y*(-u(t, x, y) + u(t, x - h_x, y)) + h_x*h_y*u(t, x, y))/(h_x*h_y)

The careful reader will note that this is now the equivalent symbolic expression to the RHS term of the NumPy code we showed earlier - only with `dx` and `dy` denoted as `h` and `dt` denoted as `s`, while `u(t, x, y)`, `u(t, x - h, y)` and `u(t, x, y - h)` denote the equivalent of $u^{n}_{i, j}$, $u^{n}_{i-1, j}$ and $u^{n}_{i, j-1}$ respectively.

We can now use this stencil expression to create an operator to apply to our data object:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Operator

# Reset our initial condition in both buffers.
# This is required to avoid 0s propagating into
# our solution, which has a background value of 1.
init_hat(field=u.data[0], dx=dx, dy=dy, value=2.)
init_hat(field=u.data[1], dx=dx, dy=dy, value=2.)

# Create an operator that updates the forward stencil point
op = Operator(Eq(u.forward, stencil, region=INTERIOR))

# Apply the operator for a number of timesteps
op(time=nt, dt=dt)

plot_field(u.data[0, :, :])

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

Great, that looks to have done the same thing as the original NumPy example, so we seem to be doing something right, at least.

**A note on performance:** During the code generation phase of the previous operatorm Devito has created some log output with information about two optimisation steps: The DSE (Devito Symbolics Engine) and the DLE (Devito Loop Engine). We can ignore these for now, because our exmaple is tiny - but for large runs where performance matters, these are the engines that make the Devito kernel run very fast in comparison to raw Python/NumPy.

Now, despite getting a correct looking result, there is still one problem with the above operator: It doesn't set any boundary conditions as part of the time loop. We also note that the operator includes a time loop, but at this point Devito doesn;t actually provide any language constructs to explicitly define different types of boundary conditions (Devito is probably still a kind of prototype at this point). Luckily though, Devito provides a backdoor for us to insert custom expression in the so-called "indexed" or "low-level" API that allow us to encode the Dirichlet boundary condition of the original example.

#### The "indexed" or low-level API
The `TimeFunction` field we created earlier behaves symbolically like a `sympy.Function` object with the appropriate indices, eg. `u(t, x, y)`. If we take a simple first-order derivative of that we have a term that includes the spacing variable `h`, which Devito uses as the default for encoding $dx$ or $dy$. For example, `u.dx` simply expands to `-u(t, x, y)/h + u(t, x + h, y)/h`.

Now, when the `Operator` creates explicit C-code from that expression, it at some point "lowers" that expression by resolving explicit data accesses (or indices) into our grid by transforming it into a `sympy.Indexed` object. During this process all occurences of `h` in data accesses get replaced with integers, so that the expression now looks like `-u[t, x, y]/h + u[t, x + 1, y]/h`. This is the "indexed" notation and we can create custom expression of the same kind by using the `u.indexed` property with explicit indices in square-bracket notation. These custom expressions can then be injected them into our operator like this:

```python
#NBVAL_IGNORE_OUTPUT

# Reset our data field and ICs in both buffers
init_hat(field=u.data[0], dx=dx, dy=dy, value=2.)
init_hat(field=u.data[1], dx=dx, dy=dy, value=2.)

# For defining BCs, we want to explicitly set rows/columns in our field
# We can use Devito's "indexed" notation to do this:
x, y = grid.dimensions
t = grid.stepping_dim
bc_left = Eq(u.indexed[t + 1, 0, y], 1.)
bc_right = Eq(u.indexed[t + 1, nx-1, y], 1.)
bc_top = Eq(u.indexed[t + 1, x, ny-1], 1.)
bc_bottom = Eq(u.indexed[t + 1, x, 0], 1.)

# Now combine the BC expressions with the stencil to form operator
expressions = [Eq(u.forward, stencil)]
expressions += [bc_left, bc_right, bc_top, bc_bottom]
op = Operator(expressions=expressions, dle=None, dse=None)  # <-- Turn off performance optimisations
op(time=nt, dt=dt)

plot_field(u.data[0, :, :])

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

You might have noticed that we used the arguments `dle=None` and `dse=None` in the creation of the previous operator. This suppresses the various performance optimisation steps in the code-generation pipeline, which makes the auto-generated C code much easier to look at. So, for the brave, let's have a little peek under the hood...

```python
print(op.ccode)
```

    #define _POSIX_C_SOURCE 200809L
    #include "stdlib.h"
    #include "math.h"
    #include "sys/time.h"

    struct Profiler
    {
      double section0;
      double section1;
      double section2;
    } ;

    int Kernel(const float dt, const float h_x, const float h_y, float *restrict u_vec, const int time_M, const int time_m, void *_timers, const int x_M, const int x_m, const int x_size, const int y_M, const int y_m, const int y_size)
    {
      float (*restrict u)[x_size + 1 + 1][y_size + 1 + 1] __attribute__((aligned(64))) = (float (*)[x_size + 1 + 1][y_size + 1 + 1]) u_vec;
      struct Profiler *timers = (struct Profiler*) _timers;
      for (int time = time_m, t0 = (time)%(2), t1 = (time + 1)%(2); time <= time_M; time += 1, t0 = (time)%(2), t1 = (time + 1)%(2))
      {
        struct timeval start_section0, end_section0;
        gettimeofday(&start_section0, NULL);
        for (int x = x_m; x <= x_M; x += 1)
        {
          for (int y = y_m; y <= y_M; y += 1)
          {
            u[t1][x + 1][y + 1] = (dt*h_x*(u[t0][x + 1][y] - u[t0][x + 1][y + 1]) + dt*h_y*(u[t0][x][y + 1] - u[t0][x + 1][y + 1]) + h_x*h_y*u[t0][x + 1][y + 1])/(h_x*h_y);
          }
        }
        gettimeofday(&end_section0, NULL);
        timers->section0 += (double)(end_section0.tv_sec-start_section0.tv_sec)+(double)(end_section0.tv_usec-start_section0.tv_usec)/1000000;
        struct timeval start_section1, end_section1;
        gettimeofday(&start_section1, NULL);
        for (int y = y_m; y <= y_M; y += 1)
        {
          u[t1][1][y + 1] = 1.00000000000000F;
          u[t1][81][y + 1] = 1.00000000000000F;
        }
        gettimeofday(&end_section1, NULL);
        timers->section1 += (double)(end_section1.tv_sec-start_section1.tv_sec)+(double)(end_section1.tv_usec-start_section1.tv_usec)/1000000;
        struct timeval start_section2, end_section2;
        gettimeofday(&start_section2, NULL);
        for (int x = x_m; x <= x_M; x += 1)
        {
          u[t1][x + 1][81] = 1.00000000000000F;
          u[t1][x + 1][1] = 1.00000000000000F;
        }
        gettimeofday(&end_section2, NULL);
        timers->section2 += (double)(end_section2.tv_sec-start_section2.tv_sec)+(double)(end_section2.tv_usec-start_section2.tv_usec)/1000000;
      }
      return 0;
    }


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

    Bad key "text.kerning_factor" on line 4 in
    C:\Users\sensio\miniconda3\lib\site-packages\matplotlib\mpl-data\stylelib\_classic_test_patch.mplstyle.
    You probably need to get an updated matplotlibrc file from
    https://github.com/matplotlib/matplotlib/blob/v3.1.3/matplotlibrc.template
    or from the matplotlib source distribution

# One-dimensional Advection Equation

We want to solve the following PDE:

\begin{equation}
    \frac{\partial \phi}{\partial t} + u \frac{\partial \phi}{\partial x} = 0
\end{equation}

The independent variables (i.e, $x$ and $t$) are used as input values for the NN, and the solution (i.e. $\phi$) is the output. In order to find the solution, at each step the NN outputs are derived w.r.t the inputs. Then, a loss function that matches the PDE is built and the weights are updated accordingly. If the loss function goes to zero, we can assume that our NN is indeed the solution to our PDE. We will try to find a general solution for different values of $u$, so it will be set also as an input.

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

device = "cuda" if torch.cuda.is_available() else "cpu"

nangs.__version__, torch.__version__
```

    ('0.1.2', '1.5.0')

```python
from nangs import PDE

class Adv1d(PDE):
    def computePDELoss(self, inputs, outputs):

        # compute gradients
        grads = self.computeGrads(outputs, inputs)

        # compute loss
        dpdx, dpdt = grads[:, 0], grads[:, 1]
        u = inputs[:,2]
        return {'pde': dpdt + u*dpdx}

pde = Adv1d(inputs=('x', 't', 'u'), outputs='p')
```

```python
# define the mesh

from nangs import Mesh

x = np.linspace(0,1,20)
t = np.linspace(0,1,30)
u = np.linspace(-1,1,10)

mesh = Mesh({'x': x, 't': t, 'u': u}, device=device)

pde.set_mesh(mesh)
```

```python
# initial condition (t = 0)

t0 = np.array([0])
_x, _t, _u = np.meshgrid(x, t0, u)
p0 = np.sin(2*np.pi*(_x- _u*_t))
```

```python
plt.plot(x, p0[0,:,0])
plt.grid(True)
plt.xlabel('x')
plt.ylabel('$p_0$')
plt.show()
```

```python
# boundary conditions

from nangs import Dirichlet, Periodic

# initial condition
initial_condition = Dirichlet({'x': x, 't': t0, 'u': u}, {'p': p0.reshape(-1)}, device=device, name="initial")
pde.add_boco(initial_condition)

# periodic bocos
x1 = np.array([0.])
x2 = np.array([1.])
periodic = Periodic({'x': x1, 't': t, 'u': u}, {'x': x2, 't': t, 'u': u}, device=device)
pde.add_boco(periodic)
```

```python
# solve

from nangs import MLP

BATCH_SIZE = 256
LR = 1e-2
EPOCHS = 30
NUM_LAYERS = 3
NUM_HIDDEN = 128

mlp = MLP(len(pde.inputs), len(pde.outputs), NUM_LAYERS, NUM_HIDDEN).to(device)
optimizer = torch.optim.Adam(mlp.parameters())
scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=LR, pct_start=0.1, div_factor=10, final_div_factor=1, total_steps=EPOCHS)

pde.compile(mlp, optimizer, scheduler)
%time hist = pde.solve(EPOCHS, BATCH_SIZE)
```

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

def update_plot(i, x, u, t, p):
    ax.clear()
    pe = np.sin(2.*np.pi*(x-u*t[i]))
    ax.plot(x, pe, label=f"exact (u = {U})")
    ax.plot(x, p[i], '.k', label="solution")
    ax.set_xlabel("x", fontsize=14)
    ax.set_ylabel("p", fontsize=14, rotation=np.pi/2)
    ax.legend(loc="upper right")
    ax.grid(True)
    ax.set_xlim([0, 1])
    ax.set_ylim([-1.2, 1.2])
    l2 = np.sqrt(np.sum((p[i]-pe)**2))
    ax.set_title(f"t = {t[i]:.3f} (L2 = {l2:.5f})")
    return ax
```

```python
# evaluate

U = 0.5

x = np.linspace(0,1,30)
t = np.linspace(0,1,20)
u = np.array([U])
eval_mesh = Mesh({'x': x, 't': t, 'u': u}, device=device)

p = pde.eval(eval_mesh)
p = p.cpu().numpy().reshape(len(t), -1)

fig = plt.figure()
ax = plt.subplot(111)
anim = animation.FuncAnimation(fig, update_plot, frames=len(t), fargs=(x, U, t, p), interval=300)
plt.close()
```

```python
anim
```


# Laboratório 11: Colheita de madeira

### Referente ao capítulo 18

Considere uma fazenda de madeiras que possui uma regulamentação em que, após uma extração fixa de porcentagem da população de árvores, deve replantar. Assumimos que a fazenda opera com porcentagem constante de extração, produzindo $x(t)$. Crescimento e mortes naturais de árvores não são consideradas. A quantidade de madeira é baseada apenas no tamanho da fazenda. Consideramos também que o nível de extração é baixo o suficiente para que a idade  da árvore não seja considerada e portanto sempre será madura. Após o processamento da madeira, ela é imediatamente vendida.

O dinheiro ganho pode ser tido como lucro ou reinvestido na fazenda. Os donos da fazenda querem uma estratégia de reinvestimento que maximiza o lucro em um período fixo de tempo. Seja $u(t)$ a porcentagem da madeira vendida que é reinvestida.

Assim $x'(t) = kx(t)u(t)$, onde $k$ é constante de retorno, que leva em conta o custo médio de trabalho e terra. Se $p$ é o preço de mercado por unidade de madeira, então o lucro total é dado por

$$
p\int_0^T x(t)[1 - u(t)]dt
$$

Como estamos lidando com tempo que pode ser considerado suficientemente longo, vamos adicionar um tempo $e^{-rt}$ para trazer a *valor presente* o valor investido no tempo $t$. Isso considera que o lucro investido mais cedo rende mais segundo a taxa de juros $r$. Assim o funcional objetivo se torna

$$
p\int_0^T e^{-rt}x(t)[1 - u(t)]dt
$$

Porém a constante $p$ não deve afetar o problema e é, portanto, desconsiderada. Além disso, seja $x(0) = x_0> 0 $ e $0 \le u(t) \le 1$.

### Importando bibliotecas

```python
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
import sympy as sp

import sys
sys.path.insert(0, '../pyscripts/')

from optimal_control_class import OptimalControl
```

## Condições necessárias

Mais uma vez vamos utilizar a biblioteca simbólica para nos ajudar nos cálculos. Note que esse sistema é tão simples que de fato não seria muito necessário, mas vamos usar assim mesmo.

### Hamiltoniano

```python
x, u, l, t, r, k = sp.symbols('x u lambda t r k')
H = sp.exp(-r*t)*x*(1 - u) + k*x*u*l
H
```

$\displaystyle k \lambda u x + x \left(1 - u\right) e^{- r t}$

### Condições do Estado

```python
print(sp.diff(H,l))
```

    k*u*x

### Equação Adjunta

```python
dl = (-1)*sp.diff(H,x)
print(dl)
```

    -k*lambda*u - (1 - u)*exp(-r*t)

### Transversalidade

$\lambda(T) = 0$

### Otimalidade

```python
psi = sp.diff(H,u)
psi
```

$\displaystyle k \lambda x - x e^{- r t}$

## Demonstração que o problema é Bang-Bang

Vemos que $x'(t) \ge 0 \implies x(t) > 0, \forall t \in [0,T]$. Suponha que $\psi = 0$ em algum intervalo. Então
$\lambda(t) = \frac{1}{k}e^{-rt}$ nesse intervalo. Portanto
$\lambda'(t) = -\frac{r}{k}e^{-rt}$. Utilizando a equação adjunta, $-\frac{r}{k}e^{-rt} = -e^{-rt}u - (1 - u)e^{-rt} = -e ^{-rt}$. Se $k \neq r$, isso é uma contradição.

Se $k=r$, verifiquemos como $\lambda(t)$ é em um intervalo contendo $t = T$, isto é, $\lambda(T) = 0$.

- Se $u^* = 1 \implies \lambda '(t) = -k\lambda \implies \lambda(t) \equiv 0$.
- Se $u^* = 0 \implies \lambda '(t) = - e^{-rt} \implies \lambda(t) = \frac{1}{k}e^{-rt}$, o que não é possível. Então $u^* = 1$.

Mas não é possível que essas funções formem uma única função contínua, o que é uma contradição. Assim o problema é Bang-Bang.

## Aplicando a classe ao exemplo

Vamos começar as experimentações

```python
parameters = {'k': None, 'r': None}

diff_state = lambda t, x, u, par: par['k']*x*u

diff_lambda = lambda t, x, u, lambda_, par: -par['k']*lambda_*u - (1 - u)*np.exp(-par['r']*t)

update_u = lambda t, x, lambda_, par: 0.0 if par['k']*lambda_ < np.exp(- par['r']*t) else 1.0
```

```python
problem = OptimalControl(diff_state, diff_lambda, update_u,
                         n_controls = 1, n_states = 1,
                         bounds = [(0,1)])
```

```python
x0 = 100
T = 5
parameters['k'] = 1
parameters['r'] = 0
```

```python
t,x,u,lambda_ = problem.solve(x0, T, parameters)
ax = problem.plotting(t,x,u,lambda_)
ax[0].legend()
plt.show()
```

Observe que existe uma mudança abrupta no controle em $t = 4$, como esperávamos de um problema Bang Bang. Entretanto, essa reta ligando os pontos não existe propriamente, é apenas uma ilusão criado pelo gráfico que liga os pontos para parecer uma função contínua. No caso sabemos que não é. Esse é o ponto de troca. Essa estratégia nos diz para os fazendeiros reinvestirem tudo no início e depois, manter para lucro. Mas veja que $r = 0$, então isso faz sentido.

```python
parameters['k'] = 0.3
parameters['r'] = 0.05
t,x,u,lambda_ = problem.solve(x0, T, parameters)
ax = problem.plotting(t,x,u,lambda_)
```

Com taxa de juros, fica mais interessante começar a manter o lucro um pouco antes, afim de ganhar com o juros. Vamos verificar como valor de $k$ representa no controle escolhido. Em geral não temos controle sobre o valor de $k$ mas já sabemos que quanto maior, mais o reinvestimento aumenta a área plantada.

```python
k_values = [0.2, 0.3, 0.35, 0.4, 0.6]
u_values = []
for k in k_values:
    parameters['k'] = k
    _,_,u,_ = problem.solve(x0, T, parameters)
    u_values.append(u)

fig = plt.figure(figsize = (10,5))
plt.xlabel("Tempo")
plt.ylabel("Reinvestimento")
for i, k in enumerate(k_values):
    plt.plot(t, u_values[i],label = r'$k$ = {}'.format(k))
plt.legend()
plt.grid(alpha = 0.5)
```

Observe que quanto maior o efeito do reinvestimento, mais tardio vai ser nossa troca de estratégia, porque vale a pena reinvestir se isso está nos garantindo maiores lucros no futuro. No caso em que $k = 0.2$ nem vale a pena reinvestir de tão baixo que o reinvestimento é considerado.

Podemos analisar o efeito da taxa de juros também. Quanto maior a taxa de juros, melhor deixar o dinheiro rendendo sozinho do que reinvestir na fazenda. No caso em que $r = 0.2$, é melhor nem mexer na fazenda, só investir tudo que se ganha.

```python
parameters['k'] = 0.3
r_values = [0.01, 0.05, 0.1, 0.2]
u_values = []
for r in r_values:
    parameters['r'] = r
    _,_,u,_ = problem.solve(x0, T, parameters)
    u_values.append(u)

fig = plt.figure(figsize = (10,5))
plt.xlabel("Tempo")
plt.ylabel("Reinvestimento")
for i, r in enumerate(r_values):
    plt.plot(t, u_values[i],label = r'$r$ = {}'.format(r))
plt.legend()
plt.grid(alpha = 0.5)
```

Por fim, vamos discutir algumas particularidades desse problema. Vamos calcular $r=0, k = T = 1$.
parameters['k'] = 1
parameters['r'] = 0
T = 1
t,x,u,lambda_ = problem.solve(x0, T, parameters)
ax = problem.plotting(t,x,u,lambda_)
Você deve ter visto que se passaram mais de 10 minutos. Isso ocorre, pois o problema não converge devido ao *round-off error*. Quando o valor do controle ótimo é $u = 0$, temos que $\lambda '(t) = -1$. Assim $\lambda(t) = 1 - t$. Entretanto, o valor calculado numericamente de $\lambda(0)$ é aproximadamente $1 + 10^{-16}$. Isso é, a princípio, insignificante, mas no leva a ter
$$
H_u(0) > 0
$$
o que nos direciona a calcular $u(0) = 1$ quando esperávamos que ele fosse $0$. Com o tempo, pela média que calculamos do valor anterior com o atual, esse valor vai se reduzindo para $0.5, 0.25, 0.125$, o que no limite é zero, mas nossa regra de atualização fará com que o problema não convirja. Assim, o erro é dado pela forma que o número é guardado no computador e o método que desenvolvemos não é capaz de lidar com isso.

Por esse motivo, ao invés de forçar uma representação para $u^*$, muitas vezes os algoritmos que resolvem problemas Bang Bang procuram estimar os tempos de troca.

### Este é o final do notebook


# Time-dependent variational forms

There are at least three different strategies for performing
a discretization in time:

1. Use *finite differences* for time derivatives to arrive at
   a recursive set of spatial problems that can be discretized by
   the finite element method.

2. Discretize in space by finite elements first, and then solve
   the resulting system of ordinary differential equations (ODEs) by
   some *standard library* for ODEs.

3. Discretize in space and time simultaneously by space-time finite elements.

With the first strategy, we discretize in time prior to the space
discretization, while the second strategy consists of doing exactly
the opposite. It should come as no surprise that in many situations
these two strategies end up in exactly the same systems to be solved, but
this is not always the case.  Also the third approach often reproduces standard
finite difference schemes such as the Backward Euler and the Crank-Nicolson
schemes for lower-order elements, but offers an interesting framework for deriving higher-order
methods. In this chapter we shall be concerned with
the first strategy,
which is the most common strategy as it turns the time-dependent
PDE problem to a sequence of stationary problems for which efficient
finite element solution strategies often are available.
The second strategy would
naturally employ well-known ODE software,
which are available as user-friendly routines
in Python. However, these routines are presently not efficient enough
for PDE problems in 2D and 3D. The first strategy gives complete hands-on
control of the implementation and the computational efficiency
in time and space.

We shall use a simple diffusion problem to illustrate the basic
principles of how a time-dependent PDE is solved by finite differences
in time and finite elements in space. Of course, instead of finite elements,
we may employ other types of basis functions, such as global polynomials.
Our model problem reads

$$
\begin{equation}
\frac{\partial u}{\partial t} = {\alpha}\nabla^2 u + f(\boldsymbol{x}, t),\quad
\boldsymbol{x}\in\Omega,\ t\in (0,T],
\label{fem:deq:diffu:eq} \tag{1}
\end{equation}
$$

$$
\begin{equation}
u(\boldsymbol{x}, 0)  = I(\boldsymbol{x}),\quad \boldsymbol{x}\in\Omega,
\label{fem:deq:diffu:ic} \tag{2}
\end{equation}
$$

$$
\begin{equation}
\frac{\partial u}{\partial n} = 0,\quad \boldsymbol{x}\in\partial\Omega,\ t\in (0,T]
\label{fem:deq:diffu:bcN} \tag{3}
{\thinspace .}
\end{equation}
$$

Here, $u(\boldsymbol{x},t)$ is the unknown function, ${\alpha}$ is a constant, and
$f(\boldsymbol{x},t)$ and $I(\boldsymbol{x})$ are given functions. We have assigned the particular
boundary condition ([3](#fem:deq:diffu:bcN)) to minimize
the details on handling boundary conditions in the finite element method.

**Remark.** For systems of PDEs the strategy for discretization in time may have great impact on
overall efficiency and accuracy. The Navier-Stokes equations for
an incompressible Newtonian fluid is a prime example where many methods have been proposed
and where there are notable differences between the different methods. Furthermore,
the differences often depend significantly on the application.
Discretization in time *before* discretization in space allows for manipulations
of the equations and schemes that are very efficient compared to
schemes based on discretizing in space first.
The schemes are so-called operator-splitting schemes or projection based schemes. These schemes do, however,
suffer from loss of accuracy particularly in terms of errors associated with the boundaries.
The numerical error is caused by the splitting of the equations which leads to non-trivial splitting
of the boundary conditions.

# Discretization in time by a Forward Euler scheme

The discretization strategy is to first apply a simple finite difference
scheme in time and derive a recursive set of spatially continuous PDE
problems, one at each time level. For each spatial PDE problem we can
set up a variational formulation and employ the finite element method
for solution.

## Time discretization

We can apply a finite difference method in time to ([1](#fem:deq:diffu:eq)).
First we need 'a mesh' in time, here taken as uniform with
mesh points $t_n = n\Delta t$, $n=0,1,\ldots,N_t$.
A Forward Euler scheme consists of sampling ([1](#fem:deq:diffu:eq))
at $t_n$ and approximating the time derivative by a forward
difference $[D_t^+ u]^n\approx
(u^{n+1}-u^n)/\Delta t$.
This approximation turns ([1](#fem:deq:diffu:eq))
into a differential equation that is discrete in time, but still
continuous in space.
With a finite difference operator notation we can write the
time-discrete problem as

$$
\begin{equation}
[D_t^+ u = {\alpha}\nabla^2 u + f]^n,
\label{fem:deq:diffu:FE:eq:FEop} \tag{4}
\end{equation}
$$

for $n=1,2,\ldots,N_t-1$.
Writing this equation out in detail and
isolating the unknown $u^{n+1}$ on the left-hand side, demonstrates that
the time-discrete problem is a recursive set of problems that are
continuous in space:

$$
\begin{equation}
u^{n+1} = u^n + \Delta t \left( {\alpha}\nabla^2 u^n + f(\boldsymbol{x}, t_n)\right)
\label{fem:deq:diffu:FE:eq:unp1} \tag{5}
{\thinspace .}
\end{equation}
$$

Given $u^0=I$, we can use ([5](#fem:deq:diffu:FE:eq:unp1)) to compute
$u^1,u^2,\dots,u^{N_t}$.

**More precise notation.**

For absolute clarity in the various stages of the discretizations, we
introduce ${u_{\small\mbox{e}}}(\boldsymbol{x},t)$ as the exact solution of the space-and time-continuous
partial differential equation ([1](#fem:deq:diffu:eq)) and
${u_{\small\mbox{e}}}^n(\boldsymbol{x})$ as the time-discrete approximation, arising from the finite
difference method in time ([4](#fem:deq:diffu:FE:eq:FEop)).
More precisely, ${u_{\small\mbox{e}}}$ fulfills

$$
\begin{equation}
\frac{\partial {u_{\small\mbox{e}}}}{\partial t} = {\alpha}\nabla^2 {u_{\small\mbox{e}}} + f(\boldsymbol{x}, t)
\label{fem:deq:diffu:eq:uex} \tag{6},
\end{equation}
$$

while ${u_{\small\mbox{e}}}^{n+1}$, with a superscript,
is the solution of the time-discrete equations

$$
\begin{equation}
{u_{\small\mbox{e}}}^{n+1} = {u_{\small\mbox{e}}}^n + \Delta t \left( {\alpha}\nabla^2 {u_{\small\mbox{e}}}^n + f(\boldsymbol{x}, t_n)\right)
\label{fem:deq:diffu:FE:eq:uex:n} \tag{7}
{\thinspace .}
\end{equation}
$$

The ${u_{\small\mbox{e}}}^{n+1}$ quantity is then discretized in space and approximated
by $u^{n+1}$.

## Space discretization

We now introduce a finite element approximation to ${u_{\small\mbox{e}}}^n$ and ${u_{\small\mbox{e}}}^{n+1}$
in ([7](#fem:deq:diffu:FE:eq:uex:n)), where the coefficients depend on the
time level:

$$
\begin{equation}
{u_{\small\mbox{e}}}^n \approx u^n = \sum_{j=0}^{N} c_j^{n}{\psi}_j(\boldsymbol{x}),
\label{fem:deq:diffu:femapprox:n} \tag{8}
\end{equation}
$$

$$
\begin{equation}
{u_{\small\mbox{e}}}^{n+1} \approx u^{n+1} = \sum_{j=0}^{N} c_j^{n+1}{\psi}_j(\boldsymbol{x})
\label{fem:deq:diffu:femapprox:np1} \tag{9}
{\thinspace .}
\end{equation}
$$

Note that, as before, $N$ denotes the number of degrees of freedom
in the spatial domain. The number of time points is denoted by $N_t$.
We define a space $V$ spanned by the basis functions $\left\{ {{\psi}}_i \right\}_{i\in{\mathcal{I}_s}}$.

## Variational forms

A Galerkin method or a
weighted residual method with weighting functions $w_i$ can
now be formulated. We insert ([8](#fem:deq:diffu:femapprox:n)) and
([9](#fem:deq:diffu:femapprox:np1)) in
([7](#fem:deq:diffu:FE:eq:uex:n)) to obtain the residual

$$
R = u^{n+1} - u^n - \Delta t \left( {\alpha}\nabla^2 u^n + f(\boldsymbol{x}, t_n)\right)
{\thinspace .}
$$

The weighted residual principle,

$$
\int_\Omega Rw{\, \mathrm{d}x} = 0,\quad \forall w\in W,
$$

results in

$$
\int_\Omega
\left\lbrack
u^{n+1} - u^n - \Delta t \left( {\alpha}\nabla^2 u^n + f(\boldsymbol{x}, t_n)\right)
\right\rbrack w {\, \mathrm{d}x} =0, \quad\forall w \in W{\thinspace .}
$$

From now on we use the Galerkin method so $W=V$.
Isolating the unknown $u^{n+1}$ on the left-hand side gives

$$
\int_{\Omega} u^{n+1}v{\, \mathrm{d}x} = \int_{\Omega}
\left\lbrack u^n + \Delta t \left( {\alpha}\nabla^2 u^n + f(\boldsymbol{x}, t_n)\right)
\right\rbrack v{\, \mathrm{d}x},\quad \forall v\in V
{\thinspace .}
$$

As usual in spatial finite element problems involving second-order
derivatives, we apply integration by parts on the term
$\int (\nabla^2 u^n)v{\, \mathrm{d}x}$:

$$
\int_{\Omega}{\alpha}(\nabla^2 u^n)v {\, \mathrm{d}x} =
-\int_{\Omega}{\alpha}\nabla u^n\cdot\nabla v{\, \mathrm{d}x} +
\int_{\partial\Omega}{\alpha}\frac{\partial u^n}{\partial n}v {\, \mathrm{d}x}
{\thinspace .}
$$

The last term vanishes because we have the Neumann condition
$\partial u^n/\partial n=0$ for all $n$. Our discrete problem in
space and time then reads

$$
\begin{equation}
\int_{\Omega} u^{n+1}v{\, \mathrm{d}x} =
\int_{\Omega} u^n v{\, \mathrm{d}x} -
\Delta t \int_{\Omega}{\alpha}\nabla u^n\cdot\nabla v{\, \mathrm{d}x} +
\Delta t\int_{\Omega}f^n v{\, \mathrm{d}x},\quad \forall v\in V{\thinspace .}
\label{fem:deq:diffu:FE:vf:u:np1} \tag{10}
\end{equation}
$$

This is the variational formulation of our recursive set of spatial
problems.

**Nonzero Dirichlet boundary conditions.**

As in stationary problems,
we can introduce a boundary function $B(\boldsymbol{x},t)$ to take care
of nonzero Dirichlet conditions:

$$
\begin{equation}
{u_{\small\mbox{e}}}^n \approx u^n = B(\boldsymbol{x},t_n) + \sum_{j=0}^{N} c_j^{n}{\psi}_j(\boldsymbol{x}),
\label{fem:deq:diffu:femapprox:n:B} \tag{11}
\end{equation}
$$

$$
\begin{equation}
{u_{\small\mbox{e}}}^{n+1} \approx u^{n+1} = B(\boldsymbol{x},t_{n+1}) +
\sum_{j=0}^{N} c_j^{n+1}{\psi}_j(\boldsymbol{x})
\label{fem:deq:diffu:femapprox:np1:B} \tag{12}
{\thinspace .}
\end{equation}
$$

## Notation for the solution at recent time levels

In a program it is only necessary to have the two variables $u^{n+1}$
and $u^n$ at the same time at a given time step.  It is therefore
unnatural to use the index $n$ in computer code. Instead a natural
variable naming is `u` for $u^{n+1}$, the new unknown, and `u_n` for
$u^n$, the solution at the previous time level.  When we have several
preceding (already computed) time levels, it is natural to number them
like `u_nm1`, `u_nm2`, `u_nm3`, etc., backwards in time, corresponding to
$u^{n-1}$, $u^{n-2}$, and $u^{n-3}$. Essentially, this means a one-to-one
mapping of notation in mathematics and software, except for $u^{n+1}$.
We shall therefore, to make the distance between mathematics and code
as small as possible, often introduce just $u$ for $u^{n+1}$ in the
mathematical notation. Equation
([10](#fem:deq:diffu:FE:vf:u:np1)) with this new naming convention is
consequently expressed as

$$
\begin{equation}
\int_{\Omega} u v{\, \mathrm{d}x} =
\int_{\Omega} u^{n} v{\, \mathrm{d}x} -
\Delta t \int_{\Omega}{\alpha}\nabla u^{n}\cdot\nabla v{\, \mathrm{d}x} +
\Delta t\int_{\Omega}f^n v{\, \mathrm{d}x}
{\thinspace .}
\label{fem:deq:diffu:FE:vf:u} \tag{13}
\end{equation}
$$

This variational form can alternatively be expressed by the inner
product notation:

$$
\begin{equation}
(u,v) = (u^{n},v) -
\Delta t ({\alpha}\nabla u^{n},\nabla v) +
\Delta t (f^n, v)
{\thinspace .}
\label{fem:deq:diffu:FE:vf:u:short} \tag{14}
\end{equation}
$$

To simplify the notation for the solution at recent previous time steps
and avoid notation like `u_nm1`, `u_nm2`, `u_nm3`, etc.,  we will let $u_1$ denote the solution at previous time step,
$u_2$ is the solution two time steps ago, etc.

## Deriving the linear systems

In the following, we adopt the previously introduced convention that
the unknowns $c_j^{n+1}$ are written as $c_j$, while the known $c_j^n$
from the previous time level is simply written as $c_{j}^n$.  To
derive the equations for the new unknown coefficients $c_j$, we insert

$$
u = \sum_{j=0}^{N}c_j{\psi}_j(\boldsymbol{x}),\quad
u^{n} = \sum_{j=0}^{N} c_{j}^n{\psi}_j(\boldsymbol{x})
$$

in ([13](#fem:deq:diffu:FE:vf:u)) or ([14](#fem:deq:diffu:FE:vf:u:short)),
let the equation hold for all $v={\psi}_i$, $i=0,\ldots,N$,
and order the terms as matrix-vector products:

$$
\begin{equation}
\sum_{j=0}^{N} ({\psi}_i,{\psi}_j) c_j =
\sum_{j=0}^{N} ({\psi}_i,{\psi}_j) c_{j}^n
-\Delta t \sum_{j=0}^{N} (\nabla{\psi}_i,{\alpha}\nabla{\psi}_j) c_{j}^n
+ \Delta t (f^n,{\psi}_i),\quad i=0,\ldots,N
{\thinspace .}
\label{_auto1} \tag{15}
\end{equation}
$$

This is a linear system $\sum_j A_{i,j}c_j = b_i$ with

$$
A_{i,j} = ({\psi}_i,{\psi}_j)
$$

and

$$
b_i = \sum_{j=0}^{N} ({\psi}_i,{\psi}_j) c_{j}^n
-\Delta t \sum_{j=0}^{N} (\nabla{\psi}_i,{\alpha}\nabla{\psi}_j) c_{j}^n
+ \Delta t (f^n,{\psi}_i){\thinspace .}
$$

It is instructive and convenient for implementations to write the linear
system on the form

$$
\begin{equation}
Mc = Mc_1 - \Delta t Kc_1 + \Delta t f,
\label{_auto2} \tag{16}
\end{equation}
$$

where

$$
\begin{align*}
M &= \{M_{i,j}\},\quad M_{i,j}=({\psi}_i,{\psi}_j),\quad i,j\in{\mathcal{I}_s},\\
K &= \{K_{i,j}\},\quad K_{i,j}=(\nabla{\psi}_i,{\alpha}\nabla{\psi}_j),
\quad i,j\in{\mathcal{I}_s},\\
f &= \{f_i\},\quad f_i=(f(\boldsymbol{x},t_n),{\psi}_i),\quad i\in{\mathcal{I}_s},\\
c &= \{c_i\},\quad i\in{\mathcal{I}_s},\\
c_1 &= \{c_{i}^n\},\quad i\in{\mathcal{I}_s}
{\thinspace .}
\end{align*}
$$

We realize that $M$ is the matrix arising from a term with the
zero-th derivative of $u$, and called the mass matrix, while $K$ is
the matrix arising from a Laplace term $\nabla^2 u$. The $K$ matrix
is often known as the *stiffness matrix*. (The terms mass and stiffness
stem from the early days of finite elements when applications to
vibrating structures dominated. The mass matrix arises from the
mass times acceleration term in Newton's second law, while the stiffness
matrix arises from the elastic forces (the "stiffness") in that law.
The mass and stiffness
matrix appearing in a diffusion have slightly different mathematical
formulas compared to the classic structure problem.)

**Remark.** The mathematical symbol $f$ has two meanings, either the
function $f(\boldsymbol{x},t)$ in the PDE or the $f$ vector in the linear system
to be solved at each time level.

## Computational algorithm

We observe that $M$ and $K$ can be precomputed so that we can avoid
computing the matrix entries at every time level. Instead, some
matrix-vector multiplications will produce the linear system to be solved.
The computational algorithm has the following steps:

1. Compute $M$ and $K$.

2. Initialize $u^0$ by interpolation or projection

3. For $n=1,2,\ldots,N_t$:

a. compute $b = Mc_1 - \Delta t Kc_1 + \Delta t f$

b. solve $Mc = b$

c. set $c_1 = c$

In case of finite element basis functions, interpolation of the
initial condition at the nodes means $c_{j}^n = I(\boldsymbol{x}_j)$. Otherwise
one has to solve the linear system

$$
\sum_j{\psi}_j(\boldsymbol{x}_i)c_{j}^n = I(\boldsymbol{x}_i),
$$

where $\boldsymbol{x}_i$ denotes an interpolation point.  Projection
(or Galerkin's method) implies solving a linear system with $M$ as
coefficient matrix:

$$
\sum_j M_{i,j}c_{j}^n = (I,{\psi}_i),\quad i\in{\mathcal{I}_s}{\thinspace .}
$$

## Example using cosinusoidal basis functions

Let us go through a computational example and demonstrate the
algorithm from the previous section. We consider a 1D problem

$$
\begin{equation}
\frac{\partial u}{\partial t} = {\alpha}\frac{\partial^2 u}{\partial x^2},\quad
x\in (0,L),\ t\in (0,T],
\label{fem:deq:diffu:pde1D:eq} \tag{17}
\end{equation}
$$

$$
\begin{equation}
u(x, 0)  = A\cos(\pi x/L) + B\cos(10\pi x/L),\quad x\in[0,L],
\label{fem:deq:diffu:pde1D:ic} \tag{18}
\end{equation}
$$

$$
\begin{equation}
\frac{\partial u}{\partial x} = 0,\quad x=0,L,\ t\in (0,T]
\label{fem:deq:diffu:pde1D:bcN} \tag{19}
{\thinspace .}
\end{equation}
$$

We use a Galerkin method with basis functions

$$
{\psi}_i = \cos(i\pi x/L){\thinspace .}
$$

These basis functions fulfill ([19](#fem:deq:diffu:pde1D:bcN)), which is
not a requirement (there are no Dirichlet conditions in this problem),
but helps to make the approximation good.

Since the initial condition ([18](#fem:deq:diffu:pde1D:ic)) lies in the
space $V$ where we seek the approximation, we know that a Galerkin or
least squares approximation of the initial condition becomes exact.
Therefore, the initial condition can be expressed as

$$
c_{1}^n=A,\quad c_{10}^n=B,
$$

while $c_{i}^n=0$ for $i\neq 1,10$.

The $M$ and $K$ matrices are easy to compute since the basis functions
are orthogonal on $[0,L]$. Hence, we
only need to compute the diagonal entries. We get

$$
M_{i,i} = \int_0^L  cos^2(i x \pi/L) {\, \mathrm{d}x},
$$

which is computed as

```python
import sympy as sym
x, L = sym.symbols('x L')
i = sym.symbols('i', integer=True)
sym.integrate(sym.cos(i*x*sym.pi/L)**2, (x,0,L))
```

which means $L$ if $i=0$ and $L/2$ otherwise. Similarly,
the diagonal entries of the $K$ matrix are computed as

```python
sym.integrate(sym.diff(cos(i*x*sym.pi/L),x)**2, (x,0,L))
```

so

$$
M_{0,0}=L,\quad M_{i,i}=L/2,\ i>0,\quad K_{0,0}=0,\quad K_{i,i}=\frac{\pi^2 i^2}{2L},\ i>0{\thinspace .}
$$

The equation system becomes

$$
\begin{align*}
Lc_0 &= Lc_{0}^0 - \Delta t \cdot 0\cdot c_{0}^0,\\
\frac{L}{2}c_i &= \frac{L}{2}c_{i}^n - \Delta t
\frac{\pi^2 i^2}{2L} c_{i}^n,\quad i>0{\thinspace .}
\end{align*}
$$

The first equation leads to $c_0=0$ for any $n$ since we start with $c_{0}^0=0$ and $K_{0,0}=0$.
The others imply

$$
c_i = (1-\Delta t (\frac{\pi i}{L})^2) c_{i}^n{\thinspace .}
$$

With the notation $c^n_i$ for $c_i$ at the $n$-th time level, we can apply
the relation above recursively and get

$$
c^n_i = (1-\Delta t (\frac{\pi i}{L})^2)^n c^0_i{\thinspace .}
$$

Since only two of the coefficients are nonzero at time $t=0$, we have
the closed-form discrete solution

$$
u^n_i = A(1-\Delta t (\frac{\pi}{L})^2)^n \cos(\pi x/L)
+ B(1-\Delta t (\frac{10\pi }{L})^2)^n \cos(10\pi x/L){\thinspace .}
$$

# Discretization in time by a Backward Euler scheme

## Time discretization

The Backward Euler scheme in time applied to our diffusion problem
can be expressed as follows using the finite difference operator notation:

$$
[D_t^- u = {\alpha}\nabla^2 u + f(\boldsymbol{x}, t)]^n
{\thinspace .}
$$

Here $[D_t^- u]^n\approx (u^{n}-u^{n-1})/\Delta t$.
Written out, and collecting the unknown $u^n$ on the left-hand side
and all the known terms on the right-hand side,
the time-discrete differential equation becomes

$$
\begin{equation}
u^{n} - \Delta t  {\alpha}\nabla^2 u^n  =
u^{n-1} + \Delta t f(\boldsymbol{x}, t_{n})
\label{fem:deq:diffu:BE:eq:un} \tag{22}
{\thinspace .}
\end{equation}
$$

From equation ([22](#fem:deq:diffu:BE:eq:un)) we can compute
$u^1,u^2,\dots,u^{N_t}$,
if we have a start $u^0=I$ from the initial condition.
However, ([22](#fem:deq:diffu:BE:eq:un)) is a partial differential
equation in space and needs a solution method based on discretization
in space. For this purpose we use an expansion as in
([8](#fem:deq:diffu:femapprox:n))-([9](#fem:deq:diffu:femapprox:np1)).

## Variational forms

Inserting ([8](#fem:deq:diffu:femapprox:n))-([9](#fem:deq:diffu:femapprox:np1))
in ([22](#fem:deq:diffu:BE:eq:un)), multiplying by any $v\in V$
(or ${\psi}_i\in V$),
and integrating by parts, as we did in the Forward Euler case, results
in the variational form

$$
\begin{equation}
\int_{\Omega} \left( u^{n}v
+ \Delta t {\alpha}\nabla u^n\cdot\nabla v\right){\, \mathrm{d}x}
= \int_{\Omega} u^{n-1}  v{\, \mathrm{d}x} +
\Delta t\int_{\Omega}f^n v{\, \mathrm{d}x},\quad\forall v\in V
\label{fem:deq:diffu:BE:vf:u:n} \tag{23}
{\thinspace .}
\end{equation}
$$

Expressed with $u$ for the unknown $u^n$ and $u^{n}$ for the previous
time level, as we have done before, the variational form becomes

$$
\begin{equation}
\int_{\Omega} \left( uv
+ \Delta t {\alpha}\nabla u\cdot\nabla v\right){\, \mathrm{d}x}
= \int_{\Omega} u^{n} v{\, \mathrm{d}x} +
\Delta t\int_{\Omega}f^n v{\, \mathrm{d}x},
\label{fem:deq:diffu:BE:vf:u} \tag{24}
\end{equation}
$$

or with the more compact inner product notation,

$$
\begin{equation}
(u,v) + \Delta t ({\alpha}\nabla u,\nabla v)
= (u^{n},v) +
\Delta t (f^n,v)
\label{fem:deq:diffu:BE:vf:u:short} \tag{25}
{\thinspace .}
\end{equation}
$$

## Linear systems

Inserting $u=\sum_j c_j{\psi}_i$ and $u^{n}=\sum_j c_{j}^n{\psi}_i$,
and choosing $v$ to be the basis functions ${\psi}_i\in V$,
$i=0,\ldots,N$, together with doing some algebra, lead
to the following linear system to be
solved at each time level:

$$
\begin{equation}
(M + \Delta t K)c = Mc_1 + \Delta t f,
\label{fem:deq:diffu:BE:vf:linsys} \tag{26}
\end{equation}
$$

where $M$, $K$, and $f$ are as in the Forward Euler case
and we use the previously introduced notation $c = \{c_i\}$ and $c_1 = \{c_{i}^n\}$.

This time we really have to solve a linear system at each time level.
The computational algorithm goes as follows.

1. Compute $M$, $K$, and $A=M + \Delta t K$

2. Initialize $u^0$ by interpolation or projection

3. For $n=1,2,\ldots,N_t$:

a. compute $b = Mc_1 + \Delta t f$

b. solve $Ac = b$

c. set $c_1 = c$

In case of finite element basis functions, interpolation of the
initial condition at the nodes means $c_{j}^n = I(\boldsymbol{x}_j)$. Otherwise
one has to solve the linear system $\sum_j{\psi}_j(\boldsymbol{x}_i)c_j =
I(\boldsymbol{x}_i)$, where $\boldsymbol{x}_i$ denotes an interpolation point.  Projection
(or Galerkin's method) implies solving a linear system with $M$ as
coefficient matrix: $\sum_j M_{i,j}c_{j}^n = (I,{\psi}_i)$,
$i\in{\mathcal{I}_s}$.

# Dirichlet boundary conditions

Suppose now that the boundary condition ([3](#fem:deq:diffu:bcN)) is
replaced by a mixed Neumann and Dirichlet condition,

$$
\begin{equation}
u(\boldsymbol{x},t) = u_0(\boldsymbol{x},t),\quad  \boldsymbol{x}\in\partial\Omega_D,
\label{_auto3} \tag{29}
\end{equation}
$$

$$
\begin{equation}
-{\alpha}\frac{\partial}{\partial n} u(\boldsymbol{x},t) = g(\boldsymbol{x},t),\quad
 \boldsymbol{x}\in\partial{\Omega}_N{\thinspace .}
\label{_auto4} \tag{30}
\end{equation}
$$

Using a Forward Euler discretization in time, the variational
form at a time level becomes

$$
\begin{equation}
\int\limits_\Omega u^{n+1}v{\, \mathrm{d}x} =
\int\limits_\Omega (u^n - \Delta t{\alpha}\nabla u^n\cdot\nabla v){\, \mathrm{d}x} +
\Delta t\int\limits_\Omega fv {\, \mathrm{d}x} -
\Delta t\int\limits_{\partial\Omega_N} gv{\, \mathrm{d}s},\quad \forall v\in V{\thinspace .}
\label{_auto5} \tag{31}
\end{equation}
$$

## Boundary function

The Dirichlet condition $u=u_0$ at $\partial\Omega_D$ can be incorporated
through a boundary function $B(\boldsymbol{x})=u_0(\boldsymbol{x})$ and demanding that the basis functions  ${\psi}_j=0$
at $\partial\Omega_D$. The expansion for $u^n$ is written as

$$
u^n(\boldsymbol{x}) = u_0(\boldsymbol{x},t_n) + \sum_{j\in{\mathcal{I}_s}}c_j^n{\psi}_j(\boldsymbol{x}){\thinspace .}
$$

Inserting this expansion in the variational formulation and letting it
hold for all test functions $v\in V$, i.e., all basis functions ${\psi}_i$ leads to the linear system

$$
\begin{align*}
\sum_{j\in{\mathcal{I}_s}} \left(\int\limits_\Omega {\psi}_i{\psi}_j{\, \mathrm{d}x}\right)
c^{n+1}_j &= \sum_{j\in{\mathcal{I}_s}}
\left(\int\limits_\Omega\left( {\psi}_i{\psi}_j -
\Delta t{\alpha}\nabla {\psi}_i\cdot\nabla{\psi}_j\right){\, \mathrm{d}x}\right) c_j^n - \\
&\quad  \int\limits_\Omega\left( u_0(\boldsymbol{x},t_{n+1}) - u_0(\boldsymbol{x},t_n)
+ \Delta t{\alpha}\nabla u_0(\boldsymbol{x},t_n)\cdot\nabla
{\psi}_i\right){\, \mathrm{d}x} \\
& \quad + \Delta t\int\limits_\Omega f{\psi}_i{\, \mathrm{d}x} -
\Delta t\int\limits_{\partial\Omega_N} g{\psi}_i{\, \mathrm{d}s},
\quad i\in{\mathcal{I}_s}{\thinspace .}
\end{align*}
$$

## Finite element basis functions

When using finite elements, each basis function ${\varphi}_i$ is associated
with a node $\boldsymbol{x}_{i}$. We have a collection of nodes
$\{\boldsymbol{x}_i\}_{i\in{I_b}}$ on the boundary $\partial\Omega_D$.
Suppose $U_k^n$ is the known
Dirichlet value at $\boldsymbol{x}_{k}$ at time $t_n$ ($U_k^n=u_0(\boldsymbol{x}_{k},t_n)$).
The appropriate boundary function is then

$$
B(\boldsymbol{x},t_n)=\sum_{j\in{I_b}} U_j^n{\varphi}_j{\thinspace .}
$$

The unknown coefficients $c_j$ are associated with the rest of the nodes,
which have numbers $\nu(i)$, $i\in{\mathcal{I}_s} = \{0,\ldots,N\}$. The basis
functions of $V$ are chosen as ${\psi}_i = {\varphi}_{\nu(i)}$, $i\in{\mathcal{I}_s}$,
and all of these vanish at the boundary nodes as they should.
The expansion for $u^{n+1}$ and $u^n$ become

$$
\begin{align*}
u^n &= \sum_{j\in{I_b}} U_j^n{\varphi}_j + \sum_{j\in{\mathcal{I}_s}}c_{j}^n{\varphi}_{\nu(j)},\\
u^{n+1} &= \sum_{j\in{I_b}} U_j^{n+1}{\varphi}_j +
\sum_{j\in{\mathcal{I}_s}}c_{j}{\varphi}_{\nu(j)}{\thinspace .}
\end{align*}
$$

The equations for the unknown coefficients $\left\{ {c}_j \right\}_{j\in{\mathcal{I}_s}}$ become

$$
\begin{align*}
\sum_{j\in{\mathcal{I}_s}} \left(\int\limits_\Omega {\varphi}_i{\varphi}_j{\, \mathrm{d}x}\right)
c_j &= \sum_{j\in{\mathcal{I}_s}}
\left(\int\limits_\Omega\left( {\varphi}_i{\varphi}_j -
\Delta t{\alpha}\nabla {\varphi}_i\cdot\nabla{\varphi}_j\right){\, \mathrm{d}x}\right) c_{j}^n
- \\
&\quad  \sum_{j\in{I_b}}\int\limits_\Omega\left( {\varphi}_i{\varphi}_j(U_j^{n+1} - U_j^n)
+ \Delta t{\alpha}\nabla {\varphi}_i\cdot\nabla
{\varphi}_jU_j^n\right){\, \mathrm{d}x} \\
&\quad + \Delta t\int\limits_\Omega f{\varphi}_i{\, \mathrm{d}x} -
\Delta t\int\limits_{\partial\Omega_N} g{\varphi}_i{\, \mathrm{d}s},
\quad i\in{\mathcal{I}_s}{\thinspace .}
\end{align*}
$$

## Modification of the linear system

Instead of introducing a boundary function $B$ we can work with
basis functions associated with all the nodes and incorporate the
Dirichlet conditions by modifying the linear system.
Let ${\mathcal{I}_s}$ be the index set that counts all the nodes:
$\{0,1,\ldots,N=N_n-1\}$. The
expansion for $u^n$ is then $\sum_{j\in{\mathcal{I}_s}}c^n_j{\varphi}_j$ and the
variational form becomes

$$
\begin{align*}
\sum_{j\in{\mathcal{I}_s}} \left(\int\limits_\Omega {\varphi}_i{\varphi}_j{\, \mathrm{d}x}\right)
c_j &= \sum_{j\in{\mathcal{I}_s}}
\left(\int\limits_\Omega\left( {\varphi}_i{\varphi}_j -
\Delta t{\alpha}\nabla {\varphi}_i\cdot\nabla{\varphi}_j\right){\, \mathrm{d}x}\right) c_{1,j}
 \\
&\quad + \Delta t\int\limits_\Omega f{\varphi}_i{\, \mathrm{d}x} -
\Delta t\int\limits_{\partial\Omega_N} g{\varphi}_i{\, \mathrm{d}s}{\thinspace .}
\end{align*}
$$

We introduce the matrices $M$ and $K$ with entries
$M_{i,j}=\int\limits_\Omega{\varphi}_i{\varphi}_j{\, \mathrm{d}x}$ and
$K_{i,j}=\int\limits_\Omega{\alpha}\nabla{\varphi}_i\cdot\nabla{\varphi}_j{\, \mathrm{d}x}$,
respectively.
In addition, we define the vectors $c$, $c_1$, and $f$ with
entries $c_i$, $c_{1,i}$, and
$\int\limits_\Omega f{\varphi}_i{\, \mathrm{d}x} - \int\limits_{\partial\Omega_N}g{\varphi}_i{\, \mathrm{d}s}$, respectively.
The equation system can then be written as

$$
\begin{equation}
Mc = Mc_1 - \Delta t Kc_1 + \Delta t f{\thinspace .}
\label{_auto6} \tag{32}
\end{equation}
$$

When $M$, $K$, and $f$ are assembled without paying attention to
Dirichlet boundary conditions, we need to replace equation $k$
by $c_k=U_k$ for $k$ corresponding to all boundary nodes ($k\in{I_b}$).
The modification of $M$ consists in setting $M_{k,j}=0$, $j\in{\mathcal{I}_s}$, and
the $M_{k,k}=1$. Alternatively, a modification that preserves
the symmetry of $M$ can be applied. At each time level one forms
$b = Mc_1 - \Delta t Kc_1 + \Delta t f$ and sets $b_k=U^{n+1}_k$,
$k\in{I_b}$, and solves the system $Mc=b$.

In case of a Backward Euler method, the system becomes
([26](#fem:deq:diffu:BE:vf:linsys)). We can write the system
as $Ac=b$, with $A=M + \Delta t K$ and $b = Mc_1 + f$.
Both $M$ and $K$ needs to be modified because of Dirichlet
boundary conditions, but the diagonal entries in $K$ should be
set to zero and those in $M$ to unity. In this way, for $k\in{I_b}$ we
have  $A_{k,k}=1$.
The right-hand side must read $b_k=U^n_k$ for $k\in{I_b}$ (assuming
the unknown is sought at time level $t_n$).

## Example: Oscillating Dirichlet boundary condition

We shall address the one-dimensional initial-boundary value problem

$$
\begin{equation}
u_t = ({\alpha} u_x)_x + f,\quad  x\in\Omega =[0,L],\ t\in (0,T],
\label{fem:deq:diffu:Dirichlet:ex:pde} \tag{33}
\end{equation}
$$

$$
\begin{equation}
u(x,0) = 0,\quad  x\in\Omega,
\label{fem:deq:diffu:Dirichlet:ex:uic} \tag{34}
\end{equation}
$$

$$
\begin{equation}
u(0,t) = a\sin\omega t,\quad  t\in (0,T],
\label{fem:deq:diffu:Dirichlet:ex:uL} \tag{35}
\end{equation}
$$

$$
\begin{equation}
u_x(L,t) = 0,\quad  t\in (0,T]{\thinspace .}
\label{fem:deq:diffu:Dirichlet:ex:uR} \tag{36}
\end{equation}
$$

A physical interpretation may be that $u$ is the temperature
deviation from a constant mean temperature in a body $\Omega$
that is subject to an oscillating temperature (e.g., day and
night, or seasonal, variations) at $x=0$.

We use a Backward Euler scheme in time and P1 elements of
constant length $h$ in space.
Incorporation of the Dirichlet condition at $x=0$ through
modifying the linear system at each time level means that we
carry out the computations as explained in the section [Discretization in time by a Backward Euler scheme](#fem:deq:diffu:BE) and get a system ([26](#fem:deq:diffu:BE:vf:linsys)).
The $M$ and $K$ matrices computed without paying attention to
Dirichlet boundary conditions become

$$
\begin{equation}
M = \frac{h}{6}
\left(
\begin{array}{cccccccccc}
2 & 1 & 0
&\cdots & \cdots & \cdots & \cdots & \cdots & 0 \\
1 & 4 & 1 & \ddots &   & &  & &  \vdots \\
0 & 1 & 4 & 1 &
\ddots & &  &  & \vdots \\
\vdots & \ddots &  & \ddots & \ddots & 0 &  & & \vdots \\
\vdots &  & \ddots & \ddots & \ddots & \ddots & \ddots & & \vdots \\
\vdots & &  & 0 & 1 & 4 & 1 & \ddots & \vdots \\
\vdots & & &  & \ddots & \ddots & \ddots &\ddots  & 0 \\
\vdots & & & &  &\ddots  & 1  & 4  & 1 \\
0 &\cdots & \cdots &\cdots & \cdots & \cdots  & 0 & 1 & 2
\end{array}
\right)
\label{_auto7} \tag{37}
\end{equation}
$$

and

$$
\begin{equation}
K = \frac{{\alpha}}{h}
\left(
\begin{array}{cccccccccc}
1 & -1 & 0 &\cdots & \cdots & \cdots & \cdots & \cdots & 0 \\
-1 & 2 & -1 & \ddots &   & &  & &  \vdots \\
0 & -1 & 2 & -1 & \ddots & &  &  & \vdots \\
\vdots & \ddots &  & \ddots & \ddots & 0 &  & & \vdots \\
\vdots &  & \ddots & \ddots & \ddots & \ddots & \ddots & & \vdots \\
\vdots & &  & 0 & -1 & 2 & -1 & \ddots & \vdots \\
\vdots & & &  & \ddots & \ddots & \ddots &\ddots  & 0 \\
\vdots & & & &  &\ddots  & -1  & 2  & -1 \\
0 &\cdots & \cdots &\cdots & \cdots & \cdots  & 0 & -1 & 1
\end{array}
\right)
\label{_auto8} \tag{38}
\end{equation}
$$

The right-hand side of the variational form contains  no source term ($f$) and no boundary term from the
integration by parts ($u_x=0$ at $x=L$ and we compute as if $u_x=0$ at
$x=0$ too) and we are therefore left with $Mc_1$. However, we must incorporate the Dirichlet boundary
condition $c_0=a\sin\omega t_n$. Let us assume that our numbering of nodes is such that
${\mathcal{I}_s} = \{0,1,\ldots,N=N_n-1\}$.
The Dirichlet condition can then be incorporated
  by ensuring that this is the
first equation in the linear system.
To this end,
the first row in $K$ and $M$ is set to zero, but the diagonal
entry $M_{0,0}$ is set to 1. The right-hand side is $b=Mc_1$,
and we set $b_0 = a\sin\omega t_n$.
We can write the complete linear system as

$$
\begin{equation}
c_0 = a\sin\omega t_n,
\label{_auto9} \tag{39}
\end{equation}
$$

$$
\begin{equation}
\frac{h}{6}(c_{i-1} + 4c_i + c_{i+1}) + \Delta t\frac{{\alpha}}{h}(-c_{i-1}
+2c_i - c_{i+1}) = \frac{h}{6}(c_{1,i-1} + 4c_{1,i} + c_{1,i+1}),
\label{_auto10} \tag{40}
\end{equation}
$$

$$
\qquad i=1,\ldots,N_n-2,\nonumber
$$

$$
\begin{equation}
\frac{h}{6}(c_{i-1} + 2c_i) + \Delta t\frac{{\alpha}}{h}(-c_{i-1}
+c_i) = \frac{h}{6}(c_{1,i-1} + 2c_{1,i}),
\label{_auto11} \tag{41}
\end{equation}
$$

$$
\qquad i=N_n-1{\thinspace .}\nonumber
$$

The Dirichlet boundary condition can alternatively be implemented
through a boundary function $B(x,t)=a\sin\omega t\,{\varphi}_0(x)$:

$$
u^n(x) = a\sin\omega t_n\,{\varphi}_0(x) +
\sum_{j\in{\mathcal{I}_s}} c_j{\varphi}_{\nu(j)}(x),\quad
\nu(j) = j+1{\thinspace .}
$$

Now, $N=N_n-2$ and the $c$ vector contains values of $u$ at nodes
$1,2,\ldots,N_n-1$. The right-hand side gets a contribution

$$
\begin{equation}
\int\limits_0^L \left(
a(\sin\omega t_n - \sin\omega t_{n-1}){\varphi}_0{\varphi}_i
- \Delta t{\alpha} a\sin\omega t_n\nabla{\varphi}_0\cdot\nabla{\varphi}_i\right){\, \mathrm{d}x}
{\thinspace .}
\label{fem:deq:diffu:Dirichlet:ex:bterm} \tag{42}
\end{equation}
$$


## Look Elsewhere Effect in 2-d

Kyle Cranmer, Nov 19, 2015

Based on
*Estimating the significance of a signal in a multi-dimensional search* by  Ofer Vitells and Eilam Gross http://arxiv.org/pdf/1105.4355v1.pdf

This is for the special case of a likelihood function of the form
$L(\mu, \nu_1, \nu_2)$ where $\mu$ is a single parameter of interest and
$\nu_1,\nu_2$ are two nuisance parameters that are not identified under the null.
For example, $\mu$ is the signal strength of a new particle and $\nu_1,\nu_2$ are the
unknown mass and width of the new particle. Under the null hypothesis, those parameters
don't mean anything... aka they "are not identified under the null" in the statistics jargon.
This introduces a 2-d look elsewhere effect.

The LEE correction in this case is based on

\begin{equation}
E[ \phi(A_u) ] = P(\chi^2_1 > u) + e^{-u/2} (N_1 + \sqrt{u} N_2) \,
\end{equation}
where
   * $A_u$ is the 'excursion set above level $u$ (eg. the set of parameter points in $(\nu_1,\nu_2)$ that have a -2 log-likelihood ratio greater than $u$ )
   * $\phi(A_u)$ is the Euler characteristic of the excursion set
   * $E[ \phi(A_u) ]$ is the expectation of the Euler characteristic of those excursion sets under the null
   * $P(\chi^2_1 > u)$ is the standard chi-square probability
   * and $N_1$ and $N_2$ are two coefficients that characterize the chi-square random field.

### structure of the notebook

The notebook is broken into two parts.
  * calculation of $N_1$ and $N_2$ based on $E[ \phi(A_u) ]$ at two different levels $u_1$ and $u_2$
  * calculation of LEE-corrected 'global p-value' given $N_1,N_2$

```python
%pylab inline --no-import-all

```

    Populating the interactive namespace from numpy and matplotlib

```python
from lee2d import *
```

### Test numerical solution to $N_1, N_2$  from the example in the paper

Usage: calculate n1,n2 based on expected value of Euler characteristic (calculated from toy Monte Carlo) at two different levels u1, u2. For example:
   * $u_1=0$ with $E[ \phi(A_{u=u_1})]=33.5 $
   * $u_2=1$ with $E[ \phi(A_{u=u_2})]=94.6 $

would lead to a call like this

```python
# An example from the paper
n1, n2 = get_coefficients(u1=0., u2=1., exp_phi_1=33.5, exp_phi_2=94.6)
print n1, n2
```

    32.5 122.945875625

```python
# reproduce Fig 5 from paper (the markers are read by eye)
u = np.linspace(5,35,100)
global_p = global_pvalue(u,n1,n2)
plt.plot(u, global_p)
plt.scatter(35,2.E-5) #from Fig5
plt.scatter(30,2.E-4) #from Fig5
plt.scatter(25,2.5E-3) #from Fig5
plt.scatter(20,2.5E-2) #from Fig5
plt.scatter(15,.3) #from Fig5
plt.xlabel('u')
plt.ylabel('P(max q > u)')
plt.semilogy()
```

### Check Euler characteristic from Fig 3 example in the paper

```python
#create Fig 3 of http://arxiv.org/pdf/1105.4355v1.pdf
a = np.zeros((7,7))
a[1,2]=a[1,3]=a[2,1]=a[2,2]=a[2,3]=a[2,4]=1
a[3,1]=a[3,2]=a[3,3]=a[3,4]=a[3,5]=1
a[4,1]=a[4,2]=a[4,3]=a[4,4]=1
a[5,3]=1
a[6,0]=a[6,1]=1
a=a.T
plt.imshow(a,cmap='gray',interpolation='none')
```

```python
#should be 2
calculate_euler_characteristic(a)
```

    2

### Try a big matrix

```python
#Fully filled, should be 1
randMatrix = np.zeros((100,100))+1
calculate_euler_characteristic(randMatrix)
```

    1

```python
# split in half vertically, should be 2
randMatrix[50,:]=0
plt.imshow(randMatrix,cmap='gray')
calculate_euler_characteristic(randMatrix)
```

```python
#split in half horizontally twice, should be 6
randMatrix[:,25]=0
randMatrix[:,75]=0
plt.imshow(randMatrix,cmap='gray')
calculate_euler_characteristic(randMatrix)
```

```python
#remove a hole from middle of one, should be 5
randMatrix[25:30,50:53]=0
plt.imshow(randMatrix,cmap='gray')
calculate_euler_characteristic(randMatrix)
```

```python
#remove a single pixel hole
randMatrix[75,50]=0
plt.imshow(randMatrix,cmap='gray')
```


## Introduction
Maximum likelihood estimation (MLE) is a way of estimating
the parameters of a statistic model given observation.
It is conducted to find the parameters that maximizing the
likelihood of observations under certain model distribution
assumptions. However, in many real life problems, we are
dealing with problems with parameters that are not directly
available to infer given the limited data we have, which
are called **hidden variables Z**. Many problems in the
areas of genomics involves dealing with hidden variables.
Typical examples in genomics are (i) inferring microbial
communities (Z: different communities) (ii) inferring the
ancestries of a set of population (Z: different ancestries) (iii)
inferring the cell type content from certain sequencing data
(Z: different cell types). Problems involving hidden variables
like these are typically hard to directly performing maximum
likelihood estimation.

### Motivating example
I find using an application to motivate the abstract math formular is more intuitive to understand. So before going to any math detail let's use the popular 1000 Genome dataset as an example. In the example dataset, we have ~1000 individuals (n) from difference ancestries, each individual has a total of ~13k carefully selected SNPs (m). Formally, let's denote it as **X**, which is a $ n \times m $ matrix with value $\{0, 1\}$, or the haplotype matrix. The objective is to learn, without given explictly, the ancestry of each individual in an unsupervised approach.

### EM algorithm
Let's assume we believe there are $K$ difference ancestries. Denote $\pmb{X}_{i,j}$ as the genotype data of individual $i$ at SNP $j$. Let's say the $j^{th}$ SNP in individual $i$ is passed by ancestry $k$, and ancestry group $k$ has $f_{k,j}$ chance to pass a value $1$ at SNP $j$ to the offspring. Let's denote $\pmb{Z}$ as a  ancestry matrix of each individual, so that $\pmb{Z}_{i,k} = 1$ means individual $i$ belongs to the ancestry group $k$. The prior distribution of $\pmb{Z}$ is determined a multivariate normal distribution characterized by $\pmb{\pi}$. (If you are wondering why we are making all these assumptions, please check my Li-Stephens HMM blog for more details).
Mathematically:
\begin{align}
     \pmb{Z}_{i}|\pmb{\pi} \overset{iid}{\sim} Mult(\pmb{\pi}) \\
    \pmb{X}_{i,j} | (\pmb{Z}_{i,k} = 1) \sim Ber(f_{j,k})
\end{align}

Given we have the ancestry information matrix $\pmb{Z}$, solving for parameter parameter $f_{k,j}$ is easy:

\begin{align}
    P(\pmb{X}, \pmb{Z} | \pmb{f},\pmb{\pi}) & = P(\pmb{X}| \pmb{Z}, \pmb{f}, \pmb{\pi}) P(\pmb{Z}|  \pmb{f},\pmb{\pi}) = \prod_{i=1}^N P(\pmb{X}_i| \pmb{Z}_i, \pmb{f},\pmb{\pi}) \\ &= \prod_{i=1}^N \prod_{k=1}^K \pi_{k}P(\pmb{X}_i| \pmb{Z}_{i,k} = 1)^{\mathbb{1}[\pmb{Z}_{i,k}=1]}
\end{align}
Which you can solve using Maximum Likelihood Estimation (MLE) easily. For simplicity, let's use $\pmb{\theta}$ to be the parameters we want to learn above. To rewrite the above equation in log form, we have
\begin{align}
    log(P(\pmb{X}, \pmb{Z} | \pmb{\theta}) & = \sum_{i=1}^n\sum_{k=1}^K \mathbb{1}[\pmb{Z}_{i,k}=1]log(\pi_k\prod_{j=1}^m p(x_{i,j} | f_{j,k}))
\end{align}

What makes it hard is when we do not observe the ancestry groups $\pmb{Z}$. Then we need to infer the probability of ancestry given the observation $\pmb{X}$: $P(\pmb{Z} | \pmb{X}, \pmb{\theta} )$. But this requres us to know $\pmb{\theta}$ first! This comes to the first step of EM algorithm: **initialize the parameters** as $\pmb{\theta}^{(0)}$. After we *cheat* the posterior $P(\pmb{Z} | \pmb{X}, \pmb{\theta}^{(0)} )$, we can form the complete data likelihood to learn the updated parameter $\pmb{\theta}$:
\begin{align*}
    & Q(\pmb{\theta}, \pmb{\theta}^{(0)}) = E_Z[log(p(\pmb{X}, \pmb{Z}| \theta))]
    \\&= \sum_{i=1}^n\sum_{k=1}^K P(z_k|x_{i}, \pmb{\theta}^{(0)})log(\pi_k\prod_{j=1}^m p(x_{i,j} | f_{j,k}))
\end{align*}

and the *cheated*  posterior can be learned as
\begin{align*}
    &P(\pmb{Z} | \pmb{X}, \pmb{\theta}^{(0)} ) =\frac{P(x_i| z_k, \pmb{\theta}^{(0)})P(z_k|\pmb{\theta}^{(0)})}{P(x_i| \pmb{\theta}^{(0)})} \\
    & = \frac{\pi_k \prod_{j=1}^mf_{i,j}^{x_{i,j}}(1-f_{i,j})^{1-x_{i,j}}}{\sum_{k=1}^K\pi_k \prod_{j=1}^mf_{i,j}^{x_{i,j}}(1-f_{i,j})^{1-x_{i,j}}}
\end{align*}

```python

```


# LASSO and Ridge Regression

This function shows how to use TensorFlow to solve lasso or ridge regression for $\boldsymbol{y} = \boldsymbol{Ax} + \boldsymbol{b}$

We will use the iris data, specifically: $\boldsymbol{y}$ = Sepal Length, $\boldsymbol{x}$ = Petal Width

```python
# import required libraries
import matplotlib.pyplot as plt
import sys
import numpy as np
import tensorflow as tf
from sklearn import datasets
from tensorflow.python.framework import ops
```

```python
# Specify 'Ridge' or 'LASSO'
regression_type = 'LASSO'
# regression_type = 'Ridge'
```

```python
# clear out old graph
ops.reset_default_graph()

# Create graph
sess = tf.Session()
```

## Load iris data

```python
# iris.data = [(Sepal Length, Sepal Width, Petal Length, Petal Width)]
iris = datasets.load_iris()
x_vals = np.array([x[3] for x in iris.data])
y_vals = np.array([y[0] for y in iris.data])
```

## Model Parameters

```python
# Declare batch size
batch_size = 50

# Initialize placeholders
x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32)
y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)

# make results reproducible
seed = 13
np.random.seed(seed)
tf.set_random_seed(seed)

# Create variables for linear regression
A = tf.Variable(tf.random_normal(shape=[1,1]))
b = tf.Variable(tf.random_normal(shape=[1,1]))

# Declare model operations
model_output = tf.add(tf.matmul(x_data, A), b)
```

## Loss Functions

**ヘビサイド関数を以下の様にして近似している**

\begin{equation}
        h = \frac{1}{1 + \exp\Big(-50\times(A-\text{constant})\Big)}
\end{equation}

```python
# Select appropriate loss function based on regression type

if regression_type == 'LASSO':
    # Declare Lasso loss function
    # Lasso Loss = L2_Loss + heavyside_step,
    # Where heavyside_step ~ 0 if A < constant, otherwise ~ 99
    lasso_param = tf.constant(0.9)
    heavyside_step = tf.truediv(1.,
                                tf.add(1.,
                                       tf.exp(
                                           tf.multiply(-50.,
                                                       tf.subtract(
                                                           A, lasso_param)))))
    regularization_param = tf.multiply(heavyside_step, 99.)
    loss = tf.add(
        tf.reduce_mean(tf.square(y_target - model_output)),
        regularization_param)
    print("Lasso loss:", loss.shape)

elif regression_type == 'Ridge':
    # Declare the Ridge loss function
    # Ridge loss = L2_loss + L2 norm of slope
    ridge_param = tf.constant(1.)
    ridge_loss = tf.reduce_mean(tf.square(A))
    loss = tf.expand_dims(
        tf.add(
            tf.reduce_mean(tf.square(y_target - model_output)),
            tf.multiply(ridge_param, ridge_loss)),
        0)  # expandするのはLassoとlossの形を同様にするため

else:
    print('Invalid regression_type parameter value', file=sys.stderr)
```

    Lasso loss: (1, 1)

## Optimizer

```python
# Declare optimizer
my_opt = tf.train.GradientDescentOptimizer(0.001)
train_step = my_opt.minimize(loss)
```

## Run regression

```python
# Initialize variables
init = tf.global_variables_initializer()
sess.run(init)

# Training loop
loss_vec = []
for i in range(1500):
    rand_index = np.random.choice(len(x_vals), size=batch_size)
    rand_x = np.transpose([x_vals[rand_index]])
    rand_y = np.transpose([y_vals[rand_index]])
    sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y})
    temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y})
    loss_vec.append(temp_loss[0])
    if (i+1)%300==0:
        print('Step #' + str(i+1) + ' A = ' + str(sess.run(A)) + ' b = ' + str(sess.run(b)))
        print('Loss = ' + str(temp_loss))
        print('\n')
```

    Step #300 A = [[0.77170753]] b = [[1.8249986]]
    Loss = [[10.26473]]

    Step #600 A = [[0.7590854]] b = [[3.2220633]]
    Loss = [[3.0629203]]

    Step #900 A = [[0.74843585]] b = [[3.9975822]]
    Loss = [[1.2322046]]

    Step #1200 A = [[0.73752165]] b = [[4.429741]]
    Loss = [[0.57872057]]

    Step #1500 A = [[0.7294267]] b = [[4.672531]]
    Loss = [[0.40874982]]

## Extract regression results

```python
# Get the optimal coefficients
[slope] = sess.run(A)
[y_intercept] = sess.run(b)

# Get best fit line
best_fit = []
for i in x_vals:
    best_fit.append(slope*i+y_intercept)
```

## Plot results

```python
%matplotlib inline
plt.style.use("ggplot")
# Plot the result
plt.plot(x_vals, y_vals, 'o', label='Data Points')
plt.plot(x_vals, best_fit, 'b-', label='Best fit line', linewidth=3)
plt.legend(loc='upper left')
plt.title('Sepal Length vs Pedal Width')
plt.xlabel('Pedal Width')
plt.ylabel('Sepal Length')
plt.show()

# Plot loss over time
plt.plot(loss_vec, 'k-')
plt.title(regression_type + ' Loss per Generation')
plt.xlabel('Generation')
plt.ylabel('Loss')
plt.show()
```

```python

```