diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6643e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.ipynb_checkpoints +/_build +*/.ipynb_checkpoints/* +*/_build/* diff --git a/README.md b/README.md index 9b549ab..b3befef 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,9 @@ -Economic Dynamics, Theory and Computation -============================================ +# Economic Dynamics, Theory and Computation -This is the code repository for the text [Economic Dynamics, Theory and Computation](http://johnstachurski.net/personal/edtc.html) by [John Stachurski](http://johnstachurski.net). +This is the GH repo for the second edition of +[Economic Dynamics, Theory and Computation](http://johnstachurski.net/personal/edtc.html) by [John Stachurski](http://johnstachurski.net). -### About - -This code repository is written in conjuction with the text [Economic -Dynamics, Theory and Computation](http://johnstachurski.net/personal/edtc.html). The Python code -contained in this repository is explained in detail in the text. The MATLAB -code provides equivalents to the Python code for those who prefer MATLAB. - -### Downloading the Repository - -Either - -* Click the 'Zip' button at the top of this page, or -* Use [Git](https://help.github.com) to clone the repository +The repo contains the source for the corresponding [Jupyter Book](https://jstac.github.io/edtc-code/intro.html) +If you find problems, please feel free to get in touch via the issue tracker. +All feedback is appreciated. diff --git a/code_book/.ipynb_checkpoints/Untitled-checkpoint.ipynb b/code_book/.ipynb_checkpoints/Untitled-checkpoint.ipynb new file mode 100644 index 0000000..7fec515 --- /dev/null +++ b/code_book/.ipynb_checkpoints/Untitled-checkpoint.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/code_book/.ipynb_checkpoints/ch4-checkpoint.ipynb b/code_book/.ipynb_checkpoints/ch4-checkpoint.ipynb new file mode 100644 index 0000000..80129ad --- /dev/null +++ b/code_book/.ipynb_checkpoints/ch4-checkpoint.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "xmin, xmax = -10.0, 10.0\n", + "ymin, ymax = -5.0, 5.0\n", + "\n", + "\n", + "A1 = np.asarray([[0.55, -0.6],\n", + " [0.5, 0.4]])\n", + "\n", + "def f(x, y): \n", + " return A1 @ (x, y)\n", + "\n", + "def draw_arrow(x, y, ax):\n", + " eps = 0.75\n", + " v1, v2 = f(x, y)\n", + " nrm = np.sqrt(v1**2 + v2**2)\n", + " scale = eps / nrm\n", + " ax.arrow(x, y, scale * v1, scale * v2,\n", + " antialiased=True, \n", + " alpha=0.4,\n", + " head_length=0.025*(xmax - xmin), \n", + " head_width=0.012*(xmax - xmin),\n", + " fill=False)\n", + "\n", + "xgrid = np.linspace(xmin * 1.1, xmax * 0.95, 8)\n", + "ygrid = np.linspace(ymin * 1.1, ymax * 0.95, 8)\n", + "\n", + "fig, ax = plt.subplots()\n", + "\n", + "ax.set_xlim(xmin, xmax)\n", + "ax.set_ylim(ymin, ymax)\n", + "\n", + "ax.set_xticks((0,))\n", + "ax.set_yticks((0,))\n", + "ax.grid()\n", + "\n", + "for x in xgrid:\n", + " for y in ygrid:\n", + " draw_arrow(x, y, ax)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "formats": "ipynb,md:myst", + "main_language": "python", + "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.ipynb b/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.ipynb new file mode 100644 index 0000000..3388617 --- /dev/null +++ b/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.ipynb @@ -0,0 +1,616 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Kolmogorov Backward Equation\n", + "\n", + "## Overview \n", + "\n", + "As models become more complex, deriving analytical representations of the\n", + "transition semigroup $(P_t)$ becomes harder.\n", + "\n", + "This is analogous to the idea that solutions to continuous time models often\n", + "lack analytical solutions.\n", + "\n", + "For example, when studying deterministic paths in continuous time,\n", + "infinitesimal descriptions (ODEs and PDEs) are often more intuitive and easier\n", + "to write down than the associated solutions.\n", + "\n", + "(This is one of the shining insights of mathematics, beginning with the work\n", + "of great scientists such as Isaac Newton.)\n", + "\n", + "We will see in this lecture that the same is true for continuous time Markov\n", + "chains.\n", + "\n", + "To help us focus on intuition in this lecture, rather than technicalities, the state space is assumed to be finite, with $|S|=n$.\n", + "\n", + "Later we will investigate the case where $|S| = \\infty$.\n", + "\n", + "We will use the following imports" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy as sp\n", + "import matplotlib.pyplot as plt\n", + "import quantecon as qe\n", + "from numba import njit\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## State Dependent Jump Intensities\n", + "\n", + "As we have seen, continuous time Markov chains jump between states, and hence can \n", + "have the form\n", + "\n", + "$$\n", + " X_t = \\sum_{k \\geq 0} Y_k \\mathbb 1\\{J_k \\leq t < J_{k+1}\\}\n", + " \\qquad (t \\geq 0)\n", + "$$ \n", + "\n", + "where $(J_k)$ are jump times and $(Y_k)$ are the states at each jump.\n", + "\n", + "(We are assuming that $J_k \\to \\infty$ with probability one, so that $X_t$ is well\n", + "defined for all $t \\geq 0$, but this is always true for when holding times are exponential and the state space is finite.)\n", + "\n", + "In the {doc}`previous lecture `, \n", + "\n", + "* the sequence $(Y_k)$ was drawn from a Markov kernel $K$ and called the embedded jump chain, while\n", + "* the holding times $W_k := J_k - J_{k-1}$ were IID and Exp$(\\lambda)$ for some\n", + "constant jump intensity $\\lambda$.\n", + "\n", + "In this lecture, we will generalize by allowing the jump intensity to vary\n", + "with the state.\n", + "\n", + "This difference sounds minor but in fact it will allow us to reach full generality\n", + "in our description of continuous time Markov chains, as\n", + "clarified below.\n", + "\n", + "### Motivation\n", + "\n", + "As a motivating example, recall {ref}`the inventory model `,\n", + "where we assumed that the wait time for the next customer was equal\n", + "to the wait time for new inventory.\n", + "\n", + "This assumption was made purely for convenience and seems unlikely to hold true.\n", + "\n", + "When we relax it, the jump intensities depend on the state.\n", + "\n", + "\n", + "### Algorithmic Construction\n", + "\n", + "We start with two primitives\n", + "\n", + "1. A Markov kernel $K$ on $S$ satisfying $K(x, x) = 0$ for all $x \\in S$\n", + " and\n", + "1. A function $\\lambda$ mapping $S$ to $(0, \\infty)$.\n", + "\n", + "The process $(X_t)$ \n", + "\n", + "* starts at state $x$, \n", + "* waits there for an exponential time $W$ with rate $\\lambda(x)$ and then\n", + "* updates to a new state $y$ drawn from $K(x, \\cdot)$.\n", + "\n", + "Now we take $y$ as the new state for the process and repeat.\n", + "\n", + "More explicitly, assuming initial condition $\\psi$, we\n", + "\n", + "1. draw $Y_0$ from $\\psi$, set $J_0 = 0$ and $n=1$.\n", + "1. Draw $W_n$ independently from Exp$(\\lambda(Y_{n-1}))$.\n", + "1. Set $J_n = J_{n-1} + W_n$.\n", + "1. Set $X_t = Y_{n-1}$ for $t$ in $[J_{n-1}, J_n)$.\n", + "1. Draw $Y_n$ from $K(Y_{n-1}, \\cdot)$.\n", + "1. Set $n = n+1$ and go to step 2.\n", + "\n", + "The sequence $(W_n)$ is drawn as an IID sequence and $(W_n)$ and $(Y_n)$ are\n", + "drawn independently.\n", + "\n", + "The restriction $K(x,x) = 0$ for all $x$ implies that $(X_t)$ actually jumps at each jump time.\n", + "\n", + "\n", + "\n", + "## Computing the Semigroup\n", + "\n", + "For the jump process $(X_t)$ with time varying intensities described in the\n", + "algorithm just given, calculating the transition semigroup is not a trivial exercise.\n", + "\n", + "The approach we adopt is\n", + "\n", + "1. use probabilistic reasoning to obtain an integral equation that the\n", + " semigroup must satisfy.\n", + "1. Convert the integral equation into a differential equation that is easier\n", + " to work with.\n", + "1. Solve this differential equation to obtain the transition semigroup $(P_t)$.\n", + "\n", + "\n", + "The differential equation in question has a special name: the Kolmogorov backward equation.\n", + "\n", + "\n", + "### An Integral Equation\n", + "\n", + "The integral equation referred to above is\n", + "\n", + "$$\n", + " P_t(x, y) = e^{-t \\lambda(x)} I(x, y)\n", + " + \\lambda(x) \n", + " \\int_0^t (K P_{t-\\tau})(x, y) e^{- \\tau \\lambda(x)} d \\tau\n", + "$$ (kbinteg)\n", + "\n", + "which, we claim, holds for all $t \\geq 0$ and $x, y$ in $S$.\n", + "\n", + "Here $(P_t)$ is the transition semigroup of $(X_t)$, the process constructed\n", + "algorithmically above, while \n", + "$K P_{t-\\tau}$ is the product of two Markov kernels as previously\n", + "defined.\n", + "\n", + "Let's see why {eq}`kbinteg` holds.\n", + "\n", + "Conditioning implicitly on $X_0 = x$, the semigroup $(P_t)$ must satisfy \n", + "\n", + "$$\n", + " P_t(x, y) \n", + " = \\mathbb P\\{X_t = y\\}\n", + " = \\mathbb P\\{X_t = y, \\; J_1 > t \\}\n", + " + \\mathbb P\\{X_t = y, \\; J_1 \\leq t \\}\n", + "$$ (pt_split)\n", + "\n", + "\n", + "Regarding the first term on the right hand side of {eq}`pt_split`, we have \n", + "\n", + "$$\n", + " \\mathbb P\\{X_t = y, \\; J_1 > t \\}\n", + " = I(x, y) P\\{J_1 > t \\}\n", + " = I(x, y) e^{- t \\lambda(x)}\n", + "$$ (pt_first)\n", + "\n", + "where $I(x, y) = \\mathbb 1\\{x = y\\}$.\n", + "\n", + "For the second term on the right hand side of {eq}`pt_split`, we have \n", + "\n", + "$$\n", + " \\mathbb P\\{X_t = y, \\; J_1 > t \\}\n", + " = \\mathbb E \n", + " \\left[\n", + " \\mathbb 1\\{J_1 > t\\} \\mathbb P\\{X_t = y \\,|\\, W_1, Y_1\\}\n", + " \\right]\n", + " = \\mathbb E \n", + " \\left[\n", + " \\mathbb 1\\{J_1 > t\\} P_{t - J_1} (Y_1, y) \n", + " \\right]\n", + "$$\n", + "\n", + "Evaluating the expectation and using the independence of $J_1$ and $Y_1$, this becomes\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + " \\mathbb P\\{X_t = y, \\; J_1 > t \\}\n", + " & = \\int_0^\\infty\n", + " \\mathbb 1\\{\\tau > t\\}\n", + " \\sum_z K(x, z) P_{t - \\tau} (z, y) \\lambda(x) e^{-\\tau \\lambda(x)} \n", + " d \\tau\n", + " \\\\\n", + " & = \\lambda(x)\n", + " \\int_0^t\n", + " \\sum_z K(x, z) P_{t - \\tau} (z, y) e^{-\\tau \\lambda(x)} \n", + " d \\tau\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "Combining this result with {eq}`pt_split` and {eq}`pt_first` gives\n", + "{eq}`kbinteg`.\n", + "\n", + "\n", + "\n", + "### Kolmogorov's Differential Equation\n", + "\n", + "We have now confirmed that the semigroup $(P_t)$ associated with the process\n", + "$(X_t)$ should satisfy {eq}`kbinteg`.\n", + "\n", + "\n", + "Equation {eq}`kbinteg` is important but we can simplify it further without\n", + "losing information by taking the time derivative.\n", + "\n", + "Doing so leads to the **Kolmogorov backward equation**, which is, for this\n", + "model\n", + "\n", + "$$\n", + " P'_t = Q P_t \n", + " \\quad \\text{where } \\;\n", + " Q(x, y) := \\lambda(x) (K(x, y) - I(x, y))\n", + "$$ (kolbackeq)\n", + "\n", + "The derivative on the left hand side of {eq}`kolbackeq` is taken element by\n", + "element, with respect to $t$, so that\n", + "\n", + "$$\n", + " P'_t(x, y) = \\left( \\frac{d}{dt} P_t(x, y) \\right)\n", + " \\qquad ((x, y) \\in S \\times S)\n", + "$$\n", + "\n", + "The proof that differentiating {eq}`kbinteg` yields {eq}`kolbackeq` is an\n", + "important exercise (see below).\n", + "\n", + "\n", + "\n", + "### Exponential Solution\n", + "\n", + "The Kolmogorov backward equation is a matrix-valued differential equation.\n", + "\n", + "Recall that, for a scalar differential equation $y'_t = a y_t$ with constant\n", + "$a$ and initial condition $y_0$, the solution is $y_t = e^{ta} y_0$.\n", + "\n", + "This, along with $P_0 = I$, encourages us to guess that the solution to\n", + "Kolmogorov's backward equation {eq}`kolbackeq` is\n", + "\n", + "$$\n", + " P_t = e^{t Q} \n", + "$$ (expsol)\n", + "\n", + "where the right hand side is the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential), with definition \n", + "\n", + "$$\n", + " e^{tQ} \n", + " = \\sum_{k \\geq 0} \\frac{1}{k!} (tQ)^k\n", + " = I + tQ + \\frac{t^2}{2!} Q^2 + \\cdots\n", + "$$ (expofun)\n", + "\n", + "Working element by element, it is not difficult to confirm that \n", + "the derivative of the exponential function $t \\mapsto e^{tQ}$ is\n", + "\n", + "$$\n", + " \\frac{d}{dt} e^{t Q} = Q e^{t Q} = e^{t Q} Q\n", + "$$ (expoderiv)\n", + "\n", + "Hence, differentiating {eq}`expsol` gives $P'_t = Q e^{t Q} = Q P_t$, which\n", + "convinces us that the exponential solution satisfies {eq}`kolbackeq`. \n", + "\n", + "Notice that our solution\n", + "\n", + "$$\n", + " P_t = e^{t Q} \n", + " \\quad \\text{where } \\;\n", + " Q(x, y) := \\lambda(x) (K(x, y) - I(x, y))\n", + "$$ (psolq)\n", + "\n", + "for the semigroup of the jump process $(X_t)$ associated with the jump kernel\n", + "$K$ and the jump intensity function $\\lambda \\colon S \\to (0, \\infty)$ is\n", + "consistent with our earlier result.\n", + "\n", + "In particular, we {ref}`showed ` that, for the model with\n", + "constant jump intensity $\\lambda$, we have $P_t = e^{t \\lambda (K - I)}$.\n", + "\n", + "This is obviously a special case of {eq}`psolq`.\n", + "\n", + "\n", + "\n", + "## Properties of the Solution\n", + "\n", + "Let's investigate further the properties of the exponential solution.\n", + "\n", + "### Checking the Transition Semigroup Properties\n", + "\n", + "While we have confirmed that $(P_t)$ defined by $P_t = e^{t Q}$ solves the\n", + "Kolmogorov backward equation, we have not yet shown that this solution is in\n", + "fact a transition semigroup.\n", + "\n", + "Let's now tie up this loose end.\n", + "\n", + "First\n", + "\n", + "$$\n", + " \\sum_y Q(x, y) \n", + " = \\lambda(x) \\sum_y (K(x, y) - I(x, y))\n", + " = 0\n", + "$$\n", + "\n", + "Hence, if $1$ is a column vector of ones, then $Q 1$ is the\n", + "zero vector.\n", + "\n", + "This implies that $Q^k 1 = 0$ for all $k$ and, as a result, for any $t \\geq\n", + "0$,\n", + "\n", + "$$\n", + " P_t 1\n", + " = e^{tQ} 1\n", + " = I1 + tQ1 + \\frac{t^2}{2!} Q^2 1 + \\cdots\n", + " = I1 = 1\n", + "$$\n", + "\n", + "We have now shown that each $P_t$ has unit row sums, so all that remains to\n", + "check is positivity of all elements (which can easily fail for matrix\n", + "exponentials).\n", + "\n", + "To this end, adopting an argument from {cite}`stroock2013introduction`, we\n", + "set $m := \\max_x \\lambda(x)$ and $\\hat P := I + Q / m$.\n", + "\n", + "It is not difficult to check that $\\hat P$ is a Markov matrix and $Q = m( \\hat\n", + "P - I)$.\n", + "\n", + "Recalling that, for matrix exponentials, $e^{A+B} = e^A e^B$ whenever $AB =\n", + "BA$, we have\n", + "\n", + "$$\n", + " e^{tQ} \n", + " = e^{tm (\\hat P - I)} \n", + " = e^{-tm I} e^{tm \\hat P}\n", + " = e^{-tm} \n", + " \\left( \n", + " I + tm \\hat P + \\frac{(tm)^2}{2!} \\hat P^2 + \\cdots\n", + " \\right)\n", + "$$\n", + "\n", + "It is clear from this representation that all entries of $e^{tQ}$ are\n", + "nonnegative.\n", + "\n", + "We can now be reassured that our solution to the Kolmogorov backward equation\n", + "is indeed a transition semigroup.\n", + "\n", + "\n", + "### Uniqueness\n", + "\n", + "Might there be another, entirely different transition semigroup that also\n", + "satisfies the Kolmogorov backward equation?\n", + "\n", + "The answer is no --- linear ODEs with constant coefficients and fixed initial\n", + "conditions (in this case $P_0 = I$) have unique solutions.\n", + "\n", + "In fact it's not hard to supply a proof --- see the exercises.\n", + "\n", + "\n", + "\n", + "## Application: The Inventory Model\n", + "\n", + "Let us look at a modified version of the inventory model where jump\n", + "intensities depend on the state.\n", + "\n", + "In particular, the wait time for new inventory will now be exponential at rate\n", + "$\\gamma$.\n", + "\n", + "The arrival rate for customers will still be denoted by $\\lambda$ and allowed\n", + "to differ from $\\gamma$.\n", + "\n", + "For parameters we take" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "α = 0.6\n", + "λ = 0.5\n", + "γ = 0.1\n", + "b = 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our plan is to investigate the distribution $\\psi_T$ of $X_T$ at $T=30$.\n", + "\n", + "We will do this by simulating many independent draws of $X_T$ and\n", + "histogramming them.\n", + "\n", + "(In the exercises you are asked to calculate $\\psi_T$ a different way, via\n", + "{eq}`psolq`.)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "@njit\n", + "def draw_X(T, X_0, max_iter=5000):\n", + " \"\"\"\n", + " Generate one draw of X_T given X_0.\n", + " \"\"\"\n", + "\n", + " J, Y = 0, X_0\n", + " m = 0\n", + "\n", + " while m < max_iter:\n", + " s = 1/γ if Y == 0 else 1/λ\n", + " W = np.random.exponential(scale=s) # W ~ E(λ)\n", + " J += W\n", + " if J >= T:\n", + " return Y\n", + " # Otherwise update Y\n", + " if Y == 0:\n", + " Y = b\n", + " else:\n", + " U = np.random.geometric(α)\n", + " Y = Y - min(Y, U)\n", + " m += 1\n", + "\n", + "\n", + "\n", + "@njit\n", + "def independent_draws(T=10, num_draws=100):\n", + "\n", + " draws = np.empty(num_draws, dtype=np.int64)\n", + "\n", + " for i in range(num_draws):\n", + " X_0 = np.random.binomial(b+1, 0.25)\n", + " draws[i] = draw_X(T, X_0)\n", + "\n", + " return draws\n" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "T = 30\n", + "n = b + 1 \n", + "draws = independent_draws(T, num_draws=100_000)\n", + "fig, ax = plt.subplots()\n", + "\n", + "ax.bar(range(n), [np.mean(draws == i) for i in range(n)], width=0.8, alpha=0.6)\n", + "ax.set(xlabel=\"inventory\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " exponential representation of semigroup to push densities forward.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Ex: Use algo to simulate forward to time $t$. Histogram. Compare.\n", + "\n", + "\n", + "\n", + "## Exercises\n", + "\n", + "### Exercise 1\n", + "\n", + "Prove that differentiating {eq}`kbinteg` at each $(x, y)$ yields {eq}`kolbackeq`.\n", + "\n", + "### Exercise 2\n", + "\n", + "We claimed above that the solution $P_t = e^{t Q}$ is the unique\n", + "transition semigroup satisfying the backward equation $P'_t = Q P_t$.\n", + "\n", + "Try to supply a proof.\n", + "\n", + "(This is not an easy exercise but worth thinking about in any case.)\n", + "\n", + "### Solution to Exercise 2\n", + "\n", + "Here is one proof of uniqueness.\n", + "\n", + "Suppose that $(\\hat P_t)$ is another transition semigroup satisfying \n", + "$P'_t = Q P_t$.\n", + "\n", + "Fix $t > 0$ and let $V_s$ be defined by $V_s = P_s \\hat P_{t-s}$ for all $s\n", + "\\geq 0$.\n", + "\n", + "Note that $V_0 = \\hat P_t$ and $V_t = P_t$.\n", + "\n", + "Note also that $s \\mapsto V_s$ is differentiable, with derivative\n", + "\n", + "$$\n", + " V'_s \n", + " = P'_s \\hat P_{t-s} - P_s \\hat P'_{t-s}\n", + " = P_s Q \\hat P_{t-s} - P_s Q \\hat P_{t-s}\n", + " = 0\n", + "$$\n", + "\n", + "where, in the second last equality, we used {eq}`expoderiv`.\n", + "\n", + "\n", + "Hence $V_s$ is constant, so our previous observations $V_0 = \\hat P_t$ and $V_t = P_t$\n", + "now yield $\\hat P_t = P_t$.\n", + "\n", + "Since $t$ was arbitrary, the proof is now done.\n", + "\n", + "\n", + "\n", + "## Solutions\n", + "\n", + "### Solution to Exercise 1\n", + "\n", + "One can easily verify that, when $f$ is a differentiable function and $\\alpha >\n", + "0$, we have\n", + "\n", + "$$\n", + " g(t) = e^{- t \\alpha} f(t)\n", + " \\quad \\implies \\quad\n", + " g'(t) = e^{- t \\alpha} f'(t) - \\alpha g(t)\n", + "$$ (gdiff)\n", + "\n", + "Note also that, with the change of variable $s = t - \\tau$, we can rewrite\n", + "{eq}`kbinteg` as\n", + "\n", + "$$\n", + " P_t(x, y) = \n", + " e^{-t \\lambda(x)} \n", + " \\left\\{ \n", + " I(x, y)\n", + " + \\lambda(x) \n", + " \\int_0^t (K P_s)(x, y) e^{s \\lambda(x)} d s\n", + " \\right\\}\n", + "$$ (kbinteg2)\n", + "\n", + "Applying {eq}`gdiff` yields\n", + "\n", + "$$\n", + " P'_t(x, y) \n", + " = e^{-t \\lambda(x)} \n", + " \\left\\{ \n", + " \\lambda(x) \n", + " (K P_t)(x, y) e^{t \\lambda(x)} \n", + " \\right\\}\n", + " - \\lambda(x) P_t(x, y)\n", + "$$\n", + "\n", + "After minor rearrangements this becomes\n", + "\n", + "$$\n", + " P'_t(x, y) \n", + " = \\lambda(x) [ (K - I) P_t](x, y) \n", + "$$\n", + "\n", + "which is identical to {eq}`kolbackeq`." + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md:myst" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.md b/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.md new file mode 100644 index 0000000..1dd4719 --- /dev/null +++ b/code_book/.ipynb_checkpoints/kolmogorov_bwd-checkpoint.md @@ -0,0 +1,551 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: '0.9' + jupytext_version: 1.5.0 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Kolmogorov Backward Equation + +## Overview + +As models become more complex, deriving analytical representations of the +transition semigroup $(P_t)$ becomes harder. + +This is analogous to the idea that solutions to continuous time models often +lack analytical solutions. + +For example, when studying deterministic paths in continuous time, +infinitesimal descriptions (ODEs and PDEs) are often more intuitive and easier +to write down than the associated solutions. + +(This is one of the shining insights of mathematics, beginning with the work +of great scientists such as Isaac Newton.) + +We will see in this lecture that the same is true for continuous time Markov +chains. + +To help us focus on intuition in this lecture, rather than technicalities, the state space is assumed to be finite, with $|S|=n$. + +Later we will investigate the case where $|S| = \infty$. + +We will use the following imports + +```{code-cell} ipython3 +import numpy as np +import scipy as sp +import matplotlib.pyplot as plt +import quantecon as qe +from numba import njit +``` + +## State Dependent Jump Intensities + +As we have seen, continuous time Markov chains jump between states, and hence can +have the form + +$$ + X_t = \sum_{k \geq 0} Y_k \mathbb 1\{J_k \leq t < J_{k+1}\} + \qquad (t \geq 0) +$$ + +where $(J_k)$ are jump times and $(Y_k)$ are the states at each jump. + +(We are assuming that $J_k \to \infty$ with probability one, so that $X_t$ is well +defined for all $t \geq 0$, but this is always true for when holding times are exponential and the state space is finite.) + +In the {doc}`previous lecture `, + +* the sequence $(Y_k)$ was drawn from a Markov kernel $K$ and called the embedded jump chain, while +* the holding times $W_k := J_k - J_{k-1}$ were IID and Exp$(\lambda)$ for some +constant jump intensity $\lambda$. + +In this lecture, we will generalize by allowing the jump intensity to vary +with the state. + +This difference sounds minor but in fact it will allow us to reach full generality +in our description of continuous time Markov chains, as +clarified below. + +### Motivation + +As a motivating example, recall {ref}`the inventory model `, +where we assumed that the wait time for the next customer was equal +to the wait time for new inventory. + +This assumption was made purely for convenience and seems unlikely to hold true. + +When we relax it, the jump intensities depend on the state. + + +### Algorithmic Construction + +We start with two primitives + +1. A Markov kernel $K$ on $S$ satisfying $K(x, x) = 0$ for all $x \in S$ + and +1. A function $\lambda$ mapping $S$ to $(0, \infty)$. + +The process $(X_t)$ + +* starts at state $x$, +* waits there for an exponential time $W$ with rate $\lambda(x)$ and then +* updates to a new state $y$ drawn from $K(x, \cdot)$. + +Now we take $y$ as the new state for the process and repeat. + +More explicitly, assuming initial condition $\psi$, we + +1. draw $Y_0$ from $\psi$, set $J_0 = 0$ and $n=1$. +1. Draw $W_n$ independently from Exp$(\lambda(Y_{n-1}))$. +1. Set $J_n = J_{n-1} + W_n$. +1. Set $X_t = Y_{n-1}$ for $t$ in $[J_{n-1}, J_n)$. +1. Draw $Y_n$ from $K(Y_{n-1}, \cdot)$. +1. Set $n = n+1$ and go to step 2. + +The sequence $(W_n)$ is drawn as an IID sequence and $(W_n)$ and $(Y_n)$ are +drawn independently. + +The restriction $K(x,x) = 0$ for all $x$ implies that $(X_t)$ actually jumps at each jump time. + + + +## Computing the Semigroup + +For the jump process $(X_t)$ with time varying intensities described in the +algorithm just given, calculating the transition semigroup is not a trivial exercise. + +The approach we adopt is + +1. use probabilistic reasoning to obtain an integral equation that the + semigroup must satisfy. +1. Convert the integral equation into a differential equation that is easier + to work with. +1. Solve this differential equation to obtain the transition semigroup $(P_t)$. + + +The differential equation in question has a special name: the Kolmogorov backward equation. + + +### An Integral Equation + +The integral equation referred to above is + +$$ + P_t(x, y) = e^{-t \lambda(x)} I(x, y) + + \lambda(x) + \int_0^t (K P_{t-\tau})(x, y) e^{- \tau \lambda(x)} d \tau +$$ (kbinteg) + +which, we claim, holds for all $t \geq 0$ and $x, y$ in $S$. + +Here $(P_t)$ is the transition semigroup of $(X_t)$, the process constructed +algorithmically above, while +$K P_{t-\tau}$ is the product of two Markov kernels as previously +defined. + +Let's see why {eq}`kbinteg` holds. + +Conditioning implicitly on $X_0 = x$, the semigroup $(P_t)$ must satisfy + +$$ + P_t(x, y) + = \mathbb P\{X_t = y\} + = \mathbb P\{X_t = y, \; J_1 > t \} + + \mathbb P\{X_t = y, \; J_1 \leq t \} +$$ (pt_split) + + +Regarding the first term on the right hand side of {eq}`pt_split`, we have + +$$ + \mathbb P\{X_t = y, \; J_1 > t \} + = I(x, y) P\{J_1 > t \} + = I(x, y) e^{- t \lambda(x)} +$$ (pt_first) + +where $I(x, y) = \mathbb 1\{x = y\}$. + +For the second term on the right hand side of {eq}`pt_split`, we have + +$$ + \mathbb P\{X_t = y, \; J_1 > t \} + = \mathbb E + \left[ + \mathbb 1\{J_1 > t\} \mathbb P\{X_t = y \,|\, W_1, Y_1\} + \right] + = \mathbb E + \left[ + \mathbb 1\{J_1 > t\} P_{t - J_1} (Y_1, y) + \right] +$$ + +Evaluating the expectation and using the independence of $J_1$ and $Y_1$, this becomes + +$$ +\begin{aligned} + \mathbb P\{X_t = y, \; J_1 > t \} + & = \int_0^\infty + \mathbb 1\{\tau > t\} + \sum_z K(x, z) P_{t - \tau} (z, y) \lambda(x) e^{-\tau \lambda(x)} + d \tau + \\ + & = \lambda(x) + \int_0^t + \sum_z K(x, z) P_{t - \tau} (z, y) e^{-\tau \lambda(x)} + d \tau +\end{aligned} +$$ + +Combining this result with {eq}`pt_split` and {eq}`pt_first` gives +{eq}`kbinteg`. + + + +### Kolmogorov's Differential Equation + +We have now confirmed that the semigroup $(P_t)$ associated with the process +$(X_t)$ should satisfy {eq}`kbinteg`. + + +Equation {eq}`kbinteg` is important but we can simplify it further without +losing information by taking the time derivative. + +Doing so leads to the **Kolmogorov backward equation**, which is, for this +model + +$$ + P'_t = Q P_t + \quad \text{where } \; + Q(x, y) := \lambda(x) (K(x, y) - I(x, y)) +$$ (kolbackeq) + +The derivative on the left hand side of {eq}`kolbackeq` is taken element by +element, with respect to $t$, so that + +$$ + P'_t(x, y) = \left( \frac{d}{dt} P_t(x, y) \right) + \qquad ((x, y) \in S \times S) +$$ + +The proof that differentiating {eq}`kbinteg` yields {eq}`kolbackeq` is an +important exercise (see below). + + + +### Exponential Solution + +The Kolmogorov backward equation is a matrix-valued differential equation. + +Recall that, for a scalar differential equation $y'_t = a y_t$ with constant +$a$ and initial condition $y_0$, the solution is $y_t = e^{ta} y_0$. + +This, along with $P_0 = I$, encourages us to guess that the solution to +Kolmogorov's backward equation {eq}`kolbackeq` is + +$$ + P_t = e^{t Q} +$$ (expsol) + +where the right hand side is the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential), with definition + +$$ + e^{tQ} + = \sum_{k \geq 0} \frac{1}{k!} (tQ)^k + = I + tQ + \frac{t^2}{2!} Q^2 + \cdots +$$ (expofun) + +Working element by element, it is not difficult to confirm that +the derivative of the exponential function $t \mapsto e^{tQ}$ is + +$$ + \frac{d}{dt} e^{t Q} = Q e^{t Q} = e^{t Q} Q +$$ (expoderiv) + +Hence, differentiating {eq}`expsol` gives $P'_t = Q e^{t Q} = Q P_t$, which +convinces us that the exponential solution satisfies {eq}`kolbackeq`. + +Notice that our solution + +$$ + P_t = e^{t Q} + \quad \text{where } \; + Q(x, y) := \lambda(x) (K(x, y) - I(x, y)) +$$ (psolq) + +for the semigroup of the jump process $(X_t)$ associated with the jump kernel +$K$ and the jump intensity function $\lambda \colon S \to (0, \infty)$ is +consistent with our earlier result. + +In particular, we {ref}`showed ` that, for the model with +constant jump intensity $\lambda$, we have $P_t = e^{t \lambda (K - I)}$. + +This is obviously a special case of {eq}`psolq`. + + + +## Properties of the Solution + +Let's investigate further the properties of the exponential solution. + +### Checking the Transition Semigroup Properties + +While we have confirmed that $(P_t)$ defined by $P_t = e^{t Q}$ solves the +Kolmogorov backward equation, we have not yet shown that this solution is in +fact a transition semigroup. + +Let's now tie up this loose end. + +First + +$$ + \sum_y Q(x, y) + = \lambda(x) \sum_y (K(x, y) - I(x, y)) + = 0 +$$ + +Hence, if $1$ is a column vector of ones, then $Q 1$ is the +zero vector. + +This implies that $Q^k 1 = 0$ for all $k$ and, as a result, for any $t \geq +0$, + +$$ + P_t 1 + = e^{tQ} 1 + = I1 + tQ1 + \frac{t^2}{2!} Q^2 1 + \cdots + = I1 = 1 +$$ + +We have now shown that each $P_t$ has unit row sums, so all that remains to +check is positivity of all elements (which can easily fail for matrix +exponentials). + +To this end, adopting an argument from {cite}`stroock2013introduction`, we +set $m := \max_x \lambda(x)$ and $\hat P := I + Q / m$. + +It is not difficult to check that $\hat P$ is a Markov matrix and $Q = m( \hat +P - I)$. + +Recalling that, for matrix exponentials, $e^{A+B} = e^A e^B$ whenever $AB = +BA$, we have + +$$ + e^{tQ} + = e^{tm (\hat P - I)} + = e^{-tm I} e^{tm \hat P} + = e^{-tm} + \left( + I + tm \hat P + \frac{(tm)^2}{2!} \hat P^2 + \cdots + \right) +$$ + +It is clear from this representation that all entries of $e^{tQ}$ are +nonnegative. + +We can now be reassured that our solution to the Kolmogorov backward equation +is indeed a transition semigroup. + + +### Uniqueness + +Might there be another, entirely different transition semigroup that also +satisfies the Kolmogorov backward equation? + +The answer is no --- linear ODEs with constant coefficients and fixed initial +conditions (in this case $P_0 = I$) have unique solutions. + +In fact it's not hard to supply a proof --- see the exercises. + + + +## Application: The Inventory Model + +Let us look at a modified version of the inventory model where jump +intensities depend on the state. + +In particular, the wait time for new inventory will now be exponential at rate +$\gamma$. + +The arrival rate for customers will still be denoted by $\lambda$ and allowed +to differ from $\gamma$. + +For parameters we take + +```{code-cell} ipython3 +α = 0.6 +λ = 0.5 +γ = 0.1 +b = 10 +``` + +Our plan is to investigate the distribution $\psi_T$ of $X_T$ at $T=30$. + +We will do this by simulating many independent draws of $X_T$ and +histogramming them. + +(In the exercises you are asked to calculate $\psi_T$ a different way, via +{eq}`psolq`.) + +```{code-cell} ipython3 +@njit +def draw_X(T, X_0, max_iter=5000): + """ + Generate one draw of X_T given X_0. + """ + + J, Y = 0, X_0 + m = 0 + + while m < max_iter: + s = 1/γ if Y == 0 else 1/λ + W = np.random.exponential(scale=s) # W ~ E(λ) + J += W + if J >= T: + return Y + # Otherwise update Y + if Y == 0: + Y = b + else: + U = np.random.geometric(α) + Y = Y - min(Y, U) + m += 1 + + +@njit +def independent_draws(T=10, num_draws=100): + + draws = np.empty(num_draws, dtype='int') + + for i in range(num_draws): + X_0 = np.random.binomial(b+1, 0.25) + draws[i] = draw_X(T, X_0) + + return draws +``` + +```{code-cell} ipython3 +T = 30 +draws = independent_draws(T, num_draws=1000) + +fig, ax = plt.subplots() + +ax.hist(draws, density=True, align='left', width=0.8, alpha=0.6) +ax.set(xlabel="inventory") + +plt.show() +``` + +```{code-cell} ipython3 +draws[draws > 9] +``` + + exponential representation of semigroup to push densities forward. + + + + + +Ex: Use algo to simulate forward to time $t$. Histogram. Compare. + + + +## Exercises + +### Exercise 1 + +Prove that differentiating {eq}`kbinteg` at each $(x, y)$ yields {eq}`kolbackeq`. + +### Exercise 2 + +We claimed above that the solution $P_t = e^{t Q}$ is the unique +transition semigroup satisfying the backward equation $P'_t = Q P_t$. + +Try to supply a proof. + +(This is not an easy exercise but worth thinking about in any case.) + +### Solution to Exercise 2 + +Here is one proof of uniqueness. + +Suppose that $(\hat P_t)$ is another transition semigroup satisfying +$P'_t = Q P_t$. + +Fix $t > 0$ and let $V_s$ be defined by $V_s = P_s \hat P_{t-s}$ for all $s +\geq 0$. + +Note that $V_0 = \hat P_t$ and $V_t = P_t$. + +Note also that $s \mapsto V_s$ is differentiable, with derivative + +$$ + V'_s + = P'_s \hat P_{t-s} - P_s \hat P'_{t-s} + = P_s Q \hat P_{t-s} - P_s Q \hat P_{t-s} + = 0 +$$ + +where, in the second last equality, we used {eq}`expoderiv`. + + +Hence $V_s$ is constant, so our previous observations $V_0 = \hat P_t$ and $V_t = P_t$ +now yield $\hat P_t = P_t$. + +Since $t$ was arbitrary, the proof is now done. + + + +## Solutions + +### Solution to Exercise 1 + +One can easily verify that, when $f$ is a differentiable function and $\alpha > +0$, we have + +$$ + g(t) = e^{- t \alpha} f(t) + \quad \implies \quad + g'(t) = e^{- t \alpha} f'(t) - \alpha g(t) +$$ (gdiff) + +Note also that, with the change of variable $s = t - \tau$, we can rewrite +{eq}`kbinteg` as + +$$ + P_t(x, y) = + e^{-t \lambda(x)} + \left\{ + I(x, y) + + \lambda(x) + \int_0^t (K P_s)(x, y) e^{s \lambda(x)} d s + \right\} +$$ (kbinteg2) + +Applying {eq}`gdiff` yields + +$$ + P'_t(x, y) + = e^{-t \lambda(x)} + \left\{ + \lambda(x) + (K P_t)(x, y) e^{t \lambda(x)} + \right\} + - \lambda(x) P_t(x, y) +$$ + +After minor rearrangements this becomes + +$$ + P'_t(x, y) + = \lambda(x) [ (K - I) P_t](x, y) +$$ + +which is identical to {eq}`kolbackeq`. diff --git a/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.ipynb b/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.ipynb new file mode 100644 index 0000000..2c7dafb --- /dev/null +++ b/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.ipynb @@ -0,0 +1,599 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The Kolmogorov Forward Equation\n", + "\n", + "\n", + "\n", + "## Overview\n", + "\n", + "In this lecture we approach continuous time Markov chains from a more\n", + "analytical perspective.\n", + "\n", + "The emphasis will be on describing distribution flows through vector-valued\n", + "differential equations.\n", + "\n", + "These distribution flows show how the time $t$ distribution associated with a\n", + "given Markov chain $(X_t)$ changes over time.\n", + "\n", + "Density flows will be identified by ordinary differential equations in vector\n", + "space that are linear and time homogeneous.\n", + "\n", + "We will see that the solutions of these flows are described by Markov\n", + "semigroups.\n", + "\n", + "This leads us back to the theory we have already constructed -- some care will\n", + "be taken to clarify all the connections.\n", + "\n", + "In order to avoid being distracted by technicalities, we continue to defer our\n", + "treatment of infinite state spaces, assuming throughout this lecture that $|S|\n", + "= n$.\n", + "\n", + "As before, $\\mathcal D$ is the set of all distributions on $S$.\n", + "\n", + "We will use the following imports" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy as sp\n", + "import matplotlib.pyplot as plt\n", + "import quantecon as qe\n", + "from numba import njit\n", + "from scipy.linalg import expm\n", + "\n", + "from matplotlib import cm\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from mpl_toolkits.mplot3d.art3d import Poly3DCollection\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## From Difference Equations to ODEs\n", + "\n", + "{ref}`Previously ` we generated this figure, which shows how distributions evolve over time for the inventory model under a certain parameterization:\n", + "\n", + "```{glue:figure} flow_fig\n", + ":name: \"flow_fig\"\n", + "\n", + "Probability flows for the inventory model.\n", + "```\n", + "\n", + "(Hot colors indicate early dates and cool colors denote later dates.)\n", + "\n", + "We also learned how this flow can be described, at least indirectly,\n", + "through the Kolmogorov backward equation, which is an ODE.\n", + "\n", + "In this section we examine distribution flows and their connection to \n", + "ODEs and continuous time Markov chains more systematically.\n", + "\n", + "Although our initial discussion appears to be orthogonal to what has come\n", + "before, the connections and relationships will soon become clear.\n", + "\n", + "\n", + "### Review of the Discrete Time Case\n", + "\n", + "Let $(X_t)$ be a discrete time Markov chain with Markov matrix $P$.\n", + "\n", + "{ref}`Recall that `, in the discrete time case, the distribution $\\psi_t$ of $X_t$ updates according to \n", + "\n", + "$$\n", + " \\psi_{t+1} = \\psi_t P, \n", + " \\qquad \\psi_0 \\text{ a given element of } \\mathcal D,\n", + "$$\n", + "\n", + "where distributions are understood as row vectors.\n", + "\n", + "Here's a visualization for the case $|S|=3$, so that $\\mathcal D$ is the unit\n", + "simplex in $\\mathbb R^3$.\n", + "\n", + "The initial condition is `` (0, 0, 1)`` and the Markov matrix is" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "P = ((0.9, 0.1, 0.0),\n", + " (0.4, 0.4, 0.2),\n", + " (0.1, 0.1, 0.8))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def unit_simplex(angle):\n", + " \n", + " fig = plt.figure(figsize=(8, 6))\n", + " ax = fig.add_subplot(111, projection='3d')\n", + "\n", + " vtx = [[0, 0, 1],\n", + " [0, 1, 0], \n", + " [1, 0, 0]]\n", + " \n", + " tri = Poly3DCollection([vtx], color='darkblue', alpha=0.3)\n", + " tri.set_facecolor([0.5, 0.5, 1])\n", + " ax.add_collection3d(tri)\n", + "\n", + " ax.set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), \n", + " xticks=(1,), yticks=(1,), zticks=(1,))\n", + "\n", + " ax.set_xticklabels(['$(1, 0, 0)$'], fontsize=12)\n", + " ax.set_yticklabels(['$(0, 1, 0)$'], fontsize=12)\n", + " ax.set_zticklabels(['$(0, 0, 1)$'], fontsize=12)\n", + "\n", + " ax.xaxis.majorTicks[0].set_pad(15)\n", + " ax.yaxis.majorTicks[0].set_pad(15)\n", + " ax.zaxis.majorTicks[0].set_pad(35)\n", + "\n", + " ax.view_init(30, angle)\n", + "\n", + " # Move axis to origin\n", + " ax.xaxis._axinfo['juggled'] = (0, 0, 0)\n", + " ax.yaxis._axinfo['juggled'] = (1, 1, 1)\n", + " ax.zaxis._axinfo['juggled'] = (2, 2, 0)\n", + " \n", + " ax.grid(False)\n", + " \n", + " return ax\n", + "\n", + "\n", + "def convergence_plot(ψ, n=14, angle=50):\n", + "\n", + " ax = unit_simplex(angle)\n", + "\n", + " P = ((0.9, 0.1, 0.0),\n", + " (0.4, 0.4, 0.2),\n", + " (0.1, 0.1, 0.8))\n", + " \n", + " P = np.array(P)\n", + " colors = cm.jet_r(np.linspace(0.0, 1, n))\n", + "\n", + " x_vals, y_vals, z_vals = [], [], []\n", + " for t in range(n):\n", + " x_vals.append(ψ[0])\n", + " y_vals.append(ψ[1])\n", + " z_vals.append(ψ[2])\n", + " ψ = ψ @ P\n", + "\n", + " ax.scatter(x_vals, y_vals, z_vals, c=colors, s=50, alpha=0.7, depthshade=False)\n", + "\n", + " return ψ\n", + "\n", + "ψ = convergence_plot((0, 0, 1))\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There's a sense in which a discrete time Markov chain \"is\" a homogeneous\n", + "linear difference equation in distribution space.\n", + "\n", + "To clarify this, suppose we \n", + "take $G$ to be a linear map from $\\mathcal D$ to itself and\n", + "write down the difference equation \n", + "\n", + "$$\n", + " \\psi_{t+1} = G(\\psi_t)\n", + " \\quad \\text{with } \\psi_0 \\in \\mathcal D \\text{ given}.\n", + "$$ (gdiff)\n", + "\n", + "Because $G$ is a linear map from a finite dimensional space to itself, it can\n", + "be represented by a matrix.\n", + "\n", + "Moreover, a matrix $P$ is a Markov matrix if and only if $\\psi \\mapsto\n", + "\\psi P$ sends $\\mathcal D$ into itself (check it if you haven't already).\n", + "\n", + "So, under the stated conditions, our difference equation {eq}`gdiff` uniquely\n", + "identifies a Markov matrix, along with an initial condition $\\psi_0$.\n", + "\n", + "Together, these objects identify the joint distribution of a discrete time Markov chain, as {ref}`previously described `.\n", + "\n", + "\n", + "### Shifting to Continuous Time\n", + "\n", + "We have just argued that a discrete time Markov chain \"is\" a linear difference\n", + "equation evolving in $\\mathcal D$.\n", + "\n", + "This strongly suggests that a continuous time Markov chain \"is\" a linear ODE\n", + "evolving in $\\mathcal D$.\n", + "\n", + "In this scenario,\n", + "\n", + "1. distributions update according to an automous linear differential equation and\n", + "2. the vector field is such that trajectories remain in $\\mathcal D$.\n", + "\n", + "This intuition is correct and highly beneficial. \n", + "\n", + "The rest of the lecture maps out the main ideas.\n", + "\n", + "\n", + "\n", + "## ODEs in Distribution Space\n", + "\n", + "Consider linear differential equation given by \n", + "\n", + "$$\n", + " \\psi_t' = \\psi_t Q, \n", + " \\qquad \\psi_0 \\text{ a given element of } \\mathcal D,\n", + "$$ (ode_mc)\n", + "\n", + "where \n", + "\n", + "* $Q$ is an $n \\times n$ matrix with suitable properties, \n", + "* distributions are again understood as row vectors, and\n", + "* derivatives are taken element by element, so that\n", + "\n", + "$$\n", + " \\psi_t' =\n", + " \\begin{pmatrix}\n", + " \\frac{d}{dt} \\psi_t(1) &\n", + " \\cdots &\n", + " \\frac{d}{dt} \\psi_t(n)\n", + " \\end{pmatrix}\n", + "$$\n", + "\n", + "\n", + "Using the matrix exponential, the unique solution to the initial value problem\n", + "{eq}`ode_mc` can be expressed as\n", + "\n", + "$$\n", + " \\psi_t = \\psi_0 P_t \n", + " \\quad \\text{where } P_t := e^{tQ}\n", + "$$ (cmc_sol)\n", + "\n", + "To check this, we use {eq}`expoderiv` again to get\n", + "\n", + "$$\n", + " \\frac{d}{d t} P_t = Q e^{tQ} = e^{tQ} Q \n", + "$$\n", + "\n", + "Recall that the first equality can be written as \n", + "$\\frac{d}{d t} P_t = Q P_t$ and this is the Kolmogorov backward equation. \n", + "\n", + "The second equality can be written as \n", + "\n", + "$$\n", + " \\frac{d}{d t} P_t = P_t Q \n", + "$$\n", + "\n", + "and is called the **Kolmogorov forward equation**.\n", + "\n", + "With $\\psi_t$ set to $\\psi_0 P_t$ and applying the Kolmogorov forward\n", + "equation, we obtain\n", + "\n", + "$$\n", + " \\frac{d}{d t} \\psi_t \n", + " = \\psi_0 \\frac{d}{d t} P_t \n", + " = \\psi_0 P_t Q\n", + " = \\psi_t Q\n", + "$$\n", + "\n", + "This confirms that {eq}`cmc_sol` solves {eq}`ode_mc`.\n", + "\n", + "\n", + "Here's an example of a distribution flow created by {eq}`ode_mc` with \n", + "initial condition `` (0, 0, 1)`` and" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "Q = ((-3, 2, 1),\n", + " (3, -5, 2),\n", + " (4, 6, -10))" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcwAAAFUCAYAAACp7gyoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nOzdeZwU1dnw/d85Vd09+7AIAoIooLijcQEVVEQQgdsIKioKAiqoaNQENzQoKovecTfxSWISjT6a547GN8ktCLgriWLcNwQVFWQflll6q+W8f1T3zDAOMEvPVFf3+X4+bY8zdPc1U1119bnOJpRSaJqmaZq2e9LvADRN0zQtCHTC1DRN07Qm0AlT0zRN05pAJ0xN0zRNawKdMDVN0zStCXTC1DRN07QmMPfwcz3nRNM0Tcs3orFv6hampmmapjWBTpiapmma1gQ6YWpawNx888088MADfofxI8cddxyfffaZ32FoWpvRCVPTAmTz5s38+c9/Zvr06QBs3bqVsWPHUlxcTO/evXn66aeb9DwtfdwjjzzCMcccQyQSYfLkyTv9bObMmcyePbtZv4+mBcmeBv1ompZFHn/8cUaNGkVhYSEAM2bMIBwOs3HjRj788ENGjx7NgAEDOPTQQ3f7PC19XI8ePbj11ltZvHgxsVhsp5+deeaZXH755axfv57u3bu37hfVtCykW5iaFiCLFi3i5JNPBqCmpobnnnuOO++8k5KSEgYPHsyZZ57Jk08+udvnaOnjAMaNG8dZZ51F586df/SzgoICjj76aJYsWdKyX07TspxOmJoWIJ988gn9+/cHYOXKlRiGwYEHHlj78wEDBuyxH7Glj2uKgw8+mI8++qjVz6Np2UgnTE0LkO3bt1NaWgpAdXU15eXlO/28vLycqqqq3T5HSx/XFKWlpWzfvr3Vz6Np2UgnTE0LkI4dO9YmtpKSEiorK3f6eWVlZW1C3ZWWPq4pqqqq6NChQ6ufR9OykU6YmhYgRxxxBCtXrgTgwAMPxLZtVq1aVfvzjz76aI8Dd1r6uKb44osvGDBgQKufR9OykU6YmhYgo0aN4vXXXweguLiYcePGMXv2bGpqali2bBl///vfmThxYu2/nzx58o+mf7T0cQC2bROPx3EcB8dxiMfj2LYNQCKR4L333mP48OGZ/8U1LQvohKlpATJp0iQWLlxYO6XjN7/5DbFYjK5du3LBBRfw6KOP7tRSXLNmDSeeeOKPnqelj7vrrrsoLCxkwYIFPPXUUxQWFnLXXXcB8I9//INTTjmFHj16ZPrX1rSsIJTa7frqevF1Tcsys2bNomvXrlx77bW7/XfJZJIBAwbw8ccfEwqFmvz8LX3cwIED+cMf/sBhhx3W5MdoWpZqdPF1nTA1TdM0bWd6txJN0zRNaymdMDVN0zStCXTC1DRN07Qm0AlT0/KAUgrXdf0OQ9MCTe9Womk5KJ0gbduuvUkpCYfDGIaBYRgI0ei4Bk3TdkGPktW0HOG6Lo7jYFkWlmVR/9yWUmKaJlLK2u8bhoFpmgghdPLUtJ3paSWalkuUUjiOg23bJJPJnUquUsofJUEhBJ988gn9+vWjtLS0NnEKIWpbnVLqXhpNYxcJU5dkNS0g6idIy7JwHKf2Z0KIRpPk6tWrefXVV3nttdf46quvGDBgALNnz6asrKz23yqldirb6pKtpjVOtzA1LUs17Ie0LKv2++nkuLukZlkWU6ZM4cQTT+TUU0/lkEMOwTCM3b5e/fv6rU6dPLU8o0uympbtdtUPmU6OLUlcicpKopVR1m12OWrgfk16jFJKl2y1fKYTpqZlm/rlUMuy9tgP2VyVa9aw6bPPuemBLbz+bg0v/X0ox57avG286idPXbLV8oROmJrmtz31Q2ZyxKqTTLLmjTe498kdvP95gpMG7s3SN9bw7+UXUVxW1KLY69/rkq2Ww/RasprW3tIJMpFIUF1dzY4dO6iuriYWi+G67k4ttkwnHte2efyvG3hl2XZ+cW4nTj3IoVOpyezb3m7R86WTeTpOx3FIJpMkEona1vEePoBrWqDphKlpGea6Lslkkmg0SmVlJVVVVcRiMRzHqU2Q6TmRbdky+98X1/HECzFuvqgrZSUmru1w2ahynn1uNS+99H2rnjudONN9mrZtk0gkSCaT2LatE6eWk3RJVtNaqbF+SKXULqd6tId//Ws9F164hF9O600XawNWPI4ZiVDUqRPrig7grrv/xX/+cwF77VWYsdfUJVsth+g+TE3LhPbsh2yJL7/cxhln/JM77zyNXs4aajZtJFRcDI6DFYux39ChPPrnL9m8eRvPPju6TWLd1Shbv/82mtZEug9T01rCz37I5lq/voaxYxdyzTUnMGhQL8JlZTiJBFXfr6Fm40aklLiWzdVXH88331Txhz981iZx6JKtlov0Sj+a1kC6dVS/zFq/teR3UtyVysok48YtYuzYQxkzpr/3TSkQpklpr54IV5GsqcGK1lASNlmw4AwmT/4rQ4b0oH//Tm0WV7pVmf67pueYZsuHDE1rKt3C1DS8gTqWZRGNRqmqqqKyspKamhqSyeSPJu5n48U9mXS46KKlHHJIN6ZM+Unt98PFxUilqF6zhuj69UgUrm0D0KdPJ2bMOJ5Jk5aQTDq7euqM0aNstaDTCVPLS+kWZDwe/1GChLrdPYIwQV8pxVVXvQGY3HjjkJ3iFaml8Er32Yfi7t1BCOyaaO3Px48/nE6dSrj99pZNNWmpXZVsE4mELtlqWUuXZLW8UH9dVsuysFOtLMjuMmtT3HHHu3zxxQ4effRMDGPnz8BmURFCSmp+WIc0JGZxCY5t1f5cCMHtt5/G+PFPM3z4vgwd2qu9w9+pZAvUlmzrb0kW1GOj5RadMLWcVL+/LN0XGYR+yOZ67LHPefbZb/jTn8ZRWBj60c8lgFIUddsbAbjJJHY0ijcA3vv9O3UqYs6c4VxyyUu8++75dO6cuakmzZE+HunkmZ7PqkfZatlCl2S1nNGwH3LHjh1Eo1EsywpEP2RzLVz4LQsWvMfDD4+hY8fGk5wRDmNGCoht2kR8SwWuZYFlQ4OS54kn9ua00/px5ZWvZkU5VJdstWykE6YWWHvqh0yvqBOEfsjmWr58IzNmvM59951Br17lu/6HhoFrW0Q6dSJSXoaQEte2ce0fD/L52c9O5Msvd/D445+3YeTN17AiYFkW8XicRCKB4zg6eWrtRpdktcBIl+nSfVxN2UA5F3311Q4mTFjCbbcN49BD997tv5XSIFRYSGzTJq9PMBxBOTbKdYCdS7iRiMn8+SOZOvVZBg/uwQEHdGzD36L5GpZslVK1H47qfzDKh/eA5g/dwtSyVv0FA2pqaqisrKSysjIrFwxoL5s3xxg7diGXX34cQ4b03uO/FyET5biYRUUYoRCOY6Nct3ZqSUP9+nXmiisGMWnSEiyr7aeatFTDKSr1S7b1581qWibphKlllfr9kPUTZLof0jTNvB05WVNjcc45izj99AMZO/aQJj1GCEGoIIJKeC2xUCSCsiyU1XjCBDj//CMoKytizpx3MhJ3W2tYXUiX6XXJVss0nTA1X9Xvh0wnyPoLBqRLbbnYD9kctu0yadJL7LdfZy6//NgmP05ICWYIIQVuMolVVYVynFRJdhePSU01eeKJL3jjjR8yEX67aNjqTJds4/G4XhhBywjdh6m1qz0tXJ6PLcc9UUpx7bVvEovB3LknN/vvY4RMEIJQYREiFEJZFk48sdvH7LVXMbfffhpTpy7l3XfPp2PHgtb8Cu2uYX9nempRerS0aZr6faY1m25ham2qsX7IbF24PFstWPA+//lPBXfffTqhkNHsx5tFRSjLwqqqJLllM8q2cJW7x8eddNL+nHxyH2bMyI6pJi3V2BQVXbLVWkInTC3jGvZDpjdQbvgJXyfIPXvyyRU8+eSXPPjgaIqKfrwwQVMIIZGhEOGOHSno0gVhmjg1NU167LXXDuaTT7by1FMrWvTa2UaXbLXW0CVZrdUa20A5rf4ne615li79nttuW87vf38We+1V1OLnCRUVoiwbq7oCBMhwpMk73RYUeFNNpk37Gyec0J2+fTu0OI5ssqeSbbrioWn16YSpNVtj/ZBKqR8NutBa7oMPNjNt2qvce+8o9tuvlfMhpYFhmpjlZQilcC0Lu6a6yQ/v378Ll112HBdfvIRXXz27RWXhbFZ/7mb95KnXstUa0h+htD1qygbK6dGs+uLSet9+W8l5573ILbecwoAB3Vr9fEZxEcp1SGzaTLKiAicWQzl77sOsb8KEIykoiDB37rutjieb6ZKttju6hak1Kr2zR5A2UM4FFRVxxo5dyOTJRzN0aJ+MPKdQChkKYRYUgOMCCjvatD7MNCkFd945gvHjn+a003oxePA+GYktW+mSrdYYfcQ1gNqdPWKxWO18yGg0GpgNlHNBLGYzfvyLnHRSH8477/CMPa9RUIhwXZwdO3CjUZRto1JLyjXHXnsVM3v2MKZMWcr27buflpJL9ELwWppOmHmq4cLlO3bsoKamhkQigVJqp+keOkG2PcdxmTLlZbp2LeOqqwZl9LmF8DaSNgoKEIb0Fi5wHHCbV5YFOOWUPpx44n5cffVreZkoGpZs0wvBJ5NJXbLNAzph5omm9EPqVqQ/lFJcf/2/2LrV5rbbTkXKzP7thWkihHeqpxOnSiRRTR0q28DPfz6EDz7YzDPPfJnJMAOl4QC39LmVXsvWbcGHES376T7MHJXezUH3Q2a/Bx74iDff3MDvf39Wm4xAFdJAhkysyh0IaeDGYoRKSlC2jQg3//UKC0PMn38G06f/jeOP787+++9me7E8sLtRtrpKk1t0CzOHNNxAueG6rLoFmX3+539W8dvffsZDD42mtDTSNi9iGijHJVRWjlkQIdyxI2483uyRsvUddFAXLrnkWC6+eAm2rVtTabpkm9t0wgywPW2gXH+6h06Q2ee119Zy443/5qGHxtC1a0mbvY4QEqMggr19G040hrV1G1KI3S7A3hQXXXQUphli/vzcnmrSErpkm5t0STZA0hsop0usdr09DXWZNVg+/bSCKVNeZsGC0+nbt1ObvpYwDXAcQmVl3jZfpuFt8bWLPTGbKj3V5Pzzn+G00/bl+OO7Zyji3KJLtrlDtzCzWDpB1l+4PL0uq+M4eqBOQK1dW83ZZy/ihhtO4phj2mM+o8AIh7FranBty0uaCpTd+g2iu3Yt4ZZbTmXy5CVUVjZ/qkq+2V3JVi8En/10wswyjfVDRqPR2g2UdYIMtu3bE4wdu5AJEwYwYkS/dnlNIUAa9VZicl1cy2rxKNmGhg3ry3HH7cs117yWkefLB42VbJPJ5E4lW508s49OmD7T/ZD5I5FwOP/8xRx7bC8uuujI9n3xkIkwTISUyEgEKSUqHs/Y08+ceRLvvLOJv/xlZcaeM1/samGEZDKpF0bIMroPs53pfsj85LqKyy57hZKSIq677sR2f30jUoCKRb1E6SYRptnkHUuaoqgoxPz5I7niiucZNKgb++1XlrknzyPplmd6WphlWViWpStLWUInzDZWP0Gmb3o+ZP659da3WbMmxq9//V8ZX5igKYQUyMIisC2kKUBKVCJzLUyAQw7pyuTJRzNlyhKWLh2HaeoCVks1XMvWcRwcx9mpW6b+YCKtfeh3dBtouIFyZWUlsVhM90Pmqd/85hMWLVrDvfeeQSTiz2dUGQp7CdJxUba3NJ5jtW6UbGMuvvholDK45573Mv7c+UqXbLOHTpgZUL8fMp0gG1swQPdD5p/nn/+GBx74iIcfHkN5eYFvcYhwCIFACm8QEI7T6mkljUlPNfnNbz5i+fINGX/+fKdH2fpLJ8wWaGzh8urqauLxeKMLl+skmZ/+9a/1/Pznb/LAA6Pp3r3U11iElMiQ128pVKrFmWybHUe6dStl1qyhXHzxEqqq9FSTtqBH2fpDJ8wmqL9weXo+pF64XNudFSu2cdFFS7nrruH077+X3+EgwxFIJkF5+2GqRLxFu5U01fDhB/CTn+zDdde90WavoXn09mPtRyfMXWjYD5leMKD+JrKmaeoEqf3I+vU1jBu3kGuuOYFBg3r5HQ7gtTC9XUsEAm9BdpVo2z0tb7jhZN56az1//euqNn0drU7DgYTpkm0ikdAl2wzQo2RT0q1I27ZrF0pOq//pTdN2p7Iyybhxixg79lDGjOnvdzh1TBOhvA+CKIUMhaEN+jDrKyoKM3/+6Vx11T8YOLAb++7rb1k6nzQcZeu67o/GVOjuoubL2yzQ3H5ITduTZNLhwguXcOih3Zgy5Sd+h7MTIxQC18WQEsM0IZnIyNJ4e3LYYd246KKjmDp1KU4rdkfRWk6XbDMnbxKm7ofU2pJSihkzXkfKMDfcMCQL3z8KGQ6Bq1DJJDIchjYa9NPQlClHk0zCr371fru8nrZrumTbOjldkq2/YEDDDZTTbxxNy4Q77niXFSsqefTRMzGMLHxfpVqYApChkNe6dNq+hQlgGJK5c0/n/POfYdiwXhxzzN7t8rrarumSbctk4ZmdOdFolGg0qjdQ1trUY499zrPPfsP994+isDDkdziNSr/nBcqbiKlclOOkRs22vW7dSrnpplO4+OIlVFfrqSbZZHcl2/oNDS3HE6ZSqjZR6gSptYUXXviWBQve45FHxtCxY6Hf4eyaUghpgFIQjSINs80H/TQ0cuSBHHFEd37xizfb9XW1pmtYsk2P89AlW09OJ0xdctXa0vLlG5kx43Xuu28UPXuW+x3O7pkmQnkjZEVBAdgWQilUG87FbMyNN57Ca6/9wPPPf9Wur6s1T8OFEZRSJJNJ4vF4Xi+MkNMZJX2gNS3TVq3azgUXLOb224dx6KFd/Q5nj2ovgODNwxTCS5Zu+54fxcVh5s0bydVXv8batdXt+tpayzRMnvlcss35hKlpmbZpU5Rx4xZx5ZUDGTKkt9/hNIkSwlusQIGqqQFAtGMfZn1HHNGNCy44kilTluK2c8LWWiffS7Y6YWpaM1RXW5xzzouMHHkgZ511iN/hNJkwDG+LL0AWFSFcBT6UZNMuueQYolGX++//wJfX11onX0u2OZ0wpZQ5edA0f9i2y8UXv8T++3dm+vRj/Q6neYT0SrG1N4XwcSEBw5DMm3c69933Ph98sMm3OLTWy6eSbU4nTNCtTC0zlFJcc82bxGJw880nB+59VRuuclHRKMp1vUFA7TxStr4ePcq44YaTmTRpMTU1lm9xaJnT2BSVXCrZ5nTCDNpFTcteCxa8z/vvV3D33acTChl+h9N8UkJq4XUZCiOFQCnafdBPQ6NG9eegg/bm+uv1VJNck4slW50wNW0P/vznFTz11EoefHA0RUXZuTDBnglk6nyQpuHti+k6Xn3WZ7NmDWXp0jX84x9f+x2K1gb2VLJ1fepHb4mcTpia1lpLlnzP7bcv5+GHx9C5c5Hf4bScTCVLBSQSSJS3H2Y7LY+3OyUlEebNO50ZM15j3To91SSX7W5VIScL3ot7ktMJU7cwtdb44IPNTJv2Kr/61Rn07t3B73Bap3ZdUOWVYR3HO/mz5NP9kUf2YPz4w7nkkpf0VJM8Ub/VmV73O9vphKlpjfj220rOO+9Fbr31FAYM6OZ3OBlRu3l0QQRhGJBl221deulxbN9u8dBDH/oditaO0iXbIFyvdcLUtAYqKmKMHbuQKVOOYejQPn6HkxnpZKkUwrK9+ZjgrS2bJUxTMm/eSO655z989NFmv8PR2lF63e9sl9MJMy2Io7E0f0SjFuPHL+bkk/swfvxhfoeTQQKhUgkzmYBEEgVZ0YdZX8+e5cyceTITJy4mGtVTTbTsktMJMwifWLTs4TguU6e+QteuZcyYMcjvcDIvPfDHNCFkIt3sSpZpY8b054ADunDTTcv8DkXTdpLTCRN00tSaRinFzJnL2LrV5rbbTkXK3HvfSCGQSkEohJQCkSUDfhoSQjBr1qm88MK3vPDCar/D0dpJEHaXyv4IW0knTK0p7r//I956ayP33BPQhQn2IH0aSKGQyQTScRCKrBkl21BZWYS5c0/n8stfYcOGGr/D0TQgTxKm7sPUduf//b9V/O53n/HQQ6MpLY34HU6bEeDth6lchGOnR/34G9RuHH30Powbd5ieaqJljbxImJq2K6+9tpabbvo3Dz00hq5dS/wOp00JmRrCLwVCSqQPW3s11/Tpx7FlS4Jf//ojv0PR2pCeVpIldAtT25VPPqlgypSXWbBgBH37dvI7nHYgECgIhcE0vCklWX5qhEIG8+ePZP78d/nkky1+h6O1kaBco/MiYWpaQ2vWVHHOOYu44YaTOOaYffwOp10Ib14JMplA2DbSEN6OJVmuV68OXHfdECZOXEwslv2rwWi5K+cTZhBGXmnta9u2BOPGLWLChCMZMaKf3+G0G6kUUimEAKlchFLeLiYB8NOfHsx++3Vm1iw91SRXBaFxE4yzpZWC0tzX2l48bnP++Ys59theXHTRAL/DaWcKgQDlgvA2kc6mlX52RwjBrbeeyt//vpoXX/zW73C0PJXzCTO9uK+mua5i2rRXKS0t4rrrTvQ7nHYnAIlChkPIkEHWd2A2UF5ewF13jWD69FfYuDHqdzhahgSpQZPzCVMP+tHSbrnlbdaujXPHHcNycmGCPRGuC0KBcrzpJSIrtsNslmOP7cmZZx7MZZe9pM/rHKJHyWaRIBwIrW39+tcf8+KLa7j33pFEIqbf4bQ7pQDpJUkcG2wbIaldLi9IrrhiEOvXx3j00Y/9DkXLMzmfMHWy1P72t6954IGPefjhMZSVFfgdji+8gT4KgUIKiTCEV5EN4PmRnmpy553L+fzzCr/D0TIgKNdpnTC1nLZs2Tp+8Yu3ePDB0XTvXup3OP5RgOt4XwiFwA30udG7d0euvfZEJk5cTDyup5po7UMnTC1nrVixjYkTX+Kuu4bTv/9efofjM+XdBKnRPwJwA9nCTBs79lD22acDt9zyb79D0VopKNfpnE+YWn5av76GceMWcs01JzBoUC+/w/GfSiXM+onTq8n6GVWrCCGYPfs0/va3r1i69Du/w9FaKCibR0MeJMygHAgtcyork4wdu5CxYw9lzJj+foeTPVw3lR9dby6mIQPdwgRvqsmdd47gssteZvPmmN/haDku5xNmmh6Cnh+SSYcJE5Zw2GHdmTLlJ36Hkz2U8pIkbu3IWAWBWelndwYO7MWoUQcxbdrL+jzX2lTwz5Y9CMr8Hq31lFJceeXrGEaYG24Yoo97fbUJk9qVfgjotJLGXHXV8Xz/fTW///2nfoeitUBQljANRpStpC+c+WHOnOV8+WUlc+eehmHkxVu76dIjZKkrywopciZhelNNzuD2299mxYqtfoej5ai8uKro1X5y3+9//xnPPbea++8fRWFhyO9wso9SgJNKkILaZX5E7lwC9t+/I1dffQITJy4mkXD8DkfLQblztuyGbmHmthde+Ja7736fRx4ZQ8eOhX6Hk51c17spBdJNnfkipxImwNlnH0bXrmX88pd6qkmQBOUanVtnyy4E5WBozffOOxuYMeN17rtvFD17lvsdTvZyHa/vUqrUyFgFhgDD8DuyjBJCcNttp/E//7OSl1/+3u9wtByTNwlTl2Rzz6pV25kwYQm33z6MQw/t6nc42c1xgFSZMt2fKUVOjJJtqGPHQubMGc6ll75ERYWeaqJlTu6dLY0Iyggsrek2bYoybtwirrxyIEOG9PY7nOynUiVZ8M56kWpp5ui5ccIJvTn99P5Mn/6K/rAcAEGpAubm2aLltOpqi7PPXsTIkQdy1lmH+B1OMLgO3ghZt+57Mvf6MOu7+urj+frrSv74x8/8DkXLEbl7ttQjpdSfMnOEbbtMmrSUPn26MH36sX6HExy23WB5PLzWpcytPsz6wmGT+fNH8stf/puVK7f5HY7WiKBdl/MiYUJwmvzariml+NnP3iSRENx880n6mDaDcGxv42ihUjcXTDNnS7Jpfft25sorj2fSpCUkk3qqSTYK0uIyuX22pATlYGi7N3/+e3zwQQULFpxOKJS7LaM24dpAqhwr8JJmKD/mq44ffzgdOhQxZ87bfoeiBZxOmFogPPHECp56aiUPPjiaoqL8uNBnkrAsvFGy9XYrMU1/g2onQghuv304Tz75Ja+9ttbvcLQA0wlTy3pLlnzPnDnLeeSR/6Jz5yK/wwkmO1FvHqbXb6TC+fPBo3PnIubMOY2pU5eydWvc73C0eoJ0fc6LhKkF1/vvb2batFf51a/OoHfvDn6HE1xWkp02kBZu3pRk0wYP3o9hw/pxxRV6qkk20QkzywTpgGh1Vq+u5LzzXuTWW09hwIBufocTbFYsNaVE1e1UEi7wOaj2d801J7JixXaeeOILv0PRCNbm0ZBHCVN/ogyWiooYY8cuZOrUYxg6tI/f4QRfIuZ1X6YXLQBUOOJrSH6IRLypJjffvIyvvtrudzhawORFwgSdNIMkGrU499wXOeWUPowff5jf4eQEYUXrtTBT50EetjABDjhgLy6/fCCTJi3GsvRUE63p8iJhBmmeT75zHJepU1+hW7cOXHXVIL/DyR3JeO0+mJDarSSSvzu7XHDBAEpLi7jjjuV+h5L3gnRtzouECcE6KPlKKcXMmcvYutVm9uyh+phlkLCi1LYuRaofMw9LsmneVJPTePzxz3nzzR/8DievBek8z5uECcFbhinf3Hffh7z11kbuuUcvTJBRCrASdYlSSDAlmPmbMAH22quY228/jSlTlrJtm55qou1Z3iTMIH2KyUfPPLOSxx77goceGkNpaX5fyDPOtcFNpuZg4vVlSgmhsN+R+e6kk/bnpJP2Z8aMV/UHap8E6dqcNwlTb/GVvV59dS2zZr3Ngw+OpmvXYr/DyT22hbCT9eZg4m0cnaeDfhq67rohfPLJVp56aoXfoWhZLm+yiB4lm50++aSCqVNfZsGCEfTt28nvcHKTk/BamULhbfGlwDTyviSbVlDgTTW58ca3+PrrHX6Ho2WxvEqYWnZZs6aKc85ZxA03nMQxx+zjdzi5K5kAZQEKDLzSrGFCSCfMtP79u3DZZcdx8cV6qkl7C9K1WSdMzRfbtiUYO3YREyYcyYgR/fwOJ6eJZA93oWwAACAASURBVBSh7LpdSsAb9GPk19J4ezJhwpEUFESYO/ddv0PRslReJUxdks0O8bjN+ecv5rjj9uWiiwb4HU7OE3YcpZydFy4IF+b8XpjNJaXgjjtG8Ic/fMayZev8DifnBfF6nDdnjF68IDu4rmLatFcpLS3iuutO8Duc/BCrBOy6jaOFQEX04KrGdOlSzC9/OYzJk5ewfXvC73ByXtCuy3mVMDX/zZr1b9aujXPHHcOQUh+TdhHfgRAKpAAhQLpQWOZ3VFlr6NA+nHjifvzsZ6/5HYqWZXTC1NrNI498zJIla7n33pFEIvmxeXFWiFem+i7TZVlQRaX+xpTlfv7zIbz33maefvpLv0PRskjeJEzNX8899zUPPvgxDz00hrIyPf+vPYn4dlR6STyJN1JWtzB3q7AwxPz5I5k58w1Wr9ZTTdpK0BoyeZMwg3ZgcsmyZeuYOfMtHnxwNN2765ZNu1IgrGqEdOtW+pESCvVx2JODD+7K1KnHMnnyUmzb9TucnBS067JOmFqb+uKLrUyc+BJ33TWc/v338juc/GMnUlt7CS9RShdMEyIlfkcWCBMnHoVhmCxY8B+/Q8k5Qds8GnTC1NrQunU1jBu3iGuvPYFBg3r5HU5+sqLeOrLpPkxILYunR8k2hZSCO+8cwW9/+wlvv73e73A0n+VNwkwL4tyfIKqsTDJu3ELOPvswRo/u73c4+StZjXATXjlWpvswTZ0wm6Fr1xJuuWUoF1+8hMrKpN/haD7Km4SpW5jtJ5l0mDBhCYcf3oPJk4/yO5y8JuM78OZgCm9bL4G3aIGpdyppjmHD+nHccb245prX/A4lpwTtupw3CROCd3CCSCnFlVe+jmGEuf76wfpv7rf4VhA24Hj9l8L1BvyIvDr1M2LmzJN5++2N/OUvK/0OJWcE7fqQV2dN0A5OEN1++3K+/LKSuXNPwzDy6u2VnaKbUQIwXBAOGAJVpHeFaYmiIm+qyS9+8QbffVfpdziaD/LqiqbXk21bv/vdZzz//Gruv38UhYV6Ye9sIBIVCOl4LUpDAgpVqkcrt9Shh+7NxRf/hClTluI4eqpJawWtEZN3CVNrGy+88C333PM+Dz88ho4dC/0ORwNvDmZiq3eWS9srx5oSinTCbI2LLz4ax5Hcc897foeitTOdMLVWe+edDcyY8Tr33TeKnj3L/Q5HS7NqkFaNN9AH8PbDDEGBPkatIaXgrrtG8Otff8Ty5Rv8DkdrR3mXMHVJNrNWrdrOhAlLmDNnGIce2tXvcLT6ktuAuDfYxwBMkVq0oKPfkQVet26lzJrlTTWpqtJTTVoqaI2YvEqYUu//l1EbN0YZO3YhM2YMYvDg3n6HozUg49tQJFNzLxXgQigCYb3KTyYMH34ARx21Dz//+Rt+h6K1k7zLILqFmRnV1RbnnLOIUaMO4qc/PdjvcLTG1KyjboeS1PZeBR1A6p1iMuXGG0/mjTfW8eyzq/wOJVCCeh3Oq4QppQxcCSAbWZbDpElL6dOnC9OmHeN3ONouiPgGRO3qPgKEiyru5ndYOaWoKMyCBSO55prXWbOmyu9wAiVom0dDniVM3YfZekopfvazN0kkBDfffFLg3vD5RCQ3eMvhGamdSgyBKtEJM9MOO6wbF154pJ5qkgfyKmFC8DqZs828ee/x4YdbWbDgdEIhw+9wtF2xYwh7W2pLL+HdmwYU7u13ZDlp6tRjSCQU9977vt+haG0orxKmTpat88QTK3j66VU8+OBoior0wgRZLVGBcOPe6FjD9Qb9SBMKu/gdWU4yDMncuafz4IMf8t57G/0OJxCCeD3WCVNrksWLv2fOnOU8/PAYOncu8jscbQ9kfB1ICwzHS5pSejuU6CklbaZ79zJuuukUJk1aQnW1nmqyJ0G8HudVwtRa5v33NzN9+qv86ldn0Lt3B7/D0ZpAxNZ4CVNSt+h6QWeQujLQlkaOPJDDD+/OzJlv+h1KVgvi5tGQZwkziAfIb6tXV3LeeS9y661DGTBADxgJChH/tt4emF4fplvc0++w8sJNN53CK6+s5fnnv/I7FC3DdMLUdqmiIsbYsQu55JJjGDp0f7/D0ZrKtRD25tR0Eryl8aSE4v38jStPFBeHmTdvJFdf/Rpr11b7HY6WQXmVMNP01JI9i0Ytzj33RU45pQ/nnnuY3+FozZHciKA61XfpeoN+QiEo2sfvyPLGgAHdOf/8AUyduhTX1debxgSxAZNXCTOIE2X94DguU6a8QrduHbjqqkF+h6M1k4x9B4blJUsJCFBGBCJ6rd/2dMklx1Jd7XD//R/4HUpWCuK1OK8SJgTzILUnpRS/+MUytm2zmT17qP57BZCIrwTDBpPUousKFdkbZMTv0PKKaUrmzRvJvfe+xwcfbPI7HC0D8jJh6pLsrt1334csW7aR//7vkXphgiBSIOxvU6Nj04N+gML9/I0rT+2zTxk33HAykyYtJhq1/A4nqwTxw3heJkytcc88s5LHHvuChx4aQ0lJ2O9wtJawtyJFRYOEaeAWHeB3ZHlr9OiDOOigvbn++rf8DkVrJZ0wNQBefXUts2a9zYMPjqZr12K/w9FaSFirQcbBdMFU3uhYMwwFvfwOLa/dfPNQFi/+nn/+8xu/Q9FaIS8Tpi7J7uzjj7cwderL3H336fTt28nvcLRWkIkvQNp1pVjDwTU7gNnZ79DyWmlphHnzTufKK19l3To91QSC2XjJy4Sp1VmzpopzznmRG288iaOP7uF3OFprKBBqhTfYx0yVYw1Qkf29Bdg1Xx11VA/OPfdwLrnkJT3VJKB0wsxj27YlGDt2ERdeeCTDh/fzOxyttdzNSLk1tdg6XtI0DFREb/CdLS677Di2b7d4+OEP/Q7FN0Gu8OVdwpRSBvqAZUo8bnPeeS9y3HH7ctFFA/wOR8sAaX0JMlq3YIEEzDBuWA/4yRamKZk7dyR33/0fPvpos9/h+Caoc+LzLmGCbmW6ruKyy16lvLyY6647we9wtAwRvJ8a7IPXujTBNTqCobf0yia9epUzc+ZJTJyop5oETd4lzHxPlgA33/xvfvghzpw5w5BS/z1ygptEsArXULiGizIACSp0iLeWrJZVxow5iH79unDTTcv8DkVrBp0w88wjj3zM0qVruffekUQipt/haJmiVuGGqnBNhWuCMsA1TBzzcL8j0xohhOCWW07lhRe+ZeHC1X6H0+6Ceh3Ou4SZz5577mseeuhjHnpoDGVlBX6Ho2WQEstxQgrHlN7NAGQhiAP9Dk3bhbKyCHPnns7ll7/Chg01fofTrnTCDIigHqjWeuutdcyc+RYPPDCa7t1L/Q5HyyCFi21+ii0ljiFxUve27A1God/habtx9NH7MHbsYVx6af5MNQnyoEudMPPAF19sZdKkl5g7dwT9++/ldzhahjl8jW1WY0uJLY1UwjRR8ji/Q9OaYPr049i0KcFvfvOx36G0m6Beh3XCzHHr1tUwbtwirr32BAYO7Ol3OFobSBr/xgYcTO8mDBxRAOJIlFK4rott24BK3bRsEgoZzJ8/knnzlvPpp1v8DkfbjbxLmGlBLgs01Y4dCcaNW8jZZx/G6NH9/Q5HawMuDpb8HBszdTNwkCi3F45dguu6te91x3FS/++iE2d22XffDlx33RAmTlxCLGb7HU6bC2rDJe8SZlAPVHMlkw4XXriUww/vweTJR/kdjtZGknyGLeKpZCmxkTiYCOc4DMPANE1CoTCmGap977uuW3vTiTN7/PSnB9O7d0dmzfqX36G0uaBeh/MuYUJwD1ZTua7iiitewzDCXH/94Jz/ffOVUopquYwEEhsDO1WSVRQQlgMxDBMh6p/iAhC131PKrW116sTpPyEEt946jL///WsWL/7W73C0RuRtwszlkuycOctZubKKuXNPwzDy8hDnpHR/pOM42LZNwt1GzFxbW451UuVY6fbBpHwPz+YlTu9caJg4c/fcyHbl5QXcddfpTJv2Cps2Rf0OR2sgL6+mudzi+t3vPuP551dz//2jKCwM+R2O1kr1k6TjOCilEEJgmibR8H9qB/t4LUwDF5OIe2IzXqHxxKlbnf459tie/Nd/Hcyll76Usx/sg3oNzsuEKWVu/tr/+7+rueee93n44TF07Kjn3wVV/ZGt6UE7UkpM00zdQiBhh/EhSUwsQlipkiyUEKElq/vUJU4vBl2u9dOVVw5i/foY/+f/fOJ3KFo9uZk5miDXPrm9884GrrrqDe67bxQ9e+6pHKdlG6UUjuPWG8nKToN2DMNESqO2/3E7nxDFwsbEwkhNKTGIuIdhEG5FJHX9nLpc659QyGDevJHcccc7fP55hd/hZJxuYQZIUA/WrqxatZ0JE5YwZ84wDj20q9/haE3kJUmnNiEJQW1LMhQKNTJop856418kMUgSqp1OoghT7p6UwQgbT5x6Wkr72G+/jlxzzYlMnLiYeDz3p5oEQV4mzFwqyW7cGGXs2IXMmDGIwYN7+x2Othv1S611rTaBlEZtqXV3STJtG19TJaIkCWFhYmHiYhJ29yHC3m0Q+c7lWj0tpf2MG3coPXp04NZb/+13KBkR9Mpe7mSOZsiVUbLV1RbnnLOIUaMO4qc/PdjvcLRGNDZoR0pZr9wawjCMPSbJ+lYbb5HAJEkYi1DtYJ/O7uA2/E1AT0tpf0IIZs8exnPPfcXSpd/7HU5GBHXzaMjjhBnUA5ZmWQ4TJy6lT58uTJt2jN/haPU0ZdBO/f7I5tjCaraJaGqgTwg7VZI1VQfKac+dSXQ/Z3vp0KGQO+8cwWWXvcTmzTG/w8lreZswg0wpxdVXv0kyKbj55pMC//vkguYO2mmpz423iREiQSQ14EfiYrK3czzCl9NZT0tpDwMH9uKMMw5i+vSXc6I6FlR5mTCDbt689/joo60sWHA6oZDhdzh5qzWDdlriO1ZSIWyShEmk+i8dQoQpoSuHZex1WkZPS2lrV199PN9+W83vf/+p36G0SpA/4OdlwgzyAXv88S94+ulVPPjgaIqK9MIE7SlTg3ZawsHmffNT4oRJEsauTZiSfe3jkGTLB6c9TUvRWiq9q8ltt73NihVb/Q4nL+mEGSCLF3/PHXe8y8MPj6Fz5yK/w8kLbTFopyXe4X22YxAnQoIwydRyeAWqjH04pE1fu+UaS5yOnpbSCn36dOLqq09g4sTFJBKO3+E0W3qlqqDSCTMg3ntvE9Onv8qvfnUGvXt38DucnNaWg3ZaYjPb+NTcSowCEkSwCOEQQmFwiHOsT32XzVE/cSo9LaWVzjnnMLp2LWP27GBONQni9Tct2880Dfjmmx2cf/5ibr11KAMGdPM7nJzUXoN2WuLvxmdUp5JlklBq/qVBuduBHuzf7vG0XDpx6mkprSGE4LbbTuMvf1nJyy/nxlSToMjLhBmkTzhbtsQYN24Rl1xyDEOHBunimP3ae9BOS/ydz9kowsQoJE4EizA2JhKDge4gX2NrHT0tpTU6dizkjjuGc+mlL1FREaypJkG6/jaU1wkz24dnR6MW5577Iqec0pdzz/V7FGTwNTZoR4j2GbTTEp9RwX9MlyhFxCkgQZgEYVwM+to96EgulOb18nstdcIJvRkx4kAuv/yVrL+W1acTZgBl+0GzbZfJk1+me/cOXHXVQL/DCaz6g3bq90emy61ekvSn1Lo7FcR4wtxOFUVEKSBJJLXAuqRQhRnMEX6HmGF6+b2W+NnPTuCrryr5058+9zuUvGD6HYBfsjlhKqWYOXMZ27c7PPTQ0KyONRt5SVKhlFv7t5NSIqXMusTYGBuLu+QOqlQJEoUpbBRJwKUAm9FOX4ysmUaSad7x8o6bSrU6QQiJlKL255onHDaZP38kU6Y8y+DBPTjwwI5+h5TTsv/q0UayeT3Ze+/9kH/9ayP//d8j9cIETdRw0A78eNBOEJIlwEwrzrpYGTXxEqJWAQk3TJIQCsmRdid60tnvENtJ49NSdD/nzvr27cyMGYOYNGkJyWT2TzUJcgMgGFeQNpCtB+2ZZ1by2GOf8+CDYygpac2+hrkvCIN2muvqCocfKspxd5RgVxeQSBSQdLwpJHu5IUYHalRspuw8LUUvv/dj48cfQYcORcyZ87bfoeS0YF1NMigbE+Yrr6xl1qy3eeihMXTtWux3OFlnV4N2du6PDF6STJv+JWzYEIatAqoFxMIQD+E4JmEFM9yefofoMz0tZVeEENx++3CefPJLXnttrd/h7FY2XnubKphXlgzItpLsxx9v4ZJLXubuu0+nb99OfoeTNZoyaMev+ZGZkkjAhW8Itv5gwBagEqgB4oBrYCjFdU4Hion4G2hW0dNSGurcuYg5c07jkkuWsnVr3O9wclJwrzKtlE2bSH//fRXnnPMiN954Ekcf3cPvcHz340UEVL1SazgnkmTayk1w0bOS5BoJm4AdeMkyBliAgitlhP3QSyE2Tk9LqW/w4P049dR+XHnlq1nVIIDsn8bXFMG/4gTc1q1xxo1bxEUXHcnw4f38Dsc36SSZXo4O2ClJBrnUuiv3/xNu+Z0B3wnYyM4tSxsQMLHI5nhDtyz3TE9LSbvmmhP5/PNt/PnPX/gdSqOCXJIVe8j6OftOSyaTRKNRDMO/UajxuM2ZZ75Av35784tfnOhbHH5Jl1vT0ht7B2X6R0t99x3MnANYBkSAEqA8desAdAH2gvMPdDg7XwbEtglV26rJt2kpq1Zt4dJLn+ONN86lX7/sWOAifSwKCgp8jqRJGn2j5G3CtCyLmpoa3xKm6yomTXqJREIwb96I1Mmc25RStUky/SmzLkGKnE6SAJWV1cyYsZl4XABFQBlQCCUSOuIlyq7ebcoxDqO6+xltLmkscUKuJ8+nn/6QxYu/4PXXz8mK6Wm5kDBz+wqVxW6++d+sWxdnzpxhOZ0s82HQTlPMnr2MSy55mXj8Y2AF8D1QAcQhileCVYCpmDVQJ8vMaryfM9fLtRdcMIDi4kLuvHO536HUCnI5FvK4hWnbNtXV1b60MB9++GP+9KcV/OEPYykrC8SnrWZJr7RT/+0TpJV2Mu2xxz5i8eJvUv/nrWADhUAvotFSamo2U4WFKitl6IHlGLIURSGqIIwsBVkGshTMIjBKwSyBSDFECrxbSQQKCqAgBMUhRYnhUIJDCS7FCIoxCKE3G6/jvS/zoVy7ZUsN48c/zdNPj2TIkH18jSW9F2YkEog+eV2Src9xHKqqqto9YT777FfMmvU2f/zjOLp3L23X125LP16OTiBl7vdHNsW0aS+ybVuMupUoHZSSrF8fpbp6LeDinZ8lwD5Az9R9JyDilWo7p24dUvdlqa87pL4uB0pTX5coKE1iFsUoLI5SEo5TJOIUqzilKkGRSlLq2pS6SUpth3LboNwNUWqFKVEhSqxCInYhxXYxEbuEMMU5vBRf7pdr33hjNfPnv8p//nMBHTr4l6xc18UwDMLhQCzI0ugbIG/XkvXDm2+u4/rrl/HrX58Z+GSZ7o+s/4HL6480kTKY/ZGu6/Dpp5V8/PFGTFMxcGA39t+/9WtzhkIN/xaKmpoaqqvX4X0mDeHVZKuBrXiJswSvFRqCGul1eUaBgnr3iXq3OBBOfR0SEAphmw6JsIMwFK5p48oQSSyiSGpw2IZNAfHULUmYBAUkKCBJoUoSxiGiEhQqh0LXIeKEiDghCq1iCq0OFLp7YVqdCCU7Ip0uSLsDZuCmv4hG1q0VtbdcSJwnnbQ/y5Z9y4wZr/LUU6f7WhYNekk2bxNmex+4L77YyqRJS5k7dwT9++/Vrq+dKbtOktk/aKciBp9vgVUb4ft1sHEtbF0jcNcJiCbxEtUPwHogCSj+9reVjB3blwsuaN3Wamed1Y/HHvsE17VT3xFYliTVaZn6nsRradqAk7ql/s5uvVv6T19/jn5jXyO851TpY2KgEKnXs3CRKCQKgar9mURh4CCxhEDiIIVCYqOMJHaoiiQWCSyieAk1rCxCyiKsXEwlCLkmYauEkN0Rw+qOTPZCJXuA3R3X7gaqE9k5dKIucQL1BqaJnCjXXnfdEC644Bn+7/9dwUUXHex3OIGlE2Y7WLeuhnHjFvHznw9m4MBgLW8WhCRZSYK1JPkOxbeOwQ8xk/XVEaqrJW6l8BYDqAS24329HS8/VgBxh7qW3Xa8ZAlgopTD//7vNwwZ0ouePctbHN/w4X0oKDB5/vmvqKmxOfLIHhxzTClXXvkDyaSNdxqmp9eY9W6pC7XEuxl130I2+Fo2+D4KhEII73lFKjWK2tTofa+xROA9raj9F94t/cjUXrIIFApXKFwBDg4GFq5K4oQ3IZSNFBZS2ZiuSxgwlYFhF0CiAyrWAztxILbVj6TTG9vticPeeM1kP+XmbikFBSYLFoxk2rS/cfzxPejbt+Xv53yWtwkzLd0R3VZ27EgwduxCzj77MEaNOrDNXieTfjxoR/g2aMfBpoZqtlHFVmrYTJzN0marEFQKk2oi1BAhqgqJJQtJxkNQXQA1om7FnDheqdLCy4dJvIacTep3TLfo0i1AWXtv2y4VFbFWJUyAIUP2ZciQfWv/XynF2LGH8de/flyv5VmM12/ZCa9zsghMUdc/ma7Ulqb+aTFe1bYYbz5nQfqmIGwhwgmkYREykpjCwsDGwEKSROIgcJDYqa8VCjf1d6i7T6eIndMnO92LVN717gVSpXN26rwSDq6wQTmI0DYMYw2h8McYiUUYlgF2GDcZQVlF2E43EtZ+JNwDsDgQi32xxL4ooyO0ezmvsXJtcPs5+/fvwqWXHsvkyUt45ZVxvkw1yaYV1loibxNmXR9F20kmHSZMWMIRR/Rg8uSj2vS1WqvhIgLtlSQdHCy2E6eCGiqoETuoEpVUiwTVQhLHII5JnAhxQiSJkCBCnAISmMQIYRHGUSFc1wDHINXkqbvup0uVDWcRiPR/0s239Ongpv7fxTQN9t67JOO/txCCWbNGcPbZR/D669+yY4eirKwTHTt2oLy8iKKiIopLDSJFLoaZGiFrgKO8KqsSYBuQFGAJiEuwQhAPuSQMm6Tj4kQFdsLErY5gS1BCYhkCWwJSoAwXKRyE4SCFJCTBRHhdoAhCCEzhInG98myqiAsq1UJ1vDG/wkUoN9UYVnV/YuFS24hVyvuJEqmbAqlAOggjimlEUWoHJuspcD8E10DZBsopwHWKcFRHLLc3CQ4kaRxEItwP2+yJa3SENr8IN97PGcRy7YUXHsWyZd8xb9673HbbIL/DCZy8TZjQtmVZ11VcccVrhEIRrr9+cNZ1djdWaq1LkpkttdpUo9iIwwZsNhGTm0mKbSREggSKJJIkJklCqaEnXhL0/t9rFzmp/jevv82rQSoMRP3+sNoWzU6/0s5ly3ReNPFaZZbEa5aV4A0xTY+gsRFCMmHCwXTrlvmEmda/fzf69++WwWeU4IS9DwqJ5k9ZsrCwRIyEWU3crMQ1KnHMKhxjO7ZZAeZWMLZCqJKwqEHKGkzh1JV4BQjhtV6FApFqpcpUkRfRoMNVCUSqn1XiotJ9rkIhhIMSNUg3iulUEHZWUWK/grJNFAW4dhG26kRS9iFeeAhW5BCSBftjhXt4nzAybud+ziCWa6UU3HnnCM4772mGD9+XE07Qa1c3R14nTGi7kuztty9n1aoqHn30pxhGdpQh2qw/0nXA3QHOBlDrEOoHhFyLbVZgmdVYhouFTBUETWxMkql7ixBOqv3ipi67df1not4lqOExUohUszHdN4dQIF0wXAhJb/BpBK8Ua6fu0y1NqEugNYXgdqEwUkxxcSe6dKnhiCNMRozYl7KyoI36bJ0QIUIqRJFVBtaeL6YW1bjGVpS5GRHehDA3YoR+wAyvxZAbMEJbkbIKIeJI4kjHRSoH4SqkIxFKgCsRSgHSS6gC0gdJKJn6HKSQQqKEQggbYdcgnBqks4WIu4qS6qUI28RRxbiqGCvUi3jBwSRKDsEqOQCrsDduqDxDZd1g93N26VLML385jMmTl/Luu+dTXt4+U03ao6rX1vJ2HiZAVVUVrutmvK7+u999yiOPfMof/ziOjh0LM/rczZWx5eiUC8kdkNgI1nqktRbsH5BUgNwORhxMB0IubsjFNQVWSGIbJrYwapOjTQivN837f6v2Ppy6D5FIfS9JJHXvtTYThGu/TlJAkhBxIqkybZi4U0AyGcJKFEAshBk3KIorOtiKzgr2BvoWQ78y6Nn62SJak+1AmhsxzDWEQmsJidUUiG8Ii7WYqgLDjSIcC2nbCMvGdUDYAtcGYYNwBMoR4CiELcARKFsibIVSXvld2UZqgLFE2d6/9erQBq4qwJVlWEY3EiUHEu9wFMmyftjFvXEjmUqi9edzBmNayl13vQIkeeKJEe3yeq7rEolEgtKPqRcuaKimpgbbtjN6AP/5z9Vcd91b/OEPY1s9UKSlGh+008RFBJI1EN8M0Y2I6A+I+A+I5AaEu81LitICwwHTBUN5g1JCymvNmQpMcEMKx5TYIQNbGjiphJn8UcI0sQmlvu/93KqXWJOpm0UYmxB2KmG6REBFMFUhRaqIclVABwroRgHdCVGq940MEBuDzYT4AVN9T0h9RcT5mrDzHYazBenUIJwEwrYRjguWRDkuwpFecnRB2A7KMRCOi+sYSFugHMAW4AqU43X+CkegXMA2cEQhjlGOHelGosMhxDsNINnxQOzSnqhwazZv3zlxZnM/ZyxmccEFz3DLLccyYUL/Nn8913UpKCgISitTJ8yGotEoyWQyY6v9vPPOBs47bzEPPzyGQw7pmpHnbKrGB+3sIkk6DsS2Q/VGqNqIrPoeUbMOkagAtxpk3EuK6fKmiVe+DJFKktQmR4zU90Mq9bXCNcENCSzTwJEmlpSphJdOlAZOKkmmW5UOJjYRHEI4FCDdEsJ0IOJ2IkwpZXSilFJKKCEbLz5aG3ASGO4GQs73hK3VRJIrCFvfEEqsxbCrwEkg7YTXmrQEKFCOBAeErcARqamtXtk3PRhaipPzGgAAGrxJREFUOSa4CuVIr0vVEShMXIqwIx2winsT73IE8a5HYHXYD6d4bzBa0nuV/bulfPHFJi6//HmWLRvP/vu37Qd8nTADLhaLkUgkMpIwV63azsiR/2D27FMZPLh3BqLbvfqlVqj/abbeoB3bguoK2LERsW09YsdaROUGRKwCVAxUEqRTlxQN10t66Zvpei1Ig3r/hrqEWX8Ajalqk6gKgWsI7JCBY0gsw2tJOpg4RHApRqlOoPbCdLth0IUQnYmwFyHaboCNliNcG2lvImx9Ryj2DQXVXxCKrSKcWI+0qpF2FGwH4SiULcFWgNcCVbZEuF4yVa5IJVUFTghwcR2JcL0E6xLGNYuxI12JdzmEeLcBJPc+CLu8FyrSnL7t7F5+7/HH32PZsq956aVxmGbblEvT1yudMAMsHo8Tj8dbnTA3bowybNj/x9Spx/DTn7bdKhr1B+2kBysJIZCOjajahti2Gbl1HWxZi9i+ARHbBk4MXMtLeNL1hvIbqi7hSVWXHA3ASCfIdAtS1bUwzVQJtv7jDVJlWQGGBDMEZgGuUYIb6oIj98GV3UD0APbGoAMyZ9cl1XzlxDDj6wnFVhOu/JLC6s8JVa/GjFcgEzGwLYSrULZAKq98KxzXm4bkSqj92kS5LjjeqDDlKITrTapxZDFOQUeSHfsQ32cAiR6HY+21P25RU/pCs7Of03UVl1/+PMOG9eDWW49rk9dIX7MKC/0d09EMOmE2lEwmqampwTRbPli4qirJGWf8kxNO2J/p04/NYHSenQbt2A5UbcfYthm5ZQNy0w+IresR1dvBjoNKgHLqEqOkrlUoXZCpZGeouhKrJPW91L1UXvKTNGhVytS9CaEQhApRZkdUQTfcgh4Q6ubdzK5g5NfIUi2LuQ4yvplw7DvC21ZQsP1zIttWYkS3IKwo0rLA8fpApeN6g4hc6SVShTcf1FHgSFwlEcprobquAAyUjOBEykl23I94zwHEex2G1bUvbmnn3STQ7NstZePGas4//2mefXY0gwZlfm+5gO2FCTph/lgymSQajba4hWlZDuPHL6a8vIRbbz0lY6UGx3Fgxw7YsgU2b8BYvxZj80ZkZQUiGQMnTu38iNol09y6xGiIVMJMJUejXgI0ZKq1Sb2Wpaz37wSETDBDqHAhFHZClXRDFXVHFXWDwu4Q7gxmYD4patrOlIsR20Jox3eEd3xJ4abPCG39ErOmApms8boylDcKF1JlW9tNLYIhvUUXlMRVBkKlSrs2oATKKMApLCfZoRfx3keR2O9wEl37oMo67SKBZk8/58svf8UDD7zJ8uUXUFaW2SUKdcLMAZZlUVNT06KEqZTiiiteZ82aGPfdN6rFtX83FsPduhV7wwbctT8g1/2A3LIZI1aNtBIYyvJmJ0oXYQgg1eoTqm4yfm1ypO57QtW1INM/N2XqXnjLxoRNCBWgSjqiyvZGlfVAlXeH4i5Q0AUiuj9RyxOOjRHdRGj7NxRs+pzIhk8JbfsWs2Y7MhFDuK438tZNrVTkeEsC1n0PUKlFNVzpDURC4BoFuIXlJDv1JL7/kcT7HInVbX/ckoYl3J3LtXUj99s3ed5xx0uEQg5//OPwjD5vwPbCBJ0wf6w1m0jfdde7LFy4ht/+9iyKiva8Oa9yXZwdldgVW7DXb8Ba8z3uhg2wbRsyFv//27v32LjKO43j33OZmy+JEydx4iQNlJIQAgQIBbFUpbvarlS65dYKSCW6rFqVqqy67IpKmwpFlUCo3faPrlpRJBrCAmJXbINKViqEViuhFrrNFkgIIWnSEhIHHGMwjh3bcznnffeP4xnPjG/j+8z4+UhR7Jk5x2OkzMN7+b0/vFwWNwjxHIvvWtzhwZ7vWhzXwXXD0lPc8lOsLsDwNKpjRw7odomK930PEjFsqglaVmBb2zEta6GlDZpWQqolCk8RKeFkB/F7O0h0Hyf13kHiZ47i953BSQ/i5IJos9BwWDp25DhGa4dHnA6Ajw0drHVwHQfjxgkaWwlWncfghVeSPv9ScqvXQypfyrKwZSmDgznuuONpvvvda7j99tk7+1qBWQemG5i7dx/hhz88wO7dt9LaOnq9LswFhL0fken+gMy7p8mdPk3Q1QX9/TiZDG4uh2MMnrWFQaGf/9qx0VKi5+A6Ab7nRUeNuS5O8UgRMzKijHlRMCaT2KYl2NaVsKIds3IttKyCJSsgpdGiyIyEIe5AN4n3/0zi3TdInjpEvOc07mAfTjYT1Xza6MAEx1iwBoeoVhTjgONhTf57wLqEyUZyTa2k119E5qIryH38YsLW1dE+gQWarj18uIt77nmOV165jQ0blszKPa21uK5bK82jQYE5WhiG9Pf3TykwX3jhJN/85kv87Ge3sGFDC0E6Q7anh0z3+wx0dJB+9z3CDz/ADAwOh2MW11pcx8Ez0cjRCw2u6+BZGz2Hg4/FNRbXc/Cd6PWuY/G84UPiPHB9P5pGTSWwzc3YlW2wuh2zah0sXwHLWmFK291FZNqsxR3oJfbBOyQ63iJ56gDxrhO4A7146Sw2DAEXTLTLNhqREq11GjAm6kdqHI9oj5FPkGwgt2IN2QsvJ3fxFYRrPw5LW0aOIJmnspTHHvsDv//9CX71q1tm5WhPYwy+7xOLTT4bVyUUmOWMMfT19VUcmP/7m3e4/cv/w85/2MTHEmcZOn2abM9H2MFByGZwwnDkjO/ocEn80EQhCbjG4Dru8MDQDu/DsdEBA6ZotOk5uPEYbkMCp7EZr20Vztp27Op2WNUGS5dDg4JRpNo4g/3EPjhJ4tRbJN4+SPLM2/i9H0I2M7zOGa1vWmOxBqyJjqcPLRjrYnCi5t7GIfDjhM0tBOsvINh8BWbzZdjlbTBcyziXZSlhaPj615/lc59bz44dM9/9r8CsA9Zazp49O2FgmiCg6+BBfvv0Pv75ySXcdkkfly0/F4WjjRb+PQcca6NAxMGzJnrOdUcC03VxwzAaadpo1Oi5Lp7n4ybi+KkUXmsr3tq1+OvW47atguXLcZuaqqJJs4hMnTvUj991isQ7h0kdf51Y5wn8c2chPYQxDta6WAthaDHWDo86IbAOxhmu/XSiUahJpQhaV2M3bcFu2YZdez5Oy/I5m649c6afO+74D37xi7/lk5+cWUcdBWYdyAdm/iDysZw5cIDXfv48/7i7ib+8IMu1K7pxrI3+GIPnRA2mCiNEp2gDq8PwOqWD44Afj+Mmk8RbWoivXk1i3Tpiq1fjrViB19KCM63jt0SkVjh9vcTfP0Xs7UOk/vgaXudp6D+LyQbR9KyNNg2FxgzP4LoYxxI6HsZxMO7wNK4fI2xphfXnw8VXwgWb8drW4cVjzGZw7tt3nIcffpn9+++gqWn664/GGOLx+KwdQzoPFJhj6e3tHTcwrTEc2vPffHXne3xipcsXNvaR6fkwWsDOjxbzgcnwBlULru8Ri8eJpVLEV6ygYe1akmvXEW9bRWz5cryGBhb6dA8RWWDW4vZ+iH/6T8SOHsT/0xu473fCYJogyA03rXMJneHRpxNN30YHE7lYx8U4TvR3UzN25Rq4eBv+psuIfezjuMkUs/E5s3Pnr2hqsjz66F9P+x4KzDpx9uzZcfu05XIhN//N0zg5w99dF5Dr7SXz0UfYbDZaa3RdPN8nlkiSaGok2dZG47p1NKxpJ7FyJfGWFtzamYIQkYWUy+F80IX/57dwj7yOd/I4tqcbE+YIrQUnGoGG1gy35XYIcbA+GMfHYqPusA0NmJZWvAsvJXbxlcTPuxB3ybJpH6wyOJjlttue5qGHruWLX7xwWvdQYNaJvr4+IKp52r9/P1deeSWu62KM4YknjnLvvS+zvi1GwsnhmSweIcmET0NzisaWZpqWNpNa2khDcyPJVIxEwh/+4xW+TiY94nGfZHLkuWTSLzwWi40/JSwii9RAP05nB87RA3DkIPbMaRjsx4Rm5KwEhs/8cizGAp5P6DiEjhM9H49jm5fhnbeJ5KVXET9/E/7ylThTDK5Dh87wrW/t5Xe/u53165un/KtYa4nH47XSCxMUmGPLN5EGeOihh3j++ee5++67uf3227HWpaNjkHM9ffR29TDYP4RNLcF4STJZSzodMDQUMDQUkk4HpNMBg4Ph8GMBmUxYeH5oKHo+/1j0+ui6MLTDAesVQrU4XEe+j4K3PJDLw7g4qEcC3C+5fzzuKaRFakUYQk839sQfMW+9in3nj/BRDyYzRIiLdfMvM9FBRJ6PtURTtp5L6ESbiVi6nNi680hechXJT2zBX7kGt8LwfPTR/bz++ileeOHmKZea1FjzaFBgjq2vr49cLlc4jqq3t5cf//jH/PrXv+b73/8+n/70p+f8PYShIZ0uDdLo69HhG4V0WPK6wcGg8Pr888XfF1+TyUR/Z7Mh8XgUoNGf0lFwPmyjgC4OZ49EIlYSvuXBnr/vSHDnr/OLashEZLrM4AD2vZPYowcJjr6Off8M4bmz0YjTdQlxwIbR6NNA6DlYz8c6DiY0WN/HbWzGa/8Yqc1X0Lj5CmIr10y4hBSGhq99bQ833XQe3/72tqm9XwVmfQiCgHQ6XdIuy3Ecuru7SafTrF+/fqHf4pwwxpaMcotHwcXhXR7O0Si6OMzDstH0yHWZTPH9R0K6eLSbHzmPNZU9EtCjg7d4JF7+WOkoWyEt9c0GAeb9dwnePkburT8QdJzAnO3BYgldFxuEWMdG4Tk8bWutIfA8rBP1znUam/BXtdOw5SoaNl5Kom0d3hin8nR29rF9+3+yd+8X2LatreL3WGPNo0GBOT5rLWEYks1mCcOwrChYZou1tmikHJaNikePkvOPZzJhYRRdPnrOP5bJlF8/MpqOxdxRIR2NpscO4/xrR6a/xw/j8oDPj9TnqhGvyESstZjeD8mdPkHu0B/InDhK2NNNGGSwOITWYB0v+mC3htBarOtiY3GsHW5p1thMfPVaGjZextKLt5FYvRY3NhKeL7xwjEce+R37999BY2NlmxoVmHVIwVl/rLVks2ZUmI41ei4fYReHc/nouXzUXTz9nU6HuK5TCOTiYB0rePNfFwf0WOvX5cFc/tpYrGZ2IMo8MQPnCDpPkT56gKEjB8n1nCEcGMR6LtZzMblctDnIWmwsgTUhUavPGNZ1IJEi3tbOkouuZMnFl5Nc2Y4bj3P//ftYtszjkUf+atL3kO/pq8CsUwpOmQlrLblc6br0eKPn8pFy8fT4WJvHSqfRR77OZMLhD6XSoJ14ynvsoC5evy4N/dL16WQyGknr30VtMNkMuc4Oho69ydCR18i8/x5maCCaujXDB8X7HkE2S4jF9X1sLIkxOXBcnEQD8ba1LNmyDX/dRr5672/41x9ez003XTDhz63BXpigwJw6BafUkiAw44bs6BH26Knt4rXp0lF16Yi6eG06v8N7rHAuDtvS6W6fWGzsKfHxNpKVTpVrh/dM2SAg98EZBo+/ycDhV8l0dpAb6McAeC5hEGDDAOvFsJ6PyaVxEsnokAQDblMTvXYJj70Y8Mxv/4nzNq0Z/2cpMBcXBafI2MLQlJRVTbYJrHxzWfHa9Ojp8ZFrRqa7A3I5UxbIE013jw7rsdeiRz9WGuD1G9ImDAk+7GLw7aP0v/kqQ50nyZ07G50uFIQQi0Wj0EwG63q4DU0YkyPevJwDJw1/7mvm31/6FxJNjWPevwZ7YYICc+bGCs4a2iYtUhfyO7wnmtouD+fSzWPlG89GX5cP53zwF+/wTia9USFbPAou3gk+uk567DAebyPZfO/wtmFIrqebc28f5ezhVxl67x2y/WexRJuDwmwGPA+/eRnx5qUkVraz6+en+IsvXs99D9ww9j0VmIubglNkcTFmZId3+eh5srrpwcHRa9Njr2cHJbvIM5loh3fpYSbjjYJLDzWJx2Njrl+PXzs9MlLPH0pgrSXb0825t4/R++Z+Bt49SW6wHxMExJYuJdW2Hq+xiaH4Snb82wn+a9/fc/nlK0f9t6vB5tGgwJx9Ck4RmSvFO7wnm9ou38FdvnFsvHrrsdamPc8pCeNk0icRd2n2BlgedLI8fZKUOYttWIZZsganaQX/17mcdGB57bUvj9qtXYOtvWCcwFQ/qRlwHAff9/E8jyAIFJwiMmuiaczoyMyWlvmZzszv8C6vby5ZYx7I0N/VxWDPR+SMTzaxjA2Bh+NALpfDdaGjo4NnnnmG6667jmuvvXZe3vt8qJrA3LFjB21tbdx7770L/VZKXH311ezevZstW7aM+xrHcYjFYvi+r+AUkZrlOA7xeLQTeenSiV45upSkq6uLxx77GXv27CGVSvGlL32Jiy66aM7e60Koik/z7u5unnjiCe6++24AfvKTn3DVVVeRSCS46667Kr5PT08Pt9xyC42NjWzYsIGnn356xtfdd9997Ny5s6L75IOzoaGhUKQbhmHhcHcRkXr1yCOPYK3lqaeeYu/evdx55500NzcXzuquB1WxhvmDH/yAY8eO8eijjwLw7LPP4rou+/btY2hoiMcff7yi+2zfvh1jDLt27eLAgQN8/vOf55VXXplwdDjZdel0mvb2dg4fPsyaNePXGo3FWlsYcRpjxm1ULSJSD/J5kj/dx/M8fN/H9/1am20b84O6Kn6D559/nuuvv77w/a233srNN99Ma2trxfcYGBhgz549PPDAAzQ1NfGpT32KG2+8kSeffHJG1yWTSbZt28aLL7445d+rfMQJ0QL4JP+TIiJSU/IbIPOzafF4nMbGRhoaGmqtD+aEquK3OHToEJs2bZrRPY4dO4bneWzcuLHw2NatWzl8+PCMr9u8eTMHDx6c9nsrDs58LZKCU0Rq2Vgh2dDQUHchWawqNv309vbS3Dz1Lt7Fzp07x9KyVeqlS5fS398/4+uam5vp7Oyc0fuD8TcHaapWRGpBfqo1fxhBPB4vTLcuhs+wqgjMZcuWTRpsk2lqaqKvr6/ksb6+vkmDuJLr+vv7aWlpmdH7K6bgFJFakQ/IvPyapOfV73GB46mKMfNll13GsWPHZnSPjRs3EgQBx48fLzx28ODBSTf8VHLdkSNH2Lp164ze31jGWuMMw1BTtSKyoKy1GGMKU66e55FMJmlsbCSZTOL7/qILS6iSwLzhhht46aWXCt8HQUA6nSYMQ8IwJJ1OEwRB4fm77rprVLlJY2Mjt956Kzt37mRgYICXX36Z5557jjvvvHNG12UyGV599VU++9nPzv4vPkzBKSILrTwkXdclmUzS1NREKpVatCFZrCoC8ytf+Qq//OUvGRoaAuDBBx8klUrxve99j6eeeopUKsWDDz5YeH1HRwfXXXfdqPs8/PDDDA0NsWrVKrZv385Pf/rTkpHidK7bu3cvn/nMZ2hvb5/tX3sUBaeIzKf8dKsxphCSiUSisMM1Fost+pAsVhV1mADf+c53WLVq1aQn/WSzWbZu3cobb7wxpbMJp3vdNddcw65du7jkkksqvma2RMdU5cjlcqrjFJFZkw9JANd1C3sq6nFn6zTp8PVapeAUkZnKh2S+l2/xgQL6PBlFgVnrFJwiMhXlZSDFI0l9dkyoek/6kcrk656KD0DQGqeIFCtek7TW4vs+qVSKxsZGEonEvJSD7Nixgx/96Edz+jOm4+qrr570MJuJKDBrkIJTRIpVUxlIeTON6TbFmIsmHFNppjGWqji4QKYnH5yxWKwwVasDEEQWh+LpVgDP8wpTrgv57//xxx/nhhtuIJVKAXDPPfcQj8fp6uoqNLfYunXrpDXy7e3t3H///YUmHJWa6OfdeOONfOMb36Czs3PKzTRAI8y6oBGnyOJQC2Ugxc00ptsUA+amCcdMmmmAArOulAdn/nBkBadIbaulbiDFzTSm2xRjuua6mYamZOtQ+VSt+nGK1J7yMpBaOei8uJnGdJtiTNdcN9NQYNYxBadIbamHbiDFzTSm2xRjuua6mUb1jONlzuT/4eW3lWuqVqR6VEMZyGwqbqYx3aYY0zXXzTQUmIuIglOkOlRTGchsK26mMd2mGDA3TThm2kxDgbkIKThF5t9i6QZS3kxjuk0x5qIJx0ybaehoPCkcuZfNZrHW1tR6iUg1y3++5tclPc8rOcO1XtVBMw2dJSsTKw5OoLA7T0SmRt1Aap4CUyqj4BSZOnUDqSsKTJkaBafIxNQNpG4pMGV6FJwiI4rPbwXwfZ9YLKaQrC8KTJkZBacsVuUHnedDstZqJKViCkyZHdZastksuVwOUHBKfarWbiAyLxSYMrsUnFJvFmsZiIyiwJS5oeCUWqcyECmjwJS5peCUWqIyEJmAAlPmhzGGXC6n4JSqozIQqZACU+aXglOqgcpAZBoUmLIwioNTZ9XKfFAZiMyQAlMWVj4483WcCk6ZTSoDkVmkwJTqoOCU2aIyEJkjCkypLgpOmS6VgcgcU2BKdVJwSiVUBiLzSIEp1U3BKeVUBiILRIEptUHBubipDESqgAJTaouCc/FQGYhUGQWm1CYFZ31SGYhUMQWm1DZjTMlZtQrO2qMyEKkRCkypD/ngDIJAJwfVCJWBSI1RYEp9UXBWN5WBSA1TYEp9UnBWD5WBSJ1QYEp9U3AuDJWBSB1SYMrioOCceyoDkTqnwJTFRcE5u1QGIouIAlMWp+LgBDWyngqVgcgipcCUxU3BWTmVgcgip8AUgdIDEPKhqeBUGYhIEQWmSDEFp8pARMahwBQZy2ILTpWBiExKgSkykTAMC2uc9RacKgMRmRIFpkgl6iU4VQYiMm0KTJGpqMXgVBmIyKxQYIpMRy0Ep8pARGaVAlNkJqotOFUGIjJnFJgis2Ehg1NlICLzQoEpMpvmKzhVBiIy7xSYInNhLoJTZSAiC0qBKTKXZhqcKgMRqRoKTJG5lt+IU2lwqgxEpCopMEXmSz44M5kMYRiOCk6VgYhUNQWmyHwrD05AZSAi1U+BKbJQ8sEZBIFCUqT6KTBFREQqMGZgasFERESkAgpMERGRCigwRUREKqDAFBERqYACU0REpAIKTBERkQooMEVERCqgwBQREamAAlNERKQCCkwREZEKKDBFREQqoMAUERGpgAJTRESkAgpMERGRCigwRUREKqDAFBERqYA/yfNqCS8iIoJGmCIiIhVRYIqIiFRAgSkiIlIBBaaIiEgFFJgiIiIVUGCKiIhU4P8B2PdiwZEU0/AAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "Q = np.array(Q)\n", + "ψ_00 = np.array((0.01, 0.01, 0.99))\n", + "ψ_01 = np.array((0.01, 0.99, 0.01))\n", + "ψ_02 = np.array((0.99, 0.01, 0.01))\n", + "\n", + "ax = unit_simplex(angle=50) \n", + "\n", + "def flow_plot(ψ, h=0.001, n=400, angle=50):\n", + " colors = cm.jet_r(np.linspace(0.0, 1, n))\n", + "\n", + " x_vals, y_vals, z_vals = [], [], []\n", + " for t in range(n):\n", + " x_vals.append(ψ[0])\n", + " y_vals.append(ψ[1])\n", + " z_vals.append(ψ[2])\n", + " ψ = ψ @ expm(h * Q)\n", + "\n", + " ax.scatter(x_vals, y_vals, z_vals, c=colors, s=20, alpha=0.2, depthshade=False)\n", + "\n", + "flow_plot(ψ_00)\n", + "flow_plot(ψ_01)\n", + "flow_plot(ψ_02)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(We use Euler discretization to trace out the flow.)\n", + "\n", + "\n", + "In this calculation, $Q$ was chosen with some care, so that the flow remains\n", + "in $\\mathcal D$.\n", + "\n", + "This raises a key question: what properties do we require on $Q$ such that\n", + "$\\psi_t$ is always in $\\mathcal D$?\n", + "\n", + "We seek necessary and sufficient conditions, so we can determine\n", + "exactly the set of continuous time Markov models on our state space.\n", + "\n", + "We will answer this question in stages.\n", + "\n", + "\n", + "\n", + "#### Preserving Distributions\n", + "\n", + "Recall that the linear update rule $\\psi \\mapsto \\psi P$ is invariant on\n", + "$\\mathcal D$\n", + "if and only if $P$ is a Markov matrix.\n", + "\n", + "So now we can rephrase our key question regarding invariance on $\\mathcal D$:\n", + "\n", + "What properties do we need to impose on $Q$ so that $P_t$ is a Markov matrix\n", + "for all $t$?\n", + "\n", + "A square matrix $Q$ is called a **transition rate matrix** if $Q$ has zero row\n", + "sums and $Q(i, j) \\geq 0$ whenever $i \\not= j$.\n", + "\n", + "(Some authors call a transition rate matrix a $Q$ matrix.)\n", + "\n", + "Having zero row sums can be expressed as $Q \\mathbb 1 = 0$.\n", + "\n", + "As a small exercise, you can check that the following is true\n", + "\n", + "$$\n", + " Q \\text{ has zero row sums }\n", + " \\iff\n", + " Q^k \\mathbb 1 = 0 \\text{ for all } k \\geq 1\n", + "$$ (zrsnec)\n", + "\n", + "**Theorem** If $Q$ is an $n \\times n$ matrix and $P_t := e^{tQ}$, then the\n", + "following statements are equivalent:\n", + "\n", + "1. $P_t$ is a Markov matrix for all $t$.\n", + "1. $Q$ is a transition rate matrix.\n", + "\n", + "*Proof:* Suppose first that $Q$ is a transition rate matrix and set $P_t =\n", + "e^{tQ}$ for all $t$.\n", + "\n", + "By the definition of the exponential function, for all $t \\geq 0$,\n", + "\n", + "$$\n", + " P_t \\mathbb 1 = \\mathbb 1 + tQ \\mathbb 1 + \\frac{1}{2!} t^2 Q^2 \\mathbb 1 + \\cdots\n", + "$$\n", + "\n", + "From {eq}`zrsnec`, we see that $P_t$ has unit row sums.\n", + "\n", + "As a second observation, note that, for any $i, j$ and $t \\geq 0$,\n", + "\n", + "$$\n", + " P_t(i, j) = \\mathbb 1\\{i = j\\} + t Q(i, j) + o(t)\n", + "$$ (otp)\n", + "\n", + "From {eq}`otp`, both off-diagonal and on-diagonal elements of $P_t$ are nonnegative.\n", + "\n", + "Hence $P_t$ is a Markov matrix.\n", + "\n", + "Regarding the converse implication, suppose that $P_t = e^{tQ}$ is a Markov\n", + "matrix for all $t$.\n", + "\n", + "Because $P_t$ has unit row sums and differentiation is linear, \n", + "we can employ the Kolmogorov backward equation to obtain\n", + "\n", + "$$\n", + " Q \\mathbb 1\n", + " = Q P_t \\mathbb 1\n", + " = \\left( \\frac{d}{d t} P_t \\right) \\mathbb 1\n", + " = \\frac{d}{d t} (P_t \\mathbb 1)\n", + " = \\frac{d}{d t} \\mathbb 1\n", + " = 0\n", + "$$\n", + "\n", + "Hence $Q$ has zero row sums.\n", + "\n", + "Moreover, in view of {eq}`otp`, the off diagonal elements of $Q$ must be positive.\n", + "\n", + "Hence $Q$ is a transition rate matrix.\n", + "\n", + "This completes the proof.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "### Representations\n", + "\n", + "Both the semigroup and its infintessimal generator are natural representations\n", + "of a given continuous time Marko chain.\n", + "\n", + "The semigroup can be constructed uniquely constructed from its generator $Q$\n", + "via $P_t = e^{tQ}$, and the generator can be recovered from the semigroup via\n", + "\n", + "$$\n", + " Q = \\frac{d}{d t} P_t \\big|_0\n", + "$$\n", + "\n", + "The last claim follows from $P_t = I$ and either the forward or backward\n", + "Kolmogorov equation.\n", + "\n", + "The semigroup is, in some sense, more informative than the generator, since it\n", + "allows us to update distributions to any point in time.\n", + "\n", + "But the generator is simpler and often more intuitive in particular\n", + "applications.\n", + "\n", + "### The Inventory Example\n", + "\n", + "Let's go back to the inventory example we discussed above.\n", + "\n", + "What is the infintessimal generator for this problem?\n", + "\n", + "The intuitive interpretation of a given generator $Q$ is that\n", + "\n", + "$$\n", + " Q(i, j) = \\text{ rate of flow from state $i$ to state $j$}\n", + "$$\n", + "\n", + "For example, if we observe the inventories of many firms independently\n", + "following the model above, then $Q(i, j) = r$ means that firms inventories\n", + "transition from state $i$ to state $j$ at a rate of $r$ per unit of time.\n", + "\n", + "[improve this, make it clearer]\n", + "\n", + "## Jump Chains: The General Case\n", + "\n", + "In this section we provide a natural way to construct continuous time\n", + "continuous time Markov chain on our finite state space $S$.\n", + "\n", + "Later we will show that *every* continuous time Markov chain on a finite\n", + "state space can be represented in this way.\n", + "\n", + "Intro model based on $\\lambda$ fixed and $\\Pi(x, y)$.\n", + "\n", + "Build $Q$ from this model, and then $P_t$ from $Q$ in the usual way.\n", + "\n", + "Build $P_t$ directly using probabilistic reasoning and show that the two\n", + "coincide.\n", + "\n", + "\n", + "\n", + "\n", + "## All CTMCs are Jump Chains \n", + "\n", + "Start with a Q matrix and construct the jump chain.\n", + "\n", + "When does $Q$ admit the decomposition $Q = \\lambda (\\Pi - I)$?\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## The Gillespie Algorithm\n", + "\n", + "Exponential clocks.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## Exercises\n", + "\n", + "The Markov semigroup properties lead to the KF and KB equations.\n", + "\n", + "When treating Kolmogorov forward and backward equations, do a first order\n", + "Euler discretization and link to the discrete case.\n", + "\n", + "\n", + "\n", + "## Solutions\n", + "\n", + "To be added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md:myst" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.md b/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.md new file mode 100644 index 0000000..4dfb466 --- /dev/null +++ b/code_book/.ipynb_checkpoints/kolmogorov_fwd-checkpoint.md @@ -0,0 +1,508 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: '0.9' + jupytext_version: 1.5.0 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# The Kolmogorov Forward Equation + + + +## Overview + +In this lecture we approach continuous time Markov chains from a more +analytical perspective. + +The emphasis will be on describing distribution flows through vector-valued +differential equations. + +These distribution flows show how the time $t$ distribution associated with a +given Markov chain $(X_t)$ changes over time. + +Density flows will be identified by ordinary differential equations in vector +space that are linear and time homogeneous. + +We will see that the solutions of these flows are described by Markov +semigroups. + +This leads us back to the theory we have already constructed -- some care will +be taken to clarify all the connections. + +In order to avoid being distracted by technicalities, we continue to defer our +treatment of infinite state spaces, assuming throughout this lecture that $|S| += n$. + +As before, $\mathcal D$ is the set of all distributions on $S$. + +We will use the following imports + +```{code-cell} ipython3 +import numpy as np +import scipy as sp +import matplotlib.pyplot as plt +import quantecon as qe +from numba import njit +from scipy.linalg import expm + +from mpl_toolkits.mplot3d import Axes3D +from mpl_toolkits.mplot3d.art3d import Poly3DCollection +``` + +## From Difference Equations to ODEs + +{ref}`Previously ` we generated this figure, which shows how distributions evolve over time for the inventory model under a certain parameterization: + +```{glue:figure} flow_fig +:name: "flow_fig" + +Probability flows for the inventory model. +``` + +(Hot colors indicate early dates and cool colors denote later dates.) + +We also learned how this flow can be described, at least indirectly, +through the Kolmogorov backward equation, which is an ODE. + +In this section we examine distribution flows and their connection to +ODEs and continuous time Markov chains more systematically. + +Although our initial discussion appears to be orthogonal to what has come +before, the connections and relationships will soon become clear. + + +### Review of the Discrete Time Case + +Let $(X_t)$ be a discrete time Markov chain with Markov matrix $P$. + +{ref}`Recall that `, in the discrete time case, the distribution $\psi_t$ of $X_t$ updates according to + +$$ + \psi_{t+1} = \psi_t P, + \qquad \psi_0 \text{ a given element of } \mathcal D, +$$ + +where distributions are understood as row vectors. + +Here's a visualization for the case $|S|=3$, so that $\mathcal D$ is the unit +simplex in $\mathbb R^3$. + +The initial condition is `` (0, 0, 1)`` and the Markov matrix is + +```{code-cell} ipython3 +P = ((0.9, 0.1, 0.0), + (0.4, 0.4, 0.2), + (0.1, 0.1, 0.8)) +``` + +```{code-cell} ipython3 +:tags: [hide-input] + +def unit_simplex(angle): + + fig = plt.figure(figsize=(8, 6)) + ax = fig.add_subplot(111, projection='3d') + + vtx = [[0, 0, 1], + [0, 1, 0], + [1, 0, 0]] + + tri = Poly3DCollection([vtx], color='darkblue', alpha=0.3) + tri.set_facecolor([0.5, 0.5, 1]) + ax.add_collection3d(tri) + + ax.set(xlim=(0, 1), ylim=(0, 1), zlim=(0, 1), + xticks=(1,), yticks=(1,), zticks=(1,)) + + ax.set_xticklabels(['$(1, 0, 0)$'], fontsize=12) + ax.set_yticklabels(['$(0, 1, 0)$'], fontsize=12) + ax.set_zticklabels(['$(0, 0, 1)$'], fontsize=12) + + ax.xaxis.majorTicks[0].set_pad(15) + ax.yaxis.majorTicks[0].set_pad(15) + ax.zaxis.majorTicks[0].set_pad(35) + + ax.view_init(30, angle) + + # Move axis to origin + ax.xaxis._axinfo['juggled'] = (0, 0, 0) + ax.yaxis._axinfo['juggled'] = (1, 1, 1) + ax.zaxis._axinfo['juggled'] = (2, 2, 0) + + ax.grid(False) + + return ax + + +def convergence_plot(ψ, n=14, angle=50): + + ax = unit_simplex(angle) + + P = ((0.9, 0.1, 0.0), + (0.4, 0.4, 0.2), + (0.1, 0.1, 0.8)) + + P = np.array(P) + + x_vals, y_vals, z_vals = [], [], [] + for t in range(n): + x_vals.append(ψ[0]) + y_vals.append(ψ[1]) + z_vals.append(ψ[2]) + ψ = ψ @ P + + ax.scatter(x_vals, y_vals, z_vals, c='darkred', s=80, alpha=0.7, depthshade=False) + + return ψ + +ψ = convergence_plot((0, 0, 1)) + +plt.show() +``` + +There's a sense in which a discrete time Markov chain "is" a homogeneous +linear difference equation in distribution space. + +To clarify this, suppose we +take $G$ to be a linear map from $\mathcal D$ to itself and +write down the difference equation + +$$ + \psi_{t+1} = G(\psi_t) + \quad \text{with } \psi_0 \in \mathcal D \text{ given}. +$$ (gdiff) + +Because $G$ is a linear map from a finite dimensional space to itself, it can +be represented by a matrix. + +Moreover, a matrix $P$ is a Markov matrix if and only if $\psi \mapsto +\psi P$ sends $\mathcal D$ into itself (check it if you haven't already). + +So, under the stated conditions, our difference equation {eq}`gdiff` uniquely +identifies a Markov matrix, along with an initial condition $\psi_0$. + +Together, these objects identify the joint distribution of a discrete time Markov chain, as {ref}`previously described `. + + +### Shifting to Continuous Time + +We have just argued that a discrete time Markov chain "is" a linear difference +equation evolving in $\mathcal D$. + +This strongly suggests that a continuous time Markov chain "is" a linear ODE +evolving in $\mathcal D$. + +In this scenario, + +1. distributions update according to an automous linear differential equation and +2. the vector field is such that trajectories remain in $\mathcal D$. + +This intuition is correct and highly beneficial. + +The rest of the lecture maps out the main ideas. + + + +## ODEs in Distribution Space + +Consider linear differential equation given by + +$$ + \psi_t' = \psi_t Q, + \qquad \psi_0 \text{ a given element of } \mathcal D, +$$ (ode_mc) + +where + +* $Q$ is an $n \times n$ matrix with suitable properties, +* distributions are again understood as row vectors, and +* derivatives are taken element by element, so that + +$$ + \psi_t' = + \begin{pmatrix} + \frac{d}{dt} \psi_t(1) & + \cdots & + \frac{d}{dt} \psi_t(n) + \end{pmatrix} +$$ + + +Using the matrix exponential, the unique solution to the initial value problem +{eq}`ode_mc` can be expressed as + +$$ + \psi_t = \psi_0 P_t + \quad \text{where } P_t := e^{tQ} +$$ (cmc_sol) + +To check this, we use {eq}`expoderiv` again to get + +$$ + \frac{d}{d t} P_t = Q e^{tQ} = e^{tQ} Q +$$ + +Recall that the first equality can be written as +$\frac{d}{d t} P_t = Q P_t$ and this is the Kolmogorov backward equation. + +The second equality can be written as + +$$ + \frac{d}{d t} P_t = P_t Q +$$ + +and is called the **Kolmogorov forward equation**. + +With $\psi_t$ set to $\psi_0 P_t$ and applying the Kolmogorov forward +equation, we obtain + +$$ + \frac{d}{d t} \psi_t + = \psi_0 \frac{d}{d t} P_t + = \psi_0 P_t Q + = \psi_t Q +$$ + +This confirms that {eq}`cmc_sol` solves {eq}`ode_mc`. + + +Here's an example of a distribution flow created by {eq}`ode_mc` with +initial condition `` (0, 0, 1)`` and + +```{code-cell} ipython3 +Q = ((2, -3, 1), + (3, -5, 2), + (1, -4, 3)) +``` + +```{code-cell} ipython3 +:tags: [hide-input] + +Q = np.array(Q) + +def P_t(ψ, t): + return ψ @ expm(t * Q) + +def flow_plot(ψ, h=0.01, n=200, angle=50): + + ax = unit_simplex(angle) + + Q = ((2, -3, 1), + (3, -5, 2), + (1, -4, 3)) + Q = np.array(Q) + + x_vals, y_vals, z_vals = [], [], [] + for t in range(n): + x_vals.append(ψ[0]) + y_vals.append(ψ[1]) + z_vals.append(ψ[2]) + ψ = P_t(ψ, h) + + ax.scatter(x_vals, y_vals, z_vals, c='darkred', s=80, alpha=0.7, depthshade=False) + + return ψ + +ψ = flow_plot(np.array((0, 0, 1))) + +plt.show() +``` + +(We use Euler discretization to trace out the flow.) + + +In this calculation, $Q$ was chosen with some care, so that the flow remains +in $\mathcal D$. + +This raises a key question: what properties do we require on $Q$ such that +$\psi_t$ is always in $\mathcal D$? + +We seek necessary and sufficient conditions, so we can determine +exactly the set of continuous time Markov models on our state space. + +We will answer this question in stages. + + + +#### Preserving Distributions + +Recall that the linear update rule $\psi \mapsto \psi P$ is invariant on +$\mathcal D$ +if and only if $P$ is a Markov matrix. + +So now we can rephrase our key question regarding invariance on $\mathcal D$: + +What properties do we need to impose on $Q$ so that $P_t$ is a Markov matrix +for all $t$? + +A square matrix $Q$ is called a **transition rate matrix** if $Q$ has zero row +sums and $Q(i, j) \geq 0$ whenever $i \not= j$. + +(Some authors call a transition rate matrix a $Q$ matrix.) + +Having zero row sums can be expressed as $Q \mathbb 1 = 0$. + +As a small exercise, you can check that the following is true + +$$ + Q \text{ has zero row sums } + \iff + Q^k \mathbb 1 = 0 \text{ for all } k \geq 1 +$$ (zrsnec) + +**Theorem** If $Q$ is an $n \times n$ matrix and $P_t := e^{tQ}$, then the +following statements are equivalent: + +1. $P_t$ is a Markov matrix for all $t$. +1. $Q$ is a transition rate matrix. + +*Proof:* Suppose first that $Q$ is a transition rate matrix and set $P_t = +e^{tQ}$ for all $t$. + +By the definition of the exponential function, for all $t \geq 0$, + +$$ + P_t \mathbb 1 = \mathbb 1 + tQ \mathbb 1 + \frac{1}{2!} t^2 Q^2 \mathbb 1 + \cdots +$$ + +From {eq}`zrsnec`, we see that $P_t$ has unit row sums. + +As a second observation, note that, for any $i, j$ and $t \geq 0$, + +$$ + P_t(i, j) = \mathbb 1\{i = j\} + t Q(i, j) + o(t) +$$ (otp) + +From {eq}`otp`, both off-diagonal and on-diagonal elements of $P_t$ are nonnegative. + +Hence $P_t$ is a Markov matrix. + +Regarding the converse implication, suppose that $P_t = e^{tQ}$ is a Markov +matrix for all $t$. + +Because $P_t$ has unit row sums and differentiation is linear, +we can employ the Kolmogorov backward equation to obtain + +$$ + Q \mathbb 1 + = Q P_t \mathbb 1 + = \left( \frac{d}{d t} P_t \right) \mathbb 1 + = \frac{d}{d t} (P_t \mathbb 1) + = \frac{d}{d t} \mathbb 1 + = 0 +$$ + +Hence $Q$ has zero row sums. + +Moreover, in view of {eq}`otp`, the off diagonal elements of $Q$ must be positive. + +Hence $Q$ is a transition rate matrix. + +This completes the proof. + + + + + + + + +### Representations + +Both the semigroup and its infintessimal generator are natural representations +of a given continuous time Marko chain. + +The semigroup can be constructed uniquely constructed from its generator $Q$ +via $P_t = e^{tQ}$, and the generator can be recovered from the semigroup via + +$$ + Q = \frac{d}{d t} P_t \big|_0 +$$ + +The last claim follows from $P_t = I$ and either the forward or backward +Kolmogorov equation. + +The semigroup is, in some sense, more informative than the generator, since it +allows us to update distributions to any point in time. + +But the generator is simpler and often more intuitive in particular +applications. + +### The Inventory Example + +Let's go back to the inventory example we discussed above. + +What is the infintessimal generator for this problem? + +The intuitive interpretation of a given generator $Q$ is that + +$$ + Q(i, j) = \text{ rate of flow from state $i$ to state $j$} +$$ + +For example, if we observe the inventories of many firms independently +following the model above, then $Q(i, j) = r$ means that firms inventories +transition from state $i$ to state $j$ at a rate of $r$ per unit of time. + +[improve this, make it clearer] + +## Jump Chains: The General Case + +In this section we provide a natural way to construct continuous time +continuous time Markov chain on our finite state space $S$. + +Later we will show that *every* continuous time Markov chain on a finite +state space can be represented in this way. + +Intro model based on $\lambda$ fixed and $\Pi(x, y)$. + +Build $Q$ from this model, and then $P_t$ from $Q$ in the usual way. + +Build $P_t$ directly using probabilistic reasoning and show that the two +coincide. + + + + +## All CTMCs are Jump Chains + +Start with a Q matrix and construct the jump chain. + +When does $Q$ admit the decomposition $Q = \lambda (\Pi - I)$? + + + + + +## The Gillespie Algorithm + +Exponential clocks. + + + + + + +## Exercises + +The Markov semigroup properties lead to the KF and KB equations. + +When treating Kolmogorov forward and backward equations, do a first order +Euler discretization and link to the discrete case. + + + +## Solutions + +To be added. + +```{code-cell} ipython3 + +``` diff --git a/code_book/.ipynb_checkpoints/markov_prop-checkpoint.ipynb b/code_book/.ipynb_checkpoints/markov_prop-checkpoint.ipynb new file mode 100644 index 0000000..0af7d8a --- /dev/null +++ b/code_book/.ipynb_checkpoints/markov_prop-checkpoint.ipynb @@ -0,0 +1,1077 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The Markov Property \n", + "\n", + "## Overview\n", + "\n", + "\n", + "A continuous time stochastic process is said to have the Markov property if\n", + "that the past and future are independent given the current state.\n", + "\n", + "(A more formal definition is provide below.)\n", + "\n", + "As we will see, the Markov property imposes a great deal of structure on\n", + "continuous time processes.\n", + "\n", + "This structure leads to an elegant and powerful collection of results on\n", + "evolution and dynamics.\n", + "\n", + "At the same time, the Markov property is general enough to cover many applied\n", + "problems, as described in {doc}`the introduction `.\n", + "\n", + "\n", + "\n", + "### Setting\n", + "\n", + "In this lecture and much of what follows, the state space where dynamics\n", + "evolve will be a [countable set](https://en.wikipedia.org/wiki/Countable_set),\n", + "denoted henceforth by $S$, with typical elements $x, y$.\n", + "\n", + "(Note that \"countable\" is understood to include finite.)\n", + "\n", + "Regarding notation, in what follows, $\\sum_{x \\in S}$ is abbreviated to\n", + "$\\sum_x$, the supremum $\\sup_{x \\in S}$ is abbreviated to $\\sup_x$ and so on.\n", + "\n", + "A **distribution** on $S$ is a function $\\phi$ from $S$ to $\\mathbb R_+$ with\n", + "$\\sum_x \\phi(x) = 1$.\n", + "\n", + "Let $\\mathcal D$ denote the set of all distributions on $S$.\n", + "\n", + "In expressions involving matrix algebra, we **always treat distributions as row\n", + "vectors**.\n", + "\n", + "We will use the following imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy as sp\n", + "import matplotlib.pyplot as plt\n", + "import quantecon as qe\n", + "from numba import njit\n", + "\n", + "from scipy.linalg import expm\n", + "from scipy.stats import binom\n", + "\n", + "from matplotlib import cm\n", + "from mpl_toolkits.mplot3d import Axes3D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Markov Processes\n", + "\n", + "We now introduce the definition of Markov processes, first reviewing the\n", + "discrete case and then shifting to continuous time.\n", + "\n", + "\n", + "\n", + "\n", + "### Discrete Time, Finite State \n", + "\n", + "The simplest Markov processes are those with a discrete time parameter and finite state space.\n", + "\n", + "Assume for now that $S$ has $n$ elements and let $P$ be a Markov matrix (i.e., nonnegative with unit row sums) of size $n \\times n$.\n", + "\n", + "We write $P(x, y)$ for a typical element of $P$.\n", + "\n", + "(Most of the time, this is more convenient than using symbols such as $P_{ij}$, and it aligns better with the infinite state case.)\n", + "\n", + "In applications, $P(x, y)$ represents the probability of transitioning from $x$ to\n", + "$y$ in one step.\n", + "\n", + "#### Markov Chains\n", + "\n", + "A Markov chain $(X_t)_{t \\in \\mathbb Z_+}$ on finite set $S$ with Markov matrix $P$ is a\n", + "sequence of random variables satisfying \n", + "\n", + "$$\n", + " \\mathbb P\\{X_{t+1} = y \\,|\\, X_0, X_1, \\ldots, X_t \\} = P (X_t, y)\n", + "$$ (markovpropd)\n", + "\n", + "with probability one for all $y \\in S$ and any $t \\in \\mathbb Z_+$.\n", + "\n", + "In addition to connecting probabilities to the Markov matrix,\n", + "{eq}`markovpropd` says that the process depends on its history only through\n", + "the current state.\n", + "\n", + "We [recall that](https://python.quantecon.org/finite_markov.html), if $X_t$\n", + "has distribution $\\psi$, then $X_{t+1}$ has distribution $\\psi P$.\n", + "\n", + "Since $\\psi$ is understood as a row vector, the meaning is\n", + "\n", + "$$\n", + " (\\psi P)(y) = \\sum_x \\psi(x) P(x, y) \n", + " \\qquad (y \\in S)\n", + "$$ (update_rule)\n", + "\n", + "(jdfin)=\n", + "#### The Joint Distribution\n", + "\n", + "In general, for given Markov matrix $P$, there can be many Markov chains\n", + "$(X_t)$ that satisfy {eq}`markovpropd`.\n", + "\n", + "This is due to the more general observation that, for a given distribution\n", + "$\\phi$, we can construct many random variables having distribution $\\phi$.\n", + "\n", + "(The exercises below ask for one example.)\n", + "\n", + "Hence $P$ is, in a sense, a more primitive object than $(X_t)$.\n", + "\n", + "There is another way to see the fundamental importance of $P$: by constructing the joint distribution.\n", + "\n", + "Consider the infinite sequence space $S^\\infty := S \\times S \\times \\cdots$.\n", + "\n", + "Together with an initial condition $\\psi \\in\n", + "\\mathcal D$, a Markov matrix $P$ defines a distribution $\\mathbf P_\\psi$\n", + "over $S^\\infty$ such that any\n", + "Markov chain $(X_t)$ satisfying {eq}`markovpropd` and $X_0 \\sim \\psi$ has\n", + "$\\mathbf P_\\psi$ as its joint distribution.\n", + "\n", + "The last statement is equivalent to \n", + "\n", + "$$\n", + " \\mathbb P\\{ X_{t_1} = y_{t_1}, \\ldots, X_{t_k} = y_{t_k} \\}\n", + " =\n", + " \\mathbf P_\\psi\\{ (x_t) \\in S^\\infty \\,:\\, \n", + " x_{t_i} = y_{t_i} \\text{ for } i = 1, \\ldots m\\}\n", + "$$ (jointdeq)\n", + "\n", + "for any $m$ positive integers $t_i$ and $m$ elements $y_{t_i}$ of the state space $S$.\n", + "\n", + "\n", + "(Joint distributions of discrete time processes\n", + "are uniquely defined by their values at finite collections of times in the sense of\n", + "{eq}`jointdeq` --- see, for example, Theorem 7.2 of {cite}`walsh2012knowing`.)\n", + "\n", + "\n", + "To construct the joint distribution over $S^\\infty$, one first constructs a\n", + "finite dimensional version over the Cartiesian product $S^{n+1}$\n", + "via\n", + "\n", + "$$\n", + " \\mathbf P_\\psi^n(x_0, x_1, \\ldots, x_n)\n", + " = \\psi(x_0)\n", + " P(x_0, x_1)\n", + " \\times \\cdots \\times\n", + " P(x_{n-1}, x_n)\n", + "$$ (mathjointd)\n", + "\n", + "Then one shows that for any Markov chain $(X_t)$ satisfying {eq}`markovpropd`\n", + "and $X_0 \\sim \\psi$, the restriction $(X_0, \\ldots, X_n)$ has joint\n", + "distribution $\\mathbf P_\\psi^n$.\n", + "\n", + "This is a solved exercise below.\n", + "\n", + "The last remaining step is to show that the family $(\\mathbf P_\\psi^n)$ defined at each $n \\in \\mathbb N$ extends uniquely to a distribution $\\mathbf P_\\psi$ over the infinite\n", + "sequences in $S^\\infty$.\n", + "\n", + "That this is true follows from a well known [theorem of Kolmogorov](https://en.wikipedia.org/wiki/Kolmogorov_extension_theorem).\n", + "\n", + "Hence $P$ defines the joint distribution $\\mathbf P_\\psi$ when paired with any initial condition $\\psi$.\n", + "\n", + "\n", + "\n", + "### Extending to Countable State Spaces\n", + "\n", + "When $S$ is infinite, we cannot view $P$ as a matrix. \n", + "\n", + "Instead, we introduce the notion of a **Markov kernel** on $S$, which is a function\n", + "$P$ from $S \\times S$ to $\\mathbb R_+$ satisfying\n", + "\n", + "$$\n", + " \\sum_y P(x, y) = 1 \n", + " \\text{ for all } x \\in S\n", + "$$\n", + "\n", + "This is a natural extension of matrices with nonnegative elements and unit row\n", + "sums.\n", + "\n", + "The definition of a Markov chain $(X_t)_{t \\in \\mathbb Z_+}$ on $S$ with Markov kernel $P$ is exactly as in {eq}`markovpropd`.\n", + "\n", + "Given Markov kernel $P$ and $\\phi \\in \\mathcal D$, we define $\\phi P$ by\n", + "{eq}`update_rule`.\n", + " \n", + "Then, as before, $\\phi P$ can be understood as the distribution of \n", + "$X_{t+1}$ when $X_t$ has distribution $\\phi$.\n", + "\n", + "The function $\\phi P$ is in $\\mathcal D$, since, by {eq}`update_rule`, it is\n", + "nonnegative and\n", + "\n", + "$$\n", + " \\sum_y (\\phi P)(y) \n", + " = \\sum_y \\sum_x P(x, y) \\phi(x)\n", + " = \\sum_x \\sum_y P(x, y) \\phi(x)\n", + " = \\sum_x \\phi(x)\n", + " = 1\n", + "$$ \n", + "\n", + "Swapping the order of infinite sums is justified here by the fact that all\n", + "elements are nonnegative (a version of Tonelli's theorem).\n", + "\n", + "We can take products of Markov kernels that are analogous to matrix products.\n", + "\n", + "In particular, if $P$ and $Q$ are Markov kernels on $S$, then, for $(x, y)$ in $S\n", + "\\times S$,\n", + "\n", + "$$\n", + " (P Q)(x, y) := \\sum_z P(x, z) Q(z, y)\n", + "$$ (kernprod)\n", + "\n", + "It is not difficult to check that the product $P Q$ is again a Markov kernel on $S$.\n", + "\n", + "The operation {eq}`kernprod` is analogous to matrix multiplication, so that\n", + "elements of $P^k$, the $k$-th product of $P$ with itself, retain the finite\n", + "state interpretation of $k$ step transition probabilities.\n", + "\n", + "For example, we have\n", + "\n", + "$$\n", + " P^k(x, y) \n", + " = (P^{k-j} P^j)(x, y) = \\sum_z P^{k-j}(x, z) P^j(z, y)\n", + "$$ (kernprodk)\n", + "\n", + "which is a version of the discrete time Chapman-Kolmogorov equation.\n", + "\n", + "Equation {eq}`kernprodk` can be obtained from the law of total probability: if\n", + "$(X_t)$ is a Markov chain with Markov kernel $P$ and initial condition $X_0 =\n", + "x$, then \n", + "\n", + "$$\n", + " \\mathbb P\\{X_k = y\\}\n", + " = \\sum_z \\mathbb P\\{X_k = y \\,|\\, X_j=z\\} \\mathbb P\\{X_j=z\\}\n", + "$$\n", + "\n", + "\n", + "All of the {ref}`preceding discussion ` on the connection between $P$\n", + "and the joint distribution of $(X_t)$ when $S$ is finite carries over \n", + "to the current setting.\n", + "\n", + "\n", + "\n", + "\n", + "### The Continuous Time Case\n", + "\n", + "A **continuous time stochastic process** on $S$ is a collection $(X_t)$ of $S$-valued\n", + "random variables $X_t$ defined on a common probability space and indexed by $t\n", + "\\in \\mathbb R_+$.\n", + "\n", + "Let $I$ be the Markov kernel on $S$ defined by $I(x,y) = \\mathbb 1\\{x = y\\}$.\n", + "\n", + "A **transition semigroup** is a family $(P_t)$ of Markov kernels\n", + "on $S$ satisfying $P_0 = I$ and\n", + "\n", + "$$\n", + " P_{s + t} = P_s P_t\n", + " \\qquad (s, t \\geq 0)\n", + "$$ (chapkol_ct)\n", + "\n", + "The interpretation of $P_t(x, y)$ is the probability of moving from state $x$\n", + "to state $y$ in $t$ units of time.\n", + "\n", + "Equation {eq}`chapkol_ct`, which is known as the semigroup property of\n", + "$(P_t)$, is another version of the Chapman-Kolmogorov equation.\n", + "\n", + "This becomes clearer if we write it more explicitly as\n", + "\n", + "$$\n", + " P_{s+t}(x, y) \n", + " = \\sum_z P_s(x, z) P_t(z, y)\n", + "$$ (chapkol_ct2)\n", + "\n", + "A stochastic process $(X_t)$ is called a (time homogeneous) **Markov process** on $S$\n", + "with transition semigroup $(P_t)$ if\n", + "\n", + "$$\n", + " \\mathbb P\\{X_{s + t} = y \\,|\\, \\mathcal F_s \\}\n", + " = P_t (X_s, y)\n", + "$$ (markovprop)\n", + "\n", + "with probability one for all $y \\in S$ and $s, t \\geq 0$.\n", + "\n", + "Here $\\mathcal F_s$ is the history $(X_r)_{r \\leq s}$ of the process up until\n", + "time $s$.\n", + "\n", + "If you are an economist you might call $\\mathcal F_s$ the \"information set\" at time\n", + "$s$.\n", + "\n", + "If you are familiar with measure theory, you can understand $\\mathcal F_s$ as\n", + "the $\\sigma$-algebra generated by $(X_r)_{r \\leq s}$.\n", + "\n", + "\n", + "Analogous to the discrete time case, the joint\n", + "distribution of $(X_t)$ is determined by its transition semigroup plus an\n", + "initial condition.\n", + "\n", + "To prove this, one first builds finite dimensional distributions using\n", + "expressions similar to {eq}`mathjointd`.\n", + "\n", + "Next the Kolmogorov extension theorem is applied, similar to the discrete time case\n", + "(see, e.g., Corollary 6.4 of {cite}`le2016brownian`).\n", + "\n", + "\n", + "\n", + "\n", + "### Example: Poisson Processes\n", + "\n", + "The Poisson process discussed in our {doc}`previous lecture ` is a\n", + "Markov process on state space $\\mathbb Z_+$.\n", + "\n", + "To obtain the transition semigroup, we observe that, for $k \\geq j$,\n", + "\n", + "$$\n", + " \\mathbb P\\{N_{s + t} = k \\,|\\, N_s = j\\}\n", + " = \\mathbb P\\{N_{s + t} - N_s = k - j \\,|\\, N_s = j\\}\n", + " = \\mathbb P\\{N_{s + t} - N_s = k - j\\}\n", + "$$\n", + "\n", + "where the last step is due to independence of increments.\n", + "\n", + "From stationarity of increments we have\n", + "\n", + "$$\n", + " \\mathbb P\\{N_{s + t} - N_s = k - j\\}\n", + " = \\mathbb P\\{N_t = k - j\\}\n", + " = e^{-\\lambda t} \\frac{ (\\lambda t)^{k-j} }{(k-j)!}\n", + "$$\n", + "\n", + "In summary, the transition semigroup is\n", + "\n", + "$$\n", + " P_t(j, k) \n", + " = e^{-\\lambda t} \\frac{ (\\lambda t)^{k-j} }{(k-j)!} \n", + "$$ (poissemi)\n", + "\n", + "whenever $j \\leq k$ and $P_t(j, k) = 0$ otherwise.\n", + "\n", + "This chain of equalities was obtained with $N_s = j$ for arbitrary $j$, so we\n", + "can replace $j$ with $N_s$ in {eq}`poissemi` to verify the Markov property {eq}`markovprop` for the Poisson process.\n", + "\n", + "Under {eq}`poissemi`, each $P_t$ is a Markov kernel and $(P_t)$ is a\n", + "transition semigroup.\n", + "\n", + "The proof of the semigroup property is a solved exercise below.\n", + "\n", + "(In {eq}`poissemi` we use the convention that $0^0 = 1$, which leads to $P_0 = I$.)\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "### Example: Failure of the Markov Property\n", + "\n", + "Let's look at how the Markov property can fail, via an intuitive rather than\n", + "formal discussion.\n", + "\n", + "Let $(X_t)$ be a continuous time stochastic process with state space $S = \\{0, 1\\}$.\n", + "\n", + "The process starts at $0$ and updates at follows:\n", + "\n", + "1. Draw $W$ independently from a fixed Pareto distribution.\n", + "1. Hold $(X_t)$ in its current state for $W$ units of time and then switch\n", + " to the other state.\n", + "1. Go to step 1.\n", + "\n", + "What is the probability that $X_{s+h} = i$ given both the history $(X_r)_{r \\leq s}$ and current information $X_s = i$?\n", + "\n", + "If $h$ is small, then this is close to the\n", + "probability that there are zero switches over the time interval $(s, s+h]$.\n", + "\n", + "To calculate this probability, it would be helpful to know how long the\n", + "state has been at current state $i$.\n", + "\n", + "This is because the Pareto distribution {ref}`is not memoryless `.\n", + "\n", + "(With a Pareto distribution, if we know that $X_t$ has been at $i$ for a long\n", + "time, then a switch in the near future becomes more likely.)\n", + "\n", + "As a result, the history prior to $X_s$ is useful for predicting $X_{s+h}$,\n", + "even when we know $X_s$.\n", + "\n", + "Thus, the Markov property fails.\n", + "\n", + "\n", + "\n", + "### Restrictions Imposed by the Markov Property\n", + "\n", + "From the discussion above, we see that, for continuous time Markov chains,\n", + "the length of time between jumps must be memoryless.\n", + "\n", + "Recall that the {ref}`only ` memoryless distribution supported on $\\mathbb R_+$ is the exponential distribution.\n", + "\n", + "[XX why isn't this link working??]\n", + "\n", + "Hence, a continuous time Markov chain waits at states for an\n", + "exponential amount of time and then jumps.\n", + "\n", + "The way that the new state is chosen must also satisfy the Markov property,\n", + "which adds another restriction. \n", + "\n", + "In summary, we already understand the following about continuous time Markov chains:\n", + "\n", + "1. Holding times are independent exponential draws.\n", + "1. New states are chosen in a ``Markovian'' way, independent of the past given the current state.\n", + "\n", + "We just need to clarify the details in these steps to have a complete description.\n", + "\n", + "\n", + "We start this process with an example.\n", + "\n", + "\n", + "(inventory_dynam)=\n", + "## A Model of Inventory Dynamics\n", + "\n", + "\n", + "Let $X_t$ be the inventory of a firm at time $t$, taking values in the\n", + "integers $0, 1, \\ldots, b$.\n", + "\n", + "If $X_t > 0$, then a customer arrives after $W$\n", + "units of time, where $W \\sim E(\\lambda)$ for some fixed $\\lambda > 0$.\n", + "\n", + "Upon arrival, each customer purchases $\\min\\{U, X_t\\}$ units, where $U$ is an\n", + "IID draw from the geometric distribution started at 1 rather than 0:\n", + "\n", + "$$\n", + " \\mathbb P\\{U = k\\} = (1-\\alpha)^{k-1} \\alpha\n", + " \\qquad (k = 1, 2, \\ldots, \\; \\alpha \\in (0, 1))\n", + "$$\n", + "\n", + "If $X_t = 0$, then no customers arrive and the firm places an order for $b$ units.\n", + "\n", + "The order arrives after a delay of $D$ units of time, where $D \\sim E(\\lambda)$.\n", + "\n", + "(We use the same $\\lambda$ here just for convenience, to simplify the exposition.)\n", + "\n", + "### Representation\n", + "\n", + "The inventory process jumps to a new value either when a new customer arrives\n", + "or when new stock arrives.\n", + "\n", + "Between these arrival times it is constant.\n", + "\n", + "Hence, to track $X_t$, it is enough to track the jump times and the new values\n", + "taken at the jumps.\n", + "\n", + "In what follows, we denote the jump times by $\\{J_k\\}$ and the values at jumps\n", + "by $\\{Y_k\\}$.\n", + "\n", + "Then we construct the state process via\n", + "\n", + "$$\n", + " X_t = \\sum_{k \\geq 0} Y_k \\mathbb 1\\{J_k \\leq t < J_{k+1}\\}\n", + " \\qquad (t \\geq 0)\n", + "$$ (xfromy)\n", + "\n", + "\n", + "\n", + "### Simulation\n", + "\n", + "Let's simulate this process, starting at $X_0 = 0$.\n", + "\n", + "As above,\n", + "\n", + "* $J_k$ is the time of the $k$-th jump (up or down) in inventory.\n", + "* $Y_k$ is the size of the inventory after the $k$-th jump.\n", + "* $(X_t)$ is defined from these objects via {eq}`xfromy`.\n", + "\n", + "Here's a function that generates and returns one path $t \\mapsto X_t$.\n", + "\n", + "(We are not aiming for computational efficiency at this stage.)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def sim_path(T=10, seed=123, λ=0.5, α=0.7, b=10):\n", + " \"\"\"\n", + " Generate a path for inventory starting at b, up to time T.\n", + "\n", + " Return the path as a function X(t) constructed from (J_k) and (Y_k).\n", + " \"\"\"\n", + "\n", + " J, Y = 0, b\n", + " J_vals, Y_vals = [J], [Y]\n", + " np.random.seed(seed)\n", + "\n", + " while True:\n", + " W = np.random.exponential(scale=1/λ) # W ~ E(λ)\n", + " J += W\n", + " J_vals.append(J)\n", + " if J >= T:\n", + " break\n", + " else:\n", + " # Update Y\n", + " if Y == 0:\n", + " Y = b\n", + " else:\n", + " U = np.random.geometric(α)\n", + " Y = Y - min(Y, U)\n", + " Y_vals.append(Y)\n", + " \n", + " Y_vals = np.array(Y_vals)\n", + " J_vals = np.array(J_vals)\n", + "\n", + " def X(t):\n", + " if t == 0.0:\n", + " return Y_vals[0]\n", + " else:\n", + " k = np.searchsorted(J_vals, t)\n", + " return Y_vals[k-1]\n", + "\n", + " return X\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot the process $(X_t)$ using the ``step`` method of ``ax``." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX4AAAEGCAYAAABiq/5QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAUDklEQVR4nO3df7BcZX3H8fe3EAzUpAJJ25CQJmasAzIl0jtFwTqOPzpKNbGOrYVCk6JNOxSLtrTFwRZr25naSkfrWPQWBX9Qi/FHCSgUxp9jJ9IGGhESFKVKL0SIUbxUGwH99o89sZfL3eTsvXvOuXuf92tmZ/eec/acb549+8nZZ8+eJzITSVI5fqzrAiRJ7TL4JakwBr8kFcbgl6TCGPySVJjDuy6gjmXLluWaNWu6LkOSRsott9zyzcxcPn36SAT/mjVr2LFjR9dlSNJIiYivzzTdrh5JKozBL0mFMfglqTAGvyQVxuCXpMI0FvwR8e6IeCAibp8y7ZiIuCki7qruj25q+5KkmTV5xH8l8MJp0y4CPpGZTwE+Uf0tSWpRY+fxZ+ZnI2LNtMkbgedUj98DfBr4k6Zq+PNr72DXfZNNrX4gG9ev5KxTV3ddhqQB/dPN93DNzntnnDeq7+u2+/h/KjP3AFT3P9lvwYjYEhE7ImLH3r17WyuwCbv2TPbdcSTNb9fsvJddex5/ADnK7+t5+8vdzBwHxgHGxsZmNVrMJS952lBrmq1XvHN71yVImoMTVyzl6t955mOmjfL7uu0j/vsjYgVAdf9Ay9uXpOK1HfzbgE3V403ANS1vX5KK1+TpnB8AtgNPjYiJiHgl8NfACyLiLuAF1d+SpBY1eVbPmX1mPa+pbUqSDs1f7kpSYQx+SSqMwS9JhTH4JakwBr8kFcbgl6TCGPySVBiDX5IKY/BLUmEMfkkqjMEvSYUx+CWpMAa/JBVm3o7AtdDs2jN5yBF7RnX8TqlUdd7Xg2ojBwz+Fmxcv/KQyxwY09Pgl0ZDnff1oNrKAYO/BWeduvqQL+Qoj98plajO+3pQbeWAffySVBiDX5IKY/BLUmEMfkkqjMEvSYUx+CWpMAa/JBXG4Jekwhj8klQYg1+SCmPwS1JhDH5JKozBL0mFMfglqTAGvyQVppPgj4jXRsQdEXF7RHwgIhZ3UYcklaj14I+IlcDvA2OZeRJwGPDrbdchSaXqagSuw4EjI+IR4Cjgvo7qmFeaGL9zrhwHWGrX9Bw48bilXPKSpw11G60Hf2beGxFvBu4B/he4MTNvnL5cRGwBtgCsXr3wg6eJ8TvnynGApXa1lQOtB39EHA1sBNYCDwJbI+LszHz/1OUycxwYBxgbG8u262xbE+N3ztV8+/QhLXRt5UAXX+4+H/ivzNybmY8AHwFO66AOSSpSF8F/D/CMiDgqIgJ4HrC7gzokqUitB39m3gx8CLgV+GJVw3jbdUhSqTo5qyczLwEu6WLbklQ6f7krSYUx+CWpMAa/JBXG4Jekwhj8klQYg1+SCmPwS1JhDH5JKozBL0mFMfglqTAGvyQVxuCXpMIY/JJUmK7G3NWImI/jAKsZjq9cDoNffc3HcYDVDMdXLovBr77m4zjAaoaf6spiH78kFcbgl6TCGPySVBiDX5IKY/BLUmEMfkkqjMEvSYUx+CWpMAa/JBXG4Jekwhj8klQYg1+SCmPwS1JhDH5JKkwnwR8RT4qID0XEnRGxOyKe2UUdklSirq7H/1bghsx8eUQcARzVUR2SVJxawR8Rx2Tmt4axwYhYCjwb2AyQmQ8DDw9j3ZJmb5jDbDqM4/xWt6vn5ojYGhFnRETMcZtPBvYCV0TEf0bE5RHx49MXiogtEbEjInbs3bt3jpuUdDAb16/kxBVLh7KuXXsmuWbnvUNZl5pRt6vnZ4HnA+cCb4uIq4ErM/PLs9zmKcCrM/PmiHgrcBHwp1MXysxxYBxgbGwsZ7EdSTUNc5hNh3Gc/2od8WfPTZl5JvAqYBPw7xHxmVl8MTsBTGTmzdXfH6L3H4EkqQV1+/iPBc4GzgHuB14NbAPWA1uBtXU3mJnfiIj/joinZuaXgOcBuwYtXJI0O3W7erYD7wNempkTU6bviIh3zGK7rwauqs7ouRv4rVmsQ5I0C4cM/og4DLguM/9ipvmZ+aZBN5qZO4GxQZ8nSZq7Q/bxZ+YPgJNbqEWS1IK6XT07I2Ibvf787x6YmJkfaaQqSVJj6gb/McA+4LlTpiVg8EvSiKkV/Jnpl6+StEDUOo8/IlZFxEcj4oGIuD8iPhwRq5ouTpI0fHUv2XAFvfP2jwNWAtdW0yRJI6Zu8C/PzCsy89HqdiWwvMG6JEkNqRv834yIsyPisOp2Nr0veyVJI6Zu8J8L/BrwDWAP8HL8ta0kjaS6p3Men5kbpk6IiNOBe4ZfkiSpSXWP+N9Wc5okaZ476BF/dcnl04DlEfEHU2YtBQ5rsjBJUjMO1dVzBPDEarklU6ZP0uvnlySNmIMGf2Z+BvhMRFyZmV9vqSZJI27Q8Xsdo7dddb/cfUJEjANrpj4nM5/b9xmSirRx/cqBlt+1ZxLA4G9R3eDfCrwDuBz4QXPlSBp1g47f6xi97asb/I9m5mWNViJJakXd0zmvjYjzImJFRBxz4NZoZZKkRtQ94t9U3f/RlGkJPHm45UiSmlb3evxrmy5EktSOutfjPyoiXl+d2UNEPCUiXtxsaZKkJgxyPf6H6f2KF2AC+MtGKpIkNapu8K/LzL8BHgHIzP8ForGqJEmNqRv8D0fEkfS+0CUi1gHfb6wqSVJj6p7V8wbgBuD4iLgKOB3Y3FBNkqQG1T2r58aIuAV4Br0ungsy85uNViZJakSt4I+IbcAHgG2Z+d1mS5IkNaluH/+lwC8CuyJia0S8PCIWN1iXJKkhdbt6Dlye+TDgucBvA++mNyCLJGmE1P1yl+qsnpcArwBOAd7TVFGSpObU7eO/GjiV3pk9bwc+nZk/bLIwSVIz6h7xXwGclZlDuxZ/1W20A7g3M738gyS1pG4f/w0RcVpErOGxI3C9dw7bvgDYjd8TSFKr6nb1vA9YB+zk/0fgSmBWwR8Rq4BfBv4K+IPZrEOSNDt1u3rGgBMzM4e03bcAfwws6bdARGwBtgCsXu1YnJI0LHXP478d+OlhbLC6nPMDmXnLwZbLzPHMHMvMseXLlw9j05Ik6h/xL6P3461/Z8rF2TJzwyy2eTqwISLOABYDSyPi/Zl59izWJUka0CAXaRuKzHwd8DqAiHgOcKGhL0ntGeSXu5KkBeCgwR8Rn8vMZ0XEQ1TX4j8wC8jMnNOpmJn5aeDTc1mHJGkwBw3+zHxWdd/37BtJ0mipe1aPJGmBMPglqTAGvyQVxuCXpMIY/JJUGINfkgpj8EtSYQx+SSqMwS9JhTH4JakwBr8kFcbgl6TC1L0evyQ1ZteeSV7xzu1zXs/G9Ss561SHaj0Ug19SpzauXzmU9ezaMwlg8Ndg8Evq1Fmnrh5KWA/jE0Mp7OOXpMIY/JJUGINfkgpj8EtSYQx+SSqMwS9JhTH4JakwBr8kFcbgl6TCGPySVBiDX5IKY/BLUmEMfkkqjMEvSYVpPfgj4viI+FRE7I6IOyLigrZrkKSSdXE9/keBP8zMWyNiCXBLRNyUmbs6qEWSitN68GfmHmBP9fihiNgNrAQMfklzMtchHEsZurHTEbgiYg3wdODmGeZtAbYArF698F8ISXMz1yEcSxq6sbPgj4gnAh8GXpOZk9PnZ+Y4MA4wNjaWLZcnacTMdQjHkoZu7OSsnohYRC/0r8rMj3RRgySVqouzegJ4F7A7M/+u7e1LUum6OOI/HTgHeG5E7KxuZ3RQhyQVqYuzej4HRNvblST1+MtdSSqMwS9JhTH4JakwBr8kFcbgl6TCGPySVBiDX5IKY/BLUmEMfkkqjMEvSYUx+CWpMAa/JBXG4Jekwhj8klQYg1+SCmPwS1JhDH5JKkzrI3ANyyOPPMLExAT79+/vupSBLV68mFWrVrFo0aKuS5FUoJEN/omJCZYsWcKaNWvojd8+GjKTffv2MTExwdq1a7suR1KBRrarZ//+/Rx77LEjFfoAEcGxxx47kp9UJC0MIxv8wMiF/gGjWrekhWGkg1+SNDiDX5IKY/BLUmEM/jm67LLLOO+883709+tf/3rOOeecDiuSpIMz+Odo06ZNXHvttTz44INcd911fOxjH2N8fLzrsiSpr5E9j3++OOqoozjzzDO5+OKLuf7667nppps48sgjuy5LkvpaEMH/59fewa77Joe6zhOPW8olL3larWXPPfdcTjjhBK655hrWrVv3o+nf/va3Ofroo4dalyTNlV09Q/DGN76R5cuX8+ijjz5m+mtf+9qOKpKk/hbEEX/dI/MmXHrppezfv58PfvCDXHLJJbzsZS8D4IYbbuDOO+/kzW9+MxdeeGFn9UnSdAsi+LvyyU9+kiuuuILt27ezZMkSJicn2blzJ+vXr2fZsmWcffbZnH/++V2XKUmP0UlXT0S8MCK+FBFfiYiLuqhhru655x5e9apXsXXrVpYsWQLABRdcwFve8hYAbrvtNk4++eQuS5SkGbV+xB8RhwFvB14ATAD/ERHbMnNX27XMxerVq7n77rsfM23z5s1s3rwZgGXLlnH55ZezbNkyTjjhhA4qlKSZddHV8wvAVzLzboCI+GdgIzBSwX8oGzZsYMOGDV2XIWkAu/ZM8op3bn/ctBNXLO2oomZ0Efwrgf+e8vcEcOr0hSJiC7AFekfXktSkjetXzjj9xBVL+84bVV0E/0zXJM7HTcgcB8YBxsbGHjdfkobprFNXc9apZRxkdvHl7gRw/JS/VwH3dVCHJBWpi+D/D+ApEbE2Io4Afh3YNpsVZY7mB4FRrVvSwtB68Gfmo8D5wL8Cu4EPZuYdg65n8eLF7Nu3b+RC9MCYu4sXL+66FEmF6uQHXJn5ceDjc1nHqlWrmJiYYO/evUOqqj2LFy9m1apVXZchqVAj+8vdRYsWsXbt2q7LkKSR40XaJKkwBr8kFcbgl6TCxCicFRMRe4Gvz/Lpy4BvDrGcYbGuwVjXYKxrMPO1LphbbT+TmcunTxyJ4J+LiNiRmWNd1zGddQ3GugZjXYOZr3VBM7XZ1SNJhTH4JakwJQT/eNcF9GFdg7GuwVjXYOZrXdBAbQu+j1+S9FglHPFLkqYw+CWpMAsm+A81gHv0/H01/7aIOKWFmo6PiE9FxO6IuCMiLphhmedExHciYmd1+7Om66q2+7WI+GK1zR0zzO+ivZ46pR12RsRkRLxm2jKttFdEvDsiHoiI26dMOyYiboqIu6r7o/s896D7YgN1/W1E3Fm9Th+NiCf1ee5BX/MG6npDRNw75bU6o89z226vq6fU9LWI2NnnuU2214zZ0No+lpkjfwMOA74KPBk4AvgCcOK0Zc4Arqc3AtgzgJtbqGsFcEr1eAnw5Rnqeg5wXQdt9jVg2UHmt95eM7ym36D3A5TW2wt4NnAKcPuUaX8DXFQ9vgh402z2xQbq+iXg8Orxm2aqq85r3kBdbwAurPE6t9pe0+ZfCvxZB+01Yza0tY8tlCP+Hw3gnpkPAwcGcJ9qI/De7Pk88KSIWNFkUZm5JzNvrR4/RG/8gVEZvLP19prmecBXM3O2v9iek8z8LPCtaZM3Au+pHr8HeOkMT62zLw61rsy8MXvjXAB8nt6odq3q0151tN5eB0REAL8GfGBY26vrINnQyj62UIJ/pgHcpwdsnWUaExFrgKcDN88w+5kR8YWIuD4intZSSQncGBG3RG9g++k6bS96I7P1e0N20V4AP5WZe6D3xgV+coZlum63c+l9UpvJoV7zJpxfdUG9u0+3RZft9YvA/Zl5V5/5rbTXtGxoZR9bKMFfZwD3WoO8NyEingh8GHhNZk5Om30rve6Mk4G3Af/SRk3A6Zl5CvAi4Pci4tnT5nfZXkcAG4CtM8zuqr3q6rLdLgYeBa7qs8ihXvNhuwxYB6wH9tDrVpmus/YCzuTgR/uNt9chsqHv02aYNlCbLZTgrzOAeyeDvEfEInov7FWZ+ZHp8zNzMjP/p3r8cWBRRCxruq7MvK+6fwD4KL2Pj1N10l6VFwG3Zub902d01V6V+w90d1X3D8ywTFf72SbgxcBvZNURPF2N13yoMvP+zPxBZv4Q+Mc+2+uqvQ4HXgZc3W+ZpturTza0so8tlOCvM4D7NuA3q7NVngF858BHqqZUfYjvAnZn5t/1Weanq+WIiF+g95rsa7iuH4+IJQce0/ty8PZpi7XeXlP0PRLror2m2AZsqh5vAq6ZYZk6++JQRcQLgT8BNmTm9/osU+c1H3ZdU78T+pU+22u9vSrPB+7MzImZZjbdXgfJhnb2sSa+se7iRu8slC/T+7b74mra7wK/Wz0O4O3V/C8CYy3U9Cx6H8FuA3ZWtzOm1XU+cAe9b+Y/D5zWQl1Prrb3hWrb86K9qu0eRS/If2LKtNbbi95/PHuAR+gdYb0SOBb4BHBXdX9MtexxwMcPti82XNdX6PX5HtjH3jG9rn6vecN1va/ad26jF0wr5kN7VdOvPLBPTVm2zfbqlw2t7GNeskGSCrNQunokSTUZ/JJUGINfkgpj8EtSYQx+SSqMwS9NExFPiojzqsfHRcSHuq5JGiZP55Smqa6dcl1mntRxKVIjDu+6AGke+mtgXXWd9ruAEzLzpIjYTO9qiYcBJ9G79swRwDnA94EzMvNbEbGO3o/flgPfA347M+9s/58hzcyuHunxLqJ3Sej1wB9Nm3cScBa967b8FfC9zHw6sB34zWqZceDVmfnzwIXAP7RStVSTR/zSYD6VveunPxQR3wGuraZ/Efi56mqLpwFbq0sKATyh/TKl/gx+aTDfn/L4h1P+/iG999OPAQ9WnxakecmuHunxHqI3HN7AsndN9f+KiF+FH41dfPIwi5PmyuCXpsnMfcC/VQN0/+0sVvEbwCsj4sCVHYc2lKA0DJ7OKUmF8Yhfkgpj8EtSYQx+SSqMwS9JhTH4JakwBr8kFcbgl6TC/B/ivnpBTVUjrwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "T = 20\n", + "X = sim_path(T=T)\n", + "\n", + "grid = np.linspace(0, T, 100)\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.step(grid, [X(t) for t in grid], label=\"$X_t$\")\n", + "\n", + "ax.set(xlabel=\"time\", ylabel=\"inventory\")\n", + "\n", + "ax.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, inventory falls and then jumps back up to $b$.\n", + "\n", + "\n", + "\n", + "### The Embedded Jump Chain\n", + "\n", + "In models such as the one described above, the embedded discrete time \n", + "process $(Y_n)$ is called the \"embedded jump chain\".\n", + "\n", + "It is easy to see that $(Y_n)$ is discrete time finite state Markov chain.\n", + "\n", + "Its Markov matrix $K$ is\n", + "given by $K(x, y) = \\mathbb 1\\{y=b\\}$ when $x=0$ and, when $0 < x \\leq b$, \n", + "\n", + "$$\n", + " K(x, y)\n", + " =\n", + " \\begin{cases}\n", + " \\mathbb 0 & \\text{ if } y \\geq x\n", + " \\\\\n", + " \\mathbb P\\{x - U = y\\} = (1-\\alpha)^{x-y-1} \\alpha \n", + " & \\text{ if } 0 < y < x\n", + " \\\\\n", + " \\mathbb P\\{U \\geq x\\} = (1-\\alpha)^{x-1}\n", + " & \\text{ if } y = 0\n", + " \\end{cases}\n", + "$$ (ijumpkern)\n", + "\n", + "\n", + "\n", + "\n", + "### Markov Property\n", + "\n", + "The inventory model just described has the Markov property precisely because\n", + "\n", + "1. the jump chain $(Y_n)$ is Markov in discrete time and\n", + "1. the holding times are independent exponential draws.\n", + "\n", + "Rather than providing more details on these points here, let us first describe\n", + "a more general setting where the arguments will be clearer and more useful.\n", + "\n", + "\n", + "\n", + "## Jump Processes with Constant Rates\n", + "\n", + "The examples we have focused on so far are special cases of Markov processes\n", + "with constant jump intensities.\n", + "\n", + "This processes turn out to be very representative (although the constant jump intensity will later be relaxed).\n", + "\n", + "Let's now summarize the model and its properties.\n", + "\n", + "\n", + "### Construction\n", + "\n", + "The data for a Markov process on $S$ with constant jump rates are \n", + "\n", + "* a parameter $\\lambda > 0$ called the **jump rate**, which governs the jump\n", + " intensities and\n", + "* a Markov kernel $K$ on $S$, called the **jump kernel**.\n", + "\n", + "To run the process we also need an initial condition $\\psi \\in \\mathcal D$.\n", + "\n", + "The process $(X_t)$ is constructed by holding at each state for an\n", + "exponential amount of time, with rate $\\lambda$, and then updating to a\n", + "new state via $K$.\n", + "\n", + "In more detail, the construction is\n", + "\n", + "1. draw $Y_0$ from $\\psi$ \n", + "1. set $n = 1$ and $J_0 = 0$\n", + "1. draw $W_n$ from Exp$(\\lambda)$ and set $J_n = J_{n-1} + W_n$\n", + "1. set $X_t = Y_{n-1}$ for all $t$ such that $J_{n-1} \\leq t < J_n$.\n", + "1. draw $Y_n$ from $K(Y_{n-1}, \\cdot)$ \n", + "1. set $n = n+1$ and go to step 3.\n", + "\n", + "An alternative, more parsimonious way to express the same process is to take \n", + "\n", + "* $(N_t)$ to be a Poisson process with rate $\\lambda$ and\n", + "* $(Y_n)$ to be a discrete time Markov chain with kernel $K$\n", + "\n", + "and then set\n", + "\n", + "$$\n", + " X_t := Y_{N_t} \\text{ for all } t \\geq 0\n", + "$$\n", + "\n", + "As before, the discrete time process $(Y_n)$ is called the **embedded jump chain**.\n", + "\n", + "(Not to be confused with $(X_t)$, which is often called a \"jump process\" due\n", + "to the fact that it changes states with jumps.)\n", + "\n", + "The draws $(W_n)$ are called the **wait times** or **holding times**.\n", + "\n", + "\n", + "### Examples\n", + "\n", + "The Poisson process with rate $\\lambda$ is a jump process on $S = \\mathbb Z_+$.\n", + "\n", + "The holding times are obviously exponential with constant rate $\\lambda$.\n", + "\n", + "The jump kernel is just $K(i, j) = \\mathbb 1\\{j = i+1\\}$, so that the state\n", + "jumps up by one at every $J_n$.\n", + "\n", + "The inventory model is also a jump process with constant rate $\\lambda$, this\n", + "time on $S = \\{0, 1, \\ldots, b\\}$.\n", + "\n", + "The jump kernel (or matrix in this case) was given in {eq}`ijumpkern`.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "### Markov Property\n", + "\n", + "Let's show that the jump process $(X_t)$ constructed above satisfies the\n", + "Markov property, and obtain the transition semigroup at the same time.\n", + "\n", + "We will use two facts:\n", + "\n", + "* the jump chain $(Y_n)$ has the Markov property in discrete\n", + " time and\n", + "* the Poisson process has stationary independent increments.\n", + "\n", + "From these facts it is intuitive that the distribution of $X_{t+s}$ given\n", + "the whole history $\\mathcal F_s = \\{ (N_r)_{r \\leq s}, (Y_n)_{n \\leq N_s} \\}$\n", + "depends only on $X_s$.\n", + "\n", + "Indeed, if we know $X_s$, then we can simply {ref}`restart ` the\n", + "Poisson process from $N_s$ and then update the jump chain using $K$ each time a\n", + "jump occurs, starting from $X_s$.\n", + "\n", + "Let's write this more mathematically.\n", + "\n", + "Fixing $y \\in S$ and $s, t \\geq 0$, we have\n", + "\n", + "\n", + "$$\n", + " \\mathbb P\\{X_{s + t} = y \\,|\\, \\mathcal F_s \\}\n", + " = \\mathbb P\\{Y_{N_{s + t}} = y \\,|\\, \\mathcal F_s \\}\n", + " = \\mathbb P\\{Y_{N_s + N_{s + t} - N_s} = y \\,|\\, \\mathcal F_s \\}\n", + "$$\n", + "\n", + "{ref}`Recalling ` that $N_{s + t} - N_s$ is Poisson distributed with rate $t \\lambda$, independent of the history $\\mathcal F_s$, we can write the display above as \n", + "\n", + "$$\n", + " \\mathbb P\\{X_{s + t} = y \\,|\\, \\mathcal F_s \\}\n", + " =\n", + " \\sum_{k \\geq 0}\n", + " \\mathbb P\\{Y_{N_s + k} = y \\,|\\, \\mathcal F_s \\}\n", + " \\frac{(t \\lambda )^k}{k!} e^{-t \\lambda}\n", + "$$\n", + "\n", + "Because the jump chain is Markov with kernel $K$, we can simplify further to\n", + "\n", + "\n", + "$$\n", + " \\mathbb P\\{X_{s + t} = y \\,|\\, \\mathcal F_s \\}\n", + " = \\sum_{k \\geq 0}\n", + " K^k(Y_{N_s}, y) \\frac{(t \\lambda )^k}{k!} e^{-t \\lambda}\n", + " = K^k(X_s, y) \\frac{(t \\lambda )^k}{k!} e^{-t \\lambda}\n", + "$$\n", + "\n", + "Since the expression above depends only on $X_s$,\n", + "we have proved that $(X_t)$ has the Markov property.\n", + "\n", + "\n", + "(consjumptransemi)=\n", + "### Transition Semigroup\n", + "\n", + "The transition semigroup can be obtained from our final result, conditioning\n", + "on $X_s = x$ to get\n", + "\n", + "$$\n", + " P^t(x, y) = \\mathbb P\\{X_{s + t} = y \\,|\\, X_s = x \\}\n", + " = e^{-t \\lambda} \\sum_{k \\geq 0}\n", + " K^k(x, y) \\frac{(t \\lambda )^k}{k!} \n", + "$$\n", + "\n", + "If $S$ is finite, we can write this in matrix form and use the definition of\n", + "the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential) to\n", + "get\n", + "\n", + "$$\n", + " P^t \n", + " = e^{-t \\lambda}\n", + " \\sum_{k \\geq 0}\n", + " \\frac{(t \\lambda K)^k}{k!} \n", + " = e^{-t \\lambda} e^{t \\lambda K}\n", + " = e^{t \\lambda (K - I)}\n", + "$$\n", + "\n", + "This is a simple and elegant representation of the transition semigroup that\n", + "makes it easy to understand and analyze distribution dynamics.\n", + "\n", + "For example, if $X_0$ has distribution $\\psi$, then $X_t$ has distribution\n", + "\n", + "$$\n", + " \\psi P_t = \\psi e^{t \\lambda (K - I)}\n", + "$$ (distflowconst)\n", + "\n", + "We just need to plug in $\\lambda$ and $K$ to obtain the entire flow $t \\mapsto \\psi P_t$.\n", + "\n", + "We will soon extend this representation to the case where $S$ is infinite.\n", + "\n", + "\n", + "(invdistflows)=\n", + "## Distribution Flows for the Inventory Model\n", + "\n", + "Let's apply these ideas to the inventory model described above.\n", + "\n", + "We fix \n", + "\n", + "* the parameters $\\alpha$, $b$ and $\\lambda$ in the inventory model and\n", + "* an initial condition $X_0 \\sim \\psi_0$, where $\\psi_0$ is an arbitrary\n", + "distribution on $S$.\n", + "\n", + "The state $S$ is set to $\\{0, \\ldots, b\\}$ and the kernel $K$ is defined by\n", + "{eq}`ijumpkern`.\n", + "\n", + "Now we run time forward.\n", + "\n", + "We are interesting in computing the flow of distributions $t \\mapsto \\psi_t$,\n", + "where $\\psi_t$ is the distribution of $X_t$.\n", + "\n", + "According to the theory developed above, we have two options:\n", + "\n", + "Option 1 is to use simulation.\n", + "\n", + "The first step is to simulate many independent observations the process $(X_t^m)_{m=1}^M$.\n", + "\n", + "(Here $m$ indicates simulation number $m$, which you might think of as the outcome\n", + "for firm $m$.)\n", + "\n", + "Next, for any given $t$, we define $\\hat \\psi_t \\in \\mathcal D$ as the\n", + "histogram of observations at time $t$, or, equivalently the cross-sectional\n", + "distribution at $t$:\n", + "\n", + "$$\n", + " \\hat \\psi_t(x) := \\frac{1}{M} \\sum_{m=1}^M \\mathbb 1\\{X_t = x\\}\n", + " \\qquad (x \\in S)\n", + "$$\n", + "\n", + "Then $\\hat \\psi_t(x)$ will be close to $\\mathbb P\\{X_t = x\\}$ by the law of\n", + "large numbers.\n", + "\n", + "In other words, in the limit we recover $\\psi_t$.\n", + "\n", + "\n", + "Option 2 is to insert the parameters into the right hand side of {eq}`distflowconst`\n", + "and compute $\\psi_t$ as $\\psi_0 P_t$.\n", + "\n", + "Let's try option 2, with $\\alpha = 0.6$, $\\lambda = 0.5$ and $b=10$.\n", + "\n", + "For the initial distribution we pick a binomial distribution.\n", + "\n", + "Since we cannot compute the entire uncountable flow $t \\mapsto \\psi_t$, we\n", + "iterate forward 200 steps at time increments $h=0.1$.\n", + "\n", + "In the figure below, hot colors indicate initial conditions and early dates (so that the\n", + "distribution \"cools\" over time)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " α = 0.6\n", + " λ = 0.5\n", + " b = 10\n", + " n = b + 1\n", + " states = np.arange(n)\n", + " I = np.identity(n)\n", + "\n", + " K = np.zeros((n, n))\n", + " K[0, -1] = 1\n", + " for i in range(1, n):\n", + " for j in range(0, i):\n", + " if j == 0:\n", + " K[i, j] = (1 - α)**(i-1)\n", + " else:\n", + " K[i, j] = α * (1 - α)**(i-j-1)\n", + "\n", + "\n", + " def P_t(ψ, t):\n", + " return ψ @ expm(t * λ * (K - I))\n", + "\n", + " def plot_distribution_dynamics(ax, ψ_0, steps=200, step_size=0.1):\n", + " ψ = ψ_0\n", + " t = 0.0\n", + " colors = cm.jet_r(np.linspace(0.0, 1, steps))\n", + "\n", + " for i in range(steps):\n", + " ax.bar(states, ψ, zs=t, zdir='y', \n", + " color=colors[i], alpha=0.8, width=0.4)\n", + " ψ = P_t(ψ, t=step_size)\n", + " t += step_size\n", + "\n", + " ax.set_xlabel('inventory')\n", + " ax.set_ylabel('$t$')\n", + "\n", + "\n", + " ψ_0 = binom.pmf(states, n, 0.25)\n", + " fig = plt.figure(figsize=(8, 6))\n", + " ax = fig.add_subplot(111, projection='3d')\n", + " plot_distribution_dynamics(ax, ψ_0)\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the exercises below you are asked to implement option 1 and check that the\n", + "figure looks the same.\n", + "\n", + "\n", + "\n", + "\n", + "## Exercises\n", + "\n", + "### Exercise 1\n", + "\n", + "Consider the binary (Bernoulli) distribution where outcomes $0$ and $1$ each have\n", + "probability $0.5$.\n", + "\n", + "Construct two different random variables with this distribution.\n", + "\n", + "\n", + "\n", + "### Exercise 2\n", + "\n", + "Show by direct calculation that the Poisson kernels $(P_t)$ defined in \n", + "{eq}`poissemi` satisfy the semigroup property {eq}`chapkol_ct2`.\n", + "\n", + "Hints\n", + "\n", + "* Recall that $P_t(j, k) = 0$ whenever $j > k$.\n", + "* Consider using the [binomial\n", + " formula](https://en.wikipedia.org/wiki/Binomial_theorem).\n", + "\n", + "\n", + "### Exercise 3\n", + "\n", + "Consider the distribution over $S^{n+1}$ previously shown in {eq}`mathjointd`, which is\n", + "\n", + "$$\n", + " \\mathbf P_\\psi^n(x_0, x_1, \\ldots, x_n)\n", + " = \\psi(x_0)\n", + " P(x_0, x_1)\n", + " \\times \\cdots \\times\n", + " P(x_{n-1}, x_n)\n", + "$$ \n", + "\n", + "Show that, for any Markov chain $(X_t)$ satisfying {eq}`markovpropd`\n", + "and $X_0 \\sim \\psi$, the restriction $(X_0, \\ldots, X_n)$ has joint\n", + "distribution $\\mathbf P_\\psi^n$.\n", + "\n", + "### Exercise 4\n", + "\n", + "Replicate, as best you can, the figure produced from the {ref}`discussion on distribution flows `, this time using option 1.\n", + "\n", + "You will need to use a suitably large sample.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## Solutions\n", + "\n", + "### Solution to Exercise 1\n", + "\n", + "This is easy.\n", + "\n", + "One example is to take $U$ to be uniform on $(0, 1)$ and set $X=0$ if $U <\n", + "0.5$ and $1$ otherwise.\n", + "\n", + "Then $X$ has the desired distribution.\n", + "\n", + "Alternatively, we could take $Z$ to be standard normal and set $X=0$ if $Z <\n", + "0$ and $1$ otherwise.\n", + " \n", + "\n", + "### Solution to Exercise 2\n", + "\n", + "Fixing $s, t \\in \\mathbb R_+$ and $j \\leq k$, we have \n", + "\n", + "$$\n", + "\\begin{aligned}\n", + " \\sum_{i \\geq 0} P_s(j, i) P_t(i, k)\n", + " & = \n", + " e^{-\\lambda (s+t)} \n", + " \\sum_{j \\leq i \\leq k}\n", + " \\frac{ (\\lambda s)^{i-j} }{(i-j)!} \n", + " \\frac{ (\\lambda t)^{k-i} }{(k-i)!} \n", + " \\\\\n", + " & = \n", + " e^{-\\lambda (s+t)} \\lambda^{k-j}\n", + " \\sum_{0 \\leq \\ell \\leq k-j}\n", + " \\frac{ s^\\ell }{\\ell!} \n", + " \\frac{ t^{k-j - \\ell} }{(k-j - \\ell)!} \n", + " \\\\\n", + " & = \n", + " e^{-\\lambda (s+t)} \\lambda^{k-j}\n", + " \\sum_{0 \\leq \\ell \\leq k-j}\n", + " \\binom{k-j}{\\ell}\n", + " \\frac{s^\\ell t^{k-j - \\ell}}{(k-j)!} \n", + "\\end{aligned}\n", + "$$\n", + "\n", + "Applying the binomial formula, we can write this as \n", + "\n", + "$$\n", + " \\sum_{i \\geq 0} P_s(j, i) P_t(i, k)\n", + " =\n", + " e^{-\\lambda (s+t)} \n", + " \\frac{(\\lambda (s + t))^{k-j}}{(k-j)!}\n", + " = P_{s+t}(j, k)\n", + "$$\n", + "\n", + "Hence {eq}`chapkol_ct2` holds, and the semigroup property is satisfied.\n", + "\n", + "\n", + "### Solution to Exercise 3 \n", + "\n", + "Let $(X_t)$ be a Markov chain satisfying {eq}`markovpropd` and $X_0 \\sim \\psi$.\n", + "\n", + "When $n=0$, we have $\\mathbf P_\\psi^n = \\mathbf P_\\psi^0 = \\psi$, and this\n", + "agrees with the distribution of the restriction $(X_0, \\ldots, X_n) = (X_0)$.\n", + "\n", + "Now suppose the same is true at arbitrary $n-1$, in the sense that\n", + "the distribution of $(X_0, \\ldots, X_{n-1})$ is equal to $\\mathbf P_\\psi^{n-1}$ as\n", + "defined above.\n", + "\n", + "Then \n", + "\n", + "$$\n", + " \\mathbb P \\{X_0 = x_0, \\ldots, X_n = x_n\\}\n", + " = \\mathbb P \\{X_n = x_n \\,|\\, X_0 = x_0, \\ldots, X_{n-1} = x_{n-1} \\}\n", + " \\\\\n", + " \\times \\mathbb P \\{X_0 = x_0, \\ldots, X_{n-1} = x_{n-1}\\}\n", + "$$\n", + "\n", + "From the Markov property and the induction hypothesis, the right hand side is\n", + "\n", + "$$\n", + " P (x_{n-1}, x_n )\n", + " \\mathbf P_\\psi^n(x_0, x_1, \\ldots, x_{n-1})\n", + " =\n", + " P (x_n, x_{n+1} )\n", + " \\psi(x_0)\n", + " P(x_0, x_1)\n", + " \\times \\cdots \\times\n", + " P(x_{n-1}, x_{n-1})\n", + "$$\n", + "\n", + "The last expression equals $\\mathbf P_\\psi^n$, which concludes the proof.\n", + "\n", + "\n", + "### Solution to Exercise 4 \n", + "\n", + "[To be added.]" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,md:myst" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/code_book/.ipynb_checkpoints/markov_prop-checkpoint.md b/code_book/.ipynb_checkpoints/markov_prop-checkpoint.md new file mode 100644 index 0000000..1da8b28 --- /dev/null +++ b/code_book/.ipynb_checkpoints/markov_prop-checkpoint.md @@ -0,0 +1,1014 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: '0.9' + jupytext_version: 1.5.0 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# The Markov Property + +## Overview + + +A continuous time stochastic process is said to have the Markov property if +that the past and future are independent given the current state. + +(A more formal definition is provide below.) + +As we will see, the Markov property imposes a great deal of structure on +continuous time processes. + +This structure leads to an elegant and powerful collection of results on +evolution and dynamics. + +At the same time, the Markov property is general enough to cover many applied +problems, as described in {doc}`the introduction `. + + + +### Setting + +In this lecture and much of what follows, the state space where dynamics +evolve will be a [countable set](https://en.wikipedia.org/wiki/Countable_set), +denoted henceforth by $S$, with typical elements $x, y$. + +(Note that "countable" is understood to include finite.) + +Regarding notation, in what follows, $\sum_{x \in S}$ is abbreviated to +$\sum_x$, the supremum $\sup_{x \in S}$ is abbreviated to $\sup_x$ and so on. + +A **distribution** on $S$ is a function $\phi$ from $S$ to $\mathbb R_+$ with +$\sum_x \phi(x) = 1$. + +Let $\mathcal D$ denote the set of all distributions on $S$. + +In expressions involving matrix algebra, we **always treat distributions as row +vectors**. + +We will use the following imports + +```{code-cell} ipython3 +import numpy as np +import scipy as sp +import matplotlib.pyplot as plt +import quantecon as qe +from numba import njit + +from scipy.linalg import expm +from scipy.stats import binom + +from matplotlib import cm +from mpl_toolkits.mplot3d import Axes3D +``` + +## Markov Processes + +We now introduce the definition of Markov processes, first reviewing the +discrete case and then shifting to continuous time. + + + + +### Discrete Time, Finite State + +The simplest Markov processes are those with a discrete time parameter and finite state space. + +Assume for now that $S$ has $n$ elements and let $P$ be a Markov matrix (i.e., nonnegative with unit row sums) of size $n \times n$. + +We write $P(x, y)$ for a typical element of $P$. + +(Most of the time, this is more convenient than using symbols such as $P_{ij}$, and it aligns better with the infinite state case.) + +In applications, $P(x, y)$ represents the probability of transitioning from $x$ to +$y$ in one step. + +#### Markov Chains + +A Markov chain $(X_t)_{t \in \mathbb Z_+}$ on finite set $S$ with Markov matrix $P$ is a +sequence of random variables satisfying + +$$ + \mathbb P\{X_{t+1} = y \,|\, X_0, X_1, \ldots, X_t \} = P (X_t, y) +$$ (markovpropd) + +with probability one for all $y \in S$ and any $t \in \mathbb Z_+$. + +In addition to connecting probabilities to the Markov matrix, +{eq}`markovpropd` says that the process depends on its history only through +the current state. + +We [recall that](https://python.quantecon.org/finite_markov.html), if $X_t$ +has distribution $\psi$, then $X_{t+1}$ has distribution $\psi P$. + +Since $\psi$ is understood as a row vector, the meaning is + +$$ + (\psi P)(y) = \sum_x \psi(x) P(x, y) + \qquad (y \in S) +$$ (update_rule) + +(jdfin)= +#### The Joint Distribution + +In general, for given Markov matrix $P$, there can be many Markov chains +$(X_t)$ that satisfy {eq}`markovpropd`. + +This is due to the more general observation that, for a given distribution +$\phi$, we can construct many random variables having distribution $\phi$. + +(The exercises below ask for one example.) + +Hence $P$ is, in a sense, a more primitive object than $(X_t)$. + +There is another way to see the fundamental importance of $P$: by constructing the joint distribution. + +Consider the infinite sequence space $S^\infty := S \times S \times \cdots$. + +Together with an initial condition $\psi \in +\mathcal D$, a Markov matrix $P$ defines a distribution $\mathbf P_\psi$ +over $S^\infty$ such that any +Markov chain $(X_t)$ satisfying {eq}`markovpropd` and $X_0 \sim \psi$ has +$\mathbf P_\psi$ as its joint distribution. + +The last statement is equivalent to + +$$ + \mathbb P\{ X_{t_1} = y_{t_1}, \ldots, X_{t_k} = y_{t_k} \} + = + \mathbf P_\psi\{ (x_t) \in S^\infty \,:\, + x_{t_i} = y_{t_i} \text{ for } i = 1, \ldots m\} +$$ (jointdeq) + +for any $m$ positive integers $t_i$ and $m$ elements $y_{t_i}$ of the state space $S$. + + +(Joint distributions of discrete time processes +are uniquely defined by their values at finite collections of times in the sense of +{eq}`jointdeq` --- see, for example, Theorem 7.2 of {cite}`walsh2012knowing`.) + + +To construct the joint distribution over $S^\infty$, one first constructs a +finite dimensional version over the Cartiesian product $S^{n+1}$ +via + +$$ + \mathbf P_\psi^n(x_0, x_1, \ldots, x_n) + = \psi(x_0) + P(x_0, x_1) + \times \cdots \times + P(x_{n-1}, x_n) +$$ (mathjointd) + +Then one shows that for any Markov chain $(X_t)$ satisfying {eq}`markovpropd` +and $X_0 \sim \psi$, the restriction $(X_0, \ldots, X_n)$ has joint +distribution $\mathbf P_\psi^n$. + +This is a solved exercise below. + +The last remaining step is to show that the family $(\mathbf P_\psi^n)$ defined at each $n \in \mathbb N$ extends uniquely to a distribution $\mathbf P_\psi$ over the infinite +sequences in $S^\infty$. + +That this is true follows from a well known [theorem of Kolmogorov](https://en.wikipedia.org/wiki/Kolmogorov_extension_theorem). + +Hence $P$ defines the joint distribution $\mathbf P_\psi$ when paired with any initial condition $\psi$. + + + +### Extending to Countable State Spaces + +When $S$ is infinite, we cannot view $P$ as a matrix. + +Instead, we introduce the notion of a **Markov kernel** on $S$, which is a function +$P$ from $S \times S$ to $\mathbb R_+$ satisfying + +$$ + \sum_y P(x, y) = 1 + \text{ for all } x \in S +$$ + +This is a natural extension of matrices with nonnegative elements and unit row +sums. + +The definition of a Markov chain $(X_t)_{t \in \mathbb Z_+}$ on $S$ with Markov kernel $P$ is exactly as in {eq}`markovpropd`. + +Given Markov kernel $P$ and $\phi \in \mathcal D$, we define $\phi P$ by +{eq}`update_rule`. + +Then, as before, $\phi P$ can be understood as the distribution of +$X_{t+1}$ when $X_t$ has distribution $\phi$. + +The function $\phi P$ is in $\mathcal D$, since, by {eq}`update_rule`, it is +nonnegative and + +$$ + \sum_y (\phi P)(y) + = \sum_y \sum_x P(x, y) \phi(x) + = \sum_x \sum_y P(x, y) \phi(x) + = \sum_x \phi(x) + = 1 +$$ + +Swapping the order of infinite sums is justified here by the fact that all +elements are nonnegative (a version of Tonelli's theorem). + +We can take products of Markov kernels that are analogous to matrix products. + +In particular, if $P$ and $Q$ are Markov kernels on $S$, then, for $(x, y)$ in $S +\times S$, + +$$ + (P Q)(x, y) := \sum_z P(x, z) Q(z, y) +$$ (kernprod) + +It is not difficult to check that the product $P Q$ is again a Markov kernel on $S$. + +The operation {eq}`kernprod` is analogous to matrix multiplication, so that +elements of $P^k$, the $k$-th product of $P$ with itself, retain the finite +state interpretation of $k$ step transition probabilities. + +For example, we have + +$$ + P^k(x, y) + = (P^{k-j} P^j)(x, y) = \sum_z P^{k-j}(x, z) P^j(z, y) +$$ (kernprodk) + +which is a version of the discrete time Chapman-Kolmogorov equation. + +Equation {eq}`kernprodk` can be obtained from the law of total probability: if +$(X_t)$ is a Markov chain with Markov kernel $P$ and initial condition $X_0 = +x$, then + +$$ + \mathbb P\{X_k = y\} + = \sum_z \mathbb P\{X_k = y \,|\, X_j=z\} \mathbb P\{X_j=z\} +$$ + + +All of the {ref}`preceding discussion ` on the connection between $P$ +and the joint distribution of $(X_t)$ when $S$ is finite carries over +to the current setting. + + + + +### The Continuous Time Case + +A **continuous time stochastic process** on $S$ is a collection $(X_t)$ of $S$-valued +random variables $X_t$ defined on a common probability space and indexed by $t +\in \mathbb R_+$. + +Let $I$ be the Markov kernel on $S$ defined by $I(x,y) = \mathbb 1\{x = y\}$. + +A **transition semigroup** is a family $(P_t)$ of Markov kernels +on $S$ satisfying $P_0 = I$ and + +$$ + P_{s + t} = P_s P_t + \qquad (s, t \geq 0) +$$ (chapkol_ct) + +The interpretation of $P_t(x, y)$ is the probability of moving from state $x$ +to state $y$ in $t$ units of time. + +Equation {eq}`chapkol_ct`, which is known as the semigroup property of +$(P_t)$, is another version of the Chapman-Kolmogorov equation. + +This becomes clearer if we write it more explicitly as + +$$ + P_{s+t}(x, y) + = \sum_z P_s(x, z) P_t(z, y) +$$ (chapkol_ct2) + +A stochastic process $(X_t)$ is called a (time homogeneous) **Markov process** on $S$ +with transition semigroup $(P_t)$ if + +$$ + \mathbb P\{X_{s + t} = y \,|\, \mathcal F_s \} + = P_t (X_s, y) +$$ (markovprop) + +with probability one for all $y \in S$ and $s, t \geq 0$. + +Here $\mathcal F_s$ is the history $(X_r)_{r \leq s}$ of the process up until +time $s$. + +If you are an economist you might call $\mathcal F_s$ the "information set" at time +$s$. + +If you are familiar with measure theory, you can understand $\mathcal F_s$ as +the $\sigma$-algebra generated by $(X_r)_{r \leq s}$. + + +Analogous to the discrete time case, the joint +distribution of $(X_t)$ is determined by its transition semigroup plus an +initial condition. + +To prove this, one first builds finite dimensional distributions using +expressions similar to {eq}`mathjointd`. + +Next the Kolmogorov extension theorem is applied, similar to the discrete time case +(see, e.g., Corollary 6.4 of {cite}`le2016brownian`). + + + + +### Example: Poisson Processes + +The Poisson process discussed in our {doc}`previous lecture ` is a +Markov process on state space $\mathbb Z_+$. + +To obtain the transition semigroup, we observe that, for $k \geq j$, + +$$ + \mathbb P\{N_{s + t} = k \,|\, N_s = j\} + = \mathbb P\{N_{s + t} - N_s = k - j \,|\, N_s = j\} + = \mathbb P\{N_{s + t} - N_s = k - j\} +$$ + +where the last step is due to independence of increments. + +From stationarity of increments we have + +$$ + \mathbb P\{N_{s + t} - N_s = k - j\} + = \mathbb P\{N_t = k - j\} + = e^{-\lambda t} \frac{ (\lambda t)^{k-j} }{(k-j)!} +$$ + +In summary, the transition semigroup is + +$$ + P_t(j, k) + = e^{-\lambda t} \frac{ (\lambda t)^{k-j} }{(k-j)!} +$$ (poissemi) + +whenever $j \leq k$ and $P_t(j, k) = 0$ otherwise. + +This chain of equalities was obtained with $N_s = j$ for arbitrary $j$, so we +can replace $j$ with $N_s$ in {eq}`poissemi` to verify the Markov property {eq}`markovprop` for the Poisson process. + +Under {eq}`poissemi`, each $P_t$ is a Markov kernel and $(P_t)$ is a +transition semigroup. + +The proof of the semigroup property is a solved exercise below. + +(In {eq}`poissemi` we use the convention that $0^0 = 1$, which leads to $P_0 = I$.) + + + + + +### Example: Failure of the Markov Property + +Let's look at how the Markov property can fail, via an intuitive rather than +formal discussion. + +Let $(X_t)$ be a continuous time stochastic process with state space $S = \{0, 1\}$. + +The process starts at $0$ and updates at follows: + +1. Draw $W$ independently from a fixed Pareto distribution. +1. Hold $(X_t)$ in its current state for $W$ units of time and then switch + to the other state. +1. Go to step 1. + +What is the probability that $X_{s+h} = i$ given both the history $(X_r)_{r \leq s}$ and current information $X_s = i$? + +If $h$ is small, then this is close to the +probability that there are zero switches over the time interval $(s, s+h]$. + +To calculate this probability, it would be helpful to know how long the +state has been at current state $i$. + +This is because the Pareto distribution {ref}`is not memoryless `. + +(With a Pareto distribution, if we know that $X_t$ has been at $i$ for a long +time, then a switch in the near future becomes more likely.) + +As a result, the history prior to $X_s$ is useful for predicting $X_{s+h}$, +even when we know $X_s$. + +Thus, the Markov property fails. + + + +### Restrictions Imposed by the Markov Property + +From the discussion above, we see that, for continuous time Markov chains, +the length of time between jumps must be memoryless. + +Recall that the {ref}`only ` memoryless distribution supported on $\mathbb R_+$ is the exponential distribution. + +[XX why isn't this link working??] + +Hence, a continuous time Markov chain waits at states for an +exponential amount of time and then jumps. + +The way that the new state is chosen must also satisfy the Markov property, +which adds another restriction. + +In summary, we already understand the following about continuous time Markov chains: + +1. Holding times are independent exponential draws. +1. New states are chosen in a ``Markovian'' way, independent of the past given the current state. + +We just need to clarify the details in these steps to have a complete description. + + +We start this process with an example. + + +(inventory_dynam)= +## A Model of Inventory Dynamics + + +Let $X_t$ be the inventory of a firm at time $t$, taking values in the +integers $0, 1, \ldots, b$. + +If $X_t > 0$, then a customer arrives after $W$ +units of time, where $W \sim E(\lambda)$ for some fixed $\lambda > 0$. + +Upon arrival, each customer purchases $\min\{U, X_t\}$ units, where $U$ is an +IID draw from the geometric distribution started at 1 rather than 0: + +$$ + \mathbb P\{U = k\} = (1-\alpha)^{k-1} \alpha + \qquad (k = 1, 2, \ldots, \; \alpha \in (0, 1)) +$$ + +If $X_t = 0$, then no customers arrive and the firm places an order for $b$ units. + +The order arrives after a delay of $D$ units of time, where $D \sim E(\lambda)$. + +(We use the same $\lambda$ here just for convenience, to simplify the exposition.) + +### Representation + +The inventory process jumps to a new value either when a new customer arrives +or when new stock arrives. + +Between these arrival times it is constant. + +Hence, to track $X_t$, it is enough to track the jump times and the new values +taken at the jumps. + +In what follows, we denote the jump times by $\{J_k\}$ and the values at jumps +by $\{Y_k\}$. + +Then we construct the state process via + +$$ + X_t = \sum_{k \geq 0} Y_k \mathbb 1\{J_k \leq t < J_{k+1}\} + \qquad (t \geq 0) +$$ (xfromy) + + + +### Simulation + +Let's simulate this process, starting at $X_0 = 0$. + +As above, + +* $J_k$ is the time of the $k$-th jump (up or down) in inventory. +* $Y_k$ is the size of the inventory after the $k$-th jump. +* $(X_t)$ is defined from these objects via {eq}`xfromy`. + +Here's a function that generates and returns one path $t \mapsto X_t$. + +(We are not aiming for computational efficiency at this stage.) + +```{code-cell} ipython3 +def sim_path(T=10, seed=123, λ=0.5, α=0.7, b=10): + """ + Generate a path for inventory starting at b, up to time T. + + Return the path as a function X(t) constructed from (J_k) and (Y_k). + """ + + J, Y = 0, b + J_vals, Y_vals = [J], [Y] + np.random.seed(seed) + + while True: + W = np.random.exponential(scale=1/λ) # W ~ E(λ) + J += W + J_vals.append(J) + if J >= T: + break + else: + # Update Y + if Y == 0: + Y = b + else: + U = np.random.geometric(α) + Y = Y - min(Y, U) + Y_vals.append(Y) + + Y_vals = np.array(Y_vals) + J_vals = np.array(J_vals) + + def X(t): + k = np.searchsorted(J_vals, t) + return Y_vals[k-1] + + return X +``` + +Let's plot the process $(X_t)$ using the ``step`` method of ``ax``. + +```{code-cell} ipython3 +X = sim_path(10) +``` + +```{code-cell} ipython3 +X(0) +``` + +```{code-cell} ipython3 +jv = (0, 1, 10) +np.searchsorted(jv, 0.1) +``` + +```{code-cell} ipython3 +T = 20 +X = sim_path(T=T) + +grid = np.linspace(0, T, 100) + +fig, ax = plt.subplots() +ax.step(grid, [X(t) for t in grid], label="$X_t$") + +ax.set(xlabel="time", ylabel="inventory") + +ax.legend() +plt.show() +``` + +As expected, inventory falls and then jumps back up to $b$. + + + +### The Embedded Jump Chain + +In models such as the one described above, the embedded discrete time +process $(Y_n)$ is called the "embedded jump chain". + +It is easy to see that $(Y_n)$ is discrete time finite state Markov chain. + +Its Markov matrix $K$ is +given by $K(x, y) = \mathbb 1\{y=b\}$ when $x=0$ and, when $0 < x \leq b$, + +$$ + K(x, y) + = + \begin{cases} + \mathbb 0 & \text{ if } y \geq x + \\ + \mathbb P\{x - U = y\} = (1-\alpha)^{x-y-1} \alpha + & \text{ if } 0 < y < x + \\ + \mathbb P\{U \geq x\} = (1-\alpha)^{x-1} + & \text{ if } y = 0 + \end{cases} +$$ (ijumpkern) + + + + +### Markov Property + +The inventory model just described has the Markov property precisely because + +1. the jump chain $(Y_n)$ is Markov in discrete time and +1. the holding times are independent exponential draws. + +Rather than providing more details on these points here, let us first describe +a more general setting where the arguments will be clearer and more useful. + + + +## Jump Processes with Constant Rates + +The examples we have focused on so far are special cases of Markov processes +with constant jump intensities. + +This processes turn out to be very representative (although the constant jump intensity will later be relaxed). + +Let's now summarize the model and its properties. + + +### Construction + +The data for a Markov process on $S$ with constant jump rates are + +* a parameter $\lambda > 0$ called the **jump rate**, which governs the jump + intensities and +* a Markov kernel $K$ on $S$, called the **jump kernel**. + +To run the process we also need an initial condition $\psi \in \mathcal D$. + +The process $(X_t)$ is constructed by holding at each state for an +exponential amount of time, with rate $\lambda$, and then updating to a +new state via $K$. + +In more detail, the construction is + +1. draw $Y_0$ from $\psi$ +1. set $n = 1$ and $J_0 = 0$ +1. draw $W_n$ from Exp$(\lambda)$ and set $J_n = J_{n-1} + W_n$ +1. set $X_t = Y_{n-1}$ for all $t$ such that $J_{n-1} \leq t < J_n$. +1. draw $Y_n$ from $K(Y_{n-1}, \cdot)$ +1. set $n = n+1$ and go to step 3. + +An alternative, more parsimonious way to express the same process is to take + +* $(N_t)$ to be a Poisson process with rate $\lambda$ and +* $(Y_n)$ to be a discrete time Markov chain with kernel $K$ + +and then set + +$$ + X_t := Y_{N_t} \text{ for all } t \geq 0 +$$ + +As before, the discrete time process $(Y_n)$ is called the **embedded jump chain**. + +(Not to be confused with $(X_t)$, which is often called a "jump process" due +to the fact that it changes states with jumps.) + +The draws $(W_n)$ are called the **wait times** or **holding times**. + + +### Examples + +The Poisson process with rate $\lambda$ is a jump process on $S = \mathbb Z_+$. + +The holding times are obviously exponential with constant rate $\lambda$. + +The jump kernel is just $K(i, j) = \mathbb 1\{j = i+1\}$, so that the state +jumps up by one at every $J_n$. + +The inventory model is also a jump process with constant rate $\lambda$, this +time on $S = \{0, 1, \ldots, b\}$. + +The jump kernel (or matrix in this case) was given in {eq}`ijumpkern`. + + + + + +### Markov Property + +Let's show that the jump process $(X_t)$ constructed above satisfies the +Markov property, and obtain the transition semigroup at the same time. + +We will use two facts: + +* the jump chain $(Y_n)$ has the Markov property in discrete + time and +* the Poisson process has stationary independent increments. + +From these facts it is intuitive that the distribution of $X_{t+s}$ given +the whole history $\mathcal F_s = \{ (N_r)_{r \leq s}, (Y_n)_{n \leq N_s} \}$ +depends only on $X_s$. + +Indeed, if we know $X_s$, then we can simply {ref}`restart ` the +Poisson process from $N_s$ and then update the jump chain using $K$ each time a +jump occurs, starting from $X_s$. + +Let's write this more mathematically. + +Fixing $y \in S$ and $s, t \geq 0$, we have + + +$$ + \mathbb P\{X_{s + t} = y \,|\, \mathcal F_s \} + = \mathbb P\{Y_{N_{s + t}} = y \,|\, \mathcal F_s \} + = \mathbb P\{Y_{N_s + N_{s + t} - N_s} = y \,|\, \mathcal F_s \} +$$ + +{ref}`Recalling ` that $N_{s + t} - N_s$ is Poisson distributed with rate $t \lambda$, independent of the history $\mathcal F_s$, we can write the display above as + +$$ + \mathbb P\{X_{s + t} = y \,|\, \mathcal F_s \} + = + \sum_{k \geq 0} + \mathbb P\{Y_{N_s + k} = y \,|\, \mathcal F_s \} + \frac{(t \lambda )^k}{k!} e^{-t \lambda} +$$ + +Because the jump chain is Markov with kernel $K$, we can simplify further to + + +$$ + \mathbb P\{X_{s + t} = y \,|\, \mathcal F_s \} + = \sum_{k \geq 0} + K^k(Y_{N_s}, y) \frac{(t \lambda )^k}{k!} e^{-t \lambda} + = K^k(X_s, y) \frac{(t \lambda )^k}{k!} e^{-t \lambda} +$$ + +Since the expression above depends only on $X_s$, +we have proved that $(X_t)$ has the Markov property. + + +(consjumptransemi)= +### Transition Semigroup + +The transition semigroup can be obtained from our final result, conditioning +on $X_s = x$ to get + +$$ + P^t(x, y) = \mathbb P\{X_{s + t} = y \,|\, X_s = x \} + = e^{-t \lambda} \sum_{k \geq 0} + K^k(x, y) \frac{(t \lambda )^k}{k!} +$$ + +If $S$ is finite, we can write this in matrix form and use the definition of +the [matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential) to +get + +$$ + P^t + = e^{-t \lambda} + \sum_{k \geq 0} + \frac{(t \lambda K)^k}{k!} + = e^{-t \lambda} e^{t \lambda K} + = e^{t \lambda (K - I)} +$$ + +This is a simple and elegant representation of the transition semigroup that +makes it easy to understand and analyze distribution dynamics. + +For example, if $X_0$ has distribution $\psi$, then $X_t$ has distribution + +$$ + \psi P_t = \psi e^{t \lambda (K - I)} +$$ (distflowconst) + +We just need to plug in $\lambda$ and $K$ to obtain the entire flow $t \mapsto \psi P_t$. + +We will soon extend this representation to the case where $S$ is infinite. + + +(invdistflows)= +## Distribution Flows for the Inventory Model + +Let's apply these ideas to the inventory model described above. + +We fix + +* the parameters $\alpha$, $b$ and $\lambda$ in the inventory model and +* an initial condition $X_0 \sim \psi_0$, where $\psi_0$ is an arbitrary +distribution on $S$. + +The state $S$ is set to $\{0, \ldots, b\}$ and the kernel $K$ is defined by +{eq}`ijumpkern`. + +Now we run time forward. + +We are interesting in computing the flow of distributions $t \mapsto \psi_t$, +where $\psi_t$ is the distribution of $X_t$. + +According to the theory developed above, we have two options: + +Option 1 is to use simulation. + +The first step is to simulate many independent observations the process $(X_t^m)_{m=1}^M$. + +(Here $m$ indicates simulation number $m$, which you might think of as the outcome +for firm $m$.) + +Next, for any given $t$, we define $\hat \psi_t \in \mathcal D$ as the +histogram of observations at time $t$, or, equivalently the cross-sectional +distribution at $t$: + +$$ + \hat \psi_t(x) := \frac{1}{M} \sum_{m=1}^M \mathbb 1\{X_t = x\} + \qquad (x \in S) +$$ + +Then $\hat \psi_t(x)$ will be close to $\mathbb P\{X_t = x\}$ by the law of +large numbers. + +In other words, in the limit we recover $\psi_t$. + + +Option 2 is to insert the parameters into the right hand side of {eq}`distflowconst` +and compute $\psi_t$ as $\psi_0 P_t$. + +Let's try option 2, with $\alpha = 0.6$, $\lambda = 0.5$ and $b=10$. + +For the initial distribution we pick a binomial distribution. + +Since we cannot compute the entire uncountable flow $t \mapsto \psi_t$, we +iterate forward 200 steps at time increments $h=0.1$. + +In the figure below, hot colors indicate initial conditions and early dates (so that the +distribution "cools" over time) + +```{code-cell} ipython3 + α = 0.6 + λ = 0.5 + b = 10 + n = b + 1 + states = np.arange(n) + I = np.identity(n) + + K = np.zeros((n, n)) + K[0, -1] = 1 + for i in range(1, n): + for j in range(0, i): + if j == 0: + K[i, j] = (1 - α)**(i-1) + else: + K[i, j] = α * (1 - α)**(i-j-1) + + + def P_t(ψ, t): + return ψ @ expm(t * λ * (K - I)) + + def plot_distribution_dynamics(ax, ψ_0, steps=200, step_size=0.1): + ψ = ψ_0 + t = 0.0 + colors = cm.jet_r(np.linspace(0.0, 1, steps)) + + for i in range(steps): + ax.bar(states, ψ, zs=t, zdir='y', + color=colors[i], alpha=0.8, width=0.4) + ψ = P_t(ψ, t=step_size) + t += step_size + + ax.set_xlabel('inventory') + ax.set_ylabel('$t$') + + + ψ_0 = binom.pmf(states, n, 0.25) + fig = plt.figure(figsize=(8, 6)) + ax = fig.add_subplot(111, projection='3d') + plot_distribution_dynamics(ax, ψ_0) + plt.show() +``` + +In the exercises below you are asked to implement option 1 and check that the +figure looks the same. + + + + +## Exercises + +### Exercise 1 + +Consider the binary (Bernoulli) distribution where outcomes $0$ and $1$ each have +probability $0.5$. + +Construct two different random variables with this distribution. + + + +### Exercise 2 + +Show by direct calculation that the Poisson kernels $(P_t)$ defined in +{eq}`poissemi` satisfy the semigroup property {eq}`chapkol_ct2`. + +Hints + +* Recall that $P_t(j, k) = 0$ whenever $j > k$. +* Consider using the [binomial + formula](https://en.wikipedia.org/wiki/Binomial_theorem). + + +### Exercise 3 + +Consider the distribution over $S^{n+1}$ previously shown in {eq}`mathjointd`, which is + +$$ + \mathbf P_\psi^n(x_0, x_1, \ldots, x_n) + = \psi(x_0) + P(x_0, x_1) + \times \cdots \times + P(x_{n-1}, x_n) +$$ + +Show that, for any Markov chain $(X_t)$ satisfying {eq}`markovpropd` +and $X_0 \sim \psi$, the restriction $(X_0, \ldots, X_n)$ has joint +distribution $\mathbf P_\psi^n$. + +### Exercise 4 + +Replicate, as best you can, the figure produced from the {ref}`discussion on distribution flows `, this time using option 1. + +You will need to use a suitably large sample. + + + + + + + + + +## Solutions + +### Solution to Exercise 1 + +This is easy. + +One example is to take $U$ to be uniform on $(0, 1)$ and set $X=0$ if $U < +0.5$ and $1$ otherwise. + +Then $X$ has the desired distribution. + +Alternatively, we could take $Z$ to be standard normal and set $X=0$ if $Z < +0$ and $1$ otherwise. + + +### Solution to Exercise 2 + +Fixing $s, t \in \mathbb R_+$ and $j \leq k$, we have + +$$ +\begin{aligned} + \sum_{i \geq 0} P_s(j, i) P_t(i, k) + & = + e^{-\lambda (s+t)} + \sum_{j \leq i \leq k} + \frac{ (\lambda s)^{i-j} }{(i-j)!} + \frac{ (\lambda t)^{k-i} }{(k-i)!} + \\ + & = + e^{-\lambda (s+t)} \lambda^{k-j} + \sum_{0 \leq \ell \leq k-j} + \frac{ s^\ell }{\ell!} + \frac{ t^{k-j - \ell} }{(k-j - \ell)!} + \\ + & = + e^{-\lambda (s+t)} \lambda^{k-j} + \sum_{0 \leq \ell \leq k-j} + \binom{k-j}{\ell} + \frac{s^\ell t^{k-j - \ell}}{(k-j)!} +\end{aligned} +$$ + +Applying the binomial formula, we can write this as + +$$ + \sum_{i \geq 0} P_s(j, i) P_t(i, k) + = + e^{-\lambda (s+t)} + \frac{(\lambda (s + t))^{k-j}}{(k-j)!} + = P_{s+t}(j, k) +$$ + +Hence {eq}`chapkol_ct2` holds, and the semigroup property is satisfied. + + +### Solution to Exercise 3 + +Let $(X_t)$ be a Markov chain satisfying {eq}`markovpropd` and $X_0 \sim \psi$. + +When $n=0$, we have $\mathbf P_\psi^n = \mathbf P_\psi^0 = \psi$, and this +agrees with the distribution of the restriction $(X_0, \ldots, X_n) = (X_0)$. + +Now suppose the same is true at arbitrary $n-1$, in the sense that +the distribution of $(X_0, \ldots, X_{n-1})$ is equal to $\mathbf P_\psi^{n-1}$ as +defined above. + +Then + +$$ + \mathbb P \{X_0 = x_0, \ldots, X_n = x_n\} + = \mathbb P \{X_n = x_n \,|\, X_0 = x_0, \ldots, X_{n-1} = x_{n-1} \} + \\ + \times \mathbb P \{X_0 = x_0, \ldots, X_{n-1} = x_{n-1}\} +$$ + +From the Markov property and the induction hypothesis, the right hand side is + +$$ + P (x_{n-1}, x_n ) + \mathbf P_\psi^n(x_0, x_1, \ldots, x_{n-1}) + = + P (x_n, x_{n+1} ) + \psi(x_0) + P(x_0, x_1) + \times \cdots \times + P(x_{n-1}, x_{n-1}) +$$ + +The last expression equals $\mathbf P_\psi^n$, which concludes the proof. + + +### Solution to Exercise 4 + +[To be added.] diff --git a/code_book/_config.yml b/code_book/_config.yml new file mode 100644 index 0000000..367159e --- /dev/null +++ b/code_book/_config.yml @@ -0,0 +1,35 @@ +# Book settings +title: Economic Dynamics +author: John Stachurski +logo: logo.png + + +sphinx: + config: + mathjax3_config: + tex: + macros: + "Exp" : ["\\operatorname{Exp}"] + "Binomial" : ["\\operatorname{Binomial}"] + "Poisson" : ["\\operatorname{Poisson}"] + "BB" : ["\\mathbb{B}"] + "EE" : ["\\mathbb{E}"] + "PP" : ["\\mathbb{P}"] + "RR" : ["\\mathbb{R}"] + "NN" : ["\\mathbb{N}"] + "ZZ" : ["\\mathbb{Z}"] + "dD" : ["\\mathcal{D}"] + "fF" : ["\\mathcal{F}"] + "lL" : ["\\mathcal{L}"] + "linop" : ["\\mathcal{L}(\\mathbb{B})"] + "linopell" : ["\\mathcal{L}(\\ell_1)"] + mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + +execute: + execute_notebooks: cache + timeout: -1 + +latex: + latex_engine: "xelatex" + latex_documents: + targetname: book.tex diff --git a/code_book/_toc.yml b/code_book/_toc.yml new file mode 100644 index 0000000..36007cf --- /dev/null +++ b/code_book/_toc.yml @@ -0,0 +1,16 @@ +format: jb-book +root: intro +chapters: +- file: ch1 +- file: ch2 +- file: ch3 +- file: ch4 +- file: ch5 +- file: ch6 +- file: ch7 +- file: ch8 +- file: ch9 +- file: ch10 +- file: ch11 + + diff --git a/code_book/ch1.md b/code_book/ch1.md new file mode 100644 index 0000000..42357f6 --- /dev/null +++ b/code_book/ch1.md @@ -0,0 +1,415 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 1 Code + +Here is the code for chapter 1. I will not spend time explaining concepts +here, since both programming and dynamics are dealt with more systematically +later. + +Rather, the code is included here for completeness, and so that readers can +circle back to it once they have read other chapters. + +We begin with some imports. + + +```{code-cell} ipython3 + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap +import quantecon as qe +from numpy.random import uniform, randint +from numba import njit + +``` + +## Markov Dynamics + +Here is the class transition model from chapter 1 expressed as a stochastic +matrix. + +```{code-cell} ipython3 +P = ((0.9, 0.1, 0.0), + (0.4, 0.4, 0.2), + (0.1, 0.1, 0.8)) + +mc = qe.MarkovChain(P) + +``` + + +The following function simulates dynamics of a large group of households from +some fixed distribution `init`. + + +```{code-cell} ipython3 + +def sim_population(init=None, sim_length=100, pop_size=1000): + + cdf = np.cumsum(init) + obs = qe.random.draw(cdf, pop_size) + updated_obs = mc.simulate(sim_length, init=obs)[:, -1] + return updated_obs + +``` + +This function creates distribution plots. + + +```{code-cell} ipython3 + +def generate_plot(initial_dist, title, ax): + population = 1000 + + draws = sim_population(init=initial_dist, pop_size=population) + histogram = [np.mean(draws == i) for i in range(3)] + + names = 'poor', 'middle', 'rich' + ax.bar(names, histogram, edgecolor='k', alpha=0.4) + ax.set_title(title) + +``` + +Now let us generate the first figure. + +```{code-cell} ipython3 + +initial_dists = ((1, 0, 0), + (0, 1, 0), + (0, 0, 1), + (0.33, 0.33, 0.34)) + +titles = ('$\\psi_0 = (1, 0, 0)$', + '$\\psi_0 = (0, 1, 0)$', + '$\\psi_0 = (0, 0, 1)$', + '$\\psi_0 = (1/3, 1/3, 1/3)$') + +fig, axes = plt.subplots(2, 2) +axes = axes.flatten() + +for psi, title, ax in zip(initial_dists, titles, axes): + generate_plot(psi, title, ax) + +plt.tight_layout() +plt.show() + +``` + +Finally, here is a histogram of states produced by a single very long time +series. + +For large samples, the histogram is essentially identical to the ones we +produced above. + +This is ergodicity. + + +```{code-cell} ipython3 + +sim_length = 100_000 +draws = mc.simulate(sim_length, init=0) +histogram = [np.mean(draws == i) for i in range(3)] + +fig, ax = plt.subplots() +names = 'poor', 'middle', 'rich' +ax.bar(names, histogram, edgecolor='k', alpha=0.4) +ax.set_title('time series average') +ax.set_yticks((0, 0.2, 0.4, 0.6)) +plt.show() + +``` + +Here is the code that produced the lattice figure in chapter 1. + + +```{code-cell} ipython3 +xx, yy = np.meshgrid(np.arange(10), np.arange(10), indexing='ij') + + +cols = np.random.randint(0, high=2, size=100) + +cmap = ListedColormap(('lavender', 'navy')) + +fig, ax = plt.subplots() +plt.axis('off') +ax.get_xaxis().set_visible(False) +ax.get_yaxis().set_visible(False) + +ax.scatter(xx,yy, c=cols, cmap=cmap, alpha=0.6, edgecolor='k') +plt.show() + +``` + + +## The Schelling Model + +Next I provide the code for the Schelling model simulations. + + +I omit most explanations. + +Hopefully readers will find the code transparent after building some +experience reading other chapters. + + +```{code-cell} ipython3 +n = 1000 # number of agents (agents = 0, ..., n-1) +k = 10 # number of agents regarded as neighbors +require_same_type = 5 # want >= require_same_type neighbors of the same type + +def initialize_state(): + locations = uniform(size=(n, 2)) + types = randint(0, high=2, size=n) # label zero or one + return locations, types + +@njit +def compute_distances_from_loc(loc, locations): + " Compute distance from location loc to all other points. " + distances = np.empty(n) + for j in range(n): + distances[j] = np.linalg.norm(loc - locations[j, :]) + return distances + +def get_neighbors(loc, locations): + " Get all neighbors of a given location. " + all_distances = compute_distances_from_loc(loc, locations) + indices = np.argsort(all_distances) # sort agents by distance to loc + neighbors = indices[:k] # keep the k closest ones + return neighbors + +def is_happy(i, locations, types): + happy = True + agent_loc = locations[i, :] + agent_type = types[i] + neighbors = get_neighbors(agent_loc, locations) + neighbor_types = types[neighbors] + if sum(neighbor_types == agent_type) < require_same_type: + happy = False + return happy + +def count_happy(locations, types): + " Count the number of happy agents. " + happy_sum = 0 + for i in range(n): + happy_sum += is_happy(i, locations, types) + return happy_sum + +def update_agent(i, locations, types): + " Move agent if unhappy. " + moved = False + while not is_happy(i, locations, types): + moved = True + locations[i, :] = uniform(), uniform() + return moved + +def plot_distribution(locations, types, title, savepdf=False): + " Plot the distribution of agents after cycle_num rounds of the loop." + fig, ax = plt.subplots() + colors = 'lavender', 'navy' + for agent_type, color in zip((0, 1), colors): + idx = (types == agent_type) + ax.plot(locations[idx, 0], + locations[idx, 1], + 'o', + markersize=8, + markerfacecolor=color, + alpha=0.8) + ax.set_title(title) + if savepdf: + plt.savefig(title + '.pdf') + plt.show() + + +def sim_sequential(max_iter=100): + """ + Simulate by sequentially stepping through the agents, one after + another. + + """ + + locations, types = initialize_state() + current_iter = 0 + + while current_iter < max_iter: + print("Entering iteration ", current_iter) + + plot_distribution(locations, types, f'cycle_{current_iter}') + + # Update all agents + num_moved = 0 + for i in range(n): + num_moved += update_agent(i, locations, types) + + if num_moved == 0: + print(f"Converged at iteration {current_iter}") + break + + current_iter += 1 + + +def sim_random_select(max_iter=100_000, flip_prob=0.01, test_freq=10_000): + """ + Simulate by randomly selecting one household at each update. + + Flip the color of the household with probability `flip_prob`. + + """ + + locations, types = initialize_state() + current_iter = 0 + + while current_iter <= max_iter: + + # Choose a random agent and update them + i = randint(0, n) + moved = update_agent(i, locations, types) + + if flip_prob > 0: + # flip agent i's type with probability epsilon + U = uniform() + if U < flip_prob: + current_type = types[i] + types[i] = 0 if current_type == 1 else 1 + + # Every so many updates, plot and test for convergence + if current_iter % test_freq == 0: + cycle = current_iter / n + plot_distribution(locations, types, f'iteration {current_iter}') + if count_happy(locations, types) == n: + print(f"Converged at iteration {current_iter}") + break + + current_iter += 1 + + if current_iter > max_iter: + print(f"Terminating at iteration {current_iter}") + + +``` + +This code creates figures 1.6 -- 1.7, modulo randomness. + +```{code-cell} ipython3 +sim_random_select(max_iter=50_000, flip_prob=0.0, test_freq=10_000) + +``` + +This code creates figures 1.8 -- 1.9. + +```{code-cell} ipython3 +sim_random_select(max_iter=200_000, flip_prob=0.01, test_freq=50_000) + +``` + +## Linear AR(1) Simulation + +In this section we generate the time path figures for the Gaussian AR(1) model +(figures 1.10, 1.12 and 1.13). + + + +```{code-cell} ipython3 +a = 0.9 +b = 1.0 + +mu_0, v_0 = 1, 1 + +def compute_mean_and_var(t): + mu, v = mu_0, v_0 + for i in range(t): + mu = a * mu + b + v = a**2 * v + 1 + + return mu, v + +def norm_pdf(x, mu, v): + return np.sqrt(1/(2 * np.pi * v)) * np.exp(-(x - mu)**2 / (2*v)) + +``` + +Here is the density sequence. + +```{code-cell} ipython3 +dates = 1, 2, 4, 8, 16, 32, 64 +y_grid = np.linspace(-5, 20, 200) +greys = [str(g) for g in np.linspace(0.2, 0.8, len(dates))] +greys.reverse() + +fig, ax = plt.subplots() + +for t, g in zip(dates, greys): + mu, v = compute_mean_and_var(t) + ax.plot(y_grid, + [norm_pdf(y, mu, v) for y in y_grid], + color=g, + lw=2, + alpha=0.6, + label=f'$t={t}$') + +ax.set_xlabel('$x$') +ax.legend() + +plt.show() + + +``` + +Here is the time series. + +```{code-cell} ipython3 + +def generate_time_series(X_0=14, ts_length=250): + X = np.empty(ts_length) + X[0] = X_0 + for t in range(ts_length-1): + X[t+1] = a * X[t] + b + np.random.randn() + return X + +mu_star = b / (1 - a) + +X = generate_time_series() + +fig, ax = plt.subplots() + +ax.plot(X, label='$X_t$', alpha=0.8) +ax.plot(np.full(len(X), mu_star), 'k--', label='$\\mu^*$', alpha=0.8) + +ax.legend() + +plt.show() + + +``` + +Finally, here is the evolution of the mean. + +```{code-cell} ipython3 + + +X = generate_time_series(ts_length=5000) +X_bar_series = np.cumsum(X) / np.arange(1, len(X)+1) + +fig, ax = plt.subplots() + +ax.plot(X_bar_series, label='$\\bar X_t$', alpha=0.8) +ax.plot(np.full(len(X_bar_series), mu_star), 'k--', label='$\\mu^*$', alpha=0.8) + +ax.legend() +plt.show() + +``` + + diff --git a/code_book/ch10.md b/code_book/ch10.md new file mode 100644 index 0000000..76d78e4 --- /dev/null +++ b/code_book/ch10.md @@ -0,0 +1,18 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 10 Code + +No code used in this chapter. diff --git a/code_book/ch11.md b/code_book/ch11.md new file mode 100644 index 0000000..038f2a5 --- /dev/null +++ b/code_book/ch11.md @@ -0,0 +1,140 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 11 Code + +We begin with some imports + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +from numba import njit + +``` + +Now we recreate figure 11.3. Our first step is to set the parameters. + + +```{code-cell} ipython3 + +alpha = 0.6 +Q = 2 +R = 1 +sigma = 0.1 # variance +mu = - sigma**2 / 2 + +``` + +Next we introduce useful functions. + + +```{code-cell} ipython3 +@njit +def wage(k, z): + return (1 - alpha) * (k**alpha) * z + +@njit +def theta(w, lda): + if (w < 1 - lda): + return lda / (1 - w) + else: + return 1 + +@njit +def g(y): + return (y/alpha)**(1/(alpha-1)) + +@njit +def update(k, z, lda): + " Update the state." + return g(R / (Q * theta(wage(k, z), lda))) + +@njit +def a(lda): + return g(R / (lda * Q)) + +@njit +def generate_ts(lda, init=None, seed=1234, ts_length=10_000): + " Generate a time series from the model." + np.random.seed(seed) + K = np.empty(ts_length) + if init is None: + init = a(lda) + K[0] = init + for t in range(ts_length-1): + z = np.exp(mu + sigma * np.random.randn()) + K[t+1] = update(K[t], z, lda) + return K + + +``` + +Now we recreate the plot. + +```{code-cell} ipython3 + +b = g(R / Q) + +lambdas = 0.48, 0.5, 0.52 +line_type = '-', '--', '-.' +mc_size = 5000 +grid_size = 150 + +fig, ax = plt.subplots() + +for lda, lt in zip(lambdas, line_type): + xvec = np.linspace(a(lda), b, grid_size) + obs = generate_ts(lda, ts_length=mc_size) + def ecdf(y): + return sum(obs <= y) / mc_size + yvec = [ecdf(x) for x in xvec] + ax.plot(xvec, yvec, lt, label=rf'$\lambda={lda}$') + +ax.set_xlabel('capital') +ax.legend() + +plt.show() + + +``` + +Now we address exercise 11.26. + +We need to determine the fraction of time the sample spends at the point $b$ +in the state space over a long horizon, for different values of $\lambda$. + + + +```{code-cell} ipython3 + +grid_size = 80 +lambdas = np.linspace(0.4, 0.6, grid_size) +mc_size = 5000 + +prob_at_b = np.empty_like(lambdas) + +for i, lda in enumerate(lambdas): + obs = generate_ts(lda, ts_length=mc_size) + prob_at_b[i] = np.mean(obs == b) + +fig, ax = plt.subplots() +ax.plot(lambdas, prob_at_b, label=r'$\psi^*({b})$') +ax.set_xlabel(r'$\lambda$') +ax.legend() + +plt.show() + + +``` diff --git a/code_book/ch2.md b/code_book/ch2.md new file mode 100644 index 0000000..915a0c8 --- /dev/null +++ b/code_book/ch2.md @@ -0,0 +1,180 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 2 Code + ++++ + +We'll use Python's NumPy library in what follows, as well as Matplotlib for plotting. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +``` + +## Bisection + ++++ + +Here's an implementation of the bisection algorithm. We will test it on this function: + +```{code-cell} ipython3 +f = lambda x: np.sin(4 * (x - 1/4)) + x + x**20 - 1 +``` + +The implementation: + +```{code-cell} ipython3 +M = 1000 +ϵ = 1e-8 +α, β = 0, 1 + + +i = 1 +a, b = α, β +while i <= M: + c = (a + b) / 2 + if abs(f(c)) < ϵ: + print(c) + break + i += 1 + if f(a) * f(c) < 0: + b = c + else: + a = c + +if i > M: + print("Failed to converge.") +``` + +## User Defined Functions + ++++ + +Here's the basic implementation of the function $\tau$. + +```{code-cell} ipython3 +def tau(z, S, phi): + """ + Evaluates the function tau(z) given data S, phi, where + S and phi are assumed to be arrays. + """ + a = 0 + for i, x in enumerate(S): + b = a + phi[i] + if a < z <= b: + return x + a = b +``` + +Here's a more efficient implementation. + +```{code-cell} ipython3 +def tau(z, S, phi): + i = np.searchsorted(np.cumsum(phi), z) + return S[i] +``` + +And here's a closure that generates the function $\tau$. + +```{code-cell} ipython3 +def tau_factory(S, phi): + Φ = np.cumsum(phi) + + def tau(z): + i = np.searchsorted(Φ, z) + return S[i] + + return tau +``` + +We generate a function $\tau$ that acts on $z$ alone by calling the function factory: + +```{code-cell} ipython3 +phi = 0.2, 0.5, 0.3 +S = 0, 1, 2 +tau = tau_factory(S, phi) +``` + +```{code-cell} ipython3 +tau(0.1) # Should be 0 +``` + +All of these functions work as expected. To illustrate, here $\tau$ is used to generate draws from a given distribution $\phi$. + +```{code-cell} ipython3 + +size = 100_000 + +draws = np.empty(size) +for j in range(size): + W = np.random.uniform() + draws[j] = tau(W) + +# Compute fraction of draws with each possible value +frequency = [np.mean(draws==j) for j in S] + +``` + +Let's check that the empirical frequency approximately coincides with the probabilities in $\phi$. + +```{code-cell} ipython3 +fig, ax = plt.subplots() + +ax.bar(S, frequency, + edgecolor='k', + facecolor='b', + alpha=0.25, + label="histogram") + +ax.stem(S, phi, label='$\\phi$') + +ax.legend() + +plt.show() +``` + +## Object Oriented Programming + ++++ + +Here's a class that implements the function $\tau$ as a method, as well as a method to generate draws from $\phi$. + +```{code-cell} ipython3 +class Tau: + + def __init__(self, S, phi): + self.S = S + self.Φ = np.cumsum(phi) + + def tau(self, z): + i = np.searchsorted(self.Φ, z) + return self.S[i] + + def draw(self): + W = np.random.uniform() + return self.tau(W) +``` + +```{code-cell} ipython3 +tau = Tau(S, phi) +``` + +```{code-cell} ipython3 +for i in range(5): + print(tau.draw()) +``` + + diff --git a/code_book/ch3.md b/code_book/ch3.md new file mode 100644 index 0000000..7af94f4 --- /dev/null +++ b/code_book/ch3.md @@ -0,0 +1,18 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 3 Code + +No code used in this chapter. diff --git a/code_book/ch4.md b/code_book/ch4.md new file mode 100644 index 0000000..6ff4e65 --- /dev/null +++ b/code_book/ch4.md @@ -0,0 +1,776 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 4 Code + +Let's start with some imports. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +from numba import jit +from quantecon import MarkovChain +``` + +## Introduction to Dynamics + +Here's the code that generated figure 4.1. + + +```{code-cell} ipython3 + +def draw_arrow(x, y, xp, yp, ax): + """ + Draw an arrow from (x, y) to (xp, yp). + """ + v1, v2 = xp - x, yp - y + eps = 1.0 + nrm = np.sqrt(v1**2 + v2**2) + scale = 1.0 + ax.arrow(x, y, scale * v1, scale * v2, + antialiased=True, + alpha=0.5, + head_length=0.025*(xmax - xmin), + head_width=0.012*(xmax - xmin), + fill=False) + +xmin, xmax = -10.0, 10.0 +ymin, ymax = -5.0, 5.0 + +A1 = np.asarray([[0.55, -0.6], + [0.5, 0.4]]) + +def f(x, y): + return A1 @ (x, y) + +xgrid = np.linspace(xmin * 0.95, xmax * 0.95, 10) +ygrid = np.linspace(ymin * 0.95, ymax * 0.95, 10) + +fig, ax = plt.subplots() + +#ax.set_xlim(xmin, xmax) +#ax.set_ylim(ymin, ymax) + +ax.set_xticks((0,)) +ax.set_yticks((0,)) +ax.grid() + +for x in xgrid: + for y in ygrid: + xp, yp = f(x, y) + draw_arrow(x, y, xp, yp, ax) + +#plt.savefig("sdsdiagram.pdf") # Uncomment to save figure +plt.show() +``` + + +And here's the code that generated figure 4.2. + +```{code-cell} ipython3 + +def draw_trajectory(x, y, ax, n=10): + """ + Draw the trajectory of length n starting from (x, y). + """ + for i in range(n): + x_new, y_new = f(x, y) + draw_arrow(x, y, x_new, y_new, ax) + x, y = x_new, y_new + +xgrid = np.linspace(xmin * 0.95, xmax * 0.95, 3) +ygrid = np.linspace(ymin * 0.95, ymax * 0.95, 3) + +fig, ax = plt.subplots() + +#ax.set_xlim(xmin, xmax) +#ax.set_ylim(ymin, ymax) + +ax.set_xticks((0,)) +ax.set_yticks((0,)) +ax.grid() + +for x in xgrid: + for y in ygrid: + draw_trajectory(x, y, ax) + +#plt.savefig("sdsstable.pdf") # Uncomment to save figure +plt.show() +``` + + +## Chaotic Dynamics + +Just for fun, here's a little class that allows us to simulate trajectories +from a specified dynamical system: + + +```{code-cell} ipython3 +class DS: + + def __init__(self, h=None, x=None): + """Parameters: h is a function and x is an + element of S representing the current state.""" + self.h, self.x = h, x + + def update(self): + "Update the state of the system by applying h." + self.x = self.h(self.x) + + def trajectory(self, n): + """Generate a trajectory of length n, starting + at the current state.""" + traj = [] + for i in range(n): + traj.append(self.x) + self.update() + return traj +``` + +As in the textbook, let's plot a trajectory starting from 0.11. + + +```{code-cell} ipython3 + +q = DS(h=lambda x: 4 * x * (1 - x), x=0.11) +t = q.trajectory(200) + +fig, ax = plt.subplots() +ax.plot(t) + +plt.show() + +``` + +Now let's generate a histograms from a long trajectory, to address exercise +4.16. + + +```{code-cell} ipython3 + +q.x = 0.11 +t = q.trajectory(5000) + +fig, ax = plt.subplots() +ax.hist(t, bins=40, alpha=0.5, edgecolor='k') + +plt.show() + +``` + +If you experiment with different initial conditions you will find that, for +all most all choices, the histogram looks the same. + + +```{code-cell} ipython3 + +q.x = 0.65 +t = q.trajectory(5000) + +fig, ax = plt.subplots() +ax.hist(t, bins=40, alpha=0.5, edgecolor='k') + +plt.show() + +``` + +What we have learned is that, although the trajectories seem very random, when +we take a statistical perspective we can make predictions. + +In particular, we can say what will happen "one average, in the long run." + + +Here's the set of maps. + + +```{code-cell} ipython3 + +xgrid = np.linspace(0, 1, 100) + +h = lambda x, r: r * x * (1 - x) + +fig, ax = plt.subplots() + +ax.plot(xgrid, xgrid, '-', color='grey') + +r = 0 +step = 0.3 + +while r <= 4: + y = [h(x, r) for x in xgrid] + ax.plot(xgrid, y) + r = r + step + +plt.show() + +``` + +Here's the bifurcation diagram. + + + +```{code-cell} ipython3 + +q = DS(h=None, x=0.1) + +fig, ax = plt.subplots() + +r = 2.5 +while r < 4: + q.h = lambda x: r * x * (1 - x) + t = q.trajectory(1000)[950:] + ax.plot([r] * len(t), t, 'k.', ms=0.4) + r = r + 0.005 + +ax.set_xlabel('$r$', fontsize=16) +plt.show() + +``` + + +## Markov Chains + + +Our first task is to simulate time series from Hamilton's Markov chain + + +```{code-cell} ipython3 +p_H = ((0.971, 0.029, 0.000), # Hamilton's kernel + (0.145, 0.778, 0.077), + (0.000, 0.508, 0.492)) + +p_H = np.array(p_H) # Convert to numpy array + +S = np.array((0, 1, 2)) +``` + + +We'll borrow this code from Chapter 2. + + +```{code-cell} ipython3 +@jit +def tau(z, S, phi): + i = np.searchsorted(np.cumsum(phi), z) + return S[i] +``` + +(We have targeted the function for JIT compilation via `@jit` because we need +fast execution below.) + +As discussed in that chapter, if we create a function `tau` using this code and feed it uniform draws on $(0,1]$, we get draws from `S` distributed according to `phi`. + +Here's some code to generate a trajectory starting at $x \in S$, using +stochastic kernel $p$. + +```{code-cell} ipython3 +def trajectory(x, p, S, n=100): + + X = np.empty(n, dtype=int) + X[0] = x + for t in range(n-1): + W = np.random.rand() + X[t+1] = tau(W, S, p[X[t], :]) + return X +``` + +Let's plot a trajectory. + +```{code-cell} ipython3 +fig, ax = plt.subplots() + +X = trajectory(0, p_H, S, n=500) + +ax.plot(X) +plt.show() + +``` + +Another option is to use existing code from QuantEcon. This code is +JIT-compiled and very fast. + + +```{code-cell} ipython3 + +mc = MarkovChain(p_H, state_values=S) +X = mc.simulate(init=0, ts_length=500) + +fig, ax = plt.subplots() +ax.plot(X) +plt.show() +``` + +Here's a solution to exercise 4.23. + + +```{code-cell} ipython3 +@jit +def compute_marginal(n=100_000, T=10): + X_vals = np.empty(n) + + for i in range(n): + X = 2 # start in state SR + for t in range(T): + W = np.random.rand() + X = tau(W, S, p_H[X, :]) + X_vals[i] = X + return np.mean(X_vals == 0) + +compute_marginal() +``` + +The answer is close to 0.6, as expected. + + +Here's a solution to exercise 4.24. + + +```{code-cell} ipython3 +@jit +def compute_marginal_2(n=100_000, T=10): + counter = 0 + + for i in range(n): + X = 2 # start in state SR + for t in range(T): + W = np.random.rand() + X = tau(W, S, p_H[X, :]) + if X == 0: + counter += 1 + + return counter / n + +compute_marginal_2() +``` + +Here's a solution to exercise 4.29. + + +```{code-cell} ipython3 +T = 5 +psi = (1, 0, 0) # start in NG +h = (1000, 0, -1000) # profits + +for t in range(T): + psi = psi @ p_H + +print(psi @ h) +``` + +Now let's see what happens when we start in severe recession. + +```{code-cell} ipython3 +psi = (0, 0, 1) + +for t in range(T): + psi = psi @ p_H + +print(psi @ h) +``` + +Profits are much lower because the Markov chain is relatively persistent, +implying that starting in recession increases the probability of recession at +date $t=5$. + + +Here's a solution to exercise 4.30. + + +```{code-cell} ipython3 +T = 1000 + +for i in (0, 1, 2): + psi = np.zeros(3) + psi[i] = 1 + for t in range(T): + psi = psi @ p_H + print(f"Profits in state {i} at date {T} equals {psi @ h}") +``` + +Notice that profits are almost invariant with respect to the initial +condition. + +This is due to inherent stability of the kernel, which implies that initial +conditions become irrelevant after sufficient time has elapsed. + +Here's a solution to exercise 4.31. + +```{code-cell} ipython3 +T = 5 +psi = (0.2, 0.2, 0.6) + +for t in range(T): + psi = psi @ p_H + +print(psi @ h) +``` + +Here's a solution to exercise 4.32. + +```{code-cell} ipython3 +def path_prob(p, psi, X): # X gives a time path + prob = psi[X[0]] + for t in range(len(X)-1): + prob = prob * p[X[t], X[t+1]] + return prob + +psi = np.array((0.2, 0.2, 0.6)) +prob = path_prob(p_H, psi, (0, 1, 0)) + +print(prob) +``` + +Here's a solution to exercise 4.33. + +```{code-cell} ipython3 +counter = 0 +recession_states = 1, 2 +for x0 in recession_states: + for x1 in recession_states: + for x2 in recession_states: + path = x0, x1, x2 + counter += path_prob(p_H, psi, path) + +print(counter) +``` + + +Here's a solution to exercise 4.34. + +```{code-cell} ipython3 +counter = 0 +m = 10_000 +mc = MarkovChain(p_H) + +for i in range(m): + x0 = tau(np.random.rand(), S, psi) + X = mc.simulate(init=x0, ts_length=3) + if 0 not in X: + counter += 1 + +print(counter / m) +``` + +Next we turn to exercise 4.36. + +```{code-cell} ipython3 +max_T = 12 +T_vals = range(max_T) +profits = [] +r = 0.05 +rho = 1 / (1+r) + +psi = (0.2, 0.2, 0.6) +h = (1000, 0, -1000) +current_profits = np.inner(psi, h) +discount = rho +Q = np.identity(3) + +for t in T_vals: + Q = Q @ p_H + current_profits += discount * np.inner(psi, Q @ h) + profits.append(current_profits) + discount = discount * rho + +fig, ax = plt.subplots() +ax.plot(profits, label='profits') +ax.plot(np.zeros(max_T), '--', label='break even') +ax.set_xlabel('time') +ax.legend() + +plt.show() + +``` + +Here's figure 4.11. + + +```{code-cell} ipython3 +p_Q = ((0.97 , 0.03 , 0.00 , 0.00 , 0.00), + (0.05 , 0.92 , 0.03 , 0.00 , 0.00), + (0.00 , 0.04 , 0.92 , 0.04 , 0.00), + (0.00 , 0.00 , 0.04 , 0.94 , 0.02), + (0.00 , 0.00 , 0.00 , 0.01 , 0.99)) + +p_Q = np.array(p_Q) +states = 0, 1, 2, 3, 4 +dates = 10, 60, 160 +initial_states = 0, 4 + +rows, cols = 2, 3 +fig, axes = plt.subplots(rows, cols) + +for row, init in enumerate(initial_states): + psi = np.zeros(5) + psi[init] = 1 + for col, d in enumerate(dates): + ax = axes[row, col] + ax.bar(states, + psi @ np.linalg.matrix_power(p_Q, d), + alpha=0.5, + edgecolor='k') + ax.set_title(f"$X_0 = {init}, t = {d}$") + ax.set_ylabel("prob") + ax.set_xlabel("state") + +plt.tight_layout() + +#plt.savefig("dds2.pdf") +plt.show() +``` + + +And here's figure 4.12. + + +```{code-cell} ipython3 +dates = 160, 500, 1000 + +fig, axes = plt.subplots(rows, cols) + +for row, init in enumerate(initial_states): + psi = np.zeros(5) + psi[init] = 1 + for col, d in enumerate(dates): + ax = axes[row, col] + ax.bar(states, + psi @ np.linalg.matrix_power(p_Q, d), + alpha=0.5, + edgecolor='k') + ax.set_title(f"$X_0 = {init}, t = {d}$") + ax.set_ylabel("prob") + ax.set_xlabel("state") + +plt.tight_layout() + +#plt.savefig("dds3.pdf") +plt.show() +``` + + +Next we turn to exercise 4.42. + +First here's a function to compute the stationary distribution, assuming it is +unique. + +```{code-cell} ipython3 +from numpy.linalg import solve + +def compute_stationary(p): + N = p.shape[0] + I = np.identity(N) + O = np.ones((N, N)) + A = I - p + O + return solve(A.T, np.ones((N, 1))).flatten() + +``` + +Now let's apply it to `p_Q`. + +```{code-cell} ipython3 + +psi_star = compute_stationary(p_Q) + +fig, ax = plt.subplots() +ax.bar(states, psi_star, alpha=0.5, edgecolor='k') + +plt.show() +``` + +As expected, the distribution is very similar to the time 1,000 distribution +we obtained above by shifting forward in time. + + +Now let's look at exercise 4.43. + + +```{code-cell} ipython3 +psi_star = compute_stationary(p_H) +print(np.inner(psi_star, h)) +``` + +If we compute profits at $t=1000$, starting from a range of initial +conditions, we get very similar results. + +```{code-cell} ipython3 +pT = np.linalg.matrix_power(p_H, 1000) +psi_vecs = (1, 0, 0), (0, 1, 0), (0, 0, 1) + +for psi in psi_vecs: + print(np.inner(psi, pT @ h)) +``` + +This is not surprising, since `p_H` is globally stable. + + +Here's the solution to exercise 4.57, which computes mean return time. + +I'll use JIT compilation to make the code fast. + +```{code-cell} ipython3 +@jit +def compute_return_time(x, p, max_iter=100_000): + X = x + t = 0 + while t < max_iter: + W = np.random.rand() + X = tau(W, S, p_H[X, :]) + t += 1 + if X == x: + return t + +@jit +def compute_mean_return_time(x, p, n=100_000): + counter = 0 + for i in range(n): + counter += compute_return_time(x, p) + return counter / n + + +[compute_mean_return_time(i, p_H) for i in range(3)] + +``` + +For comparison: + +```{code-cell} ipython3 +psi_star = compute_stationary(p_H) +1/ psi_star + +``` + +As predicted by the theory, the values are approximately equal. + + +Now let's turn to the inventory model. + +```{code-cell} ipython3 +def b(d): + " Returns probability that demand = d." + return (d >= 0) * (1/2)**(d + 1) + +def h(x, q, Q): + return x + (Q - x) * (x <= q) + +def build_p(q=2, Q=5): + p = np.empty((Q+1, Q+1)) + for x in range(Q+1): + for y in range(Q+1): + if y == 0: + p[x,y] = (1/2)**h(x, q, Q) # prob D >= h(x, q) + else: + p[x,y] = b(h(x, q, Q) - y) # prob h(x, q) - D = y + + return p + +``` + +Let's verify that the stationary distribution at $q=2$ and $Q=5$ matches the +one shown in the text. + + +```{code-cell} ipython3 + +p = build_p() +compute_stationary(p) + + +``` + +Yep, looks good. + +Now let's check that the optimal policy agrees with $q=7$, as claimed in the text. + +```{code-cell} ipython3 +Q = 20 # inventory upper bound +C = 0.1 # cost of restocking + +# profit given state x, demand d, policy q +def profit(x, d, q): + stock = h(x, q, Q) + revenue = stock if stock < d else d + cost = C * (x <= q) + return revenue - cost + +# expected profit given x and policy q +def g(x, q): + counter = 0 + for d in range(1000): + counter += profit(x, d, q) * b(d) + return counter + +def profit_at_stationary(q): + p = build_p(q=q, Q=Q) + stationary = compute_stationary(p) + counter = 0 + for x in range(Q+1): + counter += g(x, q) * stationary[x] + return counter + +def compute_optimal_policy(): + running_max = -np.inf + for q in range(Q+1): + counter = profit_at_stationary(q) + if counter > running_max: + running_max = counter + argmax = q + return(argmax) + +compute_optimal_policy() +``` + + +Here's a solution for exercise 4.61. From preceding calculations we have +the stationary probability assigned to normal growth: + + +```{code-cell} ipython3 +psi_star = compute_stationary(p_H) +psi_star[0] +``` + +The fraction of the time a long path spends in this state can be calculated as +follows. + + +```{code-cell} ipython3 +T = 1_000_000 +mc = MarkovChain(p_H) +X = mc.simulate(init=0, ts_length=T) +np.mean(X == 0) +``` + +As expected given the LLN results for stable Markov chains, the two numbers are approximately equal. + + +Finally, let's look at the expected profits question in exercise 4.63. + +Previously we calculated steady state profits via + + +```{code-cell} ipython3 +h = (1000, 0, -1000) +psi_star = compute_stationary(p_H) +print(np.inner(psi_star, h)) +``` + +To check that we get approximately the same results when simulating a long +time series, we can calculate as follows. + + +```{code-cell} ipython3 +y = [h[x] for x in X] +np.mean(y) +``` diff --git a/code_book/ch5.md b/code_book/ch5.md new file mode 100644 index 0000000..98b91cd --- /dev/null +++ b/code_book/ch5.md @@ -0,0 +1,395 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 5 Code + + +We will use the following imports. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +from numba import jit +``` + +## The Optimal Savings Problem + +First we set parameters and construct the state and action spaces. + +```{code-cell} ipython3 +beta, rho = 0.5, 0.9 +z_bar, s_bar = 10, 5 + +S = np.arange(z_bar + s_bar + 1) # State space = 0,...,z_bar + s_bar +Z = np.arange(z_bar + 1) # Shock space = 0,...,z_bar +``` + +Next we write down the primitives of the problem, such as the utility function. + + +```{code-cell} ipython3 +def U(c): + "Utility function." + return c**beta + +def phi(z): + "Probability mass function, uniform distribution." + return 1.0 / len(Z) if 0 <= z <= z_bar else 0 + +def Gamma(x): + "The correspondence of feasible actions." + return range(min(x, s_bar) + 1) +``` + + +### Value Function Iteration + +To implement VFI, we first construct the Bellman operator $T$. + +```{code-cell} ipython3 +def T(v): + Tv = np.empty_like(v) + for x in S: + # Compute the value of the objective function for each + # a in Gamma(x) and record highest value. + running_max = -np.inf + for a in Gamma(x): + y = U(x - a) + rho * sum(v[a + z]*phi(z) for z in Z) + if y > running_max: + running_max = y + # Store the maximum reward for this x in Tv + Tv[x] = running_max + return Tv +``` + +The next function computes a $w$-greedy policy. + +```{code-cell} ipython3 +def get_greedy(w): + sigma = np.empty_like(w) + for x in S: + running_max = -np.inf + for a in Gamma(x): + y = U(x - a) + rho * sum(w[a + z]*phi(z) for z in Z) + # Record the action that gives highest value + if y > running_max: + running_max = y + sigma[x] = a + return sigma +``` + + +The following function implements value function iteration, starting with the +utility function as the initial condition. + +Iteration continues until the sup deviation between iterates is less than +`tol`. + +```{code-cell} ipython3 +def compute_value_function(tol=1e-4, + max_iter=1000, + verbose=True, + print_skip=5): + + # Set up loop + v = [U(x) for x in S] # Initial condition + i = 0 + error = tol + 1 + + while i < max_iter and error > tol: + v_new = T(v) + error = np.max(np.abs(v - v_new)) + i += 1 + if verbose and i % print_skip == 0: + print(f"Error at iteration {i} is {error}.") + v = v_new + + if i == max_iter: + print("Failed to converge!") + + if verbose and i < max_iter: + print(f"\nConverged in {i} iterations.") + + return v_new +``` + +Let's run the code and plot the result. + +```{code-cell} ipython3 +v_star = compute_value_function() + +fig, ax = plt.subplots() +ax.plot(S, v_star, 'k-', label='approximate value function') +ax.legend() +ax.set_xlabel("$x$") +ax.set_ylabel("value") + +#plt.savefig("vfiv.pdf") + +plt.show() + +``` + +Now we can compute the $v^*$-greedy policy, which is approximately optimal. + +```{code-cell} ipython3 +sigma_star = get_greedy(v_star) + +fig, ax = plt.subplots() +ax.plot(S, sigma_star, 'k-', label='optimal policy') +ax.legend() + +plt.show() + +``` + +Even though value function iteration only guarantees an approximately optimal +policy, since computation of the value function is only up to a certain degree +of precision, it turns out that our policy is exactly optimal. + +This becomes clear in the next section. + +Before that, let's check that the Markov chain for wealth is globally stable +by calculating the Dobrushin coefficient. + +Here's a function to compute the Dobrushin coefficient. + +```{code-cell} ipython3 +def dobrushin(p): + running_min = 1 + for x in S: + for xp in S: + a = sum((min(p[x,y], p[xp, y]) for y in S)) + if a < running_min: + running_min = a + return running_min +``` + +Now we construct the stochastic kernel at the optimal policy and check the +Dobrushin coefficient. + +```{code-cell} ipython3 +p_sigma = np.empty((len(S), len(S))) + +for x in S: + for y in S: + p_sigma[x, y] = phi(y - sigma_star[x]) + +dobrushin(p_sigma) +``` + +The Dobrushin coefficient is positive so global stability holds. + +We'll borrow some earlier code to compute the stationary distribution. + + +```{code-cell} ipython3 +from numpy.linalg import solve + +def compute_stationary(p): + N = p.shape[0] + I = np.identity(N) + O = np.ones((N, N)) + A = I - p + O + return solve(A.T, np.ones((N, 1))).flatten() + +``` + +Now let's compute it. + +```{code-cell} ipython3 +fig, ax = plt.subplots() +psi_star = compute_stationary(p_sigma) + +ax.bar(S, psi_star, edgecolor='k', alpha=0.5, label=r'$\psi^*$') +ax.set_xlabel('wealth') +ax.legend() + +# plt.savefig("opt_wealth_dist.pdf") + +plt.show() +``` + + +### Policy Iteration + +Now we turn to the Howard policy iteration algorithm. + +In the finite state setting, this algorithm converges to the exact optimal +policy. + +To implement the algorithm, we need to be able to evaluate the lifetime reward +of any given policy. + +The next function does this using the algebraic method suggested in the +textbook. + +```{code-cell} ipython3 +def compute_policy_value(sigma): + + # Construct r_sigma and p_sigma = M_sigma + n = len(S) + r_sigma = np.empty(n) + p_sigma = np.empty((n, n)) + for x in S: + r_sigma[x] = U(x - sigma[x]) + for y in S: + p_sigma[x, y] = phi(y - sigma[x]) + + + # Solve sigma = (I - rho p_sigma)^{-1} r_sigma + I = np.identity(n) + sigma = np.linalg.solve(I - rho * p_sigma, r_sigma) + return sigma +``` + +Now we can implement policy iteration. + + +```{code-cell} ipython3 +def policy_iteration(max_iter=1e6, verbose=True): + + sigma = np.zeros(len(S)) # Starting point + i = 1 + + while i < max_iter: + v_sigma = compute_policy_value(sigma) + new_sigma = get_greedy(v_sigma) + if np.all(new_sigma == sigma): + break + else: + sigma = new_sigma + i += 1 + + if i == max_iter: + print("Failed to converge!") + + if verbose and i < max_iter: + print(f"\nConverged in {i} iterations.") + + return sigma + +``` + +Now let's compute using policy iteration and plot. + +```{code-cell} ipython3 +sigma_star_pi = policy_iteration() + +fig, ax = plt.subplots() +ax.plot(S, sigma_star_pi, 'k-', label='optimal policy via PI') +ax.legend() +ax.set_xlabel("$x$") +ax.set_ylabel("savings") + +# plt.savefig("wealth_opt_pol.pdf") + +plt.show() + +``` + +Let's check that the two ways of computing the optimal policy are +equivalent up. + + +```{code-cell} ipython3 +np.all(sigma_star == sigma_star_pi) +``` + +Success! We have found the optimal policy and both methods of computing lead +us to it. + + + + +## Equilibrium Selection + + +Here are the parameter values. + +```{code-cell} ipython3 +N = 12 +u = 2 +w = 1 +``` + +```{code-cell} ipython3 +@jit +def reward1(z): + return (z/N) * u + +@jit +def reward2(z): + return ((N - z)/N) * w + +@jit +def B(z): + if reward1(z) > reward2(z): + return N + elif reward1(z) < reward2(z): + return 0 + else: + return z + +@jit +def update(z, epsilon): + Vu = np.random.binomial(N - B(z), epsilon) + Vw = np.random.binomial(B(z), epsilon) + return B(z) + Vu - Vw +``` + +Plot the function $B$ as a 45 degree diagram. + +```{code-cell} ipython3 +fig, ax = plt.subplots() +state = np.arange(0, N+1) +ax.plot(state, state, 'k--', label='45 degrees') +ax.scatter(state, [B(x) for x in state], label='$B(x)$') +ax.legend() +ax.set_xlabel('$x$') +# plt.savefig("os_rewards.pdf") +plt.show() +``` + +Now we barplot the fraction of time sample paths spend at $N$ as epsilon varies. + +```{code-cell} ipython3 + +@jit +def compute_time_at_state(s, epsilon, sim_length=1_000_000, x_init=5): + X = x_init + counter = 0 + for t in range(sim_length): + X = update(X, epsilon) + if X == s: + counter += 1 + return counter / sim_length + +epsilons = np.arange(0.001, 0.1, step=0.01) +sample_frac = np.empty(len(epsilons)) +for i, eps in enumerate(epsilons): + sample_frac[i] = compute_time_at_state(N, eps) + +fig, ax = plt.subplots() +ax.bar(epsilons, sample_frac, width=0.005, edgecolor='k', alpha=0.5) +ax.set_ylim(0, 1.01) +ax.set_xlabel('$\\epsilon$') +ax.set_ylabel('fraction') +#plt.savefig("mutation_ess.pdf") +plt.show() + +``` + + diff --git a/code_book/ch6.md b/code_book/ch6.md new file mode 100644 index 0000000..383409f --- /dev/null +++ b/code_book/ch6.md @@ -0,0 +1,1012 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 6 Code + +We start with some imports. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +from numba import jit, vectorize +from interpolation import interp +from scipy.interpolate import interp1d +from scipy.optimize import minimize_scalar, brentq +from scipy.stats import beta + +``` + +## First Steps + +Here is the code that generated figure 6.1. + +```{code-cell} ipython3 +alpha = 0.5 # capital intensity +sigma = 0.2 # shock parameter +s = 0.5 # savings rate +delta = 0.1 # depreciation + +default_params = alpha, sigma, s, delta + +@jit +def f(k, params=default_params): + alpha, sigma, s, delta = params + return k**alpha + +@jit +def update(k, params=default_params): + alpha, sigma, s, delta = params + w = np.exp(sigma * np.random.randn()) + new_k = s * f(k) * w + (1 - delta) * k + return new_k + +@jit +def generate_solow_ts(init=1, ts_length=200, params=default_params): + " A function to generate time series. " + k = np.empty(ts_length) + k[0] = init + for t in range(ts_length-1): + k[t+1] = update(k[t], params=params) + return k + +fig, ax = plt.subplots() + +ax.plot(generate_solow_ts(init=1), label='$k_0 = 1$') +ax.plot(generate_solow_ts(init=60), '--', label='$k_0 = 60$') +ax.legend() +ax.set_xlabel('time') +#plt.savefig('solow_ts.pdf') +plt.show() + +``` + +Here is a function to generate draws from the time $t$ marginal distribution of +capital. + +```{code-cell} ipython3 + +@jit +def sim_from_marginal(t=10, init=1, sim_size=1000, params=default_params): + k_draws = np.empty(sim_size) + for i in range(sim_size): + k = init + for j in range(t): + k = update(k, params=params) + k_draws[i] = k + return k_draws +``` + +Now we generate some draws for date $t=20$ under the default parameters and +take the mean, which solves exercise 6.2. + +```{code-cell} ipython3 +draws = sim_from_marginal(t=20) +draws.mean() +``` + +Here is the solution to exercise 6.3. + + +```{code-cell} ipython3 +new_s = 3/4 +new_params = alpha, sigma, new_s, delta +draws = sim_from_marginal(t=20, params=new_params) +draws.mean() +``` + +Not surprisingly, mean capital stock at this (and any given) date is higher, +due to higher savings. + + +Here is the solution to exercise 6.4. + + +```{code-cell} ipython3 +init_values = 5, 10, 20 +for init_k in init_values: + draws = sim_from_marginal(t=20, init=init_k) + print(f"Mean capital from initial condition {init_k} is {draws.mean():.2f}") +``` + +Higher initial capital leads to higher mean capital at $t=20$. + +This is as expected, since the law of motion for capital is increasing in +lagged capital. + + +Here is the solution to exercise 6.5. + + +```{code-cell} ipython3 +dates = 50, 100, 200 +for date in dates: + draws = sim_from_marginal(t=date) + print(f"Mean capital at {date} is {draws.mean():.2f}") +``` + +Mean capital is slightly increasing, although it stabilizes as $t \to \infty$. + +The initial increase is due to the fact that the initial condition is +relatively small. Hence we see growth on average, as shown in the time series +plot starting from $k_0=1$ above. + +For exercise 6.6 we compute the 95% confidence interval as follows + +```{code-cell} ipython3 +draws = sim_from_marginal(t=20) +n = len(draws) +k_bar = draws.mean() +sigma_hat = draws.std() +c = 1.96 + +lower = k_bar - (sigma_hat / np.sqrt(n)) * c +upper = k_bar + (sigma_hat / np.sqrt(n)) * c + +print(f"Sample mean is {k_bar:.4f}") +print("The 95% Confidence interval is ", [round(x, 4) for x in (lower, upper)]) + +``` + +For exercise 6.7 we approximate steady state consumption at a range of savings +values using the method described in the exercise. + + +```{code-cell} ipython3 +m = 100_000 +min_s, max_s, grid_size = 0.2, 0.8, 20 +savings_rates = np.linspace(min_s, max_s, grid_size) +expected_consumption = np.empty_like(savings_rates) + +for i, s in enumerate(savings_rates): + params = alpha, sigma, s, delta + draws = sim_from_marginal(t=100, sim_size=m, params=params) + w_vec = np.exp(sigma * np.random.randn(m)) + c_bar = np.mean( (1-s) * f(draws) * w_vec ) + expected_consumption[i] = c_bar + +fig, ax = plt.subplots() + +ax.plot(savings_rates, expected_consumption, label='expected consumption') +ax.legend() +ax.set_xlabel('savings rate') +#plt.savefig('solow_golden.pdf') +plt.show() + +``` + +Exercise 6.8 asks us to compute the empirical distribution of $\psi_t$ when +$t=20$ at different sample sizes. + + +```{code-cell} ipython3 +fig, axes = plt.subplots(2, 2) +axes = axes.flatten() +sample_sizes = 4, 25, 100, 5000 +grid_points = np.linspace(0, 25, 100) + +for ax, n in zip(axes, sample_sizes): + draws = sim_from_marginal(t=20, sim_size=n) + ecdf_vals = [np.mean(draws <= x) for x in grid_points] + ax.plot(grid_points, ecdf_vals) + ax.set_title(f"$n={n}$") + +plt.tight_layout() +#plt.savefig('solow_ecdf.pdf') +plt.show() + +``` + +Our next task is to simulate from the "threshold exernalities" model. + +The following code generates figures 6.6 and 6.7. + + +```{code-cell} ipython3 +alpha = 0.5 # capital intensity +s = 0.25 # savings rate +sigma = 0.14 # shock parameter +delta = 1.0 # depreciation +A1, A2 = 15, 25 +k_b = 21.6 + + +@jit +def A(k): + return A1 if k < k_b else A2 + +@jit +def f(k): + return k**alpha + +@jit +def update(k): + w = np.exp(sigma * np.random.randn()) + new_k = s * A(k) * f(k) * w + (1 - delta) * k + return new_k + +@jit +def generate_ad_ts(init=1, ts_length=400): + k = np.empty(ts_length) + k[0] = init + for t in range(ts_length-1): + k[t+1] = update(k[t]) + return k + +fig, ax = plt.subplots() + +ax.plot(generate_ad_ts(init=1), label='$k_0 = 1$') +ax.plot(generate_ad_ts(init=80), '--', label='$k_0 = 80$') +ax.legend() +ax.set_xlabel('time') +#plt.savefig('adtssim.pdf') +plt.show() + +``` + +The next piece of code computes an approximation of the first passage time +above $k_b$ using simulation. + + +```{code-cell} ipython3 +@jit +def draw_tau(): + tau = 0 + k = 1 + while k < k_b: + k = update(k) + tau += 1 + return tau + +def tau_draws(m=10_000): + draws = np.empty(m) + for i in range(m): + draws[i] = draw_tau() + +print(np.mean(draws)) +``` + +Here is a solution to Exercise 6.11, which implements a kernel density +estimator and tests it on some data. + +The kernel density estimator uses a Gaussian kernel and is implemented via a closure. + +We use the `vectorize` decorator from Numba to ensure that the function can act +correctly on arrays as well as scalars. + +```{code-cell} ipython3 + +k0 = np.sqrt(1 / (2 * np.pi)) # for Gaussian kernel +@jit +def K(z): + return k0 * np.exp(-z**2) + +def kde_factory(data, delta): + + @vectorize + def kde(x): + return (1 / delta) * np.mean( K((x - data) / delta) ) + + return kde +``` + +Now we produce the figure. + +```{code-cell} ipython3 +Y = np.random.randn(100) +deltas = 0.01, 0.05, 0.1, 0.5 +x_grid = np.linspace(-2, 2, 200) + +fig, axes = plt.subplots(2, 2) +axes = axes.flatten() + +for delta, ax in zip(deltas, axes): + f = kde_factory(Y, delta) + ax.plot(x_grid, f(x_grid)) + ax.set_title(rf"$\delta_n = {delta}$") + +plt.tight_layout() +#plt.savefig("kdes.pdf") +plt.show() + +``` + +Next we turn to exercise~6.12, which concerns the STAR model. + +We will use a `jitclass`, which requires us to set up parameters. + +```{code-cell} ipython3 +from numba import float64 +from numba.experimental import jitclass + +star_data = ( + ('alpha_0', float64), + ('alpha_1', float64), + ('beta_0', float64), + ('beta_1', float64) +) +``` + +Next we set up the class, which implements the law of motion and the +stochastic kernel. + + +```{code-cell} ipython3 +@jitclass(star_data) +class STAR: + + def __init__(self, alpha_0=-4.0, alpha_1=0.4, beta_0=5.0, beta_1=0.6): + + self.alpha_0, self.alpha_1 = alpha_0, alpha_1 + self.beta_0, self.beta_1 = beta_0, beta_1 + + def G(self, x): + " Logistic function " + return 1 / (1 + np.exp(-x)) + + def g(self, x): + " Smooth transition function " + a = (self.alpha_0 + self.alpha_1 * x) * (1-self.G(x)) + b = (self.beta_0 + self.beta_1 * x) * self.G(x) + return a + b + + def update(self, x): + " Update by one time step. " + W = np.random.randn() + return self.g(x) + W + + def draws_from_marginal(self, t_date, init=0, n=10_000): + draws = np.empty(n) + for i in range(n): + X = init + for t in range(t_date): + X = self.update(X) + draws[i] = X + return draws + + def phi(self, z): + " Standard normal density " + return k0 * np.exp(-z**2) + + def p(self, x, y): + " Stochastic kernel " + return self.phi(y - self.g(x)) +``` + +```{code-cell} ipython3 +s = STAR() +x_grid = np.linspace(-10, 20, 200) + +fig, ax = plt.subplots() +ax.plot(x_grid, x_grid, '--', label='45 degrees') +ax.plot(x_grid, s.g(x_grid), label='$g$') +ax.legend() + +#plt.savefig("star_g.pdf") +plt.show() +``` + +Now we use a closure to produce a look ahead estimator of the time $t$ +marginal given a stochastic kernel and a set of draws from time $t+1$. + +```{code-cell} ipython3 +def lae_factory(p, x_data): + + def f(y): + return np.mean(p(x_data, y)) + + return f + +``` + +This code solves exercise 6.12. + + +```{code-cell} ipython3 +dates = 1, 2, 4, 8, 16, 32 +y_grid = np.linspace(-20, 20, 200) +greys = [str(g) for g in np.linspace(0.2, 0.8, len(dates))] +greys.reverse() + +s = STAR() + +fig, ax = plt.subplots() + +for t, g in zip(dates, greys): + x_obs = s.draws_from_marginal(t) + f = lae_factory(s.p, x_obs) + ax.plot(y_grid, + [f(y) for y in y_grid], + color=g, + lw=2, + alpha=0.6, + label=f'$t={t}$') + +ax.set_xlabel('$x$') +ax.legend() + +#plt.savefig("starseq.pdf") +plt.show() +``` + +Solutions to exercises 6.13 -- 6.15 are omitted. They can be solved using +techniques similar to those described above and immediately below. + +We complete section 6.1 by solving exercise 6.16. + + +```{code-cell} ipython3 +ad_data = ( + ('alpha', float64), + ('s', float64), + ('sigma', float64), + ('A1', float64), + ('A2', float64), + ('k_b', float64), +) + + +@jitclass(ad_data) +class AD: + + def __init__(self, + alpha=0.5, + s=0.25, + sigma=0.14, + A1=15, + A2=25, + k_b = 21.6): + + self.alpha, self.s, self.sigma = alpha, s, sigma + self.A1, self.A2, self.k_b = A1, A2, k_b + + def A(self, k): + return self.A1 * (k < self.k_b) + self.A2 * (k >= self.k_b) + + def f(self, k): + return k**self.alpha + + def update(self, k): + w = np.exp(self.sigma * np.random.randn()) + new_k = self.s * self.A(k) * self.f(k) * w + return new_k + + def generate_ts(self, init=1, ts_length=100_000): + k = np.empty(ts_length) + k[0] = init + for t in range(ts_length-1): + k[t+1] = self.update(k[t]) + return k + + def phi(self, z): + " Lognormal density for N(0, sigma^2) " + a = k0 / (z * self.sigma) + b = np.exp(- np.log(z)**2 / (2 * self.sigma**2)) + return a * b + + def p(self, x, y): + z = self.s * self.A(x) * self.f(x) + return self.phi(y/z) / z + +``` + +Now we use the class to generate the figure. + + + +```{code-cell} ipython3 +sigmas = 0.1, 0.12, 0.14, 0.16, 0.18 +y_grid = np.linspace(0, 60, 140) +greys = [str(g) for g in np.linspace(0.2, 0.8, len(sigmas))] +greys.reverse() + +fig, ax = plt.subplots() + +for sigma, g in zip(sigmas, greys): + ad = AD(sigma=sigma) + x_obs = ad.generate_ts() + f = lae_factory(ad.p, x_obs) + ax.plot(y_grid, + [f(y) for y in y_grid], + color=g, + lw=2, + alpha=0.6, + label=rf'$\sigma={sigma}$') + +ax.set_xlabel('$k$') +ax.legend() + +#plt.savefig("ad_sdla.pdf") +plt.show() +``` + +## Optimal Savings + +Optimization routines in the Python numerical tool set typically perform +minimization. + +It is useful for us to have a function +maximizises a map g over an interval [a, b]. + +We use the fact that the maximizer of g on any interval is also the minimizer +of -g. + + +```{code-cell} ipython3 +def maximize(g, a, b, args): + """ + Returns the maximal value and the maximizer. + The tuple args collects any extra arguments to g. + """ + objective = lambda x: -g(x, *args) + result = minimize_scalar(objective, bounds=(a, b), method='bounded') + maximizer, maximum = result.x, -result.fun + return maximizer, maximum +``` + + +```{code-cell} ipython3 +class OptimalSaving: + + def __init__(self, + U, # utility function + R=1.05, # gross return on saving + rho=0.96, # discount factor + b=0.1, # shock scale parameter + grid_max=2, + grid_size=120, + shock_size=250, + seed=1234): + + self.U, self.R, self.rho, self.b = U, R, rho, b + + # Set up grid + self.grid = np.linspace(1e-4, grid_max, grid_size) + + # Store shocks (with a seed, so results are reproducible) + np.random.seed(seed) + self.shocks = np.exp(b * np.random.randn(shock_size)) + + def state_action_value(self, s, a, v_array): + """ + Right hand side of the Bellman equation. + """ + + U, R, rho, shocks = self.U, self.R, self.rho, self.shocks + + v = interp1d(self.grid, + v_array, + fill_value=(v_array[0], v_array[-1]), + bounds_error=False) + + return U(a-s) + rho * np.mean(v(R * s + shocks)) + +``` + +In the last line, the expectation is computed via Monte Carlo. + + +Now we write up the Bellman operator. + +```{code-cell} ipython3 +def T(v, os): + """ + The Bellman operator. Updates the guess of the value function + and also computes a v-greedy policy. + + * os is an instance of OptimalSaving + * v is an array representing a guess of the value function + + """ + v_new = np.empty_like(v) + v_greedy = np.empty_like(v) + + for i in range(len(os.grid)): + a = os.grid[i] + + # Maximize RHS of Bellman equation at state a + c_star, v_max = maximize(os.state_action_value, 1e-10, a, (a, v)) + v_new[i] = v_max + v_greedy[i] = c_star + + return v_greedy, v_new +``` + +Here is a figure showing the function sequence generated by value function +iteration, starting at the utility function. + + +```{code-cell} ipython3 +def solve_model(os, + tol=1e-4, + max_iter=1000, + verbose=True, + print_skip=25): + """ + Solve the model by iterating with the Bellman operator. + + * os is an instance of OptimalSaving + + """ + + # Set up loop + v = U(os.grid) # Initial condition + i = 0 + error = tol + 1 + + while i < max_iter and error > tol: + v_greedy, v_new = T(v, os) + error = np.max(np.abs(v - v_new)) + i += 1 + if verbose and i % print_skip == 0: + print(f"Error at iteration {i} is {error}.") + v = v_new + + if i == max_iter: + print("Failed to converge!") + + if verbose and i < max_iter: + print(f"\nConverged in {i} iterations.") + + return v_greedy, v_new +``` + + +Let us put this code to work. + +```{code-cell} ipython3 +@jit +def U(c, gamma=0.5): + "The utility function." + return c**(1 - gamma) / (1 - gamma) + +os = OptimalSaving(U) + +#v = U(os.grid) # Initial condition +v = np.zeros_like(os.grid) # Initial condition +n = 40 # Number of iterations +greys = [str(g) for g in np.linspace(0.0, 0.8, n)] +greys.reverse() + +fig, ax = plt.subplots() + +for i in range(n): + v_greedy, v = T(v, os) # Apply the Bellman operator + ax.plot(os.grid, v, color=greys[i], lw=1.0, alpha=0.6) + +ax.set_xlabel('assets') +ax.set_ylabel('value') +ax.set(xlim=(np.min(os.grid), np.max(os.grid))) +plt.show() + +``` + + + +The next code block uses a more systematic method to find a good approximation +to the value function and then computes the associated greedy policy. + +```{code-cell} ipython3 +os = OptimalSaving(U, grid_max=4) +sigma_star, v_star = solve_model(os) + +fig, ax = plt.subplots() + +ax.plot(os.grid, sigma_star, + label='optimal policy', lw=2, alpha=0.6) +ax.plot(os.grid, os.R * sigma_star + 1.01, '-.', + label='next period assets', lw=2, alpha=0.6) +ax.plot(os.grid, os.grid, 'k--', + label='45 degrees', lw=1, alpha=0.6) + +ax.legend() +plt.show() + +``` + +Now we use the look-ahead estimator to compute asset density dynamics under +the optimal policy. + + + +```{code-cell} ipython3 +# Turn the policy array into a function +sigma = lambda x: interp(os.grid, sigma_star, x) + +@vectorize +def phi(z, b): + " Lognormal density for N(0, b^2) " + if z <= 0: + return 0 + else: + c1 = k0 / (z * b) + c2 = np.exp(- np.log(z)**2 / (2 * b**2)) + return c1 * c2 + +def p(x, y): + return phi(y - os.R * sigma(x), os.b) + +def draws_from_marginal(t_date, init=0, n=10_000): + draws = np.empty(n) + for i in range(n): + a = init + for t in range(t_date): + xi = np.exp(os.b * np.random.randn()) + a = os.R * sigma(a) + xi + draws[i] = a + return draws + +dates = 1, 2, 4, 8, 16, 32 +greys = [str(g) for g in np.linspace(0.2, 0.8, len(dates))] +greys.reverse() +grid = np.linspace(0, 5, 200) + +fig, ax = plt.subplots() + +for t, g in zip(dates, greys): + a_obs = draws_from_marginal(t) + f = lae_factory(p, a_obs) + ax.plot(grid, + [f(y) for y in grid], + color=g, + lw=2, + alpha=0.6, + label=f'$t={t}$') + +ax.set_xlabel('assets') +ax.legend() + +plt.show() + +``` + +Finally we turn to policy function iteration, in order to solve exercise 6.19. + +We need to compute the value of any feasible policy $\sigma$. + +To do so we will iterate with the policy value operator $T_\sigma$. + +```{code-cell} ipython3 +def T_sigma(v, sigma, os): + """ + Here os is an instance of OptimalSaving and v and sigma + are both arrays defined on the grid. + """ + v_new = np.empty_like(v) + + + sigma_interp = interp1d(os.grid, + sigma, + fill_value=(sigma[0], sigma[-1]), + bounds_error=False) + + for i in range(len(os.grid)): + a = os.grid[i] + v_new[i] = os.state_action_value(sigma_interp(a), a, v) + + return v_new +``` + +To compute the policy value we iterate until convergence, starting +from some guess of $v_\sigma$ represented by `v_guess`. + +```{code-cell} ipython3 +def compute_policy_value(os, + sigma, + v_guess=None, + tol=1e-3, + max_iter=1000): + + if v_guess is None: + v_guess = U(os.grid) + v = v_guess + i = 0 + error = tol + 1 + + while i < max_iter and error > tol: + v_new = T_sigma(v, sigma, os) + error = np.max(np.abs(v - v_new)) + i += 1 + v = v_new + + return v_new +``` + + +We will use existing code to get a greedy policy from a guess $v$ of the value +function. + +```{code-cell} ipython3 +def get_greedy(v, os): + sigma, _ = T(v, os) + return sigma +``` + + +```{code-cell} ipython3 +def policy_iteration(os, tol=1e-3, max_iter=1e5, verbose=True): + + sigma = np.zeros_like(os.grid) # Initial condition is consume everything + v_guess = U(os.grid) # Starting guess for value function + i = 1 + + while i < max_iter: + v_sigma = compute_policy_value(os, sigma, v_guess=v_guess) + new_sigma = get_greedy(v_sigma, os) + if np.abs(np.max(new_sigma - sigma)) < tol: + break + else: + sigma = new_sigma + v_guess = v_sigma # Use last computed value to start iteration + i += 1 + + if i == max_iter: + print("Failed to converge!") + + if verbose and i < max_iter: + print(f"\nConverged in {i} iterations.") + + return sigma + +``` + +Running the following code produces essentially the same figure we obtained +for the optimal policy when using value function iteration, just as we hoped. + +```{code-cell} ipython3 +os = OptimalSaving(U, grid_max=4) +sigma_star = policy_iteration(os) + +fig, ax = plt.subplots() + +ax.plot(os.grid, sigma_star, + label='optimal policy', lw=2, alpha=0.6) +ax.plot(os.grid, os.R * sigma_star + 1.01, '-.', + label='next period assets', lw=2, alpha=0.6) +ax.plot(os.grid, os.grid, 'k--', + label='45 degrees', lw=1, alpha=0.6) + +ax.legend() +plt.show() + +``` + + +## Stochastic Speculative Price + + +Finally, to conclude code for this chapter, we turn to the commodity pricing +model. + +The next code block computes figure 6.16 and hence solves exercise 6.26. + +```{code-cell} ipython3 +alpha, a, c = 0.8, 5.0, 2.0 +beta_a, beta_b = 5, 5 +mc_draw_size = 250 +gridsize = 150 +grid_max = 35 +grid = np.linspace(a, grid_max, gridsize) + +beta_dist = beta(5, 5) +W = a + beta_dist.rvs(mc_draw_size) * c # Shock observations +D = P = lambda x: 1.0 / x +tol = 1e-4 + +def fix_point(h, lower, upper): + "Computes the fixed point of h on [upper, lower]." + return brentq(lambda x: x - h(x), lower, upper) + +def T(p_array): + + new_p = np.empty_like(p_array) + + # Interpolate to obtain p as a function. + p = interp1d(grid, + p_array, + fill_value=(p_array[0], p_array[-1]), + bounds_error=False) + + for i, x in enumerate(grid): + y = alpha * np.mean(p(W)) + if y <= P(x): + new_p[i] = P(x) + continue + h = lambda r: alpha * np.mean(p(alpha*(x - D(r)) + W)) + new_p[i] = fix_point(h, P(x), y) + + return new_p + + +fig, ax = plt.subplots() + +price = P(grid) +error = tol + 1 +while error > tol: + ax.plot(grid, price, 'k--', alpha=0.5, lw=1) + new_price = T(price) + error = max(np.abs(new_price - price)) + price = new_price + +ax.plot(grid, price, 'k-', alpha=0.5, lw=2, label=r'$p^*$') +ax.legend() + +plt.show() + +``` + +Our final task for this chapter is to solve exercise 6.28, which involves +computing the stationary density of the associated state process for +quantities. + +To do so we use the look ahead estimator. + +```{code-cell} ipython3 + +# Turn the price array into a price function +p_star = lambda x: interp(grid, price, x) + +def carry_over(x): + return alpha * (x - D(p_star(x))) + +def phi(z): + "Shock distribution computed by change of variables." + return beta_dist.pdf((z - a)/c) / c + +def p(x, y): + return phi(y - carry_over(x)) + +def generate_cp_ts(init=0, n=1000): + X = np.empty(n) + X[0] = init + for t in range(n-1): + W = a + c * beta_dist.rvs() + X[t+1] = carry_over(X[t]) + W + return X + +fig, ax = plt.subplots() + +plot_grid = np.linspace(a, a * 1.5, 200) + +x_ts = generate_cp_ts() +f = lae_factory(p, x_ts) +ax.plot(plot_grid, + [f(y) for y in plot_grid], + lw=2, + alpha=0.6, + label=r'$\psi^*$') +ax.plot(plot_grid, + phi(plot_grid), + '--', + lw=2, + alpha=0.6, + label=r'$\phi$') + +ax.set_xlabel('$x$') +ax.legend() + +plt.show() + +``` + +As claimed in the statement of the exercise, the two distributions line up +exactly. + +Hence, for these particular parameters, speculation has no impact on long run +average outcomes. + +If we change parameters to make storage more attractive, then we will see +differences between the distributions. + + diff --git a/code_book/ch7.md b/code_book/ch7.md new file mode 100644 index 0000000..0a4f62a --- /dev/null +++ b/code_book/ch7.md @@ -0,0 +1,19 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 7 Code + +No code for this chapter. + diff --git a/code_book/ch8.md b/code_book/ch8.md new file mode 100644 index 0000000..2cac6c6 --- /dev/null +++ b/code_book/ch8.md @@ -0,0 +1,92 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 8 Code + +Let us start with some imports. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d.axes3d import Axes3D +from matplotlib import cm + +``` + +Here is the code for figure 8.1. + +```{code-cell} ipython3 +a, b = 0.8, 1.0 # parameters + +def phi(z): + "Standard normal density." + return (1/np.sqrt(2 * np.pi)) * np.exp(-z**2/2) + +def p(x, y): + return phi(y - a * x - b) + +xgrid = np.linspace(-3, 3, 50) +ygrid = xgrid +x, y = np.meshgrid(xgrid, ygrid) + +fig = plt.figure() +ax = fig.add_subplot(111, projection='3d') + +ax.plot_surface(x, + y, + p(x, y), + rstride=2, cstride=2, + cmap=cm.Blues_r, + alpha=0.9, + linewidth=0.25) +ax.set_zticks((0.0, 0.05, 0.1, 0.15)) +ax.set_xlabel('$x$') +ax.set_ylabel('$y$') +ax.zaxis.set_rotate_label(False) +ax.set_zlabel('$p(x,y)$', rotation=0, labelpad=20) + +azimuth, elevation = -128, 25 +ax.view_init(elevation, azimuth) + +plt.show() + +``` + +Here is the code for the time series in figure 8.2. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt + +ts_length = 100 + +X = np.zeros(ts_length) + +for t in range(ts_length-1): + Z = np.random.randn() + X[t+1] = a * X[t] + b + Z + +fig, ax = plt.subplots() + +ax.plot(X, alpha=0.7, label='$X_t$') +ax.legend() +ax.set_xlabel("time") + +plt.show() + +``` + +Other figures from this chapter were written using different tools and are +omitted. diff --git a/code_book/ch9.md b/code_book/ch9.md new file mode 100644 index 0000000..1d286a0 --- /dev/null +++ b/code_book/ch9.md @@ -0,0 +1,18 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Chapter 9 Code + +No code used in this chapter. diff --git a/code_book/intro.md b/code_book/intro.md new file mode 100644 index 0000000..330884c --- /dev/null +++ b/code_book/intro.md @@ -0,0 +1,39 @@ +Economic Dynamics Code Book +=========================== + +**AUTHOR**: [John Stachurski](https://johnstachurski.net/) + + +This Jupyter book provides code to accompany the second edition of the +textbook [Economic Dynamics: Theory and + Computation](https://johnstachurski.net/edtc.html), published by the [MIT +Press](https://mitpress.mit.edu/). The code recreates figures from the book and gives solutions to exercises. + + +The only code provided at this stage is Python. A Julia version is lacking +only due to time constraints. If there are readers who would like to create a +Julia version, please get in touch via email or the [issue +tracker](https://github.com/jstac/edtc-code/issues). A MATLAB version is also +welcome --- although I am not personally familiar with the language. + +```{note} +To run the code contained here, please install the latest version of +[Anaconda Python](https://www.anaconda.com/). For a few of the programs you will also +need the [QuantEcon Python library](https://quantecon.org/quantecon-py/) and +the [Interpolation library](https://github.com/EconForge/interpolation.py) +(install as required following the instructions on each library page). + +If you encounter errors, please open an +[issue](https://github.com/jstac/edtc-code/issues) and copy and paste your +error message. +``` + +The Python code contained in these notes is accelerated through a combination of +[NumPy](https://numpy.org/) and just-in-time compilation +(via [Numba](http://numba.pydata.org/)). For those who require it, QuantEcon +provides a fast-paced [introduction to scientific computing with +Python](https://python-programming.quantecon.org/) with background on +these topics. + + +This code book is created using [Jupyter Book](https://jupyterbook.org/intro.html). diff --git a/code_book/logo.png b/code_book/logo.png new file mode 100644 index 0000000..c61d333 Binary files /dev/null and b/code_book/logo.png differ diff --git a/matlab_code/README.txt b/matlab_code/README.txt deleted file mode 100644 index 145c408..0000000 --- a/matlab_code/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -The following files are left untouched (i.e. no modification) because they are written in a standard way for Matlab coding. -- ar1.m -- ecdf.m -- fphamilton.m -- polyclass0.m -- quadmap1.m diff --git a/matlab_code/ar1.m b/matlab_code/ar1.m deleted file mode 100644 index bce6d6e..0000000 --- a/matlab_code/ar1.m +++ /dev/null @@ -1,12 +0,0 @@ -% Filename: ar1.m -% Author: Andy Qi -% Date: December 2008 -% Corresponds to: Listing 8.1 - -a = 0.5; -b = 1; -X = zeros(1, 101); % Create an empty array to store path -X(1) = normrnd(0, 1); % X_0 has dist N(0, 1) -for t = 1:100 - X(t + 1) = normrnd(a * X(t) + b, 1); -end diff --git a/matlab_code/cpdynam_rev1/README.txt b/matlab_code/cpdynam_rev1/README.txt deleted file mode 100644 index ab7c407..0000000 --- a/matlab_code/cpdynam_rev1/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Listing 2.2 is divided into two functions, evaluate and differentiate. -def __init__(self,coef) in the python code is omitted. -Instead, "coef" is just dealt with as an input in those two functions. \ No newline at end of file diff --git a/matlab_code/cpdynam_rev1/T.m b/matlab_code/cpdynam_rev1/T.m deleted file mode 100644 index 009ee1d..0000000 --- a/matlab_code/cpdynam_rev1/T.m +++ /dev/null @@ -1,22 +0,0 @@ -% Filename: T.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: T in Listing 6.7 - - -function t = T(p, x, W, P, D) - -global alpha - -% Computes Tp(x), where T is the pricing functional operator. -% Parameters : p is an instance of lininterp and x is a number. - -y = alpha * mean(p(W)); - if y <= P(x) - t = P(x); - return; - end -h = @(r) alpha * mean(p(alpha * (x - D(r)) + W)); -t = fix_point(h, P(x), y); -end - diff --git a/matlab_code/cpdynam_rev1/cpdynam_rev1.m b/matlab_code/cpdynam_rev1/cpdynam_rev1.m deleted file mode 100644 index e078c2d..0000000 --- a/matlab_code/cpdynam_rev1/cpdynam_rev1.m +++ /dev/null @@ -1,46 +0,0 @@ -% Filename: cpdynam_rev1.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: Listing 6.7 - -% Note: All of the code is wrapped in a class definition so that it can -% be put in one file. See the comments in kurtzbellman.m. An example -% of usage is given below. - -global alpha a c - alpha = 0.8; - a = 5.0; - c = 2.0; - - W = betarnd(5, 5, 1, 1000) * c + a; % Shock obs. - P = @(x) 1.0/x; % Inverse demand function - D = P; - - -% The following test shows how to use the codes. lininterp_rev1.m must -% be in the current folder. - -% gridsize = 150; -% grid = linspace(a, 35, gridsize); -% tol = 0.0005; -% vals = zeros(1, gridsize); -% new_vals = zeros(1, gridsize); -% for i = 1:gridsize -% vals(i) = P(grid(i)); -% end -% while 1 -% hold on; -% plot(grid, vals); -% p = @(z)lininterp_rev1(grid, vals,z); -% f = @(x) T(p, x, W, P, D); -% for i = 1:gridsize -% new_vals(i) = f(grid(i)); -% end -% if max(abs(new_vals - vals)) < tol -% break -% end -% vals = new_vals; -% end -% hold off; - - diff --git a/matlab_code/cpdynam_rev1/fix_point.m b/matlab_code/cpdynam_rev1/fix_point.m deleted file mode 100644 index ff6291f..0000000 --- a/matlab_code/cpdynam_rev1/fix_point.m +++ /dev/null @@ -1,14 +0,0 @@ -% Filename: fix_point.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: fixpoint in Listing 6.7 - -function fp = fix_point(h, lower, upper) -% Computes the fixed point of h on [upper,lower] using fzero, -% which finds the zeros (roots) of a univariate function. -% Inputs : h is a function and lower and upper are numbers -% (floats or integers). - -fp = fzero(@(x) x - h(x), [lower, upper]); - -end \ No newline at end of file diff --git a/matlab_code/cpdynam_rev1/lininterp_rev1.m b/matlab_code/cpdynam_rev1/lininterp_rev1.m deleted file mode 100644 index e1dc413..0000000 --- a/matlab_code/cpdynam_rev1/lininterp_rev1.m +++ /dev/null @@ -1,17 +0,0 @@ -% Filename: lininterp_rev1.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: Listing 6.4 - -function g = lininterp_rev1(X,Y,z) - % Uses MATLAB's interp1 function for interpolation. - % The z values are truncated so that they lie inside - % the grid points. The effect is that evaluation of - % a point to the left of X(1) returns Y(1), while - % evaluation of a point to the right of X(end) returns - % Y(end) - - z = max(z, X(1)); - z = min(z, X(end)); - g = interp1(X, Y, z); -end diff --git a/matlab_code/ds_rev1.m b/matlab_code/ds_rev1.m deleted file mode 100644 index 3631d8e..0000000 --- a/matlab_code/ds_rev1.m +++ /dev/null @@ -1,25 +0,0 @@ -% Filename: ds_rev1.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Listing 4.2 - - -function [ X ] = ds_rev1(h,x,n) -% Inputs: -% h -> an arbitrary handle function; -% x -> the initial state; -% n -> size of length; -% -% Output: -% X -> trajectory - -% Set the initial state -X(1)= x; - -% Generate a tragectory of length n, starting at the current state -for i = 1 : n-1 - X(i+1) = h(X(i)); % Update the state of the system by applying h -end - -end - diff --git a/matlab_code/ecdf.m b/matlab_code/ecdf.m deleted file mode 100644 index dcd580f..0000000 --- a/matlab_code/ecdf.m +++ /dev/null @@ -1,2 +0,0 @@ - -% Please use the MATLAB built in ecdf function diff --git a/matlab_code/fphamilton.m b/matlab_code/fphamilton.m deleted file mode 100644 index 79850cf..0000000 --- a/matlab_code/fphamilton.m +++ /dev/null @@ -1,13 +0,0 @@ -% Filename: fphamilton.m -% Author: Andy Qi -% Date: December 2008 -% Corresponds to: Listing 4.6 - -pH = [0.971,0.029,0.000; % Hamilton's kernel - 0.145,0.778,0.077; - 0.000,0.508,0.492]; -I = eye(3); % 3 by 3 identity matrix -Q = ones(3); % Matrix of ones -b = ones(3,1); % Vector of ones -A = transpose(I - pH + Q); -solution = A \ b; diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/README.txt b/matlab_code/fvi_rev1_and_fpi_rev1/README.txt deleted file mode 100644 index e659426..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -Listing 6.5 is divided into four functions, U, f, maximum and bellman. -In order to run test_fvi_rev1, you need, in the same folder, m files for the four functions and lininterp_rev1. - -Listing 6.6 is divided into four functions, maximiser, T, get_greedy and get_value. -In order to run test_fpi_rev1, you need, in the same folder, m files for the four functions, U, f, maximum and lininterp_rev1. diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/T.m b/matlab_code/fvi_rev1_and_fpi_rev1/T.m deleted file mode 100644 index f47f6e3..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/T.m +++ /dev/null @@ -1,32 +0,0 @@ -% Filename: T.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: T(sigma,w) in Listing 6.6 - -function [t, t_func] = T(grid, W, sigma, w_func) - -global rho gridsize - -% Implements the operator L T_sigma - -% Inputs: A grid array, grid; -% A shock array, W; -% A policy array, sigma; -% An instance of lininterp (see -% the file lininterp_rev1.m), w_func (a function handle); -% -% Returns: An output array for LT_sigma(w_func), t; -% An instance of lininterp, t_func (a function handle). - -len = gridsize; -vals = zeros(1, len); - -for i = 1:len - x = grid(i); - Tw_y = U(x - sigma(i)) + rho * mean(w_func(f(sigma(i), W))); - vals(i) = Tw_y; -end -t = vals; -t_func = @(x) lininterp_rev1 (grid, vals, x); -end - diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/U.m b/matlab_code/fvi_rev1_and_fpi_rev1/U.m deleted file mode 100644 index 397dd32..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/U.m +++ /dev/null @@ -1,13 +0,0 @@ -% Filename: U.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: U(c) in Listing 6.5 and 6.6 - -function [ utility ] = U(c) - -global theta - -utility = 1 - exp(-theta.* c); % Utility function - -end - diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/bellman.m b/matlab_code/fvi_rev1_and_fpi_rev1/bellman.m deleted file mode 100644 index 486f3dd..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/bellman.m +++ /dev/null @@ -1,29 +0,0 @@ -% Filename: bellman.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: bellman(w) in Listing 6.5 - -function [b, b_func] = bellman (grid, w_func, W) - -% The approximate Bellman operator. -% Inputs : A grid array, grid; -% An instance of lininterp (see -% the file lininterp_rev1.m), w_func (a function handle); -% A shock array, W. -% -% Returns : An output array for T(w_func), b; -% An instance of lininterp, b_func (a function handle). - - -global rho gridsize - -len = gridsize; - - for i = 1:len - y = grid(i); - h = @(k) U(y - k) + rho * mean(w_func(f(k,W))); - vals(i) = maximum(h,0,y); - end - - b = vals; - b_func = @(x) lininterp_rev1 (grid, vals, x); diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/f.m b/matlab_code/fvi_rev1_and_fpi_rev1/f.m deleted file mode 100644 index c325977..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/f.m +++ /dev/null @@ -1,13 +0,0 @@ -% Filename: f.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: f(k,z) in Listing 6.5 and 6.6 - -function [ prod ] = f(k, z) - -global alpha - -prod = (k.^alpha) .* z; % Production function - -end - diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/get_greedy.m b/matlab_code/fvi_rev1_and_fpi_rev1/get_greedy.m deleted file mode 100644 index 49a65c5..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/get_greedy.m +++ /dev/null @@ -1,19 +0,0 @@ -% Filename: get_greedy.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: get_greedy(w) in Listing 6.6 - -function [new_w] = get_greedy(grid,w_func,W) - -global rho gridsize - % Computes a w-greedy policy, where w_func is an instance of lininterp_rev1 - -len = gridsize; -vals = zeros(1, len); - -for i = 1:len - x = grid(i); - h = @(k) U(x - k) + rho * mean(w_func(f(k, W))); - vals(i) = maximizer(h, 0, x); -end -new_w = vals; diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/get_value.m b/matlab_code/fvi_rev1_and_fpi_rev1/get_value.m deleted file mode 100644 index 5e91244..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/get_value.m +++ /dev/null @@ -1,32 +0,0 @@ -% Filename: get_value.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: get_value(sigma,v) in Listing 6.6 - -function [new_v, new_v_func] = get_value(grid, W, sigma, v_func) - -% Computes an approximation to v_sigma, the value of following -% policy sigma. Function v is a guess of v_sigma. - -% Inputs : A grid array, grid; -% A shock array, W; -% A policy array, sigma; -% An instance of lininterp (see -% the file lininterp_rev1.m), v_func (a function handle). -% -% Returns : An output array for v, new_v; -% An instance of lininterp, new_v_func (a function handle). - -global gridsize - -tol = 1e-2; % Error tolerance -err = 1; - -v = zeros(1,gridsize); - -while err > tol - [new_v, new_v_func] = T(grid, W, sigma, v_func); - err = max(abs(new_v - v)); - v_func = new_v_func; - v = new_v; -end diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/lininterp_rev1.m b/matlab_code/fvi_rev1_and_fpi_rev1/lininterp_rev1.m deleted file mode 100644 index e1dc413..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/lininterp_rev1.m +++ /dev/null @@ -1,17 +0,0 @@ -% Filename: lininterp_rev1.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: Listing 6.4 - -function g = lininterp_rev1(X,Y,z) - % Uses MATLAB's interp1 function for interpolation. - % The z values are truncated so that they lie inside - % the grid points. The effect is that evaluation of - % a point to the left of X(1) returns Y(1), while - % evaluation of a point to the right of X(end) returns - % Y(end) - - z = max(z, X(1)); - z = min(z, X(end)); - g = interp1(X, Y, z); -end diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/maximizer.m b/matlab_code/fvi_rev1_and_fpi_rev1/maximizer.m deleted file mode 100644 index 16a5f4e..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/maximizer.m +++ /dev/null @@ -1,14 +0,0 @@ -% Filename: maximizer.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: maximizer(h,a,b) in Listing 6.6 - -function [max] = maximizer(h, a, b) - -% Inputs : a function handle, h; constants, a and b -% Return : a maximizer for h in domain (a,b) - -max = fminbnd(@(k) -h(k), a, b); - -end - \ No newline at end of file diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/maximum.m b/matlab_code/fvi_rev1_and_fpi_rev1/maximum.m deleted file mode 100644 index 2b949ca..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/maximum.m +++ /dev/null @@ -1,11 +0,0 @@ -% Filename: maximum.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: maximum(h,a,b) in Listing 6.5 and 6.6 - -function [ m ] = maximum( h, a, b) - -m = h(fminbnd(@(k) -h(k),a,b)); - -end - diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/test_fpi_rev1.m b/matlab_code/fvi_rev1_and_fpi_rev1/test_fpi_rev1.m deleted file mode 100644 index 1940ed1..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/test_fpi_rev1.m +++ /dev/null @@ -1,40 +0,0 @@ -clear all -close all - -global theta alpha rho gridmax gridsize - - theta = 0.5; - alpha = 0.8; - rho = 0.9; - gridmax = 8; - gridsize = 150; - - -W = exp(randn(1,1000)); % Draws of shock - -grid = linspace(0,gridmax^(1e-1),gridsize).^10; % Grid for state space - -len = length(grid); - -vals = zeros(1,len); - -sigma = get_greedy(grid, @(x) lininterp_rev1(grid, U(grid),x), W); - -[new_v, new_v_func] = get_value(grid, W, sigma, @(x) lininterp_rev1(grid, U(grid), x)); - -v = zeros(1, len); -v_func = new_v_func; - -tol = 0.005; -err = 1; - -while err > tol - hold on; - plot(grid, sigma); - new_sigma = get_greedy(grid, v_func, W); - [new_v, new_v_func] = get_value(grid, W, new_sigma, v_func); - err = max(abs(new_v - v)); - v = new_v; - v_func = new_v_func; - sigma = new_sigma; -end diff --git a/matlab_code/fvi_rev1_and_fpi_rev1/test_fvi_rev1.m b/matlab_code/fvi_rev1_and_fpi_rev1/test_fvi_rev1.m deleted file mode 100644 index be9c146..0000000 --- a/matlab_code/fvi_rev1_and_fpi_rev1/test_fvi_rev1.m +++ /dev/null @@ -1,35 +0,0 @@ -% Filename: test_fvi_rev1.m -% Author: Tomohito Okabe -% Date: April 2013 - -clear all -close all - -global theta alpha rho gridmax gridsize - theta = 0.5; - alpha = 0.8; - rho = 0.9; - gridmax = 8; - gridsize = 150; - - -W = exp(randn(1,1000)); % Draws of shock - -grid = linspace(0,gridmax^(1e-1),gridsize).^10; % Grid for state space - -len = length(grid); - -vals = zeros(1,len); - -v_func = @(x) lininterp_rev1(grid, U(grid),x); % Initial input - - - for i = 1:10 - [w, w_func] = bellman(grid,v_func,W); - v_func = w_func; - hold on; - plot(grid, w); - end - -hold off; - \ No newline at end of file diff --git a/matlab_code/genfinitemc_rev1.m b/matlab_code/genfinitemc_rev1.m deleted file mode 100644 index 0ccc813..0000000 --- a/matlab_code/genfinitemc_rev1.m +++ /dev/null @@ -1,26 +0,0 @@ -% Filename: genfinitemc_rev1.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Listing 4.4 - -function [ path ] = genfinitemc_rev1(p, X, n, len) -% Inputs: -% p -> stochastic kernel; -% x -> the initial state; -% n -> # of periods; -% len -> # of states; -% -% Output: -% path -> series of the finite Markov chain - -% Set the initial state -path(1) = X; - -% Generate a sample path of length n, starting at the current state -for i = 1:n-1 - psi = p(path(i),:); - path(i+1) = randsample(len,1,true,psi); -end - -end - diff --git a/matlab_code/kurtzbellman_rev1/Gamma.m b/matlab_code/kurtzbellman_rev1/Gamma.m deleted file mode 100644 index 6ad9b86..0000000 --- a/matlab_code/kurtzbellman_rev1/Gamma.m +++ /dev/null @@ -1,32 +0,0 @@ -% Filename: Gamma.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Gamma(x) in Listing 5.1 - -function [ G ] = Gamma(S) - -% The correspondence of feasible actions = a (M+1) x (B+M+1) matrix -% Gamma = [Gamma(0), Gamma(1), ... Gamma(B+M)] -% where Gamma(s) (s in S) is a (M+1)-column vector s.t. -% Gamma(s) = [0,1,...,M]' if s >= M -% Gamma(s) = [0,1,...,m*,...,m*]' otherwise -% m* denotes the maximum value of the feasible action in the set. -% The latter equation is for a matter of convenience to generate a grid for -% consumption. - -global M - -e = ones(1,length(S)); -Gamma = [0:M]'*e; -for i = 1:M-1 -for j = 1:M+1 - if j > i - Gamma(j,i)= i-1; - end -end -end - -G = Gamma; - -end - diff --git a/matlab_code/kurtzbellman_rev1/README.txt b/matlab_code/kurtzbellman_rev1/README.txt deleted file mode 100644 index 478c556..0000000 --- a/matlab_code/kurtzbellman_rev1/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Listing 5.1 is divided into four functions, U, phi, Gamma and T. -T is the core function of the program. -In order to run the program, kurtzbellman_rev1_test.m you need, in the same folder, m files for those four functions. \ No newline at end of file diff --git a/matlab_code/kurtzbellman_rev1/T.m b/matlab_code/kurtzbellman_rev1/T.m deleted file mode 100644 index 04a91ab..0000000 --- a/matlab_code/kurtzbellman_rev1/T.m +++ /dev/null @@ -1,62 +0,0 @@ -% Filename: T.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: T(v) in Listing 5.1 - -function [ Tv ] = T(v) - -% Set parameters -global beta rho B M -beta = 0.5; -rho = 0.9; -B = 10; -M = 5; - -S = [0:B+M]; % State space = 0,...,B + M -Z = [0:B]; % Shock space = 0,...,B - -% Call phi.m to define the probability mass function, uniform distribution. -p = phi(Z); - -% Call Gamma.m to compute the correspondence of feasible action -G = Gamma(S); - -C = ones(1,1+M)'*S - G; % Generate a grid for consumption -C = max(0,C); % Replace negative values with zero -u = U(C); % Call U.m to compute utility - -% An implementation of the Bellman operator -% Parameter: v is a sequence representing a function on S. -% Returns: Tv -for i= 1:size(G,1) % Compute the value of the objective junc for each - for j = 1:length(S) % a in G, and stores the result in y - a = G(i,j); - v_new(i,j) = sum(v(a+1:a+length(Z)).*p); - end -end - -y = u + rho*v_new; - -for i=1:length(S) - Tv(i) = max(y(:,i)); -end - - -end - -% The following test shows how to use the class. Put the code in a -% separate file, in the same directory as kurtzbellman_rev1.m -% -% clear all -% close all -% -% w = zeros(1, 16); % 16 = B + M -% err = 1; -% while err > 0.001 -% v = kurtzbellman_rev1(w); -% err = max(abs(v - w)); -% w = v; -% hold on; -% plot(w); -% end -% hold off; \ No newline at end of file diff --git a/matlab_code/kurtzbellman_rev1/U.m b/matlab_code/kurtzbellman_rev1/U.m deleted file mode 100644 index fba0421..0000000 --- a/matlab_code/kurtzbellman_rev1/U.m +++ /dev/null @@ -1,14 +0,0 @@ -% Filename: U.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: U(c) in Listing 5.1 - -function [ utility ] = U( c ) -%UNTITLED4 Summary of this function goes here -% Detailed explanation goes here -global beta - -utility = c.^beta; - -end - diff --git a/matlab_code/kurtzbellman_rev1/kurtzbellman_rev1_test.m b/matlab_code/kurtzbellman_rev1/kurtzbellman_rev1_test.m deleted file mode 100644 index 8e83539..0000000 --- a/matlab_code/kurtzbellman_rev1/kurtzbellman_rev1_test.m +++ /dev/null @@ -1,13 +0,0 @@ - clear all - close all - - w = zeros(1, 16); - err = 1; - while err > 0.001 - v = T(w); - err = max(abs(v - w)); - w = v; - hold on; - plot(w); - end - hold off; diff --git a/matlab_code/kurtzbellman_rev1/phi.m b/matlab_code/kurtzbellman_rev1/phi.m deleted file mode 100644 index ef8a5e9..0000000 --- a/matlab_code/kurtzbellman_rev1/phi.m +++ /dev/null @@ -1,25 +0,0 @@ -% Filename: phi.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: phi(z) in Listing 5.1 - -function [ dist ] = phi(Z) - -global B - -% Generate Z1 replacing zero elements with one, -% and more-than-B elements with zero for the subsequent line. -for i = 1:length(Z) - if Z(i) == 0 - Z(i) =1; - elseif Z(i)> B - Z(i)=0; - end -end - -% Probability mass function, uniform distribution. -% length Z(Z>0)) counts the number of positive intergers in Z. -dist = unidpdf(Z,length(Z(Z>0))); - -end - diff --git a/matlab_code/kurtzsigma_rev1/README.txt b/matlab_code/kurtzsigma_rev1/README.txt deleted file mode 100644 index cc346b4..0000000 --- a/matlab_code/kurtzsigma_rev1/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Listing 5.2 corresponds to kurtzvsigma_rev1.m. -In order to run the m file, you also need, in the same folder, m files for the two functions, phi and U that are employed for kurtzbellman_rev1.m. -You can run a sample program, kurtzsigma_rev1_test.m in a folder that contains kurtzvsigma_rev1, phi and U. \ No newline at end of file diff --git a/matlab_code/kurtzsigma_rev1/U.m b/matlab_code/kurtzsigma_rev1/U.m deleted file mode 100644 index fba0421..0000000 --- a/matlab_code/kurtzsigma_rev1/U.m +++ /dev/null @@ -1,14 +0,0 @@ -% Filename: U.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: U(c) in Listing 5.1 - -function [ utility ] = U( c ) -%UNTITLED4 Summary of this function goes here -% Detailed explanation goes here -global beta - -utility = c.^beta; - -end - diff --git a/matlab_code/kurtzsigma_rev1/kurtzsigma_rev1_test.m b/matlab_code/kurtzsigma_rev1/kurtzsigma_rev1_test.m deleted file mode 100644 index e297c80..0000000 --- a/matlab_code/kurtzsigma_rev1/kurtzsigma_rev1_test.m +++ /dev/null @@ -1,5 +0,0 @@ - clear all - close all - - sigma = ones(1,16); - kurtzvsigma_rev1(sigma) \ No newline at end of file diff --git a/matlab_code/kurtzsigma_rev1/kurtzvsigma_rev1.m b/matlab_code/kurtzsigma_rev1/kurtzvsigma_rev1.m deleted file mode 100644 index 047f10c..0000000 --- a/matlab_code/kurtzsigma_rev1/kurtzvsigma_rev1.m +++ /dev/null @@ -1,61 +0,0 @@ -% Filename: kurtzvsigma_rev1.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Listing 5.2 - -% Note: See the comments below for an example of usage. - -function v_sigma = kurtzvsigma_rev1(sigma) - % Computes the value of following policy sigma. - - - % Copy of kurtzbellman_rev1 - global beta rho B M - beta = 0.5; - rho = 0.9; - B = 10; - M = 5; - S = [0:B+M]; % State space = 0,...,B + M - Z = [0:B]; % Shock space = 0,...,B - - - - % Set up the stochastic kernel p_sigma as a 2D array : - len = length(S); - p_sigma = zeros(len,len); - y = S; - for x = S - p_sigma(x+1,:) = phi(y - sigma(x+1)); - end - - % Create the right Markov operator M_sigma : - - % Set up the function r_sigma as an array : - % Initialize r_sigma into a column vector - - r_sigma = zeros(len,1); - for x = S - c = x-sigma(x+1); - if c <0 - c =0; - end - r_sigma(x+1) = U(c); - end - - v_sigma = zeros(len,1); - discount = 1; - for i=1:50 - v_sigma = v_sigma + discount * r_sigma; - M_sigma = p_sigma * r_sigma; - r_sigma = M_sigma; - discount = discount * rho; - end - - % Example of usage (put this in a separate file in the same - % directory as kutzvsigma_rev1.m / U.m / phi.m - % - % clear all - % close all - % - % sigma = ones(1,16); - % kurtzvsigma_rev1(sigma) \ No newline at end of file diff --git a/matlab_code/kurtzsigma_rev1/phi.m b/matlab_code/kurtzsigma_rev1/phi.m deleted file mode 100644 index ef8a5e9..0000000 --- a/matlab_code/kurtzsigma_rev1/phi.m +++ /dev/null @@ -1,25 +0,0 @@ -% Filename: phi.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: phi(z) in Listing 5.1 - -function [ dist ] = phi(Z) - -global B - -% Generate Z1 replacing zero elements with one, -% and more-than-B elements with zero for the subsequent line. -for i = 1:length(Z) - if Z(i) == 0 - Z(i) =1; - elseif Z(i)> B - Z(i)=0; - end -end - -% Probability mass function, uniform distribution. -% length Z(Z>0)) counts the number of positive intergers in Z. -dist = unidpdf(Z,length(Z(Z>0))); - -end - diff --git a/matlab_code/lininterp_rev1.m b/matlab_code/lininterp_rev1.m deleted file mode 100644 index e1dc413..0000000 --- a/matlab_code/lininterp_rev1.m +++ /dev/null @@ -1,17 +0,0 @@ -% Filename: lininterp_rev1.m -% Author: Tomohito Okabe -% Date: April 2013 -% Corresponds to: Listing 6.4 - -function g = lininterp_rev1(X,Y,z) - % Uses MATLAB's interp1 function for interpolation. - % The z values are truncated so that they lie inside - % the grid points. The effect is that evaluation of - % a point to the left of X(1) returns Y(1), while - % evaluation of a point to the right of X(end) returns - % Y(end) - - z = max(z, X(1)); - z = min(z, X(end)); - g = interp1(X, Y, z); -end diff --git a/matlab_code/p2srs_rev1.m b/matlab_code/p2srs_rev1.m deleted file mode 100644 index 44f4af0..0000000 --- a/matlab_code/p2srs_rev1.m +++ /dev/null @@ -1,30 +0,0 @@ -% Filename: p2srs_rev1.m -% Author: Tomohito Okabe -% Date: March 2013 -% Corresponds to: Listing 5.3 - - -function F = p2srs_rev1(x, z, p) - % Given a kernel p, takes p on S = {1,...,N}, - % and reurns F(x,z;p) which represents it as an SRS. - % Returns : A value (NOT a function!) of F - S = 1:length(p(1,:)); - a = 0; - for y = S - if a < z && z <= a + p(x,y) - F = y; - return; - end - a = a + p(x,y); - end -end - - -% Example of usage (put this in a separate file in the same directory). -% -% p = [0.971,0.029,0.000; -% 0.145,0.778,0.077; -% 0.000,0.508,0.492]; -% x = 2; -% z = 0.7; -% F = p2srs_rev1(x, z, p) % should return 2 diff --git a/matlab_code/polyclass0.m b/matlab_code/polyclass0.m deleted file mode 100644 index 5f0572c..0000000 --- a/matlab_code/polyclass0.m +++ /dev/null @@ -1,7 +0,0 @@ - -% Filename: polyclass0.m -% -% The MATLAB version of the Python code in polyclass0.py is omitted, since -% polyclass0.py is not real Python, but rather for illustration purposes -% only. Please see polyclass.m -% diff --git a/matlab_code/quadmap1.m b/matlab_code/quadmap1.m deleted file mode 100644 index d5f72d0..0000000 --- a/matlab_code/quadmap1.m +++ /dev/null @@ -1,12 +0,0 @@ -% Filename: quadmap1.m -% Author: Andy Qi -% Date: December 2008 -% Corresponds to: Listing 4.1 - -datapoints = zeros(1,200); -x = 0.11; -for t = 1:200 - datapoints(t) = x; - x = 4 * x * (1-x); -end -plot(datapoints) diff --git a/matlab_code/srs_testrs/README.txt b/matlab_code/srs_testrs/README.txt deleted file mode 100644 index 76b78c7..0000000 --- a/matlab_code/srs_testrs/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -Listing 6.1 is divided into two functions, update and sample_path. -In order to run testsrs_rev1, you need, in the same folder, m files for the two functions, update and sample_path. \ No newline at end of file diff --git a/matlab_code/srs_testrs/sample_path.m b/matlab_code/srs_testrs/sample_path.m deleted file mode 100644 index bf0438b..0000000 --- a/matlab_code/srs_testrs/sample_path.m +++ /dev/null @@ -1,22 +0,0 @@ -% Filename: sample_path.m -% Author: Tomohito Okabe -% Date: March 2013 -% Corresponds to: sample_path in Listing 6.1 - -function path = sample_path(F, phi, X, n) - - global alpha sigma s delta; - - n = n-1; % Ajust the last period of path such that - % length equals n. - - path(1) = X; - - % Generate path of length n from current state. - for t= 1:n - X_next = update(F, phi(), X); - path(t+1) = X_next; - X = X_next; - end - -end \ No newline at end of file diff --git a/matlab_code/srs_testrs/testsrs_rev1.m b/matlab_code/srs_testrs/testsrs_rev1.m deleted file mode 100644 index c0115b9..0000000 --- a/matlab_code/srs_testrs/testsrs_rev1.m +++ /dev/null @@ -1,32 +0,0 @@ -% Filename: testsrs_rev1.m -% Author: Tomohito Okabe -% Date: March 2013 -% Corresponds to: Listing 6.2 - -clear all -close all - -% Set parameters -global alpha sigma s delta -alpha = 0.5; -sigma = 0.2; -s = 0.5; -delta = 0.1; - -% Define F(k, z) = s * k ^ alpha * z + (1 - delta ) * k -F = @(k,z) s * (k^alpha) * z + (1 - delta) * k; -lognorm = @() lognrnd(0,sigma); -X = 1; -T = 500; - -P1 = sample_path(F, lognorm, X, T); % Generate path from X = 1 - -X = 60; % Reset the current state -P2 = sample_path(F, lognorm, X, T); % Generate path from X = 60 - - -% The below are commands to plot the paths as figure 6.1 -% figure(1) -% plot(P1); -% hold on; -% plot(P2); \ No newline at end of file diff --git a/matlab_code/srs_testrs/update.m b/matlab_code/srs_testrs/update.m deleted file mode 100644 index 00b83be..0000000 --- a/matlab_code/srs_testrs/update.m +++ /dev/null @@ -1,12 +0,0 @@ -% Filename: update.m -% Author: Tomohito Okabe -% Date: March 2013 -% Corresponds to: update in Listing 6.1 - -function X_next = update(F, phi, X) - - global alpha sigma s delta ; - - X_next = F(X,phi); - -end diff --git a/matlab_code/testds_rev1.m b/matlab_code/testds_rev1.m deleted file mode 100644 index 60049b6..0000000 --- a/matlab_code/testds_rev1.m +++ /dev/null @@ -1,25 +0,0 @@ -% Filename: testds_rev1.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Listing 4.3 - -% Clear all existing variables -clear all -% Close all figures -close all - -% Define function h as a function handle -quadmap = @(x)4 * x * (1 - x); - -% Set the initial state for T1 / the length of the trajectory -x1 = 0.1; -N = 100; - -% T1 holds tragectory from x1 -T1 = ds_rev1(quadmap,x1,N); - -% Set the initial state for T2 -x2 = 0.2; - -% T2 holds tragectory from x2 -T2 = ds_rev1(quadmap,x2,N); diff --git a/matlab_code/testgenfinitemc_rev1.m b/matlab_code/testgenfinitemc_rev1.m deleted file mode 100644 index 22103e0..0000000 --- a/matlab_code/testgenfinitemc_rev1.m +++ /dev/null @@ -1,27 +0,0 @@ -% Filename: testgenfinitemc_rev1.m -% Author: Tomohito Okabe -% Date: February 2013 -% Corresponds to: Listing 4.5 - -clear all -close all - -N = 1000; % Number of periods - -pH = [0.971,0.029,0.000; % Stochastic kernel - 0.145,0.778,0.077; - 0.000,0.508,0.492]; - -psi = [0.3,0.4,0.3]; % Initial condition -len = length(psi); -rs = randsample(len,1,true,psi); % Use randsample fuction returns i with - % probability phi[i], where phi serves - % as a vector of weights. -T1 = genfinitemc_rev1(pH,rs,N,len); % T1 holds Markov-(pH,psi) - - -psi2 = [0.8,0.1,0.1]; % Alternative initial cond. -len2 = length(psi2); -rs2 = randsample(len,1,true,psi2); -T2 = genfinitemc_rev1(pH,rs2,N,len2); % T2 holds Markov-(pH,psi2) - diff --git a/python_code/ar1.py b/python_code/ar1.py deleted file mode 100644 index 4d7324f..0000000 --- a/python_code/ar1.py +++ /dev/null @@ -1,15 +0,0 @@ -# Filename: ar1.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 8.1 - -from random import normalvariate as N - -a, b = 0.5, 1 # Parameters -X = {} # An empty dictionary to store path -X[0] = N(0, 1) # X_0 has distribution N(0, 1) - -for t in range(100): - X[t+1] = N(a * X[t] + b, 1) - - diff --git a/python_code/cpdynam.py b/python_code/cpdynam.py deleted file mode 100644 index 2cdfd70..0000000 --- a/python_code/cpdynam.py +++ /dev/null @@ -1,32 +0,0 @@ -# Filename: cpdynam.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 6.7 - -from scipy import mean -from scipy.stats import beta -from scipy.optimize import brentq - -alpha, a, c = 0.8, 5.0, 2.0 -W = beta(5, 5).rvs(1000) * c + a # Shock observations -D = P = lambda x: 1.0 / x - -def fix_point(h, lower, upper): - """Computes the fixed point of h on [upper, lower] - using SciPy's brentq routine, which finds the - zeros (roots) of a univariate function. - Parameters: h is a function and lower and upper are - numbers (floats or integers). """ - return brentq(lambda x: x - h(x), lower, upper) - -def T(p, x): - """Computes Tp(x), where T is the pricing functional - operator. - Parameters: p is a vectorized function (i.e., acts - pointwise on arrays) and x is a number. """ - y = alpha * mean(p(W)) - if y <= P(x): - return P(x) - h = lambda r: alpha * mean(p(alpha*(x - D(r)) + W)) - return fix_point(h, P(x), y) - diff --git a/python_code/ds.py b/python_code/ds.py deleted file mode 100644 index 07c89e6..0000000 --- a/python_code/ds.py +++ /dev/null @@ -1,24 +0,0 @@ -# Filename: ds.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.2 - -class DS: - - def __init__(self, h=None, x=None): - """Parameters: h is a function and x is a number - in S representing the current state.""" - self.h, self.x = h, x - - def update(self): - "Update the state of the system by applying h." - self.x = self.h(self.x) - - def trajectory(self, n): - """Generate a trajectory of length n, starting - at the current state.""" - traj = [] - for i in range(n): - traj.append(self.x) - self.update() - return traj diff --git a/python_code/ecdf.py b/python_code/ecdf.py deleted file mode 100644 index 224f731..0000000 --- a/python_code/ecdf.py +++ /dev/null @@ -1,17 +0,0 @@ -# Filename: ecdf.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 6.3 - -class ECDF: - - def __init__(self, observations): - self.observations = observations - - def __call__(self, x): - counter = 0.0 - for obs in self.observations: - if obs <= x: - counter += 1 - return counter / len(self.observations) - diff --git a/python_code/fig1point2.py b/python_code/fig1point2.py deleted file mode 100644 index fe5e869..0000000 --- a/python_code/fig1point2.py +++ /dev/null @@ -1,43 +0,0 @@ -from pyx import * -from pyx.deco import earrow -text.set(mode="latex") -text.preamble(r"\usepackage{times}") -from scipy import log, sqrt, pi, linspace, exp - -theta = 0.3 -alpha = 0.4 -c = (log(theta)) * alpha -muinit = -2 -varinit = 0.8 -numden = 7 - -updatemu = lambda m: c + alpha * m -updatevar = lambda v: alpha**2 * v + 1 - -def phi(z, mu, var): - return exp(- (z - mu)**2 / (2.0 * var)) / sqrt(2 * pi * var) - -gridmin, gridmax = -3.2, 1 -g = graph.graphxy(width=10, x=graph.axis.lin(min=gridmin, max=gridmax)) - -x_grid = linspace(gridmin, gridmax, 100) -mu, var = muinit, varinit -i = 1 -gr = 0.7 -step = gr / numden -while i <= numden: - plotpairs = [(x, phi(x, mu, var)) for x in x_grid] - g.plot(graph.data.points(plotpairs, - x=1, y=2), - [graph.style.line([color.gray(gr)])]) - mu, var = updatemu(mu), updatevar(var) - gr -= step - i += 1 - -x1, y1 = g.pos(-1.3, 0.1) -x2, y2 = g.pos(-0.8, 0.17) -g.stroke(path.line(x1, y1, x2, y2), [earrow.normal]) -g.text(x1-0.1, y1-0.3, r"$x_t \sim N(-2, 0.8)$", [text.halign.right]) - -g.writePDFfile("normaldensities") - diff --git a/python_code/fig1point4.py b/python_code/fig1point4.py deleted file mode 100644 index 03da480..0000000 --- a/python_code/fig1point4.py +++ /dev/null @@ -1,26 +0,0 @@ - -import numpy as np -import matplotlib.pyplot as plt - -theta, alpha = 0.3, 0.4 -C = np.log(theta) * alpha -N = 250 -initx = 4 -meanx = C / (1 - alpha) - -def update(x): - return C + alpha * x + np.random.randn(1) - -path = np.zeros(N) -path[0] = initx -for t in range(N-1): - path[t+1] = update(path[t]) - -plt.plot(path, 'k') -plt.axhline(meanx, xmin=0, xmax=N, color='k') -plt.xlabel("time") -plt.ylabel("log income") -plt.show() - - - diff --git a/python_code/fig1point5.py b/python_code/fig1point5.py deleted file mode 100644 index da79ce1..0000000 --- a/python_code/fig1point5.py +++ /dev/null @@ -1,30 +0,0 @@ - -import numpy as np -import matplotlib.pyplot as plt - -theta, alpha = 0.3, 0.4 -C = np.log(theta) * alpha -N = 250 -initx = 4 -meanx = C / (1 - alpha) - -def update(x): - return C + alpha * x + np.random.randn(1) - -path = np.zeros(N) -path[0] = initx -for t in range(N-1): - path[t+1] = update(path[t]) - -mean_path = np.zeros(N) -for t in range(N): - mean_path[t] = path[:t+1].mean() - -plt.plot(mean_path, 'k') -plt.axhline(meanx, xmin=0, xmax=N, color='k') -plt.xlabel("time") -plt.ylabel("sample mean") -plt.show() - - - diff --git a/python_code/fig4point1.py b/python_code/fig4point1.py deleted file mode 100644 index cf7d55c..0000000 --- a/python_code/fig4point1.py +++ /dev/null @@ -1,34 +0,0 @@ - -import numpy as np -from pyx import * - -xdim = (-10.0, 10.0) -ydim = (-5.0, 5.0) - -A1 = np.asarray([[0.58, -0.6], - [0.65, 0.3]]) - -def f(x): return np.dot(A1, x) - -# Create a PyX canvas -c = canvas.canvas() - -# Set up the axis -c.stroke(path.line(-10, 0, 10, 0), [style.linestyle.dashed]) -c.stroke(path.line(0, -5, 0, 5), [style.linestyle.dashed]) - -# A function to plot arrows -def plotarrow(x): - fx = f(x) - c.stroke(path.line(x[0], x[1], fx[0], fx[1]), - [style.linewidth.Thick, deco.earrow(size=0.5)]) - -# A 2 by 2 grid for start points of arrows -xpoints = np.linspace(xdim[0], xdim[1], 14) -ypoints = np.linspace(ydim[0], ydim[1], 8) - -for x in xpoints: - for y in ypoints: - plotarrow((x, y)) - -c.writePDFfile("sdsdiagram") diff --git a/python_code/fig4point3.py b/python_code/fig4point3.py deleted file mode 100644 index 6a3a863..0000000 --- a/python_code/fig4point3.py +++ /dev/null @@ -1,50 +0,0 @@ - -import numpy as np -from pyx import * - -alpha, s, a = 0.5, 0.25, 7 -kstar = ((s*a)**(1/(1 - alpha))) - -def h(k): - return s * a * (k**alpha) - -def plotcurve(X, Y, canv): - for i in range(len(X)-1): - canv.stroke(path.line(X[i], Y[i], X[i+1], Y[i+1]), [style.linewidth.Thick]) - -c = canvas.canvas() -upper = 6 -# axes -c.stroke(path.line(0, 0, upper, 0), [deco.earrow(size=0.3)]) -c.stroke(path.line(0, 0, 0, upper), [deco.earrow(size=0.3)]) -# 45 degrees -c.stroke(path.line(0, 0, upper, upper), [style.linestyle.dashed]) -# function curve -X = np.linspace(0, upper, 100) -Y = [h(x) for x in X] -plotcurve(X, Y, c) -# arrows -k = 0.5 -for i in range(4): - c.stroke(path.line(k, k, k, h(k)), - [deco.earrow(size=0.15), style.linewidth.Thin]) - c.stroke(path.line(k, h(k), h(k), h(k)), - [deco.earrow(size=0.15), style.linewidth.Thin]) - c.stroke(path.line(k, 0, h(k), 0), - [deco.earrow(size=0.15), style.linewidth.THIN]) - k = h(k) -k = 5.8 -for i in range(3): - c.stroke(path.line(k, k, k, h(k)), - [deco.earrow(size=0.15), style.linewidth.Thin]) - c.stroke(path.line(k, h(k), h(k), h(k)), - [deco.earrow(size=0.15), style.linewidth.Thin]) - c.stroke(path.line(k, 0, h(k), 0), - [deco.earrow(size=0.15), style.linewidth.THIN]) - k = h(k) - -c.stroke(path.line(kstar, 0, kstar, kstar), [style.linestyle.dotted]) -c.text(kstar, -0.5, r"k^*", [text.mathmode, text.size.normal]) -c.text(upper*1.06, -0.5, r"k_t", [text.mathmode, text.size.normal]) -c.text(-0.5, upper*1.06, r"k_{t+1}", [text.mathmode, text.size.normal]) -c.writePDFfile("stable45deg") diff --git a/python_code/fig4point6.py b/python_code/fig4point6.py deleted file mode 100644 index a210420..0000000 --- a/python_code/fig4point6.py +++ /dev/null @@ -1,20 +0,0 @@ - -import numpy as np -import matplotlib.pyplot as plt - -xgrid = np.linspace(0, 1, 100) - -h = lambda x, r: r * x * (1 - x) - -plt.plot(xgrid, xgrid, '-', color='grey') - -r = 0 -step = 0.3 - -while r <= 4: - y = [h(x, r) for x in xgrid] - plt.plot(xgrid, y, 'k-') - r = r + step - -plt.show() - diff --git a/python_code/fig4point7.py b/python_code/fig4point7.py deleted file mode 100644 index 98e2ccd..0000000 --- a/python_code/fig4point7.py +++ /dev/null @@ -1,16 +0,0 @@ -from ds import DS -from matplotlib.pyplot import plot, show, xlabel - -q = DS(h=None, x=0.1) - -r = 2.5 -while r < 4: - q.h = lambda x: r * x * (1 - x) - t = q.trajectory(1000)[950:] - plot([r] * len(t), t, 'k.', ms=0.4) - r = r + 0.005 - -xlabel(r'$r$', fontsize=16) -show() - - diff --git a/python_code/fig6point13.py b/python_code/fig6point13.py deleted file mode 100644 index 6a3bca2..0000000 --- a/python_code/fig6point13.py +++ /dev/null @@ -1,24 +0,0 @@ - -import numpy as np -from fvi import LinInterp -from cpdynam import * # Listing 6.7 in the text -import matplotlib.pyplot as plt - -gridsize = 150 -grid = np.linspace(a, 35, gridsize) - -vals = P(grid) -for i in range(20): - if i == 0: - plt.plot(grid, vals, 'k-', label=r'$P$') - if i == 1: - plt.plot(grid, vals, 'k--', label=r'$TP$') - if i == 19: - plt.plot(grid, vals, 'k-.', label=r'$T^{50}P$') - p = LinInterp(grid, vals) - new_vals = [T(p, x) for x in grid] - vals = new_vals - -plt.legend(axespad=0.1) -plt.show() - diff --git a/python_code/fig6point4.py b/python_code/fig6point4.py deleted file mode 100644 index 11a00a0..0000000 --- a/python_code/fig6point4.py +++ /dev/null @@ -1,30 +0,0 @@ - -import matplotlib.pyplot as plt -from math import exp, sqrt -from random import normalvariate - -alpha = 0.5 -s = 0.25 -a1 = 15 -a2 = 25 -ss = sqrt(0.02) -kb = 24.1 - -def update(k): - a = a1 if k < kb else a2 - return s * a * (k**alpha) * exp(normalvariate(0, ss)) - -def ts(init, T): - k = init - path = [k] - for t in range(T): - k = update(k) - path.append(k) - return path - -L = 500 -plt.plot(ts(1, L), 'k-') -plt.plot(ts(60, L), '-', color='grey') -plt.xlabel('time') -plt.ylabel('investment') -plt.show() diff --git a/python_code/fphamilton.py b/python_code/fphamilton.py deleted file mode 100644 index 62528b0..0000000 --- a/python_code/fphamilton.py +++ /dev/null @@ -1,16 +0,0 @@ -# Filename: fphamilton.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.6 - -from numpy import ones, identity, transpose -from numpy.linalg import solve - -pH = ((0.971, 0.029, 0.000), # Hamilton's kernel - (0.145, 0.778, 0.077), - (0.000, 0.508, 0.492)) - -I = identity(3) # 3 by 3 identity matrix -Q, b = ones((3, 3)), ones((3, 1)) # Matrix and vector of ones -A = transpose(I - pH + Q) -print(solve(A, b)) diff --git a/python_code/fpi.py b/python_code/fpi.py deleted file mode 100644 index 9f7781d..0000000 --- a/python_code/fpi.py +++ /dev/null @@ -1,38 +0,0 @@ -# Filename: fpi.py -# Author: John Stachurski -# Date: August 2009 -# Corresponds to: Listing 6.6 - -from fvi import * # Import all definitions from listing 6.5 -from scipy import absolute as abs - -def maximizer(h, a, b): - return float(fminbound(lambda x: -h(x), a, b)) - -def T(sigma, w): - "Implements the operator L T_sigma." - vals = [] - for y in grid: - Tw_y = U(y - sigma(y)) + rho * mean(w(f(sigma(y), W))) - vals.append(Tw_y) - return LinInterp(grid, vals) - -def get_greedy(w): - "Computes a w-greedy policy." - vals = [] - for y in grid: - h = lambda k: U(y - k) + rho * mean(w(f(k, W))) - vals.append(maximizer(h, 0, y)) - return LinInterp(grid, vals) - -def get_value(sigma, v): - """Computes an approximation to v_sigma, the value - of following policy sigma. Function v is a guess. - """ - tol = 1e-2 # Error tolerance - while 1: - new_v = T(sigma, v) - err = max(abs(new_v(grid) - v(grid))) - if err < tol: - return new_v - v = new_v diff --git a/python_code/fvi.py b/python_code/fvi.py deleted file mode 100644 index 266e752..0000000 --- a/python_code/fvi.py +++ /dev/null @@ -1,33 +0,0 @@ -# Filename: fvi.py -# Author: John Stachurski -# Date: August 2009 -# Corresponds to: Listing 6.5 - -from scipy import linspace, mean, exp, randn -from scipy.optimize import fminbound -from lininterp import LinInterp # From listing 6.4 - -theta, alpha, rho = 0.5, 0.8, 0.9 # Parameters -def U(c): return 1 - exp(- theta * c) # Utility -def f(k, z): return (k**alpha) * z # Production -W = exp(randn(1000)) # Draws of shock - -gridmax, gridsize = 8, 150 -grid = linspace(0, gridmax**1e-1, gridsize)**10 - -def maximum(h, a, b): - return float(h(fminbound(lambda x: -h(x), a, b))) - -def bellman(w): - """The approximate Bellman operator. - Parameters: w is a vectorized function (i.e., a - callable object which acts pointwise on arrays). - Returns: An instance of LinInterp. - """ - vals = [] - for y in grid: - h = lambda k: U(y - k) + rho * mean(w(f(k,W))) - vals.append(maximum(h, 0, y)) - return LinInterp(grid, vals) - - diff --git a/python_code/genfinitemc.py b/python_code/genfinitemc.py deleted file mode 100644 index 134f209..0000000 --- a/python_code/genfinitemc.py +++ /dev/null @@ -1,43 +0,0 @@ -# Filename: genfinitemc.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.4 - -from random import uniform - -def sample(phi): - """Returns i with probability phi[i], where phi is an - array (e.g., list or tuple).""" - a = 0.0 - U = uniform(0,1) - for i in range(len(phi)): - if a < U <= a + phi[i]: - return i - a = a + phi[i] - - -class MC: - """For generating sample paths of finite Markov chains - on state space S = {0,...,N-1}.""" - - def __init__(self, p=None, X=None): - """Create an instance with stochastic kernel p and - current state X. Here p[x] is an array of length N - for each x, and represents p(x,dy). - The parameter X is an integer in S.""" - self.p, self.X = p, X - - def update(self): - "Update the state by drawing from p(X,dy)." - self.X = sample(self.p[self.X]) - - def sample_path(self, n): - """Generate a sample path of length n, starting from - the current state.""" - path = [] - for i in range(n): - path.append(self.X) - self.update() - return path - - diff --git a/python_code/kurtzbellman.py b/python_code/kurtzbellman.py deleted file mode 100644 index 178b2b1..0000000 --- a/python_code/kurtzbellman.py +++ /dev/null @@ -1,37 +0,0 @@ -# Filename: kurtzbellman.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 5.1 - -beta, rho, B, M = 0.5, 0.9, 10, 5 -S = range(B + M + 1) # State space = 0,...,B + M -Z = range(B + 1) # Shock space = 0,...,B - -def U(c): - "Utility function." - return c**beta - -def phi(z): - "Probability mass function, uniform distribution." - return 1.0 / len(Z) if 0 <= z <= B else 0 - -def Gamma(x): - "The correspondence of feasible actions." - return range(min(x, M) + 1) - -def T(v): - """An implementation of the Bellman operator. - Parameters: v is a sequence representing a function on S. - Returns: Tv, a list.""" - Tv = [] - for x in S: - # Compute the value of the objective function for each - # a in Gamma(x), and store the result in vals - vals = [] - for a in Gamma(x): - y = U(x - a) + rho * sum(v[a + z]*phi(z) for z in Z) - vals.append(y) - # Store the maximum reward for this x in the list Tv - Tv.append(max(vals)) - return Tv - diff --git a/python_code/kurtzvsigma.py b/python_code/kurtzvsigma.py deleted file mode 100644 index 0a12f30..0000000 --- a/python_code/kurtzvsigma.py +++ /dev/null @@ -1,38 +0,0 @@ -# Filename: kurtzvsigma.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 5.2 - -from numpy import zeros, dot, array -from kurtzbellman import S, rho, phi, U # From listing 5.1 - -def value_of_policy(sigma): - "Computes the value of following policy sigma." - - # Set up the stochastic kernel p_sigma as a 2D array: - N = len(S) - p_sigma = zeros((N, N)) - for x in S: - for y in S: - p_sigma[x, y] = phi(y - sigma[x]) - - # Create the right Markov operator M_sigma: - M_sigma = lambda h: dot(p_sigma, h) - - # Set up the function r_sigma as an array: - r_sigma = array([U(x - sigma[x]) for x in S]) - # Reshape r_sigma into a column vector: - r_sigma = r_sigma.reshape((N, 1)) - - # Initialize v_sigma to zero: - v_sigma = zeros((N,1)) - # Initialize the discount factor to 1: - discount = 1 - - for i in range(50): - v_sigma = v_sigma + discount * r_sigma - r_sigma = M_sigma(r_sigma) - discount = discount * rho - - return v_sigma - diff --git a/python_code/lininterp.py b/python_code/lininterp.py deleted file mode 100644 index 47eb839..0000000 --- a/python_code/lininterp.py +++ /dev/null @@ -1,23 +0,0 @@ -# Filename: lininterp.py -# Author: John Stachurski -# Date: August 2009 -# Corresponds to: Listing 6.4 - -from scipy import interp - -class LinInterp: - "Provides linear interpolation in one dimension." - - def __init__(self, X, Y): - """Parameters: X and Y are sequences or arrays - containing the (x,y) interpolation points. - """ - self.X, self.Y = X, Y - - def __call__(self, z): - """Parameters: z is a number, sequence or array. - This method makes an instance f of LinInterp callable, - so f(z) returns the interpolation value(s) at z. - """ - return interp(z, self.X, self.Y) - diff --git a/python_code/p2srs.py b/python_code/p2srs.py deleted file mode 100644 index 566e6df..0000000 --- a/python_code/p2srs.py +++ /dev/null @@ -1,21 +0,0 @@ -# Filename: p2srs.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 5.3 - -def createF(p): - """Takes a kernel p on S = {0,...,N-1} and returns a - function F(x,z) which represents it as an SRS. - Parameters: p is a sequence of sequences, so that p[x][y] - represents p(x,y) for x,y in S. - Returns: A function F with arguments (x,z).""" - S = range(len(p[0])) - def F(x,z): - a = 0 - for y in S: - if a < z <= a + p[x][y]: - return y - a = a + p[x][y] - return F - - diff --git a/python_code/polyclass.py b/python_code/polyclass.py deleted file mode 100644 index 412d15f..0000000 --- a/python_code/polyclass.py +++ /dev/null @@ -1,22 +0,0 @@ -# Filename: polyclass.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 2.2 - -class Polynomial: - - def __init__(self, coef): - """Creates an instance p of the Polynomial class, - where p(x) = coef[0] x^0 + ... + coef[N] x^N.""" - self.coef = coef - - def evaluate(self, x): - y = sum(a*x**i for i, a in enumerate(self.coef)) - return y - - def differentiate(self): - new_coef = [i*a for i, a in enumerate(self.coef)] - # Remove the first element, which is zero - del new_coef[0] - # And reset coefficients data to new values - self.coef = new_coef diff --git a/python_code/polyclass0.py b/python_code/polyclass0.py deleted file mode 100644 index c501a2d..0000000 --- a/python_code/polyclass0.py +++ /dev/null @@ -1,22 +0,0 @@ -# Filename: polyclass0.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing xxxx - -class Polynomial: - - def initialize(coef): - """Creates an instance p of the Polynomial class, - where p(x) = coef[0] x^0 + ... + coef[N] x^N.""" - - def evaluate(x): - y = sum(a*x**i for i, a in enumerate(coef)) - return y - - def differentiate(): - new_coef = [i*a for i, a in enumerate(coef)] - # Remove the first element, which is zero - del new_coef[0] - # And reset coefficients data to new values - coef = new_coef - diff --git a/python_code/quadmap1.py b/python_code/quadmap1.py deleted file mode 100644 index 658e35f..0000000 --- a/python_code/quadmap1.py +++ /dev/null @@ -1,15 +0,0 @@ -# Filename: quadmap1.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.1 - -from pylab import plot, show # Requires Matplotlib - -datapoints = [] # Stores trajectory -x = 0.11 # Initial condition -for t in range(200): - datapoints.append(x) - x = 4 * x * (1 - x) - -plot(datapoints) -show() diff --git a/python_code/srs.py b/python_code/srs.py deleted file mode 100644 index c11f809..0000000 --- a/python_code/srs.py +++ /dev/null @@ -1,25 +0,0 @@ -# Filename: srs.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 6.1 - -class SRS: - - def __init__(self, F=None, phi=None, X=None): - """Represents X_{t+1} = F(X_t, W_{t+1}); W ~ phi. - Parameters: F and phi are functions, where phi() - returns a draw from phi. X is a number representing - the initial condition.""" - self.F, self.phi, self.X = F, phi, X - - def update(self): - "Update the state according to X = F(X, W)." - self.X = self.F(self.X, self.phi()) - - def sample_path(self, n): - "Generate path of length n from current state." - path = [] - for i in range(n): - path.append(self.X) - self.update() - return path diff --git a/python_code/testds.py b/python_code/testds.py deleted file mode 100644 index 48581c4..0000000 --- a/python_code/testds.py +++ /dev/null @@ -1,16 +0,0 @@ -# Filename: testds.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.3 - -from ds import DS # Import from listing 4.2 - -def quadmap(x): - return 4 * x * (1 - x) - -q = DS(h=quadmap, x=0.1) # Create an instance q of DS -T1 = q.trajectory(100) # T1 holds trajectory from 0.1 - -q.x = 0.2 # Reset current state to 0.2 -T2 = q.trajectory(100) # T2 holds trajectory from 0.2 - diff --git a/python_code/testfpi.py b/python_code/testfpi.py deleted file mode 100644 index 475d3dd..0000000 --- a/python_code/testfpi.py +++ /dev/null @@ -1,20 +0,0 @@ - -from fpi import * -import matplotlib.pyplot as plt -import numpy as np - -tol = 0.005 -sigma = get_greedy(U) -v = get_value(sigma, U) - -while 1: - plt.plot(grid, sigma(grid)) - sigma_new = get_greedy(v) - v_new = get_value(sigma_new, v) - if np.max(abs(v(grid) - v_new(grid))) < tol: - break - else: - sigma = sigma_new - v = v_new - -plt.show() diff --git a/python_code/testgenfinitemc.py b/python_code/testgenfinitemc.py deleted file mode 100644 index 25592ca..0000000 --- a/python_code/testgenfinitemc.py +++ /dev/null @@ -1,19 +0,0 @@ -# Filename: testgenfinitemc.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 4.5 - -from genfinitemc import sample, MC # Import from listing 4.4 - -pH = ((0.971, 0.029, 0.000), - (0.145, 0.778, 0.077), - (0.000, 0.508, 0.492)) - -psi = (0.3, 0.4, 0.3) # Initial condition -h = MC(p=pH, X=sample(psi)) # Create an instance of class MC -T1 = h.sample_path(1000) # Series is Markov-(p, psi) - -psi2 = (0.8, 0.1, 0.1) # Alternative initial condition -h.X = sample(psi2) # Reset the current state -T2 = h.sample_path(1000) # Series is Markov-(p, psi2) - diff --git a/python_code/testsrs.py b/python_code/testsrs.py deleted file mode 100644 index ac9d88f..0000000 --- a/python_code/testsrs.py +++ /dev/null @@ -1,18 +0,0 @@ -# Filename: testsrs.py -# Author: John Stachurski -# Date: December 2008 -# Corresponds to: Listing 6.2 - -from srs import SRS # Import from listing 6.1 -from random import lognormvariate - -alpha, sigma, s, delta = 0.5, 0.2, 0.5, 0.1 -# Define F(k, z) = s k^alpha z + (1 - delta) k -F = lambda k, z: s * (k**alpha) * z + (1 - delta) * k -lognorm = lambda: lognormvariate(0, sigma) - -solow_srs = SRS(F=F, phi=lognorm, X=1.0) -P1 = solow_srs.sample_path(500) # Generate path from X = 1 -solow_srs.X = 60 # Reset the current state -P2 = solow_srs.sample_path(500) # Generate path from X = 60 -