# Model selection using GpABC

## Problem formulation

This notebook illustrates how to perform [model selection](https://tanhevg.github.io/GpABC.jl/latest/overview-ms/) using the algorithm described in (Toni et al, 2009). The example here is adapted from the SIR (susceptible-infected-recovered) model in Section 3.3 of the [paper](https://royalsocietypublishing.org/doi/10.1098/rsif.2008.0172#d3e1991).

The example gives two models that describe the evolution of the number of of susceptible ($S$), infected ($I$) and recovered ($R$) individuals in a population (the number of dead individuals is not included in the model).

There are two possible models, both of which are described below. Given some observed data on the number of individuals in each disease state over time we wish to determine which of the two models is better supported by these data. The data was generated using the first model so the algorithm should reflect this.

### Model 1

A basic SIR model assumes that each individual can be infected once and is then able to infect other individuals immediately.

The model is given by

\begin{align}
    \dot{S} &= \alpha - \gamma S I - d S, \\
    \dot{I} &= \gamma S I - v I - d I, \\
    \dot{R} &= v I - d R \, ,
\end{align}

where the notation $\dot{x}$ denotes $\text{d}x/\text{d}t$.

The paramters of this model are the birth rate $\alpha$, the infection rate $\gamma$, the recovery rate $v$ and the death rate $d$. Note that the death rate is constant for all three disease states.

### Model 2

A slightly more sophisticated model includes a time delay between an individual becoming infected and being able to infect others by introducing an additional latent state ($L$). This model is given by

\begin{align}
    \dot{S} &= \alpha - \gamma S I - d S , \\
    \dot{L} &= \gamma S I - \delta L - d L , \\
    \dot{I} &= \delta L - v I - d I , \\
    \dot{R} &= v I - d R \, ,
\end{align}

where $\delta$ is the transition rate from latent to infected.

### References
- Toni, T., Welch, D., Strelkowa, N., Ipsen, A. and Stumpf, M.P., 2009. Approximate Bayesian computation scheme for parameter inference and model selection in dynamical systems. Journal of the Royal Society Interface, 6(31), pp.187-202. https://doi.org/10.1098/rsif.2008.0172

# To run this notebook locally:
* Make sure that [Jupyter](https://jupyter.org/) is installed, along with [Julia](https://www.julialang.org/) and its [Jupyter Kernel](https://github.com/JuliaLang/IJulia.jl).
* Clone or download [GpABC.jl](https://github.com/tanhevg/GpABC.jl) to your machine.
* Run `jupyter` from the `examples` directory of the local copy of `GpABC.jl`:
```bash
$ cd GpABC.jl/examples
$ jupyter notebook model-selection-example.ipynb
```
* The first line of the cell below activates the local project environment and downloads all the dependencies.

```julia
import Pkg; Pkg.activate("."); Pkg.resolve()
using GpABC, OrdinaryDiffEq, Distributions, Distances, Plots
pyplot()
```

     Resolving package versions...

    Plots.PyPlotBackend()

Prepare the observed data into the correct format: a 2D array with size (number of trajectories, number of time points).

The observed data contains information the number of susceptible, infected and recovered individuals at 13 time points.

```julia
times = [0.0, 0.6, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0]
data = [[ 20.     ,  10.     ,   0.     ],
        [  0.12313,  13.16813,   9.42344],
        [  0.12102,   7.17251,  11.18957],
        [  0.09898,   2.36466,  10.0365 ],
        [  0.37887,   0.92019,   6.87117],
        [  1.00661,   0.61958,   4.44955],
        [  1.20135,   0.17449,   3.01271],
        [  1.46433,   0.28039,   1.76431],
        [  1.37789,   0.0985 ,   1.28868],
        [  1.57073,   0.03343,   0.81813],
        [  1.4647 ,   0.28544,   0.52111],
        [  1.24719,   0.10138,   0.22746],
        [  1.56065,   0.21671,   0.19627]]

data = hcat(data...);
```

Next, we define the models. As in other `GpABC` functions, the model is represented using a function that takes parameters as an argument and returns trajectories that are the solution to the differential equation system. This is called the `simulator_function`. The solution should have the same shape as the reference data, which is (number of trajectories, number of time points).

This means that for the second model, which has 4 states, we need to modify the `simulator_function` in order to ignore the solution for the latent state $L$ as we have no reference data for that trajectory.

We also need to define different initial conditions for the two models as the first model has 3 states and the second has 4.

```julia
# Need initial conditions for each model since they have different numbers of states
ic1 = [20.0, 10.0, 0.0]
ic2 = [20.0, 0.0, 10.0, 0.0]
ics = [ic1, ic2]

# Define a simulator function for each model. Each function returns the solution of the model
# as a 2D array for some parameter values. The appropriate ODE is solved at the same time
# points as the observed data
function simulator1(params::Array{Float64,1})

    # p = (alpha, gamma, d, v)
    # x = (S, I, R)
    function model1(dx, x, p, t)
        dx[1] = p[1] - p[2]*x[1]*x[2] - p[3]*x[1] # dS/dt = alpha - gamma*S*I - d*S
        dx[2] = p[3]*x[1]*x[2] - p[4]*x[2] - p[3]*x[2] # dI/dt = gamma*S*I - v*I - d*I
        dx[3] = p[4]*x[2] - p[3]*x[3] # dR/dt = v*I - d*R
    end

    return(Array{Float64,2}(
        solve(ODEProblem(model1, ics[1], (times[1], times[end]), params),
                RK4(), saveat=times, force_dtmin=true)))
end

function simulator2(params::Array{Float64,1})

    # p = (alpha, gamma, d, v, delta)
    # x = (S, L, I, R)
    function model2(dx, x, p, t)
        dx[1] = p[1] - p[2]*x[1]*x[3] - p[3]*x[1] # dS/dt = alpha - gamma*S*I - d*S
        dx[2] = p[2]*x[1]*x[3] - p[5]*x[2] - p[3]*x[2] # dL/dt = gamma*S*I - delta*L - d*L
        dx[3] = p[5]*x[2] - p[4]*x[3] - p[3]*x[3] # dI/dt = delta*L - v*I - d*I
        dx[4] = p[4]*x[3] - p[3]*x[4] # dR/dt = v*I - d*R
    end

    # Model2 contains the species L, which is not measured - we remove it from the returned ODE solution
    # so that it can be compared to the reference data "data", which only contains S, I and R
    return(Array{Float64,2}(
        solve(ODEProblem(model2, ics[2], (times[1], times[end]), params),
                RK4(), saveat=times, force_dtmin=true)))[[1,3,4],:]
end;
```

We also need a set of priors for each model, as well as a summary statistic and threshold schedule. We will compare the full model solution with the data, which is the "`keep_all`" summary statistic. The model prior will be the default discrete uniform prior with span 2 (as there are two models).

```julia
#
# Priors and initial conditions - these are model-specfic as each model can
# have different numbers of parameters/species
#
priors1 = [Uniform(0.0, 5.0) for i in 1:4]
priors2 = vcat([Uniform(0.0, 5.0) for i in 1:4], Uniform(0.0, 10.0))
priors3 = vcat([Uniform(0.0, 5.0) for i in 1:4], Uniform(0.0, 10.0))

threshold_schedule = [20, 15, 10, 5, 3, 2.5, 2, 1.7, 1.5]
summary_statistic = "keep_all";
```

We are now ready to perform the model selection computation by calling [`SimulatedModelSelection`](https://tanhevg.github.io/GpABC.jl/latest/ref-ms/#GpABC.SimulatedModelSelection) and accepting 200 particles at each population (`n_particles = 200`).

First, we will do this using full model simulations:

```julia
n_particles = 200

ms_sim_result = SimulatedModelSelection(data,
    [simulator1, simulator2],
    [priors1, priors2],
    threshold_schedule,
    n_particles);
```

    ┌ Info: GpABC model selection simulation  Population 1 - ABC Rejection ϵ = 20.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:9
    ┌ Info: GpABC model selection simulation
    │ Completed 721 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 104	Model 2: 96
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:113
    ┌ Info: GpABC model selection simulation  Population 2 - ABC SMC ϵ = 15.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 1274 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 52	Model 2: 148
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 3 - ABC SMC ϵ = 10.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 3187 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 50	Model 2: 150
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 4 - ABC SMC ϵ = 5.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 7374 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 102	Model 2: 98
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 5 - ABC SMC ϵ = 3.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Warning: Simulated model selection reached maximum number of iterations (10000) on an SMC run - consider trying more iterations.
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:304
    ┌ Info: GpABC model selection simulation
    │ Completed 10000 iterations, accepting 191 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 117	Model 2: 74
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 6 - ABC SMC ϵ = 2.5
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 2113 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 150	Model 2: 50
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 7 - ABC SMC ϵ = 2.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 3604 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 111	Model 2: 89
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 8 - ABC SMC ϵ = 1.7
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 3122 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 107	Model 2: 93
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307
    ┌ Info: GpABC model selection simulation  Population 9 - ABC SMC ϵ = 1.5
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection simulation
    │ Completed 4119 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 150	Model 2: 50
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:307

A plot recipe allows this result to be plotted easily:

```julia
plot(ms_sim_result, title="Model selection result - simulation")
```

The number of accepted particles per population can be seen in the plot. Here, the "population" is the algorithmic population approach that is also taken during ABC-SMC. The number of particles accepted may be taken as a proxy for the posterior probability. Hence, the first model is correctly identified as the correct one.

Now, do the same computation using emulation by calling [`EmulatedModelSelection`](https://tanhevg.github.io/GpABC.jl/latest/ref-ms/#GpABC.EmulatedModelSelection). The only difference between the simulation and emulation function call is that we specify the number of design points as the first argument when using emulation.

We will use 200 design points.

```julia
ms_emu_result = EmulatedModelSelection(data,
    [simulator1, simulator2],
    [priors1, priors2],
    threshold_schedule,
    n_particles,
    200;
    summary_statistic = "keep_all",
    distance_function=Distances.euclidean);
```

    ┌ Info: GpABC model selection emulation  Population 1 - ABC Rejection ϵ = 20.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:9
    ┌ Info: GpABC model selection emulation
    │ Completed 15 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 108	Model 2: 92
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:212
    ┌ Info: GpABC model selection emulation  Population 2 - ABC SMC ϵ = 15.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection emulation
    │ Completed 45 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 19	Model 2: 181
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:398
    ┌ Info: GpABC model selection emulation  Population 3 - ABC SMC ϵ = 10.0
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:24
    ┌ Info: GpABC model selection emulation
    │ Completed 512 iterations, accepting 200 particles in total.
    │ Number of accepted parameters by model:
    │ Model 1: 0	Model 2: 200
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:398
    ┌ Warning: All but one model is dead after population 3 - terminating model selection algorithm
    └ @ GpABC /home/tah17/.julia/packages/GpABC/rp6LJ/src/abc/model_selection.jl:35

```julia
plot(ms_emu_result, title="Model selection result - emulation")
```

We can see that emualtion also correctly identified the first model is as the preferred one.


# Monte Carlo Methods: Lab 1

Take a look at Chapter 10 of Newman's *Computational Physics with Python* where much of this material is drawn from.

```python
from IPython.core.display import HTML
css_file = 'https://raw.githubusercontent.com/ngcm/training-public/master/ipython_notebook_styles/ngcmstyle.css'
HTML(url=css_file)
```

<link href='http://fonts.googleapis.com/css?family=Open+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 1000px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1200px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Open Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:900px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Arvo', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Arvo', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Arvo', serif;
    margin-top:16px;
	font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Arvo', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Arvo', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 90%;
}

</style>

## Integration

If we have an ugly function, say

$$
\begin{equation}
  f(x) = \sin^2 \left(\frac{1}{x (2-x)}\right),
\end{equation}
$$

then it can be very difficult to integrate. To see this, just do a quick plot.

```python
%matplotlib inline
import numpy
from matplotlib import pyplot
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
rcParams['figure.figsize'] = (12,6)

from __future__ import division
```

```python
def f(x):
    return numpy.sin(1.0/(x*(2.0-x)))**2
```

```python
x = numpy.linspace(0.0, 2.0, 10000)
pyplot.plot(x, f(x))
pyplot.xlabel(r"$x$")
pyplot.ylabel(r"$\sin^2([x(x-2)]^{-1})$");
```

We see that as the function oscillates *infinitely often*, integrating this with standard methods is going to be very inaccurate.

However, we note that the function is bounded, so the integral (given by the shaded area below) must itself be bounded - less than the total area in the plot, which is $2$ in this case.

```python
pyplot.fill_between(x, f(x))
pyplot.xlabel(r"$x$")
pyplot.ylabel(r"$\sin^2([x(x-2)]^{-1})$");
```

So if we scattered (using a *uniform* random distribution) a large number of points within this box, the fraction of them falling *below* the curve is approximately the integral we want to compute, divided by the area of the box:

$$
\begin{equation}
  I = \int_a^b f(x) \, dx \quad \implies \quad I \simeq \frac{k A}{N}
\end{equation}
$$

where $N$ is the total number of points considered, $k$ is the number falling below the curve, and $A$ is the area of the box. We can choose the box, but we need $y \in [\min_{x \in [a, b]} (f(x)), \max_{x \in [a, b]} (f(x))] = [c, d]$, giving $A = (d-c)(b-a)$.

So let's apply this technique to the function above, where the box in $y$ is $[0,1]$.

```python
def mc_integrate(f, domain_x, domain_y, N = 10000):
    """
    Monte Carlo integration function: to be completed. Result, for the given f, should be around 1.46.
    """
    import numpy.random

    return I
```

```python

```

### Accuracy

To check the accuracy of the method, let's apply this to calculate $\pi$.

The area of a circle of radius $2$ is $4\pi$, so the area of the *quarter* circle in $x, y \in [0, 2]$ is just $\pi$:

$$
\begin{equation}
  \pi = \int_0^2 \sqrt{4 - x^2} \, dx.
\end{equation}
$$

Check the convergence of the Monte Carlo integration with $N$. (I suggest using $N = 100 \times 2^i$ for $i = 0, \dots, 19$; you should find the error scales roughly as $N^{-1/2}$)

```python

```

## Mean Value Method

Monte Carlo integration is pretty inaccurate, as seen above: it converges slowly, and has poor accuracy at all $N$. An alternative is the *mean value* method, where we note that *by definition* the average value of $f$ over the interval $[a, b]$ is precisely the integral multiplied by the width of the interval.

Hence we can just choose our $N$ random points in $x$ as above, but now just compute

$$
\begin{equation}
  I \simeq \frac{b-a}{N} \sum_{i=1}^N f(x_i).
\end{equation}
$$

```python
def mv_integrate(f, domain_x, N = 10000):
    """
    Mean value Monte Carlo integration: to be completed
    """
    import numpy.random
    return I
```

Let's look at the accuracy of this method again applied to computing $\pi$.

```python

```

The convergence *rate* is the same (only roughly, typically), but the Mean Value method is *expected* to be better in terms of its absolute error.

### Dimensionality

Compared to standard integration methods (Gauss quadrature, Simpson's rule, etc) the convergence rate for Monte Carlo methods is very slow. However, there is one crucial advantage: as you change dimension, the amount of calculation required is *unchanged*, whereas for standard methods it grows geometrically with the dimension.

Try to compute the volume of an $n$-dimensional unit *hypersphere*, which is the object in $\mathbb{R}^n$ such that

$$
\begin{equation}
  \sum_{i=1}^n x_i^2 \le 1.
\end{equation}
$$

The volume of the hypersphere [can be found in closed form](http://en.wikipedia.org/wiki/Volume_of_an_n-ball#The_volume), but can rapidly be computed using the Monte Carlo method above, by counting the $k$ points that randomly fall within the hypersphere and using the standard formula $I \simeq V k / N$.

```python
def mc_integrate_multid(f, domain, N = 10000):
    """
    Monte Carlo integration in arbitrary dimensions (read from the size of the domain): to be completed
    """

    return I
```

```python
from scipy import special
```

```python
def volume_hypersphere(ndim=3):
    return numpy.pi**(float(ndim)/2.0) / special.gamma(float(ndim)/2.0 + 1.0)
```

Now let us repeat this across multiple dimensions.

The errors clearly vary over a range, but the convergence remains roughly as $N^{-1/2}$ independent of the dimension; using other techniques such as Gauss quadrature would see the points required scaling geometrically with the dimension.

## Importance sampling

Consider the integral (which arises, for example, in the theory of Fermi gases)

$$
\begin{equation}
  I = \int_0^1 \frac{x^{-1/2}}{e^x + 1} \, dx.
\end{equation}
$$

This has a finite value, but the integrand diverges as $x \to 0$. This *may* cause a problem for Monte Carlo integration when a single value may give a spuriously large contribution to the sum.

We can get around this by changing the points at which the integrand is sampled. Choose a *weighting* function $w(x)$. Then a weighted average of any function $g(x)$ can be

$$
\begin{equation}
  <g>_w = \frac{\int_a^b w(x) g(x) \, dx}{\int_a^b w(x) \, dx}.
\end{equation}
$$

As our integral is

$$
\begin{equation}
  I = \int_a^b f(x) \, dx
\end{equation}
$$

we can, by setting $g(x) = f(x) / w(x)$ get

$$
\begin{equation}
  I = \int_a^b f(x) \, dx = \left< \frac{f(x)}{w(x)} \right>_w \int_a^b w(x) \, dx.
\end{equation}
$$

This gives

$$
\begin{equation}
  I \simeq \frac{1}{N} \sum_{i=1}^N \frac{f(x_i)}{w(x_i)} \int_a^b w(x) \, dx,
\end{equation}
$$

where the points $x_i$ are now chosen from a *non-uniform* probability distribution with pdf

$$
\begin{equation}
  p(x) = \frac{w(x)}{\int_a^b w(x) \, dx}.
\end{equation}
$$

This is a generalization of the mean value method - we clearly recover the mean value method when the weighting function $w(x) \equiv 1$. A careful choice of the weighting function can mitigate problematic regions of the integrand; e.g., in the example above we could choose $w(x) = x^{-1/2}$, giving $p(x) = x^{-1/2}/2$. In general, the hard part of the algorithm is going to be generating the samples from this non-uniform distribution. Here we have the advantage that $p$ is given by the `numpy.random.power` distribution.

So, let's try to solve the integral above. We need $\int_0^1 w(x) = 2$. The expected solution is around 0.84.So, let's try to solve the integral above. The expected solution is around 0.84.

```python

```

In the general case, how do we generate the samples from the non-uniform probability distribution $p$?

What really matters here is not the function $p$ from which we draw the random numbers `x`. What really matters is that the random numbers appear to follow the behaviour, the distribution $p$, that we want. This may seem like stating the same thing, but it's not. We can use a technique called *rejection sampling* to construct a set of numbers that follows a certain (cumulative) distribution without having to construct the pdf that it actually follows at all.

To do this, we need to know the distribution we want (here $p(x) = 1/(2 \sqrt{x})$) and another distribution $q(x)$ that we can easily compute with a constant $K$ such that $p(x) \le K q(x)$. What we're doing here is just for illustration, as the *power* distribution $p(x) = a x^{a-1}$ is provided by `numpy.random.power` and perfectly matches the distribution we want for $a=1/2$. Here we're going to need some distribution that diverges faster than $p$ for small $x$, so we can choose the power distribution with $a=1/3$, provided, for example, $K = 1.6$:

```python
x = numpy.linspace(0.01,1,2000)
p = 1/(2*numpy.sqrt(x))
q = 1/(3*x**(2/3))
K = 1.6
pyplot.semilogy(x, p, lw=2, label=r"$p(x)$")
pyplot.semilogy(x, K * q, lw=2, label=r"$K q(x)$")
pyplot.xlabel(r"$x$")
pyplot.legend()
pyplot.show()
```

Rejection sampling works by drawing random samples from the easy-to-compute distribution $q$. We then keep the samples drawn from $q$ with a certain probability: if $p(x) / (K q(x)) < U$, where $U$ is drawn from the uniform distribution, then we keep the sample. As we're calculating $p$, not drawing samples from it, this shouldn't be a problem.

Let's check this working:

```python

```

So now we can write an importance sampling algorithm without having to integrate the weighting function first, by using rejection sampling to find a set of samples from the resulting pdf without actually having to sample it.

We won't do that here, but it's a key conceptual step for MCMC algorithms such as Metropolis Hastings.

FEEG6016 Simulation and Modelling/01-Monte-Carlo-Lab-1.ipynb


Tài liệu này mang giấy phép Creative Commons Attribution (CC BY).
(c) Nguyễn Ngọc Sáng, Zhukovsky 12/2018.

[@SangVn](https://github.com/SangVn) [@VnCFD](https://vncfdgroup.wordpress.com/)

*Thực hành CFD với Python!*

# Bài 6. Phương trình Burgers (dòng chảy có nhớt)

Phương trình Burgers dạng đầy đủ:
$$\frac {\partial u}{\partial t} + u \frac{\partial u}{\partial x} = \nu \frac{\partial^2 u}{\partial x^2}  \qquad (1)$$
Ở đây hạng tử nhớt có vai trò làm giảm biên độ sóng và ngăn chặn sự xuất hiện khả năng có nhiều nghiệm (hình 1).

Để tìm nghiệm gần đúng của phương trình này ta sử dụng sơ đồ sai phân phải cho thời gian và sai phân trung tâm cho không gian:
$$\frac{u_i^{n+1}-u_i^n}{\Delta t} + u_i^n \frac{u_{i+1}^n - u_{i-1}^n}{2\Delta x} = \nu \frac{u_{i+1}^n - 2u_i^n + u_{i-1}^n}{\Delta x^2}$$

Ta có công thức xác định nghiệm:

$$u_i^{n+1} = u_i^n - u_i^n \frac{\Delta t}{2\Delta x} (u_{i+1}^n - u_{i-1}^n) + \nu \frac{\Delta t}{\Delta x^2}(u_{i+1}^n - 2u_i^n + u_{i-1}^n)$$

**Bài toán**

Ta sẽ xem quá trình lan truyền sóng xung kích với các điều kiện:
$$u(x, 0) = 1, -1 \leq x \leq 0; \quad u(x, 0) = 0, 0 < x \leq 1$$
$$u(-1, t) = 1, \quad u(1, t) = 0$$
Nghiệm chính xác của bài toán này có dạng:
$$u(x, t) = f(\xi) = 1/(1+e^{\xi/2\nu}), \xi = x-t/2$$

**Coding**

Ở bài này ta sẽ làm quen với thư viện `sympy` - hỗ trợ tính toán các biểu thức toán học biểu tượng (symbolic). Cái này giống với chức năng tương tự trong Matlab, Mathematica.
Để biến biểu thức toán học thành một hàm tính toán ta sẽ sử dụng module `lambdify`.

```python
import numpy as np # gọi thư viện numpy
import sympy as sp # gọi thư viện sympy
import matplotlib.pyplot as plt #gọi thư viện pyplot
from sympy.utilities.lambdify import lambdify #gọi module lambdify
from sympy import init_printing #

init_printing(use_latex=True) #in biểu thức dạng latex

xi, nu = sp.symbols('xi nu') #khai báo biểu tượng
f = 1/(1 + sp.exp(xi/(2*nu)))    #khai báo biểu thức f - nghiệm chính xác
fxi = lambdify((xi, nu), f)  #chuyển biểu thức f thành hàm fxi với biến là xi và nu

fdiff = f.diff(xi)  #dễ dàng tính được đạo hàm fxi theo xi - test
f, fdiff, fxi(0, 1) #kiểm tra
```

```python
#thông số tính toán
nx = 201
dx = 2./(nx-1)
x = np.linspace(-1, 1, nx)

#bước thời gian
r = 0.05
dt = r*dx

#điều kiện ban đầu
u0 = np.zeros(nx)
u0[:int(1./dx)] = 1

#nghiệm chính xác, nu - độ nhớt
def u_exact(nu, t):
    xi = x - t/2
    return np.array([fxi(xi_i, nu) for xi_i in xi])

#tìm nghiệm với biến đầu vào nu, và số bước thời gian nt
def solver(nu, nt):
    u = u0.copy()  #mảng u chứa nghiệm
    un = u0.copy() #mảng un chứa nghiệm tạm thời
    for n in range(nt):
        un = u.copy() #copy giá trị u - lưu giá trị ở bước hiện tại
        #tìm nghiệm ở bước thời gian tiếp theo
        u[1:-1] = un[1:-1] - dt/(2*dx)*un[1:-1]*(un[2:] - un[0:-2]) +\
                  nu*dt/dx**2*(un[2:] - 2*un[1:-1] + un[0:-2])
        u[0] = 1
        u[-1] = 0
    return u

#xét trường hợp nu = 0.1
nu = .1
u_1 = solver(nu, 180)
u_2 = solver(nu, 360)
u_3 = solver(nu, 720)
u_ext = u_exact(nu, 720*dt)

plt.plot(x, u0, label='u_0')
plt.plot(x, u_1, label='u_1')
plt.plot(x, u_2, label='u_2')
plt.plot(x, u_3, label='u_3')
plt.plot(x, u_ext, label='u_exact')
plt.legend()
plt.show()
```

Ta thấy, theo thời gian nghiệm càng mượt mà hơn do tính nhớt của phương trình, với độ nhớt $\nu$ lớn thành phần nhớt đóng vai trò chủ đạo. Sơ đồ cho nghiệm kém chính xác.

```python
#trường hợp nu = 0.02
nu = .02
u_1 = solver(nu, 180)
u_2 = solver(nu, 360)
u_3 = solver(nu, 720)
u_ext = u_exact(nu, 720*dt)

plt.plot(x, u0, label='u_0')
plt.plot(x, u_1, label='u_1')
plt.plot(x, u_2, label='u_2')
plt.plot(x, u_3, label='u_3')
plt.plot(x, u_ext, label='u_exact')
plt.legend()
plt.show()
```

Rõ ràng khi $\nu$ nhỏ, phương trình Burgers có nhớt dần chuyển thành dạng không nhớt (bài 5), thành phần 'chuyển dịch' đóng vai trò chủ đạo. Sơ đồ cho nghiệm khá chính xác.

## [Bài 7. Phương trình Burgers hai chiều](Bai_7.ipynb)

```python

```


#1) Determinar as funções de interpolação dos elementos

## a)

funções bilineares dos elementos lineares

\begin{equation}
\begin{split}
&N_1(\xi ,\eta) = \frac{1}{4}(1 + \xi )(1 + \eta)\\
&N_2(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 + \eta)\\
&N_3(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 - \eta)\\
&N_4(\xi ,\eta) = \frac{1}{4}(1 + \xi )(1 - \eta)\\
\end{split}
\end{equation}

Quadratica no ponto do meio na direção $\eta$

\begin{equation}
\begin{split}
l^2_3(\eta) = (1 + \eta) (1 -\eta)
\end{split}
\end{equation}

Solução do problema

\begin{equation}
\begin{split}
&N_1(\xi ,\eta) = N^{b}_1(\xi ,\eta) - \frac{1}{2} N_5(\xi ,\eta)\\
&N_2(\xi ,\eta) = N^{b}_2\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 + \eta)\\
&N_3(\xi ,\eta) = N^{b}_3(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 - \eta)\\
&N_4(\xi ,\eta) = N^{b}_4(\xi ,\eta) - \frac{1}{2} N_5(\xi ,\eta)\\
&N_5(\xi ,\eta) = \frac{1}{2}(1 + \xi) l^2_3(\eta) = \frac{1}{2}(1 + \xi)(1 + \eta)(1 - \eta)
\end{split}
\end{equation}

```python
no1 = ( 1, 1)
no2 = (-1, 1)
no3 = (-1,-1)
no4 = ( 1,-1)
no5 = ( 1, 0)

nos = (no1, no2, no3, no4, no5)
```

```python
def N1b(no):
  e, n = no
  return (1/4)*(1+e)*(1+n)

def N2b(no):
  e, n = no
  return (1/4)*(1-e)*(1+n)

def N3b(no):
  e, n = no
  return (1/4)*(1-e)*(1-n)

def N4b(no):
  e, n = no
  return (1/4)*(1+e)*(1-n)
```

```python
def N5(no):
  e, n = no
  return (1/2)*(1+e)*(1-n)*(1+n)

def N1(no):
  e, n = no
  return N1b(no) - (1/2)*N5(no)

def N2(no):
  return N2b(no)

def N3(no):
  return N3b(no)

def N4(no):
  return N4b(no) - (1/2)*N5(no)
```

```python
# Checando o No1
print("N1")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N1 = {N1(no)}")

# Checando o No2
print("\nN2")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N2 = {N2(no)}")

# Checando o No3
print("\nN3")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N3 = {N3(no)}")

# Checando o No4
print("\nN4")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N4 = {N4(no)}")

# Checando o No5
print("\nN5")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N5 = {N5(no)}")
```

    N1
    no1 -> N1 = 1.0
    no2 -> N1 = 0.0
    no3 -> N1 = 0.0
    no4 -> N1 = 0.0
    no5 -> N1 = 0.0

    N2
    no1 -> N2 = 0.0
    no2 -> N2 = 1.0
    no3 -> N2 = 0.0
    no4 -> N2 = 0.0
    no5 -> N2 = 0.0

    N3
    no1 -> N3 = 0.0
    no2 -> N3 = 0.0
    no3 -> N3 = 1.0
    no4 -> N3 = 0.0
    no5 -> N3 = 0.0

    N4
    no1 -> N4 = 0.0
    no2 -> N4 = 0.0
    no3 -> N4 = 0.0
    no4 -> N4 = 1.0
    no5 -> N4 = 0.0

    N5
    no1 -> N5 = 0.0
    no2 -> N5 = 0.0
    no3 -> N5 = 0.0
    no4 -> N5 = 0.0
    no5 -> N5 = 1.0

## b)

funções bilineares dos elementos lineares

\begin{equation}
\begin{split}
&N_1(\xi ,\eta) = \frac{1}{4}(1 + \xi )(1 + \eta)\\
&N_2(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 + \eta)\\
&N_3(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 - \eta)\\
&N_4(\xi ,\eta) = \frac{1}{4}(1 + \xi )(1 - \eta)\\
\end{split}
\end{equation}

Quadratica no ponto 7 $\eta$

\begin{equation}
\begin{split}
l^2_3(\eta) = (1 + \eta) (1 -\eta)
\end{split}
\end{equation}

Cubico nos pontos 5, 6 na direção $\xi$

\begin{equation}
\begin{split}
l^3_3(\xi) = \frac{27}{16}(1 + \xi) (\xi - 1/3) (\xi - 1)\\
l^3_4(\xi) = \frac{27}{16}(1 - \xi) (\xi + 1/3) (\xi + 1)
\end{split}
\end{equation}

Solução do problema

\begin{equation}
\begin{split}
&N_1(\xi ,\eta) = N^{b}_1(\xi ,\eta) - \frac{1}{2} N_7(\xi ,\eta) -  \frac{1}{3} N_6(\xi ,\eta) - \frac{2}{3} N_5(\xi ,\eta)\\
&N_2(\xi ,\eta) = N^{b}_2\xi ,\eta) -  \frac{2}{3} N_6(\xi ,\eta) - \frac{1}{3} N_5(\xi ,\eta)\\
&N_3(\xi ,\eta) = N^{b}_3(\xi ,\eta) = \frac{1}{4}(1 - \xi )(1 - \eta)\\
&N_4(\xi ,\eta) = N^{b}_4(\xi ,\eta) - \frac{1}{2} N_7(\xi ,\eta)\\
&N_5(\xi ,\eta) = \frac{1}{2}(1 + \eta) l^3_4(\xi) = \frac{27}{32}(1 + \eta)(1 - \xi) (\xi + 1/3) (\xi + 1) \\
&N_6(\xi ,\eta) = \frac{1}{2}(1 + \eta) l^3_3(\xi) = \frac{27}{32}(1 + \eta)(1 + \xi) (\xi - 1/3) (\xi - 1)\\
&N_7(\xi ,\eta) = \frac{1}{2}(1 + \xi) l^2_3(\eta) = \frac{1}{2}(1 + \xi)(1 + \eta)(1 - \eta)
\end{split}
\end{equation}

```python
no1 = (   1, 1)
no2 = (  -1, 1)
no3 = (  -1,-1)
no4 = (   1,-1)
no5 = ( 1/3, 1)
no6 = (-1/3, 1)
no7 = (   1, 0)

nos = (no1, no2, no3, no4, no5, no6, no7)
```

```python
def N5(no):
  e, n = no
  return (27/32)*(1+n)*(1-e)*(e+1/3)*(e+1)

def N6(no):
  e, n = no
  return (27/32)*(1+n)*(1+e)*(e-1/3)*(e-1)

def N7(no):
  e, n = no
  return (1/2)*(1+e)*(1+n)*(1-n)

def N1(no):
  return N1b(no) - 1/2 * N7(no) - 1/3 * N6(no) - 2/3 * N5(no)

def N2(no):
  return N2b(no) - 2/3 * N6(no) - 1/3 * N5(no)

def N3(no):
  return N3b(no)

def N4(no):
  return N4b(no) - 1/2 * N7(no)
```

```python
# Checando o No1
print("N1")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N1 = {N1(no)}")

# Checando o No2
print("\nN2")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N2 = {N2(no)}")

# Checando o No3
print("\nN3")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N3 = {N3(no)}")

# Checando o No4
print("\nN4")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N4 = {N4(no)}")

# Checando o No5
print("\nN5")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N5 = {N5(no)}")

# Checando o No5
print("\nN6")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N6 = {N6(no)}")

# Checando o No5
print("\nN7")
for i, no in enumerate(nos):
  print(f"no{i+1} -> N7 = {N7(no)}")
```

    N1
    no1 -> N1 = 1.0
    no2 -> N1 = 0.0
    no3 -> N1 = 0.0
    no4 -> N1 = 0.0
    no5 -> N1 = 0.0
    no6 -> N1 = 5.551115123125783e-17
    no7 -> N1 = 0.0

    N2
    no1 -> N2 = 0.0
    no2 -> N2 = 1.0
    no3 -> N2 = 0.0
    no4 -> N2 = 0.0
    no5 -> N2 = 5.551115123125783e-17
    no6 -> N2 = 0.0
    no7 -> N2 = 0.0

    N3
    no1 -> N3 = 0.0
    no2 -> N3 = 0.0
    no3 -> N3 = 1.0
    no4 -> N3 = 0.0
    no5 -> N3 = 0.0
    no6 -> N3 = 0.0
    no7 -> N3 = 0.0

    N4
    no1 -> N4 = 0.0
    no2 -> N4 = 0.0
    no3 -> N4 = 0.0
    no4 -> N4 = 1.0
    no5 -> N4 = 0.0
    no6 -> N4 = 0.0
    no7 -> N4 = 0.0

    N5
    no1 -> N5 = 0.0
    no2 -> N5 = -0.0
    no3 -> N5 = -0.0
    no4 -> N5 = 0.0
    no5 -> N5 = 1.0
    no6 -> N5 = 0.0
    no7 -> N5 = 0.0

    N6
    no1 -> N6 = 0.0
    no2 -> N6 = 0.0
    no3 -> N6 = 0.0
    no4 -> N6 = 0.0
    no5 -> N6 = -0.0
    no6 -> N6 = 1.0
    no7 -> N6 = 0.0

    N7
    no1 -> N7 = 0.0
    no2 -> N7 = 0.0
    no3 -> N7 = 0.0
    no4 -> N7 = 0.0
    no5 -> N7 = 0.0
    no6 -> N7 = 0.0
    no7 -> N7 = 1.0


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

# Homework 8

**Instructions:** Complete the notebook below. Download the completed notebook in HTML format. Upload assignment using Canvas.

**Due:** Mar. 4 at **2pm.**

## Exercise: Changing $\beta$ in Prescott's Real Business Cycle Model

Recall that the equilibrium conditions for Prescott's RBC model are:

\begin{align}
\frac{1}{C_t} & = \beta E_t \left[\frac{\alpha A_{t+1}K_{t+1}^{\alpha-1}L_{t+1}^{1-\alpha} +1-\delta }{C_{t+1}}\right]\\
\frac{\varphi}{1-L_t} & = \frac{(1-\alpha)A_tK_t^{\alpha}L_t^{-\alpha}}{C_t} \\
Y_t & = A_t K_t^{\alpha}L_t^{1-\alpha}\\
K_{t+1} & = I_t + (1-\delta) K_t\\
Y_t & = C_t + I_t\\
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1}
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$.

The objective is use `linearsolve` to simulate impulse responses to a TFP shock for $\beta = 0.96,0.97,0.98,0.99$. Other parameter values are given in the table below:

| $$\sigma$$ | $$\rho$$  | $$\varphi$$ | $$\alpha$$ | $$\delta $$ |
|------------|-----------|-------------|------------|-------------|
| 0.006      | 0.75      | 1.7317      | 0.35       |  0.025      |

## Model Preparation

As usual, we recast the model in the form required for `linearsolve`. Write the model with all variables moved to the left-hand side of the equations and dropping the expecations operator $E_t$ and the exogenous shock $\epsilon_{t+1}$:

\begin{align}
0 & = \beta\left[\frac{\alpha A_{t+1}K_{t+1}^{\alpha-1}L_{t+1}^{1-\alpha} +1-\delta }{C_{t+1}}\right] - \frac{1}{C_t}\\
0 & = \frac{(1-\alpha)A_tK_t^{\alpha}L_t^{-\alpha}}{C_t} - \frac{\varphi}{1-L_t}\\
0 & = A_t K_t^{\alpha}L_t^{1-\alpha} - Y_t\\
0 & = I_t + (1-\delta) K_t - K_{t+1}\\
0 & = C_t + I_t - Y_t\\
0 & = \rho \log A_t - \log A_{t+1}
\end{align}

Remember, capital and TFP are called *state variables* because they're $t+1$ values are predetermined. Output, consumption, and investment are called a *costate* or *control* variables. Note that the model as 5 equations in 5 endogenous variables.

## Initialization, Approximation, and Solution

The next several cells initialize the model in `linearsolve` and then approximate and solve it.

```python
# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series.
parameters = pd.Series(dtype=float)
parameters['rho'] = 0.75
parameters['phi'] = 1.7317
parameters['alpha'] = 0.35
parameters['delta'] = 0.025

# Print the model's parameters
print(parameters)
```

    rho      0.7500
    phi      1.7317
    alpha    0.3500
    delta    0.0250
    dtype: float64

```python
# Create variable called 'varNames' that stores the variable names in a list with state variables ordered first.
var_names = ['a','k','y','c','i','l']

# Create variable called 'shockNames' that stores an exogenous shock name for each state variable.
shock_names = ['e_a','e_k']
```

```python
# Define a function that evaluates the equilibrium conditions of the model solved for zero
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Parameters
    p = parameters

    # Current variables
    cur = variables_current

    # Forward variables
    fwd = variables_forward

    # Euler equation
    mpk = p.alpha*fwd.a*fwd.k**(p.alpha-1)*fwd.l**(1-p.alpha)
    euler_equation = p.beta*(mpk+1-p.delta)/fwd.c - 1/cur.c

    # Labor-labor choise
    mpl = (1-p.alpha)*cur.a*cur.k**p.alpha*cur.l**(-p.alpha)
    labor_leisure = mpl/cur.c - p.phi/(1-cur.l)

    # Production function
    production_function = cur.a*cur.k**p.alpha*cur.l**(1-p.alpha) - cur.y

    # Capital evolution
    capital_evolution = cur.i + (1 - p.delta)*cur.k - fwd.k

    # Market clearing
    market_clearing = cur.c+cur.i - cur.y

    # Exogenous tfp
    tfp_process = p.rho*np.log(cur.a) - np.log(fwd.a)

    # Stack equilibrium conditions into a numpy array
    return np.array([
        euler_equation,
        labor_leisure,
        production_function,
        capital_evolution,
        market_clearing,
        tfp_process
        ])
```

Next, initialize the model using `ls.model` which takes the following required arguments:

* `equations`
* `n_states`
* `var_names`
* `shock_names`
* `parameters`

```python
# Initialize the model into a variable named 'rbc_model'
rbc_model = ls.model(equations = equilibrium_equations,
                 n_states=2,
                 var_names=var_names,
                 shock_names=shock_names,
                 parameters=parameters)
```

## SImulation and Plotting

Create a $2\times 2$ grid of plots containing the impulse responses of TFP, output, labor, and consumption to a one percent shock to TFP for each of the values for $\beta$: 0.96,0.97,0.98,0.99. Your figure should have a legend that clearly indicates which curves were produced from

Here are the steps that that you should take:

1. Initilize figure and axes for plotting.
2. Iterate over each desired value for $\beta$.
    1. Set `rbc_model.parameters['beta']` equal to current value of $\beta$.
    2. Use `rbc_model.compute_ss()` to compute the steady state with `guess` equal to `[1,4,1,1,1,0.5]`.
    3. Use `rbc_model.approximate_and_solve()` to approximate and solve the model with the current value of $\beta$.
    4. Use `rbc_model.impulse()` to compute the **31** period impulse response to a 0.01 unit shock to TFP in period 5.
    5. Add the computed impulse responses to the axes.

Be sure to add a legend somewhere in your figure so that it's clear which impulse response lines were determined by which value of $\beta$.

```python
# Create a 12x8 figure. CELL NOT PROVIDED
fig = plt.figure(figsize=(12,8))

# Create four axis variables: 'ax1', 'ax2', 'ax3', 'ax4'
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,3)
ax4 = fig.add_subplot(2,2,4)

# Create an axis equal to the size of the figure
ax0 = fig.add_subplot(1,1,1)
# Turn off the axis so that the underlying axes are visible
ax0.set_frame_on(False)
# Hide the x-axis
ax0.get_xaxis().set_visible(False)
# Hide the y-axis
ax0.get_yaxis().set_visible(False)

# Create variable called 'beta_values' that stores the desired values of rho
beta_values = [0.96,0.97,0.98,0.99]

# Iterate over the elements of beta_values
for beta in beta_values:

    # Update the value of rho in rbc_model.parameters
    rbc_model.parameters['beta'] = beta

    # Compute the steady state with initial guess equal to [1,4,1,1,1,0.5]
    rbc_model.compute_ss([1,4,1,1,1,0.5])

    # Approximate the model and solve
    rbc_model.approximate_and_solve()

    # Compute the impulse responses to a 0.01 unit shock to TFP
    rbc_model.impulse(T=31,t0=5,shocks=[0.01,0])

    # Add plots of TFP, output, labor, and consumption to ax1, ax2, ax3, and ax4
    ax1.plot(rbc_model.irs['e_a']['a']*100,lw=3,alpha=0.75)
    ax2.plot(rbc_model.irs['e_a']['y']*100,lw=3,alpha=0.75)
    ax3.plot(rbc_model.irs['e_a']['l']*100,lw=3,alpha=0.75)
    ax4.plot(rbc_model.irs['e_a']['c']*100,lw=3,alpha=0.75)

    # Plot the point 0,0 on ax0 with the same line properties used for the other plotted lines and provide a label
    ax0.plot(0,0,lw=3,alpha=0.75,label='$\\beta='+str(beta)+'$')

# Set axis titles
ax1.set_title('TFP')
ax2.set_title('Output')
ax3.set_title('Labor')
ax4.set_title('Consumption')

# Add grids to the axes
ax1.grid()
ax2.grid()
ax3.grid()
ax4.grid()

# Set ax1 y-axis limits to [0,2]
ax1.set_ylim([0,2])
# Set ax2 y-axis limits to [0,2]
ax2.set_ylim([0,2])
# Set ax3 y-axis limits to [-0.5,1.25]
ax3.set_ylim([-0.5,1.5])
# Set ax4 y-axis limits to [-0.5,1.5]
ax4.set_ylim([-0.5,1.5])

# Add legend below the figure.
legend = ax0.legend(loc='upper center',bbox_to_anchor=(0.5,-0.075), ncol=4,fontsize=15)
```

**Questions**

1. Describe in your own words how increasing $\beta$ from 0.96 to 0.99 affects the impulse response of consumption to a TFP shock.
2. Explain in your own words the intuition for *why* your observation in the previous question makes sense.
3. Describe in your own words how increasing $\beta$ from 0.96 to 0.99 affects the impulse response of labor to a TFP shock.
4. Explain in your own words the intuition for *why* your observation in the previous question makes sense.

**Answers**

1. Increasing beta causes the effect of the shock at impact (period 5) to be smaller and for the peak level of consumption reached to be smaller. But the effect of the shock on consumption dissipates less quickly.<!-- answer -->
2. With a higher level of $\beta$, the household is being assumed to place a higher value on future expected utility flows and so wants to save more, leading to less consumption when the shock hits, but more consumption in the distant future. <!-- answer -->
1. Increasing beta causes the effect of the shock at impact (period 5) to be larger and for the subsequent drop in labor to not be as pronounced. <!-- answer -->
2. With a higher level of $\beta$, the household is being assumed to place a higher value on future expected utility flows and is therefore more willing to work harder when the shock hits to generate income that can be saved for the future in the form of capital. <!-- answer -->

## Exercise: Monetary policy Regimes

In August 6, 1979, Paul Volcker began his first of two terms as chair of the Board of Governors of the Federal Reserve System. In the two decades prior to Volcker's appointment, inflation in the US had been acccelerating rapidly, as the graph produced by the code in the next cell shows:

```python
# Import GDP deflator data from FRED. CELL 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
deflator = deflator['VALUE']

# Compute the inflation rate
inflation = (deflator - deflator.shift(4))/deflator.shift(4)*100

# Plot
inflation.plot(title='US GDP Deflator Inflation',ylabel='Percent',grid=True)
```

The decline and stabilization of inflation in the US starting in the early 1980s is widely attributed to Volcker's leadership. As chair, he pushed the FOMC to aggressively pursue tight monetary policy that led to a sharp contraction in the rate of money growth and an increase in the federal fund rate.

In light of this story, many economists argue that Volcker was the start of a new monetary policy *regime*. Before Volcker, the Fed pursued a looser monetary policy that allowed inflation to accelerate and starting with Volcker, the Fed has pursued a tighter monetary policy that is more aggressive to managing inflation.

Here you will test this proposition by estimating a monetary policy rule for the US on data before Volcker's arrival at the Fed and after. Basically, you will replicate the estimation at the end of class 16 for pre- and post-Volcker data.

### Preliminaries

The next block of code imports federal funds rate, GDP deflator inflation, and output gap data from FRED and returs the data as a DataFrame called `df`.

```python
# Initial an empty DataFrame that will store data. CELL PROVIDED
df = pd.DataFrame()

# Import federal funds rate data from FRED. Use arguments: sep='\s+',skiprows=62,index_col=0,parse_dates=True
fed_funds = pd.read_csv('https://fred.stlouisfed.org/data/FEDFUNDS.txt',sep='\s+',skiprows=62,index_col=0,parse_dates=True)

# Set fed_funds equal to 'VALUE' column of fed_funds
fed_funds = fed_funds['VALUE']

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

# Import GDP deflator data from FRED
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
deflator = deflator['VALUE']

# Compute the inflation rate
inflation = (deflator - deflator.shift(4))/deflator.shift(4)*100

# Import real GDP data from FRED
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
gdp_actual = gdp_actual['VALUE']

# Import potential real GDP data from FRED
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
gdp_potential = gdp_potential['VALUE']

# Create variable 'df' that is a DataFrame storing fed funds, inflation, actual and potential real GDP
df = pd.DataFrame({
    'fed_funds':fed_funds,
    'inflation':inflation,
    'gdp_actual':gdp_actual,
    'gdp_potential':gdp_potential
})

# Drop missing values from 'df'
df = df.dropna()
```

### The 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
# Construct an output gap column
df['output_gap'] = (df['gdp_actual'] - df['gdp_potential'])/df['gdp_potential']*100
```

### OLS Regressions

The rule to be estimated is the same considered in Class 16:

\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
# Import statsmodels.api as sm
import statsmodels.api as sm
```

For a Pandas DataFrame or Series with a DateTime index, you can select a subinterval of the dates using data strings in `.ilo[]`. For example,

    `df.loc[:'1960']`

will return all data through the end of 1960. And:

    df.loc['08-2000':]

will return all data from and after August 2000.

#### Pre-Volcker Era

Estimate the monetary policy rule for dates through September 1979.

```python
# Create variable 'X' with columns inflation, output and a constant
X = sm.add_constant(df[['inflation','output_gap']].loc[:'09-1979'])

# Create variable 'Y' equal to the federal funds rate
Y = df['fed_funds'].loc[:'09-1979']
```

```python
# Initialize OLS model
model = sm.OLS(Y,X)

# Fit OLS model
results = model.fit()

# Print regression results
print(results.summary2())
```

                     Results: Ordinary least squares
    =================================================================
    Model:              OLS              Adj. R-squared:     0.716
    Dependent Variable: fed_funds        AIC:                354.9140
    Date:               2021-02-25 16:56 BIC:                362.7594
    No. Observations:   101              Log-Likelihood:     -174.46
    Df Model:           2                F-statistic:        127.2
    Df Residuals:       98               Prob (F-statistic): 5.83e-28
    R-squared:          0.722            Scale:              1.9096
    -------------------------------------------------------------------
                 Coef.    Std.Err.      t      P>|t|    [0.025   0.975]
    -------------------------------------------------------------------
    const        1.2483     0.2644    4.7211   0.0000   0.7236   1.7730
    inflation    0.8751     0.0555   15.7668   0.0000   0.7649   0.9852
    output_gap   0.3102     0.0589    5.2618   0.0000   0.1932   0.4271
    -----------------------------------------------------------------
    Omnibus:              0.088        Durbin-Watson:           0.334
    Prob(Omnibus):        0.957        Jarque-Bera (JB):        0.258
    Skew:                 0.016        Prob(JB):                0.879
    Kurtosis:             2.754        Condition No.:           9
    =================================================================

#### Post-Volcker Era

Estimate the monetary policy rule for dates after October 1979.

```python
# Create variable 'X' with columns inflation, output and a constant
X = sm.add_constant(df[['inflation','output_gap']].loc['10-1979':])

# Create variable 'Y' equal to the federal funds rate
Y = df['fed_funds'].loc['10-1979':]
```

```python
# Initialize OLS model
model = sm.OLS(Y,X)

# Fit OLS model
results = model.fit()

# Print regression results
print(results.summary2())
```

                     Results: Ordinary least squares
    =================================================================
    Model:              OLS              Adj. R-squared:     0.715
    Dependent Variable: fed_funds        AIC:                728.4910
    Date:               2021-02-25 16:56 BIC:                737.8089
    No. Observations:   165              Log-Likelihood:     -361.25
    Df Model:           2                F-statistic:        206.7
    Df Residuals:       162              Prob (F-statistic): 2.58e-45
    R-squared:          0.718            Scale:              4.7550
    ------------------------------------------------------------------
                    Coef.   Std.Err.     t     P>|t|    [0.025  0.975]
    ------------------------------------------------------------------
    const           0.0274    0.3148   0.0870  0.9308  -0.5942  0.6489
    inflation       1.8517    0.0912  20.3130  0.0000   1.6717  2.0318
    output_gap      0.2719    0.0828   3.2844  0.0013   0.1084  0.4353
    -----------------------------------------------------------------
    Omnibus:               16.806       Durbin-Watson:          0.158
    Prob(Omnibus):         0.000        Jarque-Bera (JB):       5.790
    Skew:                  0.110        Prob(JB):               0.055
    Kurtosis:              2.109        Condition No.:          7
    =================================================================

**Questions**

1. Compare the results from the two regressions. For which monetary policy era is the estimated coefficient on inflation greater?
2. How do your results help to explain the high and variable inflation of the 1960s and 1970s and the lower and more stable inflation of the 1980s and after?

**Answers**

1. The estimated coefficient on inflation is higher in the regression on the post-Volcker data.<!-- answer -->
2. Before Volcker, the Federal Reserve did not respond aggressively enough to increases in the inflation rate and so the inflation rate became out of control. <!-- answer -->