# Writing a SAS Global Forum Paper in Jupyter Lab

This paper outlines how you can submit your SAS Global forum paper directly from Jupyter lab

In the cells below you will see examples of common elements in SAS Global Forum papers

This template leverages [Jupyter Book](https://jupyterbook.org/start/overview.html)

The [MyST Cheat sheet](https://jupyterbook.org/reference/cheatsheet.html) is here

# Setup

# This is a heading

## this is a subheading

This is normal text

**This code is in bold**

This is *italic* text

You can use either underscore `_` or asterisk `*` to augment your text.

A single symbol for italics _underscore_ or *asterisk*

A double symbol for bold __underscore__ or **asterisk**

## To create inline runable code, use a code cell

To create code that is not runable use the single backtick '`' for code that is less than one line long
and triple backtick '```' for blocks of code

Here is an example of a single backtick `data _null_`

Here is an example of a triple backtick
```
proc print data=sashelp.class(obs=10)
run;
```

# Math Equations

$$
a_1 = b_1 + c_1
$$ (my_other_label)

### Numbered equation

```{math}
:label: my_label
w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}

```

To link to those equations you can use code like this:
- A link to an equation directive: {eq}`my_label`
- A link to a dollar math block: {eq}`my_other_label`

In equation \eqref{eq:sample}, we find the value of an
interesting integral:

\begin{equation}
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \label{eq:sample}
\end{equation}

# Figures

# Tables

```sas

```

```{note}
Here is a note
```

```sas

```


# UCL BIOC0016 - Bioinformatics
Alan R. Lowe (a.lowe@ucl.ac.uk)

---

### Bioimage informatics / Machine learning

The aim of the exercises in this notebook is to familiarise you with the different steps of evaluating the performance of a simple convolutional neural network (CNN). You will use a real CNN to classify different cell states using image data provided to you. A more sophisticated version of this neural network has been used in recently published research. The architecture of the network is similar to those you will have learnt about in the lectures.

The network is able to tell whether a cell is proliferating or dead.  This could be useful to understand whether a drug is effective or not, or to understand the normal cellular behaviour.

The CNN has been pre-trained on a dataset of real images from data collected at UCL. This is a very simple CNN, and you are going to assess the performance of it by making predictions of the cell state using image data, and comparing these with your ground truth annotation.  The images that you are provided with are called a 'hold out' set, since they have come from the much larger original data set, but have not been used to train the neural network. The CNN has not 'seen' these data during the training phase, and therefore represent a real test of the performance of the network.

The practical contains following sections:

1. Data annotation
2. Make predictions with a convolutional neural network
3. Compare these with the data that you have annotated
4. Determine the accuracy of the model

### Instructions

Code sections are coloured according to the following scheme:

*
 Code that needs to be written by you.

*
 Code that needs to be edited by you, perhaps changing some parameters.

*
 A task that needs to be completed by you. This may be recording the results.

### IMPORTANT NOTES:

1. This notebook will 'timeout' if you do not interact with it for more than 10-20 minutes. Make sure you save your progress occasionally.
2. If the colors above are not showing, you can run the first line of the notebook.

### Be part of the research project!

This is based on a real research project, and you can contribute to the project by recording your results as part of the practical. You can read more about the research project [here](http://lowe.cs.ucl.ac.uk/cellx.html).

As you complete different sections of the practical, we ask you to complete different sections of a google form. By recording the results, you can contribute to the research programme.

### Further reading
* http://lowe.cs.ucl.ac.uk/cellx.html
* https://en.wikipedia.org/wiki/Bioimage_informatics
* https://teachablemachine.withgoogle.com/train/image
* https://towardsdatascience.com/the-5-classification-evaluation-metrics-you-must-know-aa97784ff226

---
# Beginning of the practical

### If you are using MyBinder, please make sure to run the following cell:

```python
%%html
<link rel="stylesheet" type="text/css" href="./files/style.css">
```

<link rel="stylesheet" type="text/css" href="./files/style.css">

### If you are using Google Colab, please make sure to run the following cell:

```python
if 'google.colab' in str(get_ipython()):
    !git clone https://github.com/quantumjot/BIOC0016-MachineLearning.git
    import sys
    sys.path.append("./BIOC0016-MachineLearning/")
```

---
## PART 1 - Data Annotation

To test how well the neural network is able to perform on unseen images, we need to manually annotate some new and unseen images with a label. We can then analyse how many labels the neural network correctly predicts. In this section, you will be provided with a random sample of unlabeled images. You will need at least 50, although the more the better. Using the guide below, please annotate each image with one of the six labels provided. If you are unsure, use the 'Unknown' label.

Here are some examples of cells and their corresponding labels:

### Set up plotting and import some useful libraries

We first need to load some libraries of code that will help with the data processing and visualization. If you are more experienced at Python, you can read the code for the `helpers` library at [github](https://github.com/quantumjot/BIOC0016-MachineLearning).

```python
%matplotlib inline

import helpers
import numpy as np
```

### Get a sample of images from the test dataset

Next, we need to get a random sample of images from the dataset. The dataset has thousands of images in it. The first command `images = helpers.get_example_images()` uses the `helpers` library to call a function `get_example_images`. This requests images from the dataset and places them in a list called `images`.

The second command `helpers.plot_images()` takes the list of images as an argument and plots them in the notebook, showing the unique ID numbers above them.

```python
images = helpers.get_example_images(num_images=5)
helpers.plot_images(images)
```

 TASK: Try changing the number of images. For the remainder of the practical, you will need at least 50 images.

**NOTE:** If you need to reload a specific set of images, you can use the following command:
```python
image_ID_list = [14,33,6]
images = helpers.get_specific_images(image_ID_list)```

Now that you have your own sample of images, you need to annotate them to create the **ground truth**. You will store these annotations using two Python data structures, a `list` and a `dictionary`.

A `list` is just what it sounds like, a sequence of items in order. A list can have multiple entries (separated by commas), or be empty:
```python
list_of_numbers = [0, 1, 2, 3, 4]
empty_list = []
```

A `dictionary` stores key-value pairs.

The final data structure may look something like this:

```python
annotation = {'interphase': [0,5,3,20],
              'prometaphase': [7,2],
              'metaphase': [14,33,6],
              'anaphase': [508,1],
              'apoptosis': [8],
              'unknown': []}```

    TASK: Complete the annotation dictionary. To do this you will need to make a list of the image numbers, and your best guess as to the label that each image should have. Once you have this, compile the information into a dictionary structure, and run the validation to make sure that the annotation format is correct. If you have correctly formatted the result it will return True. If it is incorrect, it will return a list of errors that need to be corrected.

```python
annotation = {}
```

```python
# validate the annotation
helpers.validate_annotation(annotation)
```

    TASK: If the validation returns True, Complete sections 1 and 2 of the Google form, to record the annotations you have made.

If your annotation has validated `True`, you can now assign these annotations to the images:

```python
helpers.annotate_images(images, annotation)
```

---
## PART 2 - Using the CNN to make predictions

### Loading the pre-trained model

In the following lines of code `helpers.load_CNN_model()` builds the convolutional neural network (CNN) that we will test, and sets the weights and biases using the pretrained values. This model has been trained with thousands of images, but the network is very simple. The goal is to assess the performance of the network.

```python
model = helpers.load_CNN_model()
```

 TASK: Summarise the model and get the number of parameters. Answer the following questions:

How many convolutional layers are there?

How many kernels are used in each convolutional layer?

How many output classes are there?

What is the size of the input image, and after the convolutional layers?

**HINT**: You can use the command `model.summary()` to get the details of the model.

```python

```

### Visualise activations within the network

When the network is given an example, we can visualize the activations within the layes of the network from the sample. To visualize the activations within the network, you can use the `helpers.visualize_layers()` command.

To do this, the function needs three (3) arguments:
```python
helpers.visualize_layers(model, image, layer)
```

You can only use one image at a time, so you need to pass just one image as an argument. You could do this by selecting the first image (0) or the second (1), third (2) etc..:
```python
image = images[0]   # select the first image from the list
```

TASK: visualize the activations within the network for the first and second convolutional layers (layer=0 or layer=1). What can you see?

```python

```

### Make the predictions

Now that we have loaded the pre-trained model. We can feed it the same images that you have labeled and generate predictions for the label. The predictions are returned as the probability of the label given the image data, $\text{P}(\text{label} | \text{data})$. In fact, the model returns the probability for *all* labels given the data.

 TASK: Using the model, make predictions for the images that you have annotated.

```python
images_for_prediction = np.concatenate([im.as_tensor() for im in images], axis=0)
predictions = model.predict(images_for_prediction)
```

You can use the command `print()` to print the predictions to the screen.  Since `predictions` is a 2D array, it also has a shape. You can access the shape information using the `.shape` property.

TASK: Print the predictions to the screen. Why is the output this shape?

```python

```

---
## PART 3 - Compare the predictions with your annotations

We can visualize the images and their predictions using one of the helper functions:

TASK: Print out the predictions from the model using the helper functions below. How well do they compare to the annotations you have made? Which predictions is the model confident in, and which ones are less confident?

**HINT:** You can use the following helper functions to do this:
`helpers.plot_predictions(images, predictions)` and `helpers.print_predictions(images, predictions)`

```python

```

```python

```

### Calculate the confusion matrix

A *confusion matrix* is used to assess how well a multi-label classifier performs. The matrix is square; it has the same number of rows and columns, $n \times n$, where $n$ is the number of classes.

By definition, a confusion matrix $\mathbf{C}$ is such that $\mathbf{C}_{i,j}$ is equal to the number of observations known to be in group $i$ and predicted to be in group $j$.

If the predictions for every label were correct, i.e. all predictions equal the ground truth, then all of the entries will be on the diagonal of the matrix:

TASK:  Use the helper function `calculate_confusion_matrix` to create the confusion matrix.

```python
confusion_matrix = helpers.calculate_confusion_matrix(images, predictions)
```

```python
print(confusion_matrix)
```

```python
confusion_matrix.plot()
```

TASK: Did the model perform better with some labels than others? Which performed best, and which worst?

---
## PART 4 - Calculate the accuracy of the model using your ground truth data

We need to determine the overall accuracy of the model. We will make several simple calculations:

1. **Accuracy** is a single number that captures the fraction of true predictions among the total number of examples examined.
2. **Precision** is the fraction of correct label $i$ out of all instances where the model predicted a label $i$.  Precision is a good metric when we want to be very sure that our model is making correct predictions.
3. **Recall** is the fraction of correct label $i$ out of all instances where the true label is $i$. Recall is a good metric when we want our model to capture as many positives predictions as possible.

It's important to think about all of these metrics. Ideally we would want our model to correctly predict the label (precision) and to label all occurences (recall).

TASK: Calculate the overall accuracy of the model, by counting the total number of correct labels divided by the total number of examples you annotated. Use the information in confusion matrix to help you. How well does the model perform?

```python

```

    TASK:  Complete section 3 of the Google form.

TASK: Try calculating the precision and recall using the confusion matrix.

Precision is the number of correct labels (on the diagonal of the confusion matrix) divided by the sum of the column

Recall is the number of correct labels (on the diagonal of the confusion matrix) divided by the sum of the row

```python

```

#### F1-score

The final metric we will calculate is called the $F_1$-score. This metric tries to balance the precision and recall into a single score, for each label of the classifier.  This is important to enable us to assess the performance of the model and determine whether it works well enough for our purposes, or to compare different models.

We will now write our own function to calculate the $F_1$-score for each of the labels. A *function* is a block of code that takes some inputs (*arguments*) and performs a task, often returning a result. For example, a function that calculates the value of $y$ for a straight line $y=mx+c$ may look something like this:

```python
def straight_line(m, x, c):
    y = m*x + c
    return y
```

The `def` keyword means that we are defining a function. This is followed by the function name, which is `straight_line` in this case, with the arguments `(m, x, c)` in that order. The remainder is simply the calculation. The `return` keyword, returns the result `y` to the user.

One can use the function as follows:
```python
y = straight_line(1, 10, 0)
print(y)
```

which returns the result: `10`

You will need to create a function below, that calculates the $F_{1}$ score, according to the following equation:

\begin{equation}
F_1 = 2 \times \frac{\text{precision} \times \text{recall}}{\text{precision} + \text{recall}}
\end{equation}

TASK: Complete the function below to calculate the F1 score for each class

```python
def F1_score(recall, precision):
    """ Function to calculate the F1-score. You need to complete the function below. """
    score =
    return score
```

TASK: Now perform the calculation by running the code block below. Does the calculation work?

```python
for recall, precision, label in confusion_matrix:
    score = F1_score(recall, precision)
    print(f'{label:<12} --> recall: {recall:.2f}, precision: {precision:.2f}, F1-score: {score:.2f}')
```

The results here demonstrate how well the model is performing, by assessing the $F_1$-score for each class within our dataset. You will see that the model performs better for some classes than others. This may mean that we need to add more training data, improve our annotations, or try alternative neural network architectures. What would you do next?

If you would like to learn more about the research underpinning this practical, you can read more here:
http://lowe.cs.ucl.ac.uk/cellx.html

    TASK: Please download and save your notebook and make sure you have completed the Google form and uploaded your results. If you have any feedback about the practical, please record it in the form.

# End of the practical
---

```python

```


[[source]](../api/alibi.explainers.ale.rst)

# Accumulated Local Effects

## Overview

Accumulated Local Effects (ALE) is a method for computing feature effects based on the paper [Visualizing the Effects of Predictor Variables in Black Box Supervised Learning Models](https://arxiv.org/abs/1612.08468) by Apley and Zhu. The algorithm provides model-agnostic (*black box*) global explanations for classification and regression models on tabular data.

ALE addresses some key shortcomings of [Partial Dependence Plots](https://christophm.github.io/interpretable-ml-book/pdp.html) (PDP), a popular method for estimating first order feature effects. We discuss these limitations and motivate ALE after presenting the method usage.

## Usage

Initialize the explainer by passing a black-box prediction function and optionally a list of feature names and target (class) names for interpretation:

```python
from alibi.explainers import ALE
ale = ALE(predict_fn, feature_names=feature_names, target_names=target_names)
```

Following the initialization, we can immediately produce an explanation given a dataset of instances $X$:

```python
exp = ale.explain(X)
```
The `explain` method has a default argument, `min_bin_points=4`, which determines the number of bins the range of each feature is subdivided into so that the ALE estimate for each bin is made with at least `min_bin_points`. Smaller values can result in less accurate local estimates while larger values can also result in less accurate estimates by averaging across large parts of the feature range.

The result `exp` is an `Explanation` object which contains the following data-related attributes:

 - `ale_values` - a list of arrays of ALE values (one for each feature). Each array can have multiple columns (if the number of targets is >1 as in classification)
 - `constant_value` - the mean prediction over $X$ (zeroth order effects)
 - `ale0` - a list of "centering" values (one for each feature) used by the algorithm to center the `ale_values` around the expected effect for the feature (i.e. the sum of `ale_values` and `ale0` will be the uncentered ALE)
 - `feature_values` - a list of arrays (one for each feature) of feature values at which the ALE values were computed
 - `feature_names` - a list of feature names
 - `target_names` - a list of target names
 - `feature_deciles` - a list of arrays (one for each feature) of the feature deciles

Plotting `ale_values` against `feature_values` recovers the ALE curves. For convenience we include a plotting function `plot_ale` which automatically produces ALE plots using `matplotlib`:

```python
from alibi.explainers import plot_ale
plot_ale(exp)
```

The following is an example ALE plot of a logistic regression model on the Iris dataset (see worked [example](../examples/ale_classification.nblink)):

## Examples
[ALE regression example (Boston house prices)](../examples/ale_regression_boston.nblink)

[ALE classification example (Iris dataset)](../examples/ale_classification.nblink)

## Motivation and definition

The following exposition largely follows [Apley and Zhu (2016)](https://arxiv.org/abs/1612.08468) and [Molnar (2019)](https://christophm.github.io/interpretable-ml-book/ale.html).

Given a predictive model $f(x)$ where $x=(x_1,\dots, x_d)$ is a vector of $d$ features, we are interested in computing the *feature effects* of each feature $x_i$ on the model $f(x)$. A feature effect of feature $x_i$ is some function $g(x_i)$ designed to disentangle the contribution of $x_i$ to the response $f(x)$. To simplify notation, in the following we condiser the $d=2$ case and define the feature effect functions for the first feature $x_1$.

### Partial Dependence

Partial Dependence Plots (PDP) is a very common method for computing feature effects. It is defined as

$$
\text{PD}(x_1) = \mathbb{E}[f(x_1, X_2)] = \int p(x_2)f(x_1, x_2)dx_2,
$$

where $p(x_2)$ is the marginal distribution of $X_2$. To estimate the expectation, we can take the training set $X$ and average the predictions of instances where the first feature for all instances is replaced by $x_1$:

$$
\widehat{\text{PD}}(x_1) = \frac{1}{n}\sum_{j=1}^{n}f(x_1, x_{2, j}).
$$

The PD function attempts to calculate the effect of $x_1$ by averaging the effects of the other feature $x_2$ over it's marginal distribution. This is problematic because by doing so we are averaging predictions of many *out of distribution* instances. For example, if $x_1$ and $x_2$ are a person's height and weight and $f$ predicts some other attribute of the person, then the PD function at a fixed height $x_1$ would average predictions of persons with height $x_1$ *and all possible weights* $x_2$ observed in the training set. Clearly, since height and weight are strongly correlated this would lead to many unrealistic data points. Since the predictor $f$ has not been trained on such impossible data points, the predictions are no longer meaningful. We can say that an implicit assumption motivating the PD approach is that the features are uncorrelated, however this is rarely the case and severely limits the usage of PDP.

An attempt to fix the issue with the PD function is to average over the conditional distribution instead of the marginal which leads to the following feature effect function:

$$
M(x_1) = \mathbb{E}[f(X_1, X_2)\vert X_1=x_1] = \int p(x_2\vert x_1)f(x_1,x_2)dx_2,
$$

where $p(x_2\vert x_1)$ is the conditional distribution of $X_2$. To estimate this function from the training set $X$ we can compute

$$
\widehat{M}(x_1) = \frac{1}{n(x_1)}\sum_{j\in N(x_1)}f(x_1,x_{2,j}),
$$

where $N(x_1)$ is a subset of indices $j$ for which $x_{1,j}$ falls into some small neighbourhood of $x_1$ and $n(x_1)$ is the number of such instances.

While this refinement addresses the issue of the PD function averaging over impossible data points, the use of the $M(x_1)$ function as feature effects remains limited when the features are correlated. To go back to the example with people's height and weight, if we fix the height to be some particular value $x_1$ and calculate the effects according to $M(x_1)$, because of the correlation of height and weight the function value mixes effects of *both* features and estimates the **combined** effect. This is undesirable as we cannot attribute the value of $M(x_1)$ purely to height. Furthermore, suppose height doesn't actually have any effect on the prediction, only weight does. Because of the correlation between height and weight, $M(x_1)$ would still show an effect which can be highly misleading. Concretely, for a model like $f(x_1, x_2)=x_2$ it is possible that $M(x_1)\neq 0$ if $x_1, x_2$ are correlated.

The following plot summarizes the two approaches for estimating the effect of $x_1$ at a particular value when $x_2$ is strongly correlated with $x_1$:

### ALE

ALE solves the problem of mixing effects from different features. As with the function $M(x_1)$, ALE uses the conditional distribution to average over other features, but instead of averaging the predictions directly, it averages *differences in predictions* to block the effect of correlated features. The ALE function is defined as follows:

\begin{align}
\text{ALE}(x_1) &= \int_{\min(x_1)}^{x_1}\mathbb{E}\left[\frac{\partial f(X_1,X_2)}{\partial X_1}\Big\vert X_1=z_1\right]dz_1 - c_1 \\
&= \underbrace{\int_{\min(x_1)}^{x_1}\int p(x_2\vert z_1)\frac{\partial f(z_1, x_2)}{\partial z_1}dx_2dz_1}_{\text{uncentered ALE}} - c_1,
\end{align}
where the constant $c_1$ is chosen such that the resulting ALE values are independent of the point $\min(x_1)$ and have zero mean over the distribution $p(x_1)$.

The term $\dfrac{\partial f(x_1, x_2)}{\partial x_1}$ is called the *local effect* of $x_1$ on $f$. Averaging the local effect over the conditional distribution $p(x_2\vert x_1)$ allows us to isolate the effect of $x_1$ from the effects of other correlated features avoiding the issue of $M$ plots which directly average the predictor $f$. Finally, note that the local effects are integrated over the range of $x_1$, this corresponds to the *accumulated* in ALE. This is done as a means of visualizing the *global* effect of the feature by "piecing together" the calculated local effects.

In practice, we calculate the local effects by finite differences so the predictor $f$ need not be differentiable. Thus, to estimate the ALE from data, we compute the following:

$$
\widehat{\text{ALE}}(x_1)=\underbrace{\sum_{k=1}^{k(x_1)}\frac{1}{n(k)}\sum_{i:x_{1}^{(i)}\in{}N(k)}\left[f(z_{k},x^{(i)}_{\setminus{}1})-f(z_{k-1},x^{(i)}_{\setminus{}1})\right]}_{\text{uncentered ALE}} - c_1.
$$

Here $z_0,z_1,\dots$ is a sufficiently fine grid of the feature $x_1$ (typically quantiles so that each resulting interval contains a similar number of points), $N(k)$ denotes the interval $[z_{k-1}, z_k)$, $n(k)$ denotes the number of points falling into interval $N(k)$ and $k(x_1)$ denotes the index of the interval into which $x_1$ falls into, i.e. $x_1\in [z_{k(x_1)-1}, z_{k(x_1)})$. Finally, the notation $f(z_{k}, x^{(i)}_{\setminus{}1})$ means that for instance $i$ we replace $x_1$ with the value of the right interval end-point $z_k$ (likewise for the left interval end-point using $z_{k-1}$), leaving the rest of the features unchanged, and evaluate the difference of predictions at these points.

The following plot illustrates the ALE estimation process. We have subdivided the feature range of $x_1$ into $5$ bins with roughly the same number of points indexed by $N(k)$. Focusing on bin $N(4)$, for each point falling into this bin, we replace their $x_1$ feature value by the left and right end-points of the interval, $z_3$ and $z_4$. Then we evaluate the difference of the predictions of these points and calculate the average by dividing by the number of points in this interval $n(4)$. We do this for every interval and sum up (accumulate) the results. Finally, to calculate the constant $c_1$, we subtract the expectation over $p(x_1)$ of the calculated uncentered ALE so that the resulting ALE values have mean zero over the distribution $p(x_1)$.

We show the results of ALE calculation for a model $f(x_1, x_2) = 3x_1 + 2x_2^2$. The resulting plots correctly recover the linear effect of $x_1$ and the quadratic effect of $x_2$ on $f$. Note that the ALE is estimated for each interval edge and linearly interpolated in between, for real applications it is important to have a sufficiently fine grid but also one that has enough points into each interval for accurate estimates. The x-axis also shows feature deciles of the feature to help judge in which parts of the feature
space the ALE plot is interpolating more and the estimate might be less trustworthy.

The value of $\text{ALE}(x_i)$ is the main effect of feature $x_i$ as compared to the average prediction for the data. For example, the value of $\text{ALE}(x_1)=0.75$ at $x_1=0.7$, if we sample data from the joint distribution $p(x_1, x_2)$ (i.e. realistic data points) and $x_1=0.7$, then we would expect the first order effect of feature $x_1$ to be $0.75$ higher than the *average* first order effect of this feature. Seeing that the $\text{ALE}(x_1)$ plot crosses zero at $x_1\approx 0.45$, realistic data points with $x_1\approx 0.45$ will have effect on $f$ similar to the average first order effect of $x_1$. For realistic data points with smaller $x_1$, the effect will become negative with respect to the average effect.

Because the model $f(x_1, x_2) = 3x_1 + 2x_2^2$ is explicit and differentiable, we can calculate the ALE functions analytically which gives us even more insight. The partial derivatives are given by $(3, 4x_2)$. Assuming that the conditional distributions $p(x_2\vert x_1)$ and $p(x_1\vert x_2)$ are uniform, the expectations over the conditional distributions are equal to the partial derivatives. Next, we integrate over the range of the features to obtain the *uncentered* ALE functions:

\begin{align}
\text{ALE}_u(x_1) &= \int_{\min(x_1)}^{x_1}3dz_1 = 3x_1 - 3\min(x_1) \\
\text{ALE}_u(x_2) &= \int_{\min(x_2)}^{x_2}4z_2dz_2 = 2x_2^2 - 2\min(x_2)^2.
\end{align}

Finally, to obtaine the ALE functions, we center by setting $c_i = \mathbb{E}(\text{ALE}_u(x_i))$ where the expectation is over the marginal distribution $p(x_i)$:

\begin{align}
\text{ALE}(x_1) &= 3x_1 - 3\min(x_1) - \mathbb{E}(3x_1 - 3\min(x_1)) = 3x_1 - 3\mathbb{E}(x_1) \\
\text{ALE}(x_2) &= 2x_2^2 - 2\min(x_2)^2 - \mathbb{E}(2x_2^2  -2\min(x_2)^2) = 2x_2^2 - 2\mathbb{E}(x_2^2).
\end{align}

This calculation verifies that the ALE curves are the desired feature effects (linear for $x_1$ and quadratic for $x_2$) relative to the mean feature effects across the dataset. In fact if $f$ is additive in the individual features like our toy model, then the ALE main effects recover the correct additive components ([Apley and Zhu (2016)](https://arxiv.org/abs/1612.08468)). Furthermore, for additive models we have the decomposition $f(x) = \mathbb{E}(f(x)) + \sum_{i=1}^{d}\text{ALE}(x_i)$, here the first term which is the average prediction across the dataset $X$ can be thought of as zeroth order effects.


# Theoretical Foundations of Buffer Stock Saving

Generated-Source-REMARK-make

[](https://mybinder.org/v2/gh/econ-ark/REMARK/master?filepath=REMARKs%2FBufferStockTheory%2FBufferStockTheory.ipynb)

This notebook uses the [Econ-ARK/HARK](https://github.com/econ-ark/hark) toolkit to describe the main results and reproduce the figures in the paper [Theoretical Foundations of Buffer Stock Saving](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory).

If you are not familiar with the HARK toolkit, you may wish to browse the ["Gentle Introduction to HARK"](https://mybinder.org/v2/gh/econ-ark/DemARK/master?filepath=Gentle-Intro-To-HARK.ipynb) before continuing (since you are viewing this document, you presumably know a bit about [Jupyter Notebooks](https://jupyter-notebook-beginner-guide.readthedocs.io/en/latest/)).

For instructions on how to install the [Econ-ARK/HARK](https://github.com/econ-ark/hark) toolkit on your computer, please refer to the [QUICK START GUIDE](https://github.com/econ-ark/HARK/blob/master/README.md).

The main HARK tool used here is $\texttt{ConsIndShockModel.py}$, in which agents have CRRA utility and face idiosyncratic shocks to permanent and transitory income.  For an introduction to this module, see the [ConsIndShockModel.ipynb](https://econ-ark.org/notebooks) notebook at the [Econ-ARK](https://econ-ark.org) website.

```python
# This cell does some setup and imports generic tools used to produce the figures

Generator=False # Is this notebook the master or is it generated?
# Import related generic python packages
import numpy as np
from time import clock
mystr = lambda number : "{:.4f}".format(number)

# This is a jupytext paired notebook that autogenerates BufferStockTheory.py
# which can be executed from a terminal command line via "ipython BufferStockTheory.py"
# But a terminal does not permit inline figures, so we need to test jupyter vs terminal
# Google "how can I check if code is executed in the ipython notebook"

from IPython import get_ipython # In case it was run from python instead of ipython
def in_ipynb():
    try:
        if str(type(get_ipython())) == "<class 'ipykernel.zmqshell.ZMQInteractiveShell'>":
            return True
        else:
            return False
    except NameError:
        return False

# Determine whether to make the figures inline (for spyder or jupyter)
# vs whatever is the automatic setting that will apply if run from the terminal
if in_ipynb():
    # %matplotlib inline generates a syntax error when run from the shell
    # so do this instead
    get_ipython().run_line_magic('matplotlib', 'inline')
else:
    get_ipython().run_line_magic('matplotlib', 'auto')

# Import the plot-figure library matplotlib

import matplotlib.pyplot as plt

# In order to use LaTeX to manage all text layout in our figures, we import rc settings from matplotlib.
from matplotlib import rc
plt.rc('font', family='serif')

# LaTeX is huge and takes forever to install on mybinder
# so if it is not installed then do not use it
from distutils.spawn import find_executable
iflatexExists=False
if find_executable('latex'):
    iflatexExists=True

plt.rc('text', usetex= iflatexExists)
plt.rc('text', usetex= iflatexExists)

# The warnings package allows us to ignore some harmless but alarming warning messages
import warnings
warnings.filterwarnings("ignore")

# The tools for navigating the filesystem
import sys
import os

sys.path.insert(0, os.path.abspath('../../lib')) # REMARKs directory is two down from root

from HARK.utilities import plotFuncsDer, plotFuncs
from copy import copy, deepcopy

# Define (and create, if necessary) the figures directory "Figures"
if Generator:
    my_file_path = os.path.dirname(os.path.abspath("BufferStockTheory.ipynb")) # Find pathname to this file:
    Figures_HARK_dir = os.path.join(my_file_path,"Figures/")
    Figures_HARK_dir = os.path.join(my_file_path,"/tmp/Figures/") # Uncomment to make figures outside of git path
    if not os.path.exists(Figures_HARK_dir):
        os.makedirs(Figures_HARK_dir)
```

## [The Problem](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#The-Problem)

The paper defines and calibrates a small set of parameters:

| Parameter | Description | Code | Value |
| :---: | ---         | ---  | :---: |
| $\newcommand{\PermGroFac}{\Gamma}\PermGroFac$ | Permanent Income Growth Factor | $\texttt{PermGroFac}$ | 1.03 |
| $\newcommand{\Rfree}{\mathrm{\mathsf{R}}}\Rfree$ | Interest Factor | $\texttt{Rfree}$ | 1.04 |
| $\newcommand{\DiscFac}{\beta}\DiscFac$ | Time Preference Factor | $\texttt{DiscFac}$ | 0.96 |
| $\newcommand{\CRRA}{\rho}\CRRA$ | Coeﬃcient of Relative Risk Aversion| $\texttt{CRRA}$ | 2 |
| $\newcommand{\UnempPrb}{\wp}\UnempPrb$ | Probability of Unemployment | $\texttt{UnempPrb}$ | 0.005 |
| $\newcommand{\IncUnemp}{\mu}\IncUnemp$ | Income when Unemployed | $\texttt{IncUnemp}$ | 0. |
| $\newcommand{\PermShkStd}{\sigma_\psi}\PermShkStd$ | Std Dev of Log Permanent Shock| $\texttt{PermShkStd}$ | 0.1 |
| $\newcommand{\TranShkStd}{\sigma_\theta}\TranShkStd$ | Std Dev of Log Transitory Shock| $\texttt{TranShkStd}$ | 0.1 |

For a microeconomic consumer with 'Market Resources' (net worth plus current income) $M_{t}$, end-of-period assets $A_{t}$ will be the amount remaining after consumption of $C_{t}$.
\begin{eqnarray}
A_{t}   &=&M_{t}-C_{t}  \label{eq:DBCparts} \\
%B_{t+1}   & = & A_{t} R \notag \\
\end{eqnarray}

The consumer's permanent noncapital income $P$ grows by a predictable factor $\PermGroFac$ and is subject to an unpredictable lognormally distributed multiplicative shock $\mathbb{E}_{t}[\psi_{t+1}]=1$,
\begin{eqnarray}
P_{t+1} & = & P_{t} \PermGroFac \psi_{t+1}
\end{eqnarray}

and actual income is permanent income multiplied by a logormal multiplicative transitory shock, $\mathbb{E}_{t}[\theta_{t+1}]=1$, so that next period's market resources are
\begin{eqnarray}
%M_{t+1} &=& B_{t+1} +P_{t+1}\theta_{t+1},  \notag
M_{t+1} &=& A_{t}R +P_{t+1}\theta_{t+1}.  \notag
\end{eqnarray}

When the consumer has a CRRA utility function $u(c)=\frac{c^{1-\rho}}{1-\rho}$, the paper shows that the problem can be written in terms of ratios of money variables to permanent income, e.g. $m_{t} \equiv M_{t}/P_{t}$, and the Bellman form of [the problem reduces to](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#The-Related-Problem):

\begin{eqnarray*}
v_t(m_t) &=& \max_{c_t}~~ u(c_t) + \beta~\mathbb{E}_{t} [(\Gamma\psi_{t+1})^{1-\rho} v_{t+1}(m_{t+1}) ] \\
& s.t. & \\
a_t &=& m_t - c_t \\
m_{t+1} &=& R/(\Gamma \psi_{t+1}) a_t + \theta_{t+1} \\
\end{eqnarray*}

```python
# Define a parameter dictionary with baseline parameter values

# Set the baseline parameter values
PermGroFac = 1.03
Rfree      = 1.04
DiscFac    = 0.96
CRRA       = 2.00
UnempPrb   = 0.005
IncUnemp   = 0.0
PermShkStd = 0.1
TranShkStd = 0.1
# Import default parameter values
import HARK.ConsumptionSaving.ConsumerParameters as Params

# Make a dictionary containing all parameters needed to solve the model
base_params = Params.init_idiosyncratic_shocks

# Set the parameters for the baseline results in the paper
# using the variable values defined in the cell above
base_params['PermGroFac'] = [PermGroFac]   # Permanent income growth factor
base_params['Rfree']      = Rfree          # Interest factor on assets
base_params['DiscFac']    = DiscFac        # Time Preference Factor
base_params['CRRA']       = CRRA           # Coefficient of relative risk aversion
base_params['UnempPrb']   = UnempPrb       # Probability of unemployment (e.g. Probability of Zero Income in the paper)
base_params['IncUnemp']   = IncUnemp       # Induces natural borrowing constraint
base_params['PermShkStd'] = [PermShkStd]   # Standard deviation of log permanent income shocks
base_params['TranShkStd'] = [TranShkStd]   # Standard deviation of log transitory income shocks

# Some technical settings that are not interesting for our purposes
base_params['LivPrb']       = [1.0]   # 100 percent probability of living to next period
base_params['CubicBool']    = True    # Use cubic spline interpolation
base_params['T_cycle']      = 1       # No 'seasonal' cycles
base_params['BoroCnstArt']  = None    # No artificial borrowing constraint
```

```python
from ConsIndShockModel import IndShockConsumerType
```

```python
# PROBLEM
# The "right" way to do this if you have the latest version of HARK (as of 2019/02/03) cloned
# to your computer, is via the command below; but this version includes some fixes that are
# not in the latest pip installable version of HARK, so we have included the ConsIndShockModel.py
# file directly in the repo

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

## Convergence of the Consumption Rules

[The paper's first figure](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#Convergence-of-the-Consumption-Rules) depicts the successive consumption rules that apply in the last period of life $(c_{T}(m))$, the second-to-last period, and earlier periods under the baseline parameter values given above.

```python
# Create a buffer stock consumer instance by passing the dictionary to the class.
baseEx = deepcopy(IndShockConsumerType(**base_params))
baseEx.cycles = 100   # Make this type have a finite horizon (Set T = 100)
baseEx.solve()        # Solve the model
baseEx.unpackcFunc()  # Make the consumption function easily accessible
```

    This method only checks for the conditions for infinite horizon models with a 1 period cycle

```python
# Plot the different periods' consumption rules.

m1 = np.linspace(0,9.5,1000) # Set the plot range of m
m2 = np.linspace(0,6.5,500)
c_m  = baseEx.cFunc[0](m1)   # c_m can be used to define the limiting inﬁnite-horizon consumption rule here
c_t1 = baseEx.cFunc[-2](m1) # c_t1 defines the second-to-last period consumption rule
c_t5 = baseEx.cFunc[-6](m1) # c_t5 defines the T-5 period consumption rule
c_t10 = baseEx.cFunc[-11](m1)  # c_t10 defines the T-10 period consumption rule
c_t0 = m2                            # c_t0 defines the last period consumption rule
plt.figure(figsize = (12,9))
plt.plot(m1,c_m,color="black")
plt.plot(m1,c_t1,color="black")
plt.plot(m1,c_t5,color="black")
plt.plot(m1,c_t10,color="black")
plt.plot(m2,c_t0,color="black")
plt.xlim(0,11)
plt.ylim(0,7)
plt.text(7,6,r'$c_{T}(m) = 45$ degree line',fontsize = 22,fontweight='bold')
plt.text(9.6,5.3,r'$c_{T-1}(m)$',fontsize = 22,fontweight='bold')
plt.text(9.6,2.6,r'$c_{T-5}(m)$',fontsize = 22,fontweight='bold')
plt.text(9.6,2.1,r'$c_{T-10}(m)$',fontsize = 22,fontweight='bold')
plt.text(9.6,1.7,r'$c(m)$',fontsize = 22,fontweight='bold')
plt.arrow(6.9,6.05,-0.6,0,head_width= 0.1,width=0.001,facecolor='black',length_includes_head='True')
plt.tick_params(labelbottom=False, labelleft=False,left='off',right='off',bottom='off',top='off')
plt.text(0,7.05,"$c$",fontsize = 26)
plt.text(11.1,0,"$m$",fontsize = 26)
# Save the figures in several formats
if Generator:
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncsConverge.png'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncsConverge.jpg'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncsConverge.pdf'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncsConverge.svg'))
```

### PROBLEM: Natural Borrowing Constraint Approaches Artificial Constraint

Show numerically the result that is proven analytically in [The-Liquidity-Constrained-Solution-as-a-Limit](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#The-Liquidity-Constrained-Solution-as-a-Limit), by solving the model for successively smaller values of $\wp$.
   * You need only to solve for the second-to-last period of life to do this
   * You should show the consumption rules for different values of $\wp$ on the same graph
      * To make this easier, you will want to use the plotFuncs command imported by `from HARK.utilities import plotFuncsDer, plotFuncs`

Create a cell or cells in the notebook below this cell and put your solution there

```python
# Setup
%matplotlib inline
import matplotlib.pyplot as plt
import sys
import os
sys.path.insert(0, os.path.abspath('../lib'))
from util import log_progress
import numpy as np
from copy import deepcopy
import HARK
from HARK.utilities import plotFuncs

# Artificial borrowing constraint
baseExTypes = deepcopy(IndShockConsumerType(**base_params))
baseExTypes.cycles = 100   # Make this type have a finite horizon (Set T = 100)
baseExTypes.UnempPrb = 0
baseExTypes.BoroCnstArt  = 0    # Artificial borrowing constraint
baseExTypes.updateIncomeProcess()
baseExTypes.solve()
baseExTypes.unpackcFunc()
c_t = baseExTypes.cFunc[-2](m1) # c_t1 defines the second-to-last period consumption rule

# Plot results
plt.figure(figsize=(10,7))
plt.plot(m1,c_t, 'k--', label='Artificial constraint')

# Creates Consumers with different unemployment probabilities
MybaseExTypes = []
UnempPrb_types = np.array([0.5, 0.05, 0.005, 0.001, 0.0000000001])
nn = len(UnempPrb_types)

for t in range(nn):
    baseExTypes = deepcopy(IndShockConsumerType(**base_params))
    baseExTypes.cycles = 100   # Make this type have a finite horizon (Set T = 100)
    baseExTypes.UnempPrb = UnempPrb_types[t]
    baseExTypes.updateIncomeProcess()
    baseExTypes.seed = t
    MybaseExTypes.append(baseExTypes)

# Solves consumption function for different consumers
for ThisType in MybaseExTypes:
    ThisType.solve()
    ThisType.unpackcFunc()

# Plot results
for t in range(nn):
    c_t = MybaseExTypes[t].cFunc[-2](m1) # c_t1 defines the second-to-last period consumption rule
    plt.plot(m1,c_t, label=UnempPrb_types[t])

plt.xlim(0,2)
plt.ylim(0,1.5)
plt.legend(loc='upper left')
plt.title('Consumption function for different values of $\wp$')
plt.text(0,1.55,"$c$",fontsize = 12)
plt.text(2.05,0,"$m$",fontsize = 12)
plt.show()

```

## Factors and Conditions

### The Finite Human Wealth Condition

Human wealth for a perfect foresight consumer is defined as the present discounted value of future income:

\begin{eqnarray}
H_{t} & = & \mathbb{E}[P_{t} + R^{-1} P_{t+1} + R^{2} P_{t+2} ... ] \\
      & = & P_{t}\mathbb{E}[P_{t} + (\Gamma/R) + (\Gamma/R)^{2} ... ]
\end{eqnarray}
which is an infinite number if $\Gamma/R \geq 1$.  We say that the 'Finite Human Wealth Condition' (FHWC) holds if
$0 \leq (\Gamma/R) \leq 1$.

### Absolute Patience and the AIC

The paper defines an object which it calls the Absolute Patience Factor, equal to the ratio of $C_{t+1}/C_{t}$ for a perfect foresight consumer.  The Old English character "Þ" is used for this object in the paper, but "Þ" cannot currently be rendered conveniently in Jupyter notebooks, so we will substitute $\Phi$ here:

\begin{equation}
\Phi = (R \beta)^{1/\rho}
\end{equation}

If $\Phi = 1$, a perfect foresight consumer will spend exactly the amount that can be sustained perpetually (given their current and future resources).  If $\Phi < 1$ (the consumer is 'absolutely impatient'; or, 'the absolute impatience condition holds'), the consumer is consuming more than the sustainable amount, so consumption will fall, and if the consumer is 'absolutely patient' with $\Phi > 1$ consumption will grow over time.

### Growth Patience and the GIC

For a [perfect foresight consumer](http://econ.jhu.edu/people/ccarroll/public/lecturenotes/consumption/PerfForesightCRRA), whether the ratio of consumption to the permanent component of income $P$ is rising, constant, or falling depends on the relative growth rates of consumption and permanent income, which is measured by the "Perfect Foresight Growth Patience Factor":

\begin{eqnarray}
\Phi_{\Gamma} & = & \Phi/\Gamma
\end{eqnarray}
and whether the ratio is falling or rising over time depends on whether $\Phi_{\Gamma}$ is below or above 1.

An analogous condition can be defined when there is uncertainty about permanent income.  Defining $\tilde{\Gamma} = (\mathbb{E}[\psi^{-1}])^{-1}\Gamma$, the 'Growth Impatience Condition' (GIC) is that
\begin{eqnarray}
  \Phi/\tilde{\Gamma} & < & 1
\end{eqnarray}

### The Finite Value of Autarky Condition (FVAC)

The paper [shows](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#Autarky-Value) that a consumer who planned to spend his permanent income $\{ p_{t}, p_{t+1}, ...\} $ in every period would have value defined by

\begin{equation}
v_{t}^{\text{autarky}} = u(p_{t})\left(\frac{1}{1-\beta \Gamma^{1-\rho} \mathbb{E}[\psi^{1-\rho}]}\right)
\end{equation}

and defines the 'Finite Value of Autarky Condition' as the requirement that the denominator of this expression be a positive finite number:

\begin{equation}
\beta \Gamma^{1-\rho} \mathbb{E}[\psi^{1-\rho}] < 1
\end{equation}

### The Weak Return Impatience Condition (WRIC)

The 'Return Impatience Condition' $\Phi/R < 1$ has long been understood to be required for the perfect foresight model to have a nondegenerate solution (when $\rho=1$, this reduces to $\beta < R$).  If the RIC does not hold, the consumer is so patient that the optimal consumption function approaches zero as the horizon extends.

When the probability of unemployment is $\wp$, the paper articulates an analogous (but weaker) condition:

\begin{eqnarray}
 \wp^{1/\rho} \Phi/R & < & 1
\end{eqnarray}

# Key Results

## Nondegenerate Solution Requires FVAC and WRIC

The central result of the paper is that the conditions required for the model to have a nondegenerate solution ($0 < c(m) < \infty$ for feasible $m$) are that the Finite Value of Autarky (FVAC) and Weak Return Impatience Condition (WRAC) hold.

A [table](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#Sufficient-Conditions-For-Nondegenerate-Solution) puts this result in the context of implications of other conditions and restrictions.

## Natural versus Artificial Borrowing Constraints

Defining $\chi(\wp)$ as the consumption function associated with any particular value of $\wp$, and defining $\hat{\chi}$ as the consumption function that would apply in the absence of the zero-income shocks but in the presence of an 'artificial' borrowing constraint requiring $a \geq 0$, a la Deaton (1991), the paper shows that

\begin{eqnarray}
\lim_{\wp \downarrow 0}~\chi(\wp) & = & \hat{\chi}
\end{eqnarray}

That is, as $\wp$ approaches zero the problem with uncertainty becomes identical to the problem that instead has constraints.  (See [Precautionary Saving and Liquidity Constraints](http://econ.jhu.edu/people/ccarroll/papers/LiqConstr) for a full treatment of the relationship between precautionary saving and liquidity constraints).

## $c(m)$ is Finite Even When Human Wealth Is Infinite

In the perfect foresight model, if $R < \Gamma$ the present discounted value of future labor income is infinite and so the limiting consumption function is $c(m) = \infty$ for all $m$.

The presence of uncertainty changes this: The limiting consumption function is finite for all values of $m$.

This is because uncertainty imposes a "natural borrowing constraint" that deters the consumer from borrowing against their unbounded future labor income.

## If the GIC Holds, $\exists$ a finite 'target' $m$

Section [There Is Exactly One Target $m$ Ratio, Which Is Stable]() shows that, under parameter values for which the limiting consumption function exists, if the GIC holds then there will be a value $\check{m}$ such that:

\begin{eqnarray}
\mathbb{E}[m_{t+1}] & > & m_{t}~\text{if $m_{t} < \check{m}$} \\
\mathbb{E}[m_{t+1}] & < & m_{t}~\text{if $m_{t} > \check{m}$} \\
\mathbb{E}[m_{t+1}] & = & m_{t}~\text{if $m_{t} = \check{m}$}
\end{eqnarray}

## Target Wealth is Infinite if the GIC Fails

The section [The GIC](http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#The-GIC) depicts a solution when the **FVAC** (Finite Value of Autarky Condition) and **WRIC** hold (so that the model has a solution) but the **GIC** (Growth Impatience Condition) fails.  In this case the target wealth ratio is infinity.

The parameter values in this specific example are:

| Param | Description | Code | Value |
| :---: | ---         | ---  | :---: |
| $\Gamma$ | Permanent Income Growth Factor | $\texttt{PermGroFac}$ | 1.00 |
| $\mathrm{\mathsf{R}}$ | Interest Factor | $\texttt{Rfree}$ | 1.08 |

The figure is reproduced below.

```python
# Construct the "GIC fails" example.
GIC_fail_dictionary = dict(base_params)
GIC_fail_dictionary['Rfree']      = 1.08
GIC_fail_dictionary['PermGroFac'] = [1.00]

GICFailExample = IndShockConsumerType(
    cycles=0, # cycles=0 makes this an infinite horizon consumer
    **GIC_fail_dictionary)
```

    The given type violates the absolute impatience condition with the supplied parameter values; the AIF is 1.01823
    The given parameter values violate the growth impatience condition for this consumer type; the GIF is: 1.0088

The $\mathtt{IndShockConsumerType}$ tool automatically checks various parametric conditions, and will give a warning as well as the values of the factors if any conditions fail to be met.

We can also directly check the conditions, in which case results will be a little more verbose by default.

```python
# The checkConditions method does what it sounds like it would
GICFailExample.checkConditions(verbose=True)
```

    The given type violates the absolute impatience condition with the supplied parameter values; the AIF is 1.01823
        Therefore, the absolute amount of consumption is expected to grow over time
    The given parameter values violate the growth impatience condition for this consumer type; the GIF is: 1.0088
        Therefore, a target level of wealth does not exist.
    The weak return impatience factor value for the supplied parameter values satisfies the weak return impatience condition.
    The finite value of autarky factor value for the supplied parameter values satisfies the finite value of autarky condition.

    [!] For more information on the conditions, see Table 3 in "Theoretical Foundations of Buffer Stock Saving" at http://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/

Next we define the function $\mathrm{\mathbb{E}}_{t}[\Delta m_{t+1}]$ that shows the ‘sustainable’ level of spending at which $m$ is expected to remain unchanged.

```python
# Calculate "Sustainable" consumption that leaves expected m unchanged
# In the perfect foresight case, this is just permanent income plus interest income
# A small adjustment is required to take account of the consequences of uncertainty
InvEpShInvAct = np.dot(GICFailExample.PermShkDstn[0][0], GICFailExample.PermShkDstn[0][1]**(-1))
InvInvEpShInvAct = (InvEpShInvAct) ** (-1)
PermGroFacAct = GICFailExample.PermGroFac[0] * InvInvEpShInvAct
ER = GICFailExample.Rfree / PermGroFacAct
Er = ER - 1
mSSfunc = lambda m : 1 + (m-1)*(Er/ER)
```

### PROBLEM: Target Wealth and Impatience

Given the above results proving that there is a finite target $m$ if the Growth Impatience Factor is less than one ('the GIC holds'), but that $m \uparrow \infty$ if the GIC fails, it will not surprise you to learn that the target approaches infinity as the GIF approaches 1.

This problem asks you to verify that proposition by calculating and plotting values of the GIF for a sequence of $\DiscFac$ values that cause the GIF to be closer and closer to 1.

In doing this, it will be useful to know that the GIF is calculated in the $\texttt{checkConditions}$ method.  (Look in $\texttt{ConsIndShockModel.py}$).

```python
# Define GIF as function of beta
GICExample = deepcopy(IndShockConsumerType(**base_params))
GICExample.cycles=0 # cycles=0 makes this an infinite horizon consumer

def GIF_function(x):
    EPermShkInv=np.dot(GICExample.PermShkDstn[0][0],1/GICExample.PermShkDstn[0][1])
    PermGroFacAdj=GICExample.PermGroFac[0]*EPermShkInv
    Thorn=GICExample.LivPrb[0]*(GICExample.Rfree*x)**(1/GICExample.CRRA)
    GIF=Thorn/PermGroFacAdj
    return GIF

# Plot GIF as function of beta
betas = np.linspace(0.96,1.06,10)
GIFs = GIF_function (betas)
ones = np.ones(10)

plt.plot(betas,GIFs, label='GIF')
plt.plot(betas, ones, label='Limit')
plt.legend(loc='upper left')
plt.title('GIF as a function of $\\beta$')
plt.text(1.07,0.9575,"$\\beta$",fontsize = 12)
plt.show()

# Compute target m and GIF as a function of beta
m_list = []
GIF_list=[]
beta_list=[]
beta = 0.96

while GIF_function(beta) < 1-0.008:
    GICExample = deepcopy(IndShockConsumerType(**base_params))
    GICExample.cycles=0 # cycles=0 makes this an infinite horizon consumer
    GICExample.DiscFac = beta
    GICExample.updateIncomeProcess()
    GICExample.solve()
    GICExample.unpackcFunc()
    print("beta = %0.2f and GIF = %0.3f and target m = %0.2f." % (beta, GIF_function(beta), GICExample.solution[0].mNrmSS))
    m_list.append(GICExample.solution[0].mNrmSS)
    GIF_list.append(GIF_function(beta))
    beta_list.append(beta)
    beta += 0.01

color = 'tab:red'
fig, ax1 = plt.subplots()
ax1.set_xlabel('$\\beta$')
ax1.set_ylabel('$m$', color=color)
ax1.plot(beta_list,m_list, color=color)
ax1.tick_params(axis='y', labelcolor=color)

color = 'tab:blue'
ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis
ax2.set_ylabel('GIF', color=color)
ax2.plot(beta_list,GIF_list, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout()  # otherwise the right y-label is slightly clipped

#plt.plot(beta_list,m_list, label='m')
#plt.plot(beta_list,GIF_list, label='GIF')
#plt.legend(loc='upper left')
plt.title('GIF and target $m$ as a function of $\\beta$')
plt.show()
```

```python
# Plot GICFailExample consumption function against the sustainable level of consumption
GICFailExample.solve() # Above, we set up the problem but did not solve it
GICFailExample.unpackcFunc()  # Make the consumption function easily accessible for plotting
m = np.linspace(0,5,1000)
c_m = GICFailExample.cFunc[0](m)
E_m = mSSfunc(m)
plt.figure(figsize = (12,8))
plt.plot(m,c_m,color="black")
plt.plot(m,E_m,color="black")
plt.xlim(0,5.5)
plt.ylim(0,1.6)
plt.text(0,1.63,"$c$",fontsize = 26)
plt.text(5.55,0,"$m$",fontsize = 26)
plt.tick_params(labelbottom=False, labelleft=False,left='off',right='off',bottom='off',top='off')
plt.text(1,0.6,"$c(m_{t})$",fontsize = 18)
plt.text(1.5,1.2,"$\mathrm{\mathsf{E}}_{t}[\Delta m_{t+1}] = 0$",fontsize = 18)
plt.arrow(0.98,0.62,-0.2,0,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(2.2,1.2,0.3,-0.05,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
if Generator:
    plt.savefig(os.path.join(Figures_HARK_dir, 'FVACnotGIC.png'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'FVACnotGIC.jpg'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'FVACnotGIC.pdf'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'FVACnotGIC.svg'))

# This figure reproduces the figure shown in the paper.
# The gap between the two functions actually increases with $m$ in the limit.
```

As a foundation for the remaining figures, we define another instance of the class $\texttt{IndShockConsumerType}$, which has the same parameter values as the instance $\texttt{baseEx}$ defined previously but is solved to convergence (our definition of an infinite horizon agent type)

```python
# cycles=0 tells the solver to find the infinite horizon solution
baseEx_inf = IndShockConsumerType(cycles=0,**base_params)

baseEx_inf.solve()
baseEx_inf.unpackcFunc()
```

### Target $m$, Expected Consumption Growth, and Permanent Income Growth

The next figure is shown in  [Analysis of the Converged Consumption Function](https://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#Analysis-of-the-Converged-Consumption-Function), which shows the expected consumption growth factor $\mathrm{\mathbb{E}}_{t}[c_{t+1}/c_{t}]$ for a consumer behaving according to the converged consumption rule.

The first step of the figure's construction is to calculate the t+1 period expected consumption. We define an auxiliary function to calculate the expectation of t+1 period consumption given t end-of-period assets.

```python
# Define a function to calculate expected consumption
def exp_consumption(a):
    '''
    Taking end-of-period assets as input, return expectation of next period's consumption
    Inputs:
       a: end-of-period assets
    Returns:
       expconsump: next period's expected consumption
    '''
    GrowFactp1 = baseEx_inf.PermGroFac[0]* baseEx_inf.PermShkDstn[0][1]
    Rnrmtp1 = baseEx_inf.Rfree / GrowFactp1
    # end-of-period assets plus normalized returns
    btp1 = Rnrmtp1*a
    # expand dims of btp1 and use broadcasted sum of a column and a row vector
    # to obtain a matrix of possible beginning-of-period assets next period
    mtp1 = np.expand_dims(btp1, axis=1) + baseEx_inf.TranShkDstn[0][1]
    part_expconsumption = GrowFactp1*baseEx_inf.cFunc[0](mtp1).T
    # finish expectation over permanent income shocks by right multiplying with
    # the weights
    part_expconsumption = np.dot(part_expconsumption, baseEx_inf.PermShkDstn[0][0])
    # finish expectation over transitory income shocks by right multiplying with
    # weights
    expconsumption = np.dot(part_expconsumption, baseEx_inf.TranShkDstn[0][0])
    # return expected consumption
    return expconsumption
```

```python
# Calculate the expected consumption growth factor
m1 = np.linspace(1,baseEx_inf.solution[0].mNrmSS,50) # m1 defines the plot range on the left of target m value (e.g. m <= target m)
c_m1 = baseEx_inf.cFunc[0](m1)
a1 = m1-c_m1
exp_consumption_l1 = []
for i in range(len(a1)):
    exp_consumption_tp1 = exp_consumption(a1[i])
    exp_consumption_l1.append(exp_consumption_tp1)

# growth1 defines the values of expected consumption growth factor when m is less than target m
growth1 = np.array(exp_consumption_l1)/c_m1

# m2 defines the plot range on the right of target m value (e.g. m >= target m)
m2 = np.linspace(baseEx_inf.solution[0].mNrmSS,1.9,50)

c_m2 = baseEx_inf.cFunc[0](m2)
a2 = m2-c_m2
exp_consumption_l2 = []
for i in range(len(a2)):
    exp_consumption_tp1 = exp_consumption(a2[i])
    exp_consumption_l2.append(exp_consumption_tp1)

# growth 2 defines the values of expected consumption growth factor when m is bigger than target m
growth2 = np.array(exp_consumption_l2)/c_m2
```

```python
# Define a function to construct the arrows on the consumption growth rate function
def arrowplot(axes, x, y, narrs=15, dspace=0.5, direc='neg',
              hl=0.01, hw=3, c='black'):
    '''
    The function is used to plot arrows given the data x and y.

    Input:
        narrs  :  Number of arrows that will be drawn along the curve

        dspace :  Shift the position of the arrows along the curve.
                  Should be between 0. and 1.

        direc  :  can be 'pos' or 'neg' to select direction of the arrows

        hl     :  length of the arrow head

        hw     :  width of the arrow head

        c      :  color of the edge and face of the arrow head
    '''

    # r is the distance spanned between pairs of points
    r = np.sqrt(np.diff(x)**2+np.diff(y)**2)
    r = np.insert(r, 0, 0.0)

    # rtot is a cumulative sum of r, it's used to save time
    rtot = np.cumsum(r)

    # based on narrs set the arrow spacing
    aspace = r.sum() / narrs

    if direc is 'neg':
        dspace = -1.*abs(dspace)
    else:
        dspace = abs(dspace)

    arrowData = [] # will hold tuples of x,y,theta for each arrow
    arrowPos = aspace*(dspace) # current point on walk along data
                                 # could set arrowPos to 0 if you want
                                 # an arrow at the beginning of the curve

    ndrawn = 0
    rcount = 1
    while arrowPos < r.sum() and ndrawn < narrs:
        x1,x2 = x[rcount-1],x[rcount]
        y1,y2 = y[rcount-1],y[rcount]
        da = arrowPos-rtot[rcount]
        theta = np.arctan2((x2-x1),(y2-y1))
        ax = np.sin(theta)*da+x1
        ay = np.cos(theta)*da+y1
        arrowData.append((ax,ay,theta))
        ndrawn += 1
        arrowPos+=aspace
        while arrowPos > rtot[rcount+1]:
            rcount+=1
            if arrowPos > rtot[-1]:
                break

    for ax,ay,theta in arrowData:
        # use aspace as a guide for size and length of things
        # scaling factors were chosen by experimenting a bit

        dx0 = np.sin(theta)*hl/2.0 + ax
        dy0 = np.cos(theta)*hl/2.0 + ay
        dx1 = -1.*np.sin(theta)*hl/2.0 + ax
        dy1 = -1.*np.cos(theta)*hl/2.0 + ay

        if direc is 'neg' :
            ax0 = dx0
            ay0 = dy0
            ax1 = dx1
            ay1 = dy1
        else:
            ax0 = dx1
            ay0 = dy1
            ax1 = dx0
            ay1 = dy0

        axes.annotate('', xy=(ax0, ay0), xycoords='data',
                xytext=(ax1, ay1), textcoords='data',
                arrowprops=dict( headwidth=hw, frac=1., ec=c, fc=c))
```

```python
# Plot consumption growth as a function of market resources
# Calculate Absolute Patience Factor Phi = lower bound of consumption growth factor
AbsPatientFac = (baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)

fig = plt.figure(figsize = (12,8))
ax = fig.add_subplot(111)
# Plot the Absolute Patience Factor line
ax.plot([0,1.9],[AbsPatientFac,AbsPatientFac],color="black")

# Plot the Permanent Income Growth Factor line
ax.plot([0,1.9],[baseEx_inf.PermGroFac[0],baseEx_inf.PermGroFac[0]],color="black")

# Plot the expected consumption growth factor on the left side of target m
ax.plot(m1,growth1,color="black")

# Plot the expected consumption growth factor on the right side of target m
ax.plot(m2,growth2,color="black")

# Plot the arrows
arrowplot(ax, m1,growth1)
arrowplot(ax, m2,growth2, direc='pos')

# Plot the target m
ax.plot([baseEx_inf.solution[0].mNrmSS,baseEx_inf.solution[0].mNrmSS],[0,1.4],color="black",linestyle="--")
ax.set_xlim(1,2.05)
ax.set_ylim(0.98,1.08)
ax.text(1,1.082,"Growth Rate",fontsize = 26,fontweight='bold')
ax.text(2.055,0.98,"$m_{t}$",fontsize = 26,fontweight='bold')
ax.text(1.9,1.01,"$\mathrm{\mathsf{E}}_{t}[c_{t+1}/c_{t}]$",fontsize = 22,fontweight='bold')
ax.text(baseEx_inf.solution[0].mNrmSS,0.975, r'$\check{m}$', fontsize = 26,fontweight='bold')
ax.tick_params(labelbottom=False, labelleft=False,left='off',right='off',bottom='off',top='off')
ax.text(1.9,0.998,r'$\Phi = (\mathrm{\mathsf{R}}\beta)^{1/\rho}$',fontsize = 22,fontweight='bold')
ax.text(1.9,1.03, r'$\Gamma$',fontsize = 22,fontweight='bold')
if Generator:
    fig.savefig(os.path.join(Figures_HARK_dir, 'cGroTargetFig.png'))
    fig.savefig(os.path.join(Figures_HARK_dir, 'cGroTargetFig.jpg'))
    fig.savefig(os.path.join(Figures_HARK_dir, 'cGroTargetFig.pdf'))
    fig.savefig(os.path.join(Figures_HARK_dir, 'cGroTargetFig.svg'))
```

### The Consumption Function Bounds

The next figure is also shown in [Analysis of the Converged Consumption Function](https://econ.jhu.edu/people/ccarroll/papers/BufferStockTheory/#Analysis-of-the-Converged-Consumption-Function), and illustrates theoretical bounds for the consumption function.

We define two useful variables: lower bound of $\kappa$ (marginal propensity to consume) and limit of $h$ (Human wealth), along with some functions such as limiting perfect foresight consumption functions ($\bar{c}(m)$), $\bar{\bar c}(m)$ and $\underline{c}(m)$.

```python
# Define k_lower, h_inf and perfect foresight consumption function, upper bound of consumption function and lower
# bound of consumption function.
k_lower = 1.0-(baseEx_inf.Rfree**(-1.0))*(baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)
h_inf = (1.0/(1.0-baseEx_inf.PermGroFac[0]/baseEx_inf.Rfree))
conFunc_PF = lambda m: (h_inf -1)* k_lower + k_lower*m
conFunc_upper = lambda m: (1 - baseEx_inf.UnempPrb ** (1.0/baseEx_inf.CRRA)*(baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)/baseEx_inf.Rfree)*m
conFunc_lower = lambda m: (1 -(baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)/baseEx_inf.Rfree) * m
intersect_m = ((h_inf-1)* k_lower)/((1 - baseEx_inf.UnempPrb
            **(1.0/baseEx_inf.CRRA)*(baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)/baseEx_inf.Rfree)-k_lower)
```

```python
# Plot the consumption function and its bounds
x1 = np.linspace(0,25,1000)
x3 = np.linspace(0,intersect_m,300)
x4 = np.linspace(intersect_m,25,700)
cfunc_m = baseEx_inf.cFunc[0](x1)
cfunc_PF_1 = conFunc_PF(x3)
cfunc_PF_2 = conFunc_PF(x4)
cfunc_upper_1 = conFunc_upper(x3)
cfunc_upper_2 = conFunc_upper(x4)
cfunc_lower = conFunc_lower(x1)
plt.figure(figsize = (12,8))
plt.plot(x1,cfunc_m, color="black")
plt.plot(x1,cfunc_lower, color="black",linewidth=2.5)
plt.plot(x3,cfunc_upper_1, color="black",linewidth=2.5)
plt.plot(x4,cfunc_PF_2 , color="black",linewidth=2.5)
plt.plot(x4,cfunc_upper_2 , color="black",linestyle="--")
plt.plot(x3,cfunc_PF_1 , color="black",linestyle="--")
plt.tick_params(labelbottom=False, labelleft=False,left='off',right='off',bottom='off',top='off')
plt.xlim(0,25)
plt.ylim(0,1.12*conFunc_PF(25))
plt.text(0,1.12*conFunc_PF(25)+0.05,"$c$",fontsize = 22)
plt.text(25+0.1,0,"$m$",fontsize = 22)
plt.text(2.5,1,r'$c(m)$',fontsize = 22,fontweight='bold')
plt.text(6,5,r'$\overline{\overline c}(m)= \overline{\kappa}m = (1-\wp^{1/\rho}\Phi_{R})m$',fontsize = 22,fontweight='bold')
plt.text(2.2,3.8, r'$\overline{c}(m) = (m-1+h)\underline{\kappa}$',fontsize = 22,fontweight='bold')
plt.text(9,4.1,r'Upper Bound $ = $ Min $[\overline{\overline c}(m),\overline{c}(m)]$',fontsize = 22,fontweight='bold')
plt.text(7,0.7,r'$\underline{c}(m)= (1-\Phi_{R})m = \underline{\kappa}m$',fontsize = 22,fontweight='bold')
plt.arrow(2.45,1.05,-0.5,0.02,head_width= 0.05,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(2.15,3.88,-0.5,0.1,head_width= 0.05,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(8.95,4.15,-0.8,0.05,head_width= 0.05,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(5.95,5.05,-0.4,0,head_width= 0.05,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(14,0.70,0.5,-0.1,head_width= 0.05,width=0.001,facecolor='black',length_includes_head='True')
if Generator:
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncBounds.png'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncBounds.jpg'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncBounds.pdf'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cFuncBounds.svg'))
```

### The Consumption Function and Target $m$

This figure shows the $\mathrm{\mathbb{E}}_{t}[\Delta m_{t+1}]$ and consumption function $c(m_{t})$, along with the intrsection of these two functions, which defines the target value of $m$

```python
# This just plots objects that have already been constructed
m1 = np.linspace(0,4,1000)
cfunc_m = baseEx_inf.cFunc[0](m1)
mSSfunc = lambda m:(baseEx_inf.PermGroFac[0]/baseEx_inf.Rfree)+(1.0-baseEx_inf.PermGroFac[0]/baseEx_inf.Rfree)*m
mss = mSSfunc(m1)
plt.figure(figsize = (12,8))
plt.plot(m1,cfunc_m, color="black")
plt.plot(m1,mss, color="black")
plt.xlim(0,3)
plt.ylim(0,1.45)
plt.plot([baseEx_inf.solution[0].mNrmSS, baseEx_inf.solution[0].mNrmSS],[0,2.5],color="black",linestyle="--")
plt.tick_params(labelbottom=False, labelleft=False,left='off',right='off',bottom='off',top='off')
plt.text(0,1.47,r"$c$",fontsize = 26)
plt.text(3.02,0,r"$m$",fontsize = 26)
plt.text(2.3,0.95,r'$\mathrm{\mathsf{E}}[\Delta m_{t+1}] = 0$',fontsize = 22,fontweight='bold')
plt.text(2.3,1.1,r"$c(m_{t})$",fontsize = 22,fontweight='bold')
plt.text(baseEx_inf.solution[0].mNrmSS,-0.05, r"$\check{m}$",fontsize = 26)
plt.arrow(2.28,1.12,-0.1,0.03,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(2.28,0.97,-0.1,0.02,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
if Generator:
    plt.savefig(os.path.join(Figures_HARK_dir, 'cRatTargetFig.png'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cRatTargetFig.jpg'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cRatTargetFig.pdf'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'cRatTargetFig.svg'))
```

### Upper and Lower Limits of the Marginal Propensity to Consume

The paper shows that as $m_{t}~\uparrow~\infty$ the consumption function in the presence of risk gets arbitrarily close to the perfect foresight consumption function.  Defining $\underline{\kappa}$ as the perfect foresight model's MPC, this implies that $\lim_{m_{t}~\uparrow~\infty} c^{\prime}(m) = \underline{\kappa}$.

The paper also derives an analytical limit $\bar{\kappa}$ for the MPC as $m$ approaches 0., its bounding value.  Strict concavity of the consumption function implies that the consumption function will be everywhere below a function $\bar{\kappa}m$, and strictly declining everywhere.  The last figure plots the MPC between these two limits.

```python
# The last figure shows the upper and lower limits of the MPC
plt.figure(figsize = (12,8))
# Set the plot range of m
m = np.linspace(0.001,8,1000)

# Use the HARK method derivative to get the derivative of cFunc, and the values are just the MPC
MPC = baseEx_inf.cFunc[0].derivative(m)

# Define the upper bound of MPC
MPCUpper = (1 - baseEx_inf.UnempPrb ** (1.0/baseEx_inf.CRRA)*(baseEx_inf.Rfree*baseEx_inf.DiscFac)**(1.0/baseEx_inf.CRRA)/baseEx_inf.Rfree)

# Define the lower bound of MPC
MPCLower = k_lower

plt.plot(m,MPC,color = 'black')
plt.plot([0,8],[MPCUpper,MPCUpper],color = 'black')
plt.plot([0,8],[MPCLower,MPCLower],color = 'black')
plt.xlim(0,8)
plt.ylim(0,1)
plt.text(1.5,0.6,r'$\kappa(m) \equiv c^{\prime}(m)$',fontsize = 26,fontweight='bold')
plt.text(6,0.87,r'$(1-\wp^{1/\rho}\Phi_{R})\equiv \overline{\kappa}$',fontsize = 26,fontweight='bold')
plt.text(0.5,0.07,r'$\underline{\kappa}\equiv(1-\Phi_{R})$',fontsize = 26,fontweight='bold')
plt.text(8.05,0,"$m$",fontsize = 26)
plt.arrow(1.45,0.61,-0.4,0,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(1.7,0.07,0.2,-0.01,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
plt.arrow(5.95,0.875,-0.2,0.03,head_width= 0.02,width=0.001,facecolor='black',length_includes_head='True')
if Generator:
    plt.savefig(os.path.join(Figures_HARK_dir, 'MPCLimits.png'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'MPCLimits.jpg'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'MPCLimits.pdf'))
    plt.savefig(os.path.join(Figures_HARK_dir, 'MPCLimits.svg'))
```

```python

```


# Monte Carlo Methods: Lab 2

```
from IPython.core.display import HTML
css_file = '../ipython_notebook_styles/ngcmstyle.css'
HTML(open(css_file, "r").read())
```

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

/*
#ipython_notebook::before{content:"My new Title"}
#ipython_notebook img{display:none;}
*/

@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>

Point to note: an electronic copy of the [Frenkel and Smit book is available through the library](http://site.ebrary.com/lib/soton/detail.action?docID=10186686). This lab is based on case study 1 in chapter 3.4 of that book.

## Lennard-Jones fluids

Use a Lennard-Jones potential inside a box size $[0,L]^3$ with a cut-off $r_c = L/2$,

$$
\begin{equation}
U = \begin{cases} 4 \left[ \frac{1}{r^{12}} - \frac{1}{r^6} \right] & r < r_c \\ 0 & r > r_c. \end{cases}
\end{equation}
$$

Include tail corrections (that is, additional energy and pressure terms resulting from the particles outside the cutoff radius) as

$$
\begin{align}
  U^{\text{tail}} & = \frac{8 \pi \rho}{3} \left[ \frac{1}{3} \frac{1}{r_c^9} - \frac{1}{r_c^3} \right] \\
  p^{\text{tail}} & = \frac{16 \pi \rho^2}{3} \left[ \frac{2}{3} \frac{1}{r_c^9} - \frac{1}{r_c^3} \right].
\end{align}
$$

For each configuration we need to compute the pressure using

$$
\begin{equation}
  p = \frac{\rho}{\beta} + \frac{\text{Virial}}{V}
\end{equation}
$$

where

$$
\begin{equation}
  \text{Virial} = \sum_i \sum_{j > i} \vec{f}( \vec{r}_{ij} ) \cdot \vec{r}_{ij}
\end{equation}
$$

where, as usual, $\vec{r}_{ij}$ is the separation between the atoms, $\vec{r}_{ij} = \vec{r}_i - \vec{r}_j$, and the intermolecular force $\vec{f}$ is given by

$$
\begin{align}
  \vec{f}(\vec{r}_{ij}) &= - \nabla U \\
  & = \begin{cases} 24 \left[ 2 \frac{1}{r^{14}} - \frac{1}{r^8} \right] \vec{r}_{ij} & r < r_c \\ \vec{0} & r > r_c \end{cases}
\end{align}
$$

Note that in the reduced coordinates $\beta = T^{-1}$.

## Monte Carlo code

We will be using an $NTV$ approach, keeping the number of particles fixed ($N = 100$), the temperature fixed (either $T=2$ or $T=0.9$) and the volume fixed (indirectly, via the density $\rho = N / V = N L^{-3}$; use $\rho = a/10$ for $a = 1, \dots, 9$). You will need to take at least $10,000$ steps; $20,000$ is better, but in all cases you should test with a smaller number of particles and steps.

For reference we note the solutions, taken from [Johnson, Zollweg and Gubbins](http://www.tandfonline.com/doi/abs/10.1080/00268979300100411) for the pressures at $T=2$ are:

```
p_JZG_T2 = [0.1776, 0.329, 0.489, 0.7, 1.071, 1.75, 3.028, 5.285, 9.12]
```

And for the pressures at $T=0.9$ are (noting that very little data is meaningful here - see the book):

```
p_JZG_T0_9 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.317, 0.23, 2.24]
```

### Efficiency

Note that the sum over all particles scales as $n^2$ where $n$ is the number of particles. As the number of steps the algorithm will need to take will also scale as $n$, this makes the number of calculations at least as bad as $n^3$. This is expensive; if you try the naive approach then you'll have difficulty using more than 50 particles in a moderate time.

Instead we can note that, at each stage, the algorithm will move only one particle. Therefore, if we store not just the locations of the particles but also their pairwise separations, at each step we will only have to modify a small number of the separations. So we can store $r^2_{ij} = \vec{r}_{ij} \cdot \vec{r}_{ij}$ only, for $j > i$, and when perturbing particle $k$ we only need to update the separations $r^2_{ik}$ for $i<k$ and $r^2_{kj}$ for $k<j$.

This should significantly reduce the number of calculations done in each step.

In addition, note that for reasonable behaviour the acceptance rate should be $\sim 40\%$. This depends on the fractional perturbation distance $\Delta$; values $\sim 0.4$ are reasonable when $\rho \sim 0.1$, but values $\sim 0.02$ are reasonable when $\rho \sim 0.9$.

### Results

* Check that the energy has converged to a "constant" state.
* Plot, for fixed temperature, $p$ vs $\rho$. The true solution is given on page 53 of the reference above.
* Plot a histogram of the energies to show that they follow the Boltzmann distribution.

```
%matplotlib inline
import numpy as np
import scipy.constants as const
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
rcParams['figure.figsize'] = (12,6)
```

FEEG6016 Simulation and Modelling/2014/Monte Carlo Lab 2.ipynb


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Promijeni vidljivost <a href="javascript:code_toggle()">ovdje</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Promijeni vidljivost ovdje.

```python
import sympy as sp # Symbolic Python
import numpy as np # Arrays, matrices and corresponding mathematical operations
from IPython.display import Latex, display, Markdown, clear_output # For displaying Markdown and LaTeX code
from ipywidgets import widgets # Interactivity module
from IPython.display import Javascript

# Function for the conversion of array/matrix to LaTeX/Markdown format.
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix moze prikazati najvise dvije dimenzije')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)
```

## Routh i Hurwitz kriteriji stabilnosti

U teoriji sustava upravljanja, kriterij stabilnosti Routh-Hurwitz je matematički test koji se koristi za otkrivanje broja polova prijenosne funkcije zatvorene petlje koji imaju pozitivne realne dijelove. Broj promjena predznaka u prvom stupcu Routh-niza daje broj polova u desnoj polovici kompleksne ravnine. Nužan i dovoljan uvjet za stabilnost linearnog vremenski nepromjenjivog sustava upravljanja jest da svi polovi sustava zatvorene petlje imaju negativne realne dijelove. To znači da u prvom stupcu ne bi trebalo biti promjene predznaka. Sličan kriterij stabilnosti koji se temelji na determinantama sustava naziva se Hurwitzov kriterij.

Polazna točka za određivanje stabilnosti sustava je karakteristični polinom definiran kao:

\begin{equation}
    a_ns^n+a_{n-1}s^{n-1}+...+a_1s+a_0
\end{equation}

U slučaju Routh-ovog kriterija, formira se takozvani Routh-ov niz:

\begin{array}{l|ccccc}
     & 1 & 2 & 3 & 4 & 5 \\
     \hline
    s^n & a_n & a_{n-2} & a_{n-4} & a_{n-6} & \dots \\
    s^{n-1} & a_{n-1} & a_{n-3} & a_{n-5} & a_{n-7} &\dots \\
    s^{n-2} & b_1 & b_2 & b_3 & b_4 & \dots \\
    s^{n-3} & c_1 & c_2 & c_3 & c_4 & \dots \\
    s^{n-4} & d_1 & d_2 & d_3 & d_4 & \dots \\
    \vdots & \vdots & \vdots & \vdots & \vdots & \ddots\\
\end{array}

Koeficijenti u prva dva retka ($a_i$) dobivaju se iz karakterističnog polinoma. Svi ostali utvrđuju se pomoću sljedećih formula:

\begin{array}{cccc}
    \,  \! \! \! \! b_1 \! = \! \frac{a_{n-1}a_{n-2}-a_n a_{n-3}}{a_{n-1}} & \! \!  \! \! \,  \! \! b_2 \!  = \!  \frac{a_{n-1}a_{n-4}-a_n a_{n-5}}{a_{n-1}} & \,  \! \! b_3 \! = \! \frac{a_{n-1}a_{n-6}-a_n a_{n-7}}{a_{n-1}} & \, \! \! \! \! \dots \\
     c_1=\frac{b_1a_{n-3}-a_{n-1} b_2}{b_1} & c_2=\frac{b_1a_{n-5}-a_{n-1}b_3}{b_1} & c_3=\frac{b_1a_{n-7}-a_{n-1}b_4}{b_1} & \, \! \! \! \! \dots \\
     d_1=\frac{c_1 b_2-b_1 c_2}{c_1} & d_2=\frac{c_1 b_3-b_1 c_3}{c_1} & d_3=\frac{c_1 b_4-b_1 c_4}{c_1} & \, \! \! \! \! \dots \\
    \vdots & \vdots & \vdots & \, \! \! \! \! \ddots \\
\end{array}

Ako svi koeficijenti u prvom stupcu ($n+1$ koeficijenti) imaju isti predznak (ili su svi pozitivni ili svi negativni), sustav je stabilan. Broj promjena predznaka u prvom stupcu daje nam broj korijena karakterističnog polinoma koji se nalaze u lijevoj polovici kompleksne ravnine.

U slučaju Hurwitz-ovog kriterija, na temelju karakterističnog polinoma formira se determinanta $\Delta_n$ dimenzija $n\times n$.

\begin{equation}
    \Delta_n=
    \begin{array}{|cccccccc|}
        a_{n-1} & a_{n-3} & a_{n-5} & \dots & \left[ \begin{array}{cc} a_0 & \mbox{if
        }n \mbox{ is odd} \\ a_1 & \mbox{if }n \mbox{ is even} \end{array}
        \right] & 0 & \dots & 0  \\[3mm]
        a_{n} & a_{n-2} & a_{n-4} & \dots & \left[ \begin{array}{cc} a_1 & \mbox{if }n \mbox{ is odd} \\ a_0 & \mbox{if }n \mbox{ is even} \end{array} \right] & 0 & \dots & 0 \\
        0 & a_{n-1} & a_{n-3} & a_{n-5} & \dots &  \dots & \dots & 0 \\
        0 & a_{n} & a_{n-2} & a_{n-4} & \dots &  \dots & \dots & 0 \\
        0 & 0 & a_{n-1} & a_{n-3} & \dots &  \dots & \dots & 0 \\
        0 & 0 & a_{n} & a_{n-2} & \dots &  \dots & \dots & 0 \\
        \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\
        0 & \dots & \dots & \dots & \dots & \dots & \dots & a_0 \\
    \end{array}
\end{equation}

Na temelju determinante $\Delta_n$ formiraju se subdeterminante na glavnoj dijagonali. Subdeterminanta $\Delta_1$ jednaka je

\begin{equation}
    \Delta_1=a_{n-1},
\end{equation}

subdeterminanta $\Delta_2$

\begin{equation}
    \Delta_2=
    \begin{array}{|cc|}
    a_{n-1} & a_{n-3} \\
    a_{n} & a_{n-2} \\
    \end{array},
\end{equation}

i subdeterminanta $\Delta_3$

\begin{equation}
    \Delta_3=
    \begin{array}{|ccc|}
    a_{n-1} & a_{n-3} & a_{n-5} \\
    a_{n} & a_{n-2} & a_{n-4} \\
    0 & a_{n-1} & a_{n-3} \\
    \end{array}.
\end{equation}

Nastavljamo na ovaj način dok ne dođemo do subdeterminante
$\Delta_{n-1}$. Sustav je stabilan ako su sve subdeterminante na glavnoj dijagonali (od $\Delta_1$ do $\Delta_{n-1}$),  kao i determinanta $\Delta_n$, strogo veće od nule.

---

### Kako koristiti ovaj interaktivni primjer?

Definirajte karakteristični polinom navođenjem njegovog reda i upisom odgovarajućih koeficijenata, a zatim odaberite željeni kriterij stabilnosti (Routh ili Hurwitz).

```python
polynomialOrder = input ("Odredite red karakterističnog polinoma (Enter za potvrdu):")
try:
    val = int(polynomialOrder)
except ValueError:
    display(Markdown('Red polinoma mora biti integer vrijednost. Ponovno unesite vrijednost.+'))
display(Markdown('Upišite koeficijente karakterističnog polinoma (upotrijebite $K$ za nedefinirani koeficijent) i potvrdite klikom na "Potvrdi".'))
text=[None]*(int(polynomialOrder)+1)
for i in range(int(polynomialOrder)+1):
    text[i]=widgets.Text(description=('$s^%i$'%(-(i-int(polynomialOrder)))))
    display(text[i])
btn1=widgets.Button(description="Potvrdi")
btnReset=widgets.Button(description="Resetiraj")
display(widgets.HBox((btn1, btnReset)))

btn2=widgets.Button(description="Potvrdi")
w=widgets.Select(
    options=['Routh', 'Hurwitz'],
    rows=3,
    description='Odaberi:',
    disabled=False
)

coef=[None]*(int(polynomialOrder)+1)

def on_button_clickedReset(ev):
    display(Javascript("Jupyter.notebook.execute_cells_below()"))

def on_button_clicked1(btn1):
    clear_output()
    for i in range(int(polynomialOrder)+1):
        if text[i].value=='' or text[i].value=='Upišite koeficijent':
            text[i].value='Upišite koeficijent'
        else:
            try:
                coef[i]=float(text[i].value)
            except ValueError:
                if text[i].value!='' or text[i].value!='Upišite koeficijent':
                    coef[i]=sp.var(text[i].value)
    coef.reverse()
    enacba="$"
    for i in range (int(polynomialOrder),-1,-1):
        if i==int(polynomialOrder):
            enacba=enacba+str(coef[i])+"s^"+str(i)
        elif i==1:
            enacba=enacba+"+"+str(coef[i])+"s"
        elif i==0:
            enacba=enacba+"+"+str(coef[i])+"$"
        else:
            enacba=enacba+"+"+str(coef[i])+"s^"+str(i)
    coef.reverse()
    display(Markdown('Karakteristični polinom:'), Markdown(enacba))
    display(Markdown('Koji kriterij želite koristiti za provjeru stabilnosti (Routh ili Hurwitz)?'))
    display(w)
    display(widgets.HBox((btn2, btnReset)))
    display(out)

def on_button_clicked2(btn2):

    if w.value=='Routh':

        s=np.zeros((len(coef), len(coef)//2+(len(coef)%2)),dtype=object)
        xx=np.zeros((len(coef), len(coef)//2+(len(coef)%2)),dtype=object)
        check_index=0

        if len(s[0]) == len(coef[::2]):
            s[0] = coef[::2]
        elif len(s[0])-1 == len(coef[::2]):
            s[0,:-1] = coef[::2]
        #soda mesta
        if len(s[1]) == len(coef[1::2]):
            s[1] = coef[1::2]
        elif len(s[1])-1 == len(coef[1::2]):
            s[1,:-1] = coef[1::2]

        for i in range(len(s[2:,:])):
            i+=2
            for j in range(len(s[0,0:-1])):
                s[i,j] = (s[i-1,0]*s[i-2,j+1]-s[i-2,0]*s[i-1,j+1]) / s[i-1,0]
                if s[i,0] == 0:
                    epsilon=sp.Symbol('\u03B5')
                    s[i,0] = epsilon
                    check_index=1

        if check_index==1:
            for i in range(len(s)):
                for j in range(len(s[0])):
                    xx[i,j] = sp.limit(s[i,j],epsilon,0)

            positive_check=xx[:,0]>0
            negative_check=xx[:,0]<0
            if all(positive_check)==True:
                with out:
                    clear_output()
                    display(Markdown('Jedan od elemenata u prvom stupcu tablice Routh jednak je 0. Zamjenjujemo ga s $\epsilon$ i promatramo vrijednosti elemenata kada $\epsilon$ prijeđe u nulu.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(s)))
                    display(Markdown('Sustav je stabilan jer su svi elementi u prvom stupcu Routh tablice pozitivni.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(xx)))

            elif all(negative_check)==True:
                with out:
                    clear_output()
                    display(Markdown('Jedan od elemenata u prvom stupcu tablice Routh jednak je 0. Zamjenjujemo ga s $\epsilon$ i promatramo vrijednosti elemenata kada $\epsilon$ prijeđe u nulu.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(s)))
                    display(Markdown('Sustav je stabilan jer su svi elementi u prvom stupcu Routh tablice negativni.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(xx)))
            else:
                with out:
                    clear_output()
                    display(Markdown('Jedan od elemenata u prvom stupcu tablice Routh jednak je 0. Zamjenjujemo ga s $\epsilon$ i promatramo vrijednosti elemenata kada $\epsilon$ prijeđe u nulu.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(s)))
                    display(Markdown('Sustav nije stabilan jer svi elementi u prvom stupcu Routh tablice nemaju isti predznak.'))
                    display(Markdown('Routh tablica $%s$\n' % vmatrix(xx)))

        elif check_index==0:

            if all(isinstance(x, (int,float)) for x in coef):
                positive_check=s[:,0]>0
                negative_check=s[:,0]<0
                if all(positive_check)==True:
                    with out:
                        clear_output()
                        display(Markdown('Sustav je stabilan jer su svi elementi u prvom stupcu Routh tablice pozitivni.'))
                        display(Markdown('Routh tablica $%s$' % vmatrix(s)))
                elif all(negative_check)==True:
                    with out:
                        clear_output()
                        display(Markdown('Sustav je stabilan jer su svi elementi u prvom stupcu Routh tablice negativni.'))
                        display(Markdown('Routh tablica $%s$' % vmatrix(s)))
                else:
                    with out:
                        clear_output()
                        display(Markdown('Sustav nije stabilan jer svi elementi u prvom stupcu Routh tablice nemaju isti predznak.'))
                        display(Markdown('Routh tablica $%s$' % vmatrix(s)))

            else:
                testSign=[]
                for i in range(len(s)):
                    if isinstance(s[i,0],(int,float)):
                        testSign.append(s[i,0]>0)
                solution=[]
                if all(elem == True for elem in testSign):
                    for x in s[:,0]:
                        if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                            solution.append(sp.solve(x>0,K)) # Define the solution for each value of the determinant
                    with out:
                        clear_output()
                        display(Markdown('Routh tablica $%s$' % vmatrix(s)))
                        display(Markdown('Svi poznati koeficijenti u prvom stupcu Routh tablice su pozitivni, stoga je sustav stabilan za:'))
                        print(solution)
                elif all(elem == False for elem in test):
                    for x in s[:,0]:
                        if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                            solution.append(sp.solve(x<0,K)) # Define the solution for each value of the determinant
                    with out:
                        clear_output()
                        display(Markdown('Routh tablica $%s$' % vmatrix(s)))
                        display(Markdown('Svi poznati koeficijenti u prvom stupcu Routh tablice su negativni, stoga je sustav stabilan za:'))
                        print(solution)
                else:
                    with out:
                        display(Markdown('Routh table $%s$' % vmatrix(s)))
                        display(Markdown('Sustav nije stabilan jer se predznaci koeficijenata u prvom stupcu međusobno razlikuju.'))

    elif w.value=='Hurwitz':

        # Check if all the coefficients are numbers or not and preallocate basic determinant.

        if all(isinstance(x, (int,float)) for x in coef):
            determinant=np.zeros([len(coef)-1,len(coef)-1])
        else:
            determinant=np.zeros([len(coef)-1,len(coef)-1],dtype=object)

        # Define the first two rows of the basic determinant.
        for i in range(len(coef)-1):
            try:
                determinant[0,i]=coef[2*i+1]
            except:
                determinant[0,i]=0

        for i in range(len(coef)-1):
            try:
                determinant[1,i]=coef[2*i]
            except:
                determinant[1,i]=0
        # Define the remaining rows of the basic determinant by shifting the first two rows.
        for i in range(2,len(coef)-1):
            determinant[i,:]=np.roll(determinant[i-2,:],1)
            determinant[2:,0]=0

        # Define all the subdeterminants.
        subdet=[];
        for i in range(len(determinant)-1):
            subdet.append(determinant[0:i+1,0:i+1])

        # Append the basic determinant to the subdeterminants' array.
        subdet.append(determinant)

        # Check if all coefficients are numbers.
        if all(isinstance(x, (int,float)) for x in coef):
            det_value=[] # Preallocate array containing values of all determinants.
            for i in range(len(subdet)):
                det_value.append(np.linalg.det(subdet[i])); # Calculate determinant and append the values to det_value.

            if all(i > 0 for i in det_value)==True: # Check if all values in det_value are positive or not.
                with out:
                    clear_output()
                    display(Markdown('Sustav je stabilan jer su sve determinante pozitivne.'))
                    for i in range(len(subdet)):
                        display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
            else:
                with out:
                    clear_output()
                    display(Markdown('Sustav nije stabilan jer sve determinante nisu pozitivne.'))
                    for i in range(len(subdet)):
                        display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
        else:
            subdetSym=[] # Preallocate subdetSym.
            det_value=[] # Preallocate det_value.
            solution=[] # Preallocate solution.
            for i in subdet:
                subdetSym.append(sp.Matrix(i)) # Transform matrix subdet to symbolic.
            for i in range(len(subdetSym)):
                det_value.append(subdetSym[i].det()) # Calculate the value of the determinant.
            testSign=[]
            for i in range(len(det_value)):
                if isinstance(det_value[i],(int,float,sp.numbers.Integer,sp.numbers.Float)):
                    testSign.append(det_value[i]>0)
            if all(elem == True for elem in testSign):
                solution=[]
                for x in det_value:
                    if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                        solution.append(sp.solve(x>0,K)) # Define the solution for each value of the determinant
                with out:
                    clear_output()
                for i in range(len(subdet)):
                    display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
                display(Markdown('Sustav je stabilan za:'))
                print(solution)

            else:
                with out:
                    clear_output()
                    display(Markdown('Sustav nije stabilan jer sve determinante nisu pozitivne.'))
                for i in range(len(subdet)):
                    display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))

global out
out=widgets.Output()

btn3=widgets.Button(description="Resetiraj sve")
w=widgets.Select(
    options=['Routh', 'Hurwitz'],
    rows=3,
    description='Odaberi:',
    disabled=False
)

btn1.on_click(on_button_clicked1)
btn2.on_click(on_button_clicked2)
btnReset.on_click(on_button_clickedReset)
```

Karakteristični polinom:

$3.0s^3+2.0s^2+Ks+1.0$

Koji kriterij želite koristiti za provjeru stabilnosti (Routh ili Hurwitz)?

    Select(description='Odaberi:', options=('Routh', 'Hurwitz'), rows=3, value='Routh')

    HBox(children=(Button(description='Potvrdi', style=ButtonStyle()), Button(description='Resetiraj', style=Butto…

    Output()

$\Delta_{1}=$$\begin{vmatrix}
  2.0\\
\end{vmatrix}$$=2.00000000000000$

$\Delta_{2}=$$\begin{vmatrix}
  2.0 & 1.0\\
  3.0 & K\\
\end{vmatrix}$$=2.0*K - 3.0$

$\Delta_{3}=$$\begin{vmatrix}
  2.0 & 1.0 & 0\\
  3.0 & K & 0\\
  0 & 2.0 & 1.0\\
\end{vmatrix}$$=2.0*K - 3.0$

Sustav je stabilan za:

    [(1.5 < K) & (K < oo), (1.5 < K) & (K < oo)]