{
"cells": [
{
"cell_type": "markdown",
"id": "826339dd",
"metadata": {},
"source": [
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "de7e3fbb",
"metadata": {},
"source": [
"# Optimal Growth II: Time Iteration"
]
},
{
"cell_type": "markdown",
"id": "f8cfe279",
"metadata": {},
"source": [
"## Contents\n",
"\n",
"- [Optimal Growth II: Time Iteration](#Optimal-Growth-II:-Time-Iteration) \n",
" - [Overview](#Overview) \n",
" - [The Euler Equation](#The-Euler-Equation) \n",
" - [Comparison with Value Function Iteration](#Comparison-with-Value-Function-Iteration) \n",
" - [Implementation](#Implementation) \n",
" - [Exercises](#Exercises) \n",
" - [Solutions](#Solutions) "
]
},
{
"cell_type": "markdown",
"id": "7787974f",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"In this lecture we’ll continue our [earlier study](https://julia.quantecon.org/optgrowth.html) of the stochastic optimal growth model.\n",
"\n",
"In that lecture we solved the associated discounted dynamic programming problem using value function iteration.\n",
"\n",
"The beauty of this technique is its broad applicability.\n",
"\n",
"With numerical problems, however, we can often attain higher efficiency in specific applications by deriving methods that are carefully tailored to the application at hand.\n",
"\n",
"The stochastic optimal growth model has plenty of structure to exploit for this purpose, especially when we adopt some concavity and smoothness assumptions over primitives.\n",
"\n",
"We’ll use this structure to obtain an **Euler equation** based method that’s more efficient than value function iteration for this and some other closely related applications.\n",
"\n",
"In a [subsequent lecture](https://julia.quantecon.org/egm_policy_iter.html) we’ll see that the numerical implementation part of the Euler equation method can be further adjusted to obtain even more efficiency."
]
},
{
"cell_type": "markdown",
"id": "5d5b8486",
"metadata": {},
"source": [
"## The Euler Equation\n",
"\n",
"Let’s take the model set out in [the stochastic growth model lecture](https://julia.quantecon.org/optgrowth.html) and add the assumptions that\n",
"\n",
"1. $ u $ and $ f $ are continuously differentiable and strictly concave \n",
"1. $ f(0) = 0 $ \n",
"1. $ \\lim_{c \\to 0} u'(c) = \\infty $ and $ \\lim_{c \\to \\infty} u'(c) = 0 $ \n",
"1. $ \\lim_{k \\to 0} f'(k) = \\infty $ and $ \\lim_{k \\to \\infty} f'(k) = 0 $ \n",
"\n",
"\n",
"The last two conditions are usually called **Inada conditions**.\n",
"\n",
"Recall the Bellman equation\n",
"\n",
"\n",
"\n",
"$$\n",
"v^*(y) = \\max_{0 \\leq c \\leq y}\n",
" \\left\\{\n",
" u(c) + \\beta \\int v^*(f(y - c) z) \\phi(dz)\n",
" \\right\\}\n",
"\\quad \\text{for all} \\quad\n",
"y \\in \\mathbb R_+ \\tag{35.1}\n",
"$$\n",
"\n",
"Let the optimal consumption policy be denoted by $ c^* $.\n",
"\n",
"We know that $ c^* $ is a $ v^* $ greedy policy, so that $ c^*(y) $ is the maximizer in [(35.1)](#equation-cpi-fpb30).\n",
"\n",
"The conditions above imply that\n",
"\n",
"- $ c^* $ is the unique optimal policy for the stochastic optimal growth model \n",
"- the optimal policy is continuous, strictly increasing and also **interior**, in the sense that $ 0 < c^*(y) < y $ for all strictly positive $ y $, and \n",
"- the value function is strictly concave and continuously differentiable, with \n",
"\n",
"\n",
"\n",
"\n",
"$$\n",
"(v^*)'(y) = u' (c^*(y) ) := (u' \\circ c^*)(y) \\tag{35.2}\n",
"$$\n",
"\n",
"The last result is called the **envelope condition** due to its relationship with the [envelope theorem](https://en.wikipedia.org/wiki/Envelope_theorem).\n",
"\n",
"To see why [(35.2)](#equation-cpi-env) might be valid, write the Bellman equation in the equivalent\n",
"form\n",
"\n",
"$$\n",
"v^*(y) = \\max_{0 \\leq k \\leq y}\n",
" \\left\\{\n",
" u(y-k) + \\beta \\int v^*(f(k) z) \\phi(dz)\n",
" \\right\\},\n",
"$$\n",
"\n",
"differentiate naively with respect to $ y $, and then evaluate at the optimum.\n",
"\n",
"Section 12.1 of [EDTC](http://johnstachurski.net/edtc.html) contains full proofs of these results, and closely related discussions can be found in many other texts.\n",
"\n",
"Differentiability of the value function and iteriority of the optimal policy\n",
"imply that optimal consumption satisfies the first order condition associated\n",
"with [(35.1)](#equation-cpi-fpb30), which is\n",
"\n",
"\n",
"\n",
"$$\n",
"u'(c^*(y)) = \\beta \\int (v^*)'(f(y - c^*(y)) z) f'(y - c^*(y)) z \\phi(dz) \\tag{35.3}\n",
"$$\n",
"\n",
"Combining [(35.2)](#equation-cpi-env) and the first-order condition [(35.3)](#equation-cpi-foc) gives the famous **Euler equation**\n",
"\n",
"\n",
"\n",
"$$\n",
"(u'\\circ c^*)(y)\n",
"= \\beta \\int (u'\\circ c^*)(f(y - c^*(y)) z) f'(y - c^*(y)) z \\phi(dz) \\tag{35.4}\n",
"$$\n",
"\n",
"We can think of the Euler equation as a functional equation\n",
"\n",
"\n",
"\n",
"$$\n",
"(u'\\circ \\sigma)(y)\n",
"= \\beta \\int (u'\\circ \\sigma)(f(y - \\sigma(y)) z) f'(y - \\sigma(y)) z \\phi(dz) \\tag{35.5}\n",
"$$\n",
"\n",
"over interior consumption policies $ \\sigma $, one solution of which is the optimal policy $ c^* $.\n",
"\n",
"Our aim is to solve the functional equation [(35.5)](#equation-cpi-euler-func) and hence obtain $ c^* $."
]
},
{
"cell_type": "markdown",
"id": "b0774a29",
"metadata": {},
"source": [
"### The Coleman Operator\n",
"\n",
"Recall the Bellman operator\n",
"\n",
"\n",
"\n",
"$$\n",
"Tw(y) := \\max_{0 \\leq c \\leq y}\n",
"\\left\\{\n",
" u(c) + \\beta \\int w(f(y - c) z) \\phi(dz)\n",
"\\right\\} \\tag{35.6}\n",
"$$\n",
"\n",
"Just as we introduced the Bellman operator to solve the Bellman equation, we\n",
"will now introduce an operator over policies to help us solve the Euler\n",
"equation.\n",
"\n",
"This operator $ K $ will act on the set of all $ \\sigma \\in \\Sigma $\n",
"that are continuous, strictly increasing and interior (i.e., $ 0 < \\sigma(y) < y $ for all strictly positive $ y $).\n",
"\n",
"Henceforth we denote this set of policies by $ \\mathscr P $\n",
"\n",
"1. The operator $ K $ takes as its argument a $ \\sigma \\in \\mathscr P $ and \n",
"1. returns a new function $ K\\sigma $, where $ K\\sigma(y) $ is the $ c \\in (0, y) $ that solves \n",
"\n",
"\n",
"\n",
"\n",
"$$\n",
"u'(c)\n",
"= \\beta \\int (u' \\circ \\sigma) (f(y - c) z ) f'(y - c) z \\phi(dz) \\tag{35.7}\n",
"$$\n",
"\n",
"We call this operator the **Coleman operator** to acknowledge the work of [[Col90](https://julia.quantecon.org/../zreferences.html#id71)] (although many people have studied this and other closely related iterative techniques).\n",
"\n",
"In essence, $ K\\sigma $ is the consumption policy that the Euler equation tells\n",
"you to choose today when your future consumption policy is $ \\sigma $.\n",
"\n",
"The important thing to note about $ K $ is that, by\n",
"construction, its fixed points coincide with solutions to the functional\n",
"equation [(35.5)](#equation-cpi-euler-func).\n",
"\n",
"In particular, the optimal policy $ c^* $ is a fixed point.\n",
"\n",
"Indeed, for fixed $ y $, the value $ Kc^*(y) $ is the $ c $ that\n",
"solves\n",
"\n",
"$$\n",
"u'(c)\n",
"= \\beta \\int (u' \\circ c^*) (f(y - c) z ) f'(y - c) z \\phi(dz)\n",
"$$\n",
"\n",
"In view of the Euler equation, this is exactly $ c^*(y) $."
]
},
{
"cell_type": "markdown",
"id": "36ae472f",
"metadata": {},
"source": [
"### Is the Coleman Operator Well Defined?\n",
"\n",
"In particular, is there always a unique $ c \\in (0, y) $ that solves\n",
"[(35.7)](#equation-cpi-coledef)?\n",
"\n",
"The answer is yes, under our assumptions.\n",
"\n",
"For any $ \\sigma \\in \\mathscr P $, the right side of [(35.7)](#equation-cpi-coledef)\n",
"\n",
"- is continuous and strictly increasing in $ c $ on $ (0, y) $ \n",
"- diverges to $ +\\infty $ as $ c \\uparrow y $ \n",
"\n",
"\n",
"The left side of [(35.7)](#equation-cpi-coledef)\n",
"\n",
"- is continuous and strictly decreasing in $ c $ on $ (0, y) $ \n",
"- diverges to $ +\\infty $ as $ c \\downarrow 0 $ \n",
"\n",
"\n",
"Sketching these curves and using the information above will convince you that they cross exactly once as $ c $ ranges over $ (0, y) $.\n",
"\n",
"With a bit more analysis, one can show in addition that $ K \\sigma \\in \\mathscr P $\n",
"whenever $ \\sigma \\in \\mathscr P $."
]
},
{
"cell_type": "markdown",
"id": "dd6cad97",
"metadata": {},
"source": [
"## Comparison with Value Function Iteration\n",
"\n",
"How does Euler equation time iteration compare with value function iteration?\n",
"\n",
"Both can be used to compute the optimal policy, but is one faster or more\n",
"accurate?\n",
"\n",
"There are two parts to this story.\n",
"\n",
"First, on a theoretical level, the two methods are essentially isomorphic.\n",
"\n",
"In particular, they converge at the same rate.\n",
"\n",
"We’ll prove this in just a moment.\n",
"\n",
"The other side to the story is the speed of the numerical implementation.\n",
"\n",
"It turns out that, once we actually implement these two routines, time iteration is faster and more accurate than value function iteration.\n",
"\n",
"More on this below."
]
},
{
"cell_type": "markdown",
"id": "80c806df",
"metadata": {},
"source": [
"### Equivalent Dynamics\n",
"\n",
"Let’s talk about the theory first.\n",
"\n",
"To explain the connection between the two algorithms, it helps to understand\n",
"the notion of equivalent dynamics.\n",
"\n",
"(This concept is very helpful in many other contexts as well).\n",
"\n",
"Suppose that we have a function $ g \\colon X \\to X $ where $ X $ is a given set.\n",
"\n",
"The pair $ (X, g) $ is sometimes called a **dynamical system** and we\n",
"associate it with trajectories of the form\n",
"\n",
"$$\n",
"x_{t+1} = g(x_t), \\qquad x_0 \\text{ given}\n",
"$$\n",
"\n",
"Equivalently, $ x_t = g^t(x_0) $, where $ g $ is the $ t $-th\n",
"composition of $ g $ with itself.\n",
"\n",
"Here’s the picture\n",
"\n",
"![https://julia.quantecon.org/_static/figures/col_pol_composition.png](https://julia.quantecon.org/_static/figures/col_pol_composition.png)\n",
"\n",
" \n",
"Now let another function $ h \\colon Y \\to Y $ where $ Y $ is another set.\n",
"\n",
"Suppose further that\n",
"\n",
"- there exists a bijection $ \\tau $ from $ X $ to $ Y $ \n",
"- the two functions **commute** under $ \\tau $, which is to say that\n",
" $ \\tau(g(x)) = h (\\tau(x)) $ for all $ x \\in X $ \n",
"\n",
"\n",
"The last statement can be written more simply as\n",
"\n",
"$$\n",
"\\tau \\circ g = h \\circ \\tau\n",
"$$\n",
"\n",
"or, by applying $ \\tau^{-1} $ to both sides\n",
"\n",
"\n",
"\n",
"$$\n",
"g = \\tau^{-1} \\circ h \\circ \\tau \\tag{35.8}\n",
"$$\n",
"\n",
"Here’s a commutative diagram that illustrates\n",
"\n",
"![https://julia.quantecon.org/_static/figures/col_pol_bij1.png](https://julia.quantecon.org/_static/figures/col_pol_bij1.png)\n",
"\n",
" \n",
"Here’s a similar figure that traces out the action of the maps on a point\n",
"$ x \\in X $\n",
"\n",
"![https://julia.quantecon.org/_static/figures/col_pol_bij2.png](https://julia.quantecon.org/_static/figures/col_pol_bij2.png)\n",
"\n",
" \n",
"Now, it’s easy to check from [(35.8)](#equation-cpi-ghcom) that $ g^2 = \\tau^{-1} \\circ h^2 \\circ \\tau $ holds.\n",
"\n",
"In fact, if you like proofs by induction, you won’t have trouble showing that\n",
"\n",
"$$\n",
"g^n = \\tau^{-1} \\circ h^n \\circ \\tau\n",
"$$\n",
"\n",
"is valid for all $ n $.\n",
"\n",
"What does this tell us?\n",
"\n",
"It tells us that the following are equivalent\n",
"\n",
"- iterate $ n $ times with $ g $, starting at $ x $ \n",
"- shift $ x $ to $ Y $ using $ \\tau $, iterate $ n $ times with $ h $ starting at $ \\tau(x) $, and shift the result $ h^n(\\tau(x)) $ back to $ X $ using $ \\tau^{-1} $ \n",
"\n",
"\n",
"We end up with exactly the same object."
]
},
{
"cell_type": "markdown",
"id": "8650be24",
"metadata": {},
"source": [
"### Back to Economics\n",
"\n",
"Have you guessed where this is leading?\n",
"\n",
"What we’re going to show now is that the operators $ T $ and $ K $\n",
"commute under a certain bijection.\n",
"\n",
"The implication is that they have exactly the same rate of convergence.\n",
"\n",
"To make life a little easier, we’ll assume in the following analysis (although not\n",
"always in our applications) that $ u(0) = 0 $."
]
},
{
"cell_type": "markdown",
"id": "2398bd25",
"metadata": {},
"source": [
"#### A Bijection\n",
"\n",
"Let $ \\mathscr V $ be all strictly concave, continuously differentiable functions $ v $ mapping $ \\mathbb R_+ $ to itself and satisfying $ v(0) = 0 $ and $ v'(y) > u'(y) $ for all positive $ y $.\n",
"\n",
"For $ v \\in \\mathscr V $ let\n",
"\n",
"$$\n",
"M v := h \\circ v' \\qquad \\text{where } h := (u')^{-1}\n",
"$$\n",
"\n",
"Although we omit details, $ \\sigma := M v $ is actually the unique\n",
"$ v $-greedy policy.\n",
"\n",
"- See proposition 12.1.18 of [EDTC](http://johnstachurski.net/edtc.html) \n",
"\n",
"\n",
"It turns out that $ M $ is a bijection from $ \\mathscr V $ to $ \\mathscr P $.\n",
"\n",
"A (solved) exercise below asks you to confirm this."
]
},
{
"cell_type": "markdown",
"id": "303f0a5d",
"metadata": {},
"source": [
"#### Commutative Operators\n",
"\n",
"It is an additional solved exercise (see below) to show that $ T $ and $ K $ commute under $ M $, in the sense that\n",
"\n",
"\n",
"\n",
"$$\n",
"M \\circ T = K \\circ M \\tag{35.9}\n",
"$$\n",
"\n",
"In view of the preceding discussion, this implies that\n",
"\n",
"$$\n",
"T^n = M^{-1} \\circ K^n \\circ M\n",
"$$\n",
"\n",
"Hence, $ T $ and $ K $ converge at exactly the same rate!"
]
},
{
"cell_type": "markdown",
"id": "cb71f6ff",
"metadata": {},
"source": [
"## Implementation\n",
"\n",
"We’ve just shown that the operators $ T $ and $ K $ have the same rate of convergence.\n",
"\n",
"However, it turns out that, once numerical approximation is taken into account, significant differences arises.\n",
"\n",
"In particular, the image of policy functions under $ K $ can be calculated faster and with greater accuracy than the image of value functions under $ T $.\n",
"\n",
"Our intuition for this result is that\n",
"\n",
"- the Coleman operator exploits more information because it uses first order and envelope conditions \n",
"- policy functions generally have less curvature than value functions, and hence admit more accurate approximations based on grid point information "
]
},
{
"cell_type": "markdown",
"id": "9c89035c",
"metadata": {},
"source": [
"### The Operator\n",
"\n",
"Here’s some code that implements the Coleman operator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6865959f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"using LinearAlgebra, Statistics\n",
"using BenchmarkTools, Interpolations, LaTeXStrings, Plots, Roots\n",
"using Optim, Random"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "412c3033",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"using BenchmarkTools, Interpolations, Plots, Roots"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27ce9b30",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function K!(Kg, g, grid, beta, dudc, f, f_prime, shocks)\n",
" # This function requires the container of the output value as argument Kg\n",
"\n",
" # Construct linear interpolation object\n",
" g_func = LinearInterpolation(grid, g, extrapolation_bc = Line())\n",
"\n",
" # solve for updated consumption value\n",
" for (i, y) in enumerate(grid)\n",
" function h(c)\n",
" vals = dudc.(g_func.(f(y - c) * shocks)) .* f_prime(y - c) .* shocks\n",
" return dudc(c) - beta * mean(vals)\n",
" end\n",
" Kg[i] = find_zero(h, (1e-10, y - 1e-10))\n",
" end\n",
" return Kg\n",
"end\n",
"\n",
"# The following function does NOT require the container of the output value as argument\n",
"function K(g, grid, beta, dudc, f, f_prime, shocks)\n",
" K!(similar(g), g, grid, beta, dudc, f, f_prime, shocks)\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "b55a6ae3",
"metadata": {},
"source": [
"It has some similarities to the code for the Bellman operator in our [optimal growth lecture](https://julia.quantecon.org/optgrowth.html).\n",
"\n",
"For example, it evaluates integrals by Monte Carlo and approximates functions using linear interpolation.\n",
"\n",
"Here’s that Bellman operator code again, which needs to be executed because we’ll use it in some tests below"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0ed27a7",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"using Optim\n",
"\n",
"function T(w, grid, beta, u, f, shocks, Tw = similar(w);\n",
" compute_policy = false)\n",
"\n",
" # apply linear interpolation to w\n",
" w_func = LinearInterpolation(grid, w, extrapolation_bc = Line())\n",
"\n",
" if compute_policy\n",
" sigma = similar(w)\n",
" end\n",
"\n",
" # set Tw[i] = max_c { u(c) + beta E w(f(y - c) z)}\n",
" for (i, y) in enumerate(grid)\n",
" objective(c) = u(c) + beta * mean(w_func.(f(y - c) .* shocks))\n",
" res = maximize(objective, 1e-10, y)\n",
"\n",
" if compute_policy\n",
" sigma[i] = Optim.maximizer(res)\n",
" end\n",
" Tw[i] = Optim.maximum(res)\n",
" end\n",
"\n",
" if compute_policy\n",
" return Tw, sigma\n",
" else\n",
" return Tw\n",
" end\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "83031086",
"metadata": {},
"source": [
"### Testing on the Log / Cobb–Douglas case\n",
"\n",
"As we [did for value function iteration](https://julia.quantecon.org/optgrowth.html), let’s start by\n",
"testing our method in the presence of a model that does have an analytical\n",
"solution.\n",
"\n",
"Here’s an object containing data from the log-linear growth model we used in the [value function iteration lecture](https://julia.quantecon.org/optgrowth.html)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cf8cd896",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"isoelastic(c, gamma) = isone(gamma) ? log(c) : (c^(1 - gamma) - 1) / (1 - gamma)\n",
"function Model(; alpha = 0.65, # Productivity parameter\n",
" beta = 0.95, # Discount factor\n",
" gamma = 1.0, # Risk aversion\n",
" mu = 0.0, # First parameter in lognorm(mu, sigma)\n",
" s = 0.1, # Second parameter in lognorm(mu, sigma)\n",
" grid = range(1e-6, 4, length = 200), # Grid\n",
" grid_min = 1e-6, # Smallest grid point\n",
" grid_max = 4.0, # Largest grid point\n",
" grid_size = 200, # Number of grid points\n",
" u = (c, gamma = gamma) -> isoelastic(c, gamma), # utility function\n",
" dudc = c -> c^(-gamma), # u_prime\n",
" f = k -> k^alpha, # production function\n",
" f_prime = k -> alpha * k^(alpha - 1))\n",
" return (; alpha, beta, gamma, mu, s, grid, grid_min, grid_max, grid_size, u,\n",
" dudc, f, f_prime)\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "21ccce7d",
"metadata": {},
"source": [
"Next we generate an instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1422a6e",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"m = Model();"
]
},
{
"cell_type": "markdown",
"id": "1be5dbfe",
"metadata": {},
"source": [
"We also need some shock draws for Monte Carlo integration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc7e5f91",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"using Random\n",
"Random.seed!(42) # for reproducible results.\n",
"\n",
"shock_size = 250 # number of shock draws in Monte Carlo integral\n",
"shocks = collect(exp.(m.mu .+ m.s * randn(shock_size))); # generate shocks"
]
},
{
"cell_type": "markdown",
"id": "45396611",
"metadata": {},
"source": [
"As a preliminary test, let’s see if $ K c^* = c^* $, as implied by the\n",
"theory"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb498af3",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function verify_true_policy(m, shocks, c_star)\n",
" # compute (Kc_star)\n",
" (; grid, beta, dudc, f, f_prime) = m\n",
" c_star_new = K(c_star, grid, beta, dudc, f, f_prime, shocks)\n",
"\n",
" # plot c_star and Kc_star\n",
" plot(grid, c_star, label = L\"optimal policy $c^*$\")\n",
" plot!(grid, c_star_new, label = L\"Kc^*\")\n",
" plot!(legend = :topleft)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7cd4ff91",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"c_star = (1 - m.alpha * m.beta) * m.grid # true policy (c_star)\n",
"verify_true_policy(m, shocks, c_star)"
]
},
{
"cell_type": "markdown",
"id": "51ff6f64",
"metadata": {},
"source": [
"We can’t really distinguish the two plots, so we are looking good, at least\n",
"for this test.\n",
"\n",
"Next let’s try iterating from an arbitrary initial condition and see if we\n",
"converge towards $ c^* $.\n",
"\n",
"The initial condition we’ll use is the one that eats the whole pie: $ c(y) = y $"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f43ccc0",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function check_convergence(m, shocks, c_star, g_init; n_iter = 15)\n",
" (; grid, beta, dudc, f, f_prime) = m\n",
" g = g_init\n",
" plot(m.grid, g, lw = 2, alpha = 0.6, label = L\"intial condition $c(y) = y$\")\n",
" for i in 1:n_iter\n",
" new_g = K(g, grid, beta, dudc, f, f_prime, shocks)\n",
" g = new_g\n",
" plot!(grid, g, lw = 2, alpha = 0.6, label = \"\")\n",
" end\n",
" plot!(grid, c_star, color = :black, lw = 2, alpha = 0.8,\n",
" label = L\"true policy function $c^*$\")\n",
" plot!(legend = :topleft)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cccb49b4",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"check_convergence(m, shocks, c_star, m.grid, n_iter = 15)"
]
},
{
"cell_type": "markdown",
"id": "dc190d18",
"metadata": {},
"source": [
"We see that the policy has converged nicely, in only a few steps.\n",
"\n",
"Now let’s compare the accuracy of iteration using the Coleman and Bellman operators.\n",
"\n",
"We’ll generate\n",
"\n",
"1. $ K^n c $ where $ c(y) = y $ \n",
"1. $ (M \\circ T^n \\circ M^{-1}) c $ where $ c(y) = y $ \n",
"\n",
"\n",
"In each case we’ll compare the resulting policy to $ c^* $.\n",
"\n",
"The theory on equivalent dynamics says we will get the same policy function\n",
"and hence the same errors.\n",
"\n",
"But in fact we expect the first method to be more accurate for reasons\n",
"discussed above"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0bc6d7dc",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function iterate_updating(func, arg_init; sim_length = 20)\n",
" arg = arg_init\n",
" for i in 1:sim_length\n",
" new_arg = func(arg)\n",
" arg = new_arg\n",
" end\n",
" return arg\n",
"end\n",
"\n",
"function compare_error(m, shocks, g_init, w_init; sim_length = 20)\n",
" (; grid, beta, u, dudc, f, f_prime) = m\n",
" g, w = g_init, w_init\n",
"\n",
" # two functions for simplification\n",
" bellman_single_arg(w) = T(w, grid, beta, u, f, shocks)\n",
" coleman_single_arg(g) = K(g, grid, beta, dudc, f, f_prime, shocks)\n",
"\n",
" g = iterate_updating(coleman_single_arg, grid, sim_length = 20)\n",
" w = iterate_updating(bellman_single_arg, u.(grid), sim_length = 20)\n",
" new_w, vf_g = T(w, grid, beta, u, f, shocks, compute_policy = true)\n",
"\n",
" pf_error = c_star - g\n",
" vf_error = c_star - vf_g\n",
"\n",
" plot(grid, zero(grid), color = :black, lw = 1, label = \"\")\n",
" plot!(grid, pf_error, lw = 2, alpha = 0.6, label = \"policy iteration error\")\n",
" plot!(grid, vf_error, lw = 2, alpha = 0.6, label = \"value iteration error\")\n",
" plot!(legend = :bottomleft)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2e50135",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"compare_error(m, shocks, m.grid, m.u.(m.grid), sim_length = 20)"
]
},
{
"cell_type": "markdown",
"id": "b9f55e9e",
"metadata": {},
"source": [
"As you can see, time iteration is much more accurate for a given\n",
"number of iterations."
]
},
{
"cell_type": "markdown",
"id": "22379d48",
"metadata": {},
"source": [
"## Exercises"
]
},
{
"cell_type": "markdown",
"id": "07db22d3",
"metadata": {},
"source": [
"### Exercise 1\n",
"\n",
"Show that [(35.9)](#equation-cpi-ed-tk) is valid. In particular,\n",
"\n",
"- Let $ v $ be strictly concave and continuously differentiable on $ (0, \\infty) $ \n",
"- Fix $ y \\in (0, \\infty) $ and show that $ MTv(y) = KMv(y) $ "
]
},
{
"cell_type": "markdown",
"id": "17c1b955",
"metadata": {},
"source": [
"### Exercise 2\n",
"\n",
"Show that $ M $ is a bijection from $ \\mathscr V $ to $ \\mathscr P $."
]
},
{
"cell_type": "markdown",
"id": "8a9b8937",
"metadata": {},
"source": [
"### Exercise 3\n",
"\n",
"Consider the same model as above but with the CRRA utility function\n",
"\n",
"$$\n",
"u(c) = \\frac{c^{1 - \\gamma} - 1}{1 - \\gamma}\n",
"$$\n",
"\n",
"Iterate 20 times with Bellman iteration and Euler equation time iteration\n",
"\n",
"- start time iteration from $ c(y) = y $ \n",
"- start value function iteration from $ v(y) = u(y) $ \n",
"- set $ \\gamma = 1.5 $ \n",
"\n",
"\n",
"Compare the resulting policies and check that they are close."
]
},
{
"cell_type": "markdown",
"id": "8b3ffd7f",
"metadata": {},
"source": [
"### Exercise 4\n",
"\n",
"Do the same exercise, but now, rather than plotting results, benchmark both approaches with 20\n",
"iterations."
]
},
{
"cell_type": "markdown",
"id": "519a0eb4",
"metadata": {},
"source": [
"## Solutions"
]
},
{
"cell_type": "markdown",
"id": "7b3a2dbf",
"metadata": {},
"source": [
"### Solution to Exercise 1\n",
"\n",
"Let $ T, K, M, v $ and $ y $ be as stated in the exercise.\n",
"\n",
"Using the envelope theorem, one can show that $ (Tv)'(y) = u'(c(y)) $\n",
"where $ c(y) $ solves\n",
"\n",
"\n",
"\n",
"$$\n",
"u'(c(y))\n",
"= \\beta \\int v' (f(y - c(y)) z ) f'(y - c(y)) z \\phi(dz) \\tag{35.10}\n",
"$$\n",
"\n",
"Hence $ MTv(y) = (u')^{-1} (u'(c(y))) = c(y) $.\n",
"\n",
"On the other hand, $ KMv(y) $ is the $ c(y) $ that solves\n",
"\n",
"$$\n",
"\\begin{aligned}\n",
" u'(c(y))\n",
" & = \\beta \\int (u' \\circ (Mv)) (f(y - c(y)) z ) f'(y - c(y)) z \\phi(dz)\n",
" \\\\\n",
" & = \\beta \\int (u' \\circ ((u')^{-1} \\circ v'))\n",
" (f(y - c(y)) z ) f'(y - c(y)) z \\phi(dz)\n",
" \\\\\n",
" & = \\beta \\int v'(f(y - c(y)) z ) f'(y - c(y)) z \\phi(dz)\n",
"\\end{aligned}\n",
"$$\n",
"\n",
"We see that $ c(y) $ is the same in each case."
]
},
{
"cell_type": "markdown",
"id": "e7a103ce",
"metadata": {},
"source": [
"### Solution to Exercise 2\n",
"\n",
"We need to show that $ M $ is a bijection from $ \\mathscr V $ to $ \\mathscr P $.\n",
"\n",
"To see this, first observe that, in view of our assumptions above, $ u' $ is a strictly decreasing continuous bijection from $ (0,\\infty) $ to itself.\n",
"\n",
"It [follows](https://math.stackexchange.com/questions/672174/continuity-of-an-inverse-function) that $ h $ has the same properties.\n",
"\n",
"Moreover, for fixed $ v \\in \\mathscr V $, the derivative $ v' $ is\n",
"a continuous, strictly decreasing function.\n",
"\n",
"Hence, for fixed $ v \\in \\mathscr V $, the map $ M v = h \\circ v' $\n",
"is strictly increasing and continuous, taking values in $ (0, \\infty) $.\n",
"\n",
"Moreover, interiority holds because $ v' $ strictly dominates $ u' $, implying that\n",
"\n",
"$$\n",
"(M v)(y) = h(v'(y)) < h(u'(y)) = y\n",
"$$\n",
"\n",
"In particular, $ \\sigma(y) := (Mv)(y) $ is an element of $ \\mathscr\n",
"P $.\n",
"\n",
"To see that each $ \\sigma \\in \\mathscr P $ has a preimage $ v \\in \\mathscr V $ with $ Mv = \\sigma $, fix any $ \\sigma \\in \\mathscr P $.\n",
"\n",
"Let $ v(y) := \\int_0^y u'(\\sigma(x)) dx $ with $ v(0) = 0 $.\n",
"\n",
"With a small amount of effort you will be able to show that $ v \\in \\mathscr V $ and $ Mv = \\sigma $.\n",
"\n",
"It’s also true that $ M $ is one-to-one on $ \\mathscr V $.\n",
"\n",
"To see this, suppose that $ v $ and $ w $ are elements of $ \\mathscr V $\n",
"satisfying $ Mv = Mw $.\n",
"\n",
"Then $ v(0) = w(0) = 0 $ and $ v' = w' $ on $ (0, \\infty) $.\n",
"\n",
"The fundamental theorem of calculus then implies that $ v = w $ on $ \\mathbb R_+ $."
]
},
{
"cell_type": "markdown",
"id": "d40091f9",
"metadata": {},
"source": [
"### Solution to Exercise 3\n",
"\n",
"Here’s the code, which will execute if you’ve run all the code above"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af7e33a5",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"# Model instance with risk aversion = 1.5\n",
"# others are the same as the previous instance\n",
"m_ex = Model(gamma = 1.5);"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b7e7096",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function exercise2(m, shocks, g_init = m.grid, w_init = m.u.(m.grid);\n",
" sim_length = 20)\n",
" (; grid, beta, u, dudc, f, f_prime) = m\n",
" # initial policy and value\n",
" g, w = g_init, w_init\n",
" # iteration\n",
" bellman_single_arg(w) = T(w, grid, beta, u, f, shocks)\n",
" coleman_single_arg(g) = K(g, grid, beta, dudc, f, f_prime, shocks)\n",
"\n",
" g = iterate_updating(coleman_single_arg, grid, sim_length = 20)\n",
" w = iterate_updating(bellman_single_arg, u.(m.grid), sim_length = 20)\n",
" new_w, vf_g = T(w, grid, beta, u, f, shocks, compute_policy = true)\n",
"\n",
" plot(grid, g, lw = 2, alpha = 0.6, label = \"policy iteration\")\n",
" plot!(grid, vf_g, lw = 2, alpha = 0.6, label = \"value iteration\")\n",
" return plot!(legend = :topleft)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8816effb",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"exercise2(m_ex, shocks, m.grid, m.u.(m.grid), sim_length = 20)"
]
},
{
"cell_type": "markdown",
"id": "fe1c2b0a",
"metadata": {},
"source": [
"The policies are indeed close."
]
},
{
"cell_type": "markdown",
"id": "0e20f6c8",
"metadata": {},
"source": [
"### Solution to Exercise 4\n",
"\n",
"Here’s the code.\n",
"\n",
"It assumes that you’ve just run the code from the previous exercise"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b08cd4d",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"function bellman(m, shocks)\n",
" (; grid, beta, u, dudc, f, f_prime) = m\n",
" bellman_single_arg(w) = T(w, grid, beta, u, f, shocks)\n",
" iterate_updating(bellman_single_arg, u.(grid), sim_length = 20)\n",
"end\n",
"function coleman(m, shocks)\n",
" (; grid, beta, dudc, f, f_prime) = m\n",
" coleman_single_arg(g) = K(g, grid, beta, dudc, f, f_prime, shocks)\n",
" iterate_updating(coleman_single_arg, grid, sim_length = 20)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51d76965",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"@benchmark bellman(m_ex, shocks)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df3c1915",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"@benchmark bellman(m_ex, shocks)"
]
}
],
"metadata": {
"date": 1704478550.4024603,
"filename": "coleman_policy_iter.md",
"kernelspec": {
"display_name": "Julia",
"language": "julia",
"name": "julia-1.10"
},
"title": "Optimal Growth II: Time Iteration"
},
"nbformat": 4,
"nbformat_minor": 5
}