```javascript
%%javascript
IPython.OutputArea.prototype._should_scroll = function(lines) {
    return false;
}
```

    <IPython.core.display.Javascript object>

```python
import numpy as np
import scipy as sp
import seaborn as sn
import pandas as pd
from scipy.stats import gaussian_kde
from sympy import *
import math
import matplotlib.pylab as pl
%matplotlib inline

import os
import hedfpy

from hedfpy.EDFOperator import EDFOperator
from hedfpy.HDFEyeOperator import HDFEyeOperator
from hedfpy.EyeSignalOperator import EyeSignalOperator, detect_saccade_from_data

sn.set(style="ticks")
import warnings
warnings.filterwarnings("ignore")

from PIL import Image
```

```python
# define an Impulse Response Function:
def pupil_IRF(timepoints, s=1.0/(10**26), n=10.1, tmax=0.93):

    """ pupil_IRF defines the IRF (response to a transient input) of the pupil.

    Parameters
    ----------
    t: IRF is defined with respect to 't'
    s: scaling factor
    n: sets the width
    tmax: sets the time to peak
    IRF_len: function is evaluated for t = [0:IRF_len]

    Returns
    -------
    y: IRF evaluated for t = [0:IRF_len]
    yprime: IRF first derivative evaluated for t = [0:IRF_len]

    """

    # in sympy:
    t = Symbol('t')
    y = ( (s) * (t**n) * (math.e**((-n*t)/tmax)) )
    yprime = y.diff(t)

    # lambdify:
    y = lambdify(t, y, "numpy")
    yprime = lambdify(t, yprime, "numpy")

    # evaluate:
    y = y(timepoints)
    yprime = yprime(timepoints)

    return (y, yprime)

# create the IRF:
sample_rate = 10
IRF_len = 3.0 # in seconds
timepoints = np.linspace(0,IRF_len,IRF_len*sample_rate)

IRF, IRF_prime = pupil_IRF(timepoints=timepoints)
IRF = IRF / IRF.std()
IRF_prime = IRF_prime / IRF_prime.std()

# plot the IRF:
fig = pl.figure(figsize = (15,5))
pl.plot(timepoints, IRF, color='r')
# pl.plot(timepoints, IRF_prime, color='g')
pl.legend(['IRF'])
pl.title('Impulse Response Function')
pl.xlabel('time (s)')
pl.ylabel('a.u.')
sn.despine(fig=fig, offset=10)
```

```python
# input:
duration = 35 # in seconds
times = np.array([1,6,11,11.5,16,16.5,21,22,26,27])
input_signal = np.zeros(duration * sample_rate)
for i in times:
    input_signal[int(i*sample_rate)] = 1

# convolve inputs with IRF:
convolved_signal = (sp.convolve(input_signal, IRF, 'full'))[:-(IRF.shape[0]-1)]

# let's add some noise:
convolved_signal_noise = convolved_signal + np.random.normal(0,0.25,len(convolved_signal))

timepoints = np.linspace(0,duration,duration*sample_rate)
# plot simulated convolved signal with noise:
fig = pl.figure()
pl.plot(timepoints, convolved_signal_noise)
for i in times:
    pl.axvline(i, color='r', alpha=0.5)
pl.legend(['pupil time series'], loc=1)
pl.title('simulated pupil time series, with measurement noise')
pl.xlabel('time (s)')
pl.ylabel('a.u.')
sn.despine(fig=fig, offset=10)
```

Let's epoch the data and compute mean response.

```python
times*sample_rate
```

    array([  10.,   60.,  110.,  115.,  160.,  165.,  210.,  220.,  260.,  270.])

```python
# times for epoching:
epoch_times = [0, 3] # in seconds

# mean response:
epochs = np.vstack([convolved_signal_noise[int(i)+int(epoch_times[0]*sample_rate):int(i)+int(epoch_times[1]*sample_rate)] for i in times*sample_rate])
mean_response = np.mean(epochs, axis=0)

# plot mean response versus IRF:
timepoints = np.linspace(0,3,3*sample_rate)
fig = pl.figure(figsize=(6,6))
timepoints = np.linspace(epoch_times[0],epoch_times[1],(epoch_times[1]-epoch_times[0])*sample_rate)
fig.add_subplot(211)
for data in epochs:
    pl.plot(timepoints,data, color='b')
pl.title('epoched responses')
pl.xlabel('time from event (s)')
pl.ylabel('a.u.')
sn.despine(fig=fig, offset=10)
fig.add_subplot(212)
pl.plot(timepoints, mean_response, color='b')
pl.plot(timepoints, IRF, color='r')
pl.legend(['mean epoched response', 'true response'])
pl.title('mean response')
pl.xlabel('time from event (s)')
pl.ylabel('a.u.')
fig.tight_layout()
sn.despine(fig=fig, offset=10)
```

This is obviously not good... what we need to do is to is correct for for overlap between temporally adjacent responses...
We'll have to try deconvolution.
First make a design matrix:

```python

# make design matrix:
nr_samples = 3 * sample_rate # here we define the length of the deconvolution response we're interested in (30 samples = 3-s in our case).
designMatrix = np.zeros((nr_samples, duration*sample_rate))
for i in (times*sample_rate):
    for j in range(int(nr_samples)):
        designMatrix[j,int(i+j)] = 1

# plot design matrix:
fig = pl.figure(figsize=(16,4))
pl.imshow(designMatrix, cmap='gray')
pl.xticks([0,nr_samples])
pl.title('design matrix')
pl.xlabel('nr samples')
pl.ylabel('length run')
```

Now let's do deconvolution.

For every regressor (in the above example, we have 30 regressors (the duration in samples of the response we are interested in) we want to find an associated scalar value (the "beta", $b$) that we can use to scale that particular regressor with, such that it best describes the measured data. In a deconvolution analysis, with a procedure called "multiple regression" we look for betas that minimimize the sum of squares of errors across all $k$ regressors in our design matrix at the same time.

To do so, we set up the following equation:

$$ b = (X'X)^{-1} X'y $$

In which,

- $b$ is a vector containing the betas (size: number of regressors; in the above example: 30). In the case of deconvolution, this vector is the actual deconvolved response to some input;
- $X$ is the design matrix (size: length measured time series x number of regressors);
- $y$ is the measured BOLD timeseries.

```python
# deconvolution:
designMatrix = np.mat(designMatrix).T
deconvolved_response = ((designMatrix.T * designMatrix).I * designMatrix.T) * np.mat(convolved_signal_noise).T
deconvolved_response = np.array(deconvolved_response)

# plot deconvoled response versus true response:
timepoints = np.linspace(0,3,3*sample_rate)
fig = pl.figure(figsize=(6,6))
fig.add_subplot(211)
pl.plot(timepoints,deconvolved_response, color='g')
pl.plot(timepoints,mean_response, color='b')
pl.xlim(xmax=3)
pl.ylim(ymin=-0.5, ymax=3.5)
pl.legend(['epoched response','deconvolved response'])
pl.title('deconvolved response')
pl.xlabel('time (s)')
pl.ylabel('a.u.')
sn.despine(fig=fig, offset=10)
fig.add_subplot(212)
pl.plot(timepoints,IRF, color='r')
pl.xlim(xmax=3)
pl.ylim(ymin=-0.5, ymax=3.5)
pl.legend(['true response'])
pl.xlabel('time from event (s)')
pl.ylabel('a.u.')
sn.despine(fig=fig, offset=10)
pl.tight_layout()
```

```python

```


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

```python
methodeA = Series([79.98, 80.04, 80.02, 80.04, 80.03, 80.03, 80.04,
79.97, 80.05, 80.03, 80.02, 80.00, 80.02])
print(methodeA.mean())
print(methodeA.std())
```

    80.02076923076923
    0.023965787580611863

```python
from scipy.stats import norm
np.random.seed(1)
methodeA_sim1 = Series(np.round(norm.rvs(size=6, loc=80, scale=0.02), 2))
methodeA_sim1
print(methodeA_sim1.mean())
print(methodeA_sim1.std())
```

    79.99333333333333
    0.028751811537128993

```python
np.random.seed(17)
for i in range(5):
    methodeA_sim1 = Series(np.round(norm.rvs(size=6, loc=80, scale=0.02), 2))
    print("Mittelwert:", np.round(methodeA_sim1.mean(), 3))
    print("Standardabw.:", np.round(methodeA_sim1.std(), 3))
    print()
```

    Mittelwert: 80.01
    Standardabw.: 0.027

    Mittelwert: 80.007
    Standardabw.: 0.02

    Mittelwert: 79.992
    Standardabw.: 0.028

    Mittelwert: 79.995
    Standardabw.: 0.016

    Mittelwert: 79.992
    Standardabw.: 0.013

```python
np.random.seed(463137)
methodeA_sim2 = Series(np.round(norm.rvs(size=6, loc=80, scale=0.02), 2))
methodeA_sim2
print(methodeA_sim2.mean())
print(methodeA_sim2.std())
```

    80.04
    0.019999999999998862

```python
np.random.seed(647)
methodeA_sim3 = Series(np.round(norm.rvs(size=13, loc=80, scale= 0.02), 2))
methodeA_sim3
print(methodeA_sim3.mean())
print(methodeA_sim3.std())
```

    79.99307692307691
    0.023588350014576355

```python
norm.cdf(x=499.57, loc=500, scale=1/np.sqrt(100))
```

    8.539905470989153e-06

```python
norm.cdf(x=171.54, loc=180, scale=10/np.sqrt(2))*100
```

    11.576543872967077

```python
print(1-norm.cdf(x=80.02, loc=80, scale=0.01))
print(norm.ppf(q=[0.025, 0.975], loc=80, scale=0.01/np.sqrt(13)))
print(norm.interval(alpha=0.95, loc=80, scale=0.01/np.sqrt(13)))
```

    0.022750131948200636
    [79.99456404 80.00543596]
    (79.9945640379659, 80.0054359620341)

```python
norm.ppf(q=0.025)
```

    -1.9599639845400545

```python
series = Series([5.9, 3.4, 6.6, 6.3, 4.2, 2.0, 6.0, 4.8, 4.2, 2.1, 8.7, 4.4, 5.1, 2.7, 8.5, 5.8, 4.9, 5.3, 5.5, 7.9])
print(series.mean())
print(series.std())
```

    5.215000000000001
    1.8838021462764578

```python
t.cdf(x=series.mean(), df=series.size-1, loc=5, scale=series.std()/np.sqrt(series.size))
```

    0.6921780567888249

```python
help(norm.cdf)
```

    Help on method cdf in module scipy.stats._distn_infrastructure:

    cdf(x, *args, **kwds) method of scipy.stats._continuous_distns.norm_gen instance
        Cumulative distribution function of the given RV.

        Parameters
        ----------
        x : array_like
            quantiles
        arg1, arg2, arg3,... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information)
        loc : array_like, optional
            location parameter (default=0)
        scale : array_like, optional
            scale parameter (default=1)

        Returns
        -------
        cdf : ndarray
            Cumulative distribution function evaluated at `x`

```python

```


_Tugas 3 - Analisis Sumber Daya Air oleh Taruma S.M. (25017046)_

# Tugas 3 - Analisis Sumber Daya Air

> Suatu saluran persegiempat mempunyai lebar $2.5\ meter$ dan mempunyai kemiringan dasar saluran sama dengan $1:400$.
Jika konstanta Chezy adalah 30 dalam satuan SI, tentukan kedalaman normal jika debit aliran adalah $0.80\ m^3/detik$. Tentukan solusi dengan menggunakan **Metoda Interval Halving, Newton Rhapson, dan Secant**.

> Petunjuk: $Q = AC\sqrt{RS},\ A=by_n, \text{dan}\ \large{y_{n1} = y_{n0}-\frac{f(y_{n0})}{f'(y_{n0})}}$, untuk harga awal $y_{n0}$ ditentukan dengan asumsi kecepatan aliran $v = 1 m/detik$.

```python
# Ignore this block code.
# Define Function for printing result
def cetak(name, val, unit = ""):
    print('| {:>15s} = {:>10.5f} {:<{n}s}|'.format(name, val, unit, n = pad-15-10-7))

def new_line(pad = 50):
    print('='*pad)

pad = 50
```

***
## Penurunan dan Penentuan Nilai Awal
### Fungsi dan Turunannya

Fungsi $f(y)$ diperoleh dari:

$$\begin{aligned}
Q &= A C \sqrt{R S} &\leftrightarrow 0 = A C \sqrt{R S} - Q \\
f(y) &= A C \sqrt{R S} - Q = 0 &
\end{aligned}$$

Turunan dari fungsi $f(y)$:

$$\begin{aligned}f(y) &= C b y \sqrt{\frac{b s y}{b + 2 y}} - Q \\
f'(y) &= C b \sqrt{\frac{b s y}{b + 2 y}} + \frac{C}{s} \sqrt{\frac{b s y}{b + 2 y}} \left(b + 2 y\right) \left(- \frac{b s y}{\left(b + 2 y\right)^{2}} + \frac{b s}{2 \left(b + 2 y\right)}\right) \end{aligned}$$
Disederhanakan, $f'(y)$ menjadi:
$$\begin{aligned} f'(y) &= \frac{C b \sqrt{\frac{b s y}{b + 2 y}}}{2 \left(b + 2 y\right)} \left(3 b + 4 y\right) \end{aligned}$$

> Catatan : Hasil turunan diperoleh dari kode dibawah menggunakan _sympy_.

```python
# Mencari turunan fungsi menggunakan library sympy
from sympy import symbols, simplify, sqrt, latex

sb, sy, sC, sS, sQ = symbols('b, y, C, s, Q')
sA = sb * sy
sP = sb + 2* sy
sR = sA/sP
sfy = sA*sC*sqrt(sR*sS) - sQ

# Mencari Turunan f(y)
print('Persamaan Q: \n\tf(y)=', sfy, '\n==\n In Latex:', latex(sfy), '\n')
sfy_d = sfy.diff(sy)
print('Turunan Persamaan Q terhadap y : \n\tf\'(y)=', sfy_d, '\n==\n In Latex:', latex(sfy_d), '\n')
print('Bentuk sederhana : \n\tf\'(y)=', simplify(sfy_d), '\n==\n In Latex:', latex(simplify(sfy_d)), '\n')

# Mendefinisikan fungsi dalam python
def f(y):
    return float(sfy.subs({sS: S, sC: C, sb: b, sy: y, sQ: Q}))

def fd(y):
    return float(sfy_d.subs({sS: S, sC: C, sb: b, sy: y}))

```

    Persamaan Q:
    	f(y)= C*b*y*sqrt(b*s*y/(b + 2*y)) - Q
    ==
     In Latex: C b y \sqrt{\frac{b s y}{b + 2 y}} - Q

    Turunan Persamaan Q terhadap y :
    	f'(y)= C*b*sqrt(b*s*y/(b + 2*y)) + C*sqrt(b*s*y/(b + 2*y))*(b + 2*y)*(-b*s*y/(b + 2*y)**2 + b*s/(2*(b + 2*y)))/s
    ==
     In Latex: C b \sqrt{\frac{b s y}{b + 2 y}} + \frac{C}{s} \sqrt{\frac{b s y}{b + 2 y}} \left(b + 2 y\right) \left(- \frac{b s y}{\left(b + 2 y\right)^{2}} + \frac{b s}{2 \left(b + 2 y\right)}\right)

    Bentuk sederhana :
    	f'(y)= C*b*sqrt(b*s*y/(b + 2*y))*(3*b + 4*y)/(2*(b + 2*y))
    ==
     In Latex: \frac{C b \sqrt{\frac{b s y}{b + 2 y}}}{2 \left(b + 2 y\right)} \left(3 b + 4 y\right)

***
### Menentukan nilai awal $y_{n0}$ dan $y_{n1}$

#### Nilai $y_{n0}$

Dengan mengasumsikan kecepatan aliran $v = 1\ m/det$, $y_{n0}$ dapat dicari dari persamaan $Q = VA$:

$$Q = VA \leftrightarrow Q = V\ (by_{n0})$$

Dimasukkan nilai $V = 1\ m/det$, $b = 2.5\ m$ dan $Q = 0.8\ m^3/det$, maka diperoleh nilai $y_{n0}$ sebesar:

$$\begin{aligned} V = 1\ m/det, b = 2.5\ m, Q = 0.8\ m^3/det &\rightarrow& Q &= V\ (b\ y_{n0}) \\
&& 0.8 &= 1\ (2.5\ y_{n0}) \\
&& y_{n0} &= \frac{0.8}{2.5} = 0.32\ m
\end{aligned}$$

#### Nilai $y_{n1}$

Nilai $y_{n1}$ diperoleh dari persamaan yang diberikan di petunjuk yaitu $y_{n1} = y_{n0}-\frac{f(y_{n0})}{f'(y_{n0})}$:

$$\begin{aligned} y_{n0} = 0.32\ m, f(y_{n0}) = -0.1942946, f'(y_{n0}) = 2.646344 &\rightarrow& y_{n1} &= y_{n0}-\frac{f(y_{n0})}{f'(y_{n0})} \\
&& y_{n1} &= 0.32 - \frac{-0.1942946}{2.646344} \\
&& y_{n1} &= 0.39342\ m
\end{aligned}$$

### Nilai $y_{n0}$ dan $y_{n1}$

Disimpulkan bahwa nilai $y_{n0} = 0.32\ m$ dan $y_{n1} = 0.39342\ m$. Dengan catatan:
-  Untuk Metoda Interval Halving, nilai batas kiri: $x_a = y_{n0}$ dan batas kanan: $x_b = y_{n1}$.
-  Untuk Metoda Newton-Rhapson, nilai awal: $x_k = y_{n1}$.
-  Untuk Metoda Secant, nilai $x_n = y_{n1}$ dan $x_{n-1} = y_{n0}$.

Catatan: Hasil diatas diperoleh dari perhitungan melalui python dibawah ini.

```python
# Diketahui:
b = 2.5 # (m) - Lebar Saluran
S = 1/400 # (m/m) - Kemiringan dasar Saluran
C = 30 # (..) - Chezy
Q = 0.80 # (m3/det) - Debit Aliran

# Asumsikan
V = 1 # (m3/det)

new_line(pad)
print('|{:^{n}s}|'.format('Diketahui', n=pad-2))
new_line(pad)
cetak('b', b, 'm')
cetak('S', S, 'm/m')
cetak('C', C)
cetak('Q', Q, 'm^3/det')
new_line(pad)

# Menentukan harga awal
# Q = V.A -> Q = V.b.y -> y = Q/(V.b)
yn0 = Q/(V*b)
fy, fdy = f(yn0), fd(yn0)
yn1 = yn0 - fy/fdy

print()
new_line(pad)
print('|{:^{n}s}|'.format('Mencari nilai y_{n0} dan y_{n1}', n=pad-2))
new_line(pad)
cetak('y_{n0}', yn0, 'm')
cetak('f(y_{n0})',fy)
cetak('f\'(y_{n0})',fdy)
cetak('y_{n1}', yn1, 'm')
new_line(pad)
```

    ==================================================
    |                   Diketahui                    |
    ==================================================
    |               b =    2.50000 m                 |
    |               S =    0.00250 m/m               |
    |               C =   30.00000                   |
    |               Q =    0.80000 m^3/det           |
    ==================================================

    ==================================================
    |        Mencari nilai y_{n0} dan y_{n1}         |
    ==================================================
    |          y_{n0} =    0.32000 m                 |
    |       f(y_{n0}) =   -0.19429                   |
    |      f'(y_{n0}) =    2.64634                   |
    |          y_{n1} =    0.39342 m                 |
    ==================================================

*******
## Penyelesaian Numerik (Interval Halving, Newton, Secant)

Kode diperoleh dari Latihan Soal Notebook [Interval-Halving, Newton-Rhapson, Secant (Minggu 15)](https://github.com/taruma/belajar-tsa/blob/master/ansis/Interval-Halving%2C%20Newton-Rhapson%2C%20Secant%20(Minggu%2015).ipynb) atau dapat dilihat dengan nbviewer [Interval-Halving, Newton-Rhapson, Secant (Minggu 15)](https://nbviewer.jupyter.org/github/taruma/belajar-tsa/blob/master/ansis/Interval-Halving%2C%20Newton-Rhapson%2C%20Secant%20%28Minggu%2015%29.ipynb). Dan dimodifikasi sesuai kebutuhan.

***
### Metode Interval Halving

#### Langkah Pengerjaan

Solusi menggunakan metode _Interval Halving_ dengan langkah sebagai berikut:
-  Nilai batas kiri dan kanan yang digunakan diperoleh dari perhitungan sebelumnya untuk mendapatkan nilai $y_{n0}$ dan $y_{n1}$.

$$\begin{aligned} &\text{Batas bawah/kiri: }& x_a &= y_{n0} &\text{Batas atas/kanan: }& x_b &=&\ y_{n1} \\
&& x_a &= 0.32\ m && x_b &=&\ 0.39342\ m \end{aligned}$$

-  Periksa nilai $f(x_a)$ dan $f(x_b)$ lebih kecil dari $0$. Langkah ini memastikan bahwa akar persamaannya berada di antara $x_a$ dan $x_b$. Dan diperoleh bahwa nilai akar-akarnya berada di antara $x_a$ dan $x_b$

$$\begin{aligned} f(x_a) &=\ -0.19429 ; f(x_b) &=\ 0.00704 \\
f(x_a)f(x_b) &< 0 &\rightarrow \text{OK} \end{aligned}$$

-  Cari nilai tengah $(x_h)$ yang merupakan titik tengah dari $x_a$ dan $x_b$:

$$x_h = \frac{x_a + x_b}{2}$$

-  Tentukan batas atas/bawah berikutnya. Nilai $x_h$ sebagai batas atas ketika $f(x_a)f(x_h)<0$ dan sebaliknya menjadi batas bawah ketika $f(x_b)f(x_h)<0$.

$$\begin{aligned} x_b \leftarrow x_h &: \text{if } f(x_a)f(x_h)<0 \text{ TRUE} \\
x_a \leftarrow x_h &: \text{if } f(x_h)f(x_b)<0 \text{ TRUE}
\end{aligned}$$

```python
x1, x2 = yn0, yn1
iterasi = 20
xa, xb = x1, x2

pad = 70
new_line(pad)
print('Periksa nilai akarnya berada diantara xa dan xb')
if f(xa)*f(xb) < 0:
    print('f(x_a) x f(x_b) < 0 === OK \n\tdengan nilai f(x_a) = {fxa:12.5f} dan f(x_b) = {fxb:12.5f}'.format(
        fxa=f(xa), fxb=f(xb)))
else:
    print('f(x_a) x f(x_b) < 0 === NOT OK \n\ttidak akan ditemukan akarnya diantara {xa} dan {xb}\n\tdengan nilai f(x_a) = {fxa} dan f(x_b) = {fxb}'.format(
        xa=xa, xb=xb, fxa=f(xa), fxb=f(xb)))
new_line(pad)
print('\n')

lebar_text = 12*7 + 8
print('='*lebar_text)
print('|{:^{n:d}s}|'.format('Solusi Numerik Metoda Interval Halving', n=lebar_text-2))
print('='*lebar_text)
print('| {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} |'.format(
    'n', 'x_a', 'x_b', 'f(x_a)', 'f(x_b)', 'x_h', 'f(x_h)', n=10))
print('='*lebar_text)

for i in range(1, iterasi+1):
    fxa, fxb = f(xa), f(xb)
    xh = (xa+xb)/2
    fxh = f(xh)

    print('| {:^{n:d}d} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} |'.format(
        i, xa, xb, f(xa), f(xb), xh, f(xh), n=10, f1=10, f2=7))
    if fxa*fxh < 0:
        xb = xh
    elif fxb*fxh < 0:
        xa = xh
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f} dengan hasil f(x_h) = {:15.10f}'.format(xh, f(xh)))
```

    ======================================================================
    Periksa nilai akarnya berada diantara xa dan xb
    f(x_a) x f(x_b) < 0 === OK
    	dengan nilai f(x_a) =     -0.19429 dan f(x_b) =      0.00704
    ======================================================================

    ============================================================================================
    |                          Solusi Numerik Metoda Interval Halving                          |
    ============================================================================================
    |     n      |    x_a     |    x_b     |   f(x_a)   |   f(x_b)   |    x_h     |   f(x_h)   |
    ============================================================================================
    |     1      |  0.3200000 |  0.3934200 | -0.1942946 |  0.0070430 |  0.3567100 | -0.0953224 |
    |     2      |  0.3567100 |  0.3934200 | -0.0953224 |  0.0070430 |  0.3750650 | -0.0445413 |
    |     3      |  0.3750650 |  0.3934200 | -0.0445413 |  0.0070430 |  0.3842425 | -0.0188469 |
    |     4      |  0.3842425 |  0.3934200 | -0.0188469 |  0.0070430 |  0.3888313 | -0.0059261 |
    |     5      |  0.3888313 |  0.3934200 | -0.0059261 |  0.0070430 |  0.3911256 |  0.0005525 |
    |     6      |  0.3888313 |  0.3911256 | -0.0059261 |  0.0005525 |  0.3899785 | -0.0026883 |
    |     7      |  0.3899785 |  0.3911256 | -0.0026883 |  0.0005525 |  0.3905521 | -0.0010683 |
    |     8      |  0.3905521 |  0.3911256 | -0.0010683 |  0.0005525 |  0.3908389 | -0.0002580 |
    |     9      |  0.3908389 |  0.3911256 | -0.0002580 |  0.0005525 |  0.3909822 |  0.0001472 |
    |     10     |  0.3908389 |  0.3909822 | -0.0002580 |  0.0001472 |  0.3909105 | -0.0000554 |
    |     11     |  0.3909105 |  0.3909822 | -0.0000554 |  0.0001472 |  0.3909464 |  0.0000459 |
    |     12     |  0.3909105 |  0.3909464 | -0.0000554 |  0.0000459 |  0.3909285 | -0.0000048 |
    |     13     |  0.3909285 |  0.3909464 | -0.0000048 |  0.0000459 |  0.3909374 |  0.0000206 |
    |     14     |  0.3909285 |  0.3909374 | -0.0000048 |  0.0000206 |  0.3909330 |  0.0000079 |
    |     15     |  0.3909285 |  0.3909330 | -0.0000048 |  0.0000079 |  0.3909307 |  0.0000016 |
    |     16     |  0.3909285 |  0.3909307 | -0.0000048 |  0.0000016 |  0.3909296 | -0.0000016 |
    |     17     |  0.3909296 |  0.3909307 | -0.0000016 |  0.0000016 |  0.3909302 | -0.0000000 |
    |     18     |  0.3909302 |  0.3909307 | -0.0000000 |  0.0000016 |  0.3909304 |  0.0000008 |
    |     19     |  0.3909302 |  0.3909304 | -0.0000000 |  0.0000008 |  0.3909303 |  0.0000004 |
    |     20     |  0.3909302 |  0.3909303 | -0.0000000 |  0.0000004 |  0.3909302 |  0.0000002 |
    ============================================================================================
    Maka diperoleh nilai akar-akarnya =   0.390930 dengan hasil f(x_h) =    0.0000001907

#### Solusi Numerik Metoda Interval Halving

Dengan menggunakan prosedur diatas dan dilakukan iterasi sebanyak $20$ kali, diperoleh bahwa nilai $y_n = 0.390930\ m$ dengan nilai $f(x_h) = 0.0000001907$. Dari tabel hasil perhitungan dibawah, dapat dilihat bahwa nilai akarnya sudah dapat ditemukan pada langkah ke $16$ jika error yang ditargetkan $\epsilon = 0.000001$.

***
### Metoda Newton-Rhapson

#### Langkah Pengerjaan

Solusi Numerik menggunakan metoda _Newton-Rhapson_ dimulai dari:

-  Nilai awal $x_k$ menggunakan nilai $y_{n0}$ maka $x_k = 0.32\ m$

-  Dalam metoda Newton-Rhapson diperlukan turunan dari fungsi $f(y)$. Persamaan yang digunakan:

$$\begin{aligned} f(y) &=& C b y \sqrt{\frac{b s y}{b + 2 y}} - Q \\
f'(y) &=& \frac{C b \sqrt{\frac{b s y}{b + 2 y}}}{2 \left(b + 2 y\right)} \left(3 b + 4 y\right)
\end{aligned}$$

dengan: $C = 30, b = 2.5\ m, s = \frac{1}{400}\ m/m$

-  Akar persamaan $x_{k+1}$ diperoleh dengan melakukan iterasi sebanyak $k$ dengan menggunakan persamaan:

$$x_{k+1} = x_k - \frac{f(x_k)}{f'(x_k)}$$

```python
# Defining function

iterasi = 10  # Iteration
xk = yn0  # x_k

lebar_text = 12*5 + 6
print('='*lebar_text)
print('|{:^{n:d}s}|'.format('Solusi Numerik Metoda Newton Rhapson', n=lebar_text-2))
print('='*lebar_text)
print('| {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} |'.format(
    'k', 'x_k', 'f(x_k)', 'f\'(x_k)', 'x_{k+1}', n=10))
print('='*lebar_text)

for i in range(1, iterasi+1):
    xk_p1 = xk - f(xk)/fd(xk)
    print('| {:^{n:d}d} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | '.format(i,
                                                                                xk, f(xk), fd(xk), xk_p1, n=10, f1=10, f2=6))
    xk = xk_p1
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f} dengan hasil f(x_k) = {:15.10f}'.format(xk, f(xk)))
```

    ==================================================================
    |              Solusi Numerik Metoda Newton Rhapson              |
    ==================================================================
    |     k      |    x_k     |   f(x_k)   |  f'(x_k)   |  x_{k+1}   |
    ==================================================================
    |     1      |  0.320000  | -0.194295  |  2.646344  |  0.393420  |
    |     2      |  0.393420  |  0.007043  |  2.831491  |  0.390933  |
    |     3      |  0.390933  |  0.000007  |  2.825843  |  0.390930  |
    |     4      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     5      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     6      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     7      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     8      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     9      |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    |     10     |  0.390930  |  0.000000  |  2.825838  |  0.390930  |
    ==================================================================
    Maka diperoleh nilai akar-akarnya =   0.390930 dengan hasil f(x_k) =    0.0000000000

#### Solusi Numerik Metoda Newton Rhapson

Dengan menggunakan prosedur diatas dan dilakukan iterasi sebanyak $10$ kali, diperoleh bahwa nilai $y_n = 0.390930\ m$ dengan nilai $f(x_k) = 0.0000000000$. Dari tabel hasil perhitungan diatas, dapat dilihat bahwa nilai akarnya sudah dapat ditemukan pada langkah ke $4$ jika error yang ditargetkan $\epsilon = 0.000001$.

***
### Metoda Secant

#### Langkah Pengerjaan

Solusi Numerik menggunakan metoda _Secant_ dimulai dari:

-  Menentukan nilai $x_0 = x_{n-1}$ dan $x_1 = x_n$ dari nilai $y_{n0}$ dan $y_{n1}$:
$$\begin{aligned}x_0 &= x_{n-1} &= y_{n0} &= 0.32\ m \\
x_1 &= x_{n} &= y_{n1} &= 0.39342\ m\end{aligned}$$

-  Dalam metoda _Secant_ hanya diperlukan fungsi $f(y)$. Persamaan yang digunakan:

$$\begin{aligned} f(y) &=& C b y \sqrt{\frac{b s y}{b + 2 y}} - Q \\
\end{aligned}$$

dengan: $C = 30, b = 2.5\ m, s = \frac{1}{400}\ m/m$

-  Akar persamaan $x_{n+1}$ diperoleh dengan melakukan iterasi sebanyak $n$ dengan menggunakan persamaan:

$$x_{n+1} = x_n - f(x_n)\frac{x_n - x_{n-1}}{f(x_n) - f(x_{n-1})}$$

```python
x0, x1 = yn1, yn0

lebar_text = 10*5+12+2*6+7
print('='*lebar_text)
print('|{:^{n:d}s}|'.format(
    'Solusi Numerik Menggunakan Metoda Secant', n=lebar_text-2))
print('='*(10*5+12+2*6+7))
print('| {:^10s} | {:^10s} | {:^10s} | {:^10s} | {:^10s} | {:^12s} |'.format(
    'n', 'x_{n-1}', 'x_n', 'N_n', 'D_n', 'x_{n+1}-x_n'))
print('='*(10*5+12+2*6+7))

iterasi = 6

xn = x1
xn_m1 = x0
xn_p1 = 0
for n in range(1, iterasi+1):
    Nn = f(xn)*(xn-xn_m1)
    Dn = f(xn)-f(xn_m1)
    xn_p1 = xn - Nn / Dn
    dx = xn_p1-xn

    print('| {:^10d} | {:^ 10.6f} | {:^ 10.6f} | {:^ 10.6f} | {:^ 10.6f} | {:^ 12.6f} |'.format(n, xn_m1, xn, Nn, Dn, dx))

    xn_m1 = xn
    xn = xn_p1

print('='*(10*5+12+2*6+7))
print('Maka diperoleh nilai akar-akarnya = {:10.6f} dengan f(x_{{n+1}}) = {:15.10f}'.format(xn, f(xn)))
```

    =================================================================================
    |                   Solusi Numerik Menggunakan Metoda Secant                    |
    =================================================================================
    |     n      |  x_{n-1}   |    x_n     |    N_n     |    D_n     | x_{n+1}-x_n  |
    =================================================================================
    |     1      |  0.393420  |  0.320000  |  0.014265  | -0.201338  |   0.070852   |
    |     2      |  0.320000  |  0.390852  | -0.000016  |  0.194073  |   0.000081   |
    |     3      |  0.390852  |  0.390933  |  0.000000  |  0.000229  |  -0.000002   |
    |     4      |  0.390933  |  0.390930  |  0.000000  | -0.000007  |   0.000000   |
    |     5      |  0.390930  |  0.390930  | -0.000000  |  0.000000  |   0.000000   |
    |     6      |  0.390930  |  0.390930  |  0.000000  |  0.000000  |   0.000000   |
    =================================================================================
    Maka diperoleh nilai akar-akarnya =   0.390930 dengan f(x_{n+1}) =    0.0000000000

#### Solusi Numerik Metoda _Secant_

Dengan menggunakan prosedur diatas dan dilakukan iterasi sebanyak $6$ kali, diperoleh bahwa nilai $y_n = 0.390930\ m$ dengan nilai $f(x_k) = 0.0000000000$. Dari tabel hasil perhitungan diatas, dapat dilihat bahwa nilai akarnya sudah dapat ditemukan pada langkah ke $4$ jika error yang ditargetkan $\epsilon = 0.000001$.

***
## Kesimpulan

Ringkasan dari penyelesaian permasalahan dengan 3 metoda yaitu _Interval-Halving, Newton-Rhapson, Secant_:

$$\begin{array}{|l|c|c|c|c|}
\hline
\text{Metoda} & \text{Jumlah Iterasi Coba} & y_{n0} & f(y_{n0}) & \text{Jumlah Iterasi jika } \epsilon = 0.000001 \\
\hline
\text{Interval-Halving} & 20 & 0.390930 & 0.0000001907 & 16 \\
\text{Newton-Rhapson} & 10 & 0.390930 & 0.0000000000 & 4 \\
\text{Secant} & 6 & 0.390930 & 0.0000000000 & 4 \\\hline
\end{array}
$$

Dari ketiga metoda diatas, metoda _Newton-Rhapson_ dan _Secant_ memiliki iterasi yang lebih sedikit dengan $\epsilon = 1\times10^{-6}$, akan tetapi metoda _Newton-Rhapson_ memerlukan persamaan turunan $f'(y)$ yang jika persamaannya akan sulit diturunkan jika dilakukan secara manual. Sedangkan metoda _Secant_ hanya menggunakan persamaan $f(y)$.

ansis/Tugas 3 - Taruma S. (25017046).ipynb


## Questionário - Semana 2

Leia o texto a seguir para responder o questionário.

### Modelos epidemiológicos SEIR

Modelos matemáticos epidemiológicos modernos funcionam com base em "estados" que são considerados como "compartimentos". Por exemplo, o modelo SEIR considera que uma população, quando ameaçada por uma epidemia, pode ser dividida em quatro compartimentos:

- S, das pessoas *suscetíveis* à infecção;
- E, das pessoas *expostas*, que embora hospedeiras, não espalham a doença pelo fato de o vírus ainda estar em incubação;
- I, das pessoas *infectadas* e
- R, das pessoas *recuperadas*

O número de pessoas $N$ em cada "compartimento" muda com o tempo. Logo, a dinâmica de mudança de estados ocorre por transições. Em cada instante de tempo, um fluxo de transição da forma
S -> E -> I -> R pode ocorrer. Isto é, pessoas suscetíveis podem contrair o vírus, em seguida tornar-se expostas, serem infectadas e, por sua vez, se recuperarem. Portanto, supondo que não haja mortalidade, $N$ deve se conservar ao longo do tempo, mas S, E, I e R podem variar com o tempo. Matematicamente, se $t$ é um instante de tempo, então:

$$N(t) = S(t) + E(t) + I(t) + R(t)$$

Um modelo SEIR busca prever o número de pessoas em cada estado em um instante de tempo futuro $t_N$ (dia N) a partir de um instante de tempo inicial $t_0$ (dia 0).

Considere que $a(t)$ seja o número de pessoas que uma pessoa infecta considerado para o dia $t$ – diante de um quadro epidêmico, desejamos que este valor seja o mais próximo de zero possível. Este valor pode se alterar para mais ou para menos mediante o relaxamento ou restrição de medidas de distanciamento social. A função matemática

$$a(t) = a_N + \dfrac{a_0 - a_N}{1 + e^{-k(L-t)}},$$

pode ser usada como um modelo para o cálculo de $a$ levando em consideração medidas drásticas de "lockdown". Na equação:

- $a_0$ é o valor de $a$ no dia 0
- $a_N$ é o valor de $a$ no dia N
- $L$ é o dia entre o dia 0 e o dia N em que o "lockdown" é decretado.
- $k$ é uma constante de ajuste no intervalo real $(0,1]$ que aumenta a velocidade de declínio de $a$ à medida que se aproxima de $1$.

A figura a seguir mostra o gráfico de $a(t)$ para $k = 0.2$, $k = 0.4$ e $k = 0.9$ em um intervalo de 100 dias, considerando $a_0 = 7$ e $a_{100} = 0.5$. O lockdown foi determinado no dia 40. Como se vê, quanto maior o valor de $k$, mais brusca é a queda do poder de contágio de uma pessoa.

Base: [[artigo]](https://towardsdatascience.com/infectious-disease-modelling-beyond-the-basic-sir-model-216369c584c4)

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

f = lambda a0,aN,L,k,t : aN + (a0 - aN)/(1 + np.exp(-k*(L-t)))

t = np.linspace(0,100)
plt.plot(t,f(7,0.5,40,0.2,t));
plt.plot(t,f(7,0.5,40,0.4,t));
plt.plot(t,f(7,0.5,40,0.9,t));
plt.legend(['$k=0.2$','$k=0.4$','$k=0.9$'])
plt.xlabel('$t$ [dias]'); plt.ylabel('$a(t)$'); plt.title('Lockdown no dia 40')
plt.annotate('$a_0 = 7; a_{100} = 0.5$',(75,5));
```

### Questão 1

Construa um modelo simbólico para $a(t)$ e o atribua à variavel `a`. Qual(is) alternativa(s) seguinte(s) pode(m) ser uma resposta esperada por `type(a)`, independentemente da construção?

a. sympy.core.add.Add

b. sympy.core.add.Mul

c. sympy.core.add.Exp

d. exp

### Resposta:

a e b são possíveis.

```python
import sympy as sy

# pode ser Add
t,k,L,a0,aN = sy.symbols('t k L a0 aN',positive=True)
a = aN + (a0 - aN)/(1 + sy.exp(-k*(L-t)))
print(type(a))

# pode ser Mul
am = (aN*(1 + sy.exp(-k*(L-t))) + (a0 - aN) )/(1 + sy.exp(-k*(L-t)))
print(type(am))
```

    <class 'sympy.core.add.Add'>
    <class 'sympy.core.mul.Mul'>

```python
a
```

$\displaystyle aN + \frac{a_{0} - aN}{1 + e^{- k \left(L - t\right)}}$

## Questão 2

Considere as seguintes situações:

1) A cidade A decretou lockdown 30 dias após o dia inicial de observação (dia 0) do contágio epidêmico, assumindo que $a_0 = 5$, $a_{100} = 1$ e modelou a curva $a(t)$ com $k = 0.2$.

2) A cidade B decretou lockdown 40 dias após o dia inicial de observação (dia 0) do contágio epidêmico, assumindo que $a_0 = 5$, $a_{100} = 1$ e modelou a curva $a(t)$ com $k = 0.1$.

Sendo $P_1$ e $P_2$ os valores de $a(t)$ calculados pelos modelos das cidades A e B, respectivamente, para o 21o. dia após o início da análise, o que podemos afirmar?

a. P1 > P2

b. P1 < P2

c. P1 = P2 > 0

d. P1 = P2 = 0

### Resposta

Resposta b.

```python
p1 = a.subs([ (k,0.2),(L,30),(t,21),(a0,5),(aN,1) ])
p2 = a.subs([ (k,0.1),(L,40),(t,21),(a0,5),(aN,1) ])

```

$\displaystyle -0.0469703621499598$

## Questão 3

Considere que a população de uma cidade acometida pela epidemia seja de 38.416 habitantes. No 16o. dia, a análise mostrou que o número de pessoas suscetíveis ao contágio era o triplo das que estavam já em exposição ao vírus. Estas, por sua vez, estavam em mesmo número que as infectadas no dia. As recuperadas contabilizavam exatamente 16.

O código abaixo, iniciado por um analista, buscou determinar o percentual de pessoas expostas em relação à população total da cidade no 16o. dia.

```python
from sympy.abc import N,S,E,I,R
from sympy import Eq, solveset
tot = Eq( S + E + I + R - N,0)
...
```

Ao completar este código, ou desenvolver o seu próprio, você pode concluir que este percentual é de:

a. maior do que 50%.
b. entre 30% e 45%.
c. aproximadamente 20%.
d. menor do que 10%.

### Resposta

Alternativa c)

```python
# Q3
# Dia 16

from sympy.abc import N,S,E,I,R
from sympy import Eq, solveset

tot = Eq( S + E + I + R - N,0)
tot = tot.subs(N,38416)
tot = tot.subs(S,3*E)
tot = tot.subs(I,E)
tot = tot.subs(R,16)
print(sy.solveset(tot,E))
7680/38416
```

    FiniteSet(7680)

    0.19991670137442732

## Questão 4

Algumas funções anônimas foram programadas na tentativa de representar o termo $e^{-k(L-t)}$ que aparece no denominador da fração do modelo de $a(t)$. Assinale a alternativa cuja definição expressa corretamente o termo, tendo em vista que `exp` é a exponencial simbólica:

```
a. fexp = lambda k,L,t: exp(-k(L-t))
b. fexp = lambda k,L-t: exp(-k(L-t))
c. fexp = lambda k,Lt: exp(-k*(Lt))
d. fexp = lambda k,L,t: exp(-k*(L-t))
```

### Resposta

Alternativa d)

```python
from sympy import exp

#fexp = lambda k,L,t: exp(-k(L-t))
#fexp = lambda k,L-t: exp(-k(L-t))
fexp = lambda k,L,t: exp(-k*(L-t))
#fexp = lambda k,Lt: exp(-k*(Lt))
fexp(k,L,t)
```

$\displaystyle e^{- k \left(L - t\right)}$

## Questão 5

Considere o seguinte excerto de código:

```python
from sympy.abc import S,E,I,R
SEIR = S*E*I*R
```

Podemos concluir que

$$\text{sen}( SEIR + SEIR^{-1} + 1/SEIR^{-1}) = \text{sen}(x + y),$$

onde

a. $x = 2SEIR; \ \ y = 1/SEIR$

b. $x = 3SEIR; \ \ y = 0$

c. $x = SEIR; \ \ y = 2/SEIR$

d. $x = SEIR; \ \ y = 1/SEIR$

### Resposta

Alternativa a).

```python
from sympy.abc import S,E,I,R
SEIR = S*E*I*R
sy.simplify(sy.expand_trig(sy.sin( SEIR + 1/SEIR + 1/(1/SEIR))))
```

$\displaystyle \sin{\left(2 E I R S + \frac{1}{E I R S} \right)}$


# Consumer optimization problem and Ramsey Model

Imports and set magics:

```python
import numpy as np
from scipy import optimize
from scipy import linalg
from scipy import interpolate
import sympy as sm
import mpmath as mp

# autoreload modules when code is run
%load_ext autoreload
%autoreload 2

# local modules
import modelproject
```

```python
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
```

## Intertemporal consumer optimization

First we consider consumer with **utility function** under period t and  where:

**utility** is given by $$u(c_t) = \frac{c^{1-\theta}}{1-\theta} $$.

suppose consumer can live two periods: today and tomorrow, with the discount factor $\beta$, consumer's lifelong utility is:

$$ u(c_0,c_1) = \frac{c_0^{1-\theta}}{1-\theta} + \beta \frac{c_1^{1-\theta}}{1-\theta} $$

the customer is trying to solve utility maximization problem with **intertemporal budet constraint**

$$ c_0 + \frac{c_1}{1+r} = w_0 + \frac{w_1}{1+r} $$

where $w_0$ is the income for today, $w_1$ is the income tomorrow, r is discount rate

```python
# define parameter values
theta = 0.4
beta = 0.7
w0 = 20
w1 = 10
r = 0.05
```

From FOC we have $$ c_0^{-\theta} = \beta(1+r)c_1^{-\theta} $$
$$ c_0 + \frac{c_1}{1+r} = w_0 + \frac{w_1}{1+r} $$

```python
# Define implicit functions using result from the FOC
def function_0(c0, c1):
    """

    Implicit function derived from the Euler equation.
    """
    return c0**(-theta)-beta*(1+r)*c1**(-theta)

def function_1(c0, c1):
    """

    Implicit function derived from the budget constraint.
    """
    return w0+(1/(1+r))*w1-c0-(1/(1+r))*c1

```

```python
# Fefine a function that optimize consumption bundle
def optimal_bundle(X):
    result = [function_0(X[0], X[1])]
    result.append(function_1(X[0], X[1]))
    return result

# Apply fsolve()...note that we need to give the algorithm a decent guess!
c0_initial, c1_initial = 1, 1
c0_star, c1_star = optimize.fsolve(func=optimal_bundle, x0=(c0_initial, c1_initial))
```

```python
print ("Optimal choice of c0 is:", c0_star)
print ("Optimal choice of c1 is:", c1_star)
```

    Optimal choice of c0 is: 20.487111357724327
    Optimal choice of c1 is: 9.48853307438946

## Ramsey Model

We consider the **Ramsey Model** from macro 3, in which we have sectors:

**Households** own capital, supply labor and consume
**Firm** give out capital and hire labor for production

there are 4 variables
**capital**: ${K_t}$
**labor**: ${L_t}$
**output**: ${Y_t}$
**consumption**: ${C_t}$

for individuals there are
output per capita ${y_t}$ = ${Y_t}$/ ${L_t}$, capital per capita ${k_t}$ = ${K_t}$/${L_t}$, consumption per capita ${c_t}$ = ${C_t}$/${L_t}$

**prices** are taken as given by households and firms
${r_t}$: capital rent rate
${w_t}$: wage rate

return on capital owned by households, depreciation rate $\delta$>0
$$ {R_t} = 1 + {r_t} - {\delta} $$

**Firms** production function is given by:
$$ Y_t = F(K_t,L_t) = f(k_t)L_t $$

Firm profit maximization:
$$ max_{k_t,l_t} f(k_t)L_t - r_tK_t - w_tL_t $$

With FOC
$$ f'(k_t) = r_t $$
$$ f(k_t) - f'(k_t)k_t = w_t $$

consider the case with **Cobb-Douglas Production** function:
$$ F(K_t,L_t) = AK^{\alpha}L^{1-\alpha} $$

$$f(k_t) = Ak_t^{\alpha} $$

and now $$ r_t = A{\alpha}k_t^{\alpha-1}$$, $$w_t=  A(1-\alpha)k_t^{\alpha} $$

**Households** maximize utility from consumption:
$$ max_{c_t} \sum_{t=0}^{\infty} \beta^t u(c_t), u'>0, u''<0 $$

under resource constraint
$$ k_{t+1} = R_tk_t + w_t - c_t $$

with given $k_0 $, $ ({R_t})_{t=0}^{\infty} $ and  $ ({w_t})_{t=0}^{\infty} $

euler equation $$\frac{u'(c_t)}{u'(c_{t+1})} = \beta{R_{t+1}} $$

set $\theta$ = 1 thus *CRRA utility* function becomes $$u(c_t) = \log( c_t )$$

## Analytical solution for Steady State

First we define all **symbols**:

```python
u = sm.symbols('u')
c0 = sm.symbols('c_t')
c1 = sm.symbols('c_{t+1}')
beta = sm.symbols('beta')
R = sm.symbols('R_{t+1}')
r = sm.symbols('r_t')
delta = sm.symbols('delta')
A = sm.symbols('A')
alpha = sm.symbols('alpha')
k = sm.symbols('k_t')
w = sm.symbols('w_t')
k_star = sm.symbols('k^*')
c_star = sm.symbols('c^*')

r = A*alpha* k **(alpha-1)
w = A*(1-alpha)*k**alpha
R = 1 + r - delta
k1 = R*k + w - c0
```

utility function of 2 time period

```python
u0 = sm.log(c0)
u1 = sm.log(c1)
```

derive euler equation

```python
euler = sm.Eq(sm.diff(u0,c0)/sm.diff(u1,c1), beta*R)
euler
```

$\displaystyle \frac{c_{t+1}}{c_{t}} = \beta \left(A \alpha k_{t}^{\alpha - 1} - \delta + 1\right)$

solve euler equation

```python
sol = sm.solve(euler, c1)
sm.Eq(c1,sol[0])
```

$\displaystyle c_{t+1} = \beta c_{t} \left(A \alpha k_{t}^{\alpha - 1} - \delta + 1\right)$

when under steady state $c_t = c_{t+1}$, $k_t = k_{t+1}$, thus

```python
c_steadystate = sm.Eq(sol[0],c0)
c_steadystate
```

$\displaystyle \beta c_{t} \left(A \alpha k_{t}^{\alpha - 1} - \delta + 1\right) = c_{t}$

```python
c_sol = sm.solve(c_steadystate,k)
```

```python
sm.Eq(k_star,c_sol[0])
```

$\displaystyle k^{*} = \left(\frac{\beta \left(\delta - 1\right) + 1}{A \alpha \beta}\right)^{\frac{1}{\alpha - 1}}$

Then we define the **steady state capital**

```python
k_steadystate = sm.Eq(k,k1)
k_steadystate
```

$\displaystyle k_{t} = A k_{t}^{\alpha} \left(1 - \alpha\right) - c_{t} + k_{t} \left(A \alpha k_{t}^{\alpha - 1} - \delta + 1\right)$

and **solve** it

```python
k_sol = sm.solve(k_steadystate,c0)[0]
sm.Eq(c_star,k_sol)
```

$\displaystyle c^{*} = A k_{t}^{\alpha} - \delta k_{t}$

Define the model **parameters**:

```python
A = 1
alpha = 0.4
delta = 0.1
beta = 0.7
```

## Numerical solution

```python
# define production function:
def f(k):

    return A*k**alpha

# define wage rate:
def w(k):

    return A*(1-alpha)*k**alpha

# define return on capital
def R(k):

    return 1+ A*alpha*k**(alpha-1) - delta
```

define function which calculates the period 1 capital stock per capita.

```python
def capital(k,c):
    """
    Input:
        k_t: period_0 capital per capita
        c_t: period_0 consumption per capita

    Return:
        k_t+1: period_1 capital per capita
    """
    return (R(k))*k + w(k) - c
```

define a function which calculates the period 1 consumption derived form euler equation.

```python
def consumption(k,c):
    """
    Input:
        k_t: period_0 capital per capita
        c_t: period_0 consumption per capita

    Return:
        c_t+1: period_1 consumption per capita
    """
    return c*(beta*R(k))
```

define a function in which func_1, func_2= 0 in steady state

```python
# function 1 express the change in capital stock per capita between 2 periods.
def func_1(k,c):
    return capital(k,c) - k

# when under steady state capital(k,c) - k = 0
```

```python
# function 2 express the change in consumption per capita between 2 periods
def func_2(k,c):
    return consumption(k,c) - c

# when under steady state consumption(k,c) - c = 0
```

```python
def steadystate(x):
    result = [func_1(x[0],x[1])]
    result.append(func_2(x[0],x[1]))
    return result
```

```python
# make initial guess
k0,c0 = 3,3

# imply solver to calculate steady state
k_star, c_star = optimize.fsolve(steadystate,x0=(k0,c0))

print ("Numeric k*:  ", k_star)
print ("Numeric c*:  ", c_star)
```

    Numeric k*:   0.6284352134711177
    Numeric c*:   0.7675887250298413

define functions with capital k and consumption c on steady state:

```python
# define steady state capital k _star
def k_star():
    return ((beta*(delta-1) + 1)/(A*alpha*beta))**(1/(alpha-1))

# steady state consumption c_star
def c_star():
    return A*k_star()** alpha - delta*k_star()
```

```python
print ("Analytic k*:", k_star())
print ("Analytic c*:", c_star())
```

    Analytic k*: 0.6284352134762944
    Analytic c*: 0.7675887250317597

```python
# steady state production and saving
y_star = f(k_star())
s_star = (y_star - c_star())/ y_star
print ("steady state y*",y_star)
print ("steady state s*",s_star)
```

    steady state y* 0.8304322463793891
    steady state s* 0.07567567567567561

## Visualization

```python
#  locus for capital
def locus(k):
    return f(k) - delta*k
```

```python
#  create a grid for plotting
xs = np.linspace(0, 500, 6000)

#  Constructing the generic phase diagram
phase = plt.figure(figsize=(6,4))
plt.xlim(0,3)
plt.ylim(0,2)
plt.grid(False)
plt.xlabel([])

#  Adding the capital and consumption locii
plt.plot(xs, locus(xs), linestyle ='-', color='blue')
plt.axvline(k_star(), linestyle = '-', color = 'red')
plt.plot(k_star(), c_star(), marker='o', markersize = 10, color ='black')

#  Adding arrows to indicate dynamics
plt.arrow((1/2)*k_star(), (1/2)*c_star(), dx=0, dy=0.2, head_width = 0.05, head_length= 0.1, color = 'grey')
plt.arrow((1/2)*k_star(), (1/2)*c_star(), dx=0.2, dy=0, head_width = 0.05, head_length= 0.1, color = 'grey')
plt.arrow((1/2)*k_star(), 2*c_star(), dx=0, dy=0.2, head_width = 0.05, head_length= 0.1, color = 'grey')
plt.arrow((1/2)*k_star(), 2*c_star(), dx=-0.2, dy=0, head_width = 0.05, head_length= 0.1, color = 'grey')

plt.arrow(2*k_star(), (1/2)*c_star(), dx=0, dy=-0.2, head_width = 0.05, head_length = 0.1, color = 'grey')
plt.arrow(2*k_star(), (1/2)*c_star(), dx=0.2, dy=0, head_width = 0.05, head_length = 0.1, color = 'grey')
plt.arrow(2*k_star(), 2*c_star(), dx=0, dy=-0.2, head_width = 0.05, head_length = 0.1, color = 'grey')
plt.arrow(2*k_star(), 2*c_star(), dx=-0.2, dy=0, head_width = 0.05, head_length = 0.1, color = 'grey')

#  Adding axis labels and title
plt.xlabel('Capital (${k}$)', fontsize = 12)
plt.ylabel('Consumption (${c}$)', fontsize = 12)
plt.title('Ramsey Model Phase Diagram ', fontsize=15, pad = 10)
plt.show()
```

```python
def k_star(delta):
    return ((beta*(delta-1) + 1)/(A*alpha*beta))**(1/(alpha-1))

# steady state consumption c_star
def c_star(delta):
    return A*k_star()** alpha - delta*k_star()

def locus_0(k):
    return f(k) - delta*k
```

```python
gridmax, gridsize = 6, 100
grid = np.linspace(0, gridmax, gridsize)

# Create a new figure
plt.figure(figsize=(8,8))

# Plot utility for varying values of alpha
delta = 0.01
plt.plot(grid, locus_0(grid), '-', label=r'$\theta=%.2f$' %delta)

delta = 0.05
plt.plot(grid, locus_0(grid), '-', label=r'$\theta=%.2f$' %delta)

delta = 0.1
plt.plot(grid, locus_0(grid), '-', label=r'$\theta=%.2f$' %delta)

delta = 0.2
plt.plot(grid, locus_0(grid), '-', label=r'$\theta=%.2f$' %delta)

# label the axis
plt.xlabel('capital(k)')
plt.ylabel('consumption(c)')
plt.axis([0, 6, 0, 5])

# add title
plt.title('How do capital accumulation change by changing $\delta$', fontsize=15)

# add legend
plt.legend(loc=0, frameon=False)

# save and display figure!
plt.show()
```

From the figure, by increasing $\delta$,capital accumulation curve becomes flatter

# Conclusion

ADD CONCISE CONCLUSION.


Contents

1  Vectorised code - what and why?

1.1  Example 1: Vectorising a simple calculation of 2 arrays

1.2  Example 2: A Resource-Consumer model of Bacterial Carbon Uptake

1.2.1  Consumer growth rate:

1.2.2  Resource availability:

1.2.3  Loop and vector forms of these equations:

1.2.4  Comparing the loop and vectorised models:

1.3  WARNING: THIS WILL TAKE TIME.

2  When to vectorise?

2.1  "No free lunch!"

2.2  When can you vectorise?

# Vectorised code - what and why?

We have now had fairly extensive practise in iteratively creating solutions to problems using for loops. Thus far all our problems have been mathematically quite straightforward, and not very computationally intensive. As you begin to move on from your taught modules and into current research, you may find yourselves solving larger and more complex problems, at which point you will start to discover that for-loops have a fundamental weakness - speed!

In a nutshell, there are two issues keeping loops slow:

1. Producing solutions to a large problem one loop iteration at a time means that our scripts and functions spend a lot of time doing stuff other than actually solving the problem we want them to solve - e.g. shuffling data around between variables in each loop iteration, or storing the result of the current loop's calculations in a (sometimes temporary) variable.

2. Loops (at least in Python) are not generally able to take advantage of the parallel computing capabilities of modern-day computers, and thus must literally compute a solution one loop iteration at a time. Vectorised functions tend to be optimised to spread their computations over multiple processors/threads, which can be much faster!

These issues become especially pronounced in the case of nested loops - which often appear in more spatially-explicit problems or time-dependent ones.

Rather than expecting you to simply take my word for it, the following two examples will showcase the difference in runtime between a loop method and a vectorised method using numpy. The first is a relatively simple (if artificial) problem, intended to demonstrate basically at-a-glace the difference between the two approaches. The second is taken from current research on metabolic models of bacterial communities[2].

[1] see the `multiprocessing` package for one way around this!

[2] thanks to Emma Cavan (https://www.imperial.ac.uk/people/e.cavan/research.html) in Samraat's lab for bringing this model to my attention.

## Example 1: Vectorising a simple calculation of 2 arrays

Let us imagine we have two simple 1D arrays $a = (a_1, a_2, ... , a_N)$ and $b = (b_1, b_2, ... ,b_N)$, each of length N, and that we want to calculate a new array $c$ in which each entry is just the product of the two corresponding entries in $a$ and $b$:

$$c = (a_1 \times b_1, a_2 \times b_2, ... , a_N \times b_N)$$

This operation is called the *entrywise* product of $a$ and $b$.

Below are a loop-based function and a vectorised function to calculate the entrywise product of two 1D arrays of the same length. We will test them both on larger and larger 1D arrays to see how the vectorised approach is faster.

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

```python
def loop_product(a, b):
    N = len(a)
    c = np.zeros(N)
    for i in range(N):
        c[i] = a[i] * b[i]
    return c

def vect_product(a, b):
    return np.multiply(a, b)
```

The `multiply` function from numpy is a vectorised implementation of the elementwise product that we have explicitly written in the function `loop_product` above it. In general, numpy is an excellent choice for vectorised implementations of functions involving matrix maths (or maths using higher-dimensional analogues of matricies).

Let's try comparing the runtimes of `loop_product` and `vect_product` on increasingly large randomly-generated 1D arrays:

```python
import timeit

array_lengths = [1, 100, 10000, 1000000, 10000000]
t_loop = []
t_vect = []

for N in array_lengths:
    print("\nSet N=%d" %N)
    #randomly generate our 1D arrays of length N
    a = np.random.rand(N)
    b = np.random.rand(N)

    # time loop_product 3 times and save the mean execution time.
    timer = timeit.repeat('loop_product(a, b)', globals=globals().copy(), number=3)
    t_loop.append(1000 * np.mean(timer))
    print("Loop method took %d ms on average." %t_loop[-1])

    # time vect_product 3 times and save the mean execution time.
    timer = timeit.repeat('vect_product(a, b)', globals=globals().copy(), number=3)
    t_vect.append(1000 * np.mean(timer))
    print("Vectorised method took %d ms on average." %t_vect[-1])

# compare the timings in a plot
plt.figure()
plt.plot(array_lengths, t_loop, label="loop method")
plt.plot(array_lengths, t_vect, label="vect method")
plt.xlabel("Array length")
plt.ylabel("Execution time (ms)")
plt.legend()
plt.show()

```

## Example 2: A Resource-Consumer model of Bacterial Carbon Uptake

The above example clearly shows that some operations can be vastly accelerated with vectorised code - but you aren't going to spend your research projects computing the elementwise products of absurdly long 1D arrays. This example will show how the vectorised approach is applicable to biologically relevant problems and real, ongoing research.

Do not worry too much about the equations themselves, the

Imagine we have $N$ different bacterial species (our consumers) in an environment consisting of $M$ carbon sources (our resources).

### Consumer growth rate:

In reality, each species will uptake carbon from the different sources at different rates, as well as respiring and losing carbon to the environment at different rates. This yields a complex relationship between overall per-species growth rate and resource availability, which we can describe with:

\begin{align}
    \frac{1}{C_i}\frac{dC_i}{dt} = (\sum_{j=1}^M u_{ij} S_j  (1 - {p_{R}}_j - \sum_{k=1}^{M} l_{jk})) - R_{i}
\end{align}

where:

* $C = $ vector of bacterial populations
* $S = $ vector of carbon output from different sources,
* $u = $ matrix of carbon-uptake rates for each species and carbon source,
* $p_R = $ vector of respiratory carbon loss,
* $l = $ matrix of non-respiratory carbon loss,
* $R = $ vector of maintenance cost of growth.

### Resource availability:

The change in carbon resource avaliblity can be attributed either to abiotic processes (which we represent here through the constant term $\rho_j$) or through the activity of the bacterial community which takes up resource $j$ at the rate $U_j$ and releases it (via the inefficency of uptake) through the $L_j$ term:

\begin{align}
    \frac{dS_j}{dt} = \rho_j - U_j + L_j
\end{align}

How do the vectors $U_j$ and $L_j$ relate to the matricies $u$ and $l$ above? We can get the rates of uptake and release by the community by summing across all $N$ species in the community and across all $M$ resources which they release as a result of the $L_j$ term:

\begin{align}
    \frac{dS_j}{dt} = \rho_j -
                      \sum_{i = 1}^{N} \left( S_j C_i u_{ij}  - \sum_{k = 1}^{N} C_i S_k u_{ik} l_{kj} \right),
\end{align}

where:
* $\rho =$ constant vector of carbon resource change due to abiotic processes,
* $C, S, u, l$ are the same as above.

\begin{align}
    \frac{1}{C_i}\frac{dC_i}{dt} = (\sum_{j=1}^M u_{ij} S_j  (1 - {p_{R}}_j - \sum_{k=1}^{M} l_{jk})) - R_{i}
\end{align}
\begin{align}
    \frac{dS_j}{dt} = \rho_j -
                      \sum_{i = 1}^{N} \left( S_j C_i u_{ij}  - \sum_{k = 1}^{N} C_i S_k u_{ik} l_{kj} \right),
\end{align}

### Loop and vector forms of these equations:

Since $\frac{dC_i}{dt}$ and $\frac{dS_j}{dt}$ are defined individually for each consumer $i$ and each resource $j$, and since they consist simply of matrix arithmetic, we could define them using for loops:

```python
def dCdt_loop(C, S, u, l, pR, R):
    N = C.shape[0]
    M = S.shape[0]

    dC_dt = np.zeros(N)
    lsum = np.sum(l, axis=1)
    for i in range(N):
        term1 = 0
        for j in range(M):
            term1 += u[i, j] * S[j] * (1 - pR[j] - lsum[j])
        dC_dt[i] = C[i] * (term1 - R[i])
    return dC_dt

def dSdt_loop(C, S, u, l, rho):
    N = C.shape[0]
    M = S.shape[0]

    dS_dt = np.zeros(M)
    for j in range(M):
        SCu = 0
        for i in range(N):
            CSul = 0
            for k in range(M):
                CSul += C[i] * S[k] * u[i, k] * l[k, j]
            SCu += S[j] * C[i] * u[i, j] - CSul
        dS_dt[j] = rho[j] - SCu
    return dS_dt
```

It should be immediately clear why this system might be a good candidate for vectorisation - look at those all those nested loops! Now consider that this is just for calculating the change in $C$ and in $S$ at each timestep, and you can see how simulating this whole system over many timesteps with the loop approach could get very painful if the number of bacterial species $N$ and/or carbon sources $M$ is large.

Luckily, since all of the above are matrix operations, we can vectorise the functions with Numpy:

```python
def dCdt_vect(C, S, u, l, pR, R):
    Sl = (u - pR - np.sum(l, axis=1)) * S
    uSl = np.sum(Sl * u, axis=1) - R
    dCdt = C * uSl
    return dCdt

def dSdt_vect(C, S, u, l, rho):
    return rho - np.multiply((C @ u).transpose(), S) + np.einsum('i,k,ik,kj->j', C, S, u, l)
```

Do not panic if you can't easily interpret these numpy expressions! This is a relatively complex example and if you aren't familiar with the relevant linear algebra then it will seem much spookier than it truly is. The point of this example is to show a real-world scenario in which vectorising code will generate much quicker results, hopefully with the idea of motivating you to look into vectorising your own code in your own research.

### Comparing the loop and vectorised models:

We will now run two simulations of our system of bacteria and carbon sources. The initial values and all equation parameters will be identical - the only difference will be that one will be run with the loop functions, and the other with the vectorised functions. We will compare the outputs (to make sure they are the same!) and the execution times.

First we will import some extra functions and set up the simulation parameters.

```python
from scipy.integrate import odeint
import matplotlib.pylab as pie
import time

# set up simulation parameters
N = 200
M = 100

u = np.zeros([N,M])
np.fill_diagonal(u,1)

R = np.full([N], (0.3))

pR = np.full([M], (0.1))

l = np.zeros([M,M])
for i in range(M-1):
    l[i,i+1] = 0.3

rho = np.zeros(M)

t = np.linspace(0,100,101)

x0 = np.concatenate((np.full([N], (0.1)),np.full([M], (1.0))))  # initial values for C and S

def model_loop(pops, t, N, M):
    x = pops
    C = x[0:N] # consumer
    S = x[N:N+M] # resources

    dCdt = dCdt_loop(C, S, u, l, pR, R)
    dSdt = dSdt_loop(C, S, u, l, rho)
    return np.array(np.concatenate((dCdt, dSdt)))

def model_vect(pops, t, N, M):
    x = pops

    C = x[0:N] # consumer
    S = x[N:N+M] # resources

    dCdt = dCdt_vect(C, S, u, l, pR, R)
    dSdt = dSdt_vect(C, S, u, l, rho)

    return np.array(np.concatenate((dCdt, dSdt)))
```

Now run the two simulations:

## WARNING: THIS WILL TAKE TIME.

Expect the cell to execute for at least 5 minutes. Go and make a cup of tea while you wait. If it takes longer than 10 minutes then interrupt the cell with the stop button at the top of the notebook window, and try running with N = 50, M = 25 instead.

```python
plt.close('all')

# # loop model
starttime = time.time()
pops_loop = odeint(model_loop, y0=x0, t=t, args=(N, M))
time_loop = time.time() - starttime

# vectorised model
starttime = time.time()
pops_vect = odeint(model_vect, y0=x0, t=t, args=(N, M))
time_vect = time.time() - starttime

print("Loop model took %6.3f s." %time_loop)
print("Vectorised model took %6.3f s." %time_vect)

fig = plt.figure(figsize=(15,10))
ax1 = fig.add_subplot(121)
ax1.plot(t, pops_loop[:,0], 'g-', label = 'Resources (loop)', linewidth=0.7)
ax1.plot(t, pops_loop[:,N], 'b-', label = 'Consumers (loop)', linewidth=0.7)
ax1.plot(t, pops_loop[:,0:N], 'g-', linewidth=0.7)
ax1.plot(t, pops_loop[:,N:N+M], 'b-', linewidth=0.7)
ax1.legend()
ax1.grid
ax1.set_ylabel('Population density')
ax1.set_xlabel('Time')
ax1.set_title('LOOP MODEL')

ax2 = fig.add_subplot(122)
ax2.plot(t, pops_vect[:,0], 'g--', label = 'Resources (vect)', linewidth=0.7)
ax2.plot(t, pops_vect[:,N], 'b--', label = 'Consumers (vect)', linewidth=0.7)
ax2.plot(t, pops_vect[:,0:N], 'g--', linewidth=0.7)
ax2.plot(t, pops_vect[:,N:N+M], 'b--', linewidth=0.7)
ax2.legend()
ax2.grid
ax2.set_ylabel('Population density')
ax2.set_xlabel('Time')
ax2.set_title('VECTORISED MODEL')
```

We can see from the plots that the outputs of the two models are identical, but the execution time varies dramatically. Imagine we wanted to run these models a large number of times with varying initial conditions and/or parameters, to see how our populations would behave and evolve under different environmental conditions. Running just 10 simulations in loop-mode would take close to an hour, whereas in vectorised mode we could run hundreds of simulations in mere minutes.

# When to vectorise?

We have seen that vectorising your code can have it running in a fraction of the time it otherwise would - why not always vectorise then?

Generally, you should follow the same principles as with any code profiling...don't spend time speeding up code that isn't slow in the first place, or code which you will probably not need to run more than a small number of times.

## "No free lunch!"

There are trade-offs to vectorising, most notably memory useage. One downsides of calculating many steps simultaneously is that your computer needs to hold much more in memory in order to do it. If you try to vectorise a problem thats *too* large, you will probably run into memory errors. One easy example is to re-run example 1 above, but **even bigger**:

```python
N = 1000000000

a = np.random.rand(N)
b = np.random.rand(N)
c = vect_product(a, b)

# if no error, remove a, b, c from memory.
del a
del b
del c
```

    array([0.62795691, 0.13196955, 0.02004608, ..., 0.29443425, 0.26235812,
           0.20569663])

This will almost certainly return a memory error (i.e. your computer ran out of RAM in which to store the entire of the very large arrays $a$, $b$ and $c$ while it was still calculating (if you didnt get an error, try again with an extra 0 in $N$).

Again, this is a rather contrived example - you probably won't be taking element-wise products of arrays with a billion entries in your research - but more complex problems can easily become too big for memory while still remaining biologically reasonable![3]

## When *can* you vectorise?

Note that not every system is vectorisable! You cannot generally vectorise a loop over time, for example. In our example 2 above, we vectorised everything that happens *within* a given timestep, but computing the future state of the system using its current state we did not vectorise (in fact `odeint` did this bit for us, but it still had to do it timestep-by-timestep).

This makes sense - imagine trying to simulate a resource-consumer model like ours above at some timesteps $(t_0, t_1, t_2,...)$. You can't start computing the population sizes in any order you like, since the state at each $t_n$ depends on the state before it -- you have to start with $t_1$, then $t_2$, and so on in numerical order.

[3] Check out the CMEE course on High Performance Computing to learn about using Imperial's supercomputing cluster to run extremely large problems.


## 1-1. 量子ビット
古典コンピュータ（量子コンピュータではない既存のコンピュータのこと）内部では情報は 0 と 1 の２つの状態で表現されている。例えば、スイッチのオン・オフの状態や、電荷がたまった状態とそうでない状態、電圧の高・低などでその２状態を表現している。一方、量子力学では異なる２つの状態の**重ね合わせ状態**というのが許されているので、量子の世界の情報の最小単位である**“量子”ビット**は $\alpha$と$\beta$という二つの複素数を用いた複素ベクトルを用いて

$$
\left(
\begin{array}{c}
\alpha
\\
\beta
\end{array}
\right)
$$

のようにその量子状態が記述される。

$\alpha$や$\beta$はどの程度の重みで0状態と1状態が重ね合わさっているかを表しており、**複素確率振幅**と呼ばれる。
$\alpha$や$\beta$が複素数になっているのは、量子の世界では0や1といった離散的な量も波の性質をもち干渉するためである。

古典ビットの0に対応する状態は

$$
\left(
\begin{array}{c}
1
\\
0
\end{array}
\right)
$$

1に対応する状態は

$$
\left(
\begin{array}{c}
0
\\
1
\end{array}
\right)
$$

となる。

列ベクトルを毎回書いているとスペースが無駄なので、**ディラックのブラケット表記**という簡略化した表記を導入する。これは列ベクトルである、という量子状態の型宣言のようなもので、この記号がついていると量子状態をあらわす複素ベクトルであることが一目でわかるようになっている。

$$
|0\rangle = \left(
\begin{array}{c}
1
\\
0
\end{array}
\right)
$$
$$
|1\rangle = \left(
\begin{array}{c}
0
\\
1
\end{array}
\right)
$$

この表記を用いると量子ビットは

$$
|\psi \rangle = \alpha |0\rangle + \beta |1\rangle
$$

と書かれる（スペースが省略できた！）。

### 複素確率振幅の意味

複素確率振幅はいったいどのような物理的実体に対応するだろうか。実は、量子力学では観測者（人間）は直接複素確率振幅にはアクセスすることができず、**測定という操作をした時に初めて0か1かが確率的にきまる**。測定結果の確率分布に影響するのが複素確率振幅である。測定結果が0になる確率$p_0$, 1になる確率$p_1$は複素確率振幅の絶対値の2乗で表される:

$$
p_0 = |\alpha |^2,
\\
p_1 = |\beta |^2
$$

確率の和が1になるように、規格化条件 $|\alpha |^2 + |\beta |^2 =1$を課す。

**測定を行うと、量子状態は測定結果に対応する状態に変化する**。具体的には、測定結果が0の場合は$|0\rangle$、1の場合は$|1\rangle$に変化する。この測定を、正規直交基底$|0\rangle$, $|1\rangle$での射影測定と呼ぶ。$|0\rangle$, $|1\rangle$以外の正規直交基底での射影測定や、より一般の測定もあるが、ここでは扱わない。

まとめると、

- 量子状態は、大きさが１に規格化された複素ベクトルによって記述される。
- 各成分の絶対値の２乗が、測定をしたときにその成分に対応する状態を得る確率である。
- 測定後の量子状態は、測定結果に応じて$|0\rangle$または$|1\rangle$となる。

したがって、

$$
|0\rangle = \left(
\begin{array}{c}
1
\\
0
\end{array}
\right), |1\rangle = \left(
\begin{array}{c}
0
\\
1
\end{array}
\right)
$$

は、確実に0や1が得られる古典的な状態に対応し、

$$
\frac{1}{\sqrt{2}}
(|0\rangle + |1\rangle) = \left(
\begin{array}{c}
\frac{1}{\sqrt{2}}
\\
\frac{1}{\sqrt{2}}
\end{array}
\right)
$$

は0と1が同じおもみで重ね合わさった状態であり、測定をすると0と1が確率1/2で完全にランダムに得られる。

複素確率振幅は複素数なので、

$$
\frac{1}{\sqrt{2}}(|0\rangle + i|1\rangle ) =
\left(
\begin{array}{c}
\frac{1}{\sqrt{2}}
\\
\frac{i}{\sqrt{2}}
\end{array}
\right)
$$

といった状態も許されている。より一般に、

$$
\frac{1}{\sqrt{2}} ( |0\rangle + e^{ i \phi } |1\rangle )
$$

なども許されている。この状態の場合、状態0に対する確率振幅 ($1/\sqrt{2}$) が正の実数であるのに対して、状態1に対する確率振幅 ($e^{i\phi}/\sqrt{2}$) は、複素平面上で$\phi$回転している。このような、重ね合わせ状態における確率振幅間の相対的な偏角のことを**位相**と呼び、量子力学全般において重要な役割を果たす。

(詳細は Nielsen-Chuang の `1.2 Quantum bits` を参照)

### SymPyを用いて量子ビットを表示してみる
SymPyでは、量子状態を扱うことができる。初期化された量子ビットを準備する場合は ` Qubit() ` 関数を用いる。

```python
from IPython.display import Image, display_png
from sympy import *
from sympy.physics.quantum import *
from sympy.physics.quantum.qubit import Qubit,QubitBra
init_printing() # ベクトルや行列を綺麗に表示するため
```

```python
# Google Colaboratory上でのみ実行してください
from IPython.display import HTML
def setup_mathjax():
    display(HTML('''

    '''))
get_ipython().events.register('pre_run_cell', setup_mathjax)
```

```python
psi = Qubit('0')
```

```python
psi #ブラケット表示
```

```python
represent(psi) #列ベクトル表示
```

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

SymPyでは文字をシンボルとして扱うことができるので、一般的な量子ビットも簡単に書ける。

```python
a, b = symbols('alpha, beta')  #a, bをシンボルとして、alpha, betaとして表示
ket0 = Qubit('0')
ket1 = Qubit('1')
psi = a * ket0 + b* ket1
psi # 状態をそのまま書くとケットで表示してくれる
```

```python
represent(psi)
```

$$\left[\begin{matrix}\alpha\\\beta\end{matrix}\right]$$

もちろん具体的な数値を代入することもできる。

```python
psi.subs([([a,1/sqrt(2)]),([b,1/sqrt(2)])]) # alpha, betaに具体的な数字を代入
```


## Adding a temperature dependency

### Paper reference: Yu et al (2017)
Yu, F., Luo, G., Nadykto, A. B., & Herb, J. (2017). Impact of temperature dependence on the possible contribution of organics to new particle formation in the atmosphere. Atmospheric Chemistry and Physics, 17(8), 4997–5005. https://doi.org/10.5194/acp-17-4997-2017

\begin{equation}
f_T = exp \Big[ \frac{\Delta H}{k} \big( \frac{1}{T} - \frac{1}{T_0} \big) \Big]
\end{equation}

- Best fit with obsevations according to Yu et al (2017) is by $\Delta H = 35$ kcal/mol.
- $T_0 = 278.$
- $k= 1.380649 \times 10^{−23}$ J/K
- $k = 0.001985875$ kcal/mol/K

It is unclear how the authors set the maximum value, but they say in the review that they initially used 10 as a max and that the difference was negligible.

```python
import numpy as np
```

```python
T_0 = 278
k_kcalmolK =  0.001985875
dH = 35

def fT(T, max=10):
    f =  np.exp(dH/k*(1/T-1/T_0))
    if type(T) is np.ndarray:
        f[f>10] = 10
    else:
        f = min(f,10)
    return f
```

```python
fT(270)
```

    6.5432960174993715

```python
T = np.linspace(270,310)

f = fT(T)

import matplotlib.pyplot as plt
plt.plot(T,f)
plt.yscale('log')
plt.xlim([270,310])
plt.ylim([1e-3,10])
```

```python
a = [1,2,4]
```

```python
[]
```

    []

```python
type(T)
```

    numpy.ndarray

```python

```


## The SIR model with external infection seeding

We modify the usual SIR dynamical equations to the add an explicit seeding of initial cases, such as by returning travelers from afflicted regions.

\begin{align}
\dot S &= -\lambda(t)S - f(t) \\
\dot I &= \lambda(t)I - \gamma I + f(t) \\
\dot R &= \gamma I
\end{align}

In the linear limit where $S(t) \approx S(0)$, $I(t)$ is decoupled and obeys the equation $$\dot{I}(t) = (\beta - \gamma) I + f(t)$$. We now consider a seeding function that grows exponentialy $$f(t) = f_0 e^{\tilde{\beta} t}$$ and integrate the equation to find

\begin{equation}
    I(t)  =
    \frac{f_0}{\tilde{\beta} - (\beta-\gamma)} \left( e^{\tilde{\beta} t} + e^{(\beta - \gamma) t}  \right)
    + I(0) e^{(\beta-\gamma)t}\,.
\end{equation}

A constant seeding rate is obtained for $\tilde{\beta} = 0$. We can now recognize two regimes:
1. **Transmission dominated:** $(\beta - \gamma) > \tilde{\beta}$
2. **Seeding dominated:** $(\beta - \gamma) < \tilde{\beta}$

We can also compute the total number of cases seeded externally,
$$ N_{seeded} (t) = \frac{f_0}{\tilde{\beta}} (e^{\tilde{\beta} t} - 1)$$

```python
%%capture
## compile PyRoss for this notebook
import os
owd = os.getcwd()
os.chdir('../../')
%run setup.py install
os.chdir(owd)
```

```python
%matplotlib inline
import numpy as np
import pyross
import matplotlib.pyplot as plt
#from matplotlib import rc; rc('text', usetex=True)
```

```python
# Define the analytical solutions for I(t), N(t)

def analyticalSol(t, beta, gamma, betaBar, I0, f0) :
    return (f0 / (betaBar - beta + gamma)) * np.exp(betaBar * t) + (I0 - f0 / (betaBar - beta + gamma)) * np.exp((beta-gamma)*t)

def numSeeded(t, betaBar, f0) :
    return (np.exp(betaBar * t)-1) * f0 / betaBar
```

## Transmission dominated case

In this case, the number of infected cases grows linearly at short times, until the infectious population becomes large enough to sustain the same infection rate. Therefore, at long times the exponential growth rate is identitcal to the none-seeded case. The total number of seeded cases becomes insiginificant compared to the resulting number of infections. The effect of seeding is therefore mostly to skip ahead on the save epidemic transmission curve.

```python
fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

def solFunc0(t) :
    return analyticalSol(t, 0.2, 0.1, 0, 1, 0)

def solFunc1(t) :
    return analyticalSol(t, 0.2, 0.1, 0.05, 1, 3)

t = np.arange(70);
sol0 = solFunc0(t)
sol1 = solFunc1(t)
seed1 = numSeeded(t, 0.05, 3)

# plt.fill_between(t, 0, sol1, color="#348ABD", alpha=0.3)
plt.semilogy(t, sol1, '-', color="#348ABD", label='$\\tilde{\\beta} = 0.05,f_0=3$', lw=4)
plt.semilogy(t, seed1, '-', color="dimgrey", label='$N_{seeded}(t)$', lw=4)

# plt.fill_between(t, 0, (sol0), color='#A60628', alpha=0.3)
plt.semilogy(t, (sol0), '-', color='#A60628', label='$(\\beta-\\gamma)=0.1$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.title('Transmission dominated seeding');
```

## Seeding dominated case

In this case, growth rate of the infectious population at long times matches the growth rate of the seeded cases, and those dominate the total infected case count. In this case, the spread of the epidemic is greatly accelerated.

```python
fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

def solFunc2(t) :
    return analyticalSol(t, 0.2, 0.1, 0.2, 1, 0.05)

t = np.arange(70);
sol0 = solFunc0(t)
sol2 = solFunc2(t)
seed1 = numSeeded(t, 0.2, 0.05)

# plt.fill_between(t, 0, sol1, color="#348ABD", alpha=0.3)
plt.semilogy(t, sol2, '-', color="#348ABD", label='$\\tilde{\\beta}=0.05,\, f_0 = 0.2$', lw=4)
plt.semilogy(t, seed1, '-', color="dimgrey", label='$N_{seeded}(t)$', lw=4)

# plt.fill_between(t, 0, (sol0), color='#A60628', alpha=0.3)
plt.semilogy(t, (sol0), '-', color='#A60628', label='$(\\beta - \\gamma) = 0.1$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
```

## Non-linear Dynamics

We can run the full dynamics of these cases. We use the above seeding protocols for 7 weeks and then stop, corresponding to an imposed travel ban. Because of the elavated spreading rate in the seeding-dominated case, the infection rate drops once seeding is stopped.

```python
M = 1                  # the SIR model has no age structure
Ni = 1e5*np.ones(M)   # so there is only one age group
N = np.sum(Ni)         # and the total population is the size of this age group

beta  = 0.2             # infection rate
gIa   = 0.1            # recovery rate of asymptomatic infectives
gIs   = 0.1            # recovery rate of symptomatic infectives
alpha = 0              # fraction of asymptomatic infectives
fsa   = 1              # the self-isolation parameter

Ia0 = np.array([0])     # the SIR model has only one kind of infective
Is0 = np.array([1])     # we take these to be symptomatic
R0  = np.array([0])     # and assume there are no recovered individuals initially
S0  = N-(Ia0+Is0+R0)    # so that the initial susceptibles are obtained from S + Ia + Is + R = N

vec = np.ones(M);
Toff1 = 49; Toff2 = 49; # 7 weeks
Tstop = 1; # one day

def stepFunc(t) :
    return 0.5 * (+ 1 - np.tanh(t/Tstop))

def seedRateFunc1(t) :
    return  3.0 * vec * np.exp(0.05 * t) * stepFunc(t-Toff1)

def seedRateFunc2(t) :
    return  0.05 * vec * np.exp(0.2 * t) * stepFunc(t-Toff2)

# there is no contact structure
def contactMatrix(t):
    return np.identity(M)

# duration of simulation and data file
Tf = 200;  Nt=Tf+1;

# instantiate model
parameters = {'alpha':alpha, 'beta':beta, 'gIa':gIa, 'gIs':gIs,'fsa':fsa}
model = pyross.deterministic.SIR(parameters, M, Ni)

# simulate model
data1 = model.simulate(S0, Ia0, Is0, contactMatrix, Tf, Nt, seedRate = seedRateFunc1)
data2 = model.simulate(S0, Ia0, Is0, contactMatrix, Tf, Nt, seedRate = seedRateFunc2)
data0 = model.simulate(S0, Ia0, Is0, contactMatrix, Tf, Nt)

t  = data0['t']
Is0  = data0['X'][:,2].flatten(); S0 = data0['X'][:,0].flatten();
Is1 = data1['X'][:,2].flatten(); S1 = data1['X'][:,0].flatten();
Is2 = data2['X'][:,2].flatten(); S2 = data2['X'][:,0].flatten();

numSeeded1 =int(numSeeded(Toff1, 0.05, 3))
numSeeded2 = int(numSeeded(Toff2, 0.2, 0.05))
print("The total number of seeded infections in the two cases: ", numSeeded1,", ", numSeeded2)
```

    The total number of seeded infections in the two cases:  635 ,  4508

```python
def plotData(t, S, Is, ref) :
    fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
    plt.rcParams.update({'font.size': 22})

    plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
    plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)
    R=N-S-Is;
    plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
    plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

    plt.fill_between(t, 0, (Is/N), color='#A60628', alpha=0.3)
    plt.plot(t, (Is/N), '-', color='#A60628', label='$I$', lw=4)
    plt.plot(t, (ref/N), '--', color='#A60628', label='$I_{analytical}$', lw=2)

    plt.legend(fontsize=26); plt.grid()
    plt.autoscale(enable=True, axis='x', tight=True)
#     plt.ylim(0, np.max(Is) * 1.2)
    plt.ylim(0, 1.05)
    plt.xlim(0, np.max(t))

plotData(t, S0, Is0, solFunc0(t)); plt.title('Not Seeded')
plotData(t, S1, Is1, solFunc1(t)); plt.title('Seeded, Transmission Dominated')
plotData(t, S2, Is2, solFunc2(t)); plt.title('Seeding Dominated');
```


# 2. Linear Algebra - 1. Vectors

Linear Algebra가 다루는 Vector는 우리가 익숙한 geometric vector와 크게 다르지 않습니다.

geometric vector는 데카르트 좌표계에서, direction과 magnitude를 갖는 arrow로 흔히 표현되곤 합니다.

```python
%matplotlib inline

X = [2,2]
xx = np.arange(-2,4)
yy = xx

plt.figure(figsize=(10,5))
plt.plot(xx,yy)
plt.annotate('Geometric vector',xy=(X[0],X[1]),xytext=(-1.2,0),arrowprops={'color':'red'},fontsize=20)
plt.title("Geometric vector contains 'direction' and 'magnitude'",fontsize=20)
plt.show()
```

Machine Learning(이하 "ML")은 데이터를 다루는 CS적 관점을 갖고 있습니다.
따라서, 같은 대상(Vector)을 바라보는 관점도 살짝 다르게 접근할 수 있습니다.
Computer Science에선, computer langauge가 array라는 자료구조와 이를 처리하는 알고리즘을 갖고 있습니다.

Linear Algebra for ML 은 역시, 위와 같은 관점에서 **Vector를 array로 구현**해 접근합니다.
사실 본질적인 내용은 다른 것이 없고, 단지 array구조의 객체를 활용해 선형대수를 computer 연산 한다고 보면 될 것 같습니다.
    (python의 패키지인 Numpy를 활용해 연산, 구현하겠습니다.)

### 1) Array for Vectors

array = tuples of n real number
= n-dimensional real coordiante space(n차원의 실수좌표공간)를 구성

예를 들어 $a$ 라는 Vector가 아래와 같다면

$$
\begin{align}
a=
\begin{bmatrix}
1 \\
2 \\
3 \\
4 \\
\end{bmatrix}
\end{align}
$$

```python
a = np.array([1,2,3,4,5])
a
```

    array([1, 2, 3, 4, 5])

```python
a = np.array([[1],[2],[3]])
a
```

    array([[1],
           [2],
           [3]])

이와 같이 $a$ 벡터를 array로 표현할 수 있다.

1.Study/2. with computer/1.Math_code/5. Mathematics for Machine Learning/2. Linear Algebra_1. Vectors.ipynb


# 1. 2D Linear Convection

We consider the 1d linear Convection equation, under a constant velocity

$$
\partial_t u + \mathbf{a} \cdot \nabla u = 0
$$

```python
# needed imports
from numpy import zeros, ones, linspace, zeros_like
from matplotlib.pyplot import plot, contourf, show, colorbar
%matplotlib inline
```

```python
# Initial condition
import numpy as np
u0 = lambda x,y: np.exp(-(x-.3)**2/.05**2)*np.exp(-(y-.3)**2/.05**2)

ts = linspace(0., 1., 401)
x,y = np.meshgrid(ts,ts)
u = u0(x,y)
```

```python
contourf(x,y, u); colorbar() ; show()
```

### Time scheme

$$\frac{u^{n+1}-u^n}{\Delta t} + \mathbf{a} \cdot \nabla u^{n+1} = 0 $$

$$ \left(I + \Delta t \mathbf{a} \cdot \nabla \right) u^{n+1} = u^n $$

### Weak formulation

$$
\langle v, u^{n+1} \rangle + \Delta t ~ \langle v, \mathbf{a} \cdot \nabla u^{n+1} \rangle = \langle v, u^n \rangle
$$
if we assume $\mathbf{a} = \left( a_1, a_2 \right)^T$ is a constant, then our weak formulation writes
$$
\langle v, u^{n+1} \rangle - \Delta t ~ \langle \mathbf{a} \cdot \nabla v ,  u^{n+1} \rangle = \langle v, u^n \rangle
$$

expending $u^n$ over the fem basis, we get the linear system

$$A U^{n+1} = M U^n$$

where

$$
M_{ij} = \langle b_i, b_j \rangle
$$

$$
A_{ij} = \langle b_i, b_j \rangle - \Delta t ~ \langle \mathbf{a} \cdot \nabla b_i, b_j \rangle
$$

## Abstract Model using SymPDE

```python
from sympde.core import Constant
from sympde.expr import BilinearForm, LinearForm, integral
from sympde.topology import ScalarFunctionSpace, Square, element_of
from sympde.calculus import grad, dot
from sympy import Tuple
```

```python
# ... abstract model
domain = Square()

V = ScalarFunctionSpace('V', domain)

x,y = domain.coordinates

u,v = [element_of(V, name=i) for i in ['u', 'v']]

a1 = Constant('a1')
a2 = Constant('a2')
dt = Constant('dt')

a = Tuple(a1,a2)

# bilinear form
expr = v*u + dt* dot(a, grad(u))*v
a = BilinearForm((u,v), integral(domain , expr))

# bilinear form for the mass matrix
expr = u*v
m  = BilinearForm((u,v), integral(domain , expr))

# linear form for initial condition
from sympy import exp
expr = exp(-(x-.3)**2/.05**2)*exp(-(y-.3)**2/.05**2)*v
l = LinearForm(v, integral(domain, expr))
```

## Discretization using Psydac

```python
from psydac.api.discretization import discretize
```

```python
a1 = 1. ; a2 = 0. # wavespeed
T = 0.25  # T final time
dt = 0.001
niter = int(T / dt)

degree = [3,3]    # spline degree
ncells = [64,64]   # number of elements
```

```python
# Create computational domain from topological domain
domain_h = discretize(domain, ncells=ncells, comm=None)

# Discrete spaces
Vh = discretize(V, domain_h, degree=degree)

# Discretize the bilinear forms
ah = discretize(a, domain_h, [Vh, Vh])
mh = discretize(m, domain_h, [Vh, Vh])

# Discretize the linear form for the initial condition
lh = discretize(l, domain_h, Vh)
```

```python
# assemble matrices and convert them to scipy
M = mh.assemble().tosparse()
A = ah.assemble(a1=a1, a2=a2, dt=dt).tosparse()

# assemble the rhs and convert it to numpy array
rhs = lh.assemble().toarray()
```

```python
from scipy.sparse.linalg import gmres
```

```python
# L2 projection of the initial condition
un, status = gmres(M, rhs, tol=1.e-8, maxiter=5000)
```

```python
from utilities.plot import plot_field_2d

nbasis = [W.nbasis for W in Vh.spaces]
plot_field_2d(Vh.knots, Vh.degree, un.reshape(nbasis)) ; colorbar() ; show()
```

```python
for i in range(0, niter):
    b = M.dot(un)
    un, status = gmres(A, b, tol=1.e-8, maxiter=5000)
```

```python
nbasis = [W.nbasis for W in Vh.spaces]
plot_field_2d(Vh.knots, Vh.degree, un.reshape(nbasis)) ; colorbar() ; show()
```

```python

```

```python

```


# Manometry

## Learning outcomes

* Apply knowledge of hydrostatic pressure to pressure measurement
* Understand the manometer instrument
* Learn how an inclined manometer increases measurement sensitivity

# Introduction

Manometers are a class of instruments used to measure fluid pressure which make use of the **Hydrostatic Equation**:

\begin{align}
p_2 - p_1 = -\rho g \left(z_2 - z_1 \right)
\end{align}

For a fluid of known density in a constant gravity well, a change in elevation $(z_2 - z_1)$ linearly corresponds to a change in pressure.

Consider the section of pipe shown below. A liquid flows along the length of the pipe (we can assume it is much longer than shown and the flow moves entirely parallel to the pipe) and at some point along its length a hole is drilled and a small open tube is inserted normal to the upper surface. This arrangement is called a **piezometer tube**.

Shown below is a cross sectional view of the geometry. The arrows indicate the flow direction. The small vertical tube is also shown.

**What do think the value of $h$ is?**

Will the flow in the main pipe squirt out of this smaller tube? Will the flow continue along its path and ignore the perpendicular tube completely?

The answer is that for some constant velocity in the main pipe is that the flow will rise up the perpendicular tube some finite $h$ and stop once it's reached an equilibrium.
If we change the velocity in the pipe the level $h$ will change accordingly. Why? In order to increase the velocity in the pipe we have too apply more pressure.

If we are applying a positive gauge pressure at the pipe inlet and then let the pipe exit to the atmosphere (zero gauge pressure) then there will be a *pressure gradient* along the length of the pipe and the liquid will flow from high to low pressure — at a **constant velocity** (it's a little more complicated than that but more on that later). This result is that the hydrostatic pressure is constant perpendicular to the direction of flow (assuming a sufficiently small diameter). In fact if we place a series of these *'piezometer tubes'* along the length of the pipe we could visualise the pressure gradient along the pipe.

The pressure at each location $n$ can be measured by measuring the local height $h_n$:

\begin{align}
p_{abs}(x) = p_{atm} + \rho g h_{n}
\end{align}

or

\begin{align}
p_{gauge}(x) = \rho g h_{n}
\end{align}

It is the static pressure that pushes (*piezo* is greek for push) the fluid laterally into the piezometer tube. The velocity of flow moving along the pipe does not directly play a role in the height $h$. In fact in the pipe was instead a closed pressure vessel containing a static fluid as observed in our Bourdon gauge example we would observe the same behaviour. However the Piezometer tube may need to be extremely long for any practical usage!

## Pressure head

We can use the height of a column of liquid as a unit of pressure. We see this used in the Mercury Barometer where atmospheric pressure is often recorded in $mmHg$ or $inHg$. Since the height of the liquid column is dependent on the density and acceleration due to gravity (the specific weight, $\gamma = \rho g$):

\begin{align}
\text{Pressure Head} = \frac{\Delta P}{\gamma}
\end{align}

When working with a liquid in a complex flow system such as the pipe network of a chemical plant it is often convenient to work in pressure head.

## The U-tube Manometer

We've seen various arrangements of tubes used to measure fluids so far; from Boyle's *'J-tube'* to the Piezometer tube. Now we will consider another, the *'U-tube Manometer'*

If we take a U-shaped tube, open to atmosphere at both ends, and partially fill it form one side with a liquid, what happens? The fluid flows down and around and eventually reaches an equilibrium with the free surfaces in each arm perfectly level with one another.

<video controls src="media/2.2/u-tube.mp4" width="400" />

We can perhaps understand this equilibrium better if we consider the toy example shown in the video below. Owing to friction, the spheres that fall down the right side of the tube eventually come to rest. Each sphere has an identical finite mass and therefore gravity pulls on each with the same force which results in the distribution of the spheres balanced being between either side. In the case of the fluid we can, for now, think of the fluid as consisting of many many tiny spheres.

<video controls src="media/2.2/u-tube_balls.mp4" width="400" />

Gravity $g$ pulls the fluid (with density $\rho$) down evenly and atmospheric pressure which acts at each open end (which we will denote as 1 and 2) is equal. The Hydrostatic Equation is satisfied so that $p_1 = p_2$ and the height of the fluid relative to a common datum is $h_1 = h_2$.

But what if we connect one end of our U-tube to a pressure vessel as shown on the left side of the image below?

On the right is shown an illustration of the U-tube from the previous video and on the left is it after the left side is connected to the pressure vessel containing a fluid ($\gamma_1 = \rho_1 g$) at some unknown pressure $P_A$.

**Is the pressure at point A greater or less than atmospheric?
Which is the same as asking is the *gauge pressure* greater or less than zero?**

We can use the hydrostatic equation to reason that the pressure $p_2 = p_3$ since any pressure acting on the interface at point 2 will be balanced at point 3 by the atmosphere and the weight of fluid in the column $h_2$. Working in gauge pressure we offset by atmospheric pressure so that the forces on the right side are equal to $\gamma_2 h_2$. On the left side the pressure $p_A$ is invariant along the dash-dot line so the forces acting on the left side are equal to $p_{A_\text{gauge}} + \gamma_1 h_1$. Since the sum of the forces is zero:

\begin{align}
p_{A_\text{gauge}} + \gamma_1 h_1 = \gamma_2 h_2
\end{align}

which is easily rearranged:
\begin{align}
p_{A_\text{gauge}} = \gamma_2 h_2 - \gamma_1 h_1
\end{align}

This tells us that by measuring the difference in height between the two sides for two fluids of differing specific weights we can compute the pressure at point $A$ — which, remember, is at the same vertical location as point 1 and therefore at the same hydrodynamic pressure.

To answer the question, the pressure at point A is higher than atmospheric unless the density of the fluid in the tank is greater than the fluid we are using as our manometer gauge fluid — which would simply not work as the blue gauge fluid would float up through the red fluid.

### Gauge fluids

Dense liquids like water and mercury are popular gauge fluids. See here for a list of commercial gauge fluids with various specific gravity ranges

http://www.dwyer-inst.com/PDF_files/GageFluids_i.pdf

Remember, ($SG = \frac{\rho}{\rho_{H_2O}}$)

## An Example

A tank containing air and oil (SG 0.9) is connected to a mercury U-tube manometer (SG 13.8).

When $h_1 = 914~mm$, $h_2 = 152~mm$ and $h_3 = 228~mm$ **determine the pressure of the air inside the tank**.

We can start by listing the forces on each side.
On the left we have the air pressure and the hydrostaic pressure of the height of oil from the air/oil interface down to point 1.
We will consider any hydrostatic pressure in the air volume as negligible since air has a very low density.
Since $p_1 = p_2$ we can ignore the mercury below this level.
On the right we simply have the weight of $h_3~mm$ of mercury.

This gives:

$p_{air} + \gamma_{oil} (h_2 + h_1) = \gamma_{Hg} h_3 $

Rearranging:

$p_{air} = \gamma_{Hg} h_3  - \gamma_{oil} (h_2 + h_1)$

and expanding for $\gamma = \rho g = SG~\rho_{H_2O}~g$:

$p_{air} = [SG_{Hg}~\rho_{H_2O}~g] h_3  - [SG_{oil}~\rho_{H_2O}~g] (h_2 + h_1)$

```python
# Physical properties
rho_w = 1000  # kg/m^3
SG_HG = 13.6
SG_oil = 0.9
g = 9.81      # m/s/s
h_1 = 0.914   # m
h_2 = 0.152   # m
h_3 = 0.228   # m

p_air = (SG_HG * rho_w  * g * h_3) - (SG_oil * rho_w * g * (h_1 + h_2))

# print result using an f-string
print(f"Air presure is {p_air/1000:.2f} kPa")

```

    Air presure is 21.01 kPa

## Differential Manometer

We can also connect both ends of our u-tube manometer to different pressure vessels and measure the differential pressure between them; the atmosphere is just a pressure vessel after all!

Again we can equate the pressures at the bottom $p_2 = p_3$

$p_{A_{gauge}} + \gamma_1 h_1 = p_{B_{gauge}} + \gamma_3 h_3 + \gamma_2 h_2$

$p_{A_{gauge}} - p_{B_{gauge}} = \gamma_3 h_3 + \gamma_2 h_2  - \gamma_1 h_1$

## Inclined-Tube Manometer

The inclined manometer is variant of the U-tube manometer used for increased sensitivity when measuring small pressure differences. This greater sensitivity is due to the fact that the gauge fluid is moved over a greater length of tubing on the inclined side for the same vertical displacement on the vertical side. Once again we can gain some intuition by considering a toy model with some spheres in place of the fluid. Here our manometer tube is vertical on the right and inclined at an angle of $30^\circ$ on the right.

<video controls src="media/2.2/inclined_manometer_balls.mp4" width="800" />

The image below shows the final frame of the video when the system has reached equilibrium.
The vertical height of the spheres is equal on each side. Two red laser beams marking the topmost spheres are used to illustrate this.

Now lets consider the case where there is a force sufficient to move the right side down by one sphere diameter. Our laser beams remain fixed and clearly demonstrate that on the left side the spheres are also displaced one diameter vertically. However the topmost sphere on the left is also displaced significantly to the left. Note also that since the top level of the spheres is now one diameter lower the balance of equal force is also one diameter lower.

The result of all of this is apparent from the measurement checker board placed next to each 'surface'. For the current angle, a 4 unit (1 diameter) vertical reduction on the right corresponds to a 12 unit (3 diameters) displacement along the inclined tube a 3:1 ratio. A regular u-tube manometer gives us a 2:1 ratio.

### Lets look at this a bit more rigorously

In the image below, measurements (in this case differential pressure between $p_A$ and $p_B$) are read along a scale on the inclined tube by reading the level of the gauge fluid ($\gamma_3$)

In the upper image the pressure difference between $p_A$ and $p_B$ results in the gauge fluid moving a distance $l$ along the incline relative to $h_B$.
The sum of the forces is:

\begin{equation*}
P_A + \gamma_{1}~h_A + \gamma_{3}~l \sin{\theta} = P_B + \gamma_{2}~(h_B)
\end{equation*}

If, as shown in the lower part of the image, the pressure $p_A$ is reduced by $\Delta P$ the length of gauge fluid $l$ will increase by distance $\alpha$. Accordingly the height $h_A$ will have to decrease by $\alpha \sin{\theta}$ and the the height $h_B$ will increase by $\Delta h_B = \alpha$. The sum of the forces becomes:

\begin{equation*}
[P_A - \Delta P] +
\underbrace{\gamma_{1}~[h_A - \alpha \sin{\theta}]}_\text{fluid 1} +
\underbrace{\gamma_{3}~[(l+\alpha) \sin{\theta} + \alpha]}_\text{gauge fluid} =
P_B +
\underbrace{\gamma_{2}~[h_B + \alpha]}_\text{fluid 2}
\end{equation*}

It is very important to observe that the measured distance along the inclined portion of the manometer is given by the distance $\alpha + l + \alpha/\sin{\theta}$ as the increase in $h_B$ to $h_B + \alpha$ shifts the dash-dot line of equal pressure in the gauge fluid down. This is clearly apparent in the toy example above where there is a three fold increase in the displacement of the fluid on the inclined side of the manometer.

### Example
Determine the new differential reading along the inclined leg of the mercury manometer if the pressure in pipe A is decreased 10 kPa and the pressure in pipe B remains unchanged.
The fluid in A has a specific gravity of 0.9 and the fluid in B is water.

We can write the force balance for both sides of the manometer in its initial state:

\begin{equation}
P_A + \gamma_{A}~(0.1) + \gamma_{Hg}~(0.05) \sin{30^\circ} = P_B + \gamma_{B}~(0.08) .
\end{equation}

Rewriting the balance with a $10kPa$ pressure reduction in $P_A$:

\begin{align}
[P_A - 10\times 10^{3}] + \gamma_{A}[({0.1-\alpha \sin{30^\circ}})] +
 \gamma_{Hg}[({\alpha \sin{30^\circ} + l\sin{30^\circ} + \alpha})] =
 P_B + \gamma_{B}[({0.08 + \alpha})]
\end{align}

Subtracting $2$ from $1$:

\begin{equation*}
-10\times 10^{3} - \gamma_A({~\alpha \sin{30^\circ}}) + \gamma_{Hg}({\alpha\sin{30^\circ} + \alpha}) = \gamma_{B}({\alpha})
\end{equation*}

and solving for $\alpha$
\begin{equation*}
\alpha = \frac{-10\times10^{3}}{\gamma_B + \gamma_A ({\sin{30^\circ}}) + \gamma_{Hg}({\sin{30^\circ}+1})} = 0.054~m
\end{equation*}

The differential pressure is read along the inclined portion of the manometer so we need to calculate the total change in length $L$ of mercury along it.

\begin{equation*}
L \sin{\theta} = [\alpha + l\sin(\theta) + \alpha\sin(\theta)]
\end{equation*}

\begin{equation*}
L  = \left[\frac{\alpha}{\sin{\theta}} + l + \alpha\right]
\end{equation*}

The resulting differential reading is then:

\begin{equation*}
\frac{\alpha}{\sin{30^\circ}} + \alpha + 0.05 = \frac{0.054}{\sin{30^\circ}}+0.054+0.05=0.215m
\end{equation*}

We can also solve this graphically, accounting for positive or negative changes in $\Delta P$.