From 7ce111736000054f9d3e2d5a069c4a347988631a Mon Sep 17 00:00:00 2001 From: Harveyt47 Date: Fri, 20 Mar 2020 15:31:19 +1100 Subject: [PATCH] adding ifp_vfi --- ifp_extensions/source/ifp_vfi.rst | 599 ++++++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 ifp_extensions/source/ifp_vfi.rst diff --git a/ifp_extensions/source/ifp_vfi.rst b/ifp_extensions/source/ifp_vfi.rst new file mode 100644 index 0000000..c2bab6b --- /dev/null +++ b/ifp_extensions/source/ifp_vfi.rst @@ -0,0 +1,599 @@ +.. _ifp: + +.. include:: /_static/includes/header.raw + +.. highlight:: python3 + +************************************************************** +:index:`The Income Fluctuation Problem` +************************************************************** + +.. contents:: :depth: 2 + +In addition to what's in Anaconda, this lecture will need the following libraries: + +.. code-block:: ipython + :class: hide-output + + !pip install --upgrade quantecon + !pip install interpolation + +Overview +======== + +Next, we study an optimal savings problem for an infinitely lived consumer---the "common ancestor" described in :cite:`Ljungqvist2012`, section 1.3. + +This is an essential sub-problem for many representative macroeconomic models + +* :cite:`Aiyagari1994` + +* :cite:`Huggett1993` + +* etc. + +It is related to the decision problem in the :doc:`stochastic optimal growth +model ` and yet differs in important ways. + +For example, the choice problem for the agent includes an additive income term that leads to an occasionally binding constraint. + +Our presentation of the model will be relatively brief. + +.. only:: html + + * For further details on economic intuition, implication and models, see :cite:`Ljungqvist2012`. + * Proofs of all mathematical results stated below can be found in :download:`this paper <_static/lecture_specific/ifp/pi2.pdf>`. + +.. only:: latex + + * For further details on economic intuition, implication and models, see :cite:`Ljungqvist2012`. + * Proofs of all mathematical results stated below can be found in `this paper `__. + +To solve the model we will use Euler equation based time iteration, similar to :doc:`this lecture `. + +This method turns out to be globally convergent under mild assumptions, even when utility is unbounded (both above and below). + +We'll need the following imports: + +.. code-block:: ipython + + import numpy as np + from quantecon.optimize import brent_max, brentq + from interpolation import interp + from numba import njit, jitclass, float64, prange + import matplotlib.pyplot as plt + %matplotlib inline + from quantecon import MarkovChain + + +References +---------- + +Other useful references include :cite:`Deaton1991`, :cite:`DenHaan2010`, :cite:`Kuhn2013`, :cite:`Rabault2002`, :cite:`Reiter2009` and :cite:`SchechtmanEscudero1977`. + + + +The Optimal Savings Problem +=========================== + +.. index:: + single: Optimal Savings; Problem + +Let's write down the model and then discuss how to solve it. + +Set-Up +------ + +Consider a household that chooses a state-contingent consumption plan :math:`\{c_t\}_{t \geq 0}` to maximize + +.. math:: + + \mathbb{E} \, \sum_{t=0}^{\infty} \beta^t u(c_t) + + +subject to + +.. math:: + :label: eqst + + a_{t+1} \leq R(a_t-c_t) + y_{t+1}, + \qquad c_t \geq 0, + \qquad a_t \geq -b + \qquad t = 0, 1, \ldots + + +Here: + +* :math:`\beta \in (0,1)` is the discount factor. + +* :math:`a_t` is asset holdings at time :math:`t`, with ad-hoc borrowing constraint :math:`a_t \geq -b`. + +* :math:`c_t` is consumption. + +* :math:`y_t` is non-capital income (wages, unemployment compensation, etc.). + +* :math:`R := 1 + r`, where :math:`r > 0` is the interest rate on savings. + + +Non-capital income :math:`\{y_t\}` is determined by which state :math:`\{z_t\}\in Z` the agent is in. + +More specifically we define non-capital income as the mapping :math:`y \colon Z \to \mathbb R`. + +The state process :math:`\{z_t\}` is assumed to be a Markov process taking values in :math:`Z\subset (0,\infty)` with stochastic kernel :math:`\Pi`. + +This means that :math:`\Pi(z, B)` is the probability that :math:`z_{t+1} \in +B` given :math:`z_t = z`. + +The expectation of :math:`f(z_{t+1})` given :math:`z_t = z` is written as + +.. math:: + + \int f( \acute z) \, \Pi(z, d \acute z) + +We further assume that: + +#. :math:`r > 0` and :math:`\beta R < 1`. + +#. :math:`u` is smooth, strictly increasing and strictly concave with :math:`\lim_{c \to 0} u'(c) = \infty` and :math:`\lim_{c \to \infty} u'(c) = 0`. + +#. Non-capital income is assumed to take the form :math:`y_{t} = y(z_t)=e^{z_t}`. + + + +The asset space is :math:`[-b, \infty)` and the state is the pair :math:`(a,z) \in S := [-b,\infty) \times Z`. + +A *feasible consumption path* from :math:`(a,z) \in S` is a consumption +sequence :math:`\{c_t\}` such that :math:`\{c_t\}` and its induced asset path :math:`\{a_t\}` satisfy + +#. :math:`(a_0, z_0) = (a, z)` + +#. the feasibility constraints in :eq:`eqst`, and + +#. measurability of :math:`c_t` w.r.t. the filtration generated by :math:`\{z_1, \ldots, z_t\}` + +The meaning of the third point is just that consumption at time :math:`t` can only be +a function of outcomes that have already been observed. + + +Value Function +-------------- + +The *value function* :math:`V \colon S \to \mathbb{R}` is defined by + +.. math:: + :label: eqvf + + V(a, z) := \sup \, \mathbb{E} + \left\{ + \sum_{t=0}^{\infty} \beta^t u(c_t) + \right\} + + +where the supremum is overall feasible consumption paths from :math:`(a,z)`. + + +An *optimal consumption path* from :math:`(a,z)` is a feasible consumption path from :math:`(a,z)` that attains the supremum in :eq:`eqvf`. + +Value Function Iteration +------------------------ + + +The Bellman operator for this problem is given by + +.. math:: + :label: eqbop + + Tv(a, z) + = \max_{0 \leq c \leq a} + \left\{ + u(c) + \beta \int v(R(a-c) + \acute y, \acute z) \Pi(z, d \acute z) + \right\} + + +We have to be careful with VFI (i.e., iterating with +:math:`T`) in this setting because :math:`u` is not assumed to be bounded + +* In fact typically unbounded both above and below --- e.g. :math:`u(c) = \log c`. +* In which case, the standard DP theory does not apply. +* :math:`T^n v` is not guaranteed to converge to the value function for arbitrary continuous bounded :math:`v`. + +Nonetheless, we can always try the popular strategy "iterate and hope". + + + +Implementation +============== + +.. index:: + single: Optimal Savings; Programming Implementation + +First we're going to create a ``@jitclass`` called ``ConsumerProblem`` to store the model primitives and a method to return the right hand side of the bellman equation. + +.. code-block:: python3 + + consumer_data = [ + ('r', float64), # rate of interest + ('β', float64), # discount factor + ('γ', float64), # degree of relative risk aversion + ('Π', float64[:,:]), # Markov matrix for z_t (2d array) + ('z_vals', float64[:]), # state space of z_t (array) + ('b', float64), # Borrowing constraint + ('asset_grid', float64[:]), # the finite asset grid (array) + ('R', float64), # rate of return on assets + ] + + +.. code-block:: python3 + + @jitclass(consumer_data) + class ConsumerProblem: + """ + A class that stores primitives for the income fluctuation problem. The + income process is assumed to be a finite state Markov chain. + """ + def __init__(self, + r=0.01, + β=0.96, + γ=0.99, + Π=((0.6, 0.4), + (0.05, 0.95)), + z_vals=(0.5, 1.0), + b=-1e-10, + grid_max=16, + grid_size=1000 + ): + self.r, self.R = r, 1 + r + self.β, self.γ, self.b = β, γ,b + self.Π, self.z_vals = np.array(Π), np.array(z_vals) + self.asset_grid = np.linspace(-b, grid_max, grid_size) + + # Utility function + def u(self, c): + + γ = self.γ + + if γ == 1: + return np.log(c) + else: + return (c ** (1 - γ)) / (1 - γ) + + # State determined non-capital income + def y(self, z): + return np.exp(z) + + def state_action_value(self, c, a, i_z, v_grid): + """ + Returns the RHS of the bellman equation + + * c is the consumption point and will be interpolated + * a is the point on the asset grid + * i_z is the current state + * v_grid is the grid points of the current value function. + + """ + β, u, y, R = self.β, self.u, self.y, self.R + + z_vals, asset_grid, Π = self.z_vals, self.asset_grid, self.Π + + expectation = 0 + v = lambda x: interp(asset_grid, v_grid[:,i_z], x) + + for j in prange(len(z_vals)): + expectation += v(R * (a - c) + y(z_vals[j])) * Π[i_z, j] + + return u(c) + β * expectation + + +We'll hold this class in an instant called ``cp`` + +.. code-block:: python3 + + cp = ConsumerProblem() + +Now we need to create the bellman operator function ``T`` to return the updated value function. + +.. code-block:: python3 + + @njit + def T(cp, v, ϵ=1e-11): + """ + The Bellman operator. + + * cp is an instance of ConsumerProblem. + * v is an array of the value function guess. + * ϵ is the lower bound of the maximisation. + """ + + v_new = np.empty_like(v) + asset_grid, z_vals = cp.asset_grid, cp.z_vals + + for i_z in prange(len(z_vals)): + for i_a in prange(len(asset_grid)): + a = asset_grid[i_a] + + v_star = brent_max(cp.state_action_value, ϵ, a, args=(a, i_z, v))[1] + v_new[i_a, i_z] = v_star + + return v_new + + +Nw we need to create a function ``value_function`` to iterate and update the value function using the bellman operator ``T`` until the value funciton has converged. + +.. code-block:: python3 + + def value_function(cp, + tol=1e-4, + max_iter=1000, + verbose=True, + print_skip=25): + + """ + Solves for the value function using iteration + + * cp is an instance of ConsumerProblem + """ + + u, β, b, R = cp.u, cp.β, cp.b, cp.R + asset_grid, z_vals = cp.asset_grid, cp.z_vals + + # Initial guess of v + v = np.empty((len(asset_grid), len(z_vals))) + + for z in prange(len(z_vals)): + v[:,z] = u(asset_grid) + + i = 0 + error = tol + 1 + + while i < max_iter and error > tol: + v_new = T(cp, 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 + +.. code-block:: python3 + + v_star = value_function(cp) + +Plotting the result using the default parameters of the ``ConsumerProblem`` class + +.. code-block:: python3 + + asset_grid = cp.asset_grid + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.plot(asset_grid, v_star[:, 0], label='$v^*$') + ax.set(xlabel='asset level', ylabel='value function') + ax.legend() + plt.show() + +Policy +====== + +Now that we have the value function we can use it to determine the optimal policy function. We do this by passing the value function through the function ``σ`` +which finds the maximizer for everypoint on the asset grid of the value function. + +.. code-block:: python3 + + @njit + def σ(cp, v, ϵ=0): + """ + Compute policy function. + + * cp is an instance of ConsumerProblem. + * v is an array of the current value function. + * ϵ is the lower bound of the maximisation. + """ + + σ_new = np.empty_like(v) + asset_grid, z_vals = cp.asset_grid, cp.z_vals + + for i_z in prange(len(z_vals)): + for i_a in prange(len(asset_grid)): + a = asset_grid[i_a] + σ_star = brent_max(cp.state_action_value, ϵ, a, args=(a, i_z, v))[0] + σ_new[i_a, i_z] = σ_star + + return σ_new + +.. code-block:: python3 + + σ_star = σ(cp,v_star) + + +.. code-block:: python3 + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.plot(asset_grid, σ_star[:,0], lw=2, alpha=0.6, label='$c^*$') + ax.legend() + plt.show() + + +Exercises +========= + + +Exercise 1 +---------- + +Next, let's consider how the interest rate affects consumption. + +Reproduce the following figure, which shows (approximately) optimal consumption policies for different interest rates + +.. figure:: /_static/lecture_specific/ifp/ifp_policies.png + +* Other than ``r``, all parameters are at their default values. +* ``r`` steps through ``np.linspace(0, 0.03, 4)``. +* Consumption is plotted against assets for income shock fixed at the smallest value. + +The figure shows that higher interest rates boost savings and hence suppress consumption. + + + +Exercise 2 +---------- + +Now let's consider the long run asset levels held by households. + +We'll take ``r = 0.03`` and otherwise use default parameters. + +The following figure is a 45 degree diagram showing the law of motion for assets when consumption is optimal + +.. code-block:: python3 + + m = ConsumerProblem(r=0.03) + v = value_function(m, verbose=False) + + σ_star = σ(m, v) + a = m.asset_grid + R, z_vals = m.R, m.z_vals + + fig, ax = plt.subplots(figsize=(10, 8)) + ax.plot(a, R * (a- σ_star[:, 0]) + m.y(z_vals[0]), label='Low income') + ax.plot(a, R * (a - σ_star[:, 1]) + m.y(z_vals[1]), label='High income') + ax.plot(a, a, 'k--') + ax.set(xlabel='Current assets', + ylabel='Next period assets') + ax.legend() + plt.show() + + + +The blue line and orange line represent the function + +.. math:: + + a' = h(a, z) := R (a- \sigma^*(a, z)) + y + + +when income :math:`y` takes its high and low values respectively. + +The dashed line is the 45 degree line. + +We can see from the figure that the dynamics will be stable --- assets do not +diverge. + +In fact there is a unique stationary distribution of assets that we can calculate by simulation + +* Can be proved via theorem 2 of :cite:`HopenhaynPrescott1992`. + +* Represents the long run dispersion of assets across households when households have idiosyncratic shocks. + + +Ergodicity is valid here, so stationary probabilities can be calculated by averaging over a single long time series. + +Hence to approximate the stationary distribution we can simulate a long time series for assets and histogram, as in the following figure + +.. figure:: /_static/lecture_specific/ifp/ifp_histogram.png + +Your task is to replicate the figure + +* Parameters are as discussed above. + +* The histogram in the figure used a single time series :math:`\{a_t\}` of length 500,000. + +* Given the length of this time series, the initial condition :math:`(a_0, z_0)` will not matter. + +* You might find it helpful to use the ``MarkovChain`` class from ``quantecon``. + + + + +Exercise 3 +---------- + +Following on from exercises 1 and 2, let's look at how savings and aggregate asset holdings vary with the interest rate + +* Note: :cite:`Ljungqvist2012` section 18.6 can be consulted for more background on the topic treated in this exercise. + +For a given parameterization of the model, the mean of the stationary distribution can be interpreted as aggregate capital in an economy with a unit mass of *ex-ante* identical households facing idiosyncratic shocks. + +Let's look at how this measure of aggregate capital varies with the interest +rate and borrowing constraint. + +The next figure plots aggregate capital against the interest rate for ``b in (1, 3)`` + + +.. figure:: /_static/lecture_specific/ifp/ifp_agg_savings.png + +As is traditional, the price (interest rate) is on the vertical axis. + +The horizontal axis is aggregate capital computed as the mean of the stationary distribution. + +Exercise 3 is to replicate the figure, making use of code from previous exercises. + +Try to explain why the measure of aggregate capital is equal to :math:`-b` +when :math:`r=0` for both cases shown here. + +Solutions +========= + + + + +Exercise 1 +---------- + +.. code-block:: python3 + + r_vals = np.linspace(0, 0.03, 4) + + fig, ax = plt.subplots(figsize=(10, 8)) + for r_val in r_vals: + cp = ConsumerProblem(r=r_val) + v_ex1 = value_function(cp, verbose=False) + σ_star = σ(cp, v_ex1) + + ax.plot(cp.asset_grid, σ_star[:, 0], label=f'$r = {r_val:.3f}$') + + ax.set(xlabel='asset level', ylabel='consumption (low income)') + ax.legend() + plt.show() + + +Exercise 2 +---------- + +.. code-block:: python3 + + def compute_asset_series(cp, T=500000, verbose=False): + """ + Simulates a time series of length T for assets, given optimal + savings behavior. + + cp is an instance of ConsumerProblem + """ + Π, z_vals, R = cp.Π, cp.z_vals, cp.R # Simplify names + mc = MarkovChain(Π) + v_ex2 = value_function(cp, verbose=False) + + σ_star = σ(cp, v_ex2) + cf = lambda a, i_z: interp(cp.asset_grid, σ_star[:, i_z], a) + a = np.zeros(T+1) + z_seq = mc.simulate(T) + for t in range(T): + i_z = z_seq[t] + a[t+1] = R * (a[t] - cf(a[t], i_z)) + cp.y(z_vals[i_z]) + return a + + cp = ConsumerProblem(r=0.03) + a = compute_asset_series(cp) + + fig, ax = plt.subplots(figsize=(10, 8)) + ax.hist(a, bins=20, alpha=0.5, density=True) + ax.set(xlabel='assets') + plt.show() + + +Exercise 3 +---------- +