# Intial data generation trials in 2D

```python
import sys  # isort:skip

sys.path.insert(0, "../")  # isort:skip

import pickle
from math import pi

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import sympy
from sympy import cos, sin, symbols

from synthetic_data.synthetic_data import make_tabular_data
```

### Without correlation

```python
# define symbols
x1, x2 = symbols("x1 x2")

# define expression
expr = cos(x1 ** 2 * pi / 180.0) - sin(x2 * pi / 180.0) + x1 * x2

# define mapping from symbols to column of X - clunky TODO - make it better
col_map = {x1: 0, x2: 1}

```

```python
# define correlations via covariance matrix
cov = np.array([[1.0, 0.0], [0.0, 1.0]])
```

```python
n_samples = 1000
p_thresh = 0.5   # probability threshold to divide class 0 from class 1
# every other parameter is going to use the default - check docstring for completion
```

```python
X, y_reg, y_prob, y_label = make_tabular_data(n_samples=n_samples, cov=cov, col_map=col_map, expr=expr, p_thresh=p_thresh)

```

### With correlation

```python
cov_c = np.array([[1.0, 0.5], [0.5, 1.0]])
```

```python
X_c, y_reg_c, y_prob_c, y_label_c = make_tabular_data(n_samples=n_samples, cov=cov_c, col_map=col_map, expr=expr, p_thresh=p_thresh)
```

### Let's see what we got!
#### Uncorrelated inputs

```python
h = sns.jointplot(X[:, 0], X[:, 1], kind="hex", stat_func=None)
h.set_axis_labels("x1", "x2", fontsize=16)
```

#### Correlated inputs

```python
h = sns.jointplot(X_c[:, 0], X_c[:, 1], kind="hex", stat_func=None)
h.set_axis_labels("x1", "x2", fontsize=16)
```

## Let's check the impact on y_reg

```python
levels = np.arange(0, 2.2, 0.2)
fig, ax = plt.subplots(nrows = 1, ncols=2, figsize=(16, 7))
tri1 = ax[0].tricontourf(X[:, 0], X[:, 1], y_reg, levels=levels)
scatter = ax[0].scatter(X[:, 0], X[:, 1], c='k', label=y_label, marker=".")
leg1 = ax[0].legend(*scatter.legend_elements(), loc="lower right", title="class")
#cbar1 = fig.colorbar(tri1, ax=ax)
ax[0].set_title("No correlation")
ax[0].set_xlabel("x1")
ax[0].set_ylabel("x2")
ax[0].axis('equal')
#cbar1.formatter.set_powerlimits((0, 0))
#cbar1.update_ticks()

tri1 = ax[1].tricontourf(X_c[:, 0], X_c[:, 1], y_reg_c, levels=levels)
scatter = ax[1].scatter(X_c[:, 0], X_c[:, 1], c='k', label=y_label, marker=".")
leg1 = ax[1].legend(*scatter.legend_elements(), loc="lower right", title="class")
cbar1 = fig.colorbar(tri1, ax=ax)
ax[1].set_title("With correlation")
ax[1].set_xlabel("x1")
ax[1].axis('equal')
#ax[1].set_ylabel("x2")
#cbar1.formatter.set_powerlimits((0, 0))
cbar1.update_ticks()

```

You can see that the positive correlation has shifted density to the lower left and upper right.
But the contours remain in the same location (which is intended - the function f(X) doesn't change because we have correlation).

## Similar plots but with y_prob = sigmoid(y_reg)

This is the last step in the creation of our binary class labels.

```python
levels = np.arange(0, 2.2, 0.2)
fig, ax = plt.subplots(nrows = 1, ncols=2, figsize=(16, 7))
tri1 = ax[0].tricontourf(X[:, 0], X[:, 1], y_prob, levels=levels)
scatter = ax[0].scatter(X[:, 0], X[:, 1], c=y_label, label=y_label, marker=".")
leg1 = ax[0].legend(*scatter.legend_elements(), loc="lower right", title="class")
#cbar1 = fig.colorbar(tri1, ax=ax)
ax[0].set_title("No correlation")
ax[0].set_xlabel("x1")
ax[0].set_ylabel("x2")
ax[0].axis('equal')
#cbar1.formatter.set_powerlimits((0, 0))
#cbar1.update_ticks()

tri1 = ax[1].tricontourf(X_c[:, 0], X_c[:, 1], y_prob_c, levels=levels)
scatter = ax[1].scatter(X_c[:, 0], X_c[:, 1], c=y_label_c, label=y_label, marker=".")
leg1 = ax[1].legend(*scatter.legend_elements(), loc="lower right", title="class")
cbar1 = fig.colorbar(tri1, ax=ax)
ax[1].set_title("With correlation")
ax[1].set_xlabel("x1")
ax[1].axis('equal')
#ax[1].set_ylabel("x2")
#cbar1.formatter.set_powerlimits((0, 0))
cbar1.update_ticks()

```

### Wot?
Why don't our probabilities (and our labels) agree between the two plots?
Answer: the sigmoid function has a parameter x_0 that 'centers' the sigmoid.
The default choice is `x_0 = np.mean(<some y_reg type vector here>)`

```python
np.mean(y_reg)
```

    0.9909529

```python
np.mean(y_reg_c)
```

    1.1620378

Those means are not the same, so the sigmoid won't be 'centered' the same way between the two datasets.

### Q: Are we generating balanced classes?

```python
np.unique(y_label, return_counts=True)
```

    (array([False,  True]), array([501, 499]))

```python
np.unique(y_label_c, return_counts=True)
```

    (array([False,  True]), array([583, 417]))

The baseline case with no correlation is balanced.  But the correlated case is skewed toward class 0.  So let's fix the value we use to center the sigmoid and recalculate...maybe that will rebalance the classes?

```python
x_0_baseline = np.mean(y_reg)
```

```python
X_3, y_reg_3, y_prob_3, y_label_3 = make_tabular_data(n_samples=n_samples, cov=cov_c, col_map=col_map, expr=expr, sig_x0=x_0_baseline, p_thresh=p_thresh)
```

```python
levels = np.arange(0, 2.2, 0.2)
fig, ax = plt.subplots(nrows = 1, ncols=2, figsize=(16, 7))
tri1 = ax[0].tricontourf(X[:, 0], X[:, 1], y_prob, levels=levels)
scatter = ax[0].scatter(X[:, 0], X[:, 1], c=y_label, label=y_label, marker=".")
leg1 = ax[0].legend(*scatter.legend_elements(), loc="lower right", title="class")
#cbar1 = fig.colorbar(tri1, ax=ax)
ax[0].set_title("No correlation")
ax[0].set_xlabel("x1")
ax[0].set_ylabel("x2")
ax[0].axis('equal')
#cbar1.formatter.set_powerlimits((0, 0))
#cbar1.update_ticks()

tri1 = ax[1].tricontourf(X_3[:, 0], X_3[:, 1], y_prob_3, levels=levels)
scatter = ax[1].scatter(X_3[:, 0], X_3[:, 1], c=y_label_3, label=y_label, marker=".")
leg1 = ax[1].legend(*scatter.legend_elements(), loc="lower right", title="class")
cbar1 = fig.colorbar(tri1, ax=ax)
ax[1].set_title("With correlation - and fixed sig_x0")
ax[1].set_xlabel("x1")
ax[1].axis('equal')
#ax[1].set_ylabel("x2")
#cbar1.formatter.set_powerlimits((0, 0))
cbar1.update_ticks()
```

### TADA!

```python
np.unique(y_label, return_counts=True)
```

    (array([False,  True]), array([501, 499]))

```python
np.unique(y_label_3, return_counts=True)
```

    (array([False,  True]), array([305, 695]))

...but now we have significantly unbalanced the classes....

```python

```


# Convexity of the ohmic model

(see also handwritten notes)

PH, July 2020

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

```python
import sympy
from sympy import symbols, simplify, sqrt, lambdify
```

```python
e, y, R, v0 = symbols('e y R v0', positive=True)
p, x = symbols('p x')
```

## Convexity in the power

Correction factor c(i)

```python
c = 2/(1+sqrt(1+4*p*R/v0**2))
c
```

$\displaystyle \frac{2}{\sqrt{\frac{4 R p}{v_{0}^{2}} + 1} + 1}$

```python
c.subs({R:1, v0:1, p:0}), c.subs({R:1, v0:1, p:-1/4}), c.subs({R:1, v0:1, p:+1/4})
```

    (1, 2, 0.828427124746190)

```python
i = p/v0*c
i
```

$\displaystyle \frac{2 p}{v_{0} \left(\sqrt{\frac{4 R p}{v_{0}^{2}} + 1} + 1\right)}$

Normarlized current, with $x = 4p$ (so $x > -1 $)

```python
i_n = i.subs({R:1, v0:1, p:x/4})*2
i_n
```

$\displaystyle \frac{x}{\sqrt{x + 1} + 1}$

Joule losses

```python
pJ = R*i**2
pJ
```

$\displaystyle \frac{4 R p^{2}}{v_{0}^{2} \left(\sqrt{\frac{4 R p}{v_{0}^{2}} + 1} + 1\right)^{2}}$

normalized Joule losses

```python
pJn = i_n**2
pJn
```

$\displaystyle \frac{x^{2}}{\left(\sqrt{x + 1} + 1\right)^{2}}$

First derivative

```python
pJn.diff(x,1)
```

$\displaystyle - \frac{x^{2}}{\sqrt{x + 1} \left(\sqrt{x + 1} + 1\right)^{3}} + \frac{2 x}{\left(\sqrt{x + 1} + 1\right)^{2}}$

second derivative: difficult to proove it is ≥ 0!

```python
d2pJn = pJn.diff(x,2)
d2pJn
```

$\displaystyle \frac{\frac{x^{2} \left(\frac{3}{\left(x + 1\right) \left(\sqrt{x + 1} + 1\right)} + \frac{1}{\left(x + 1\right)^{\frac{3}{2}}}\right)}{2 \left(\sqrt{x + 1} + 1\right)} - \frac{4 x}{\sqrt{x + 1} \left(\sqrt{x + 1} + 1\right)} + 2}{\left(\sqrt{x + 1} + 1\right)^{2}}$

Focus on numerator:

```python
denom = (sqrt(x + 1) + 1)**4 * 2 * (x+1)**2 # always >0
simplify(d2pJn*denom )
```

$\displaystyle x^{2} \sqrt{x + 1} + 4 x^{2} + 8 x \sqrt{x + 1} + 12 x + 8 \sqrt{x + 1} + 8$

Comment : proof of positivity is _yet to be done_, but seems achievable!

### Plots: current, Joule losses, 2nd derivative

Note: the normalized plots here do not use the same normalization as in the sympy expression. In the plots, normalization is such that:

- $p$ is defined as $>-1$ (like $x$ above)
- $i \approx p$ around 0 (while $i\approx p/2$ above)
- $p_J \approx p^2$ around 0 (while $p_J \approx p^2/4$ above)

```python
x_vec = np.linspace(-0.9999, 2, 300)
i_vec = x_vec * 2/(1+np.sqrt(1+x_vec))

fig, ax = plt.subplots()
ax.axvline(0, color='k', lw=1)
ax.axhline(0, color='k', lw=1)
ax.axvline(-1, color='tab:red', lw=2, ls=':', label='discharge limit')

ax.plot(x_vec, i_vec)
ax.plot(x_vec, x_vec, 'C0:', label='$v=v_0$ approx.')

ax.legend()
ax.set(
    title = 'Current i(p), normalized',
    xlabel = 'p (normalized)'
)
ax.grid()
```

```python
pJ_vec = i_vec**2

fig, ax = plt.subplots()
ax.axvline(0, color='k', lw=1)
ax.axhline(0, color='k', lw=1)
ax.axvline(-1, color='tab:red', lw=2, ls=':', label='discharge limit')

ax.plot(x_vec, pJ_vec)
ax.plot(x_vec, x_vec**2, 'C0:', label='$v=v_0$ approx.')

ax.legend()
ax.set(
    title = 'Joule losses pJ(p)',
    xlabel = 'p',
    ylim = (-0.5, 4.1)
)
ax.grid()

fig.savefig('Joule losses pJ(p).png', dpi=200, bbox_inches='tight')
```

```python
d2pJ_fun(0)
```

    0.5

```python
d2pJ_fun = lambdify(x, 4*d2pJn, modules=np)

fig, ax = plt.subplots()
ax.axvline(0, color='k', lw=1)
ax.axhline(0, color='k', lw=1)
ax.axvline(-1, color='tab:red', lw=2, ls=':', label='discharge limit')

ax.plot(x_vec, d2pJ_fun(x_vec))

ax.legend()
ax.set(
    title = 'Second derivative of pJ(p)',
    xlabel = 'p',
    ylim=(-1,10)
)
ax.grid()
```

## Effect of $v_0$ variation with SoE

Two classical options:

- $v_0 = \sqrt{2E/C}$ (Super caps)
- $v_0 = v_{00} + a.E$ (affine approximation for battery OCV)

TO BE STUDIED... 21/07/20

### Supercaps

```python
pJnSC = pJ.subs({R:1, p:x/4, v0:sqrt(y)})*4
pJnSC
```

$\displaystyle \frac{x^{2}}{y \left(\sqrt{\frac{x}{y} + 1} + 1\right)^{2}}$

```python
pJnSC.diff(y,2)
```

$\displaystyle \frac{x^{2} \left(\frac{x \left(\frac{3 x}{y \left(\frac{x}{y} + 1\right) \left(\sqrt{\frac{x}{y} + 1} + 1\right)} + \frac{x}{y \left(\frac{x}{y} + 1\right)^{\frac{3}{2}}} - \frac{4}{\sqrt{\frac{x}{y} + 1}}\right)}{2 y \left(\sqrt{\frac{x}{y} + 1} + 1\right)} - \frac{2 x}{y \sqrt{\frac{x}{y} + 1} \left(\sqrt{\frac{x}{y} + 1} + 1\right)} + 2\right)}{y^{3} \left(\sqrt{\frac{x}{y} + 1} + 1\right)^{2}}$


# Section 5.5 $\quad$ Orthogonal Complements

>**Definition** Let $W$ be a subspace of an inner product space $V$.
- A vector $\mathbf{u}$ in $V$ is said to be **orthogonal to** W if

- The set of all vectors in $V$ that are orthogonal to all vectors in $W$ is called $\underline{\hspace{2in}}$ of $W$ in $V$, and is denoted by $\underline{\hspace{1in}}$.

### Example 1

Let $W$ be the subspace spanned by the vector
\begin{equation*}
  \mathbf{w} = \left[\begin{array}{c}2\\ -3\\ 4\\\end{array}\right].
\end{equation*}
- Find the orthogonal complement $W^\perp$ of $W$.
- Find a basis for $W^\perp$.

```python
from sympy import *

A = Matrix([[2, -3, 4], [1, 0, 0], [0, 1, 0]]).T;
B = A.QRdecomposition()[0];
B.col_del(0);

B
```

    Matrix([
    [  5*sqrt(29)/29,   0],
    [ 6*sqrt(29)/145, 4/5],
    [-8*sqrt(29)/145, 3/5]])

>Let $W$ be a subspace of an inner product space $V$.
- $W^{\perp}$

- $W\cap W^{\perp} = $

- $V = $

### Example 2

Let $W$ be the subspace of $\mathbb{R}_5$ spanned by $\mathbf{w}_1,\mathbf{w}_2,\cdots, \mathbf{w}_5$ where
\begin{equation*}
  \mathbf{w_1} = [2~~-1~~0~~1~~2],\quad\mathbf{w_2} = [1~~3~~1~~-2~~-4],\quad\mathbf{w_3} = [3~~2~~1~~-1~~-2],
\end{equation*}
\begin{equation*}
  \mathbf{w_4} = [7~~7~~3~~-4~~-8],\quad\mathbf{w_5} = [1~~-4~~-1~~-1~~-2].
\end{equation*}
Find a basis for $W^\perp$.

```python
from sympy import *

W = Matrix([[2, -1, 0, 1, 2], [1, 3, 1, -2, -4], [3, 2, 1, -1, -2], [7, 7, 3, -4, -8], [1, -4, -1, -1, -2]]);
A = W.rref()[0];
print(A);
A = A.T;
A[:,3] = [1, 0, 0, 0, 0];
A[:,4] = [0, 0, 0, 1, 0];
B = A.QRdecomposition()[0];
B.col_del(0);
B.col_del(0);
B.col_del(0);

B
```

    Matrix([[1, 0, 1/7, 0, 0], [0, 1, 2/7, 0, 0], [0, 0, 0, 1, 2], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]])

    Matrix([
    [   sqrt(6)/18,           0],
    [    sqrt(6)/9,           0],
    [-7*sqrt(6)/18,           0],
    [            0, 2*sqrt(5)/5],
    [            0,  -sqrt(5)/5]])

## Projections

>Let $W$ be a subspace of an inner product space $V$ with orthonormal basis $\{\mathbf{w}_1,\mathbf{w}_2,\cdots, \mathbf{w}_m\}$. For any vector $\mathbf{v}$ in $V$, there exist

Moreover, the vector $\mathbf{w}$ can be written as

which is called the

**Remark** If $\{\mathbf{w}_1,\mathbf{w}_2,\cdots, \mathbf{w}_m\}$ is an orthogonal basis for $W$, then

## An illustration of orthogonal projection

The **distance** from $\mathbf{v}$ to the subspace $W$ is given by

### Example 3

Let $W$ be the subspace of $\mathbb{R}^3$ with orthonormal basis $\{\mathbf{w}_1,\mathbf{w}_2\}$ where
\begin{equation*}
  \mathbf{w_1} = \left[\begin{array}{c}\dfrac{2}{3}\\ -\dfrac{1}{3}\\ -\dfrac{2}{3}\\ \end{array}\right],~~~
\mathbf{w_2} = \left[\begin{array}{c}\dfrac{1}{\sqrt{2}}\\ 0\\ \dfrac{1}{\sqrt{2}}\\\end{array}\right].
\end{equation*}
Find the orthogonal projection  of $\mathbf{v} = \left[\begin{array}{c}2\\ 1\\ 3\\ \end{array}\right]$. Find the distance from $\mathbf{v}$ to $W$.

```python
from numpy import *

w1 = array([2/3, -1/3, -2/3]);
w2 = array([1/sqrt(2), 0, 1/sqrt(2)]);
v = array([2, 1, 3]);
vp = dot(v, w1)*w1 + dot(v, w2)*w2

vp, linalg.norm(v - vp)
```

    (array([ 1.83333333,  0.33333333,  3.16666667]), 0.70710678118654757)


```python
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
sns.set_theme()

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning)
```

```python
# create datasets
def generate_in_distribution_data(n, mu, sigma, pi_in):
    n_1 = int(n*pi_in)
    n_0 = int(n*(1-pi_in))
    mu_1 = mu
    mu_0 = -mu_1
    X_0 = np.random.normal(mu_0, sigma, n_0)
    X_1 = np.random.normal(mu_1, sigma, n_1)
    X = np.concatenate((X_0, X_1))
    Y = np.concatenate((np.zeros(n_0), np.ones(n_1)))
    return X, Y

def generate_out_distribution_data(n, mu, sigma, pi_out, delta):
    n_1 = int(n*pi_out)
    n_0 = int(n*(1-pi_out))
    mu_1 = mu + delta
    mu_0 = -mu + delta
    X_0 = np.random.normal(mu_0, sigma, n_0)
    X_1 = np.random.normal(mu_1, sigma, n_1)
    X = np.concatenate((X_0, X_1))
    Y = np.concatenate((np.zeros(n_0), np.ones(n_1)))
    return X, Y
```

## Single-Head LDA

Consinder a univariate binary classification task with normal distributions as class condtional densities $f_0$ and $f_1$, parameterized by $(\mu_0, \sigma^2)$ and $(\mu_1, \sigma^2)$. If the class 1 prior probability is $\pi$, the Baye's decision rule for this problem is given by,

$$
g(x) =
    \begin{cases}
        1 & \pi f_1 > (1-\pi)f_0 \\
        0 & \text{otherwise}
    \end{cases}
$$

It can be shown that this decision rule can be written in terms of the above parameters as below.

$$
g(x) =
    \begin{cases}
        1 & x > \frac{\mu_0 + \mu_1}{2} + \frac{\sigma^2}{\mu_1 - \mu_0} \log \frac{1-\pi}{\pi} \\
        0 & \text{otherwise}
    \end{cases}
$$

When class priors are equal, the decision rule is simply:

$$
g(x) =
    \begin{cases}
        1 & x > \frac{\mu_0 + \mu_1}{2}\\
        0 & \text{otherwise}
    \end{cases}
$$

In terms of estimates, the threshold can be written as,

$$ c = \frac{\bar{X}_0 + \bar{X}_1}{2} $$

In the single-head model, we feed both target and OOD data (both of which have the same label distribution) into the algorithm. Thus, $\bar{X}_0, \bar{X}_1$ are estimated using both target and OOD data.

```python
def compute_singlehead_decision_rule(X_in, Y_in, X_out, Y_out):

    X = np.concatenate((X_in, X_out))
    Y = np.concatenate((Y_in, Y_out))

    X_0 = X[Y == 0]
    X_0_bar = np.mean(X_0)

    X_1 = X[Y == 1]
    X_1_bar = np.mean(X_1)

    # estimate threshold
    c = (X_1_bar + X_0_bar)/2

    return c

def compute_empirical_risk(X, Y, c):
    Y_pred = (X > c).astype('int')
    risk = 1 - np.mean(Y_pred == Y)
    return risk

def run_simulation(
    n = 4,
    n_test = 500,
    mu = 1,
    sigma = 1,
    pi_in = 0.5,
    pi_out = 0.5,
    Delta = [0, 0.2, 0.5, 0.8, 1, 1.5, 2],
    m_sizes = np.arange(0, 21, 1),
    reps = 1000,
):
    X_test, y_test = generate_in_distribution_data(n_test, mu, sigma, pi_in)

    df = pd.DataFrame()

    i = 0
    for m in m_sizes:
        for r, rep in enumerate(range(reps)):
            df.at[i, "m"] = m
            df.at[i, "r"] = r
            X_in, Y_in = generate_in_distribution_data(n, mu, sigma, pi_in)

            for delta in Delta:
                X_out, Y_out = generate_out_distribution_data(m, mu, sigma, pi_out, delta)
                c = compute_singlehead_decision_rule(X_in, Y_in, X_out, Y_out)
                df.at[i, str(delta)] = compute_empirical_risk(X_test, y_test, c)
            i+=1

    dfm = df.melt(['m', 'r'], var_name='Delta', value_name='Risk')
    fig, ax = plt.subplots(figsize=(10, 10), facecolor='white')
    ax = sns.lineplot(data=dfm, x="m", y="Risk", hue="Delta", ax=ax, markers=True, ci=95, lw=2)
    ax.set_ylabel("Expected Risk")
    ax.set_xlabel(r"$m/n, n={}$".format(n))
    # ax.set_xlim([0, 100])
    return df
```

### Simulations

```python
df = run_simulation(
    n = 6,
    n_test = 500,
    mu = 1,
    sigma = 1,
    pi_in = 0.5,
    pi_out = 0.5,
    Delta = [0, 0.2, 0.5, 0.8, 1, 1.5, 2],
    m_sizes = np.arange(0, 40, 2),
    reps = 1000,
)
```

### Theoretical Analysis

#### Analytical Computed Expected Risk Curves

```python
def expected_risk(n, m, delta):
    mu_h = (m/(n+m))*delta
    sigma_h = np.sqrt(3/(n+m))
    risk = 0.5*(1 + norm.cdf((mu_h - 1)/(np.sqrt(1 + sigma_h**2))) - norm.cdf((mu_h + 1)/(np.sqrt(1 + sigma_h**2))))
    return risk
```

```python
n = 6
m_sizes = np.arange(0, 40, 0.1)
Delta = [0, 0.2, 0.5, 0.8, 1, 1.5, 2]
i = 0
df = pd.DataFrame()
for m in m_sizes:
    df.at[i, "m"] = m
    for delta in Delta:
        df.at[i, str(delta)] = expected_risk(n, m, delta)
    i+=1

dfm = df.melt(['m'], var_name='Delta', value_name='Risk')
fig, ax = plt.subplots(figsize=(10, 10), facecolor='white')
ax = sns.lineplot(data=dfm, x="m", y="Risk", hue="Delta", ax=ax, markers=True, ci=95, lw=2)
ax.set_ylabel("Expected Risk")
ax.set_xlabel(r"$m/n, n={}$".format(n))
# ax.set_xlim([0, 100])

```

#### Optimal OOD Sample Size Curves across $n$ and $\Delta$

```python
n_sizes = np.arange(4, 20, 1)
m_sizes = np.arange(0, 100, 0.1)
Delta = np.arange(0, 2, 0.01)

df = pd.DataFrame()
dict = {}
for n in n_sizes:
    m_opt = []
    for delta in Delta:
        risk_vals = [expected_risk(n, m, delta) for m in m_sizes]
        m_opt.append(m_sizes[np.argmin(risk_vals)])
    dict[str(n)] = m_opt

df = pd.DataFrame.from_dict(dict)
df['Delta'] = Delta
```

```python
dfm = df.melt(['Delta'], var_name='n', value_name='m_opt')
fig, ax = plt.subplots(figsize=(10, 10), facecolor='white')
ax = sns.lineplot(data=dfm, x='Delta', y='m_opt', hue='n', ax=ax, lw=2)
ax.set_ylabel("Optimal OOD Sample Size")
ax.set_xlabel(r"$\Delta$")
```

### Training with $\alpha$-Weighting

Let $D_{in} : \{ X_i, Y_i \}_{i=1}^n \stackrel{iid}{\sim} \mathcal{P}_t$ and $D_{out} : \{ X_i, Y_i \}_{i=1}^m \stackrel{iid}{\sim} \mathcal{P}_o$, where $\mathcal{P}_o$ is characterised by $X|Y=y \sim \mathcal{N}(\mu_0 - (-1)^y, 1), \; Y \sim \text{Bern}(0.5)$ and $\mathcal{P}_o$ is characterised by  $X|Y=y \sim \mathcal{N}(\mu_0 + \Delta - (-1)^y, 1), \; Y \sim \text{Bern}(0.5)$. We are interested in improving an FLD's generalization error on the target task using data from both the target and OOD tasks. The FLD decision rule for the target task is given by,
$$
\begin{equation}
        h(x) =
        \begin{cases}
            1 & x > \mu_0 \\
            0 & \text{otherwise}
        \end{cases}
\end{equation}
$$
The likelihood function for $\mu_0$ can be written as follows.
$$
\begin{equation}
    L(\mu_0) = f_{XY}((x_1, y_1), \dots, (x_n, y_n))^{\alpha} f_{XY}((x_1, y_1), \dots, (x_m, y_m))^{1-\alpha}
\end{equation}
$$
where $\alpha$ denotes the weighting coefficient and $f_{XY}$ is given by,
$$
\begin{equation}
    f_{XY}(x,y) = f_{X|Y}(x|y)f_Y(y) = \frac{1}{\sqrt{2\pi}} \exp \bigg[ -\frac{1}{2} (x - \mu_0 + (-1)^y)^2 \bigg] 0.5^y (0.5)^{1-y}
\end{equation}
$$
Maximizing the $L(\mu_0)$, we arrive at the maximum likelihood estimator for $\mu_0$ (proof omitted),
$$
\begin{equation}
    \hat{\mu}_0 = \frac{\alpha \sum_{i=1}^n (X_i + (-1)^{Y_i}) + (1-\alpha) \sum_{j=1}^m (X_j + (-1)^{Y_j})}{\alpha n + (1-\alpha)m}
\end{equation}
$$
The expected value and variance of $\hat{\mu}_0$ are respectively given by (proof omitted),
$$
\begin{equation}
    \mu_h = \mathbb{E}[\hat{\mu}_0] = \mu_0 + \frac{(1-\alpha)m}{\alpha n + (1-\alpha)m} \Delta \sigma
\end{equation}
$$
$$
\begin{equation}
        \sigma_h^2 = \text{Var}[\hat{\mu}_0] = 3 \bigg[ \frac{\alpha^2 n + (1-\alpha)^2 m}{(\alpha n + (1-\alpha)m)^2} \bigg]
\end{equation}
$$
Therefore, assuming $\mu_0 = 0$, the expected risk for this problem is given by,
$$
\begin{equation}
\mathcal{E}_t(h) = \frac{1}{2}\bigg[ 1 + \Phi \bigg( \frac{\mu_h - 1}{\sqrt{1 + \sigma_h^2}} \bigg)  - \Phi \bigg( \frac{\mu_h + 1}{\sqrt{1 + \sigma_h^2}} \bigg)\bigg]
\end{equation}
$$

```python
def expected_risk(n, m, alpha, delta):
    mu_h = ((1-alpha)*m/(alpha*n + (1-alpha)*m))*delta
    sigma_h = np.sqrt(3*(alpha**2*n + (1-alpha)**2*m)/(alpha*n + (1-alpha)*m)**2)
    risk = 0.5*(1 + norm.cdf((mu_h - 1)/(np.sqrt(1 + sigma_h**2))) - norm.cdf((mu_h + 1)/(np.sqrt(1 + sigma_h**2))))
    return risk
```

```python
n = 6
m_sizes = np.arange(3, 40, 0.1)
delta = 0.8
alpha_range = np.arange(0, 1.2, 0.2)
i = 0
df = pd.DataFrame()
for m in m_sizes:
    df.at[i, "m"] = m
    for alpha in alpha_range:
        df.at[i, str(alpha)] = 1 - expected_risk(n, m, alpha, delta)
    i+=1
```

```python
dfm = df.melt(['m'], var_name='Alpha', value_name='Risk')
fig, ax = plt.subplots(figsize=(10, 10), facecolor='white')
ax = sns.lineplot(data=dfm, x="m", y="Risk", hue="Alpha", ax=ax, markers=True, ci=95, lw=2)
ax.set_ylabel("Expected Risk")
ax.set_xlabel(r"$m/n, n={}$".format(n))
# ax.set_xlim([0, 100])
```

## Multi-Head LDA

Since the data is univariate, the idea of finding a projection term lacks meaning. The following expressions for the projection term and threshold are considered (inspired by the bivariate multihead LDA)

$$ \omega = \frac{S_b}{S_w} ?? $$

$$ c_{in} = \omega \frac{\bar{X}_{t0} + \bar{X}_{t1}}{2} $$

$$ \hat{\pi} = \frac{n_1}{n} $$

The final decision rule is given by,

$$
g(x) =
    \begin{cases}
        1 & x > \frac{\bar{X}_{t0} + \bar{X}_{t1}}{2} + \frac{1}{\omega} \log \frac{1-\hat{\pi}}{\hat{\pi}} \\
        0 & \text{otherwise}
    \end{cases}
$$

```python
def compute_multihead_decision_rule(X_in, Y_in, X_out, Y_out):

    X = np.concatenate((X_in, X_out))
    Y = np.concatenate((Y_in, Y_out))
    X_bar = np.mean(X)
    X_centered = X - X_bar
    S_b = np.sum(X_centered**2)/len(Y)

    X_in_0 = X_in[Y_in==0]
    X_in_0_bar = np.mean(X_in_0)
    X_centered = X_in_0 - X_in_0_bar
    S_w_in_0 = np.sum(X_centered**2)

    X_in_1 = X_in[Y_in==1]
    X_in_1_bar = np.mean(X_in_1)
    X_centered = X_in_1 - X_in_1_bar
    S_w_in_1 = np.sum(X_centered**2)

    X_out_0 = X_out[Y_out==0]
    X_out_0_bar = np.mean(X_out_0)
    X_centered = X_out_0 - X_out_0_bar
    S_w_out_0 = np.sum(X_centered**2)

    X_out_1 = X_out[Y_out==1]
    X_out_1_bar = np.mean(X_out_1)
    X_centered = X_out_1 - X_out_1_bar
    S_w_out_1 = np.sum(X_centered**2)

    S_w = (S_w_in_0 + S_w_in_1 + S_w_out_0 + S_w_out_1)/len(Y)

    # estimate projection term
    omega = S_b/S_w

    # estimate target priors
    p_in = len(Y_in[Y_in==1])/len(Y_in)

    # estimate threshold
    h = (X_in_1_bar + X_in_0_bar)/2 + (1/omega)*np.log((1-p_in)/p_in)

    return h

def compute_empirical_risk(X, Y, h):
    Y_pred = (X > h).astype('int')
    risk = 1 - np.mean(Y_pred == Y)
    return risk

def run_simulation(
    n = 4,
    n_test = 500,
    mu = 1,
    sigma = 1,
    pi_in = 0.5,
    pi_out = 0.5,
    Delta = [0, 0.2, 0.5, 0.8, 1, 1.5, 2],
    m_sizes = np.arange(0, 21, 1),
    reps = 1000,
):
    X_test, y_test = generate_in_distribution_data(n_test, mu, sigma, pi_in)

    df = pd.DataFrame()

    i = 0
    for m in m_sizes:
        for r, rep in enumerate(range(reps)):
            df.at[i, "m"] = m
            df.at[i, "r"] = r
            X_in, Y_in = generate_in_distribution_data(n, mu, sigma, pi_in)

            for delta in Delta:
                X_out, Y_out = generate_out_distribution_data(m, mu, sigma, pi_out, delta)
                c = compute_multihead_decision_rule(X_in, Y_in, X_out, Y_out)
                df.at[i, str(delta)] = compute_empirical_risk(X_test, y_test, c)
            i+=1

    dfm = df.melt(['m', 'r'], var_name='Delta', value_name='Risk')
    fig, ax = plt.subplots(figsize=(10, 10), facecolor='white')
    ax = sns.lineplot(data=dfm, x="m", y="Risk", hue="Delta", ax=ax, markers=True, ci=95, lw=2)
    ax.set_ylabel("Expected Risk")
    ax.set_xlabel(r"$m/n, n={}$".format(n))
    # ax.set_xlim([0, 100])
    return df
```

```python
df = run_simulation(
    n = 10,
    n_test = 500,
    mu = 1,
    sigma = 1,
    pi_in = 0.7,
    pi_out = 0.7,
    Delta = [0, 1.5, 7],
    m_sizes = np.arange(0, 40, 2),
    reps = 1000,
)
```


PUNTO 2

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

def f(x):
    return x**2 - x - 1
def Df(x):
    return 2 * x - 1

x0=-1
i=1
error = 10
while error >1e-10:

    x1 = x0 - f(x0) / Df(x0)
    error = abs(x1 - x0)
    x0= x1

    print("Iteracion",i,", Raiz Aproximada: ",x0)
    i = i + 1
```

    Iteracion 1 , Raiz Aproximada:  -0.6666666666666667
    Iteracion 2 , Raiz Aproximada:  -0.6190476190476191
    Iteracion 3 , Raiz Aproximada:  -0.6180344478216819
    Iteracion 4 , Raiz Aproximada:  -0.6180339887499892
    Iteracion 5 , Raiz Aproximada:  -0.6180339887498948

```
# f(x)= x^2 - x -1
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-10, 10, 100)
plt.plot(x, x**2 - x - 1)

plt.grid()
plt.show
```

PUNTO 3

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

def f(x):
    return np.exp(-x) -x
def Df(x):
    return -np.exp(-x) - 1

x0=1
i=1
error = 10
while error >1e-10 and i<10:

    x1 = x0 - f(x0) / Df(x0)
    error = abs(x1 - x0/x1)
    x0= x1

    print("Iteracion",i,", Raiz Aproximada: ",x0)

    i = i + 1

```

    Iteracion 1 , Raiz Aproximada:  0.5378828427399902
    Iteracion 2 , Raiz Aproximada:  0.5669869914054133
    Iteracion 3 , Raiz Aproximada:  0.567143285989123
    Iteracion 4 , Raiz Aproximada:  0.5671432904097838
    Iteracion 5 , Raiz Aproximada:  0.5671432904097838
    Iteracion 6 , Raiz Aproximada:  0.5671432904097838
    Iteracion 7 , Raiz Aproximada:  0.5671432904097838
    Iteracion 8 , Raiz Aproximada:  0.5671432904097838
    Iteracion 9 , Raiz Aproximada:  0.5671432904097838

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

x = np.linspace(-50, 50, 100)
plt.plot(x, -np.exp(-x)  )

plt.grid()
plt.show
```

PUNTO 4

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

def f(x):
    return x**5 -x +1
def Df(x):
    return 5 * x**4 -1

x0=-2
i=1
error = 10
while error >1e-10:

    x1 = x0 - f(x0) / Df(x0)
    error = abs(x1 - x0)
    x0= x1

    print("Iteracion",i,", Raiz Aproximada: ",x0)
    i = i + 1
```

    Iteracion 1 , Raiz Aproximada:  -1.6329113924050633
    Iteracion 2 , Raiz Aproximada:  -1.3730855138655862
    Iteracion 3 , Raiz Aproximada:  -1.2235784352087191
    Iteracion 4 , Raiz Aproximada:  -1.1727319829379954
    Iteracion 5 , Raiz Aproximada:  -1.1673599072793783
    Iteracion 6 , Raiz Aproximada:  -1.1673039842671697
    Iteracion 7 , Raiz Aproximada:  -1.1673039782614187
    Iteracion 8 , Raiz Aproximada:  -1.1673039782614187

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

x = np.linspace(-1, 1, 100)
plt.plot(x, (5 * x**4) -1 )

plt.grid()
plt.show
```

PUNTO 5

```
def f(x):
    return np.sin(x)-x
def Df(x):
    return np.cos(x)-x

x0=1
i=1
error = 10
while error >1e-10 and i<10:

    x1 = x0 - f(x0) / Df(x0)
    error = abs(x1 - x0)
    x0= x1

    print("Iteracion",i,", Raiz Aproximada: ",x0)
    i = i + 1

```

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

x = np.linspace(-2, 2, 100)
plt.plot(x, np.cos(x)-x )

plt.grid()
plt.show
```

EJERCICIO PARCIAL

```

```

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

x = np.linspace(-2, 2, 100)
plt.plot(x, np.cos(x)-x )

plt.grid()
plt.show
```

```
from IPython.display import Math, HTML

def load_mathjax_in_cell_output():
  display(HTML(""))
get_ipython().events.register('pre_run_cell', load_mathjax_in_cell_output)
```

```
# try it out:
import sympy
sympy.init_printing()
x = sympy.symbols('x')
sympy.sqrt(1 / x)

(9*sympy.exp(x))*(sympy.cos(2*sympy.pi*x)-3)
```

```
-9*sympy.exp(x)*(2*sympy.pi)*sympy.sin(2*sympy.pi*x)-sympy.cos(2*sympy.pi*x)
```


# Generating and implementing many-body equations

## Preliminaries

Once more, let's start by importing wick&d and defining a Slater determinant reference

```python
import wicked as w
from IPython.display import display, Math, Latex

def latex(expr):
    """Function to render any object that has a member latex() function"""
    display(Math(expr.latex()))

w.reset_space()
w.add_space("o", "fermion", "occupied",   ['i','j','k','l','m','n'])
w.add_space("v", "fermion", "unoccupied", ['a','b','c','d','e','f'])
wt = w.WickTheorem()
```

## Generating equations for fully contracted terms

In the previous notebook, we computed the coupled cluster energy expression
\begin{equation}
E = \langle \Phi | e^{-\hat{T}} \hat{H} e^{\hat{T}} | \Phi \rangle
= E_0 + \sum_{i}^\mathbb{O} \sum_{a}^\mathbb{V} f^{a}_{i} t^{i}_{a} +
\frac{1}{4} \sum_{ij}^\mathbb{O} \sum_{ab}^\mathbb{V}
 (t^{i j}_{a b} + 2 t^{i}_{a} t^{j}_{b}) v^{a b}_{i j}
\end{equation}
with the following code

```python
E0 = w.op("E_0",[""])
F = w.utils.gen_op('f',1,'ov','ov')
V = w.utils.gen_op('v',2,'ov','ov')
H = E0 + F + V
T = w.op("t",["v+ o", "v+ v+ o o"])
Hbar = w.bch_series(H,T,2)
expr = wt.contract(Hbar,0,0)
expr
```

    E_0^{}_{}
    +f^{v0}_{o0} t^{o0}_{v0}
    +1/2 t^{o0}_{v0} t^{o1}_{v1} v^{v0,v1}_{o0,o1}
    +1/4 t^{o0,o1}_{v0,v1} v^{v0,v1}_{o0,o1}

First we convert the expression derived into a set of equations. You get back a dictionary that shows all the components to the equations. The vertical bar (`|`) in the key separates the lower (left) and upper (right) indices in the resulting expression

```python
mbeq = expr.to_manybody_equations('r')
mbeq
```

    {'|': [r^{}_{} +=  E_0^{}_{},
      r^{}_{} +=  f^{v0}_{o0} t^{o0}_{v0},
      r^{}_{} += 1/2 t^{o0}_{v0} t^{o1}_{v1} v^{v0,v1}_{o0,o1},
      r^{}_{} += 1/4 t^{o0,o1}_{v0,v1} v^{v0,v1}_{o0,o1}]}

## Converting equations to code

From the equations generated above, you can get tensor contractions by calling the `compile` function on each individual term in the equations. Here we generate python code that uses numpy's `einsum` function to evaluate contractions. To use this code you will need to import `einsum`
```python
from numpy import einsum
```
and you will need to define a dictionary of tensors (`f["vo"],v["vvoo"],t["ov"],...`) of appropriate dimensions:

```python
for eq in mbeq['|']:
    print(eq.compile('einsum'))
```

    r += 1.000000000 * np.einsum("->",E_0[""],optimize="optimal")
    r += 1.000000000 * np.einsum("ai,ia->",f["vo"],t["ov"],optimize="optimal")
    r += 0.500000000 * np.einsum("ia,jb,abij->",t["ov"],t["ov"],v["vvoo"],optimize="optimal")
    r += 0.250000000 * np.einsum("ijab,abij->",t["oovv"],v["vvoo"],optimize="optimal")

## Many-body equations

Suppose we want to compute the contributions to the coupled cluster residual equations
\begin{equation}
r^{i}_{a} = \langle \Phi| \{ \hat{a}^\dagger_{i} \hat{a}_a \} [\hat{F},\hat{T}_1] | \Phi \rangle
\end{equation}
Wick&d can compute this quantity using the corresponding **many-body representation** of the operator $[\hat{F},\hat{T}_1]$.
If you expand the operator $[\hat{F},\hat{T}_1]$ into its second quantized operator components we can identify a particle-hole excitation term:
\begin{equation}
[\hat{F},\hat{T}_1] = g^{j}_{b} \{ \hat{a}^\dagger_{b} \hat{a}_j \} + \cdots
\end{equation}
From this expression we see that the residual $r_{a}^{i}$ is precisely the quantity we need to evaluate since
\begin{equation}
r^{i}_{a} = \langle \Phi| \{ \hat{a}^\dagger_{i} \hat{a}_a \} [\hat{F},\hat{T}_1] | \Phi \rangle
 = g^{j}_{b}  \langle \Phi| \{ \hat{a}^\dagger_{i} \hat{a}_a \}  \{ \hat{a}^\dagger_{b} \hat{a}_j \}  | \Phi \rangle = g^{i}_{a}
\end{equation}
where in the last step we applied Wick's theorem to evaluate the expectation value.

Let's start by computing $[\hat{F},\hat{T}_1]$ with Wick's theorem:

```python
F = w.utils.gen_op('f',1,'ov','ov')
T1 = w.op("t",["v+ o"])
expr = wt.contract(w.commutator(F,T1),2,2)
latex(expr)
```

$\displaystyle + {f}^{a}_{i} {t}^{j}_{a} \hat{a}^{i} \hat{a}_{j} \\
- {f}^{a}_{i} {t}^{i}_{b} \hat{a}^{b} \hat{a}_{a} \\
- {f}^{i}_{j} {t}^{j}_{a} \hat{a}^{a} \hat{a}_{i} \\
+ {f}^{b}_{a} {t}^{i}_{b} \hat{a}^{a} \hat{a}_{i}$

Next, we call `to_manybody_equations` to generate many-body equations

```python
mbeq = expr.to_manybody_equations('g')
print(mbeq)
```

    {'o|o': [g^{o1}_{o0} +=  f^{v0}_{o0} t^{o1}_{v0}], 'o|v': [g^{o0}_{v0} += - f^{o0}_{o1} t^{o1}_{v0}, g^{o0}_{v0} +=  f^{v1}_{v0} t^{o0}_{v1}], 'v|v': [g^{v0}_{v1} += - f^{v0}_{o0} t^{o0}_{v1}]}

Out of all the terms, we select the terms that multiply the excitation operator $\{ \hat{a}^\dagger_{a} \hat{a}_i \}$ (`"o|v"`)

```python
mbeq_ov = mbeq["o|v"]

for eq in mbeq_ov:
    latex(eq)
```

$\displaystyle g^{o0}_{v0} += - f^{o0}_{o1} t^{o1}_{v0}$

$\displaystyle g^{o0}_{v0} +=  f^{v1}_{v0} t^{o0}_{v1}$

Lastly, we can compile these equations into code

```python
for eq in mbeq_ov:
    print(eq.compile('einsum'))
```

    gov += -1.000000000 * np.einsum("ij,ja->ia",f["oo"],t["ov"],optimize="optimal")
    gov += 1.000000000 * np.einsum("ba,ib->ia",f["vv"],t["ov"],optimize="optimal")

## Antisymmetrization of uncontracted operator indices

To gain efficiency, Wick&d treats contractions involving inequivalent lines in a special way. Consider the following term contributing to the CCSD doubles amplitude equations that arises from $[\hat{V}_\mathrm{ovov},\hat{T}_2]$ (see the sixth term in Eq. (153) of Crawford and Schaefer, https://doi.org/10.1002/9780470125915.ch2)
\begin{equation}
r^{ij}_{ab} \leftarrow \langle \Phi| \{ \hat{a}^\dagger_{i}\hat{a}^\dagger_{j} \hat{a}_b \hat{a}_a \} [\hat{V}_\mathrm{ovov},\hat{T}_2] | \Phi \rangle = - P(ij)P(ab) \sum_{kc} \langle kb \| jc \rangle t^{ik}_{ac}
\end{equation}
where $P(pq)$ is an antisymmetric permutation operator [$P(pq)f(p,q) = f(p,q) - f(q,p)$].
This expression corresponds to a **single diagram**, but algebraically it consists of **four terms** obtained by index permutations $i \leftrightarrow j$ and $a \leftrightarrow b$, so that the residual is antisymmetric with respect to separate permutations of upper and lower indices.

Let's first take a look at what happens when we apply Wick's theorem with wick&d to the quantity $[\hat{V}_\mathrm{ovov},\hat{T}_2]$

```python
T2 = w.op("t", ["v+ v+ o o"])
Vovov = w.op("v", ["o+ v+ v o"])
expr = wt.contract(w.commutator(Vovov, T2), 4, 4)
latex(expr)
```

$\displaystyle - {t}^{i k}_{a c} {v}^{j c}_{k b} \hat{a}^{a} \hat{a}^{b} \hat{a}_{j} \hat{a}_{i}$

In wick&d the two-body part of $[\hat{V}_\mathrm{ovov},\hat{T}_2]$ gives us only a single term
\begin{equation}
[\hat{V}_\mathrm{ovov},\hat{T}_2]_\text{2-body} = - \sum_{abcijk} \langle kb \| jc \rangle t^{ik}_{ac} \{ \hat{a}^{ab}_{ij} \} = \sum_{abij} g^{ij}_{ab}  \{ \hat{a}^{ab}_{ij} \}
\end{equation}
where the tensor $g^{ij}_{ab}$ is defined as
\begin{equation}
g^{ij}_{ab}  = -\sum_{kc} \langle kb \| jc \rangle t^{ik}_{ac}
\end{equation}
**Note that contrary to $r^{ij}_{ab}$, the tensor** $g^{ij}_{ab}$ **does not have any specific index symmetry**. In other words, **you need to enforce the antisymmetry**.
<!-- In particular, the many-body tensors generated by wick&d are not guaranteed to be antisymmetric, i -->

This quantity is related to the CCSD residual contribution reported above in the following way
\begin{equation}
r^{ij}_{ab} \leftarrow \langle \Phi| \{ \hat{a}^\dagger_{i}\hat{a}^\dagger_{j} \hat{a}_b \hat{a}_a \} [\hat{V}_\mathrm{ovov},\hat{T}_2] | \Phi \rangle = g^{ij}_{ab} - g^{ji}_{ab} - g^{ij}_{ba} + g^{ji}_{ba} = P(ij)P(ab) g^{ij}_{ab}
\end{equation}

Therefore, this example shows an important distinction between the traditional projective equation (which yields $P(ij)P(ab) g^{ij}_{ab}$) vs. the many-body approach (which yields $g^{ij}_{ab}$).

How are the difference between these two approaches reconciled in practic? When you solve the many-body equations, you must enforce the antisymmetry of the equations, which means that the residual contribution should be written as
\begin{equation}
\sum_{abij} g^{ij}_{ab}  \{ \hat{a}^{ab}_{ij} \}
= \frac{1}{4} \sum_{abij} (P(ij)P(ab) g^{ij}_{ab})  \{ \hat{a}^{ab}_{ij} \}
\end{equation}
The factor $\frac{1}{4}$ now brings this term in a form consistent with the prefactor we associate with the operator $ \{ \hat{a}^{ab}_{ij} \}$.

When you ask wick&d to compile the many-body equation we again get a single term

```python
for eq in expr.to_manybody_equations('g')['oo|vv']:
    print(eq.compile('einsum'))
```

    goovv += -1.000000000 * np.einsum("ikac,jckb->ijab",t["oovv"],v["ovov"],optimize="optimal")

This is done for efficiency, since the correct term [$P(ij)P(ab) g^{ij}_{ab}$] can be recovered by antisymmetrizing the residual **after adding all the contributions**, for example, in this way
```python
def antisymmetrize_residual_2(Roovv):
    # antisymmetrize the oovv residual
    Roovv_anti = np.zeros((nocc,nocc,nvir,nvir))
    Roovv_anti += np.einsum("ijab->ijab",Roovv)
    Roovv_anti -= np.einsum("ijab->jiab",Roovv)
    Roovv_anti -= np.einsum("ijab->ijba",Roovv)
    Roovv_anti += np.einsum("ijab->jiba",Roovv)
    return Roovv_anti
```


\forall
$\forall$

\exists
$\exists$

\hat{a}
$\hat{a}$

\grave{a}
$\grave{a}$

\bar{a}
$\bar{a}$

\check{a}
$\check{a}$

\dot{a}
$\dot{a}$

\vec{a}
$\vec{a}$

\tilde{a}
$\tilde{a}$

\ddot{a}
$\ddot{a}$

\widehat{a}
$\widehat{a}$

\acute{a}
$\acute{a}$

\breve{a}
$\breve{a}$

\widetilde{a}
$\widetilde{a}$

\alpha
$\alpha$

\beta
$\beta$

\gamma
$\gamma$

\delta
$\delta$

\epsilon
$\epsilon$

\varepsilon
$\varepsilon$

\zeta
$\zeta$

\eta
$\eta$

\theta
$\theta$

\vartheta
$\vartheta$

\iota
$\iota$

\kappa
$\kappa$

\lambda
$\lambda$

\mu
$\mu$

\nu
$\nu$

\xi
$\xi$

\pi
$\pi$

\varpi
$\varpi$

\rho
$\rho$

\varrho
$\varrho$

\sigma
$\sigma$

\varsigma
$\varsigma$

\tau
$\tau$

\upsilon
$\upsilon$

\phi
$\phi$

\varphi
$\varphi$

\chi
$\chi$

\psi
$\psi$

\omega
$\omega$

\Gamma
$\Gamma$

\Delta
$\Delta$

\Theta
$\Theta$

\Lambda
$\Lambda$

\Xi
$\Xi$

\Pi
$\Pi$

\Sigma
$\Sigma$

\Upsilon
$\Upsilon$

\Phi
$\Phi$

\Psi
$\Psi$

\Omega
$\Omega$

\notin$
$\notin$

\neq
$\neq$

\leq
$\leq$

\ll
$\ll$

\subset
$\subset$

\subseteq
$\subseteq$

\geq
$\geq$

\gg
$\gg$

\sim
$\sim$

\doteq
$\doteq$

\equiv
$\equiv$

\approx
$\approx$

\propto
$\propto$

\perp
$\perp$

\parallel
$\parallel$

\ell
$\ell$

\Re
$\Re$

\mathcal L
$\mathcal L$

\mathcal D
$\mathcal D$

\mathcal N
$\mathcal N$

\pm
$\pm$

\cdot
$\cdot$

\times
$\times$

\cup
$\cup$

\vee
$\vee$

\oplus
$\oplus$

\odot
$\odot$

\otimes
$\otimes$

\bigtriangleup
$\bigtriangleup$

\mp
$\mp$

\cap
$\cap$

\setminus
$\setminus$

\bigtriangledown
$\bigtriangledown$

\star
$\star$

\ast
$\ast$

\circ
$\circ$

\diamond
$\diamond$

\wr
$\wr$

\nabla
$\nabla$

\sum
$\sum$

\prod
$\prod$

\coprod
$\coprod$

\int
$\int$

\bigcup
$\bigcup$

\bigcap
$\bigcap$

\oint
$\oint$

\bigoplus
$\bigoplus$

\bigotimes
$\bigotimes$

\bigodot
$\bigodot$

# [字体](https://blog.csdn.net/cclethe/article/details/79022216)

\begin{array}{ll|l}
 \texttt{"normal"}      &\texttt{}         & ABCDEFGHIJKLMNOPQRSTUVWXYZ\\
 \texttt{"blackboard"}  &\texttt{\mathbb}  &\mathbb{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"boldface"}    &\texttt{\mathbf}  &\mathbf{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"typewriter"}  &\texttt{\mathtt}  &\mathtt{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"roman"}       &\texttt{\mathrm}  &\mathrm{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"sans-serif"}  &\texttt{\mathsf}  &\mathsf{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"calligraphic"}&\texttt{\mathcal} &\mathcal{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"script"}      &\texttt{\mathscr} &\mathscr{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"fraktur"}     &\texttt{\mathfrak}&\mathfrak{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
 \texttt{"???"}     &\texttt{\mathit}&\mathit{ABCDEFGHIJKLMNOPQRSTUVWXYZ}\\
\end{array}

# 颜色
\begin{array}{|lc|}
\hline
 \verb+\color{black}{黑色}+    & \color{black}{黑色}   \\
 \verb+\color{darkgray}{深灰}+ & \color{darkgray}{深灰}\\
 \verb+\color{gray}{灰色}+     & \color{gray}{灰色}    \\
 \verb+\color{silver}{银色}+   & \color{silver}{银色}  \\
 \verb+\color{lightgray}{浅灰}+&\color{lightgray}{浅灰}\\
 \verb+\color{white}{白色}+    & \color{white}{白色}   \\
\hline
 \verb+\color{brown}{棕色}+    & \color{brown}{棕色}   \\
 \verb+\color{maroon}{栗色}+   & \color{maroon}{栗色}  \\
 \verb+\color{red}{红色}+      & \color{red}{红色}     \\
 \verb+\color{fuchsia}{桃红}+  & \color{fuchsia}{桃红} \\
 \verb+\color{magenta}{品红}+  & \color{magenta}{品红} \\
 \verb+\color{pink}{粉红}+     & \color{pink}{粉红}    \\
 \verb+\color{orange}{橙色}+   & \color{orange}{橙色}  \\
 \verb+\color{yellow}{黄色}+   & \color{yellow}{黄色}  \\
 \verb+\color{lime}{青柠}+     & \color{lime}{青柠}    \\
 \verb+\color{olive}{橄榄}+    & \color{olive}{橄榄}   \\
 \verb+\color{green}{绿色}+    & \color{green}{绿色}   \\
 \verb+\color{aqua}{水绿}+     & \color{aqua}{水绿}    \\
 \verb+\color{cyan}{青色}+     & \color{cyan}{青色}    \\
 \verb+\color{teal}{靛青}+     & \color{teal}{靛青}    \\
 \verb+\color{blue}{蓝色}+     & \color{blue}{蓝色}    \\
 \verb+\color{navy}{海蓝}+     & \color{navy}{海蓝}    \\
 \verb+\color{violet}{罗兰}+   & \color{purple}{罗兰}  \\
 \verb+\color{purple}{紫色}+   & \color{purple}{紫色}  \\
\hline
\end{array}

\begin{array}{|rrrrrrrr|}
\hline
\verb+#000+ & \color{#000}{text} & \verb+#005+ & \color{#005}{text} & \verb+#00A+ & \color{#00A}{text} & \verb+#00F+ & \color{#00F}{text}  \\
\verb+#500+ & \color{#500}{text} & \verb+#505+ & \color{#505}{text} & \verb+#50A+ & \color{#50A}{text} & \verb+#50F+ & \color{#50F}{text}  \\
\verb+#A00+ & \color{#A00}{text} & \verb+#A05+ & \color{#A05}{text} & \verb+#A0A+ & \color{#A0A}{text} & \verb+#A0F+ & \color{#A0F}{text}  \\
\verb+#F00+ & \color{#F00}{text} & \verb+#F05+ & \color{#F05}{text} & \verb+#F0A+ & \color{#F0A}{text} & \verb+#F0F+ & \color{#F0F}{text}  \\
\hline
\verb+#080+ & \color{#080}{text} & \verb+#085+ & \color{#085}{text} & \verb+#08A+ & \color{#08A}{text} & \verb+#08F+ & \color{#08F}{text}  \\
\verb+#580+ & \color{#580}{text} & \verb+#585+ & \color{#585}{text} & \verb+#58A+ & \color{#58A}{text} & \verb+#58F+ & \color{#58F}{text}  \\
\verb+#A80+ & \color{#A80}{text} & \verb+#A85+ & \color{#A85}{text} & \verb+#A8A+ & \color{#A8A}{text} & \verb+#A8F+ & \color{#A8F}{text}  \\
\verb+#F80+ & \color{#F80}{text} & \verb+#F85+ & \color{#F85}{text} & \verb+#F8A+ & \color{#F8A}{text} & \verb+#F8F+ & \color{#F8F}{text}  \\
\hline
\verb+#0F0+ & \color{#0F0}{text} & \verb+#0F5+ & \color{#0F5}{text} & \verb+#0FA+ & \color{#0FA}{text} & \verb+#0FF+ & \color{#0FF}{text}  \\
\verb+#5F0+ & \color{#5F0}{text} & \verb+#5F5+ & \color{#5F5}{text} & \verb+#5FA+ & \color{#5FA}{text} & \verb+#5FF+ & \color{#5FF}{text}  \\
\verb+#AF0+ & \color{#AF0}{text} & \verb+#AF5+ & \color{#AF5}{text} & \verb+#AFA+ & \color{#AFA}{text} & \verb+#AFF+ & \color{#AFF}{text}  \\
\verb+#FF0+ & \color{#FF0}{text} & \verb+#FF5+ & \color{#FF5}{text} & \verb+#FFA+ & \color{#FFA}{text} & \verb+#FFF+ & \color{#FFF}{text}  \\
\hline
\end{array}

# 连分数
x = a_0 + \cfrac{1^2}{a_1
        + \cfrac{2^2}{a_2
        + \cfrac{3^2}{a_3
        + \cfrac{4^4}{a_4 + \cdots}}}}

$
x = a_0 + \cfrac{1^2}{a_1
        + \cfrac{2^2}{a_2
        + \cfrac{3^2}{a_3
        + \cfrac{4^4}{a_4 + \cdots}}}}$

>

\underset{j=1}{\overset{\infty}{\LARGE\mathrm K}}\frac{a_j}{b_j}
=\cfrac{a_1}{b_1
+\cfrac{a_2}{b_2
+\cfrac{a_3}{b_3
+\ddots}}}

$\underset{j=1}{\overset{\infty}{\LARGE\mathrm K}}\frac{a_j}{b_j}
=\cfrac{a_1}{b_1
+\cfrac{a_2}{b_2
+\cfrac{a_3}{b_3
+\ddots}}}$

>

x = a_0 + \frac{1^2}{a_1+}
          \frac{2^2}{a_2+}
          \frac{3^2}{a_3 +}
          \frac{4^4}{a_4 +} \cdots

$x = a_0 + \frac{1^2}{a_1+}
          \frac{2^2}{a_2+}
          \frac{3^2}{a_3 +}
          \frac{4^4}{a_4 +} \cdots       $

# 线性代数
\begin{array}{ll}
 \texttt{\overline}&\overline{AAA}\\
 \texttt{\underline}&\underline{BBB}\\
 \texttt{\widetilde}&\widetilde{CCC}\\
 \texttt{\widehat}&\widehat{DDD}\\
 \texttt{\fbox}&\fbox{EEE}\\
\hline
 \texttt{\vec}&\vec{x}\ \mathrm{or}\ \vec{AB}\\
 \texttt{\check}&\check{x}\\
 \texttt{\acute}&\acute{x}\\
 \texttt{\grave}&\grave{x}\\
 \texttt{\bar}&\bar{x}\\
 \texttt{\hat}&\hat{x}\\
 \texttt{\tilde}&\tilde{x}\\
 \texttt{\mathring}&\mathring{x}\\
 \texttt{\dot}&\dot{x}\\
 \texttt{\ddot}&\ddot{x}\\
 \texttt{\dddot}&\dddot{x}\\
\end{array}

\begin{array}{lc}
 \texttt{smallmatrix}&\bigl(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\bigr)\\
 \texttt{matrix} &\begin{matrix} 1&2\\3&4\\ \end{matrix} \\
 \texttt{pmatrix}&\begin{pmatrix}1&2\\3&4\\ \end{pmatrix}\\
 \texttt{bmatrix}&\begin{bmatrix}1&2\\3&4\\ \end{bmatrix}\\
 \texttt{Bmatrix}&\begin{Bmatrix}1&2\\3&4\\ \end{Bmatrix}\\
 \texttt{vmatrix}&\begin{vmatrix}1&2\\3&4\\ \end{vmatrix}\\
 \texttt{Vmatrix}&\begin{Vmatrix}1&2\\3&4\\ \end{Vmatrix}\\
\end{array}

\begin{pmatrix}
 1 & a_1 & a_1^2 & \cdots & a_1^n \\
 1 & a_2 & a_2^2 & \cdots & a_2^n \\
 \vdots  & \vdots& \vdots & \ddots & \vdots \\
 1 & a_m & a_m^2 & \cdots & a_m^n \\
\end{pmatrix}

$$
\left[
    \begin{array}{cc|c}
      1 & 2 & 3 \\
      4 & 5 & 6
    \end{array}
\right] \tag{7}
$$

\begin{cases}
%线性方程组加\\[2ex],不然会挤在一起.
 a_1x+b_1y+c_1z=\frac{p_1}{q_1} \\[2ex]
 a_2x+b_2y+c_2z=\frac{p_2}{q_2} \\[2ex]
 a_3x+b_3y+c_3z=\frac{p_3}{q_3}
\end{cases}

%约去符,删除线
\require{cancel}
\require{enclose}
\begin{array}{ll}
 \verb|y+\cancel{x}| & y+\cancel{x}\\
 \verb|y+\bcancel{x}| & y+\bcancel{x}\\
 \verb|y+\xcancel{x}| & y+\xcancel{x}\\
 \verb|y+\cancelto{0}{x}| & y+\cancelto{0}{x}\\
 \verb+\frac{1\cancel9}{\cancel95} = \frac15+& \frac{1\cancel9}{\cancel95} = \frac15 \\
 \verb|\enclose{horizontalstrike}{x+y}| & \enclose{horizontalstrike}{x+y}\\
 \verb|\enclose{verticalstrike}{\frac xy}| & \enclose{verticalstrike}{\frac xy}\\
 \verb|\enclose{updiagonalstrike}{x+y}| & \enclose{updiagonalstrike}{x+y}\\
 \verb|\enclose{downdiagonalstrike}{x+y}| & \enclose{downdiagonalstrike}{x+y}\\
\end{array}

$
  \begin{matrix}
   1 & 2 & 3 \\
   4 & 5 & 6 \\
   7 & 8 & 9
  \end{matrix} \tag{1}
$

$$
\left[
\begin{matrix}
 1      & 2      & \cdots & 4      \\
 7      & 6      & \cdots & 5      \\
 \vdots & \vdots & \ddots & \vdots \\
 8      & 9      & \cdots & 0      \\
\end{matrix}
\right]
$$

$$
 \begin{Bmatrix}
   1 & 2 & 3 \\
   4 & 5 & 6 \\
   7 & 8 & 9
  \end{Bmatrix} \tag{5}
$$

$$
 \begin{bmatrix}
   1 & 2 & 3 \\
   4 & 5 & 6 \\
   7 & 8 & 9
  \end{bmatrix} \tag{4}
$$

$\bigl( \begin{smallmatrix} a & b \\ c & d \end{smallmatrix} \bigr)$

\begin{equation}
\left\{
\begin{array}{**rcl**}
    IF_{k}(\hat{t}_{k,m})=IF_{m}(\hat{t}_{k,m}), & \\
    IF_{k}(\hat{t}_{k,m}) \pm h= IF_{m}(\hat{t}_{k,m}) \pm h  , &\\
    \left |IF'_{k}(\hat{t}_{k,m} - IF'_{m}(\hat{t}_{k,m} \right |\geq d , &
\end{array}
\right.
\end{equation}

$$ f(x)=\left\{
\begin{aligned}
x & = & \cos(t) \\
y & = & \sin(t) \\
z & = & \frac xy
\end{aligned}
\right.
$$

\begin{equation}
	\mathop{\arg\min}_{\theta} \ \ \| \mathrm{J} (\theta)\|
\end{equation}

```python
raw = []
latex = []
with open('a.txt', 'r') as f:
    for a in f.readlines():
        if a.startswith('$'):
            latex.append(a.strip())
            raw.append(a[1:-2])
```

```python
for a,b in zip(latex, raw):
    print(a,b)
    break
```

    $\kappa$ \kappa

```python
with open('b.txt', 'w') as f:
    for a, b in zip(latex, raw):
        f.write(b+'     \n'+a+'\n\n')
```


_Latihan soal ini dari perkuliahan Analisis Sumber Daya Air (SI-6232) tahun 2018 Semester Genap diajarkan oleh Dr. Dhemi Harlan ST, MT, MSc._

_Kode dibawah ini dibuat oleh Taruma S.M._

# Metoda Secant
> Tentukan solusi positif dari $f(x) = x - 2\ \sin{x} = 0$ dengan metode secant, mulai dari $x_0 = 2, x_1 = 1.9$

Penyelesaian:
Solusi persamaan :
$$x_{n+1} = x_n - \frac{(x_n-2\sin{x_n})(x_n-x_{n-1})}{x_n-x_{n-1}+2(\sin{x_{n-1}}-\sin{x_n})}=x_n-\frac{N_n}{D_n}$$

```python
from math import sin

x0, x1 = 2, 1.9

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 = 5

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

    print('| {:^10d} | {:^+1.7f} | {:^+1.7f} | {:^+1.7f} | {:^+1.7f} | {:^+1.9f} |'.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}'.format(xn))
```

    =================================================================================
    |                   Solusi Numerik Menggunakan Metoda Secant                    |
    =================================================================================
    |     n      |  x_{n-1}   |    x_n     |    N_n     |    D_n     | x_{n+1}-x_n  |
    =================================================================================
    |     1      | +2.0000000 | +1.9000000 | -0.0007400 | -0.1740053 | -0.004252643 |
    |     2      | +1.9000000 | +1.8957474 | -0.0000018 | -0.0069852 | -0.000252433 |
    |     3      | +1.8957474 | +1.8954949 | -0.0000000 | -0.0004136 | -0.000000658 |
    |     4      | +1.8954949 | +1.8954943 | -0.0000000 | -0.0000011 | -0.000000000 |
    |     5      | +1.8954943 | +1.8954943 | -0.0000000 | -0.0000000 | +0.000000000 |
    =================================================================================
    Maka diperoleh nilai akar-akarnya =   1.895494

# Metode Newton-Rhapson

> Tentukan sebuah akar persamaan dari fungsi berikut: $f(x) = x^3 - 3.5x^2+2x-10$

Solusi:
Persamaan umumnya:
$$\large{x_{k+1} = x_k - \frac{f(x_k)}{f'(x_k)}}$$

```python
# Defining function
def f(y):  # Fungsi f(x)
    return C*b*y*sqrt(b*s*y/(b + 2*y)) - Q

def f_d(y):  # Turunan Fungsi f(x) -> f'(x)
    return C*b*sqrt(b*s*y/(b + 2*y))*(3*b + 4*y)/(2*(b + 2*y))

# 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)

iterasi = 6  # Iteration
xk = 3  # 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)/f_d(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), f_d(xk), xk_p1, n=10, f1=10, f2=5))
    xk = xk_p1
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f}'.format(xk))
```

    ==================================================================
    |              Solusi Numerik Metoda Newton Rhapson              |
    ==================================================================
    |     k      |    x_k     |   f(x_k)   |  f'(x_k)   |  x_{k+1}   |
    ==================================================================
    |     1      |   3.00000  |   9.76753  |   4.04053  |   0.58261  |
    |     2      |   0.58261  |   0.57726  |   3.17017  |   0.40052  |
    |     3      |   0.40052  |   0.02719  |   2.84739  |   0.39097  |
    |     4      |   0.39097  |   0.00010  |   2.82592  |   0.39093  |
    |     5      |   0.39093  |   0.00000  |   2.82584  |   0.39093  |
    |     6      |   0.39093  |  -0.00000  |   2.82584  |   0.39093  |
    ==================================================================
    Maka diperoleh nilai akar-akarnya =   0.390930

# Metode Interval-halving

> Gunakan metode interval-halving, fungsi berikut didapat mempunyai suatu akar real antara x = 1 dan x = 3; tentukan akar dari: $f(x) = x^3-5x^2-2x+10$

Solusi:

Evaluasi fungsi pada kedua nilai awal, $x_1 = 1 \text{ dan } x_2 = 3$
$$\begin{align} x_1 &=& 1 &\rightarrow& f(x_1) &=& 4 \\
x_2 &=& 3 &\rightarrow& f(x_2) &=& -14 \\ \end{align}$$

```python
from math import sqrt

def f(y):
    return C*b*y*sqrt(b*S*y/(b + 2*y)) - Q

def newline(x=30):
    print('='*x)

# 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)

x1, x2 = 0.32, 0.39342
iterasi = 20
xa, xb = x1, x2

pad = 70
newline(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} dan f(x_b) = {fxb}'.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}'.format(
        xa=xa, xb=xb))
newline(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=5))
    if fxa*fxh < 0:
        xb = xh
    elif fxb*fxh < 0:
        xa = xh
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f}'.format(xh))
```

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

    ============================================================================================
    |                          Solusi Numerik Metoda Interval Halving                          |
    ============================================================================================
    |     n      |    x_a     |    x_b     |   f(x_a)   |   f(x_b)   |    x_h     |   f(x_h)   |
    ============================================================================================
    |     1      |   0.32000  |   0.39342  |  -0.19429  |   0.00704  |   0.35671  |  -0.09532  |
    |     2      |   0.35671  |   0.39342  |  -0.09532  |   0.00704  |   0.37506  |  -0.04454  |
    |     3      |   0.37506  |   0.39342  |  -0.04454  |   0.00704  |   0.38424  |  -0.01885  |
    |     4      |   0.38424  |   0.39342  |  -0.01885  |   0.00704  |   0.38883  |  -0.00593  |
    |     5      |   0.38883  |   0.39342  |  -0.00593  |   0.00704  |   0.39113  |   0.00055  |
    |     6      |   0.38883  |   0.39113  |  -0.00593  |   0.00055  |   0.38998  |  -0.00269  |
    |     7      |   0.38998  |   0.39113  |  -0.00269  |   0.00055  |   0.39055  |  -0.00107  |
    |     8      |   0.39055  |   0.39113  |  -0.00107  |   0.00055  |   0.39084  |  -0.00026  |
    |     9      |   0.39084  |   0.39113  |  -0.00026  |   0.00055  |   0.39098  |   0.00015  |
    |     10     |   0.39084  |   0.39098  |  -0.00026  |   0.00015  |   0.39091  |  -0.00006  |
    |     11     |   0.39091  |   0.39098  |  -0.00006  |   0.00015  |   0.39095  |   0.00005  |
    |     12     |   0.39091  |   0.39095  |  -0.00006  |   0.00005  |   0.39093  |  -0.00000  |
    |     13     |   0.39093  |   0.39095  |  -0.00000  |   0.00005  |   0.39094  |   0.00002  |
    |     14     |   0.39093  |   0.39094  |  -0.00000  |   0.00002  |   0.39093  |   0.00001  |
    |     15     |   0.39093  |   0.39093  |  -0.00000  |   0.00001  |   0.39093  |   0.00000  |
    |     16     |   0.39093  |   0.39093  |  -0.00000  |   0.00000  |   0.39093  |  -0.00000  |
    |     17     |   0.39093  |   0.39093  |  -0.00000  |   0.00000  |   0.39093  |  -0.00000  |
    |     18     |   0.39093  |   0.39093  |  -0.00000  |   0.00000  |   0.39093  |   0.00000  |
    |     19     |   0.39093  |   0.39093  |  -0.00000  |   0.00000  |   0.39093  |   0.00000  |
    |     20     |   0.39093  |   0.39093  |  -0.00000  |   0.00000  |   0.39093  |   0.00000  |
    ============================================================================================
    Maka diperoleh nilai akar-akarnya =   0.390930

```python

```

```python

```


# Grey radiation modeling with climlab

____________

## 1. Re-introducing `climlab`
____________

`climlab` is a specialized python package for process-oriented climate modeling.

It is based on a very general concept of a model as a collection of individual, interacting processes. climlab defines a base class called `Process`, which can contain an arbitrarily complex tree of sub-processes (each also some sub-class of `Process`). Every climate process (radiative, dynamical,  physical, turbulent, convective, chemical, etc.) can be simulated as a stand-alone process model given appropriate input, or as a sub-process of a more complex model. New classes of model can easily be defined and run interactively by putting together an
appropriate collection of sub-processes.

climlab is an open-source community project. The latest code can always be found on github:

https://github.com/brian-rose/climlab

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import xarray as xr
from xarray.ufuncs import cos, deg2rad, log
import climlab
```

____________

## 2. Using `climlab` to implement the two-layer leaky greenhouse model
____________

One of the things that ``climlab`` is set up to do is the grey-radiation modeling we have already been discussing.

Since I already derived a [complete analytical solution to the two-layer leaky greenhouse model](sympy-greenhouse.ipynb), we will use this to validate the `climlab` code.

### Validation

We want to verify that the model reproduces the observed OLR given observed temperatures and the absorptivity that we tuned in the analytical model. The target numbers are:

\begin{align}
T_s &= 288 \text{ K} \\
T_0 &= 275 \text{ K} \\
T_1 &= 230 \text{ K} \\
\end{align}

$$ \epsilon = 0.586  $$

$$ OLR = 238.5 \text{ W m}^{-2} $$

### Initialize a model in `climlab`
The first thing we do is create a new model.

The following example code is sparsely commented but will hopefully orient you on the basics of defining and working with a `climlab Process` object.

```python
#  Test in a 2-layer atmosphere
col = climlab.GreyRadiationModel(num_lev=2)
print( col)
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (2,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
col.subprocess
```

    AttrDict({'LW': <climlab.radiation.greygas.GreyGas object at 0x10d88bc50>, 'SW': <climlab.radiation.greygas.GreyGasSW object at 0x13eed44a8>, 'insolation': <climlab.radiation.insolation.FixedInsolation object at 0x13ef1d978>})

Every item in the above dictionary is itself an instance of the `climlab.Process` object:

```python
print( col.subprocess['LW'])
```

    climlab Process of type <class 'climlab.radiation.greygas.GreyGas'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (2,)
    The subprocess tree:
    Untitled: <class 'climlab.radiation.greygas.GreyGas'>

The `state` dictionary holds the state variables of the model. In this case, temperatures:

```python
print(climlab.to_xarray(col.state))
```

    <xarray.Dataset>
    Dimensions:       (depth: 1, depth_bounds: 2, lev: 2, lev_bounds: 3)
    Coordinates:
      * depth         (depth) float64 0.5
      * depth_bounds  (depth_bounds) float64 0.0 1.0
      * lev           (lev) float64 250.0 750.0
      * lev_bounds    (lev_bounds) float64 0.0 500.0 1e+03
    Data variables:
        Ts            (depth) float64 288.0
        Tatm          (lev) float64 200.0 278.0

Access these either through dictionary methods or as attributes of the model object:

```python
print( col.state['Ts'])
print( col.Ts)
col.Ts is col.state['Ts']
```

    [288.]
    [288.]

    True

Now we are assigning the "observed" temperatures to our model state:

```python
col.Ts[:] = 288.
col.Tatm[:] = np.array([230., 275.])
col.state
```

    AttrDict({'Ts': Field([288.]), 'Tatm': Field([230., 275.])})

```python
LW = col.subprocess['LW']
print( LW)
```

    climlab Process of type <class 'climlab.radiation.greygas.GreyGas'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (2,)
    The subprocess tree:
    Untitled: <class 'climlab.radiation.greygas.GreyGas'>

```python
LW.absorptivity
```

    Field([0.47737425, 0.47737425])

```python
#  copying the tuned value of epsilon from previous lecture notes
LW.absorptivity = 0.586
LW.absorptivity
```

    Field([0.586, 0.586])

```python
#  This does all the calculations that would be performed at each time step,
#  but doesn't actually update the temperatures
col.compute_diagnostics()
#  Print out the dictionary
col.diagnostics
```

    {'flux_from_sfc': Field([102.0487]),
     'flux_to_sfc': array([341.3]),
     'flux_to_space': array([102.0487]),
     'absorbed': array([0., 0.]),
     'absorbed_total': 0.0,
     'emission': Field([0., 0.]),
     'emission_sfc': Field([0.]),
     'flux_reflected_up': array([  0.    ,   0.    , 102.0487]),
     'insolation': Field([341.3]),
     'coszen': Field([1.]),
     'OLR': array([238.5247311]),
     'LW_down_sfc': array([228.53426769]),
     'LW_up_sfc': Field([390.10502995]),
     'LW_absorbed_sfc': Field([-161.57076227]),
     'LW_absorbed_atm': array([ 20.02990881, -96.98387764]),
     'LW_emission': Field([ 92.98664086, 190.03779837]),
     'ASR': Field([239.2513]),
     'SW_absorbed_sfc': Field([239.2513]),
     'SW_absorbed_atm': array([0., 0.]),
     'SW_up_sfc': Field([102.0487]),
     'SW_up_TOA': array([102.0487]),
     'SW_down_TOA': Field([341.3]),
     'planetary_albedo': Field([0.299])}

```python
# Check OLR against our analytical solution
col.OLR
```

    array([238.5247311])

```python
# Like the state variables, the diagnostics can also be accessed in two different ways
col.diagnostics['OLR']
```

    array([238.5247311])

```python
col.state
```

    AttrDict({'Ts': Field([288.]), 'Tatm': Field([230., 275.])})

```python
# perform a single time step
col.step_forward()
```

```python
col.state
```

    AttrDict({'Ts': Field([289.60514636]), 'Tatm': Field([230.33784312, 273.3641795 ])})

We just stepped forward one discreet unit in time. Because we didn't specify a timestep when we created the model, it is set to a default value:

```python
col.timestep
```

    86400.0

which is 1 day (expressed in seconds).

Now we will integrate the model out to equilibrium.

We could easily write a loop to call the `step_forward()` method many times.

Or use a handy shortcut that allows us to specify the integration length in physical time units:

```python
# integrate out to radiative equilibrium
col.integrate_years(2.)
```

    Integrating for 730 steps, 730.4844 days, or 2.0 years.
    Total elapsed time is 2.0014116660123062 years.

```python
# Check for equilibrium
col.ASR - col.OLR
```

    Field([-2.34514516e-07])

```python
#  The temperatures at radiative equilibrium
col.state
```

    AttrDict({'Ts': Field([296.38447748]), 'Tatm': Field([233.72131685, 262.28540231])})

Compare these to the analytical solutions for radiative equilibrium with $\epsilon = 0.58$:

\begin{align}
T_s &= 296.4 \text{ K} \\
T_0 &= 262.3 \text{ K} \\
T_1 &= 233.8 \text{ K} \\
\end{align}

So it looks like `climlab` agrees with our analytical results to within 0.1 K. That's good.

____________

## 3. The observed annual, global mean temperature profile
____________

We want to model the OLR in a column whose temperatures match observations. As we've done before, we'll calculate the global, annual mean air temperature from the NCEP Reanalysis data.

```python
ncep_filename = 'air.mon.1981-2010.ltm.nc'
ncep_url = 'https://psl.noaa.gov/thredds/dodsC/Datasets/ncep.reanalysis.derived/pressure/'
path = ncep_url
#  Open handle to data
ncep_air = xr.open_dataset( path + ncep_filename, use_cftime=True )
print( ncep_air)
```

    <xarray.Dataset>
    Dimensions:             (lat: 73, level: 17, lon: 144, nbnds: 2, time: 12)
    Coordinates:
      * level               (level) float32 1000.0 925.0 850.0 ... 30.0 20.0 10.0
      * lon                 (lon) float32 0.0 2.5 5.0 7.5 ... 352.5 355.0 357.5
      * time                (time) object 0001-01-01 00:00:00 ... 0001-12-01 00:00:00
      * lat                 (lat) float32 90.0 87.5 85.0 82.5 ... -85.0 -87.5 -90.0
    Dimensions without coordinates: nbnds
    Data variables:
        climatology_bounds  (time, nbnds) object ...
        air                 (time, level, lat, lon) float32 ...
        valid_yr_count      (time, level, lat, lon) float32 ...
    Attributes:
        description:                     Data from NCEP initialized reanalysis (4...
        platform:                       Model
        Conventions:                    COARDS
        not_missing_threshold_percent:  minimum 3% values input to have non-missi...
        history:                        Created 2011/07/12 by doMonthLTM\nConvert...
        title:                          monthly ltm air from the NCEP Reanalysis
        dataset_title:                  NCEP-NCAR Reanalysis 1
        References:                     http://www.psl.noaa.gov/data/gridded/data...

```python
#  Take global, annual average
weight = cos(deg2rad(ncep_air.lat)) / cos(deg2rad(ncep_air.lat)).mean(dim='lat')
Tglobal = (ncep_air.air * weight).mean(dim=('lat','lon','time'))
print( Tglobal)
```

    <xarray.DataArray (level: 17)>
    array([ 15.179084  ,  11.207003  ,   7.8383274 ,   0.21994135,
            -6.4483433 , -14.888848  , -25.570469  , -39.36969   ,
           -46.797905  , -53.652245  , -60.56356   , -67.006065  ,
           -65.53293   , -61.48664   , -55.853584  , -51.593952  ,
           -43.21999   ], dtype=float32)
    Coordinates:
      * level    (level) float32 1000.0 925.0 850.0 700.0 ... 50.0 30.0 20.0 10.0

We're going to convert this to degrees Kelvin, using a handy list of pre-defined constants in `climlab.constants`

```python
climlab.constants.tempCtoK
```

    273.15

```python
# += Adds a value and the variable and assigns the result to that variable
Tglobal += climlab.constants.tempCtoK # same as Tglobal = Tglobal + climlab.constants.tempCtoK
print( Tglobal)
```

    <xarray.DataArray (level: 17)>
    array([288.32907, 284.357  , 280.9883 , 273.36993, 266.70166, 258.26114,
           247.57953, 233.7803 , 226.35208, 219.49774, 212.58643, 206.14392,
           207.61707, 211.66336, 217.29642, 221.55605, 229.93001],
          dtype=float32)
    Coordinates:
      * level    (level) float32 1000.0 925.0 850.0 700.0 ... 50.0 30.0 20.0 10.0

```python
#  A handy re-usable routine for making a plot of the temperature profiles
#  We will plot temperatures with respect to log(pressure) to get a height-like coordinate

def zstar(lev):
    return -np.log(lev / climlab.constants.ps)

def plot_soundings(result_list, name_list, plot_obs=True, fixed_range=True):
    color_cycle=['r', 'g', 'b', 'y']
    fig, ax = plt.subplots(figsize=(9,9))
    if plot_obs:
        ax.plot(Tglobal, zstar(Tglobal.level), color='k', label='Observed')
    for i, state in enumerate(result_list):
        Tatm = state['Tatm']
        lev = Tatm.domain.axes['lev'].points
        Ts = state['Ts']
        ax.plot(Tatm, zstar(lev), color=color_cycle[i], label=name_list[i])
        ax.plot(Ts, 0, '^', markersize=12, color=color_cycle[i])
    yticks = np.array([1000., 750., 500., 250., 100., 50., 20., 10., 5.])
    ax.set_yticks(-np.log(yticks/1000.))
    ax.set_yticklabels(yticks)
    ax.set_xlabel('Temperature (K)', fontsize=14)
    ax.set_ylabel('Pressure (hPa)', fontsize=14)
    ax.grid()
    ax.legend()
    if fixed_range:
        ax.set_xlim([200, 300])
        ax.set_ylim(zstar(np.array([1000., 5.])))

    return ax
```

```python
plot_soundings([],[] );
```

____________

## 4. A 30-layer model using the observed temperatures
____________

```python
#  initialize a grey radiation model with 30 levels
col = climlab.GreyRadiationModel()
print( col)
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (30,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
col.lev
```

    array([ 16.66666667,  50.        ,  83.33333333, 116.66666667,
           150.        , 183.33333333, 216.66666667, 250.        ,
           283.33333333, 316.66666667, 350.        , 383.33333333,
           416.66666667, 450.        , 483.33333333, 516.66666667,
           550.        , 583.33333333, 616.66666667, 650.        ,
           683.33333333, 716.66666667, 750.        , 783.33333333,
           816.66666667, 850.        , 883.33333333, 916.66666667,
           950.        , 983.33333333])

```python
 col.lev_bounds
```

    array([   0.        ,   33.33333333,   66.66666667,  100.        ,
            133.33333333,  166.66666667,  200.        ,  233.33333333,
            266.66666667,  300.        ,  333.33333333,  366.66666667,
            400.        ,  433.33333333,  466.66666667,  500.        ,
            533.33333333,  566.66666667,  600.        ,  633.33333333,
            666.66666667,  700.        ,  733.33333333,  766.66666667,
            800.        ,  833.33333333,  866.66666667,  900.        ,
            933.33333333,  966.66666667, 1000.        ])

```python
# interpolate temperature to 30 evenly spaced pressure levels
lev = col.lev
Tinterp = np.interp(lev, np.flipud(Tglobal.level), np.flipud(Tglobal))
Tinterp
#  Need to 'flipud' because the interpolation routine
#  expects pressure data to be in increasing order
```

    array([224.34736633, 211.6633606 , 206.96233453, 208.29142253,
           212.58642578, 217.19396973, 221.78252157, 226.3520813 ,
           231.30422974, 236.08017476, 240.67991638, 245.279658  ,
           249.35979716, 252.92033386, 256.48087056, 259.66789246,
           262.48139954, 265.29490662, 267.81303914, 270.03579712,
           272.25855509, 274.21642049, 275.90939331, 277.60236613,
           279.29533895, 280.98831177, 282.48550415, 283.98269653,
           285.6810201 , 287.4463874 ])

```python
# Initialize model with observed temperatures
col.Ts[:] = Tglobal[0]
col.Tatm[:] = Tinterp
```

```python
# This should look just like the observations
result_list = [col.state]
name_list = ['Observed, interpolated']
plot_soundings(result_list, name_list);
```

### Tune absorptivity to get observed OLR

```python
col.compute_diagnostics()
col.OLR
```

    array([263.15000104])

```python
# Need to tune absorptivity to get OLR = 238.5
epsarray = np.linspace(0.01, 0.1, 100)
OLRarray = np.zeros_like(epsarray)
```

```python
# Introducing my favorite way to use loops in python!
# enumerate() loops through elements in a list or array

# Recall the range() method:
# for n in range(epsarray.size):
#     col.subprocess['LW'].absorptivity = epsarray[n]
#     ...

for n, eps in enumerate(epsarray):
    col.subprocess['LW'].absorptivity = eps
    col.compute_diagnostics()
    OLRarray[n] = col.OLR
```

```python
plt.plot(epsarray, OLRarray)
plt.grid()
plt.xlabel('epsilon')
plt.ylabel('OLR')
```

The necessary value seems to lie near 0.055 or so.

We can be more precise with a numerical root-finder.

```python
def OLRanom(eps):
    col.subprocess['LW'].absorptivity = eps
    col.compute_diagnostics()
    return col.OLR - 238.5
```

```python
# Use numerical root-finding to get the equilibria
from scipy.optimize import brentq
# brentq is a root-finding function
# Need to give it a function and two end-points
# It will look for a zero of the function between those end-points
eps = brentq(OLRanom, 0.01, 0.1)
print( eps)
```

    0.05369073166506245

```python
col.subprocess.LW.absorptivity = eps
col.subprocess.LW.absorptivity
```

    Field([0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073,
           0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073,
           0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073,
           0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073,
           0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073,
           0.05369073, 0.05369073, 0.05369073, 0.05369073, 0.05369073])

```python
col.compute_diagnostics()
col.OLR
```

    array([238.5])

____________

## 5. Radiative forcing in the 30-layer model
____________

Let's compute radiative forcing for a **2% increase in absorptivity**.

```python
#  clone our model using a built-in climlab function
col2 = climlab.process_like(col)
print(col2)
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (30,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
# *= Multiplies a value and the variable and assigns the result to that variable
col2.subprocess['LW'].absorptivity *= 1.02
col2.subprocess['LW'].absorptivity
```

    Field([0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455,
           0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455,
           0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455,
           0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455,
           0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455,
           0.05476455, 0.05476455, 0.05476455, 0.05476455, 0.05476455])

```python
#  Radiative forcing by definition is the change in TOA radiative flux,
# HOLDING THE TEMPERATURES FIXED.
col2.Ts - col.Ts
```

    Field([0.])

```python
col2.Tatm - col.Tatm
```

    Field([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
           0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

```python
col2.compute_diagnostics()
col2.OLR
```

    array([236.65384137])

The OLR decreased after we added the extra absorbers, as we expect. Now we can calculate the radiative forcing:

```python
RF = -(col2.OLR - col.OLR)
print('The radiative forcing is {} W/m2'.format(RF))

# Here is a new Python string formatting syntax called the formatted string literal.
# These are also informally called f-strings.

print(f'The radiative forcing is {RF} W/m2')
```

    The radiative forcing is [1.84615863] W/m2
    The radiative forcing is [1.84615863] W/m2

____________

## 6. Radiative equilibrium in the 30-layer model
____________

```python
re = climlab.process_like(col)
```

```python
#  To get to equilibrium, we just time-step the model forward long enough
re.integrate_years(1.)
```

    Integrating for 365 steps, 365.2422 days, or 1.0 years.
    Total elapsed time is 0.9993368783782377 years.

```python
#  Check for energy balance
print(f'The net downward radiative flux at TOA is {re.ASR - re.OLR} W/m2')
```

    The net downward radiative flux at TOA is [-0.00154652] W/m2

```python
result_list.append(re.state)
name_list.append('Radiative equilibrium (grey gas)')
plot_soundings(result_list, name_list)
```

Some properties of the **radiative equilibrium** temperature profile:

- The surface is warmer than observed.
- The lower troposphere is colder than observed.
- Very cold air is sitting immediately above the warm surface.
- There is no tropopause, no stratosphere.

____________

## 7. Radiative-Convective Equilibrium in the 30-layer model
____________

We recognize that the large drop in temperature just above the surface is unphysical. Parcels of air in direct contact with the ground will be warmed by mechansisms other than radiative transfer.

These warm air parcels will then become buoyant, and will convect upward, mixing their heat content with the environment.

We **parameterize** the statistical effects of this mixing through a **convective adjustment**.

At each timestep, our model checks for any locations at which the **lapse rate** exceeds some threshold. Unstable layers are removed through an energy-conserving mixing formula.

This process is assumed to be fast relative to radiative heating. In the model, it is instantaneous.

### Add the convective adjustment as an additional subprocess

```python
#  Here is the existing model
print(re)
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (30,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
#  First we make a new clone
rce = climlab.process_like(re)
#  Then create a new ConvectiveAdjustment process
conv = climlab.convection.ConvectiveAdjustment(state=rce.state,
                                               adj_lapse_rate=6.)
#  And add it to our model
rce.add_subprocess('Convective Adjustment', conv)
print(rce)
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (30,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>
       Convective Adjustment: <class 'climlab.convection.convadj.ConvectiveAdjustment'>

This model is exactly like our previous models, except for one additional subprocess called ``Convective Adjustment``.

We passed a parameter ``adj_lapse_rate`` (in K / km) that sets the neutrally stable lapse rate -- in this case, 6 K / km.

This number is chosed to very loosely represent the net effect of **moist convection**.

```python
#  Run out to equilibrium
rce.integrate_years(1.)
```

    Integrating for 365 steps, 365.2422 days, or 1.0 years.
    Total elapsed time is 1.9986737567564754 years.

```python
#  Check for energy balance
rce.ASR - rce.OLR
```

    Field([0.0007796])

```python
result_list.append(rce.state)
name_list.append('Radiatve-Convective equilibrium (grey gas)')
```

```python
plot_soundings(result_list, name_list)
```

Introducing convective adjustment into the model cools the surface quite a bit (compared to Radiative Equilibrium, in green here) -- and warms the lower troposphere. It gives us a MUCH better fit to observations.

But of course we still have no stratosphere.

____________

## 8. Putting stratospheric ozone in the grey-gas model
____________

Our model has no equivalent of the stratosphere, where temperature increases with height. That's because our model has been completely transparent to shortwave radiation up until now.

We can load the observed ozone climatology from the input files for the CESM model:

```python
#cesm_data_path = "http://thredds.atmos.albany.edu:8080/thredds/dodsC/CESMA/"
cesm_data_path = "../Data/CESMA/"
cesm_full_path = cesm_data_path + 'som_input/ozone_1.9x2.5_L26_2000clim_c091112.nc'
ozone = xr.open_dataset(cesm_full_path)
```

```python
print(ozone)
```

    <xarray.Dataset>
    Dimensions:  (ilev: 27, lat: 96, lev: 26, lon: 144, slat: 95, slon: 144, time: 12)
    Coordinates:
      * ilev     (ilev) float64 2.194 4.895 9.882 18.05 ... 903.3 956.0 985.1 1e+03
      * lat      (lat) float64 -90.0 -88.11 -86.21 -84.32 ... 84.32 86.21 88.11 90.0
      * lev      (lev) float64 3.545 7.389 13.97 23.94 ... 867.2 929.6 970.6 992.6
      * lon      (lon) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
      * slat     (slat) float64 -89.05 -87.16 -85.26 -83.37 ... 85.26 87.16 89.05
      * slon     (slon) float64 -1.25 1.25 3.75 6.25 ... 348.8 351.2 353.8 356.2
      * time     (time) object 2000-01-15 00:00:00 ... 2000-12-15 00:00:00
    Data variables:
        P0       float64 ...
        date     (time) int32 ...
        datesec  (time) int32 ...
        gw       (lat) float64 ...
        hyai     (ilev) float64 ...
        hyam     (lev) float64 ...
        hybi     (ilev) float64 ...
        hybm     (lev) float64 ...
        w_stag   (slat) float64 ...
        O3       (time, lev, lat, lon) float32 ...
        PS       (time, lat, lon) float32 ...
    Attributes:
        Conventions:                     CF-1.0
        source:                          CAM
        case:                            ar5_cam_1850-2000_03
        title:
        logname:                         lamar
        host:                            be0207en.ucar.ed
        Version:                         $Name$
        revision_Id:                     $Id$
        initial_file:                    /ptmp/lamar/cam-inputs/ar5_cam_1850_06.c...
        topography_file:                 /fs/cgd/csm/inputdata/atm/cam/topo/USGS-...
        history:                         Wed Jan  5 14:56:28 2011: ncks -d time,1...
        nco_openmp_thread_number:        1
        NCO:                             4.0.5
        DODS_EXTRA.Unlimited_Dimension:  time

The pressure levels in this dataset are:

```python
print( ozone.lev)
```

    <xarray.DataArray 'lev' (lev: 26)>
    array([  3.544638,   7.388814,  13.967214,  23.944625,  37.23029 ,  53.114605,
            70.05915 ,  85.439115, 100.514695, 118.250335, 139.115395, 163.66207 ,
           192.539935, 226.513265, 266.481155, 313.501265, 368.81798 , 433.895225,
           510.455255, 600.5242  , 696.79629 , 787.70206 , 867.16076 , 929.648875,
           970.55483 , 992.5561  ])
    Coordinates:
      * lev      (lev) float64 3.545 7.389 13.97 23.94 ... 867.2 929.6 970.6 992.6
    Attributes:
        long_name:      hybrid level at midpoints (1000*(A+B))
        units:          level
        positive:       down
        standard_name:  atmosphere_hybrid_sigma_pressure_coordinate
        formula_terms:  a: hyam b: hybm p0: P0 ps: PS

### Take the global average of the ozone climatology, and plot it as a function of pressure (or height)

```python
#  Take global, annual average
weight_ozone = cos(deg2rad(ozone.lat)) / cos(deg2rad(ozone.lat)).mean(dim='lat')
O3_global = (ozone.O3 * weight_ozone).mean(dim=('lat','lon','time'))
print( O3_global)
```

    <xarray.DataArray (lev: 26)>
    array([7.82792878e-06, 8.64150529e-06, 7.58940028e-06, 5.24567145e-06,
           3.17761574e-06, 1.82320006e-06, 9.80756960e-07, 6.22870516e-07,
           4.47620550e-07, 3.34481169e-07, 2.62570302e-07, 2.07898125e-07,
           1.57074555e-07, 1.12425545e-07, 8.06004999e-08, 6.27826498e-08,
           5.42990561e-08, 4.99506089e-08, 4.60075681e-08, 4.22977789e-08,
           3.80559071e-08, 3.38768568e-08, 3.12171619e-08, 2.97807119e-08,
           2.87980968e-08, 2.75429934e-08])
    Coordinates:
      * lev      (lev) float64 3.545 7.389 13.97 23.94 ... 867.2 929.6 970.6 992.6

```python
fig2, ax = plt.subplots(figsize=(8, 6))
ax.plot( O3_global * 1.E6, -np.log(ozone.lev/climlab.constants.ps) )
ax.set_xlabel('Ozone (ppm)', fontsize=16)
ax.set_ylabel('Pressure (hPa)', fontsize=16 )
yticks = np.array([1000., 750., 500., 250., 100., 50., 20., 10., 5.])
ax.set_yticks(-np.log(yticks/1000.))
ax.set_yticklabels(yticks)
ax.grid()
ax.set_title('Global, annual mean ozone concentration', fontsize = 16);
```

This shows that most of the ozone is indeed in the stratosphere, and peaks near the top of the stratosphere.

Now create a new column model object **on the same pressure levels as the ozone data**.  We are also going set an adjusted lapse rate of 6 K / km.

```python
# the RadiativeConvectiveModel is pre-defined in climlab
# It contains the same components are our previous model
# But here we are specifying a different set of vertical levels.
oz_col = climlab.RadiativeConvectiveModel(lev = ozone.lev, adj_lapse_rate=6)
print(oz_col)
```

    climlab Process of type <class 'climlab.model.column.RadiativeConvectiveModel'>.
    State variables and domain shapes:
      Ts: (1,)
      Tatm: (26,)
    The subprocess tree:
    Untitled: <class 'climlab.model.column.RadiativeConvectiveModel'>
       LW: <class 'climlab.radiation.greygas.GreyGas'>
       SW: <class 'climlab.radiation.greygas.GreyGasSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>
       convective adjustment: <class 'climlab.convection.convadj.ConvectiveAdjustment'>

Now we will do something new: let the column absorb some shortwave radiation. We will assume that the shortwave absorptivity is proportional to the ozone concentration we plotted above.

Now we need to weight the absorptivity by the pressure (mass) of each layer.

```python
# This number is an arbitrary parameter that scales how absorptive we are making the ozone
# in our grey gas model
ozonefactor = 75
dp = oz_col.Tatm.domain.lev.delta
epsSW = O3_global.values * dp * ozonefactor
```

We want to use the field `epsSW` as the absorptivity for our SW radiation model.

Let's see what the absorptivity is current set to:

```python
print(oz_col.subprocess['SW'].absorptivity)
```

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

It defaults to zero.

Before changing this (putting in the ozone), let's take a look at the shortwave absorption in the column:

```python
oz_col.compute_diagnostics()
```

```python
oz_col.diagnostics['SW_absorbed_atm']
```

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

Let's now put in the ozone:

```python
oz_col.subprocess['SW'].absorptivity = epsSW
print( oz_col.subprocess['SW'].absorptivity)
```

    [3.20948549e-03 3.37750296e-03 4.71182551e-03 4.57614201e-03
     3.47591203e-03 2.24450924e-03 1.18884331e-03 7.11369788e-04
     5.50761612e-04 4.84170273e-04 4.47141487e-04 4.16507314e-04
     3.70212131e-04 3.11733073e-04 2.62922861e-04 2.40936639e-04
     2.45147939e-04 2.65307555e-04 2.87482272e-04 2.95567946e-04
     2.67120872e-04 2.16427978e-04 1.66169127e-04 1.15468088e-04
     6.79353134e-05 3.81013280e-05]

Let's check how this changes the SW absorption:

```python
oz_col.compute_diagnostics()
oz_col.SW_absorbed_atm
```

    array([1.40571621, 1.47671285, 2.05685574, 1.99236565, 1.50916587,
           0.97239284, 0.51429089, 0.30750441, 0.23797743, 0.20913838,
           0.19309134, 0.17981756, 0.1597929 , 0.13452308, 0.11343944,
           0.10393806, 0.10574109, 0.11442201, 0.12396838, 0.12743553,
           0.11515129, 0.09328389, 0.07161233, 0.04975714, 0.02927232,
           0.01641659])

It is now non-zero, and largest near the top of the column (also top of the array) where the ozone concentration is highest.

Now it's time to run the model out to radiative-convective equilibrium

```python
oz_col.integrate_years(1.)
```

    Integrating for 365 steps, 365.2422 days, or 1.0 years.
    Total elapsed time is 0.9993368783782377 years.

```python
print( oz_col.ASR - oz_col.OLR)
```

    [-0.00396053]

And let's now see what we got!

```python
result_list.append(oz_col.state)
name_list.append('Radiative-Convective equilibrium with O3')
```

```python
# Make a plot to compare observations, Radiative Equilibrium, Radiative-Convective
# Equilibrium, and RCE with ozone!
plot_soundings(result_list, name_list)
```

And we finally have something that looks looks like the tropopause, with temperature increasing above at approximately the correct rate.

There are still plenty of discrepancies between this model solution and the observations, including:

- Tropopause temperature is too warm, by about 15 degrees
- Surface temperature is too cold

There are a number of parameters we might adjust if we wanted to improve the fit, including:

- Longwave absorptivity
- Surface albedo

Feel free to experiment! (That's what models are for, after all).

### The take home message

The dominant effect of stratospheric ozone is to vastly increase the radiative equilibrium temperature in the ozone layer. The temperature needs to be higher so that the longwave emission can balance the shortwave absorption.

Without ozone to absorb incoming solar radiation, the **temperature does not increase with height**.

This simple grey-gas model illustrates this principle very clearly.

____________

## Credits

This notebook is part of [The Climate Laboratory](https://brian-rose.github.io/ClimateLaboratoryBook), an open-source textbook developed and maintained by [Brian E. J. Rose](http://www.atmos.albany.edu/facstaff/brose/index.html), University at Albany. It has been modified by [Nicole Feldl](http://nicolefeldl.com), UC Santa Cruz.

It is licensed for free and open consumption under the
[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) license.

Development of these notes and the [climlab software](https://github.com/brian-rose/climlab) is partially supported by the National Science Foundation under award AGS-1455071 to Brian Rose. Any opinions, findings, conclusions or recommendations expressed here are mine and do not necessarily reflect the views of the National Science Foundation.
____________