# A Simple Model of the Labour Market

```python
import numpy as np
from scipy import optimize
import sympy as sm
from math import log
import matplotlib.pyplot as plt
%matplotlib inline
import ipywidgets as widgets
import types
import modelproject_functions as my
```

## Introduction

In this project, we look at a simple model of the labour market described below. First, we will look at the model with a production function with a constant marginal product of labour. Then, we extend the model with a bit more realistic production function with decreasing marginal product of labour. We will both look at the implications of changing the production function for the equilibrium of the model, and how it changes the methods needed to solve the model numerically.

## Model Description

We're considering a simple model for unemployment with the following variables:

- $Y$: Aggregate output
- $L$: Labour
- $P$: Price
- $W$: Nominal wage
- $w \equiv W/P$: Real wage
- $C$: Consumption

We use a neoclassical static labour supply model, where individuals enjoy leisure and consumption and finance their consumption with non-labour income and/or by working. The individuals are identical, so we let one individual represent them all.

The representative individual has a CRRA **utility function**:

$$
\begin{align}
&u(C, L) = \log (C) - \phi \frac{L^{1+\frac{1}{\epsilon}}}{1+\frac{1}{\epsilon}}
\end{align}
$$

where $\phi$ scales the disutility of labour, and $\epsilon$ is the elasticity of the labour supply with respect to the real wage rate at a given constant marginal utility of wealth, the so-called Frisch elasticity of labour supply.

The individual's **budget constraint** is:

$$
CP \leq WL \hspace{3ex} \Leftrightarrow \hspace{3ex} C \leq wL
$$

In optimum, the equality must hold:

$$
C = wL
$$

The representative individual has the **maximisation problem**:

$$
\max_{C,L} \log (C) - \phi \frac{L^{1+\frac{1}{\epsilon}}}{1+\frac{1}{\epsilon}} \hspace{3ex} \text{subject to} \hspace{3ex} C = wL
$$

The **Lagrange function** for the individual's maximisation problem is:

$$
\mathcal{L} = \log (C) - \phi \frac{L^{1+\frac{1}{\epsilon}}}{1+\frac{1}{\epsilon}} - \lambda\left( C - wL \right)
$$

We assume perfect competition and look at a representative firm. The representative firm has the **production function**:

$$
Y = L
$$

I.e. there is constant marginal product of labour.

For simcplicity, we normalise the price such that $P=1$. The firm's **profit function** is:

$$
\Pi = PY - WL = Y - wL
$$

Hence, the firm faces the maximisation problem:

$$
\max_{w} Y - wL \hspace{3ex} \text{subject to} \hspace{3ex} Y = L
$$

The **market clearing condition** is that the aggregate consumption equals aggregate output:

$$
C = Y \hspace{3ex} \Leftrightarrow \hspace{3ex} C = L
$$

## Analytical solution

Define symbols:

```python
L = sm.symbols('L')
C = sm.symbols('C')
w = sm.symbols('w')
Y = sm.symbols('Y')
u = sm.symbols('u')
epsilon = sm.symbols('epsilon')
pi = sm.symbols('pi')
phi = sm.symbols('phi')
lambd = sm.symbols('lambda')
alpha = sm.symbols('alpha')
T = sm.symbols('T')
```

### Problem of the consumer/worker

Define equation:

```python
u = sm.log(C) - phi*(L**(1 + 1/epsilon)/(1 + 1/epsilon))
budget = C - w*L
```

Define the Lagrange function:

```python
lagrange = u - lambd*(budget)
lagrange
```

$\displaystyle - \frac{L^{1 + \frac{1}{\epsilon}} \phi}{1 + \frac{1}{\epsilon}} - \lambda \left(C - L w\right) + \log{\left(C \right)}$

Find the first order condition (FOC) of the problem with respect to consumption:

```python
lagrange_C = sm.diff(lagrange, C)
FOC_C = sm.Eq(lagrange_C, 0)
FOC_C
```

$\displaystyle - \lambda + \frac{1}{C} = 0$

Isolate the Lagrange multiplier, $\lambda$:

```python
lambd_ = sm.solve(FOC_C, lambd)[0]
lambd_
```

$\displaystyle \frac{1}{C}$

Find the FOC of the problem with respect to labour supply:

```python
lagrange_L = sm.diff(lagrange, L)
FOC_L = sm.Eq(lagrange_L, 0)
FOC_L
```

$\displaystyle \lambda w - \frac{L^{1 + \frac{1}{\epsilon}} \phi}{L} = 0$

Insert the expression for the lagrange multiplier:

```python
FOC_L2 = FOC_L.subs(lambd, lambd_)
FOC_L2
```

$\displaystyle - \frac{L^{1 + \frac{1}{\epsilon}} \phi}{L} + \frac{w}{C} = 0$

Insert market clearing condition:

```python
Y = L
FOC_L3 = FOC_L2.subs(C, Y)
FOC_L3
```

$\displaystyle - \frac{L^{1 + \frac{1}{\epsilon}} \phi}{L} + \frac{w}{L} = 0$

Solve for labour supply:

```python
L_supply = sm.solve(FOC_L3, L)[0]
L_supply
```

$\displaystyle \left(\frac{w}{\phi}\right)^{\frac{\epsilon}{\epsilon + 1}}$

This is the **labour supply function**. The labour supply depends positively on the wage and negatively on $\phi$, which makes sense since $\phi$ scales the disutility of labour. Furthermore, the labour supply is increasing in $\epsilon$, the Frisch elasticity. This is because that the higher $\epsilon$ is, the more does the individual respond to a given change in the wage.

### Problem of the firm

Define production function and profit:

```python
pi = Y - w*L
pi
```

$\displaystyle - L w + L$

Find FOC of firm's problem with respect to labour:

```python
pi_L = sm.diff(pi, L)
FOC_L = sm.Eq(pi_L, 0)
FOC_L
```

$\displaystyle 1 - w = 0$

Solve for the wage:

```python
w_opt = sm.solve(FOC_L, w)[0]
w_opt
```

$\displaystyle 1$

In this setup, the firm makes a wage offer corresponding to the FOC of its profit maximisation problem, and the individual responds with a specific labour supply corresponding to the FOC of its utility maximisation problem.

Since the marginal product of labour is constant, the wage offer made by the firm does not depend on the amount of labour already hired in the firm. The firm maximises its profit by setting its marginal costs equal to its marginal benefits. Since the wage is the marginal cost of producing one more unit of output, and the price is the marginal benefit of selling one more unit of output, the firm will always offer the wage $w = P = 1$, which is what we find above.

The **labour demand function** is then just the labour supply that corresponds to a real wage rate of 1.

```python
L_demand = L_supply.subs(w, 1)
L_demand
```

$\displaystyle \left(\frac{1}{\phi}\right)^{\frac{\epsilon}{\epsilon + 1}}$

### Plot of the Labour Market Equilibrium

```python
# a. Specify x's that the functions should be plotted for
xs = np.arange(0.01, 3, 0.05)

# b. Plot labour demand and supply using make_plot() from our module modelproject_function.py (imported as my)
my.make_plot(L_supply, xs=xs, alpha_val=0.9, phi_val=0.5, epsilon_val=0.1)
my.make_plot(L_demand, xs=xs, alpha_val=0.9, phi_val=0.5, epsilon_val=0.1)
plt.grid()
plt.xlabel("Wage, $w$")
plt.ylabel("Labour, $L$")
plt.legend(['Supply', 'Demand'])
plt.title("Figure 1 Labour Market Equilibrium")

# c. Calculate the equilibrium labour supply
supply_func = sm.lambdify((w, phi, epsilon), L_supply)
L_eq = supply_func(1, 0.5, 0.1)
print(f'The labour supply in equilibrium is: {L_eq:.3f}')
```

From Figure 1, it is clear that the labour demand does not depend on the real wage rate, $w$, since it's just a horisontal line. The labour supply, on the other hand, is increasing in the real wage rate. By inserting a real wage rate of 1 in the labour supply function as well as the parameter values below, we get the equilibrium amount of labour, which is 1.07.

$$
\begin{align}
\phi = 0.5 \\
\epsilon = 0.1
\end{align}
$$

## Numerical solution

Since the labour demand/wage offer does not depend on the amount of labour already hired in the firm, it is pretty simple to find the equilibrium - just plug parameter values and $w = 1$ in the labour supply function, as we did above. So, there is no need for numerically optimising, but we're going to do it for the fun it anyways.

```python
# a. Make sure parameters are defined as symbols and not values
w = sm.symbols('w')
phi = sm.symbols('phi')
alpha = sm.symbols('alpha')
epsilon = sm.symbols('epsilon')

# b. Make a python function of the utility and the budget constraint
u_func = sm.lambdify((C, L, phi, epsilon), u)

def budget_func(L, w):
    return w*L
```

```python
# c. Set parameter values
epsilon = 0.1
phi = 0.5
w = 1
objective = lambda L: -u_func(budget_func(L, w), L, phi, epsilon)
L0 = 0.5

# d. Optimize
result = optimize.minimize_scalar(objective, method='Brent')
result
```

         fun: 0.027895710858186817
        nfev: 20
         nit: 16
     success: True
           x: 1.0650410869703453

Luckily, we got the same result as in the analytical solution: The worker will work 1.07 units of time to a real wage rate of 1 with the above specified parameter values.

# Introducing Decreasing Marginal Product of Labour

We now extend the model by changing the production function to be a bit more realistic by having decreasing returns to labour. The **production function** is then:

$$
Y = L^{(1-\alpha)}, \hspace{3ex} 0<\alpha<1
$$

## Analytical solution

Now, we do the same as before, just inserting the new production function.

Define symbols:

```python
L = sm.symbols('L')
C = sm.symbols('C')
w = sm.symbols('w')
Y = sm.symbols('Y')
u = sm.symbols('u')
epsilon = sm.symbols('epsilon')
v = sm.symbols('v')
pi = sm.symbols('pi')
phi = sm.symbols('phi')
lambd = sm.symbols('lambda')
alpha = sm.symbols('alpha')
```

### Problem of the consumer/worker

Everything is the same until inserting the market clearing condition:

```python
Y = L**(1-alpha)
FOC_L3 = FOC_L2.subs(C, Y)
FOC_L3
```

$\displaystyle L^{\alpha - 1} w - \frac{L^{1 + \frac{1}{\epsilon}} \phi}{L} = 0$

Solve for labour supply:

```python
L_supply = sm.solve(FOC_L3, L)[0]
L_supply
```

$\displaystyle \left(\frac{\phi}{w}\right)^{\frac{\epsilon}{\epsilon \left(\alpha - 1\right) - 1}}$

This is the **labour supply function**. We see that it is very close to the expression we found in the other model with constant marginal product of labour. Note that the exponent is negative, so that's why $\phi$ and $w$ have swapped places. Now, the labour supply also depends positively on the input share of labour in the production process, hence it depends negatively on $\alpha$ (since $1-\alpha$ is the input share).

### Problem of the firm

Define production function and profit:

```python
pi = Y - w*L
pi
```

$\displaystyle - L w + L^{1 - \alpha}$

Find FOC of firm's problem with respect to labour:

```python
pi_L = sm.diff(pi, L)
FOC_L = sm.Eq(pi_L, 0)
FOC_L
```

$\displaystyle - w + \frac{L^{1 - \alpha} \left(1 - \alpha\right)}{L} = 0$

Solve for the wage:

```python
w_opt = sm.solve(FOC_L, w)[0]
w_opt
```

$\displaystyle L^{- \alpha} \left(1 - \alpha\right)$

Now, the wage offer depends on the amount of labour already hired in the firm. This is due to the fact that we introduced decreasing marginal product of labour - the more labour hired in the firm, the less is one more unit of labour worth. Therefore, the wage offer is decreasing in the amount of labour already hired in the firm.

Since the wage offer of the firm now depends on the amount of labour, it is more complicated to find the equilibrium than in the previous model. Analytically, it is done by inserting the labour supply function in the FOC of the firm's profit maximisation problem and solving for the labour. This is what we have done below.

```python
FOC2 = FOC_L.subs(L, L_supply)
FOC2
```

$\displaystyle - w + \left(\frac{\phi}{w}\right)^{- \frac{\epsilon}{\epsilon \left(\alpha - 1\right) - 1}} \left(1 - \alpha\right) \left(\left(\frac{\phi}{w}\right)^{\frac{\epsilon}{\epsilon \left(\alpha - 1\right) - 1}}\right)^{1 - \alpha} = 0$

```python
w_eq = sm.solve(FOC2, w)[0]
w_eq
```

$\displaystyle \left(\phi^{\alpha \epsilon} \left(1 - \alpha\right)^{- \alpha \epsilon + \epsilon + 1}\right)^{\frac{1}{\epsilon + 1}}$

The real wage rate in equilibrium now depends on the parametrisation of the model.

### Plot of Analytical Solution

The labour demand function is derived by isolating labour in the FOC of the firm's problem with respect to labour:

```python
L_demand = sm.solve(FOC_L, L)[0]
L_demand
```

$\displaystyle \left(\frac{1 - \alpha}{w}\right)^{\frac{1}{\alpha}}$

```python
# a. Specify x's that the functions should be plotted for
xs = np.arange(0.01, 0.2, 0.01)

# b. Plot the labour supply and demand using make_plot() from our module modelproject_function.py (imported as my)
my.make_plot(L_supply, xs=xs, alpha_val=0.9, phi_val=0.5, epsilon_val=0.1)
my.make_plot(L_demand, xs=xs, alpha_val=0.9, phi_val=0.5, epsilon_val=0.1)
plt.grid()
plt.title("Figure 2 Labour Market Equilibrium with Decreasing \n Marginal Product og Labour")
plt.legend(['Supply', 'Demand'])
plt.xlabel("Wage, $w$")
plt.ylabel("Labour, $L$")
plt.show()
```

Figure 2 shows the market equilibrium using the same parametrisation as before, i.e. $\phi=0.5$ and $\epsilon=0.1$, and a input share of labour of 0.1, i.e. $\alpha=0.9$. The biggest change compared to the model with constant marginal product of labour is the labour demand function, which is not constant anymore, but decreasing in the wage. The amount labour in equilibrium seems to be around 1 as before, but the real wage rate is a lot lower now, around 0.11.

### Numerical solution

The way to solve this model numerically is by trying different values of the real wage rate and see, if the demand for labour equals the labour supply. We do this by writing our own root finder.

**Description of algorithm: Bisection**

We will code up a derivative-free algorithm of the method called bisection. The idea of bisection is to find the value at which a function is zero by following the steps:

1. Set two points, $a_0$ and $b_0$, between which you know that your function, $f(\cdot)$, has a root, and optimally only one root.

2. Compute the function value at the midpoint between $a_0$ and $b_0$, i.e.

  $$
  f(m_0) = f\left(\frac{a_0 + b_0}{2}\right)
  $$

3. Reduce the interval in which the root lies by replacing either $a_0$ or $b_0$ with $m_0$:
   - If $f(a_0)f(m_0)<0$, the start of your interval should be $m_0$ in stead of $a_0$, because this means that the function has different signs at $m_0$ and $b_0$. Hence, the (or a) root of the function is between $m_0$ and $b_0$ (under the assumption that the function is continuous).

   - If $f(b_0)f(m_0)<0$, the end of your interval should be $m_0$ in stead of $b_0$, since the function has different signs at the two points, i.e. the root of the function is between $a_0$ and $m_0$.

4. Repeat steps 2 and 3 until the value of the function in the midpoint is below some tolerance, $\varepsilon$.

The labour supply and the labour demand equals in an equilibrium, so we want to use the root finder on the difference between the supply and demand:

$$
f(w) = L_{supply}(w) - L_{demand}(w)
$$

```python
# a. Define function as difference between labour supply and demand
func = L_supply - L_demand

# b. Plot function using make_plot() from our module modelproject_function.py (imported as my)
my.make_plot(func, xs=xs, alpha_val=0.9, phi_val=0.5, epsilon_val=0.1)
plt.grid()
plt.xlabel("Wage, $w$")
plt.ylabel("Labour, $L$")
plt.title("Figure 3 Difference between Labour Supply and Demand")
plt.show()
```

Figure 3 shows the same as Figure 2 - the difference between labour supply and demand is around 0.11.

Now, we find the root using our own root finder.

```python
# a. Make sure parameters are sympy symbols
epsilon = sm.symbols('epsilon')
phi = sm.symbols('phi')
alpha = sm.symbols('alpha')
w = sm.symbols('w')

# b. Make the difference between labour supply and demand a Python function
func_ = sm.lambdify((w, alpha, phi, epsilon), func)
supply_func = sm.lambdify((w, alpha, phi, epsilon), L_supply) # Labour supply for later

alpha = 0.9
phi = 0.5
epsilon = 0.1

function = lambda w: func_(w, alpha, phi, epsilon)

# c. Find root using bisection() from our module modelproject_function.py (imported as my)
res = my.bisection(function, a=0.01, b=0.2, tol=1e-10)

print(f'Bisection solution: \n The difference between labour supply and demand has the value {res[1]}'
      f'\n at the wage {res[0]:.3f} after {res[2]} iterations')

# d. Find the corresponding labour
L_eq = supply_func(res[0], alpha, phi, epsilon)
print(f'The amount of labour in equilibrium is: {L_eq:.3f}')
```

    Bisection solution:
     The difference between labour supply and demand has the value -1.5510481787828212e-11
     at the wage 0.114 after 28 iterations
    The amount of labour in equilibrium is: 0.864

The result of the numerical optimisation is an equilibrium real wage rate of 0.114, which we also saw from Figure 2 and 3. The corresponding amount of labour is 0.864, hence it is lower than in the model with constant marginal product of labour. This is intuitive, since the firm now get less from one input of labour, it is not willing to pay as much in wage to the worker. Since the real wage rate is now a lot lower, the worker is not willing to work as much, but the labour supply actually doesn't fall that much compared to the other model.

## Results across Parametrisations

We will now further analyse the model with decreasing marginal product of labour by varying the parameters of the model and examine how this changes the equilibrium of the model. We will find the numerical solution to the model using our `bisection()` function, which is in our module `modelproject_functions.py`.

After going through how the different parameters affect the equilibrium, we have made an interactive figure, so you can change the parametrisation yourself and explore the model.

### Changing the Input Share of Labour, $1-\alpha$

Below, we vary the input share of labour, $1-\alpha$, from 0 to 1, and calculate the real wage rate in equilibrium.

```python
# a. Define values to loop over
alph = np.arange(0, 1, 0.01)

# b. Specify other parameter values
epsilon = 0.1
phi = 0.5

# c. Find equilibrium wage for different alphas in loop using bisection() from modelproject_function.py (imported as my)
w_eq = []
for a in alph:
    function = lambda w: func_(w, a, phi, epsilon)
    res = my.bisection(function,  a=0.0001, b=100, tol=1e-10)
    w_eq.append(res[0])

# d. Plot the equilibrium wage as a function of epsilon
input_share = [1 - a for a in alph]
plt.plot(input_share, w_eq)
plt.xlabel(r"Input share of labour, $1 - \alpha$")
plt.ylabel("Equilibrium real wage rate, $w$")
plt.title("Figure 4 Equilibrium Wage and Input Share \n" r"of Labour, $1 - \alpha$");
```

From Figure 4 above, wee see that the higher the input share of labour, the higher is the real wage rate in equilibrium. The input share, $1-\alpha$, captures the degree to which the marginal product of labour is increasing, i.e. how important labour is in the production process. We see that the more important labour is, the more the firm is willing to pay for it.

### Changing the Scale of Disutility of Labour, $\phi$

Below, we vary the scale of the disutility of labour, $\phi$, from 0.1 to 10, and calculate the real wage rate and the supply of labour in equilibrium.

```python
# a. Define values to loop over
ph = np.arange(0.1, 10, 0.01)

# b. Specify other parameter values
epsilon = 0.1
alpha = 0.9

# c. Find equilibrium wage for different phis in loop using bisection() from modelproject_function.py (imported as my)
w_eq = []
for p in ph:
    function = lambda w: func_(w, alpha, p, epsilon)
    res = my.bisection(function, a=0.0001, b=100, tol=1e-10)
    w_eq.append(res[0])

# d. Plot the equilibrium wage as a function of epsilon
plt.plot(ph, w_eq)
plt.xlabel("$\phi$")
plt.ylabel("Equilibrium real wage rate, $w$")
plt.title("Figure 5 Equilibrium Wage and Disutility \n of Labour, $\phi$");
```

```python
# a. Define values to loop over
ph = np.arange(0.1, 10, 0.01)

# b. Specify other parameter values
epsilon = 0.1
alpha = 0.9

# c. Find equilibrium labour for different phis in loop using bisection() from modelproject_function.py (imported as my)
L_eq = []
for p in ph:
    function = lambda w: func_(w, alpha, p, epsilon)
    res = my.bisection(function,  a=0.01, b=10, tol=1e-10)
    L_eq.append(supply_func(res[0], alpha, p, epsilon))

# d. Plot the equilibrium wage as a function of epsilon
plt.plot(ph, L_eq)
plt.xlabel("$\phi$")
plt.ylabel("Equilibrium labour, $L$")
plt.title("Figure 6 Equilibrium Labour and Disutility of \n of Labour, $\phi$");
```

Here, we see that the higher the disutility of labour, the less does the individual work in equilibrium, even though the real wage rate increases in the disutility of labour.

### Interactive Figure

Run the code block below to be able to change $\alpha$ and $\phi$ and see how it changes the equlibrium of the model.

```python
# a. Defining figures to be functions of alpha and phi
def interactive_func(alpha, phi):

    # i. Plot the labour supply and demand using make_plot() from modelproject_function.py (imported as my)
    my.make_plot(L_supply, xs=np.arange(0.01, 1, 0.01), alpha_val=alpha, phi_val=phi, epsilon_val=0.1)
    my.make_plot(L_demand, xs=np.arange(0.01, 1, 0.01), alpha_val=alpha, phi_val=phi, epsilon_val=0.1)
    plt.title('Figure 7 Labour Market equlibrium')
    plt.grid()
    plt.xlabel('Real wage rate, $w$')
    plt.ylabel('Labout supply, $L$')

    # ii. Find equilibrium using bisection() from modelproject_function.py (imported as my)
    function = lambda w: func_(w, alpha, phi, epsilon=0.1)

    res = my.bisection(function, a=0.0001, b=100)

    if type(res) is not tuple:
        print(f'No equilibrium with this parametrisation')

    elif type(res) is tuple:
        eq_w = res[0]
        eq_L = supply_func(res[0], alpha, phi, epsilon=0.1)

        # iii. Print the equilibirum wage and labour
        print(f'Equilibrium real wage rate: {eq_w:.3f}')
        print(f'Equilibrium labour supply: {eq_L:.3f}')

# b. Create interactive plot
widgets.interact(interactive_func,
    alpha=widgets.FloatSlider(description=r"$\alpha$", min=0, max=1, step=0.001, value=0.9),
    phi=widgets.FloatSlider(description="$\phi$", min=0.01, max=10, step=0.05, value=2),
);
```

    interactive(children=(FloatSlider(value=0.9, description='$\\alpha$', max=1.0, step=0.001), FloatSlider(value=…

## Conclusion

In this project, we have looked at two versions of a simple model for the labour market. In both models, it was no problem to find the equilibrium analytically using `sympy`. In the first model with constant marginal product of labour, we found that the model was very easily solved, as the representative firm always would offer the same wage to the worker - one equal to the price. In the other model with decreasing marginal product of labour, finding the equilibrium was less simple, but still not too complicated. We solved the models numerically anyways and found the same as in the analytical solution.

We coded an algorithm to find the equilibrium, a so-called root finder. Our algorithm used the bisection method, that Jeppe presented in lecture 10, which is a derivative-free method. We found the algorithm very effective and intuitive, when you get to write the code yourself.

All in all, we have learned different methods to solve a model in Python, which we might one day use on a model, that cannot be solved by hand.


# Objective mapping of one-dimensional data

Objective mapping is an useful tool for constructing gridded maps of oceanographic variables (*e.g.*, temperature, fluorescence, velocity, streamfunction) as continuous functions from discrete measurements that have random errors. For example, the random error in a measurement can be instrumental, associated with the precision of a thermistor or current meter.

## Theory

Suppose we want to map a scalar variable $D$ with measurements at $n$ sampling locations with coordinates $(x_n,y_n)$. But remember that what we measure is not the clean signal $D(x_n,y_n)$ of the variable. Our data vector $d$, with data values

\begin{equation*}
d_n = D(x_n,y_n) + \epsilon_n,
\end{equation*}

has random error with variance $\epsilon$. The objective map $\hat{D}$ is the best (minimum mean square error) linear estimate of the signal $D$:

\begin{equation*}
\hat{D}(x,y) = \mathbf{G}^T(x,y)\mathbf{d},
\end{equation*}

where $\mathbf{G}$ is the gain vector and $\mathbf{d}$ is the data vector. The entries in the gain vector are continuous functions of $x$ and $y$, and can be written as

\begin{equation}
\mathbf{G} = (\overline{\mathbf{dd}^T})^{-1}\overline{\mathbf{d}D(x,y)},
\end{equation}

where an overbar denotes an average over several sampling realizations.

To calculate the objective map $\hat{D}$ from the measurements $\mathbf{d}$, we only need information about the statistics of the data and the measurement error. Specifically, we need the autocovariance matrix of the data ($\mathbf{dd}^T$) and the variance $\epsilon$ of the random measurement error.

Also, if we assume that

1. The error is normally-distributed;
2. The error is spatially uncorrelated with itself down to the smallest scale resolved by the measurements and
3. The error is **not** correlated with the signal,

then the data autocovariance matrix can be written in this simple form

\begin{equation*}
\overline{\mathbf{dd}^T} = \overline{\mathbf{DD}^T} + \epsilon\mathbf{I}.
\end{equation*}

Let's look at an example with 1D fake data to understand the problem.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from omtools import om1

plt.rcParams['text.usetex'] = True
plt.rcParams['text.latex.unicode'] = True
```

Explain...

```python
verbose = False

eps = 0.1
t = np.linspace(-5,5,20)

# Delete some random data points.
t = np.concatenate((t[:5], t[6:]))
t = np.concatenate((t[:10], t[14:]))

x = np.sin(t)*t**2 + t + np.random.randn(t.size)*eps
tc = np.linspace(-7, 7, 100)

xm01, em = om1(x, t, tc, Td=1., errvar=0.1, unbiased=False, verbose=verbose)
xm03, _ = om1(x, t, tc, Td=1., errvar=0.3, unbiased=False, verbose=verbose)
xm06, _ = om1(x, t, tc, Td=1., errvar=0.6, unbiased=False, verbose=verbose)

xm1s, _ = om1(x, t, tc, Td=1., errvar=0.1, unbiased=False, verbose=verbose)
xm5s, _ = om1(x, t, tc, Td=5., errvar=0.1, unbiased=False, verbose=verbose)
xm10s, _ = om1(x, t, tc, Td=8., errvar=0.1, unbiased=False, verbose=verbose)

# Unbiased maps.
xm01_unb, em_unb = om1(x, t, tc, Td=1., errvar=0.1, unbiased=True, verbose=verbose)
xm03_unb, _ = om1(x, t, tc, Td=1., errvar=0.3, unbiased=True, verbose=verbose)
xm06_unb, _ = om1(x, t, tc, Td=1., errvar=0.6, unbiased=True, verbose=verbose)

xm1s_unb, _ = om1(x, t, tc, Td=1., errvar=0.1, unbiased=True, verbose=verbose)
xm5s_unb, _ = om1(x, t, tc, Td=5., errvar=0.1, unbiased=True, verbose=verbose)
xm10s_unb, _ = om1(x, t, tc, Td=8., errvar=0.1, unbiased=True, verbose=verbose)

## Plotting.
fig, ax = plt.subplots(ncols=2, figsize=(16,6))
ax1, ax2 = ax
ax1.plot(t, x, 'ko', ms=7)
ax1.plot(tc, xm1s, 'b.', linestyle='solid', ms=5, label=r'$T_d$ = 1 h, biased')
ax1.plot(tc, xm5s, 'r.', linestyle='solid', ms=5, label=r'$T_d$ = 5 h, biased')
ax1.plot(tc, xm10s, 'g.', linestyle='solid', ms=5, label=r'$T_d$ = 8 h, biased')

ax1.plot(tc, xm1s_unb, 'b.', alpha=0.25, linestyle='solid', ms=5, label=r'$T_d$ = 1 h, unbiased')
ax1.plot(tc, xm5s_unb, 'r.', alpha=0.25, linestyle='solid', ms=5, label=r'$T_d$ = 5 h, unbiased')
ax1.plot(tc, xm10s_unb, 'g.', alpha=0.25, linestyle='solid', ms=5, label=r'$T_d$ = 8 h, unbiased')

ax1.set_xlabel(r'Time [h]', fontsize=18, fontweight='black')
ax1.set_ylabel(r'$u$ [m/s]', fontsize=18, fontweight='black')
ax1.set_title(r'Sensitivity to decorrelation timescale', fontsize=18, fontweight='black')
ax1.grid()
_ = ax1.legend(loc='best', fancybox=True, fontsize=12)

# Sensitivity to sampling error variance.
ax2.plot(t, x, 'ko', ms=7)
ax2.plot(tc, xm01, 'b.', linestyle='solid', ms=5, label=r'$\epsilon$ = 0.1 (m/s)$^2$, biased')
ax2.plot(tc, xm03, 'r.', linestyle='solid', ms=5, label=r'$\epsilon$ = 0.3 (m/s)$^2$, biased')
ax2.plot(tc, xm06, 'g.', linestyle='solid', ms=5, label=r'$\epsilon$ = 0.6 (m/s)$^2$, biased')

ax2.plot(tc, xm01_unb, 'b.', alpha=0.25, linestyle='solid', ms=5, label=r'$\epsilon$ = 0.1 (m/s)$^2$, unbiased')
ax2.plot(tc, xm03_unb, 'r.', alpha=0.25, linestyle='solid', ms=5, label=r'$\epsilon$ = 0.3 (m/s)$^2$, unbiased')
ax2.plot(tc, xm06_unb, 'g.', alpha=0.25, linestyle='solid', ms=5, label=r'$\epsilon$ = 0.6 (m/s)$^2$, unbiased')

ax2.set_xlabel(r'Time [h]', fontsize=18, fontweight='black')
ax2.set_ylabel(r'$u$ [m/s]', fontsize=18, fontweight='black')
ax2.set_title(r'Sensitivity to sampling error variance', fontsize=18, fontweight='black')
ax2.grid()
_ = ax2.legend(loc='best', fancybox=True, fontsize=12)
```

## **TODO**: Fix unbiased objective map error and add error plot here.

The error of the objective map is measured by the mean square difference between the map and the signal (normalized by the signal variance), *i.e.*,

\begin{equation*}
\text{NMSD} = \frac{\overline{(\hat{D} - D)^2}}{\overline{D^2}} = 1 - \frac{(\overline{D\mathbf{d}})^T(\overline{\mathbf{dd}^T})^{-1}\overline{\mathbf{d}D(x,y)}}{\overline{D^2}}.
\end{equation*}

Recall that this NMSD quantifies **how well the map estimates** the **_continuous_** distribution of the variable we are measuring at **_discrete_** points. The map-signal NMSD depends on our choices of

1. The model for the *autocovariance matrix* of the variable (we assumed it to be Gaussian);
2. The *decorrelation length* and
3. The variance of the *measurement* error.

## References

+ [Dan Rudnick's lecture notes on objective mapping](http://chowder.ucsd.edu/Rudnick/SIO_221B_files/objective_map.pdf) (*analysis of physical oceanographic data*, SIOC221B).
+ Bretherton *et al.* (1976). "A technique for objective analysis and design of oceanographic experiments applied to MODE-73." In Deep Sea Research and Oceanographic Abstracts, vol. 23, no. 7, pp. 559-582. Elsevier.


```python
import numpy as np
import pandas as pd
import linearsolve as ls
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Class 16: Real and Nominal Business Cycle Data

In this notebook, we will briefly explore US macroeconomic data suggesting that, contrary to the assumptions of most RBC models, there is in fact a relationship between real and nominal quantities over the business cycle. We will estimate an aggregate supply equation for the US and a monetary policy rule for the FOMC.

## Data

The file `business_cycle_data_actual_trend_cycle.csv`, available at https://github.com/letsgoexploring/econ126/raw/master/Data/Csv/business_cycle_data_actual_trend_cycle.csv, contains actual and trend data for real GDP per capita, real consumption per capita, real investment per capita, real physical capital per capita, TFP, hours per capita, the rea money supply (M2), (nominal) interest rate on 3-month T-bills, the PCE inflation rate, and the unemployment rate; each at quarterly frequency. The GDP, consumption, investment, capital, and money supply data are in terms of 2012 dollars. Hours is measured as an index with the value in October 2012 set to 100.

```python
# Read business_cycle_data_actual_trend.csv into a Pandas DataFrame with the first column set as the index and parse_dates=True

# Print the last five rows of the data

```

### Exercise: GDP and Inflation

Construct a plot of the cyclical components of GDP and inflation.

```python
# Construct plot

# Place legend to right of figure. PROVIDED
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
```

### Exercise: GDP and the 3-Month T-Bill Rate

Construct a plot of the cyclical components of GDP and the 3-month T-bill rate.

```python
# Construct plot

# Place legend to right of figure. PROVIDED
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
```

### Correlations Between GDP, Inflation, and 3-Month T-Bill Rate

Compute the coefficients of corrrelation between GDP, inflation, and the 3-month T-bill rate.

```python

```

Strong (but not perfect!) correlations between GDP and inflation and GDP and the T-bill rate suggest link between nominal and real quantities over the business cycle that should be exaplined by business cycle theory.

## Aggregate Supply

Contemporary business cycle models for monetary policy emphasize the importance of an upward-sloping aggreagate supply (AS) curve: a positive relationship between the cyclical components of output and inflation. We will uncover evidence of an upward-sloping AS curve for the US by estimating the following equation:

\begin{align}
\hat{\pi}_t & = \beta \hat{y}_t + \epsilon_t,
\end{align}

where $\hat{\pi}_t$ is the cyclical component of inflation, $\hat{y}_t$ is the cyclical component of real GDP for the US, and $\epsilon_t$ is the resudual of the regression. We do not estimate a constant because the data are mean 0.

Note that estimation exercise is subject to an endogeneity bias because output and inflation are in fact determined simultaneously in equilbrium. However, while the results we obtain my not be robust, they will be suggestive and that's enough for now.

```python
# Import statsmodels.api

```

### OLS Regression

```python
# Define variable 'X' to be equal to the cyclical component of real GDP

# Define variable 'X' to be equal to the cyclical component of inflation

# Initialze the OLS model

# Fit the OLS model

# Display estimation results

```

Apparently, a percent increase in GDP relative to trend corresponds with a 0.6 percent increase in the inflation rate.

### Plot results

Construct a scatter plot of the underelying data with the predicted values from the OLS regression.

```python
# Create variable 'x_values' as an array from -10 to 6

# Create variable 'y_values' using estimated value for beta

# Create figure and axis

# Scatter plot with poitn size of 50, opacity 0.25, and label of 'actual'

# Plot estimated relationship as a red line with label of 'fitted'

# Set x-axis limits to [-10,6]

# Axis labels and title

# Legend in the lower right corner with scatterpoints=1

# Grid

```

## Taylor's Monetary Policy Rule

Central banks in many countries set monetary policy with at least some autonomy. In the US, the Federal Reserve has broad discretion to decide how to manage its federal funds rate target in response to changes in the economy. The Federal Open Market Committee (FOMC), the monetary policymaking body within the Fed, comprises members of the Board of Governors and a rotating group of presidents from five of the regional Federal Reserve Banks.

The members of the FOMC can vote to set policy how they like and the composition of the FOMC changes regularly. So it might seem that modeling the decisionmaking of the FOMC would not be fruitful. But in fact, evidence shows that the FOMC does behave in a remarkably systematic way.

In his article "Discretion versus policy rules in practice" in the December 1993 issue of the *Carnegie-Rochester Conference Series on Public Policy*, John Taylor shows that monetary policy in the US could be approximated by a simple linear equation. (link to article: https://www.sciencedirect.com/science/article/pii/016722319390009L). His equation is:

\begin{align}
\text{Federal funds rate} & = \text{Inflation} + 0.5\cdot \text{Output gap} + 0.5\cdot(\text{Inflation - 2}) + 2,
\end{align}

where *inflation* is the percent change in the GDP deflator over the previous year and *output gap* is the percent deviation of GDP from a "target rate". Taylor assumed a target rate consistent with a constant growth GDP path of 2.2%. Note that the coefficients on inflation sum to 1.5.

You can see how well Taylor's rule fit the data as of 1993 in this figure from his paper:

Figure 1. Taylor's rule and the actual federal funds rate. Source: Taylor (1993)

Taylor doesn't estimate his equation precisely and it's remarkable that such a simple linear equation would even come close to matching policy. In this example, we will gather data to replicate Taylor's rule and estimate his rule using OLS.

```python
# Initial an empty DataFrame that will store data

```

### Federal Funds Rate

The federal funds are is the interest rate on overnight interbank loans. The FOMC sets a taget for the fed funds rate and implements monetary policy so that the actual (or *effective*) federal unds rate equals the FOMS's target. The effective federal funds rate has FRED series ID `FEDFUNDS` and is available here: https://fred.stlouisfed.org/series/FEDFUNDS. Notice that there are 62 lines of text *before* the line starting with "DATE".

```python
# Import federal funds rate data from FRED. Use arguments: sep='\s+',skiprows=62,index_col=0,parse_dates=True

# Set fed_funds equal to 'VALUE' column of fed_funds

# Use .resample('QS').mean() method of 'fed_funds' to convert the fed funds data from monthdy to quarterly

# Plot the federal funds rate. PROVIDED
plt.plot(fed_funds)
```

# Inflation

Taylor computes the inflation rate as the percent change in the GDP deflator from one year prior:

\begin{align}
\text{Inflation}_t = \frac{\text{GDP deflator}_t - \text{GDP deflator}_{t-4}}{\text{GDP deflator}_{t-4}}\cdot 100
\end{align}

The GDP deflator has FRED series ID `GDPDEF` and is available here: https://fred.stlouisfed.org/series/GDPDEF. Notice that there are 15 lines of text *before* the line starting with "DATE".

```python
# Import GDP deflator data from FRED. PROVIDED
deflator = pd.read_csv('https://fred.stlouisfed.org/data/GDPDEF.txt',sep='\s+',skiprows=15,index_col=0,parse_dates=True)

# Set deflator equal to 'VALUE' column of deflator. PROVIDED
deflator = deflator['VALUE']

# Compute the inflation rate

# Plot the inflation rate data. PROVIDED
plt.plot(inflation)
```

### Output gap

The output gap is measured as the percent difference of real GDP from the CBO's estimate of the potential real GDP:

\begin{align}
\text{Output gap} & = \left(\frac{\text{Real GDP}-\text{Real potential GDP}}{\text{Real potential GDP}}\right)\cdot 100
\end{align}

Real GDP has FRED series ID `GDPC1` and is available here: https://fred.stlouisfed.org/series/GDPC1. Notice that there are 17 lines of text *before* the line starting with "DATE".

```python
# Import real GDP data from FRED. PROVIDED
gdp_actual = pd.read_csv('https://fred.stlouisfed.org/data/GDPC1.txt',sep='\s+',skiprows=17,index_col=0,parse_dates=True)

# Set gdp_actual equal to 'VALUE' column of gdp_actual. PROVIDED
gdp_actual = gdp_actual['VALUE']
```

Potential real GDP has FRED series ID `GDPPOT` and is available here: https://fred.stlouisfed.org/series/GDPPOT. Notice that there are 12 lines of text *before* the line starting with "DATE".

```python
# Import potential real GDP data from FRED. PROVIDED
gdp_potential = pd.read_csv('https://fred.stlouisfed.org/data/GDPPOT.txt',sep='\s+',skiprows=12,index_col=0,parse_dates=True)

# Set gdp_potential equal to 'VALUE' column of gdp_potential. PROVIDED
gdp_potential = gdp_potential['VALUE']
```

```python
# Plot the actual and potential real GDP data with a legend. CELL PROVIDED
plt.plot(gdp_actual,label='actual')
plt.plot(gdp_potential,label='potential')
plt.legend(loc='lower right')
plt.title('Real GDP')
```

### Combine Data into a DataFrame

```python
# Create variable 'df' that is a DataFrame storing fed funds, inflation, actual and potential real GDP

# Drop missing values from 'df'

```

```python
# Construct an output gap column

```

Recall Taylor's rule:

\begin{align}
\text{Federal funds rate} & = \text{Inflation} + 0.5\cdot \text{Output gap} + 0.5\cdot(\text{Inflation - 2}) + 2
\end{align}

```python
# Construct a column called 'taylor' that implements Taylor's rule

```

```python
# Plot actual federal funds rate and the federal funds rate implied by Taylor's rule. CELL PROVIDED
plt.plot(df['fed_funds'],label='actual')
plt.plot(df['taylor'],label='Taylor')
plt.title('Federal Funds Rate')
plt.legend()
```

### OLS Model of the FOMC's Monetary Policy Rule

Following the format suggested by Taylor's rule, we'll estimate the following monetary policy rule for the US:

\begin{align}
\hat{i}_t & = \bar{\imath} + \phi_{\pi}\pi_t + \phi_{y} y_t + \epsilon_t
\end{align}

where $\pi_t$ is the percent change in the GDP deflator over the previous year and $y_t$ is the output gap measured as the percent difference of real GDP from the CBO's estimate of the potential real GDP. $\phi_{\pi}$ is the weight that the FOMC places on inflation in the rule and $\phi_{y}$ is the weight that the central bank places on the output gap. $\epsilon_t$ is the residual of the regression.

```python
# Create variable 'X' with columns inflation, output and a constant

# Create variable 'Y' equal to the federal funds rate

# Initialize OLS model

# Fit OLS model

# Print regression results

```

The estimated constant in the regression is about 0.94 compared with 1 in Taylor's rule. The estimated coefficnets on inflation and the output gap are 1.22 and 0.12 compared with 1.5 and 0.5 in Taylor's rule. All three parameter estimates are remarkably close to Taylor's proposal. It's amazing that his rule still preforms will after almost 30 years.

Note, Taylor doens't argue that his rule is *optimal* in the sense that it is the best way for the FOMC to meet it's objectives. But there is evidence that it is actually very close to an optimal rule and therefore evidence that the FOMC does in fact tend to make good policy decisions.

```python
# Add column 'fed_funds_predicted' to df equal to predicted fed funds rate values from regression

```

```python
# Plot actual federal funds rate and the predicted federal funds. CELL PROVIDED
plt.plot(df['fed_funds'],label='actual')
plt.plot(df['fed_funds_predicted'],label='predicted')
plt.title('Federal Funds Rate')
plt.legend()
```


# 1. City level spread dynamics

## 1.2. Heterogeneity in spreading the disease

Thinking about the possibility of catching a disease, it is natural to divide the corresponding population heterogeneity into "social" and "biological" parts.

Same is true for the heterogeneity of spreading the disease. A person's ability to spread the disease depends on her social life (activity, frequency of being in crowded places, etc) and her "bioligical" properties like how often does she cough during illness. As for the latter, I do not currently see how to simulate the dymamics of heterogeneity of spreading in "biological" part. On the other hand, the non-trivial dynamics of the "social" part can be modeled on the assumption that "social" heterogeneity in the ability to catch the disease and "social" heterogeneity in the ability to spread the disease are controlled by the same factors and thus can be regarded as the identical heterogeneities.

Below I model the dynamic of "social" heterogeneity in the ability to catch and spread the disease togeter with the dynamic of "biological" heterogeneity in the ability to catch (but not spread) the disease for the SIR model.

```python
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as scst
import scipy.special as scsp
import scipy.interpolate as scint
from scipy.integrate import odeint
import matplotlib.ticker as ticker
from dataclasses import dataclass
plt.rc('legend',fontsize=12, handlelength=2) # using a size in points

plt.style.use('seaborn-darkgrid')
```

For the beginning I repeat the SIR model with heterogenity of getting infected (HC SIR from now on) from previous part. HC SIR model heterogenity is described by lognormal distribution with initial (logarithmic) variance parameter $v_0$. HC SIR equations are
$$\begin{align}
& r_0= \alpha\, M(\mu,v), \\[3pt]
& \frac{d S}{dt}=- r_0 I\,S, \\[3pt]
& \frac{dI}{dt} =- \frac{dS}{dt}- I,\\[3pt]
& \frac{d\mu}{dt}=- r_0 I\,v, \\[3pt]
& \frac{d v}{dt}=-r_0 I\,v^2, \\[3pt]
&R = 1-S-I
\end{align}$$
$$I(0)=I_0,\:S(0)=1-I_0,\:\mu(0)=0,\:v(0)=v_0,\: \alpha=\frac{r_0(0)}{M(0,v_0)}$$
where $M(\mu,v)$ - expectation of lognormal variable
$M(\mu,v)=\exp(\mu+v/2)$

```python
modeling_period=8.0
t_step=0.005
m_t=np.arange(0.0, modeling_period+t_step, t_step,dtype=np.float64)
n_steps=len(m_t)-1

def M(mu,v):return np.exp(mu+0.5*v)
```

Function to solve HC SIR equations is rather straightforward. It calculates $S$usceptible,$I$nfectious,$R$ecovered proportion values and also infection rate $|dS|$

```python
def HC_SIR(r0_0,I0,v0):
    '''
    Heterogeneous in Catching
    SIR model
    '''
    α=r0_0/M(0.0,v0)

    def ODE(y,t):
        S,I,mu,v=y

        minus_r0_mult_I=-α*M(mu,v)*I

        dS = minus_r0_mult_I*S
        dI = -dS - I
        dmu = minus_r0_mult_I*v
        dv = dmu*v
        return [dS, dI, dmu, dv]

    y0=[1.0-I0,I0,0.0,v0]

    sol = odeint(ODE,y0, m_t).T
    dS=-ODE(sol,m_t)[0]
    S,I,mu,v=sol
    R=1.0-S-I

    return S,I,R,dS
```

And now see the dynamics

```python
v0=2.5
r0_0=5.2
I0=0.0002
S,I,R,dS= HC_SIR(r0_0,I0,v0)

fig, ax = plt.subplots(dpi=120)
plt.plot(m_t, S, color='C0', label=f'$S$')
plt.plot(m_t, I,color='C1', label=f'$I$')
plt.plot(m_t, R,color='C3', label=f'$R$')
plt.plot(m_t, dS/(I*S)/r0_0,color='C4', label=f'$r_0(t)/r_0(0)$')
ax.set_ylim([-0.05,1.05])
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.1))
ax.set_ylabel(f'$SIR$ values, $r_0(t)/r_0(0)$')
plt.legend(loc='center left')
plt.xlabel(f'$t$')
ax2 = ax.twinx()
plt.plot(m_t, dS, color='C2', label=f'$|dS/dt|$')
ax2.set_ylim([-0.01,0.26])
ax2.yaxis.set_major_locator(ticker.MultipleLocator(0.025))
ax2.grid(False)
ax2.set_ylabel(f'The number of new cases per unit of time $|dS/dt|$')
plt.legend(loc='upper right')
plt.title(f'HC SIR dynamic $v=${v0}, $r_0(0)=${r0_0}')
ax.text(4,-.3, 'Fig.1', ha='center')
plt.show()
print(f'R+I at max |dS/dt| is {(R+I)[np.argmax(dS)]}.')

```

To store the results for further comparisons we create a simple dataclass.

```python
@dataclass
class SIR_solution:
    S:np.ndarray
    I:np.ndarray
    R:np.ndarray
    dS:np.ndarray

HC_dynamics=SIR_solution(S,I,R,dS)
```

Before heterogeneity of spreading the disease modelling we describe (log normally distributed) total heterogeneity in ability of getting infected among susceptible population as a combination of "social" (denoted by $s$ subscript) and "biological" (denoted by $b$ subscript) parts.

This way previosly used the univariate lognormal distribution of scalar variable $h$ described by evolving parameters $\mu,v$ becomes a bivariate lognormal distribution of vector variable $\mathbf{h}=(h_s,h_b)$ described by evolving parameters $\mu_s,\mu_b,C,v_s,v_b$ and $h=h_s h_b$. Here C is the covariance of $\ln(h_s)$ and $\ln(h_b)$.  The joint probability density function for $h_s,h_b$ is:

$$
    f(h_s,h_b) =
      \frac{1}{2 \pi h_s h_b \sqrt{(1-\rho^2)v_s v_b}} \\
      \cdot\exp\left(
        -\frac{1}{(1-\rho^2)}\left[
          \frac{(\ln(h_s)-\mu_s)^2}{2v_s} +
          \frac{(\ln(h_b)-\mu_b)^2}{2v_b} -
          \frac{C(\ln(h_s)-\mu_s)(\ln(h_b)-\mu_b)}{v_s v_b}
        \right]
      \right)
$$

where $\rho^2=\frac{C^2}{v_s v_b}$ - the square of the (logarithmic) correlation coefficient.

Using reasoning similar to that in the previous part, and more complex mathematics, new equations are obtained that describe the dynamics of "social" and "biological" heterogeneity of the susceptible population ability to become infected.
$$\begin{align}
& r_0= \alpha\, M(\mu_s,\mu_b,C,v_s,v_b), \\[3pt]
& \frac{d S}{dt}=- r_0 I\,S, \\[3pt]
& \frac{dI}{dt} =- \frac{dS}{dt}- I,\\[3pt]
& \frac{d\mu_s}{dt}=- r_0 I\,(C+v_s), \\[3pt]
& \frac{d\mu_b}{dt}=- r_0 I\,(C+v_b), \\[3pt]
& \frac{dC}{dt}=- r_0 I\,(C+v_s)\,(C+v_b), \\[3pt]
& \frac{d v_s}{dt}=-r_0 I\,(C+v_s)^2, \\[3pt]
& \frac{d v_b}{dt}=-r_0 I\,(C+v_b)^2, \\[3pt]
&R = 1-S-I
\end{align}$$

$$I(0)=I_0,\:S(0)=1-I_0,\:\mu_s(0)=\mu_b(0)=C(0)=0 \\ v_s(0)+v_b(0)=v_0,\: \alpha=\frac{r_0(0)}{M(0,0,0,v_s,v_b)}$$
where $M(\mu_s,\mu_b,C,v_s,v_b)$ - expectation of bivariate lognormal variable
$M(\mu_s,\mu_b,C,v_s,v_b)=\exp(\mu_s+\mu_b+C+(v_s+v_b)/2)$

If we denote the sum $\mu_s+\mu_b$ as $\mu$ and $2C+v_s+v_b$ as $v$ we get exactly previos  HC SIR system.

```python
def HC_SIR2(r0_0,I0,vs0,vb0):
    α=r0_0/M(0.0,vs0+vb0)

    def ODE(y,t):
        S,I,mus,mub,C,vs,vb=y
        Cpvs=C+vs
        Cpvb=C+vb
        minus_r0_mult_I=-α*M(mus+mub,Cpvs+Cpvb)*I

        dS = minus_r0_mult_I*S
        dI = -dS - I
        dmus = minus_r0_mult_I*Cpvs
        dmub = minus_r0_mult_I*Cpvb
        dC = dmus*Cpvb
        dvs = dmus*Cpvs
        dvb = dmub*Cpvb
        return [dS, dI, dmus,dmub,dC,dvs,dvb]

    y0=[1.0-I0,I0,0.0,0.0,0.0,vs0,vb0]

    sol = odeint(ODE,y0, m_t).T
    dS=-ODE(sol,m_t)[0]
    S,I,R=sol[0],sol[1],1.0-sol[0]-sol[1]
    C=sol[-3]
    vs=sol[-2]
    vb=sol[-1]
    return S,I,R,dS,C,vs,vb
```

And the result is

```python
vs0=1.3
vb0=v0-vs0
S,I,R,dS,C,vs,vb= HC_SIR2(r0_0,I0,vs0,vb0)

fig, ax = plt.subplots(dpi=120)
plt.plot(m_t, S, color='C0', label=f'$S$')
plt.plot(m_t, I,color='C1', label=f'$I$')
plt.plot(m_t, R,color='C3', label=f'$R$')
ax.set_ylim([-0.05,1.05])
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.1))
ax.set_ylabel(f'$SIR$ values')
plt.legend(loc='center left')
plt.xlabel(f'$t$')
ax2 = ax.twinx()
plt.plot(m_t, dS, color='C2', label=f'$|dS/dt|$')
ax2.set_ylim([-0.01,0.26])
ax2.yaxis.set_major_locator(ticker.MultipleLocator(0.025))
ax2.grid(False)
ax2.set_ylabel(f'The number of new cases per unit of time $|dS/dt|$')
plt.legend(loc='upper right')
plt.title(f'$v=${v0}, $r_0(0)=${r0_0}')

plt.show()
```

Exactly the same as in fig. 1, but we can see relative dynamic of social and biological heterogeneity

```python
fig, ax = plt.subplots(dpi=120)
plt.plot(m_t, vs, color='C0', label=f'$v_s$')
plt.plot(m_t, vb,color='C1', label=f'$v_b$')
ax.set_ylim([0.8,1.35])
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.1))
ax.set_ylabel(f'$v_s$, $v_b$')
plt.legend(loc='center left')
plt.xlabel(f'$t$')
ax2 = ax.twinx()
plt.plot(m_t, C/np.sqrt(vs*vb), color='C2', label=f'$ρ$')
ax2.set_ylim([-0.5,0.05])
#ax2.yaxis.set_major_locator(ticker.MultipleLocator(0.02))
ax2.grid(False)
ax2.set_ylabel(f'Correlation coefficient (of logarithms) $ρ$')
plt.legend(loc='upper right')
plt.title(f'$v=${v0}, $r_0(0)=${r0_0}')

plt.show()
```

It is interesting to note that social and biological heterogeneity becomes more and more anti-correlated over time. A person with a high social ability to pick up a disease can remain uninfected for a long time only if his biological ability to become infected is low and vice versa. Thus, as time goes on the distribution of social and biological ability to become infected among still non-infected people should become more anti-correlated.

If a person with a high social ability to pick up a disease becomes infected, then he, at least for some time, becomes a person with a high social ability to spread the disease. Thus, the social ability to become infected and spread disease flows from the uninfected to the infected as the infection progresses. A decrease in the total social ability to get infected among the susceptible part of the population is accompanied by a similar increase (among the infected part of the population) in the total social ability to infect.

Total social ability to get infected among the susceptible part of the population is a product of everage social ability to get infected among the susceptible part of the population $M_s=M(\mu_s,v_s)=\exp(\mu_s+v_s/2)$ and a portion of susceptible population — $S$. So if we denote as $A$ (from activity) the total social ability to infect we get for its dynamic inflow term
$$\frac{dA}{dt}=-\frac{d(M_s S)}{dt}- \text{[outflow term]}$$

The total social ability to infect reduces as long as infected persons recovered. The population outflow from infected to recovered is described by second term in the right side of the equation:
$$\frac{dI}{dt} =- \frac{dS}{dt}- I$$
Each convalescent takes with him a corresponding portion of the social capacity to infect. Since the order of recovery does not necessarily coincide with the order of illness, we assume that the total share of the social ability to infect taken away by the recovering is simply the product of the average social ability to infect by the number of recoveries. Everage social ability to infect is $a=A/I$ the recoveries outflow is $I$, so outflow term we are looking for is $a\cdot I=A$. That means
$$\frac{dA}{dt}=-\frac{d(M_s S)}{dt}- A$$

For some time we will not worry about the decay of a person's social ability to infect as the disease progresses. We will assume that this decay does not affect the heterogeneity of the distribution, and is taken into account by multiplying or dividing by the corresponding constant.

Now let's take a closer look at the equation
$$\frac{d S}{dt}=- r_0 I\,S$$

It describes the fact that infectious agents infect susceptible individuals at an intensity determined by the coefficient $r_0$. This coefficient should be equal, up to some constant, to the product of the average ability to get infected — $M$ and the average ability to spread the infection — $a$:

$$r_0=\alpha M a$$

and susceptible part dynamics is given by:
$$\frac{d S}{dt}=- \alpha M\, a\, I\,S=- \alpha M \frac{A}{I} I\,S=- \alpha M\, A\,S$$

As a result we get the system:
$$\begin{align}
& M =  \exp(\mu_s+\mu_b+C+(v_s+v_b)/2), \\[3pt]
& M_s =  \exp(\mu_s+v_s/2), \\[3pt]
& \frac{d S}{dt}=- \alpha M A\,S, \\[3pt]
& \frac{dI}{dt} =- \frac{dS}{dt}- I,\\[3pt]
& \frac{dA}{dt} =- \frac{d(M_s S)}{dt}- A,\\[3pt]
& \frac{d\mu_s}{dt}=- \alpha M A\,(C+v_s), \\[3pt]
& \frac{d\mu_b}{dt}=- \alpha M A\,(C+v_b), \\[3pt]
& \frac{dC}{dt}=- \alpha M A\,(C+v_s)\,(C+v_b), \\[3pt]
& \frac{d v_s}{dt}=-\alpha M A\,(C+v_s)^2, \\[3pt]
& \frac{d v_b}{dt}=-\alpha M A\,(C+v_b)^2, \\[3pt]
&R = 1-S-I
\end{align}$$

Don't be afraid of the system size, the system can be made much shorter by some substitutions, but the meaning of new variables will be quite opaque.

Now it's time for $d(M_s S)/dt$ term:

$$
\begin{equation}
\begin{matrix}
&\frac{d(M_s S)}{dt}=\frac{dM_s}{dt} S+M_s\frac{dS}{dt}\\
&=\frac{d(\exp(\mu_s+v_s/2))}{dt} S+M_s\frac{d S}{dt}\\
&=M_s(\frac{d\mu_s}{dt}+\frac{1}{2}\frac{dv_s}{dt})S+M_s\frac{d S}{dt}\\
&=M_s ((C+v_s)+\frac{1}{2}(C+v_s)^2)\alpha M A S+M_s\frac{dS}{dt}\\
&=M_s(1+(C+v_s)+\frac{1}{2}(C+v_s)^2)\frac{dS}{dt}
\end{matrix}
\end{equation}
$$

>The sum $1+(C+v_s)+\frac{1}{2}(C+v_s)^2$ looks like truncated Taylor expansion of the exponent $\exp(C+v_s)$. And this is not coincidence. Another way of reasoning is to calculate inflow term directly. Given probability distribution $f(h_s,h_b)$ discussed above, the inflow term is $\alpha A S\cdot E[h_s^2h_b] =M_s\exp(C+v_s)\dot S$. We don't use this form of the term because it is the therm from "exact" model  and this form of the term does not fit to our approximate model.

Let denote $M_s(1+(C+v_s)+\frac{1}{2}(C+v_s)^2)$ as $a_s(\mu_s,C,v_s)$ or just $a_s$ then:
$$\frac{d(M_s S)}{dt}=a_s\frac{dS}{dt}$$

The tricky part is choosing the correct starting value for $ A $ — $ A_0 $. I initialize it as a product
$$ A_0 = a_s (0) \cdot I_0 = M_s(0)\left(1 + v_s (0) + \frac {v_s ^ 2 (0)} {2}\right)   I_0 $$

That choice makes the average ability of the infection spreading at the initial moment $ a(0)=A_0 / I_0 $ exactly equal to $ a_s (0) $. There is also arguments based on initial dynamics to be presented later.

Finally, the system for SIR model with heterogeneity of getting infected and spreading the infection (HCS SIR from now on) together with initial conditions are:
$$\begin{align}
& M =  \exp(\mu_s+\mu_b+C+\frac{1}{2}(v_s+v_b)), \\[3pt]
& M_s =  \exp(\mu_s+\frac{1}{2}v_s), \\[3pt]
& a_s =  M_s(1+(C+v_s)+\frac{1}{2}(C+v_s)^2), \\[3pt]
& \frac{d S}{dt}=- \alpha M A\,S, \\[3pt]
& \frac{dI}{dt} =- \frac{dS}{dt}- I,\\[3pt]
& \frac{dA}{dt} =- a_s\frac{dS}{dt}- A,\\[3pt]
& \frac{d\mu_s}{dt}=- \alpha M A\,(C+v_s), \\[3pt]
& \frac{d\mu_b}{dt}=- \alpha M A\,(C+v_b), \\[3pt]
& \frac{dC}{dt}=- \alpha M A\,(C+v_s)\,(C+v_b), \\[3pt]
& \frac{d v_s}{dt}=-\alpha M A\,(C+v_s)^2, \\[3pt]
& \frac{d v_b}{dt}=-\alpha M A\,(C+v_b)^2, \\[3pt]
&R = 1-S-I
\end{align}$$

$$I(0)=I_0,\:S(0)=1-I_0,\:\mu_s(0)=\mu_b(0)=C(0)=0,\: \\ v_s(0)=v_{s0},\: v_b(0)=v_{b0},\: \alpha=\frac{r_0(0)}{a_s(0)M(0)},\: A_0=a_s(0)  I_0$$

```python
def HCS_SIR(r0_0,I0,vs0,vb0):
    '''
    Heterogeneous in Catching and Spreading
    SIR model
    '''
    def a_s(mus,C,vs):
        Cpvs=C+vs
        return M(mus,vs)*(1.0+Cpvs*(1.0+0.5*Cpvs))
    M0=M(0.0,vs0+vb0)
    a_s0=a_s(0.0,0.0,vs0)
    α=r0_0/M0/a_s0

    def ODE(y,t):
        S,I,A,mus,mub,C,vs,vb=y
        Cpvs=C+vs
        Cpvb=C+vb
        minus_αMA=-α*M(mus+mub,Cpvs+Cpvb)*A

        dS = minus_αMA*S
        dI = -dS - I
        dA= -a_s(mus,C,vs)*dS-A
        dmus = minus_αMA*Cpvs
        dmub = minus_αMA*Cpvb
        dC = dmus*Cpvb
        dvs = dmus*Cpvs
        dvb = dmub*Cpvb
        return [dS, dI,dA, dmus,dmub,dC,dvs,dvb]

    A0=a_s0*I0
    y0=[1.0-I0,I0,A0,0.0,0.0,0.0,vs0,vb0]

    sol = odeint(ODE,y0, m_t).T
    S,I,A,mus,mub,C,vs,vb=sol
    dS=-ODE(sol,m_t)[0]
    R=1.0-S-I
    return S,I,R,dS
```

And we can explore the result.

```python
S,I,R,dS= HCS_SIR(r0_0,I0,vs0,vb0)

fig, ax = plt.subplots(dpi=120)
plt.plot(m_t, S, color='C0', label=f'$S$')
plt.plot(m_t, I,color='C1', label=f'$I$')
plt.plot(m_t, R,color='C3', label=f'$R$')
plt.plot(m_t, dS/(I*S)/r0_0,color='C4', label=f'$r_0(t)/r_0(0)$')
ax.set_ylim([-0.05,1.05])
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.1))
ax.set_ylabel(f'$SIR$ values, $r_0(t)/r_0(0)$')
plt.legend(loc='center left')
plt.xlabel(f'$t$')
ax2 = ax.twinx()
plt.plot(m_t, dS, color='C2', label=f'$|dS/dt|$')
ax2.set_ylim([-0.01,0.26])
ax2.yaxis.set_major_locator(ticker.MultipleLocator(0.025))
ax2.grid(False)
ax2.set_ylabel(f'The number of new cases per unit of time $|dS/dt|$')
plt.legend(loc='upper right')
plt.title(f'HCS SIR dynamic $v=${v0}, $r_0(0)=${r0_0}')

plt.show()

HCS_dynamics=SIR_solution(S,I,R,dS)

print(f'R+I at max |dS/dt| is {(R+I)[np.argmax(dS)]}.')
```

Compared to Fig.1 it is worth to note that HCS SIR model predicts more fast $r_0$ declining and lower infection rate. The peak of the infection rate passed early — when around 10% of the population ever been infected, while for HC SIR model it is 15%.

Now we compare the initial infection rate dynamic for all the models we are considered including SIR and an implied dynamics of 30% daily growth which corresponds to the formula $|dS/dt| \sim\exp(4.2t)$ taking to account that our time unit is 16 days.

To get initial dynamic we fix all parameters at their initial values and consider $S$ being close to one, so we omit it in formulas. For the SIR model it simplifies the equations to

$$
\begin{align}
& \frac{dS}{dt} = - r_0 I, \\[3pt]
& \frac{dI}{dt} = r_0 I- I, \\[3pt]
&I(0)=I_0
\end{align}
$$
It has the solution
$$I(t)=I_0\exp((r_0-1)t),\dot{S}=-r_0I_0\exp((r_0-1)t)$$

For the HC SIR model the simplified equations are:
$$\begin{align}
& \frac{d S}{dt}=- \alpha\, M(0) I, \\[3pt]
& \frac{dI}{dt} = \alpha\, M(0) I- I,\\[3pt]
&I(0)=I_0
\end{align}$$
As we choose $\alpha =r_0/M(0)$ so $r_0 =\alpha M(0)$ the solution is the same
$$I(t)=I_0\exp((r_0-1)t),\dot{S}=-r_0(0)I_0\exp((r_0-1)t)$$

And finally, initial dynamics HCS SIR equations:
$$\begin{align}
& \frac{d S}{dt}=- \alpha M(0) A, \\[3pt]
& \frac{dI}{dt} =\alpha M(0) A- I,\\[3pt]
& \frac{dA}{dt} =\alpha a_s(0) M(0) A- A,\\[3pt]
&I(0)=I_0,A(0)=a_s(0)I_0
\end{align}$$
As we choose  $\alpha =r_0/(a_s(0)M(0))$ last differential equation becomes $\dot{A} =r_0 A- A$ and the solution for $A$ is $A(t)=A_0\exp((r_0-1)t)=a_s(0)I_0\exp((r_0-1)t)$ substituting to the first equation we get $\dot{S}=-r_0(0)I_0\exp((r_0-1)t)$ and you can check that $I(t)=I_0\exp((r_0-1)t)$ is the solution for the second eqution.

So, taking to account our choice of $\alpha$ and $A_0$ the initial dynamics of all three models should be the same.

Let's check it.

First we get ordinary SIR solution.

```python
def SIR(y,t,r0):
    S,I=y[0],y[1]
    dS = -r0*I*S
    dI= -dS - I
    return [dS, dI]

SIR_y0= [1.0 -I0, I0]

SIR_sol = odeint(SIR,SIR_y0, m_t,(r0_0,)).T
S,I=SIR_sol
SIR_dynamics=SIR_solution(S,I,1.0-S-I,-SIR(SIR_sol,m_t,r0_0)[0])
```

And comparative starting dynamics...

```python
fig, ax = plt.subplots(dpi=120)
plt.plot(m_t,np.exp(4.2*m_t-6.9), color='C0', label=f'implied starting dynamics $\sim \exp(4.2t)$')
plt.plot(m_t,SIR_dynamics.dS, color='C1', label=f'SIR model')
plt.plot(m_t,HC_dynamics.dS, color='C2', label=f'HC SIR model')
plt.plot(m_t,HCS_dynamics.dS, color='C4', label=f'HCS SIR model')
ax.set_ylabel(f'The number of new cases per unit of time $|dS/dt|$')
ax.set_ylim([0.001,1])
ax.set_xlim([0.0,1.5])
plt.yscale('log')
plt.legend(loc='upper left')
plt.xlabel(f't')
plt.show()

```

Now it's time to return to the decay of a person's social ability to infect as the disease progresses. As have been mentioned above it can be taken into account by dividing outflow term by the corresponding constant smaller than one. That way we replace outflow term $A$ by the term $\frac{A}{\tau}$.  Here $\tau$ can be interpreted as the average proportion of asymptomatic carriers of the disease (or average duration of asymptomatic stage as a part of whole the illness period). As has been reported, from 25% to 35% of COVID-19 infected are ill with no symptoms. Adding up those who has minor symptoms and not yet decrease social ability to spread I have chosen 0.4 as a value for  $\tau$. So modified the equation for $A$ is

$$\frac{dA}{dt}=-\frac{d(M_s S)}{dt}- \frac{A}{\tau}$$

But this modification creates us a problem with initial dynamics. Indeed, now initial dynamics for $A$ is described by equation $\dot{A} =(r_0-1/\tau) A$ instead of $\dot{A} =(r_0-1) A$. To fix this we  change $\alpha$ and $A_0$ following way:
$$\alpha=\frac{r_0(0)-1+1/{\tau}}{a_s(0)M(0)},\: A_0=\frac{r_0(0)}{r_0(0)-1+1/{\tau}}a_s(0)  I_0$$
or denoting $$\frac{r_0(0)}{r_0(0)-1+1/{\tau}}a_s(0)$$ as $a_s^0$

$$\alpha=\frac{r_0(0)}{a_s^0 M(0)},\: A_0=a_s^0  I_0$$

The modified function for the dynamics calculation is

```python
def HCS_SIR(r0_0,I0,τ,vs0,vb0):
    '''
    Heterogeneous in Catching and Spreading
    SIR model
    '''
    def a_s(mus,C,vs):
        Cpvs=C+vs
        return M(mus,vs)*(1.0+Cpvs*(1.0+0.5*Cpvs))
    M0=M(0.0,vs0+vb0)
    a_s0=r0_0*a_s(0.0,0.0,vs0)/(r0_0-1.0+1.0/τ)
    α=r0_0/M0/a_s0

    def ODE(y,t):
        S,I,A,mus,mub,C,vs,vb=y
        Cpvs=C+vs
        Cpvb=C+vb
        minus_αMA=-α*M(mus+mub,Cpvs+Cpvb)*A

        dS = minus_αMA*S
        dI = -dS - I
        dA= -a_s(mus,C,vs)*dS-A/τ
        dmus = minus_αMA*Cpvs
        dmub = minus_αMA*Cpvb
        dC = dmus*Cpvb
        dvs = dmus*Cpvs
        dvb = dmub*Cpvb
        return [dS, dI,dA, dmus,dmub,dC,dvs,dvb]

    A0=a_s0*I0
    y0=[1.0-I0,I0,A0,0.0,0.0,0.0,vs0,vb0]

    sol = odeint(ODE,y0, m_t).T
    S,I,A,mus,mub,C,vs,vb=sol
    dS=-ODE(sol,m_t)[0]
    R=1.0-S-I
    return S,I,R,dS
```

Let's look on the resulting dynamics

```python
τ=0.4

S,I,R,dS= HCS_SIR(r0_0,I0,τ,vs0,vb0)

fig, ax = plt.subplots(dpi=120)
plt.plot(m_t, S, color='C0', label=f'$S$')
plt.plot(m_t, I,color='C1', label=f'$I$')
plt.plot(m_t, R,color='C3', label=f'$R$')
plt.plot(m_t, dS/(I*S)/r0_0,color='C4', label=f'$r_0(t)/r_0(0)$')
ax.set_ylim([-0.05,1.05])
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.1))
ax.set_ylabel(f'$SIR$ values, $r_0(t)/r_0(0)$')
plt.legend(loc='center left')
plt.xlabel(f'$t$')
ax2 = ax.twinx()
plt.plot(m_t, dS, color='C2', label=f'$|dS/dt|$')
ax2.set_ylim([-0.01,0.26])
ax2.yaxis.set_major_locator(ticker.MultipleLocator(0.025))
ax2.grid(False)
ax2.set_ylabel(f'The number of new cases per unit of time $|dS/dt|$')
plt.legend(loc='upper right')
plt.title(f'HCS SIR dynamic $v=${v0}, $r_0(0)=${r0_0}')

plt.show()

HCS_dynamics=SIR_solution(S,I,R,dS)

print(f'R+I at max |dS/dt| is {(R+I)[np.argmax(dS)]}.')
```

As expected we have lower infection rate and lower PIP values.

Next we draw PIP (persentage of the infected population at infection rate peak) dependencies on $v_s$ and $v_b$. Both $v_s$ and $v_b$ are in range [0.3,1.3]. First collect the values form uniform grid:

```python
def PIPF(r0,I0,τ):
    def F(v):
        S,_,_,dS=HCS_SIR(r0,I0,τ,*v)
        return 100.0-100.0*S[np.argmax(dS)]
    return F
v_min=0.3
v_max=1.3
axis_range=np.linspace(v_min, v_max, 41)
Vs, Vb = np.meshgrid(axis_range, axis_range)

#to get PIP more pricise we use smaller step and modelling length of 3 time units is enough
m_t=np.arange(0.0, 3.0, t_step*.1,dtype=np.float64)
PIP=np.apply_along_axis(PIPF(r0_0,I0,τ),0,[Vs,Vb])

```

Next, draw them as a map

```python
fig, ax = plt.subplots(dpi=144)
contours = plt.contour(Vs, Vb, PIP, 5, colors='black')
plt.clabel(contours, inline=True, fontsize=12)
ax.set_xlabel(f'$v_s$')
ax.set_ylabel(f'$v_b$')

plt.imshow(PIP, extent=[v_min,v_max, v_min,v_max], origin='lower',
           cmap='viridis', alpha=0.7)
plt.colorbar();
plt.title(f'PIP for HCS SIR model depending on $v_s$ and $v_b$')
plt.show()
```

Let's make this image more informative placing values of ratio top 10% bound to bottom 10% bound for appropriate $v$ along the axes. We need a function for the ratio calculation.

```python
range0901=scst.norm.ppf(0.9)-scst.norm.ppf(0.1)
def get_range(v):
    '''
    ratio of top 10% bound to bottom 10% bound
    for lognormal distribution with given (logarithmic) variance v
    '''
    σ=np.sqrt(v)
    return np.exp(range0901*σ)

#b09=scst.norm.ppf(0.9)
#b01=scst.norm.ppf(0.1)
#def get_range(v):
#    '''
#    ratio of top 10% average to bottom 10% average
#    for lognormal distribution with given (logarithmic) variance v
#    '''
#    σ=np.sqrt(v)
#    return scst.norm.cdf(σ-b09)/(1.0-scst.norm.cdf(σ-b01))
```

Using the function transform the grid to the "ratio" domain and interpolate the values to new uniform grid in this domain

```python
PIP_interpolator=scint.Rbf(get_range(Vs), get_range(Vb), PIP,smooth=1)

r_min=get_range(v_min)
r_max=get_range(v_max)
NperAx=200
r_axis_range=np.linspace(r_min,r_max,NperAx)
Rs, Rb = np.meshgrid(r_axis_range, r_axis_range)
rPIP=np.zeros((NperAx,NperAx))
for i in range(NperAx):
    for j in range(NperAx):
        rPIP[i,j]=PIP_interpolator(Rs[i,j],Rb[i,j])
```

Finally, draw the result

```python
fig, ax = plt.subplots(dpi=144)
contours = plt.contour(Rs, Rb, rPIP, 5, colors='black')
plt.clabel(contours, inline=True, fontsize=12)
ax.set_xlabel(f'$r_s$')
ax.set_ylabel(f'$r_b$')

plt.imshow(rPIP, extent=[r_min,r_max, r_min,r_max], origin='lower',
           cmap='viridis', alpha=0.7)
plt.colorbar();
plt.title(f'PIP for HCS SIR model depending on $r_s$ and $r_b$')
plt.show()
```

Next interesting relationship is the relationship between PIP and the final percentage of the population never infected (FPNI). As FPNI value we use percentage of never infected population after 100 time units.

```python
def PIP_FPNIfunc(r0,I0,τ,vb0):
    def f(v):
        S,_,_,dS=HCS_SIR(r0,I0,τ,v,vb0)
        return 100.0-100.0*S[np.argmax(dS)],100.0*S[-1]
    return f
#small step and short modelling interval to get PIP
m_t1=np.arange(0.0, 3.0, t_step*.1,dtype=np.float64)
#big step and long modelling interval to get FPNI
m_t2=np.arange(t_step*.05, 100.0, t_step*10.0,dtype=np.float64)
#final modelling times
m_t=np.sort(np.concatenate((m_t1,m_t2)))

fig, ax = plt.subplots(dpi=120)
for vb in [0.6,1.0,1.4]:
    plt.plot(*np.vectorize(PIP_FPNIfunc(r0_0,I0,τ,vb))(np.linspace(0.4, 3.0, 80)),
             label=f'$v_b={vb}$')
ax.set_ylabel(f'FPNI')
plt.legend(loc='center left')
plt.xlabel(f'PIP')
plt.legend(loc='upper right')
plt.title(f'$v=${v0}, $r_0(0)=${r0_0}')

plt.show()

```

As we can see, the relationship is insignificantly dependant on $v_b$. So, although it is hard to measure real values of social and biological heterogeneity, possibly, PIP value can be the integral heterogeneity estimation indicating, among other things, the final proportion of ever been infected and uninfected.

Let's choose $v_b$, say, to be one and explore the relation little bit more.

```python
PIP,FPNI=np.vectorize(PIP_FPNIfunc(r0_0,I0,τ,1.0))(np.linspace(0.4, 4.0, 200))
scst.linregress(PIP,FPNI)
```

    LinregressResult(slope=-2.7091016644954147, intercept=99.98907438769432, rvalue=-0.9999839056780101, pvalue=0.0, stderr=0.001092318504719951)

Roughly speaking, having PIP one percent fewer saves 2.7% percent population uninfected. Or another way: daily infection peak reached when 37% (1/2.7) of disease wave is passed.

Let me remind you that all of the above models do not include public measures and change in individual behavior as the epidemic develops.

```python

```


#  ¿Cómo se mueve un péndulo?

> Calificaciones: https://docs.google.com/spreadsheets/d/1X8sAHmrIErYgoAjTocclAFS0Mx_EA8BDWlp6DgYyBVo/edit?usp=sharing

> Se dice que un sistema cualquiera, mecánico, eléctrico, neumático, etc., es un oscilador armónico si, cuando se deja en libertad fuera de su posición de equilibrio, vuelve hacia ella describiendo oscilaciones sinusoidales, o sinusoidales amortiguadas en torno a dicha posición estable.
- https://es.wikipedia.org/wiki/Oscilador_armónico

Referencias:
   - http://matplotlib.org
   - https://seaborn.pydata.org
   - http://www.numpy.org
   - http://ipywidgets.readthedocs.io/en/latest/index.html

**En realidad esto es el estudio de oscilaciones. **
___

```python
from IPython.display import YouTubeVideo
YouTubeVideo('k5yTVHr6V14')
```

Los sistemas mas sencillos a estudiar en oscilaciones son el sistema ` masa-resorte` y el `péndulo simple`.

\begin{align}
\frac{d^2 x}{dt^2} +  \omega_{0}^2 x &= 0, \quad \omega_{0} = \sqrt{\frac{k}{m}}\notag\\
\frac{d^2 \theta}{dt^2} + \omega_{0}^{2}\, \theta &= 0, \quad\mbox{donde}\quad \omega_{0}^2 = \frac{g}{l}
\end{align}

___
## Sistema `masa-resorte`

La solución a este sistema `masa-resorte` se explica en términos de la segunda ley de Newton. Para este caso, si la masa permanece constante y solo consideramos la dirección en $x$. Entonces,
\begin{equation}
F = m \frac{d^2x}{dt^2}.
\end{equation}

¿Cuál es la fuerza? **Ley de Hooke!**
\begin{equation}
F = -k x, \quad k > 0.
\end{equation}

Vemos que la fuerza se opone al desplazamiento y su intensidad es proporcional al mismo.  Y $k$ es la constante elástica o recuperadora del resorte.

Entonces, un modelo del sistema `masa-resorte` está descrito por la siguiente **ecuación diferencial**:

\begin{equation}
\frac{d^2x}{dt^2} + \frac{k}{m}x = 0,
\end{equation}

cuya solución se escribe como
\begin{equation}
x(t) = A \cos(\omega_{o} t) + B \sin(\omega_{o} t)
\end{equation}
Y su primera derivada (velocidad) sería
\begin{equation}
\frac{dx(t)}{dt} = \omega_{0}[- A \sin(\omega_{0} t) + B\cos(\omega_{0}t)]
\end{equation}

 Ver en el tablero que significa solución de la ecuación diferencial.

### **¿Cómo se ven las gráficas de $x$ vs $t$ y $\frac{dx}{dt}$ vs $t$?**

_Esta instrucción es para que las gráficas aparezcan dentro de este entorno._

```python
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
# Podemos cambiar qué tan grandes son los números de los ejes
import matplotlib as mpl
label_size = 14
mpl.rcParams['xtick.labelsize'] = label_size
mpl.rcParams['ytick.labelsize'] = label_size
```

```python
import numpy as np
```

```python
# Definición de funciones a graficar

# Gráfico

```

Y si consideramos un conjunto de frecuencias de oscilación, entonces

```python
frecuencias = np.array([.1, .2 , .5, .6])   # Vector de diferentes frecuencias
plt.figure(figsize = (7, 4))                # Ventana de gráfica con tamaño

# Graficamos para cada frecuencia

```

Si queremos manipular un poco mas las cosas, hacemos uso de lo siguiente:

```python
from ipywidgets import *
```

```python
def masa_resorte(t=0):
    A, B, w0 = .5, .1, .5                            # Parámetros
    x = A*np.cos(w0*t)+B*np.sin(w0*t)                # Función de posición

    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(x, [0],  'ko', ms = 10)
    ax.set_xlim(xmin = -0.6, xmax = .6)
    ax.axvline(x=0, color = 'r')
    ax.axhline(y=0, color = 'grey', lw = 1)
    fig.canvas.draw()
```

```python
interact(masa_resorte, t=(0, 50,.01));
```

La opción de arriba generalmente será lenta, así que lo recomendable es usar `interact_manual`.

```python
interact_manual(masa_resorte, t=(0, 50,.01));
```

___
## Péndulo simple
Ahora, si fijamos nuestra atención al movimiento de un péndulo simple _(oscilaciones pequeñas)_, la ecuación diferencial a resolver tiene la misma forma:

\begin{equation}
\frac{d^2 \theta}{dt^2} + \omega_{0}^{2}\, \theta = 0, \quad\mbox{donde}\quad \omega_{0}^2 = \frac{g}{l}.
\end{equation}

La diferencia más evidente es como hemos definido a $\omega_{0}$. Esto quiere decir que,

\begin{equation}
\theta(t) = A\cos(\omega_{0} t) + B\sin(\omega_{0}t)
\end{equation}

Si graficamos la ecuación de arriba vamos a encontrar un comportamiento muy similar al ya discutido anteriormente. Es por ello que ahora veremos el movimiento en el plano $xy$. Es decir,

\begin{align}
x &= l \sin(\theta), \quad
y = l \cos(\theta)
\end{align}

```python
# Podemos definir una función que nos entregue theta dados los parámetros y el tiempo
def theta_t(a, b, g, l, t):
    omega_0 = np.sqrt(g / l)
    return a * np.cos(omega_0 * t) + b * np.sin(omega_0 * t)
```

```python
# Hacemos un gráfico interactivo del péndulo
def pendulo_simple(t=0):
    pass
```

```python
interact_manual(pendulo_simple, t=(0, 10,.01));
```

### Condiciones iniciales

Realmente lo que se tiene que resolver es,

\begin{equation}
\theta(t) = \theta(0) \cos(\omega_{0} t) + \frac{\dot{\theta}(0)}{\omega_{0}} \sin(\omega_{0} t)
\end{equation}

> **Actividad.** Modificar el programa anterior para incorporar las condiciones iniciales.

```python
# Solución:
def theta_t(theta0, dtheta0, g, l, t):
    pass
```

```python
def pendulo_simple(t=0):
    fig = plt.figure(figsize = (5,5))
    ax = fig.add_subplot(1, 1, 1)
    l = 2
    g = 9.8
    theta0 = np.pi / 4
    dtheta0 = 0
    x = l * np.sin(theta_t(np.pi / 4, 0, 9.8, 2, t))
    y =  - l * np.cos(theta_t(np.pi / 4, 0, 9.8, 2, t))
    ax.plot(x, y, 'ko', ms = 10)
    ax.plot([0], [0], 'rD')
    ax.plot([0, x ], [0, y], 'k-', lw = 1)
    ax.set_xlim(xmin = -2.2, xmax = 2.2)
    ax.set_ylim(ymin = -2.2, ymax = .2)
    fig.canvas.draw()

interact_manual(pendulo_simple, t=(0, 10,.01));
```

### Plano fase  $(x, \frac{dx}{dt})$

La posición y velocidad para el sistema `masa-resorte` se escriben como:

\begin{align}
x(t) &= x(0) \cos(\omega_{o} t) + \frac{\dot{x}(0)}{\omega_{0}} \sin(\omega_{o} t)\\
\dot{x}(t) &= -\omega_{0}x(0) \sin(\omega_{0} t) + \dot{x}(0)\cos(\omega_{0}t)]
\end{align}

```python
# Parámetros
k = 3  #constante elástica [N]/[m]
m = 1 # [kg]
omega_0 = np.sqrt(k/m)
x_0 = .5
dx_0 = .1
```

```python
# Vector de tiempo

```

```python
# Posición y velocidad

```

```python
# Gráficas en el tiempo

```

```python
# posición vs. velocidad

```

```python
# Usando scatter y una paleta de colores

```

#### Multiples condiciones iniciales

```python
k = 3  #constante elástica [N]/[m]
m = 1 # [kg]
omega_0 = np.sqrt(k/m)
```

```python
# Condiciones iniciales
x_0s = np.array([.7, .5, .25, .1])
dx_0s = np.array([.2, .1, .05, .01])
```

```python
# Vector de tiempo

```

```python
# Gráficas

```

Trayectorias del oscilador armónico simple en el espacio fase $(x,\, \dot{x}\,/\omega_0)$ para diferentes valores de la energía.

# Anuncios parroquiales

## 1. Examen final: lo habilito el miércoles 5 de mayo, entregan el viernes 7 de mayo.

## 2. Proyecto tercer módulo para el viernes 14 de mayo.

## 3. Quiz la próxima clase

Created with Jupyter by Lázaro Alonso. Modified by Esteban Jiménez Rodríguez


# Revisão de conceitos estatísticos II

Vamos explorar alguns conceitos estatísticos aplicados à análise de sinais.

```python
# importar as bibliotecas necessárias
import numpy as np # arrays
import matplotlib.pyplot as plt # plots
from scipy.stats import norm
plt.rcParams.update({'font.size': 14})
import IPython.display as ipd # to play signals
import sounddevice as sd
```

# Distribuição de probabilidade de uma VA discreta

A distribuição de probabilidade de uma VA discreta é caracterizada pelas probabilidades de $x$(valores individuais) ser igual a $x_{i}$ (valor escolhido) para cada valor possível.

Consideremos o lançamento de uma dado de 6 façes não viciado. O universo amostral é:

$\Omega = {1, 2, 3, 4, 5, 6}$,
com 6 elementos.

A distribuição de probabilidade é dada por:

\begin{equation}
p(x=x_i) = \frac{1}{6}
\end{equation}

```python
plt.figure(figsize = (8,6))
plt.stem(np.arange(1,7), (1/6)*np.ones(6), '-b', markerfmt = 'ob', basefmt=" ", use_line_collection=  True)
plt.xticks(np.arange(1,7))
#plt.xticks(np.arange(1,7))
plt.grid(linestyle = '--', which='both')
plt.xlabel(r'Eventos - $x_i$')
plt.ylabel(r'$p(x)$')
plt.xlim((0, 7))
plt.ylim((0, 1/5));
```

# Distribuição cumulativa de probabilidade de uma VA discreta

A distribuição cumulativa especifica a probabilidade de $x \leq x_{i}$.

\begin{equation}
F(x) = P(x \leq x_i)
\end{equation}

Consideremos o lançamento do dado não viciado do exemplo anterior. Dentro do universo amostral ($\Omega = {1, 2, 3, 4, 5, 6}$, com 6 elementos), a probabilidade de obtermos um número $\leq 4$ ($\Omega = {1, 2, 3,}$) é $F(4) = P(x \leq 4) = 4/6 = 2/3$

```python
n = np.arange(-5, 10)
Fx = (1/6)*n
Fx[n<1] = 0
Fx[n>6] = 1

plt.figure(figsize = (8,6))
plt.stem(n, Fx, '-b', markerfmt = 'ob', basefmt=" ", use_line_collection=  True)
plt.xticks(n)
#plt.xticks(np.arange(1,7))
plt.grid(linestyle = '--', which='both')
plt.xlabel(r'Eventos - $x_i$')
plt.ylabel(r'$F(x) = p(x \leq x_i)$')
plt.xlim((n[0], n[-1]))
plt.ylim((0, 1.2));
```

# Distribuição cumulativa de probabilidade de uma VA contínua

A distribuição cumulativa especifica a probabilidade de $x \leq x_{i}$.

\begin{equation}
F(x) = P(x \leq x_i)
\end{equation}

As propriedades da distribuição cumulativa são:

- $F(-\infty)=0$
- $F(+\infty)=1$
- $F(x_{2}) \geq  F(x_{1})$, se $x_{2}\geq x_{1}$

Tomando a última propriedade, podemos definir a densidade de probabilidade por:

\begin{equation}
p(x) = \frac{\mathrm{d}F(x)}{\mathrm{d} x},
\end{equation}
que nos dá a probabilidade de encontrar o valor $x$ num intervalo infinitesimal.

```python
# parâmetros de uma distribuição normal
mu_x = 0.0
sigma_x = 1.0

# A distribuição cumulativa e a densidade de probabilidade
x = np.arange(-10, 10, 0.01)
Fx = norm.cdf(x, loc= mu_x, scale = sigma_x)
px = norm.pdf(x, loc= mu_x, scale = sigma_x)

# A relação entre F(x) e p(x)
x_1 = -1
x_2 = 1.5
x_section = np.arange(x_1, x_2, 0.01)

# plots
plt.figure()
plt.plot(x, Fx, color = 'b', linewidth = 2)
plt.fill_between(x_section, norm.cdf(x_section, loc= mu_x, scale = sigma_x), color = 'blue', alpha = 0.3)
plt.grid(linestyle = '--', which='both')
plt.title('Dist. Cumulativa')
plt.xlabel('x')
plt.ylabel(r'$F(x)$')
plt.xlim((x[0], x[-1]))
plt.ylim((0, 1.2))

plt.figure()
plt.plot(x, px, color = 'b', linewidth = 2)
plt.fill_between(x_section, norm.pdf(x_section, loc= mu_x, scale = sigma_x), color = 'blue', alpha = 0.3)
plt.grid(linestyle = '--', which='both')
plt.title('Dist. de densidade de probabilidade')
plt.xlabel('x')
plt.ylabel(r'$p(x)$')
plt.xlim((x[0], x[-1]))
plt.ylim((0, 1.2*np.amax(px)))
plt.tight_layout();
```

# Ruído aleatório com distribuição normal

Neste exemplo nós consideramos um sinal gerado a partir do processo de obter amostras de uma distribuição normal, dada por:

\begin{equation}
\mathcal{N}(\mu_x, \sigma_x) = \frac{1}{\sqrt{2\pi}}\mathrm{e}^{-\frac{1}{2\sigma^2}(x-\mu_x)^2}
\end{equation}
em que $\mu_x$ é a média e $\sigma_{x}$ é o desvio padrão.

Imaginemos então, que a cada instante de tempo $t$, nós sorteamos um valor da distribuição normal.

```python
fs = 200
time = np.arange(0,2, 1/fs)

# sinal
mu_x = 0
sigma_x = 1.0
xt = np.random.normal(loc = mu_x, scale = sigma_x, size=len(time))

# A distribuição cumulativa e a densidade de probabilidade
x = np.arange(-4, 4, 0.01)
Fx = norm.cdf(x, loc= mu_x, scale = sigma_x)
px = norm.pdf(x, loc= mu_x, scale = sigma_x)

# Sombreando suas probabilidades
x_1 = -0.5
x_2 = 0.8
x_section = np.arange(x_1, x_2, 0.01)

# Figura
fig, axs = plt.subplots(1, 2, gridspec_kw={'width_ratios': [8, 4]}, figsize = (14, 4))
axs[0].plot(time, xt, '-b', linewidth = 1)
axs[0].fill_between(time, x_1, x_2,  color = 'lightcoral', alpha = 1)
axs[0].set_title(r'Sinal aleatório com dist. Normal ($\mu_x$ e $\sigma_x$ ctes.)')
axs[0].grid(linestyle = '--', which='both')
axs[0].set_xlabel('Tempo [s]')
axs[0].set_ylabel('Amplitude [-]')
axs[0].set_xlim((0, time[-1]))
axs[0].set_ylim((-4, 4))

axs[1].plot(x, px, color = 'b', linewidth = 2)
axs[1].fill_between(x_section, norm.pdf(x_section, loc= mu_x, scale = sigma_x),  color = 'lightcoral', alpha = 1)
axs[1].set_title(r'Dens. Prob. Normal')
axs[1].grid(linestyle = '--', which='both')
axs[1].set_ylabel(r'$p(x)$ [-]')
axs[1].set_xlabel(r'x [-]')
axs[1].set_xlim((x[0], x[-1]))
axs[1].set_ylim((-0.01, 1.2*np.amax(px)))
plt.tight_layout()

```

Aula 50 - Rev conceitos estatisticos II/.ipynb_checkpoints/conceitos estatisticos II-checkpoint.ipynb