@@ -38,7 +38,7 @@ In this lecture, we introduce the core idea of **time iteration**: iterating on
3838a guess of the optimal policy using the Euler equation.
39394040This approach differs from the value function iteration we used in
41-{doc}`Cake Eating III <cake_eating_stochastic>`, where we iterated on the value function itself.
41+{doc}`cake_eating_stochastic`, where we iterated on the value function itself.
42424343Time iteration exploits the structure of the Euler equation to find the optimal
4444policy directly, rather than computing the value function as an intermediate step.
@@ -49,7 +49,7 @@ policy function, we can often solve problems faster than with value function ite
4949However, time iteration is not the most efficient Euler equation-based method
5050available.
515152-In {doc}`Cake Eating V <cake_eating_egm>`, we'll introduce the **endogenous
52+In {doc}`cake_eating_egm`, we'll introduce the **endogenous
5353grid method** (EGM), which provides an even more efficient way to solve the
5454problem.
5555@@ -68,9 +68,9 @@ from typing import NamedTuple, Callable
6868## The Euler Equation
69697070Our first step is to derive the Euler equation, which is a generalization of
71-the Euler equation we obtained in {doc}`Cake Eating I <cake_eating>`.
71+the Euler equation we obtained in {doc}`cake_eating`.
727273-We take the model set out in {doc}`Cake Eating III <cake_eating_stochastic>` and add the following assumptions:
73+We take the model set out in {doc}`cake_eating_stochastic` and add the following assumptions:
747475751. $u$ and $f$ are continuously differentiable and strictly concave
76761. $f(0) = 0$
@@ -120,12 +120,12 @@ v^*(x) = \max_{0 \leq k \leq x}
120120 \right\},
121121$$
122122123-Differentiating with respect to $x$, and then evaluating at the optimum yields {eq}`cpi_env`.
123+Differentiating with respect to $x$, and then evaluating at the optimum yields {eq}`cpi_env`.
124124125125(Section 12.1 of [EDTC](https://johnstachurski.net/edtc.html) contains full proofs of these results, and closely related discussions can be found in many other texts.)
126126127127Differentiability of the value function and interiority of the optimal policy
128-imply that optimal consumption satisfies the first order condition associated
128+imply that optimal consumption satisfies the first-order condition associated
129129with {eq}`cpi_fpb30`, which is
130130131131```{math}
@@ -179,7 +179,7 @@ that are continuous, strictly increasing and interior.
179179Henceforth we denote this set of policies by $\mathscr P$
1801801811811. The operator $K$ takes as its argument a $\sigma \in \mathscr P$ and
182-1. returns a new function $K\sigma$, where $K\sigma(x)$ is the $c \in (0, x)$ that solves.
182+1. returns a new function $K\sigma$, where $K\sigma(x)$ is the $c \in (0, x)$ that solves
183183184184```{math}
185185:label: cpi_coledef
@@ -194,7 +194,7 @@ We call this operator the **Coleman-Reffett operator** to acknowledge the work o
194194In essence, $K\sigma$ is the consumption policy that the Euler equation tells
195195you to choose today when your future consumption policy is $\sigma$.
196196197-The important thing to note about $K$ is that, by
197+The important thing to note about $K$ is that, by
198198construction, its fixed points coincide with solutions to the functional
199199equation {eq}`cpi_euler_func`.
200200@@ -237,28 +237,46 @@ whenever $\sigma \in \mathscr P$.
237237It is possible to prove that there is a tight relationship between iterates of
238238$K$ and iterates of the Bellman operator.
239239240-Mathematically, the two operators are **topologically conjugate**.
240+Mathematically, $T$ and $K$ are **topologically conjugate** under a translation
241+that involves differentiation in one direction and integration in the other.
241242242-Loosely speaking, this means that if iterates of one operator converge then
243+This conjugacy implies that if iterates of one operator converge then
243244so do iterates of the other, and vice versa.
244245245-Moreover, there is a sense in which they converge at the same rate, at least
246-in theory.
246+Moreover, there is a sense in which they converge *at the same rate*, at least in theory.
247247248-However, it turns out that the operator $K$ is more stable numerically
248+However, it turns out that the operator $K$ is more stable *numerically*
249249and hence more efficient in the applications we consider.
250250251+This is because
252+253+* $K$ exploits additional structure because it uses first-order conditions, and
254+* policies near the optimal policy have less curvature and hence are easier to
255+ approximate than value functions near the optimal value function.
256+251257Examples are given below.
252258259+253260## Implementation
254261255-As in {doc}`Cake Eating III <cake_eating_stochastic>`, we continue to assume that
262+Let's turn to implementation.
263+264+```{note}
265+In this lecture we mainly focus on the algorithm, favoring clarity over efficiency in the code.
266+267+In later lectures we will optimize both the algorithm and the code.
268+```
269+270+271+272+As in {doc}`cake_eating_stochastic`, we assume that
256273257274* $u(c) = \ln c$
258-* $f(k) = k^{\alpha}$
259-* $\phi$ is the distribution of $\xi := \exp(\mu + s \zeta)$ when $\zeta$ is standard normal
275+* $f(x-c) = (x-c)^{\alpha}$
276+* $\phi$ is the distribution of $\xi := \exp(\mu + \nu \zeta)$ when $\zeta$ is standard normal
260277261-This will allow us to compare our results to the analytical solutions
278+This allows us to compare our results to the analytical solutions we obtained in
279+that lecture:
262280263281```{code-cell} python3
264282def v_star(x, α, β, μ):
@@ -283,34 +301,36 @@ means iterating with the operator $K$.
283301284302For this we need access to the functions $u'$ and $f, f'$.
285303286-We use the same `Model` structure from {doc}`Cake Eating III <cake_eating_stochastic>`.
304+We use the same `Model` structure from {doc}`cake_eating_stochastic`.
287305288306```{code-cell} python3
289307class Model(NamedTuple):
290308 u: Callable # utility function
291309 f: Callable # production function
292310 β: float # discount factor
293311 μ: float # shock location parameter
294- s: float # shock scale parameter
312+ ν: float # shock scale parameter
295313 grid: np.ndarray # state grid
296314 shocks: np.ndarray # shock draws
297315 α: float = 0.4 # production function parameter
298316 u_prime: Callable = None # derivative of utility
299317 f_prime: Callable = None # derivative of production
300318301319302-def create_model(u: Callable,
303- f: Callable,
304- β: float = 0.96,
305- μ: float = 0.0,
306- s: float = 0.1,
307- grid_max: float = 4.0,
308- grid_size: int = 120,
309- shock_size: int = 250,
310- seed: int = 1234,
311- α: float = 0.4,
312- u_prime: Callable = None,
313- f_prime: Callable = None) -> Model:
320+def create_model(
321+ u: Callable,
322+ f: Callable,
323+ β: float = 0.96,
324+ μ: float = 0.0,
325+ ν: float = 0.1,
326+ grid_max: float = 4.0,
327+ grid_size: int = 120,
328+ shock_size: int = 250,
329+ seed: int = 1234,
330+ α: float = 0.4,
331+ u_prime: Callable = None,
332+ f_prime: Callable = None
333+ ) -> Model:
314334 """
315335 Creates an instance of the cake eating model.
316336 """
@@ -319,10 +339,9 @@ def create_model(u: Callable,
319339320340 # Store shocks (with a seed, so results are reproducible)
321341 np.random.seed(seed)
322- shocks = np.exp(μ + s * np.random.randn(shock_size))
342+ shocks = np.exp(μ + ν * np.random.randn(shock_size))
323343324- return Model(u=u, f=f, β=β, μ=μ, s=s, grid=grid, shocks=shocks,
325- α=α, u_prime=u_prime, f_prime=f_prime)
344+ return Model(u, f, β, μ, ν, grid, shocks, α, u_prime, f_prime)
326345```
327346328347Now we implement a method called `euler_diff`, which returns
@@ -341,14 +360,14 @@ def euler_diff(c: float, σ: np.ndarray, x: float, model: Model) -> float:
341360342361 """
343362344- β, shocks, grid = model.β, model.shocks, model.grid
345- f, f_prime, u_prime = model.f, model.f_prime, model.u_prime
363+ # Unpack
364+ u, f, β, μ, ν, grid, shocks, α, u_prime, f_prime = model
346365347- # First turn σ into a function via interpolation
366+ # Turn σ into a function via interpolation
348367 σ_func = lambda x: np.interp(x, grid, σ)
349368350369 # Now set up the function we need to find the root of.
351- vals = u_prime(σ_func(f(x - c) * shocks)) * f_prime(x - c) * shocks
370+ vals = u_prime(σ_func(f(x - c, α) * shocks)) * f_prime(x - c, α) * shocks
352371 return u_prime(c) - β * np.mean(vals)
353372```
354373@@ -365,12 +384,10 @@ def K(σ: np.ndarray, model: Model) -> np.ndarray:
365384 """
366385 The Coleman-Reffett operator
367386368- Here model is an instance of Model.
369387 """
370388371- β = model.β
372- f, f_prime, u_prime = model.f, model.f_prime, model.u_prime
373- grid, shocks = model.grid, model.shocks
389+ # Unpack
390+ u, f, β, μ, ν, grid, shocks, α, u_prime, f_prime = model
374391375392 σ_new = np.empty_like(σ)
376393 for i, x in enumerate(grid):
@@ -390,8 +407,8 @@ Let's generate an instance and plot some iterates of $K$, starting from $σ(x) =
390407α = 0.4
391408u = lambda c: np.log(c)
392409u_prime = lambda c: 1 / c
393-f = lambda k: k**α
394-f_prime = lambda k: α * k**(α - 1)
410+f = lambda k, α: k**α
411+f_prime = lambda k, α: α * k**(α - 1)
395412396413model = create_model(u=u, f=f, α=α, u_prime=u_prime, f_prime=f_prime)
397414grid = model.grid
@@ -417,20 +434,24 @@ plt.show()
417434```
418435419436We see that the iteration process converges quickly to a limit
420-that resembles the solution we obtained in {doc}`Cake Eating III <cake_eating_stochastic>`.
437+that resembles the solution we obtained in {doc}`cake_eating_stochastic`.
421438422439Here is a function called `solve_model_time_iter` that takes an instance of
423440`Model` and returns an approximation to the optimal policy,
424441using time iteration.
425442443+426444```{code-cell} python3
427-def solve_model_time_iter(model: Model,
428- σ_init: np.ndarray,
429- tol: float = 1e-5,
430- max_iter: int = 1000,
431- verbose: bool = True) -> np.ndarray:
445+def solve_model_time_iter(
446+ model: Model,
447+ σ_init: np.ndarray,
448+ tol: float = 1e-5,
449+ max_iter: int = 1000,
450+ verbose: bool = True
451+ ) -> np.ndarray:
432452 """
433453 Solve the model using time iteration.
454+434455 """
435456 σ = σ_init
436457 error = tol + 1
@@ -453,19 +474,25 @@ def solve_model_time_iter(model: Model,
453474Let's call it:
454475455476```{code-cell} python3
456-σ_init = np.copy(model.grid)
477+# Unpack
478+grid = model.grid
479+480+σ_init = np.copy(grid)
457481σ = solve_model_time_iter(model, σ_init)
458482```
459483460484Here is a plot of the resulting policy, compared with the true policy:
461485462486```{code-cell} python3
487+# Unpack
488+grid, α, β = model.grid, model.α, model.β
489+463490fig, ax = plt.subplots()
464491465-ax.plot(model.grid, σ, lw=2,
492+ax.plot(grid, σ, lw=2,
466493 alpha=0.8, label='approximate policy function')
467494468-ax.plot(model.grid, σ_star(model.grid, model.α, model.β), 'k--',
495+ax.plot(grid, σ_star(grid, α, β), 'k--',
469496 lw=2, alpha=0.8, label='true policy function')
470497471498ax.legend()
@@ -477,12 +504,15 @@ Again, the fit is excellent.
477504The maximal absolute deviation between the two policies is
478505479506```{code-cell} python3
480-np.max(np.abs(σ - σ_star(model.grid, model.α, model.β)))
507+# Unpack
508+grid, α, β = model.grid, model.α, model.β
509+510+np.max(np.abs(σ - σ_star(grid, α, β)))
481511```
482512483513Time iteration runs faster than value function iteration, as discussed in {doc}`cake_eating_stochastic`.
484514485-This is because time iteration exploits differentiability and the first order conditions, while value function iteration does not use this available structure.
515+This is because time iteration exploits differentiability and the first-order conditions, while value function iteration does not use this available structure.
486516487517At the same time, there is a variation of time iteration that runs even faster.
488518@@ -519,7 +549,7 @@ def u_crra(c):
519549def u_prime_crra(c):
520550 return c**(-γ)
521551522-# Use same production function as before
552+# Use the same production function as before
523553model_crra = create_model(u=u_crra, f=f, α=α,
524554 u_prime=u_prime_crra, f_prime=f_prime)
525555```
@@ -528,13 +558,16 @@ Now we solve and plot the policy:
528558529559```{code-cell} python3
530560%%time
531-σ_init = np.copy(model_crra.grid)
561+# Unpack
562+grid = model_crra.grid
563+564+σ_init = np.copy(grid)
532565σ = solve_model_time_iter(model_crra, σ_init)
533566534567535568fig, ax = plt.subplots()
536569537-ax.plot(model_crra.grid, σ, lw=2,
570+ax.plot(grid, σ, lw=2,
538571 alpha=0.8, label='approximate policy function')
539572540573ax.legend()