{ "cells": [ { "cell_type": "markdown", "id": "6fc9f5f5", "metadata": {}, "source": [ "# The Income Fluctuation Problem I: Basic Model" ] }, { "cell_type": "markdown", "id": "ebcf0982", "metadata": {}, "source": [ "## Contents\n", "\n", "- [The Income Fluctuation Problem I: Basic Model](#The-Income-Fluctuation-Problem-I:-Basic-Model) \n", " - [Overview](#Overview) \n", " - [The Optimal Savings Problem](#The-Optimal-Savings-Problem) \n", " - [Computation](#Computation) \n", " - [Implementation](#Implementation) \n", " - [Exercises](#Exercises) " ] }, { "cell_type": "markdown", "id": "babed37b", "metadata": {}, "source": [ "In addition to what’s in Anaconda, this lecture will need the following libraries:" ] }, { "cell_type": "code", "execution_count": null, "id": "1f136fe8", "metadata": { "hide-output": false }, "outputs": [], "source": [ "!pip install quantecon" ] }, { "cell_type": "markdown", "id": "f609431f", "metadata": {}, "source": [ "## Overview\n", "\n", "In this lecture, we study an optimal savings problem for an infinitely lived consumer—the “common ancestor” described in [[Ljungqvist and Sargent, 2018](https://python.quantecon.org/zreferences.html#id185)], section 1.3.\n", "\n", "This is an essential sub-problem for many representative macroeconomic models\n", "\n", "- [[Aiyagari, 1994](https://python.quantecon.org/zreferences.html#id140)] \n", "- [[Huggett, 1993](https://python.quantecon.org/zreferences.html#id175)] \n", "- etc. \n", "\n", "\n", "It is related to the decision problem in the [stochastic optimal growth model](https://python.quantecon.org/optgrowth.html) and yet differs in important ways.\n", "\n", "For example, the choice problem for the agent includes an additive income term that leads to an occasionally binding constraint.\n", "\n", "Moreover, in this and the following lectures, we will inject more realistic\n", "features such as correlated shocks.\n", "\n", "To solve the model we will use Euler equation based time iteration, which proved\n", "to be [fast and accurate](https://python.quantecon.org/coleman_policy_iter.html) in our investigation of\n", "the [stochastic optimal growth model](https://python.quantecon.org/optgrowth.html).\n", "\n", "Time iteration is globally convergent under mild assumptions, even when utility is unbounded (both above and below).\n", "\n", "We’ll need the following imports:" ] }, { "cell_type": "code", "execution_count": null, "id": "1897d1dd", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from quantecon.optimize import brentq\n", "from numba import njit, float64\n", "from numba.experimental import jitclass\n", "from quantecon import MarkovChain" ] }, { "cell_type": "markdown", "id": "50b65895", "metadata": {}, "source": [ "### References\n", "\n", "Our presentation is a simplified version of [[Ma *et al.*, 2020](https://python.quantecon.org/zreferences.html#id255)].\n", "\n", "Other references include [[Deaton, 1991](https://python.quantecon.org/zreferences.html#id156)], [[Den Haan, 2010](https://python.quantecon.org/zreferences.html#id158)],\n", "[[Kuhn, 2013](https://python.quantecon.org/zreferences.html#id181)], [[Rabault, 2002](https://python.quantecon.org/zreferences.html#id205)], [[Reiter, 2009](https://python.quantecon.org/zreferences.html#id207)] and\n", "[[Schechtman and Escudero, 1977](https://python.quantecon.org/zreferences.html#id210)]." ] }, { "cell_type": "markdown", "id": "466b6ddb", "metadata": {}, "source": [ "## The Optimal Savings Problem\n", "\n", "\n", "\n", "Let’s write down the model and then discuss how to solve it." ] }, { "cell_type": "markdown", "id": "e88f4b2e", "metadata": {}, "source": [ "### Set-Up\n", "\n", "Consider a household that chooses a state-contingent consumption plan $ \\{c_t\\}_{t \\geq 0} $ to maximize\n", "\n", "$$\n", "\\mathbb{E} \\, \\sum_{t=0}^{\\infty} \\beta^t u(c_t)\n", "$$\n", "\n", "subject to\n", "\n", "\n", "\n", "$$\n", "a_{t+1} \\leq R(a_t - c_t) + Y_{t+1},\n", "\\quad c_t \\geq 0,\n", "\\quad a_t \\geq 0\n", "\\quad t = 0, 1, \\ldots \\tag{42.1}\n", "$$\n", "\n", "Here\n", "\n", "- $ \\beta \\in (0,1) $ is the discount factor \n", "- $ a_t $ is asset holdings at time $ t $, with borrowing constraint $ a_t \\geq 0 $ \n", "- $ c_t $ is consumption \n", "- $ Y_t $ is non-capital income (wages, unemployment compensation, etc.) \n", "- $ R := 1 + r $, where $ r > 0 $ is the interest rate on savings \n", "\n", "\n", "The timing here is as follows:\n", "\n", "1. At the start of period $ t $, the household chooses consumption\n", " $ c_t $. \n", "1. Labor is supplied by the household throughout the period and labor income\n", " $ Y_{t+1} $ is received at the end of period $ t $. \n", "1. Financial income $ R(a_t - c_t) $ is received at the end of period $ t $. \n", "1. Time shifts to $ t+1 $ and the process repeats. \n", "\n", "\n", "Non-capital income $ Y_t $ is given by $ Y_t = y(Z_t) $, where\n", "$ \\{Z_t\\} $ is an exogeneous state process.\n", "\n", "As is common in the literature, we take $ \\{Z_t\\} $ to be a finite state\n", "Markov chain taking values in $ \\mathsf Z $ with Markov matrix $ P $.\n", "\n", "We further assume that\n", "\n", "1. $ \\beta R < 1 $ \n", "1. $ u $ is smooth, strictly increasing and strictly concave with $ \\lim_{c \\to 0} u'(c) = \\infty $ and $ \\lim_{c \\to \\infty} u'(c) = 0 $ \n", "\n", "\n", "The asset space is $ \\mathbb R_+ $ and the state is the pair $ (a,z)\n", "\\in \\mathsf S := \\mathbb R_+ \\times \\mathsf Z $.\n", "\n", "A *feasible consumption path* from $ (a,z) \\in \\mathsf S $ is a consumption\n", "sequence $ \\{c_t\\} $ such that $ \\{c_t\\} $ and its induced asset path $ \\{a_t\\} $ satisfy\n", "\n", "1. $ (a_0, z_0) = (a, z) $ \n", "1. the feasibility constraints in [(42.1)](#equation-eqst), and \n", "1. measurability, which means that $ c_t $ is a function of random\n", " outcomes up to date $ t $ but not after. \n", "\n", "\n", "The meaning of the third point is just that consumption at time $ t $\n", "cannot be a function of outcomes are yet to be observed.\n", "\n", "In fact, for this problem, consumption can be chosen optimally by taking it to\n", "be contingent only on the current state.\n", "\n", "Optimality is defined below." ] }, { "cell_type": "markdown", "id": "3936e4a9", "metadata": {}, "source": [ "### Value Function and Euler Equation\n", "\n", "The *value function* $ V \\colon \\mathsf S \\to \\mathbb{R} $ is defined by\n", "\n", "\n", "\n", "$$\n", "V(a, z) := \\max \\, \\mathbb{E}\n", "\\left\\{\n", "\\sum_{t=0}^{\\infty} \\beta^t u(c_t)\n", "\\right\\} \\tag{42.2}\n", "$$\n", "\n", "where the maximization is overall feasible consumption paths from $ (a,z) $.\n", "\n", "An *optimal consumption path* from $ (a,z) $ is a feasible consumption path from $ (a,z) $ that attains the supremum in [(42.2)](#equation-eqvf).\n", "\n", "To pin down such paths we can use a version of the Euler equation, which in the present setting is\n", "\n", "\n", "\n", "$$\n", "u' (c_t)\n", "\\geq \\beta R \\, \\mathbb{E}_t u'(c_{t+1}) \\tag{42.3}\n", "$$\n", "\n", "and\n", "\n", "\n", "\n", "$$\n", "c_t < a_t\n", "\\; \\implies \\;\n", "u' (c_t) = \\beta R \\, \\mathbb{E}_t u'(c_{t+1}) \\tag{42.4}\n", "$$\n", "\n", "When $ c_t = a_t $ we obviously have $ u'(c_t) = u'(a_t) $,\n", "\n", "When $ c_t $ hits the upper bound $ a_t $, the\n", "strict inequality $ u' (c_t) > \\beta R \\, \\mathbb{E}_t u'(c_{t+1}) $\n", "can occur because $ c_t $ cannot increase sufficiently to attain equality.\n", "\n", "(The lower boundary case $ c_t = 0 $ never arises at the optimum because\n", "$ u'(0) = \\infty $.)\n", "\n", "With some thought, one can show that [(42.3)](#equation-ee00) and [(42.4)](#equation-ee01) are\n", "equivalent to\n", "\n", "\n", "\n", "$$\n", "u' (c_t)\n", "= \\max \\left\\{\n", " \\beta R \\, \\mathbb{E}_t u'(c_{t+1}) \\,,\\; u'(a_t)\n", "\\right\\} \\tag{42.5}\n", "$$" ] }, { "cell_type": "markdown", "id": "fe6950b9", "metadata": {}, "source": [ "### Optimality Results\n", "\n", "As shown in [[Ma *et al.*, 2020](https://python.quantecon.org/zreferences.html#id255)],\n", "\n", "1. For each $ (a,z) \\in \\mathsf S $, a unique optimal consumption path from $ (a,z) $ exists \n", "1. This path is the unique feasible path from $ (a,z) $ satisfying the\n", " Euler equality [(42.5)](#equation-eqeul0) and the transversality condition \n", "\n", "\n", "\n", "\n", "$$\n", "\\lim_{t \\to \\infty} \\beta^t \\, \\mathbb{E} \\, [ u'(c_t) a_{t+1} ] = 0 \\tag{42.6}\n", "$$\n", "\n", "Moreover, there exists an *optimal consumption function*\n", "$ \\sigma^* \\colon \\mathsf S \\to \\mathbb R_+ $ such that the path\n", "from $ (a,z) $ generated by\n", "\n", "$$\n", "(a_0, z_0) = (a, z),\n", "\\quad\n", "c_t = \\sigma^*(a_t, Z_t)\n", "\\quad \\text{and} \\quad\n", "a_{t+1} = R (a_t - c_t) + Y_{t+1}\n", "$$\n", "\n", "satisfies both [(42.5)](#equation-eqeul0) and [(42.6)](#equation-eqtv), and hence is the unique optimal\n", "path from $ (a,z) $.\n", "\n", "Thus, to solve the optimization problem, we need to compute the policy $ \\sigma^* $.\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "id": "29ce376f", "metadata": {}, "source": [ "## Computation\n", "\n", "\n", "\n", "There are two standard ways to solve for $ \\sigma^* $\n", "\n", "1. time iteration using the Euler equality and \n", "1. value function iteration. \n", "\n", "\n", "Our investigation of the cake eating problem and stochastic optimal growth\n", "model suggests that time iteration will be faster and more accurate.\n", "\n", "This is the approach that we apply below." ] }, { "cell_type": "markdown", "id": "d0e0d27f", "metadata": {}, "source": [ "### Time Iteration\n", "\n", "We can rewrite [(42.5)](#equation-eqeul0) to make it a statement about functions rather than\n", "random variables.\n", "\n", "In particular, consider the functional equation\n", "\n", "\n", "\n", "$$\n", "(u' \\circ \\sigma) (a, z)\n", "= \\max \\left\\{\n", "\\beta R \\, \\mathbb E_z (u' \\circ \\sigma)\n", " [R (a - \\sigma(a, z)) + \\hat Y, \\, \\hat Z]\n", "\\, , \\;\n", " u'(a)\n", " \\right\\} \\tag{42.7}\n", "$$\n", "\n", "where\n", "\n", "- $ (u' \\circ \\sigma)(s) := u'(\\sigma(s)) $. \n", "- $ \\mathbb E_z $ conditions on current state $ z $ and $ \\hat X $\n", " indicates next period value of random variable $ X $ and \n", "- $ \\sigma $ is the unknown function. \n", "\n", "\n", "We need a suitable class of candidate solutions for the optimal consumption\n", "policy.\n", "\n", "The right way to pick such a class is to consider what properties the solution\n", "is likely to have, in order to restrict the search space and ensure that\n", "iteration is well behaved.\n", "\n", "To this end, let $ \\mathscr C $ be the space of continuous functions $ \\sigma \\colon \\mathbf S \\to \\mathbb R $ such that $ \\sigma $ is increasing in the first argument, $ 0 < \\sigma(a,z) \\leq a $ for all $ (a,z) \\in \\mathbf S $, and\n", "\n", "\n", "\n", "$$\n", "\\sup_{(a,z) \\in \\mathbf S}\n", "\\left| (u' \\circ \\sigma)(a,z) - u'(a) \\right| < \\infty \\tag{42.8}\n", "$$\n", "\n", "This will be our candidate class.\n", "\n", "In addition, let $ K \\colon \\mathscr{C} \\to \\mathscr{C} $ be defined as\n", "follows.\n", "\n", "For given $ \\sigma \\in \\mathscr{C} $, the value $ K \\sigma (a,z) $ is the unique $ c \\in [0, a] $ that solves\n", "\n", "\n", "\n", "$$\n", "u'(c)\n", "= \\max \\left\\{\n", " \\beta R \\, \\mathbb E_z (u' \\circ \\sigma) \\,\n", " [R (a - c) + \\hat Y, \\, \\hat Z]\n", " \\, , \\;\n", " u'(a)\n", " \\right\\} \\tag{42.9}\n", "$$\n", "\n", "We refer to $ K $ as the Coleman–Reffett operator.\n", "\n", "The operator $ K $ is constructed so that fixed points of $ K $\n", "coincide with solutions to the functional equation [(42.7)](#equation-eqeul1).\n", "\n", "It is shown in [[Ma *et al.*, 2020](https://python.quantecon.org/zreferences.html#id255)] that the unique optimal policy can be\n", "computed by picking any $ \\sigma \\in \\mathscr{C} $ and iterating with the\n", "operator $ K $ defined in [(42.9)](#equation-eqsifc)." ] }, { "cell_type": "markdown", "id": "e91648db", "metadata": {}, "source": [ "### Some Technical Details\n", "\n", "The proof of the last statement is somewhat technical but here is a quick\n", "summary:\n", "\n", "It is shown in [[Ma *et al.*, 2020](https://python.quantecon.org/zreferences.html#id255)] that $ K $ is a contraction mapping on\n", "$ \\mathscr{C} $ under the metric\n", "\n", "$$\n", "\\rho(c, d) := \\| \\, u' \\circ \\sigma_1 - u' \\circ \\sigma_2 \\, \\|\n", " := \\sup_{s \\in S} | \\, u'(\\sigma_1(s)) - u'(\\sigma_2(s)) \\, |\n", " \\qquad \\quad (\\sigma_1, \\sigma_2 \\in \\mathscr{C})\n", "$$\n", "\n", "which evaluates the maximal difference in terms of marginal utility.\n", "\n", "(The benefit of this measure of distance is that, while elements of $ \\mathscr C $ are not generally bounded, $ \\rho $ is always finite under our assumptions.)\n", "\n", "It is also shown that the metric $ \\rho $ is complete on $ \\mathscr{C} $.\n", "\n", "In consequence, $ K $ has a unique fixed point $ \\sigma^* \\in \\mathscr{C} $ and $ K^n c \\to \\sigma^* $ as $ n \\to \\infty $ for any $ \\sigma \\in \\mathscr{C} $.\n", "\n", "By the definition of $ K $, the fixed points of $ K $ in $ \\mathscr{C} $ coincide with the solutions to [(42.7)](#equation-eqeul1) in $ \\mathscr{C} $.\n", "\n", "As a consequence, the path $ \\{c_t\\} $ generated from $ (a_0,z_0) \\in\n", "S $ using policy function $ \\sigma^* $ is the unique optimal path from\n", "$ (a_0,z_0) \\in S $." ] }, { "cell_type": "markdown", "id": "571fa63b", "metadata": {}, "source": [ "## Implementation\n", "\n", "\n", "\n", "We use the CRRA utility specification\n", "\n", "$$\n", "u(c) = \\frac{c^{1 - \\gamma}} {1 - \\gamma}\n", "$$\n", "\n", "The exogeneous state process $ \\{Z_t\\} $ defaults to a two-state Markov chain\n", "with state space $ \\{0, 1\\} $ and transition matrix $ P $.\n", "\n", "Here we build a class called `IFP` that stores the model primitives." ] }, { "cell_type": "code", "execution_count": null, "id": "5428590a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ifp_data = [\n", " ('R', float64), # Interest rate 1 + r\n", " ('β', float64), # Discount factor\n", " ('γ', float64), # Preference parameter\n", " ('P', float64[:, :]), # Markov matrix for binary Z_t\n", " ('y', float64[:]), # Income is Y_t = y[Z_t]\n", " ('asset_grid', float64[:]) # Grid (array)\n", "]\n", "\n", "@jitclass(ifp_data)\n", "class IFP:\n", "\n", " def __init__(self,\n", " r=0.01,\n", " β=0.96,\n", " γ=1.5,\n", " P=((0.6, 0.4),\n", " (0.05, 0.95)),\n", " y=(0.0, 2.0),\n", " grid_max=16,\n", " grid_size=50):\n", "\n", " self.R = 1 + r\n", " self.β, self.γ = β, γ\n", " self.P, self.y = np.array(P), np.array(y)\n", " self.asset_grid = np.linspace(0, grid_max, grid_size)\n", "\n", " # Recall that we need R β < 1 for convergence.\n", " assert self.R * self.β < 1, \"Stability condition violated.\"\n", "\n", " def u_prime(self, c):\n", " return c**(-self.γ)" ] }, { "cell_type": "markdown", "id": "b526b66a", "metadata": {}, "source": [ "Next we provide a function to compute the difference\n", "\n", "\n", "\n", "$$\n", "u'(c) - \\max \\left\\{\n", " \\beta R \\, \\mathbb E_z (u' \\circ \\sigma) \\,\n", " [R (a - c) + \\hat Y, \\, \\hat Z]\n", " \\, , \\;\n", " u'(a)\n", " \\right\\} \\tag{42.10}\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "id": "8533355e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@njit\n", "def euler_diff(c, a, z, σ_vals, ifp):\n", " \"\"\"\n", " The difference between the left- and right-hand side\n", " of the Euler Equation, given current policy σ.\n", "\n", " * c is the consumption choice\n", " * (a, z) is the state, with z in {0, 1}\n", " * σ_vals is a policy represented as a matrix.\n", " * ifp is an instance of IFP\n", "\n", " \"\"\"\n", "\n", " # Simplify names\n", " R, P, y, β, γ = ifp.R, ifp.P, ifp.y, ifp.β, ifp.γ\n", " asset_grid, u_prime = ifp.asset_grid, ifp.u_prime\n", " n = len(P)\n", "\n", " # Convert policy into a function by linear interpolation\n", " def σ(a, z):\n", " return np.interp(a, asset_grid, σ_vals[:, z])\n", "\n", " # Calculate the expectation conditional on current z\n", " expect = 0.0\n", " for z_hat in range(n):\n", " expect += u_prime(σ(R * (a - c) + y[z_hat], z_hat)) * P[z, z_hat]\n", "\n", " return u_prime(c) - max(β * R * expect, u_prime(a))" ] }, { "cell_type": "markdown", "id": "f5cac306", "metadata": {}, "source": [ "Note that we use linear interpolation along the asset grid to approximate the\n", "policy function.\n", "\n", "The next step is to obtain the root of the Euler difference." ] }, { "cell_type": "code", "execution_count": null, "id": "ca99477d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@njit\n", "def K(σ, ifp):\n", " \"\"\"\n", " The operator K.\n", "\n", " \"\"\"\n", " σ_new = np.empty_like(σ)\n", " for i, a in enumerate(ifp.asset_grid):\n", " for z in (0, 1):\n", " result = brentq(euler_diff, 1e-8, a, args=(a, z, σ, ifp))\n", " σ_new[i, z] = result.root\n", "\n", " return σ_new" ] }, { "cell_type": "markdown", "id": "027f46b4", "metadata": {}, "source": [ "With the operator $ K $ in hand, we can choose an initial condition and\n", "start to iterate.\n", "\n", "The following function iterates to convergence and returns the approximate\n", "optimal policy." ] }, { "cell_type": "code", "execution_count": null, "id": "f5fbe737", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def solve_model_time_iter(model, # Class with model information\n", " σ, # Initial condition\n", " tol=1e-4,\n", " max_iter=1000,\n", " verbose=True,\n", " print_skip=25):\n", "\n", " # Set up loop\n", " i = 0\n", " error = tol + 1\n", "\n", " while i < max_iter and error > tol:\n", " σ_new = K(σ, model)\n", " error = np.max(np.abs(σ - σ_new))\n", " i += 1\n", " if verbose and i % print_skip == 0:\n", " print(f\"Error at iteration {i} is {error}.\")\n", " σ = σ_new\n", "\n", " if error > tol:\n", " print(\"Failed to converge!\")\n", " elif verbose:\n", " print(f\"\\nConverged in {i} iterations.\")\n", "\n", " return σ_new" ] }, { "cell_type": "markdown", "id": "fcff07b4", "metadata": {}, "source": [ "Let’s carry this out using the default parameters of the `IFP` class:" ] }, { "cell_type": "code", "execution_count": null, "id": "8b1e51f1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ifp = IFP()\n", "\n", "# Set up initial consumption policy of consuming all assets at all z\n", "z_size = len(ifp.P)\n", "a_grid = ifp.asset_grid\n", "a_size = len(a_grid)\n", "σ_init = np.repeat(a_grid.reshape(a_size, 1), z_size, axis=1)\n", "\n", "σ_star = solve_model_time_iter(ifp, σ_init)" ] }, { "cell_type": "markdown", "id": "10c70f66", "metadata": {}, "source": [ "Here’s a plot of the resulting policy for each exogeneous state $ z $." ] }, { "cell_type": "code", "execution_count": null, "id": "b1a37fae", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "for z in range(z_size):\n", " label = rf'$\\sigma^*(\\cdot, {z})$'\n", " ax.plot(a_grid, σ_star[:, z], label=label)\n", "ax.set(xlabel='assets', ylabel='consumption')\n", "ax.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "b9ccb1d3", "metadata": {}, "source": [ "The following exercises walk you through several applications where policy functions are computed." ] }, { "cell_type": "markdown", "id": "cf066e5f", "metadata": {}, "source": [ "### A Sanity Check\n", "\n", "One way to check our results is to\n", "\n", "- set labor income to zero in each state and \n", "- set the gross interest rate $ R $ to unity. \n", "\n", "\n", "In this case, our income fluctuation problem is just a cake eating problem.\n", "\n", "We know that, in this case, the value function and optimal consumption policy\n", "are given by" ] }, { "cell_type": "code", "execution_count": null, "id": "6288a32c", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def c_star(x, β, γ):\n", "\n", " return (1 - β ** (1/γ)) * x\n", "\n", "\n", "def v_star(x, β, γ):\n", "\n", " return (1 - β**(1 / γ))**(-γ) * (x**(1-γ) / (1-γ))" ] }, { "cell_type": "markdown", "id": "e92cf090", "metadata": {}, "source": [ "Let’s see if we match up:" ] }, { "cell_type": "code", "execution_count": null, "id": "28decbbd", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ifp_cake_eating = IFP(r=0.0, y=(0.0, 0.0))\n", "\n", "σ_star = solve_model_time_iter(ifp_cake_eating, σ_init)\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(a_grid, σ_star[:, 0], label='numerical')\n", "ax.plot(a_grid, c_star(a_grid, ifp.β, ifp.γ), '--', label='analytical')\n", "\n", "ax.set(xlabel='assets', ylabel='consumption')\n", "ax.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "29f10777", "metadata": {}, "source": [ "Success!" ] }, { "cell_type": "markdown", "id": "65bf6a9b", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "id": "aa9ccd7a", "metadata": {}, "source": [ "## Exercise 42.1\n", "\n", "Let’s consider how the interest rate affects consumption.\n", "\n", "Reproduce the following figure, which shows (approximately) optimal consumption policies for different interest rates\n", "\n", "![https://python.quantecon.org/_static/lecture_specific/ifp/ifp_policies.png](https://python.quantecon.org/_static/lecture_specific/ifp/ifp_policies.png)\n", "\n", " \n", "- Other than `r`, all parameters are at their default values. \n", "- `r` steps through `np.linspace(0, 0.04, 4)`. \n", "- Consumption is plotted against assets for income shock fixed at the smallest value. \n", "\n", "\n", "The figure shows that higher interest rates boost savings and hence suppress consumption." ] }, { "cell_type": "markdown", "id": "c67e3461", "metadata": {}, "source": [ "## Solution to[ Exercise 42.1](https://python.quantecon.org/#ifp_ex1)\n", "\n", "Here’s one solution:" ] }, { "cell_type": "code", "execution_count": null, "id": "ceecfc74", "metadata": { "hide-output": false }, "outputs": [], "source": [ "r_vals = np.linspace(0, 0.04, 4)\n", "\n", "fig, ax = plt.subplots()\n", "for r_val in r_vals:\n", " ifp = IFP(r=r_val)\n", " σ_star = solve_model_time_iter(ifp, σ_init, verbose=False)\n", " ax.plot(ifp.asset_grid, σ_star[:, 0], label=f'$r = {r_val:.3f}$')\n", "\n", "ax.set(xlabel='asset level', ylabel='consumption (low income)')\n", "ax.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "05940162", "metadata": {}, "source": [ "## Exercise 42.2\n", "\n", "Now let’s consider the long run asset levels held by households under the\n", "default parameters.\n", "\n", "The following figure is a 45 degree diagram showing the law of motion for assets when consumption is optimal" ] }, { "cell_type": "code", "execution_count": null, "id": "a698444b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ifp = IFP()\n", "\n", "σ_star = solve_model_time_iter(ifp, σ_init, verbose=False)\n", "a = ifp.asset_grid\n", "R, y = ifp.R, ifp.y\n", "\n", "fig, ax = plt.subplots()\n", "for z, lb in zip((0, 1), ('low income', 'high income')):\n", " ax.plot(a, R * (a - σ_star[:, z]) + y[z] , label=lb)\n", "\n", "ax.plot(a, a, 'k--')\n", "ax.set(xlabel='current assets', ylabel='next period assets')\n", "\n", "ax.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "e37ea144", "metadata": {}, "source": [ "The unbroken lines show the update function for assets at each $ z $, which is\n", "\n", "$$\n", "a \\mapsto R (a - \\sigma^*(a, z)) + y(z)\n", "$$\n", "\n", "The dashed line is the 45 degree line.\n", "\n", "We can see from the figure that the dynamics will be stable — assets do not\n", "diverge even in the highest state.\n", "\n", "In fact there is a unique stationary distribution of assets that we can calculate by simulation\n", "\n", "- Can be proved via theorem 2 of [[Hopenhayn and Prescott, 1992](https://python.quantecon.org/zreferences.html#id172)]. \n", "- It represents the long run dispersion of assets across households when households have idiosyncratic shocks. \n", "\n", "\n", "Ergodicity is valid here, so stationary probabilities can be calculated by averaging over a single long time series.\n", "\n", "Hence to approximate the stationary distribution we can simulate a long time\n", "series for assets and histogram it.\n", "\n", "Your task is to generate such a histogram.\n", "\n", "- Use a single time series $ \\{a_t\\} $ of length 500,000. \n", "- Given the length of this time series, the initial condition $ (a_0,\n", " z_0) $ will not matter. \n", "- You might find it helpful to use the `MarkovChain` class from `quantecon`. " ] }, { "cell_type": "markdown", "id": "7c7ce30d", "metadata": {}, "source": [ "## Solution to[ Exercise 42.2](https://python.quantecon.org/#ifp_ex2)\n", "\n", "First we write a function to compute a long asset series." ] }, { "cell_type": "code", "execution_count": null, "id": "aea62bdd", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def compute_asset_series(ifp, T=500_000, seed=1234):\n", " \"\"\"\n", " Simulates a time series of length T for assets, given optimal\n", " savings behavior.\n", "\n", " ifp is an instance of IFP\n", " \"\"\"\n", " P, y, R = ifp.P, ifp.y, ifp.R # Simplify names\n", "\n", " # Solve for the optimal policy\n", " σ_star = solve_model_time_iter(ifp, σ_init, verbose=False)\n", " σ = lambda a, z: np.interp(a, ifp.asset_grid, σ_star[:, z])\n", "\n", " # Simulate the exogeneous state process\n", " mc = MarkovChain(P)\n", " z_seq = mc.simulate(T, random_state=seed)\n", "\n", " # Simulate the asset path\n", " a = np.zeros(T+1)\n", " for t in range(T):\n", " z = z_seq[t]\n", " a[t+1] = R * (a[t] - σ(a[t], z)) + y[z]\n", " return a" ] }, { "cell_type": "markdown", "id": "10f43266", "metadata": {}, "source": [ "Now we call the function, generate the series and then histogram it:" ] }, { "cell_type": "code", "execution_count": null, "id": "c4b9196e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "ifp = IFP()\n", "a = compute_asset_series(ifp)\n", "\n", "fig, ax = plt.subplots()\n", "ax.hist(a, bins=20, alpha=0.5, density=True)\n", "ax.set(xlabel='assets')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "14cccc67", "metadata": {}, "source": [ "The shape of the asset distribution is unrealistic.\n", "\n", "Here it is left skewed when in reality it has a long right tail.\n", "\n", "In a [subsequent lecture](https://python.quantecon.org/ifp_advanced.html) we will rectify this by adding\n", "more realistic features to the model." ] }, { "cell_type": "markdown", "id": "2326abd5", "metadata": {}, "source": [ "## Exercise 42.3\n", "\n", "Following on from exercises 1 and 2, let’s look at how savings and aggregate\n", "asset holdings vary with the interest rate\n", "\n", ">**Note**\n", ">\n", ">[[Ljungqvist and Sargent, 2018](https://python.quantecon.org/zreferences.html#id185)] section 18.6 can be consulted for more background on the topic treated in this exercise.\n", "\n", "For a given parameterization of the model, the mean of the stationary\n", "distribution of assets can be interpreted as aggregate capital in an economy\n", "with a unit mass of *ex-ante* identical households facing idiosyncratic\n", "shocks.\n", "\n", "Your task is to investigate how this measure of aggregate capital varies with\n", "the interest rate.\n", "\n", "Following tradition, put the price (i.e., interest rate) on the vertical axis.\n", "\n", "On the horizontal axis put aggregate capital, computed as the mean of the\n", "stationary distribution given the interest rate." ] }, { "cell_type": "markdown", "id": "b5e5c2e2", "metadata": {}, "source": [ "## Solution to[ Exercise 42.3](https://python.quantecon.org/#ifp_ex3)\n", "\n", "Here’s one solution" ] }, { "cell_type": "code", "execution_count": null, "id": "7d72dcb0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "M = 25\n", "r_vals = np.linspace(0, 0.02, M)\n", "fig, ax = plt.subplots()\n", "\n", "asset_mean = []\n", "for r in r_vals:\n", " print(f'Solving model at r = {r}')\n", " ifp = IFP(r=r)\n", " mean = np.mean(compute_asset_series(ifp, T=250_000))\n", " asset_mean.append(mean)\n", "ax.plot(asset_mean, r_vals)\n", "\n", "ax.set(xlabel='capital', ylabel='interest rate')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "bd77b381", "metadata": {}, "source": [ "As expected, aggregate savings increases with the interest rate." ] } ], "metadata": { "date": 1714442505.6838198, "filename": "ifp.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "The Income Fluctuation Problem I: Basic Model" }, "nbformat": 4, "nbformat_minor": 5 }