@@ -41,19 +41,19 @@ The original reference is {cite}`Carroll2006`.
4141
4242Let's start with some standard imports:
4343
44- ``` {code-cell} ipython
44+ ``` {code-cell} python3
4545import matplotlib.pyplot as plt
4646import numpy as np
4747import quantecon as qe
4848```
4949
5050## Key Idea
5151
52- Let's start by reminding ourselves of the theory and then see how the numerics fit in .
52+ First we remind ourselves of the theory and then we turn to numerical methods .
5353
5454### Theory
5555
56- Take the model set out in {doc}` Cake Eating IV < cake_eating_time_iter> ` , following the same terminology and notation.
56+ We work with the model set out in {doc}` cake_eating_time_iter ` , following the same terminology and notation.
5757
5858The Euler equation is
5959
@@ -79,24 +79,27 @@ u'(c)
7979
8080### Exogenous Grid
8181
82- As discussed in {doc}` Cake Eating IV <cake_eating_time_iter> ` , to implement the method on a computer, we need a numerical approximation.
82+ As discussed in {doc}` cake_eating_time_iter ` , to implement the method on a
83+ computer, we need numerical approximation.
8384
8485In particular, we represent a policy function by a set of values on a finite grid.
8586
86- The function itself is reconstructed from this representation when necessary, using interpolation or some other method.
87+ The function itself is reconstructed from this representation when necessary,
88+ using interpolation or some other method.
8789
88- {doc}` Previously <cake_eating_time_iter>` , to obtain a finite representation of an updated consumption policy, we
90+ Our {doc}` previous strategy <cake_eating_time_iter>` for obtaining a finite representation of an updated consumption policy was to
8991
90- * fixed a grid of income points $\{ x_i\} $
91- * calculated the consumption value $c_i$ corresponding to each
92- $x_i$ using {eq}` egm_coledef ` and a root-finding routine
92+ * fix a grid of income points $\{ x_i\} $
93+ * calculate the consumption value $c_i$ corresponding to each $x_i$ using
94+ {eq}` egm_coledef ` and a root-finding routine
9395
9496Each $c_i$ is then interpreted as the value of the function $K \sigma$ at $x_i$.
9597
96- Thus, with the points $\{ x_i, c_i\} $ in hand, we can reconstruct $K \sigma$ via approximation.
98+ Thus, with the pairs $\{ ( x_i, c_i) \} $ in hand, we can reconstruct $K \sigma$ via approximation.
9799
98100Iteration then continues...
99101
102+
100103### Endogenous Grid
101104
102105The method discussed above requires a root-finding routine to find the
@@ -105,7 +108,7 @@ $c_i$ corresponding to a given income value $x_i$.
105108Root-finding is costly because it typically involves a significant number of
106109function evaluations.
107110
108- As pointed out by Carroll {cite}` Carroll2006 ` , we can avoid this if
111+ As pointed out by Carroll {cite}` Carroll2006 ` , we can avoid this step if
109112$x_i$ is chosen endogenously.
110113
111114The only assumption required is that $u'$ is invertible on $(0, \infty)$.
@@ -114,7 +117,7 @@ Let $(u')^{-1}$ be the inverse function of $u'$.
114117
115118The idea is this:
116119
117- * First, we fix an * exogenous* grid $\{ k_i \} $ for capital ($k = x - c$).
120+ * First, we fix an * exogenous* grid $\{ s_i \} $ for savings ($s = x - c$).
118121* Then we obtain $c_i$ via
119122
120123``` {math}
@@ -123,28 +126,28 @@ The idea is this:
123126c_i =
124127(u')^{-1}
125128\left\{
126- \beta \int (u' \circ \sigma) (f(k_i ) z ) \, f'(k_i ) \, z \, \phi(dz)
129+ \beta \int (u' \circ \sigma) (f(s_i ) z ) \, f'(s_i ) \, z \, \phi(dz)
127130\right\}
128131```
129132
130- * Finally, for each $c_i$ we set $x_i = c_i + k_i $.
133+ * Finally, for each $c_i$ we set $x_i = c_i + s_i $.
131134
132- It is clear that each $(x_i, c_i)$ pair constructed in this manner satisfies {eq}` egm_coledef ` .
135+ Importantly, each $(x_i, c_i)$ pair constructed in this manner satisfies {eq}` egm_coledef ` .
133136
134137With the points $\{ x_i, c_i\} $ in hand, we can reconstruct $K \sigma$ via approximation as before.
135138
136- The name EGM comes from the fact that the grid $\{ x_i\} $ is determined ** endogenously** .
139+ The name EGM comes from the fact that the grid $\{ x_i\} $ is determined ** endogenously** .
140+
137141
138142## Implementation
139143
140- As in {doc}` Cake Eating IV <cake_eating_time_iter> ` , we will start with a simple setting
141- where
144+ As in {doc}` cake_eating_time_iter ` , we will start with a simple setting where
142145
143146* $u(c) = \ln c$,
144- * production is Cobb-Douglas, and
147+ * the function $f$ has a Cobb-Douglas specification , and
145148* the shocks are lognormal.
146149
147- This will allow us to make comparisons with the analytical solutions
150+ This will allow us to make comparisons with the analytical solutions.
148151
149152``` {code-cell} python3
150153def v_star(x, α, β, μ):
@@ -164,7 +167,7 @@ def σ_star(x, α, β):
164167 return (1 - α * β) * x
165168```
166169
167- We reuse the ` Model ` structure from {doc}` Cake Eating IV < cake_eating_time_iter> ` .
170+ We reuse the ` Model ` structure from {doc}` cake_eating_time_iter ` .
168171
169172``` {code-cell} python3
170173from typing import NamedTuple, Callable
@@ -174,8 +177,8 @@ class Model(NamedTuple):
174177 f: Callable # production function
175178 β: float # discount factor
176179 μ: float # shock location parameter
177- s : float # shock scale parameter
178- grid : np.ndarray # state grid
180+ ν : float # shock scale parameter
181+ s_grid : np.ndarray # exogenous savings grid
179182 shocks: np.ndarray # shock draws
180183 α: float # production function parameter
181184 u_prime: Callable # derivative of utility
@@ -187,7 +190,7 @@ def create_model(u: Callable,
187190 f: Callable,
188191 β: float = 0.96,
189192 μ: float = 0.0,
190- s : float = 0.1,
193+ ν : float = 0.1,
191194 grid_max: float = 4.0,
192195 grid_size: int = 120,
193196 shock_size: int = 250,
@@ -199,53 +202,59 @@ def create_model(u: Callable,
199202 """
200203 Creates an instance of the cake eating model.
201204 """
202- # Set up grid
203- grid = np.linspace(1e-4, grid_max, grid_size)
205+ # Set up exogenous savings grid
206+ s_grid = np.linspace(1e-4, grid_max, grid_size)
204207
205208 # Store shocks (with a seed, so results are reproducible)
206209 np.random.seed(seed)
207- shocks = np.exp(μ + s * np.random.randn(shock_size))
210+ shocks = np.exp(μ + ν * np.random.randn(shock_size))
208211
209- return Model(u=u, f=f, β=β, μ=μ, s=s, grid=grid, shocks=shocks,
210- α=α, u_prime=u_prime, f_prime=f_prime, u_prime_inv=u_prime_inv)
212+ return Model(u, f, β, μ, ν, s_grid, shocks, α, u_prime, f_prime, u_prime_inv)
211213```
212214
213215### The Operator
214216
215217Here's an implementation of $K$ using EGM as described above.
216218
217219``` {code-cell} python3
218- def K(σ_array: np.ndarray, model: Model) -> np.ndarray:
220+ def K(
221+ c_in: np.ndarray, # Consumption values on the endogenous grid
222+ x_in: np.ndarray, # Current endogenous grid
223+ model: Model # Model specification
224+ ):
219225 """
220- The Coleman-Reffett operator using EGM
226+ An implementation of the Coleman-Reffett operator using EGM.
221227
222228 """
223229
224230 # Simplify names
225- f, β = model.f, model.β
226- f_prime, u_prime = model.f_prime, model.u_prime
227- u_prime_inv = model.u_prime_inv
228- grid, shocks = model.grid, model.shocks
231+ u, f, β, μ, ν, s_grid, shocks, α, u_prime, f_prime, u_prime_inv = model
229232
230- # Determine endogenous grid
231- x = grid + σ_array # x_i = k_i + c_i
232-
233- # Linear interpolation of policy using endogenous grid
234- σ = lambda x_val: np.interp(x_val, x, σ_array)
233+ # Linear interpolation of policy on the endogenous grid
234+ σ = lambda x: np.interp(x, x_in, c_in)
235235
236236 # Allocate memory for new consumption array
237- c = np.empty_like(grid )
237+ c_out = np.empty_like(s_grid )
238238
239239 # Solve for updated consumption value
240- for i, k in enumerate(grid):
241- vals = u_prime(σ(f(k) * shocks)) * f_prime(k) * shocks
242- c[i] = u_prime_inv(β * np.mean(vals))
240+ for i, s in enumerate(s_grid):
241+ vals = u_prime(σ(f(s) * shocks)) * f_prime(s) * shocks
242+ c_out[i] = u_prime_inv(β * np.mean(vals))
243+
244+ # Determine corresponding endogenous grid
245+ x_out = s_grid + c_out # x_i = s_i + c_i
243246
244- return c
247+ return c_out, x_out
245248```
246249
247250Note the lack of any root-finding algorithm.
248251
252+ ``` {note}
253+ The routine is still not particularly fast because we are using pure Python loops.
254+
255+ But in the next lecture ({doc}`cake_eating_egm_jax`) we will use a fully vectorized and efficient solution.
256+ ```
257+
249258### Testing
250259
251260First we create an instance.
@@ -261,53 +270,53 @@ f_prime = lambda k: α * k**(α - 1)
261270
262271model = create_model(u=u, f=f, α=α, u_prime=u_prime,
263272 f_prime=f_prime, u_prime_inv=u_prime_inv)
264- grid = model.grid
273+ s_grid = model.s_grid
265274```
266275
267276Here's our solver routine:
268277
269278``` {code-cell} python3
270279def solve_model_time_iter(model: Model,
271- σ_init: np.ndarray,
280+ c_init: np.ndarray,
281+ x_init: np.ndarray,
272282 tol: float = 1e-5,
273283 max_iter: int = 1000,
274- verbose: bool = True) -> np.ndarray :
284+ verbose: bool = True):
275285 """
276286 Solve the model using time iteration with EGM.
277287 """
278- σ = σ_init
288+ c, x = c_init, x_init
279289 error = tol + 1
280290 i = 0
281291
282292 while error > tol and i < max_iter:
283- σ_new = K(σ , model)
284- error = np.max(np.abs(σ_new - σ ))
285- σ = σ_new
293+ c_new, x_new = K(c, x , model)
294+ error = np.max(np.abs(c_new - c ))
295+ c, x = c_new, x_new
286296 i += 1
287297 if verbose:
288298 print(f"Iteration {i}, error = {error}")
289299
290300 if i == max_iter:
291301 print("Warning: maximum iterations reached")
292302
293- return σ
303+ return c, x
294304```
295305
296306Let's call it:
297307
298308``` {code-cell} python3
299- σ_init = np.copy(grid)
300- σ = solve_model_time_iter(model, σ_init)
309+ c_init = np.copy(s_grid)
310+ x_init = s_grid + c_init
311+ c, x = solve_model_time_iter(model, c_init, x_init)
301312```
302313
303314Here is a plot of the resulting policy, compared with the true policy:
304315
305316``` {code-cell} python3
306- x = grid + σ # x_i = k_i + c_i
307-
308317fig, ax = plt.subplots()
309318
310- ax.plot(x, σ , lw=2,
319+ ax.plot(x, c , lw=2,
311320 alpha=0.8, label='approximate policy function')
312321
313322ax.plot(x, σ_star(x, model.α, model.β), 'k--',
@@ -320,16 +329,22 @@ plt.show()
320329The maximal absolute deviation between the two policies is
321330
322331``` {code-cell} python3
323- np.max(np.abs(σ - σ_star(x, model.α, model.β)))
332+ np.max(np.abs(c - σ_star(x, model.α, model.β)))
324333```
325334
326335Here's the execution time:
327336
328337``` {code-cell} python3
329338with qe.Timer():
330- σ = solve_model_time_iter(model, σ_init , verbose=False)
339+ c, x = solve_model_time_iter(model, c_init, x_init , verbose=False)
331340```
332341
333342EGM is faster than time iteration because it avoids numerical root-finding.
334343
335344Instead, we invert the marginal utility function directly, which is much more efficient.
345+
346+ In the {doc}` next lecture <cake_eating_egm_jax> ` , we will use a fully vectorized
347+ and efficient version of EGM that is also parallelized using JAX.
348+
349+ This provides an extremely fast way to solve the optimal consumption problem we
350+ have been studying for the last few lectures.
0 commit comments