# Introduction to Deep Learning with PyTorch

In this notebook, you'll get introduced to [PyTorch](http://pytorch.org/), a framework for building and training neural networks. PyTorch in a lot of ways behaves like the arrays you love from Numpy. These Numpy arrays, after all, are just tensors. PyTorch takes these tensors and makes it simple to move them to GPUs for the faster processing needed when training neural networks. It also provides a module that automatically calculates gradients (for backpropagation!) and another module specifically for building neural networks. All together, PyTorch ends up being more coherent with Python and the Numpy/Scipy stack compared to TensorFlow and other frameworks.

## Neural Networks

Deep Learning is based on artificial neural networks which have been around in some form since the late 1950s. The networks are built from individual parts approximating neurons, typically called units or simply "neurons." Each unit has some number of weighted inputs. These weighted inputs are summed together (a linear combination) then passed through an activation function to get the unit's output.

Mathematically this looks like:

$$
\begin{align}
y &= f(w_1 x_1 + w_2 x_2 + b) \\
y &= f\left(\sum_i w_i x_i +b \right)
\end{align}
$$

With vectors this is the dot/inner product of two vectors:

$$
h = \begin{bmatrix}
x_1 \, x_2 \cdots  x_n
\end{bmatrix}
\cdot
\begin{bmatrix}
           w_1 \\
           w_2 \\
           \vdots \\
           w_n
\end{bmatrix}
$$

## Tensors

It turns out neural network computations are just a bunch of linear algebra operations on *tensors*, a generalization of matrices. A vector is a 1-dimensional tensor, a matrix is a 2-dimensional tensor, an array with three indices is a 3-dimensional tensor (RGB color images for example). The fundamental data structure for neural networks are tensors and PyTorch (as well as pretty much every other deep learning framework) is built around tensors.

With the basics covered, it's time to explore how we can use PyTorch to build a simple neural network.

```python
# First, import PyTorch
import torch
```

```python
def activation(x):
    """ Sigmoid activation function

        Arguments
        ---------
        x: torch.Tensor
    """
    return 1/(1+torch.exp(-x))
```

```python
### Generate some data
torch.manual_seed(7) # Set the random seed so things are predictable

# Features are 3 random normal variables
features = torch.randn((1, 5))
# True weights for our data, random normal variables again
weights = torch.randn_like(features)
# and a true bias term
bias = torch.randn((1, 1))
```

Above I generated data we can use to get the output of our simple network. This is all just random for now, going forward we'll start using normal data. Going through each relevant line:

`features = torch.randn((1, 5))` creates a tensor with shape `(1, 5)`, one row and five columns, that contains values randomly distributed according to the normal distribution with a mean of zero and standard deviation of one.

`weights = torch.randn_like(features)` creates another tensor with the same shape as `features`, again containing values from a normal distribution.

Finally, `bias = torch.randn((1, 1))` creates a single value from a normal distribution.

PyTorch tensors can be added, multiplied, subtracted, etc, just like Numpy arrays. In general, you'll use PyTorch tensors pretty much the same way you'd use Numpy arrays. They come with some nice benefits though such as GPU acceleration which we'll get to later. For now, use the generated data to calculate the output of this simple single layer network.
> **Exercise**: Calculate the output of the network with input features `features`, weights `weights`, and bias `bias`. Similar to Numpy, PyTorch has a [`torch.sum()`](https://pytorch.org/docs/stable/torch.html#torch.sum) function, as well as a `.sum()` method on tensors, for taking sums. Use the function `activation` defined above as the activation function.

```python
## Calculate the output of this network using the weights and bias tensors
y = activation(torch.sum(features * weights.reshape(5, 1) + bias))
y
```

    tensor(0.9897)

You can do the multiplication and sum in the same operation using a matrix multiplication. In general, you'll want to use matrix multiplications since they are more efficient and accelerated using modern libraries and high-performance computing on GPUs.

Here, we want to do a matrix multiplication of the features and the weights. For this we can use [`torch.mm()`](https://pytorch.org/docs/stable/torch.html#torch.mm) or [`torch.matmul()`](https://pytorch.org/docs/stable/torch.html#torch.matmul) which is somewhat more complicated and supports broadcasting. If we try to do it with `features` and `weights` as they are, we'll get an error

```python
>> torch.mm(features, weights)

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-13-15d592eb5279> in <module>()
----> 1 torch.mm(features, weights)

RuntimeError: size mismatch, m1: [1 x 5], m2: [1 x 5] at /Users/soumith/minicondabuild3/conda-bld/pytorch_1524590658547/work/aten/src/TH/generic/THTensorMath.c:2033
```

As you're building neural networks in any framework, you'll see this often. Really often. What's happening here is our tensors aren't the correct shapes to perform a matrix multiplication. Remember that for matrix multiplications, the number of columns in the first tensor must equal to the number of rows in the second column. Both `features` and `weights` have the same shape, `(1, 5)`. This means we need to change the shape of `weights` to get the matrix multiplication to work.

**Note:** To see the shape of a tensor called `tensor`, use `tensor.shape`. If you're building neural networks, you'll be using this method often.

There are a few options here: [`weights.reshape()`](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.reshape), [`weights.resize_()`](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.resize_), and [`weights.view()`](https://pytorch.org/docs/stable/tensors.html#torch.Tensor.view).

* `weights.reshape(a, b)` will return a new tensor with the same data as `weights` with size `(a, b)` sometimes, and sometimes a clone, as in it copies the data to another part of memory.
* `weights.resize_(a, b)` returns the same tensor with a different shape. However, if the new shape results in fewer elements than the original tensor, some elements will be removed from the tensor (but not from memory). If the new shape results in more elements than the original tensor, new elements will be uninitialized in memory. Here I should note that the underscore at the end of the method denotes that this method is performed **in-place**. Here is a great forum thread to [read more about in-place operations](https://discuss.pytorch.org/t/what-is-in-place-operation/16244) in PyTorch.
* `weights.view(a, b)` will return a new tensor with the same data as `weights` with size `(a, b)`.

I usually use `.view()`, but any of the three methods will work for this. So, now we can reshape `weights` to have five rows and one column with something like `weights.view(5, 1)`.

> **Exercise**: Calculate the output of our little network using matrix multiplication.

```python
## Calculate the output of this network using matrix multiplication
y = activation(torch.mm(features, weights.view(5, 1)) + bias)
y
```

    tensor([[0.1595]])

### Stack them up!

That's how you can calculate the output for a single neuron. The real power of this algorithm happens when you start stacking these individual units into layers and stacks of layers, into a network of neurons. The output of one layer of neurons becomes the input for the next layer. With multiple input units and output units, we now need to express the weights as a matrix.

The first layer shown on the bottom here are the inputs, understandably called the **input layer**. The middle layer is called the **hidden layer**, and the final layer (on the right) is the **output layer**. We can express this network mathematically with matrices again and use matrix multiplication to get linear combinations for each unit in one operation. For example, the hidden layer ($h_1$ and $h_2$ here) can be calculated

$$
\vec{h} = [h_1 \, h_2] =
\begin{bmatrix}
x_1 \, x_2 \cdots \, x_n
\end{bmatrix}
\cdot
\begin{bmatrix}
           w_{11} & w_{12} \\
           w_{21} &w_{22} \\
           \vdots &\vdots \\
           w_{n1} &w_{n2}
\end{bmatrix}
$$

The output for this small network is found by treating the hidden layer as inputs for the output unit. The network output is expressed simply

$$
y =  f_2 \! \left(\, f_1 \! \left(\vec{x} \, \mathbf{W_1}\right) \mathbf{W_2} \right)
$$

```python
### Generate some data
torch.manual_seed(7) # Set the random seed so things are predictable

# Features are 3 random normal variables
features = torch.randn((1, 3))

# Define the size of each layer in our network
n_input = features.shape[1]     # Number of input units, must match number of input features
n_hidden = 2                    # Number of hidden units
n_output = 1                    # Number of output units

# Weights for inputs to hidden layer
W1 = torch.randn(n_input, n_hidden)
# Weights for hidden layer to output layer
W2 = torch.randn(n_hidden, n_output)

# and bias terms for hidden and output layers
B1 = torch.randn((1, n_hidden))
B2 = torch.randn((1, n_output))
```

> **Exercise:** Calculate the output for this multi-layer network using the weights `W1` & `W2`, and the biases, `B1` & `B2`.

```python
## Your solution here
h = activation(torch.mm(features, W1) + B1)
y = activation(torch.mm(h, W2) + B2)
y
```

    tensor([[0.3171]])

If you did this correctly, you should see the output `tensor([[ 0.3171]])`.

The number of hidden units is a parameter of the network, often called a **hyperparameter** to differentiate it from the weights and biases parameters. As you'll see later when we discuss training a neural network, the more hidden units a network has, and the more layers, the better able it is to learn from data and make accurate predictions.

## Numpy to Torch and back

Special bonus section! PyTorch has a great feature for converting between Numpy arrays and Torch tensors. To create a tensor from a Numpy array, use `torch.from_numpy()`. To convert a tensor to a Numpy array, use the `.numpy()` method.

```python
import numpy as np
np.set_printoptions(precision=8)
a = np.random.rand(4,3)
a
```

    array([[0.83647367, 0.09353803, 0.2504508 ],
           [0.76247425, 0.87612109, 0.1240884 ],
           [0.75221536, 0.77306713, 0.26176122],
           [0.80366263, 0.25950889, 0.38911436]])

```python
torch.set_printoptions(precision=8)
b = torch.from_numpy(a)
b
```

    tensor([[0.83647367, 0.09353803, 0.25045080],
            [0.76247425, 0.87612109, 0.12408840],
            [0.75221536, 0.77306713, 0.26176122],
            [0.80366263, 0.25950889, 0.38911436]], dtype=torch.float64)

```python
b.numpy()
```

    array([[0.83647367, 0.09353803, 0.2504508 ],
           [0.76247425, 0.87612109, 0.1240884 ],
           [0.75221536, 0.77306713, 0.26176122],
           [0.80366263, 0.25950889, 0.38911436]])

The memory is shared between the Numpy array and Torch tensor, so if you change the values in-place of one object, the other will change as well.

```python
# Multiply PyTorch Tensor by 2, in place
b.mul_(2)
```

    tensor([[1.67294734, 0.18707605, 0.50090160],
            [1.52494850, 1.75224218, 0.24817680],
            [1.50443072, 1.54613427, 0.52352244],
            [1.60732526, 0.51901777, 0.77822872]], dtype=torch.float64)

```python
# Numpy array matches new values from Tensor
a
```

    array([[1.67294734, 0.18707605, 0.5009016 ],
           [1.5249485 , 1.75224218, 0.2481768 ],
           [1.50443072, 1.54613427, 0.52352244],
           [1.60732526, 0.51901777, 0.77822872]])


# Mandatory assignment 2: Solow growth model with human capital

* **Step 1: Package import:** The first part of the code in our project is the use of libraries with essential packages of panda, numpy and glob, which is essential for data structuring and especially arrays and pathnames and on top of that, we import statsmodel. We import the scipy package that provides various commonly used optimization algorithms. The sympy package is a Python library for symbolic computation.

```python
import pandas as pd
import ipywidgets as widgets
from ipywidgets import interact, interactive, fixed, interact_manual

import matplotlib.pyplot as plt
import math
import numpy as np
from scipy.optimize import minimize
from scipy.optimize import fsolve
import sympy as sm
sm.init_printing(use_unicode=True)
from scipy import optimize
from scipy.stats import trim_mean, kurtosis
from scipy.stats.mstats import mode, gmean, hmean
from IPython.display import display, Latex, Markdown
from IPython.display import Image
import statsmodels.api as statm
import statsmodels.formula.api as smf
from sympy import symbols, Eq, solve
import turtle
```

* **Step 2: Datapath and data processing:** The data in table 1 is from the second edition book "Introducing Advanced Macroeconomics: Growth and Business Cycles" by Peter Birch Sørensen and Hans Jørgen Whitta-Jacobsen.

```python
#Set datapath
datapath = "Dataset/"
data = pd.read_excel(datapath + "APPENDIX_TableA_SECOND_ED-7.xls")

#Remove unnecessary rows
data=data[14:-18]
#Remove unnecessary columns
data = data.iloc[:, :-3]

#Mark/select first row
new_header = data.iloc[0]
#All data below first row
data = data[1:]
#Use all data below first row and set column names equal new_header
data.columns = new_header

data.rename(columns = {list(data)[0]:"Country"}, inplace=True)
print("Table 1: Data")
data.head()

```

    Table 1: Data

      14
      Country
      Code
      y03
      y60
      gy
      sK
      sH
      u
      isi
      n
      Grade
      In Sample of 65

      15
      Luxembourg
      LUX
      1.73518
      0.966286
      0.0313231
      0.255914
      -
      -
      0.9
      0.00836579
      A
      0

      16
      USA
      USA
      1
      1
      0.017709
      0.189912
      0.115346
      12.049
      0.97333
      0.0160803
      A
      1

      17
      Ireland
      IRL
      0.971402
      0.423321
      0.0370255
      0.209641
      0.125219
      9.351
      0.76672
      0.0100189
      A
      1

      18
      Norway
      NOR
      0.968075
      0.761374
      0.0232946
      0.283681
      0.103698
      11.848
      0.87272
      0.0122046
      A
      1

      19
      Belgium
      BEL
      0.906815
      0.66365
      0.024969
      0.224658
      0.101057
      9.338
      0.86572
      0.00450328
      A
      1

We extend the dataset by combining it with data for land per worker and subsoil per worker, which we have downloaded from the World Bank data: Wealth estimates.

```python
datapath1 = "Dataset/"
#Import of natural resources from Excel file from the dataset folder
data1 = pd.read_excel(datapath1 + "Natural_resources_the_world_bank.xlsx")
#Merging the new natural resource data with the prior country data
data3 = pd.merge(data, data1, on="Code")
#Printing the first 50 rows including header sorted with the largest y03 values
print("Table 2: Final Data Table")
data3.head()
```

    Table 2: Final Data Table

      Country_x
      Code
      y03
      y60
      gy
      sK
      sH
      u
      isi
      n
      Grade
      In Sample of 65
      Country_y
      Land
      Subsoil

      0
      Luxembourg
      LUX
      1.73518
      0.966286
      0.0313231
      0.255914
      -
      -
      0.9
      0.00836579
      A
      0
      Luxembourg
      5600.667027
      1988.652571

      1
      USA
      USA
      1
      1
      0.017709
      0.189912
      0.115346
      12.049
      0.97333
      0.0160803
      A
      1
      United States
      4268.678035
      8651.206146

      2
      Ireland
      IRL
      0.971402
      0.423321
      0.0370255
      0.209641
      0.125219
      9.351
      0.76672
      0.0100189
      A
      1
      Ireland
      11973.825063
      367.476245

      3
      Norway
      NOR
      0.968075
      0.761374
      0.0232946
      0.283681
      0.103698
      11.848
      0.87272
      0.0122046
      A
      1
      Norway
      6372.783293
      83250.768105

      4
      Austria
      AUT
      0.880988
      0.554981
      0.0284557
      0.24048
      0.0951991
      8.354
      0.86356
      0.00244029
      A
      1
      Austria
      5163.836552
      1007.211843

```python
DataDescribe=data3
#From the dataset we delete unnecessary columns
DataDescribe = DataDescribe.drop(["Code", "In Sample of 65", "u", "isi", "Country_y"], 1)
#We do then delete the blank rows of growth in human capital
DataDescribe=DataDescribe[DataDescribe["sH"] != '   -']

#All data columns are defined as floats
DataDescribe["y03"] = DataDescribe.y03.astype(float)
DataDescribe["y60"] = DataDescribe.y60.astype(float)
DataDescribe["gy"] = DataDescribe.gy.astype(float)
DataDescribe["sK"] = DataDescribe.sK.astype(float)
DataDescribe["n"] = DataDescribe.n.astype(float)
DataDescribe["sH"] = DataDescribe.sH.astype(float)
DataDescribe["Land"] = DataDescribe.Land.astype(float)
DataDescribe["Subsoil"] = DataDescribe.Subsoil.astype(float)

#Print a descriptive table of maximum observation in y03
print("Table 3: The land with highest growth in income per worker")
max =DataDescribe[DataDescribe.y03 == DataDescribe.y03.max()]
display(max.head())

print("")
print("")

#Print a descriptive table of minimum observation in y03
print("Table 4: The land with the lowest growth in income per worker")
min =DataDescribe[DataDescribe.y03 == DataDescribe.y03.min()]
display(min.head())
```

    Table 3: The land with highest growth in income per worker

      Country_x
      y03
      y60
      gy
      sK
      sH
      n
      Grade
      Land
      Subsoil

      1
      USA
      1.0
      1.0
      0.017709
      0.189912
      0.115346
      0.01608
      A
      4268.678035
      8651.206146

    Table 4: The land with the lowest growth in income per worker

      Country_x
      y03
      y60
      gy
      sK
      sH
      n
      Grade
      Land
      Subsoil

      72
      Ethiopia
      0.023061
      0.026356
      0.014604
      0.035875
      0.014008
      0.021697
      C
      2538.656631
      50.165113

* **Step 3: Part 1: The Augmented Solow model with Human Capital:**

We now set up our **augmented Solow model with human capital**, which is given by the parameters:

1. $K_t$ is phusical capital
* $H_t$ is human capital
* $L_t$ is labor (with a growth rate on $n$)
* $A_t$ is technology (growing with a rate of $g$)
* $I^K_t=s_KY_t, 0<s_K<1$
* $I^H_t=s_HY_t, 0<s_H<1$
where $s_K+s_H<1$
* $K_{t+1} =s_KY_t+(1-\delta)K_t$
* $H_{t+1} =s_HY_t+(1-\delta)H_t$
* $L_{t+1}=(1+n)L_t$
* $A_{t+1}=(1+g)A_t$

* $Y_t = K_t^\alpha H_t^\varphi(A_tL_t)^{(1-\alpha)}, 0<\alpha<1, 0<\varphi<1, \alpha+\varphi<1 $ is GDP

We still have the normal framework for a Solow model such that **Savings** is a constant fraction of GDP and the production function have a **constant return to scale**.

Dividing the production function by $L_tA_t$ on both sides gives the production function per effective worker:

11. $\tilde{y}_t = \tilde{k}_t^\alpha \tilde{h}_t^\varphi$

* **Step 4: Steady State predictions:**
We are going to use equation 9 and 10 from above to model our two solow equations. We use the equation for physical capital per effective worker in the next period and the equation for human capital per effective worker in the next periode, given by:

12. $\tilde{k}_{t+1} = (1/((1+n)(1+g)))(s_K\tilde{y}_t+(1-\delta)\tilde{k}_t)$
13. $\tilde{h}_{t+1} = (1/((1+n)(1+g)))(s_H\tilde{y}_t+(1-\delta)\tilde{h}_t)$

By inserting equation 11 in equation 12 and 13, we get:

8. $\tilde{k}_{t+1} = (1/((1+n)(1+g)))(s_K\tilde{k}_t^\alpha \tilde{h}_t^\varphi+(1-\delta)\tilde{k}_t)$
9. $\tilde{h}_{t+1} = (1/((1+n)(1+g)))(s_H\tilde{k}_t^\alpha \tilde{h}_t^\varphi+(1-\delta)\tilde{h}_t)$

By using the two steady state conditions stating that $\tilde{k}_{t+1}=\tilde{k}_{t}=\tilde{k}^*$ and $\tilde{h}_{t+1}=\tilde{h}_{t}=\tilde{h}^*$, and withdrawing capital from both sides of eq. 8 and 9 we get the Solow equation which is stated below:

```python
import numpy as np
from scipy.optimize import minimize
import sympy as sm

#Define all variables and parameters as sympy symbols
a = sm.symbols("alpha")
phi = sm.symbols("varphi")
b = sm.symbols("beta")
g = sm.symbols("g")
n = sm.symbols("n")
n = sm.symbols("ng")
y = sm.symbols("y")
d = sm.symbols("delta")
k = sm.symbols("\\tilde{k}_t")
h = sm.symbols("\\tilde{h}_t")
ktildestar = sm.symbols("\\tilde{k}^*")
htildestar = sm.symbols("\\tilde{h}^*")

A = sm.symbols("A")
g_y = sm.symbols("g_y")
s_K = sm.symbols("s_K")
s_H = sm.symbols("s_H")
t = sm.symbols("t")

#Stating the Solow equations of capital and human capital with the sm equation function
SolowEq1 = sm.Eq(0,((1/((1+n)*(1+g)))*(s_K*(k**a * (h**phi))+(1-d)*k))-k)
SolowEq2 = sm.Eq(0,((1/((1+n)*(1+g)))*(s_H*(k**a * (h**phi))+(1-d)*h))-h)

#Printing the equations
SolowEq1, SolowEq2

```

We use the above Solow equations and solve the system of two equations with two unknown variables, we get:

```python
#Define k h as symbols
k, h = symbols('k h')
eq1 = Eq(s_K*k**a*h**phi-(n+g+d+(n*g))*k)
eq2 = Eq(s_H*k**a*h**phi-(n+g+d+(n*g))*h)

#derive the value of k and h in steady state from the abovementioned equations in parameter values
SS = solve((eq1,eq2), (k, h))

#Printing the steady-state value for capital and human capital
ktildestar=SS[0][0]
htildestar=SS[0][1]

# Latex print of physical capital per effective worker
display(
        Markdown(
            r'The steady state value of physical capital per effective worker is $\ \tilde k^*= $ {}.'.format(
                sm.latex(ktildestar, mode='inline'))))

# Latex print of physical capital per effective worker
display(
    Markdown(
        r'The steady state value of human capital per effective worker is $\ \tilde h^*= $ {}.'.format(
            sm.latex(htildestar, mode='inline'))))
```

The steady state value of physical capital per effective worker is $\ \tilde k^*= $ $s_{H}^{- \frac{\varphi}{\alpha + \varphi - 1}} \left(s_{K}^{\varphi - 1} \left(\delta + g ng + g + ng\right)\right)^{\frac{1}{\alpha + \varphi - 1}}$.

The steady state value of human capital per effective worker is $\ \tilde h^*= $ $\left(\left(s_{H}^{- \frac{\varphi}{\alpha + \varphi - 1}} \left(s_{K}^{\varphi - 1} \left(\delta + g ng + g + ng\right)\right)^{\frac{1}{\alpha + \varphi - 1}}\right)^{- \alpha} \left(\delta + g ng + g + ng\right) / s_{H}\right)^{\frac{1}{\varphi - 1}}$.

From above we calculated the expressions for the steady-state value for physical capital per effective worker and human capital per effective worker. We insert the below parameter values in the steady-state expressions and get:

```python
#Stating the variables and function of SS with lambdify
SS1 = sm.lambdify((n, g, s_H, s_K, a, phi, d, h), SS)

#Including the parameters known from macro-theory
SSvalue=SS1(0.01, 0.02, 0.2, 0.2, 0.33, 0.33, 0.05, 0.20)

#Printing the numerical steady-state values
print(SSvalue)
```

    [(14.696788132394444, 14.696788132394442)]

By using the analytical method we find the steady-state values for physical capital and human capital. We now show how to find the steady-state value by using the numerical method.

Conducting a numerical solution instead of an analytical solution.

```python
#We define the parameters with their known values for later use (including the steady-state value of h)
n = 0.01
g = 0.02
s_H = 0.20
a = 0.33
phi = 0.33
d = 0.05
h = 14.696788132394442

# Defining the steady-state as a lambda function
SSN = lambda k: 0 - (1/((1+n)*(1+g)))*((s_H*k**a*h**phi-(n+g+d+n*g)*k))

# Using optimize.newton for a numerical solution to our steady-state of k
# optimize.newton find the value of k that make the lambda function equal to zero, the newton method use a given starting point x0.
result_SSN
result_SSN = optimize.newton(SSN,x0=10)
np.round(result_SSN,3)
```

```python
#We do then derive the numerical solution for h knowing the value of k
k= 14.696788132394438

# Defining the steady-state as a lambda function
SSN1 = lambda h: 0 - (1/((1+n)*(1+g)))*((s_H*k**a*h**phi-(n+g+d+n*g)*h))

# Using optimize.newton for a numerical solution to our steadystate of h
result_SSN1 = optimize.newton(SSN1,x0=100)
result_SSN1
np.round(result_SSN1,3)
```

The numerical solution of the steady state value takes the Solow equations for both physical capital per effective worker and human capital per effective worker and calculating the steady-state values for capital by starting from an initial x0 value and finding the value that makes the Solow equation equal to zero

From the analytical solution we first found the Solow equation and then we found the solution to a system of two equations with two unknowns. In the numerical solution python calculate the steady state value numerically

* **Step 5: A Graphical View of the Model:**

The phase diagram illustrates a plot of the transitions equation of human capital and physical capital. The orange locus gives all combinations of $h_t$ and $k_t$ such that $h_t$ stays unchanged. Likewise, the blue locus gives all the combinations of $h_t$ and $k_t$ such that $k_t$ stays unchanged. From the graph, we see that the steady-state combination, $k^*$ and $h^*$, is where the curves intercept. This interception is exactly  where $k_t = 14.59$ and $h_t=14.69$ as we calculated above. Furthermore, this indicates that the steady-state fulfills the requirements for balanced growth. Moreover, we see that for every combination of $k_t$ and $h_t$ above the blue curve where $k_{t+1} - k_t > 0$ --> $k_t$ will be increasing and below it, $k_t$ must be decreasing.
The other curve follows the same dynamics. To the right of the orange curve $h_{t+1} - h_t > 0$, which means that $h_t$ will be increasing over time while $h_t$ to the left of the orange curve will be decreasing.

```python
# We define the parameter values
n = 0.01
g = 0.02
s_K = 0.20
a = 0.33
phi =0.33
s_H =0.20
d =0.05

# Setting the axis range to 18
k = np.array(range(18))

# We state the functions used in the phase diagram
h1 = ((((n+g+d+n*g)/(s_K))**(1/phi))*k**((1-a)/phi))
h2 = (((s_H*k**a)/(n+g+d+n*g))**(1/(1-phi)))

#We plot the x-axis array of k and the functions of h1 and h2
plt.plot(k, h1, h2)
plt.xlabel('k')
plt.ylabel('h')
plt.title('Phase Diagram for the Augmented Solow Model with Human Capital')
plt.show()
```

From below we see the exact same plot as above. The only difference is, that we now allow the saving rate for physical capital to vary. By simulating the model with a higher savings rate for physical capital, we see that the locus where $k_t$ is unchanged and rotates outwards. The economic reason is that a higher savings rate of physical capital means that people are saving more of their income. This leads to a higher saving per worker, which means that capital per capita will increase. When capital per worker increases, income per worker increases as well.

```python
#Setting a optional s_K value and S_H value in the figure below
def phase_dia(s_K,s_H):

# Setting a vector with equal linespace
    x_vec = np.linspace(0,50,50)

#Defining the phase function of capital and the known variables and parameters
    def phasek(k, s_K, a, phi, n):
        return ((((n+0.02+0.05+n*0.02)/(s_K))**(1/phi))*k**((1-a)/phi))

#Defining the phase function of human capital and the the known variables and parameters
    def phaseh(k, s_H, a, phi, n):
        return (((s_H*k**a)/(n+0.02+0.05+n*0.02))**(1/(1-phi)))

#Figure size, labels, title and known parameters of phasek and phaseh
    fig = plt.figure(figsize=(20,5))
    phasek1=phasek(x_vec, s_K, a, phi, n)
    phaseh1=phaseh(x_vec, s_H, a, phi, n)

    plt.grid()
    plt.ylim((0,50))
    plt.xlim((0,50))
    plt.plot(phasek1, linestyle='solid') #man kunne også skrive solid i stedet for dashed
    plt.plot(phaseh1, linestyle='solid')
    plt.title('Phase Diagram for the Augmented Solow Model with Human Capital')
    plt.xlabel("Human capital per effective worker")
    plt.ylabel("Physical capital per effective worker");  # dotted red - en kortere måde at skrive det på
#Defining the value of s_K as a widget with a float slider bar with a max of one and default value of 0.2
widgets.interact(phase_dia,
    s_K=widgets.FloatSlider(description="$\\ s_K$", min=0.1, max=0.35, step=0.0000001, value=0.20),
    s_H=widgets.FloatSlider(description="$\\ s_H$", min=0.1, max=0.35, step=0.0000001, value=0.20),
                );
```

    interactive(children=(FloatSlider(value=0.2, description='$\\ s_K$', max=0.35, min=0.1, step=1e-07), FloatSlid…

* **Step 4: The Augmented Solow Model with Human Capital and Natural resources:**
We now create a model to investigate the growth in income per worker.

We now set up our **augmented Solow model with human capital and natural resources**, which is given by the parameters:

1. $K_t$ is capital per worker
* $L_t$ is labor (with a growth rate on $n$)
* $A_t$ is technology (growing with a rate of $g$)
* $X_t$ is the total amount of land and it does not carry a time subscript as land is in fixed supply
* $R_t$ is the total remaining stock of an exhaustible resource, oil at time t
* $E_t$ is the part of $R_t$ that is used as energy input during period t (where the exhaustible rate is $s_E$)
* $Y_t = K_t^\alpha H_t^\varphi(A_tL_t)^\beta X^\kappa E_t^\epsilon$ is GDP

We still have the normal framework for a Solow model such that **Savings** is a constant fraction of GDP and the production function have a constant return to scale. Moreover, we are going to use the following equations to model our framework.

8. $R_{t+1} = R_t - E_t$
9. $ E_t = s_E R_t$, where $0<s_E<1$

Dividing the production function by $L_t$ on both sides and using equation 9, this gives the production function per worker:

10. $y_t = k_t^\alpha h_t^\varphi A_t^\beta (X/L_t)^\kappa ({s_E R_t}/L_t)^\epsilon $
11. $y_t = k_t^\alpha h_t^\varphi A_t^\beta x_t^\kappa e_t^\epsilon $
By taking logs on both sides we get:
11. $ln(y_t) = \alpha ln(k_t) +\varphi ln(h_t)+ \beta ln(A_t)- \kappa (ln(L_t)-ln(L_{t-1}))+ \epsilon (((ln(R_t)-ln(R_{t-1}))-(ln(L_t)-ln(L_{t-1}))) = \alpha ln(k_t) +\varphi ln(h_t)+ \beta ln(A_t)- (\kappa+\epsilon) (ln(L_t)-ln(L_{t-1}))+ \epsilon (ln(R_t)-ln(R_{t-1})) $

By combinig equation 8 and 9, we get that $ R_{t+1} = R_t - s_E R_t <=> (R_{t+1}/R_t) = s_E + 1 $, and use that $ln(R_t)-ln(R_{t-1})=-ln(s_E+1) =-s_E$, we get that:

13. $ln(y_t) = \alpha ln(k_t) +\varphi ln(h_t)+ \beta ln(A_t)- (\kappa+\epsilon) n- \epsilon s_E $

We now use the standard approximation that logs differences as a growth rate approximation. Therefore, calculate the time differences on both sides which gives:

14. $ln(y_t)-ln(y_{t-1}) = \alpha (ln(k_t)-ln(k_{t-1})) +\varphi (ln(h_t)-ln(h_{t-1}))+ \beta (ln(A_t)-ln(A_{t-1}))- (\kappa+\epsilon) n- \epsilon s_E$

We use the approximation that the growth rate for output per worker is $g_y$, the growth rate for technology is g, the growth rate for human capital per worker is $g_h$ and the growth rate in capital per worker is $g_k$. This gives us the following equation:

15. $g_y = \alpha g_k +\varphi g_h+ \beta g- (\kappa+\epsilon) n- \epsilon s_E$

We know from the Solow model that the two capital/output ratios are constant in steady-state, which is why we use the approximation that both capital and human capital grow at the same rate as $y_t$: $g_y = g_k =g_h$. This gives us the following model:

16. $g_y = \beta / {(1-\alpha-\varphi)} g - {(\kappa+\epsilon)n} / {(1-\alpha-\varphi)} - \epsilon / {(1-\alpha-\varphi)} s_E$

We now use that the Solow model with human capital and natural resources exhibit a constant return to scale, which gives us:

17. $g_y = \beta / {(\beta+\kappa+\epsilon)} g - {(\kappa+\epsilon)n} / {(\beta+\kappa+\epsilon)} - \epsilon / {(\beta+\kappa+\epsilon)} s_E$

We now have our equation 17, which can tell us how large the growth rate in GDP each country hold depending on the parameters. Therefore, we will first find the parameter values before we continue by finding the growth rate for GDP for different values for growth rate for population and growth rate for technology.

* **Step 4: Finding parameter values for all parameters that are included in our equation 17:**

We use equation (37) from page 205 in the book "Introducing  Advanced Macroeconomics - Growth and Business Cycles" by Peter Birk Sørensen and Hans Jørgen Whitta-Jacobsen and augmenting it with human capital. We then get:

18. $ln(y_t^*) = \beta / {(\beta+\kappa+\epsilon)} ln(A_t) + \epsilon / {(\beta+\kappa+\epsilon)} ln(s_E) + \alpha / {(\beta+\kappa+\epsilon)} [ln(s_K)-ln(n+0.062)]+ \varphi / {(\beta+\kappa+\epsilon)} [ln(s_H)-ln(n+0.062)] + \kappa / {(\beta+\kappa+\epsilon)} ln({X/L_t})+ \epsilon / {(\beta+\kappa+\epsilon)} ln({R_t/L_t}) $

Which gives us the following regression equation:

18. $ln(y_{00}^i) = \gamma_0 + \gamma_1 [ln(s^i_K)-ln(n^i+0.062)]+ \gamma_2 [ln(s^i_H)-ln(n^i+0.062)] + \gamma_3 ln({X/L^i})_{00}+  \gamma_4 ln({R^i/L^i})_{00} $

```python
#Linear estimation from the dataset imported in cell 2
#Restructuring the values into log format
logy60=np.log(DataDescribe["y60"])
logy03=np.log(DataDescribe["y03"])
logn1 =np.log(DataDescribe["n"]+0.075)
logsK1=np.log(DataDescribe["sK"]) - logn1
logsH1=np.log(DataDescribe["sH"]) - logn1
logland =np.log(DataDescribe["Land"])
logsubsoil =np.log(DataDescribe["Subsoil"])

# Fit regression model (using the natural log of one of the regressors) from the dataframe called datadescripe
results = smf.ols("logy03 ~ logsK1 + logsH1 + logland + logsubsoil", data=DataDescribe).fit()

# Inspect the results of log(y03) with the regressors logsK1, logsH1, logland, logsubsoil
print("Table 5: OLS Results")
print(results.summary())
```

    Table 5: OLS Results
                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                 logy03   R-squared:                       0.747
    Model:                            OLS   Adj. R-squared:                  0.731
    Method:                 Least Squares   F-statistic:                     47.14
    Date:                Sun, 26 May 2019   Prob (F-statistic):           2.08e-18
    Time:                        22:45:34   Log-Likelihood:                -62.695
    No. Observations:                  69   AIC:                             135.4
    Df Residuals:                      64   BIC:                             146.6
    Df Model:                           4
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    Intercept     -2.6310      1.200     -2.193      0.032      -5.028      -0.234
    logsK1         0.3933      0.184      2.136      0.037       0.025       0.761
    logsH1         0.8993      0.141      6.391      0.000       0.618       1.180
    logland        0.1283      0.143      0.900      0.372      -0.157       0.413
    logsubsoil     0.0301      0.035      0.867      0.389      -0.039       0.100
    ==============================================================================
    Omnibus:                        9.462   Durbin-Watson:                   1.651
    Prob(Omnibus):                  0.009   Jarque-Bera (JB):                9.414
    Skew:                          -0.887   Prob(JB):                      0.00903
    Kurtosis:                       3.355   Cond. No.                         178.
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

From the regression table above we see that the variables Land and Subsoil are insignificant, meaning we cannot use the coefficients for Land per Worker and Subsoil per Worker to estimate $ \epsilon $ and $ \kappa $. They have a P-value on 0.372 and 0.389 respectively. Therefore, we use the coefficients provided by Peter Birk Sørensen and Hans Jørgen Whitta-Jacobsen in the book "Introducing Advanced Macroeconomics Growth and Business Cycles" to estimate the growth drags below, the other coefficient is calculated below by using the expression from their book. Moreover, we have that $s_K$ and $s_H $depend positively on the growth in 2003 as we expected.

We estimate the parameters:

* $\alpha = \gamma_1/{(1+\gamma_1+\gamma_2+\gamma_3+\gamma_4)}$
* $\varphi = \gamma_2/{(1+\gamma_1+\gamma_2+\gamma_3+\gamma_4)}$
* $\kappa = \gamma_3/{(1+\gamma_1+\gamma_2+\gamma_3+\gamma_4)}$
* $\epsilon = \gamma_4/{(1+\gamma_1+\gamma_2+\gamma_3+\gamma_4)}$

By inserting the estimated values we get:

* $\alpha = 0.3933/{(1+0.3933+0.8993+0.1283+0.0301)} = 0.16 $
* $\varphi = 0.8993/{(1+0.3933+0.8993+0.1283+0.0301)} = 0.37 $
* $\kappa = 0.1283/{(1+0.3933+0.8993+0.1283+0.0301)} = 0.05 $
* $\epsilon = 0.0301/{(1+0.3933+0.8993+0.1283+0.0301)}= 0.01 $

We assume that the shares for raw labor and human capital is equal to 0.6, meaning that $\beta = 0.23$
We now continue our Model Project with the shares (the shares for land and oil are not significant so we continue with the shares from the book by Hans Jørgen and Peter Birk:

* $\alpha = 0.2 $
* $\varphi = 0.3 $
* $\kappa = 0.1 $
* $\epsilon = 0.1$
* $\beta = 0.3$

We insert the parameters in the growth eq.:

* $g_y = 0.3/{(0.3+0.1+0.1)}g - {(0.2)n} / {(0.3+0.1+0.1)} - 0.1 / {(0.3+0.1+0.1)} 0.005$
* $g_y = 0.6g - 0.4n - 0.001$

$g_y = \beta / {(\beta+\kappa+\epsilon)} g - {(\kappa+\epsilon)n} / {(\beta+\kappa+\epsilon)} - \epsilon / {(\beta+\kappa+\epsilon)} s_E$

```python
#Defining the symbols from sympy
b = sm.symbols("beta")
g = sm.symbols("g")
n = sm.symbols("n")
k = sm.symbols("kappa")
e = sm.symbols("epsilon")
g_y = sm.symbols("g_y")
s_K = sm.symbols("s_K")
s_H = sm.symbols("s_H")
s_E = sm.symbols("s_E")

#Stating the growth equation as a sympy equation
growth = sm.Eq(0, (b/(b+k+e)*g)-(k+e)*n/(b+k+e)-e/(b+k+e)*s_E)
#Print the equation
growth

```

The function above is shows all the combinations of growth in tecnology and grwoth in population that gives a growth in income per worker equal to zero. We use this function below to investigate at what level the  growth rate in technology has to be, to offset the growth drag from the population growth rate

```python
#Solve for the growth rate in technology
growth1 = sm.solve(growth, g)
#Print the solution of g
growth1
```

Below we are plotting the above function, which shows the growth rate in technology as a function of the growth rate in the population and the externalities from the other variables.

```python
#Define the new symbols with sympy
b = sm.symbols("beta")
g = sm.symbols("g")
n = sm.symbols("n")
s_E = sm.symbols("s_E")
e = sm.symbols("epsilon")
k = sm.symbols("kappa")

#Define the plot settings with the variables of s_E, b, e, k.
def dia3(s_E, b, e, k):

#Define the growthh function with the variables and parameters n, e, s_E, b, k.
    def growthh(n, e, s_E, b, k):
        return ((e*n+e*s_E+k*n)/b)

#Setting a vector with equal linespace
    x_vec1 = np.linspace(0,50,50)

    #Setting the plotted function as the growthh function with the unknown variables of x_vec1, s_E, e, b and k
    growthh1=growthh(x_vec1, s_E, e, b, k)

    fig2 = plt.figure(figsize=(20,5))
    plt.grid()
    plt.ylim((0,0.1))
    plt.xlim((0,0.1))
    plt.plot(growthh1, linestyle='solid') #man kunne også skrive solid i stedet for dashed
    plt.title('Growth')
    plt.xlabel("Population growth rate")
    plt.ylabel("Growth rate in technology");

#The optional variables are then defined as float sliders with default values
widgets.interact(dia3,
    s_E=widgets.FloatSlider(description="$\\ s_E$", min=0.1, max=0.2, step=0.0000001, value=0.03),
    b=widgets.FloatSlider(description="$ \\beta $", min=0.1, max=0.8, step=0.0000001, value=0.66),
    e=widgets.FloatSlider(description="$ \\epsilon  $", min=0.1, max=0.2, step=0.0000001, value=0.1),
    k=widgets.FloatSlider(description="$ \kappa $", min=0.1, max=0.2, step=0.0000001, value=0.1),

                );

```

    interactive(children=(FloatSlider(value=0.1, description='$\\ s_E$', max=0.2, min=0.1, step=1e-07), FloatSlide…

This graph plots the growth in technology in the y-axis and population growth on the x-axis. Along the plotted line, we have that the long run growth rate in income per worker is exactly zero. If we are in a point above the plotted line we have that the growth rate in technology is high enough to compensate from the growth drag form Land, Oil, and, Population. Moreover, if we are in a point below the plotted line we have that the growth rate in technology is not high enough to compensate from the growth drag meaning that the long run growth rate in income per worker will be negative. We can move the interactive graph and see that a higher epsilon and kappa will lead to a higher growth drag meaning that the growth rate in technology has to be higher to compensate from the growth drag.

If we have the default parameter values above, we see that if the growth rate in population is around 3-4% which is not impossible for at devoloping country, then the country would need a growth rate in technology of atleast 2-3% before getting a $g_Y$ higher than zero, but the country would need a growth rate in technology on 4-5% to get a growth rate in income per worker sufficient higher than zero.

# References
* Whitta-Jacobsen, Hans Jørgen & Soernsen, Peter Birch. (2010): Introducing Advanced Macroeconomics: Growth and Business Cycles, McGraw-Hill Higher Education


*Sebastian Raschka*
last modified: 03/31/2014

I am really looking forward to your comments and suggestions to improve and extend this tutorial! Just send me a quick note
via Twitter: [@rasbt](https://twitter.com/rasbt)
or Email: [bluewoodtree@gmail.com](mailto:bluewoodtree@gmail.com)

### Problem Category
- Statistical Pattern Recognition
- Supervised Learning
- Parametric Learning
- Bayes Decision Theory
- Univariate data
- 2-class problem
- different variances
- Gaussian model (2 parameters)
- No Risk function

# Sections

• Given information

• Deriving the decision boundary

• Plotting the class conditional densities, posterior probabilities, and decision boundary

• Classifying some random example data

• Calculating the empirical error rate

## Given information:

[back to top]

####model: continuous univariate normal (Gaussian) model for the class-conditional densities

$ p(x | \omega_j) \sim N(\mu|\sigma^2) $

$ p(x | \omega_j) \sim \frac{1}{\sqrt{2\pi\sigma^2}} \exp{ \bigg[-\frac{1}{2}\bigg( \frac{x-\mu}{\sigma}\bigg)^2 \bigg] } $

####Prior probabilities:

$ P(\omega_1) = P(\omega_2) = 0.5 $

#### Variances of the sample distributions

$ \sigma_1^2 = 4, \quad \sigma_2^2 = 1 $

#### Means of the sample distributions

$ \mu_1 = 4, \quad \mu_2 = 10 $

## Deriving the decision boundary
[back to top]

### Bayes' Rule:

$ P(\omega_j|x) = \frac{p(x|\omega_j) * P(\omega_j)}{p(x)} $

###Bayes' Decision Rule:

Decide $ \omega_1 $ if $ P(\omega_1|x) > P(\omega_2|x) $ else decide $ \omega_2 $.

\begin{equation}
\begin{aligned}
&\Rightarrow \frac{p(x|\omega_1) * P(\omega_1)}{p(x)} > \frac{p(x|\omega_2) * P(\omega_2)}{p(x)}
\end{aligned}
\end{equation}

###Bayes' Decision Rule:

Decide $ \omega_1 $ if $ P(\omega_1|x) > P(\omega_2|x) $ else decide $ \omega_2 $.

$ \Rightarrow \frac{p(x|\omega_1) * P(\omega_1)}{p(x)} > \frac{p(x|\omega_2) * P(\omega_2)}{p(x)} $

We can drop $ p(x) $ since it is just a scale factor.

$ \Rightarrow P(x|\omega_1) * P(\omega_1) > p(x|\omega_2) * P(\omega_2) $

$ \Rightarrow \frac{p(x|\omega_1)}{p(x|\omega_2)} > \frac{P(\omega_2)}{P(\omega_1)} $

$ \Rightarrow \frac{p(x|\omega_1)}{p(x|\omega_2)} > \frac{0.5}{0.5} $

$ \Rightarrow \frac{p(x|\omega_1)}{p(x|\omega_2)} > 1 $

$ \Rightarrow \frac{1}{\sqrt{2\pi\sigma_1^2}} \exp{ \bigg[-\frac{1}{2}\bigg( \frac{x-\mu_1}{\sigma_1}\bigg)^2 \bigg] } > \frac{1}{\sqrt{2\pi\sigma_2^2}} \exp{ \bigg[-\frac{1}{2}\bigg( \frac{x-\mu_2}{\sigma_2}\bigg)^2 \bigg] } \quad \bigg| \quad ln $

$ \Rightarrow ln(1) - ln\bigg({\sqrt{2\pi\sigma_1^2}}\bigg) -\frac{1}{2}\bigg( \frac{x-\mu_1}{\sigma_1}\bigg)^2  > ln(1) - ln\bigg({{\sqrt{2\pi\sigma_2^2}}}\bigg) -\frac{1}{2}\bigg( \frac{x-\mu_2}{\sigma_2}\bigg)^2 \quad \bigg| \quad \sigma_1^2 = 4, \quad \sigma_2^2 = 1,\quad \mu_1 = 4, \quad \mu_2 = 10 $

$ \Rightarrow -ln({\sqrt{2\pi4}}) -\frac{1}{2}\bigg( \frac{x-4}{2}\bigg)^2  > -ln({{\sqrt{2\pi}}}) -\frac{1}{2}(x-10)^2 $

$ \Rightarrow -\frac{1}{2} ln({2\pi}) - ln(2) -\frac{1}{8} (x-4)^2  > -\frac{1}{2}ln(2\pi) -\frac{1}{2}(x-10)^2 \quad \bigg| \; \times\; 2 $

$ \Rightarrow -ln({2\pi}) - 2ln(2) - \frac{1}{4}(x-4)^2  > -ln(2\pi) - (x-10)^2 \quad \bigg| \; + ln(2\pi) $

$ \Rightarrow -4ln(4) - (x-4)^2  >- 4(x-10)^2 $

$ \Rightarrow -ln(4) - \frac{1}{4}(x-4)^2  > -(x-10)^2 \quad \big| \; \times \; 4 $

$ \Rightarrow -8ln(2) - x^2 + 8x - 16  > - 4x^2 + 80x - 400 $

$ \Rightarrow 3x^2 - 72x + 384 -8ln(2) > 0 $

$ \Rightarrow x < 7.775 \quad and \quad x > 16.225 $

## Plotting the class conditional densities, posterior probabilities, and decision boundary

[back to top]

```
%pylab inline

import numpy as np
from matplotlib import pyplot as plt

def pdf(x, mu, sigma):
    """
    Calculates the normal distribution's probability density
    function (PDF).

    """
    term1 = 1.0 / ( math.sqrt(2*np.pi) * sigma )
    term2 = np.exp( -0.5 * ( (x-mu)/sigma )**2 )
    return term1 * term2

# generating some sample data
x = np.arange(-100, 100, 0.05)

# probability density functions
pdf1 = pdf(x, mu=4, sigma=4)
pdf2 = pdf(x, mu=10, sigma=1)

# Class conditional densities (likelihoods)
plt.plot(x, pdf1)
plt.plot(x, pdf2)
plt.title('Class conditional densities (likelihoods)')
plt.ylabel('p(x)')
plt.xlabel('random variable x')
plt.legend(['p(x|w_1) ~ N(4,4)', 'p(x|w_2) ~ N(10,1)'], loc='upper left')
plt.ylim([0,0.5])
plt.xlim([-15,20])
plt.show()
```

```
def posterior(likelihood, prior):
    """
    Calculates the posterior probability (after Bayes Rule) without
    the scale factor p(x) (=evidence).

    """
    return likelihood * prior

# probability density functions
posterior1 = posterior(pdf(x, mu=4, sigma=4), 0.5)
posterior2 = posterior(pdf(x, mu=10, sigma=1), 0.5)

# Class conditional densities (likelihoods)
plt.plot(x, posterior1)
plt.plot(x, posterior2)
plt.title('Posterior Probabilities w. Decision Boundaries')
plt.ylabel('P(w)')
plt.xlabel('random variable x')
plt.legend(['P(w_1|x)', 'p(w_2|X)'], loc='upper left')
plt.ylim([0,0.25])
plt.xlim([-15,20])
plt.axvline(7.775, color='r', alpha=0.8, linestyle=':', linewidth=2)
plt.axvline(16.225, color='r', alpha=0.8, linestyle=':', linewidth=2)
plt.annotate('R2', xy=(10, 0.2), xytext=(10, 0.22))
plt.annotate('R1', xy=(4, 0.2), xytext=(4, 0.22))
plt.annotate('R1', xy=(17, 0.2), xytext=(17.5, 0.22))
plt.show()
```

## Classifying some random example data

[back to top]

```
# Parameters
mu_1 = 4
mu_2 = 10
sigma_1_sqr = 4
sigma_2_sqr = 1

# Generating 10 random samples drawn from a Normal Distribution for class 1 & 2
x1_samples = sigma_1_sqr**0.5 * np.random.randn(20) + mu_1
x2_samples = sigma_1_sqr**0.5 * np.random.randn(20) + mu_2
y = [0 for i in range(20)]

# Plotting sample data with a decision boundary

plt.scatter(x1_samples, y, marker='o', color='green', s=40, alpha=0.5)
plt.scatter(x2_samples, y, marker='^', color='blue', s=40, alpha=0.5)
plt.title('Classifying random example data from 2 classes')
plt.ylabel('P(x)')
plt.xlabel('random variable x')
plt.legend(['w_1', 'w_2'], loc='upper right')
plt.ylim([-0.1,0.1])
plt.xlim([0,20])
plt.axvline(7.775, color='r', alpha=0.8, linestyle=':', linewidth=2)
plt.axvline(16.225, color='r', alpha=0.8, linestyle=':', linewidth=2)
plt.annotate('R2', xy=(10, 0.03), xytext=(10, 0.03))
plt.annotate('R1', xy=(4, 0.03), xytext=(4, 0.03))
plt.annotate('R1', xy=(17, 0.03), xytext=(17.5, 0.03))
plt.show()
```

## Calculating the empirical error rate

[back to top]

```
w1_as_w2, w2_as_w1 = 0, 0
for x1,x2 in zip(x1_samples, x2_samples):
    if x1 > 7.775 and x1 < 16.225:
        w1_as_w2 += 1
    if x2 <= 7.775 and x2 >= 16.225:
        w2_as_w1 += 1

emp_err =  (w1_as_w2 + w2_as_w1) / float(len(x1_samples) + len(x2_samples))

print('Empirical Error: {}%'.format(emp_err * 100))
```

    Empirical Error: 2.5%


```python
clean_up=True # removes gams-related files in work-folder if true
%run StdPackages.ipynb
os.chdir(py['main'])
import Invest
from DataBase_wheels import small_updates
os.chdir(curr)
data_folder = os.getcwd()+'\\Data\\IO'
gams_folder = os.getcwd()+'\\gamsmodels\\Itory'
```

    The file_gams_py_gdb0.gdx is still active and was not deleted.
    The file_gams_py_gdb1.gdx is still active and was not deleted.

## Investment sector with inventory sector

To account for inventory investments in data a sector is included to handle this. This sector is very simple:
* The sector demands final goods (a,b,a_F,b_F), but produces no outputs.
* There are still some taxes included from this sector; these are accounted for by defining the difference between the price before and after taxes for the inputs they use.

The current module does not have a proper theory of inventory investments underlying it; thus the investment demand/supply are modelled as simple AR(1) series converging on no investments. The demand for inventory goods are defined as:
$$\begin{align}
    qD[t,s,n]  = (0,9) qD[t-1,s,n]
\end{align}$$
defined for $t\in\lbrace tx0E\rbrace $, $(s,n)\in\lbrace itoryD[s,n]\rbrace$.

Beyond this model for the demand for inventory goods, the sector itself works as the other investment sectors: The sector demands goods defined by some nesting tree.

### *1: Read in data*

*Load IO data:*

```python
GE_data = DataBase.GPM_database(pickle_path=data_folder+'\\GE_data')
```

*Load global settings:*

```python
with open(data_folder+'\\Ex1_gs',"rb") as file:
    gs = pickle.load(file)
```

*subset data:*

```python
GE_itory = small_updates.subset_db(GE_data.copy(),GE_data.get('s_itory'))
for var in GE_itory.variables_flat:
    GE_itory[var] = DataBase_wheels.repeat_variable_windex(GE_itory.get(var),gs.get('t0'))
```

### *2: Initialize module and run*

```python
name_module = 'itory'
gm_itory = Invest.itoryD(work_folder=work_folder,databases=[GE_itory],gs_v=gs,**{'data_folder':gams_folder,'name':name_module})
gm_itory.write_and_run(kwargs_init={'check_variables':True},overwrite=True)
```

Export as pickle:

```python
db = gm_itory.model_instances['baseline'].out_db
[db.series.__delitem__(sym) for sym in db.symbols if sym not in gm_itory.model.database.symbols];
gm_itory.model.database.merge_dbs(gm_itory.model.database,db,'second')
gm_itory.export()
```

    'C:\\Users\\sxj477\\Documents\\GitHub\\GPM_v05\\examples\\gamsmodels\\Itory\\gmspython_itory'

*Inspect the solution:*

```python
var,year,kwargs = 'qD',1, {'level':'t'}
print(diagnostics.compare_data.std_diagnostics_var(db,GE_data,var,xs=year,plot=False,**kwargs))
pd.DataFrame({'Model': diagnostics.compare_data.get_db1(db,GE_data,var,xs=year,**kwargs), 'IO': diagnostics.compare_data.get_db2(db,GE_data,var,xs=year,**kwargs)}).plot.bar(figsize=(8,6));
```


# A Gentle Introduction to HARK: Buffer Stock Saving

This notebook explores the behavior of a consumer identical to the perfect foresight consumer described in [Gentle-Intro-To-HARK-PerfForesightCRRA](https://econ-ark.org/materials/Gentle-Intro-To-HARK-PerfForesightCRRA) except that now the model incorporates income uncertainty.

```python
# This cell has a bit of initial setup.
# Click the "Run" button immediately above the notebook in order to execute the contents of any cell
# WARNING: Each cell in the notebook relies upon results generated by previous cells
#   The most common problem beginners have is to execute a cell before all its predecessors
#   If you do this, you can restart the kernel (see the "Kernel" menu above) and start over
import matplotlib.pyplot as plt

import numpy as np
import HARK
from time import clock
from copy import deepcopy
mystr = lambda number : "{:.4f}".format(number)
from HARK.utilities import plotFuncs
```

## The Consumer's Problem with Transitory and Permanent Shocks
### Mathematical Description

Our new type of consumer receives two income shocks at the beginning of each period.  Permanent income would grow by a factor $\Gamma$ in the absence of any shock , but its growth is modified by a shock, $\psi_{t+1}$:
\begin{align}
 P_{t+1} & = \Gamma P_{t}\psi_{t+1}
\end{align}
whose expected (mean) value is $\mathbb{E}_{t}[\psi_{t+1}]=1$.  Actual income received $Y$ is equal to permanent income $P$ multiplied by a transitory shock $\theta$:
\begin{align}
 Y_{t+1} & = \Gamma P_{t+1}\theta_{t+1}
\end{align}
where again $\mathbb{E}_{t}[\theta_{t+1}] = 1$.

As with the perfect foresight problem, this model can be rewritten in terms of _normalized_ variables, e.g. the ratio of 'market resources' $M_{t}$ (wealth plus current income) to permanent income is $m_t \equiv M_t/P_t$.  (See [here](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/) for the theory).  In addition, lenders may set a limit on borrowing: The ratio $a_{t}$ of end-of-period assets to permanent income $A_t/P_t$ must be greater than $\underline{a} \leq 0$. (So, if $\underline{a}=-0.3$, the consumer cannot borrow more than 30 percent of their permanent income).

The consumer's (normalized) problem turns out to be:
\begin{eqnarray*}
v_t(m_t) &=& \max_{c_t} ~~u(c_t) + \beta  \mathbb{E} [(\Gamma_{t+1}\psi_{t+1})^{1-\rho} v_{t+1}(m_{t+1}) ], \\
 & \text{s.t.} & \\
a_t &=& m_t - c_t, \\
a_t &\geq& \underline{a}, \\
m_{t+1} &=& a_t  R/(\Gamma_{t+1} \psi_{t+1}) + \theta_{t+1}.
\end{eqnarray*}

For present purposes, we assume that the transitory and permanent shocks are independent.  The permanent shock is assumed to be (approximately) lognormal, while the transitory shock has two components: A probability $\wp$ that the consumer is unemployed, in which case $\theta^{u}=\underline{\theta}$, and a probability $(1-\wp)$ of a shock that is a lognormal with a mean chosen so that $\mathbb{E}_{t}[\theta_{t+n}]=1$.

### Representing the Income Shocks

Computers are discrete devices; even if somehow we knew with certainty that the transitory and permanent shocks were, say, continuously lognormally distributed, in order to be represented on a computer those distributions would need to be approximated by a finite set of points.  A large literature in numerical computation explores ways to construct such approximations; probably the easiest discretization to understand is the equiprobable approximation, in which the continuous distribution is represented by a set of $N$ outcomes that are equally likely to occur.

In the case of a single variable (say, the permanent shock $\psi$), and when the number of equiprobable points is, say, 5, the procedure is to construct a list: $\psi^{0}$ is the mean value of the continuous $\psi$ given that the draw of $\psi$ is in the bottom 20 percent of the distribution of the continuous $\psi$.  $\psi^{1}$ is the mean value of $\psi$ given that the draw is between the 20th and 40th percentiles, and so on.  Having constructed these, the approximation to the expectation of some expression $g(\psi)$ can be very quickly calculated by:

$$
\mathbb{E}_{t}[g(\psi)] \equiv \int_{0}^{\infty} g(\psi) dF_{\psi} \approx (1/N) \sum_{i=0}^{N-1} g(\psi^{i}).
$$

(For a graphical depiction of a particular instance of this, see [SolvingMicroDSOPs/#discreteApprox](http://www.econ2.jhu.edu/people/ccarroll/SolvingMicroDSOPs/#discreteApprox).)

## The New Parameters

In addition to the parameters required for the perfect foresight model (like the time preference factor $\beta$), under the assumptions above, we need to choose values for the following extra parameters that describe the income shock distribution and the artificial borrowing constraint.

| Param | Description | Code | Value |
| :---: | ---         | ---  | :---: |
| $\underline{a}$ | Artificial borrowing constraint | $\texttt{BoroCnstArt}$ | 0.0 |
| $\sigma_\psi$ | Underlying stdev of permanent income shocks | $\texttt{PermShkStd}$ | 0.1 |
| $\sigma_\theta^{e}$ | Underlying stdev of transitory income shocks | $\texttt{TranShkStd}$ | 0.1 |
| $N_\psi$ | Number of discrete permanent income shocks | $\texttt{PermShkCount}$ | 7 |
| $N_\theta$ | Number of discrete transitory income shocks | $\texttt{TranShkCount}$ | 7 |
| $\wp$ | Unemployment probability | $\texttt{UnempPrb}$ | 0.05 |
| $\underline{\theta}$ | Transitory shock when unemployed | $\texttt{IncUnemp}$ | 0.3 |

## Representation in HARK

HARK agents with this kind of problem are instances of the class $\texttt{IndShockConsumerType}$, which is constructed by "inheriting" the properties of the $\texttt{PerfForesightConsumerType}$ and then adding only the _new_ information required:

```python
# This cell defines a parameter dictionary for making an instance of IndShockConsumerType.

IndShockDictionary = {
    'PermShkStd': [0.1],  # ... by specifying the new parameters for constructing the income process.
    'PermShkCount': 7,
    'TranShkStd': [0.1],
    'TranShkCount': 7,
    'UnempPrb': 0.05,
    'IncUnemp': 0.3, # ... and income for unemployed people (30 percent of "permanent" income)
    'BoroCnstArt': 0.0, # ... and specifying the location of the borrowing constraint (0 means no borrowing is allowed)
    'cycles': 0         # signifies an infinite horizon solution (see below)
}
```

## Other Attributes are Inherited from PerfForesightConsumerType

You can see all the **attributes** of an object in Python by using the `dir()` command. From the output of that command below, you can see that many of the model variables are now attributes of this object, along with many other attributes that are outside the scope of this tutorial.

```python
from HARK.ConsumptionSaving.ConsIndShockModel import PerfForesightConsumerType

pfc = PerfForesightConsumerType()
dir(pfc)
```

    ['AgentCount',
     'BoroCnstArt',
     'CRRA',
     'DiscFac',
     'LivPrb',
     'MaxKinks',
     'PermGroFac',
     'PermGroFacAgg',
     'RNG',
     'Rfree',
     'T_age',
     'T_cycle',
     '__call__',
     '__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'aNrmInitMean',
     'aNrmInitStd',
     'addToTimeInv',
     'addToTimeVary',
     'assignParameters',
     'cFunc_terminal_',
     'checkConditions',
     'checkElementsOfTimeVaryAreLists',
     'checkRestrictions',
     'clearHistory',
     'cycles',
     'delFromTimeInv',
     'delFromTimeVary',
     'distance',
     'getAvg',
     'getControls',
     'getMortality',
     'getPostStates',
     'getRfree',
     'getShocks',
     'getStates',
     'initializeSim',
     'makeShockHistory',
     'pLvlInitMean',
     'pLvlInitStd',
     'postSolve',
     'poststate_vars',
     'poststate_vars_',
     'preSolve',
     'pseudo_terminal',
     'quiet',
     'readShocks',
     'read_shocks',
     'resetRNG',
     'seed',
     'shock_vars',
     'shock_vars_',
     'simBirth',
     'simDeath',
     'simOnePeriod',
     'simulate',
     'solution_terminal',
     'solution_terminal_',
     'solve',
     'solveOnePeriod',
     'timeFlip',
     'timeFwd',
     'timeReport',
     'timeRev',
     'time_flow',
     'time_inv',
     'time_inv_',
     'time_vary',
     'time_vary_',
     'tolerance',
     'track_vars',
     'unpackcFunc',
     'updateSolutionTerminal',
     'vFunc_terminal_',
     'verbose']

In python terminology, `IndShockConsumerType` is a **superclass** of `PerfForesightConsumerType`. This means that it builds on the functionality of its parent type (including, for example, the definition of the utility function). You can find the superclasses of a type in Python using the `__bases__` attribute:

```python
from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType

IndShockConsumerType.__bases__
```

    (HARK.ConsumptionSaving.ConsIndShockModel.PerfForesightConsumerType,)

```python
# So, let's create an instance of the IndShockConsumerType
IndShockExample = IndShockConsumerType(**IndShockDictionary)
```

As before, we need to import the relevant subclass of $\texttt{AgentType}$ into our workspace, then create an instance by passing the dictionary to the class as if the class were a function.

## The Discretized Probability Distribution

The scatterplot below shows how the discretized probability distribution is represented in HARK: The lognormal distribution is represented by a set of equiprobable point masses.

```python
# Plot values for equiprobable distribution of permanent shocks

plt.scatter(IndShockExample.PermShkDstn[0][1],
            IndShockExample.PermShkDstn[0][0])
plt.xlabel("Value")
plt.ylabel("Probability Mass")
plt.show()
```

This distribution was created, using the parameters in the dictionary above, when the `IndShockConsumerType` object was initialized.

## Solution by Backwards Induction

HARK solves this problem using _backwards induction_: It will derive a solution for each period ($t$) by finding a mapping between specific values of market resources $\{m[0],m[1],...\}$ and the corresponding optimal consumption $\{c[0],c[1],...\}$. The function that "connects the dots" will be stored in a variable named `cFunc`.

Backwards induction requires a "terminal" (last; final) period to work backwards from.  `IndShockExample` constructed above did not specify a terminal consumption function, and consequently it uses the default terminal function in which all resources are consumed: $c_{T} = m_{t}$.

```python
IndShockExample.solution_terminal
```

    <HARK.ConsumptionSaving.ConsIndShockModel.ConsumerSolution at 0x7fc761001c50>

The consumption function `cFunc` is defined by _piecewise linear interpolation_.

It is defined by a series of $(m,c)$ points on a grid; the value of the function for any $m$ is the $c$ determined by the line connecting the nearest defined gridpoints.

You can see below that in the terminal period, $c = m$; the agent consumes all available resources.

```python
# Plot terminal consumption function
plt.plot(IndShockExample.solution_terminal.cFunc.x_list,
         IndShockExample.solution_terminal.cFunc.y_list,
         color='k')
plt.scatter(IndShockExample.solution_terminal.cFunc.x_list,
            IndShockExample.solution_terminal.cFunc.y_list)
```

The solution also has a representation of a `value function`, the value `v(m)` as a function of available market resources. Because the agent consumes all their resources in the last period, the value function for the terminal solution looks just like the CRRA utility function: $v_{T}(m) = u(m)$.

```python
# Final consumption function c=m
m = np.linspace(0.1,1,100)
plt.plot(m,IndShockExample.solution_terminal.vFunc(m))
```

## Solving the problem

This solution is generated by invoking `solve()` which is a **method** that is an **attribute** of the `IndShockExample` object. **Methods** in Python are supposed to have **documentation** that tell you what they do. You can read the documentation for methods and other attributes in HARK with the built-in Python `help()` function:

```python
help(IndShockExample.solve)
```

    Help on method solve in module HARK.core:

    solve(verbose=False) method of HARK.ConsumptionSaving.ConsIndShockModel.IndShockConsumerType instance
        Solve the model for this instance of an agent type by backward induction.
        Loops through the sequence of one period problems, passing the solution
        from period t+1 to the problem for period t.

        Parameters
        ----------
        verbose : boolean
            If True, solution progress is printed to screen.

        Returns
        -------
        none

### Finite or Infinite Horizon?

$\texttt{ConsIndShockType}$ can solve either finite-horizon (e.g., life-cycle) problems, or infinite-horizon problems (where the problem is the same in every period).  Elsewhere you can find documentation about the finite horizon solution; here we are interested in the infinite-horizon solution which is obtained (by definition) when iterating one more period yields a solution that is essentially the same.  In the dictionary above we signaled to HARK that we want the infinite horizon solution by setting the "cycles" paramter to zero:

```python
IndShockExample.cycles # Infinite horizon solution is computed when cycles = 0
```

    0

```python
# Solve It
IndShockExample.solve(verbose=True) # Verbose prints progress as solution proceeds
```

    Finished cycle #1 in 0.0009517669677734375 seconds, solution distance = 100.0
    Finished cycle #2 in 0.0011010169982910156 seconds, solution distance = 10.088015890333441
    Finished cycle #3 in 0.0010843276977539062 seconds, solution distance = 3.3534114736589693
    Finished cycle #4 in 0.0009758472442626953 seconds, solution distance = 1.669952961389428
    Finished cycle #5 in 0.0015230178833007812 seconds, solution distance = 0.9967360674688486
    Finished cycle #6 in 0.0017900466918945312 seconds, solution distance = 0.6602619046109517
    Finished cycle #7 in 0.002032041549682617 seconds, solution distance = 0.46809484231437537
    Finished cycle #8 in 0.0014519691467285156 seconds, solution distance = 0.34807706501006663
    Finished cycle #9 in 0.0010118484497070312 seconds, solution distance = 0.2681341538834978
    Finished cycle #10 in 0.0008890628814697266 seconds, solution distance = 0.2122324816862755
    Finished cycle #11 in 0.001032114028930664 seconds, solution distance = 0.17162798586899441
    Finished cycle #12 in 0.0011997222900390625 seconds, solution distance = 0.14121714401876417
    Finished cycle #13 in 0.0018072128295898438 seconds, solution distance = 0.11786112023934692
    Finished cycle #14 in 0.0011539459228515625 seconds, solution distance = 0.09954374358267426
    Finished cycle #15 in 0.0013070106506347656 seconds, solution distance = 0.08492077965589928
    Finished cycle #16 in 0.0010509490966796875 seconds, solution distance = 0.07306820983636797
    Finished cycle #17 in 0.0010731220245361328 seconds, solution distance = 0.06333371450893699
    Finished cycle #18 in 0.0011019706726074219 seconds, solution distance = 0.055246317280595036
    Finished cycle #19 in 0.0009510517120361328 seconds, solution distance = 0.04845886926538867
    Finished cycle #20 in 0.0009570121765136719 seconds, solution distance = 0.042711109600137576
    Finished cycle #21 in 0.001049041748046875 seconds, solution distance = 0.037804865822300915
    Finished cycle #22 in 0.0010437965393066406 seconds, solution distance = 0.03358704056809714
    Finished cycle #23 in 0.0010540485382080078 seconds, solution distance = 0.029937835775703636
    Finished cycle #24 in 0.001168966293334961 seconds, solution distance = 0.02676242583398336
    Finished cycle #25 in 0.0010809898376464844 seconds, solution distance = 0.02398495974448922
    Finished cycle #26 in 0.0010461807250976562 seconds, solution distance = 0.021544181039296006
    Finished cycle #27 in 0.0009508132934570312 seconds, solution distance = 0.019390181762535263
    Finished cycle #28 in 0.0008988380432128906 seconds, solution distance = 0.01748196739049135
    Finished cycle #29 in 0.0009710788726806641 seconds, solution distance = 0.015785611379662168
    Finished cycle #30 in 0.0009200572967529297 seconds, solution distance = 0.014272839895088651
    Finished cycle #31 in 0.0009160041809082031 seconds, solution distance = 0.012919936192925086
    Finished cycle #32 in 0.0011010169982910156 seconds, solution distance = 0.011706884785620986
    Finished cycle #33 in 0.0009951591491699219 seconds, solution distance = 0.010616703056517629
    Finished cycle #34 in 0.0012810230255126953 seconds, solution distance = 0.009634898474986997
    Finished cycle #35 in 0.001035928726196289 seconds, solution distance = 0.00874904442068214
    Finished cycle #36 in 0.0009348392486572266 seconds, solution distance = 0.007948413988061898
    Finished cycle #37 in 0.0010099411010742188 seconds, solution distance = 0.00722372447082309
    Finished cycle #38 in 0.0009341239929199219 seconds, solution distance = 0.006566906564932307
    Finished cycle #39 in 0.0010230541229248047 seconds, solution distance = 0.005970916077075117
    Finished cycle #40 in 0.000985860824584961 seconds, solution distance = 0.005429579002497409
    Finished cycle #41 in 0.0009751319885253906 seconds, solution distance = 0.004937463273915643
    Finished cycle #42 in 0.0008919239044189453 seconds, solution distance = 0.004489772052598262
    Finished cycle #43 in 0.0009341239929199219 seconds, solution distance = 0.004082254546442954
    Finished cycle #44 in 0.0008981227874755859 seconds, solution distance = 0.003711131170160087
    Finished cycle #45 in 0.0010197162628173828 seconds, solution distance = 0.003373030500466001
    Finished cycle #46 in 0.000885009765625 seconds, solution distance = 0.0030649359736791837
    Finished cycle #47 in 0.0010480880737304688 seconds, solution distance = 0.0027841406665807256
    Finished cycle #48 in 0.0009770393371582031 seconds, solution distance = 0.0025282088157077
    Finished cycle #49 in 0.0010709762573242188 seconds, solution distance = 0.0022949429754119954
    Finished cycle #50 in 0.0009758472442626953 seconds, solution distance = 0.0020823559119378388
    Finished cycle #51 in 0.000942230224609375 seconds, solution distance = 0.0018886464739757969
    Finished cycle #52 in 0.0009889602661132812 seconds, solution distance = 0.0017121788176539532
    Finished cycle #53 in 0.0010190010070800781 seconds, solution distance = 0.0015514644867238303
    Finished cycle #54 in 0.000904083251953125 seconds, solution distance = 0.0014051468883913287
    Finished cycle #55 in 0.0011577606201171875 seconds, solution distance = 0.0012719878080478253
    Finished cycle #56 in 0.0011050701141357422 seconds, solution distance = 0.0011508556554602478
    Finished cycle #57 in 0.0010859966278076172 seconds, solution distance = 0.001040715183035168
    Finished cycle #58 in 0.0009870529174804688 seconds, solution distance = 0.0009406184572178233
    Finished cycle #59 in 0.0009639263153076172 seconds, solution distance = 0.0008496968979514463
    Finished cycle #60 in 0.0009260177612304688 seconds, solution distance = 0.0007671542298588463
    Finished cycle #61 in 0.0009109973907470703 seconds, solution distance = 0.0006922602130656763
    Finished cycle #62 in 0.0009732246398925781 seconds, solution distance = 0.00062434504219544
    Finished cycle #63 in 0.0008819103240966797 seconds, solution distance = 0.0005627943195669616
    Finished cycle #64 in 0.0008387565612792969 seconds, solution distance = 0.0005070445234869325
    Finished cycle #65 in 0.0008761882781982422 seconds, solution distance = 0.00045657890516936916
    Finished cycle #66 in 0.0009970664978027344 seconds, solution distance = 0.0004109237583840297
    Finished cycle #67 in 0.0009307861328125 seconds, solution distance = 0.0003696450150627584
    Finished cycle #68 in 0.0008831024169921875 seconds, solution distance = 0.0003323451276209255
    Finished cycle #69 in 0.0009019374847412109 seconds, solution distance = 0.0002986602050065734
    Finished cycle #70 in 0.0009551048278808594 seconds, solution distance = 0.0002682573749837047
    Finished cycle #71 in 0.0009250640869140625 seconds, solution distance = 0.00024083234929284103
    Finished cycle #72 in 0.0008687973022460938 seconds, solution distance = 0.00021610717220754694
    Finished cycle #73 in 0.0009531974792480469 seconds, solution distance = 0.00019382813573720625
    Finished cycle #74 in 0.0010759830474853516 seconds, solution distance = 0.0001737638473713332
    Finished cycle #75 in 0.0009999275207519531 seconds, solution distance = 0.0001557034380539335
    Finished cycle #76 in 0.0011141300201416016 seconds, solution distance = 0.00013945489982480908
    Finished cycle #77 in 0.0009200572967529297 seconds, solution distance = 0.00012484354371977702
    Finished cycle #78 in 0.0008566379547119141 seconds, solution distance = 0.00011171056964087711
    Finished cycle #79 in 0.0009541511535644531 seconds, solution distance = 9.991174074253095e-05
    Finished cycle #80 in 0.0009610652923583984 seconds, solution distance = 8.931615549911953e-05
    Finished cycle #81 in 0.0010380744934082031 seconds, solution distance = 7.98051111954301e-05
    Finished cycle #82 in 0.0009188652038574219 seconds, solution distance = 7.127105311699466e-05
    Finished cycle #83 in 0.00102996826171875 seconds, solution distance = 6.361660386744461e-05
    Finished cycle #84 in 0.0008890628814697266 seconds, solution distance = 5.675366786039859e-05
    Finished cycle #85 in 0.0008931159973144531 seconds, solution distance = 5.060260605649347e-05
    Finished cycle #86 in 0.0008687973022460938 seconds, solution distance = 4.5091476412739695e-05
    Finished cycle #87 in 0.0009589195251464844 seconds, solution distance = 4.0155335675251536e-05
    Finished cycle #88 in 0.0008571147918701172 seconds, solution distance = 3.573559836667073e-05
    Finished cycle #89 in 0.0008890628814697266 seconds, solution distance = 3.1779449082502964e-05
    Finished cycle #90 in 0.000865936279296875 seconds, solution distance = 2.8239304256771902e-05
    Finished cycle #91 in 0.0009510517120361328 seconds, solution distance = 2.507231993220671e-05
    Finished cycle #92 in 0.0009131431579589844 seconds, solution distance = 2.2239942116808464e-05
    Finished cycle #93 in 0.0009338855743408203 seconds, solution distance = 1.970749654489623e-05
    Finished cycle #94 in 0.0008361339569091797 seconds, solution distance = 1.7443814824158466e-05
    Finished cycle #95 in 0.0009217262268066406 seconds, solution distance = 1.5420894161621845e-05
    Finished cycle #96 in 0.0008060932159423828 seconds, solution distance = 1.361358793783296e-05
    Finished cycle #97 in 0.0010938644409179688 seconds, solution distance = 1.1999324726730265e-05
    Finished cycle #98 in 0.0008931159973144531 seconds, solution distance = 1.0557853297399333e-05
    Finished cycle #99 in 0.0008471012115478516 seconds, solution distance = 9.271011537581586e-06
    Finished cycle #100 in 0.0007710456848144531 seconds, solution distance = 8.122517190400913e-06
    Finished cycle #101 in 0.0008628368377685547 seconds, solution distance = 7.097778525810838e-06
    Finished cycle #102 in 0.0008351802825927734 seconds, solution distance = 6.183723240127392e-06
    Finished cycle #103 in 0.00084686279296875 seconds, solution distance = 5.368643900993675e-06
    Finished cycle #104 in 0.0008349418640136719 seconds, solution distance = 4.6420585193551744e-06
    Finished cycle #105 in 0.0008552074432373047 seconds, solution distance = 3.994584801603196e-06
    Finished cycle #106 in 0.0008208751678466797 seconds, solution distance = 3.4178268535356437e-06
    Finished cycle #107 in 0.0009191036224365234 seconds, solution distance = 2.9042732037076746e-06
    Finished cycle #108 in 0.0008389949798583984 seconds, solution distance = 2.4472050093038433e-06
    Finished cycle #109 in 0.0009319782257080078 seconds, solution distance = 2.0406135354811283e-06
    Finished cycle #110 in 0.0008518695831298828 seconds, solution distance = 1.6791260115667228e-06
    Finished cycle #111 in 0.0009329319000244141 seconds, solution distance = 1.3579390065743269e-06
    Finished cycle #112 in 0.001390218734741211 seconds, solution distance = 1.0727586690073565e-06
    Finished cycle #113 in 0.0009188652038574219 seconds, solution distance = 8.197470720006095e-07

```python
# plotFuncs([list],min,max) takes a [list] of functions and plots their values over a range from min to max
plotFuncs([IndShockExample.solution[0].cFunc,IndShockExample.solution_terminal.cFunc],0.,10.)
```

## Changing Constructed Attributes

In the parameter dictionary above, we chose values for HARK to use when constructing its numerical representation of $F_t$, the joint distribution of permanent and transitory income shocks. When $\texttt{IndShockExample}$ was created, those parameters ($\texttt{TranShkStd}$, etc) were used by the **constructor** or **initialization** method of $\texttt{IndShockConsumerType}$ to construct an attribute called $\texttt{IncomeDstn}$.

Suppose you were interested in changing (say) the amount of permanent income risk.  From the section above, you might think that you could simply change the attribute $\texttt{TranShkStd}$, solve the model again, and it would work.

That's _almost_ true-- there's one extra step. $\texttt{TranShkStd}$ is a primitive input, but it's not the thing you _actually_ want to change. Changing $\texttt{TranShkStd}$ doesn't actually update the income distribution... unless you tell it to (just like changing an agent's preferences does not change the consumption function that was stored for the old set of parameters -- until you invoke the $\texttt{solve}$ method again).  In the cell below, we invoke the method $\texttt{updateIncomeProcess}$ so HARK knows to reconstruct the attribute $\texttt{IncomeDstn}$.

```python
OtherExample = deepcopy(IndShockExample)  # Make a copy so we can compare consumption functions
OtherExample.PermShkStd = [0.2]           # Double permanent income risk (note that it's a one element list)
OtherExample.updateIncomeProcess()        # Call the method to reconstruct the representation of F_t
OtherExample.solve()
```

    The given parameter values violate the Individual Growth Impatience Condition; the GIFInd is: 1.0220

In the cell below, use your blossoming HARK skills to plot the consumption function for $\texttt{IndShockExample}$ and $\texttt{OtherExample}$ on the same figure.

```python
# Use the remainder of this cell to plot the IndShockExample and OtherExample consumption functions against each other
```

## Buffer Stock Saving?

There are some combinations of parameter values under which problems of the kind specified above have "degenerate" solutions; for example, if consumers are so patient that they always prefer deferring consumption to the future, the limiting consumption rule can be $c(m)=0$.

The toolkit has built-in tests for a number of parametric conditions that can be shown to result in various characteristics in the optimal solution.

Perhaps the most interesting such condition is the ["Growth Impatience Condition"](http://econ.jhu.edu/people/ccarroll/Papers/BufferStockTheory/#GIC): If this condition is satisfied, the consumer's optimal behavior is to aim to achieve a "target" value of $m$, to serve as a precautionary buffer against income shocks.

The tests can be invoked using the `checkConditions()` method:

```python
IndShockExample.checkConditions(verbose=True)
```

    The value of the Perfect Foresight Growth Impatience Factor for the supplied parameter values satisfies the Perfect Foresight Growth Impatience Condition.  Therefore, in the absence of any risk, the ratio of individual wealth to permanent income would fall indefinitely.

    The value of the Individual Growth Impatience Factor for the supplied parameter values satisfies the Individual Growth Impatience Condition. Therefore, a target level of the individual market resources ratio m exists (see http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#onetarget for more).

    The value of the Aggregate Growth Impatience Factor for the supplied parameter values satisfies the Aggregate Growth Impatience Condition. Therefore, it is possible that a target level of the ratio of aggregate market resources to aggregate permanent income exists.

    The Weak Return Impatience Factor value for the supplied parameter values satisfies the Weak Return Impatience Condition (see http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#WRIC for more).

    The Finite Value of Autarky Factor (FVAV) for the supplied parameter values satisfies the Finite Value of Autarky Condition.
    Since both WRIC and FVAC are satisfied, the problem has a nondegenerate solution
    GIFPF            = 0.984539
    GIFInd           = 0.993777
    GIFAgg           = 0.964848
    Thorn = AIF      = 0.994384
    PermGroFacAdj    = 1.000611
    uInvEpShkuInv    = 0.990704
    FVAF             = 0.932054
    WRIF             = 0.213705
    DiscFacGIFIndMax = 0.972061
    DiscFacGIFAggMax = 1.010600