Skip to content

Commit 39e1eaa

Browse files
jstacclaude
andauthored
Improve cake eating lecture clarity and mathematical notation (#727)
* Improve cake eating lecture clarity and mathematical notation - Simplify language in introductory paragraphs - Add formal note on intertemporal elasticity of substitution (IES = 1/γ) - Improve mathematical notation with explicit constraints in argmax - Enhance formatting of multi-line explanations - Clarify definitions and conditions throughout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Improve cake eating numerical lecture code quality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * misc * misc --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4d82554 commit 39e1eaa

File tree

2 files changed

+90
-56
lines changed

2 files changed

+90
-56
lines changed

lectures/cake_eating.md

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ In this lecture we introduce a simple "cake eating" problem.
2222
The intertemporal problem is: how much to enjoy today and how much to leave
2323
for the future?
2424

25-
Although the topic sounds trivial, this kind of trade-off between current
26-
and future utility is at the heart of many savings and consumption problems.
25+
This trade-off between current and future rewards is at the heart of many savings and consumption problems.
2726

28-
Once we master the ideas in this simple environment, we will apply them to
29-
progressively more challenging---and useful---problems.
27+
Once we master the ideas in a simple environment, we will apply them to
28+
progressively more challenging problems.
3029

3130
The main tool we will use to solve the cake eating problem is dynamic programming.
3231

@@ -105,7 +104,7 @@ subject to
105104

106105
for all $t$.
107106

108-
A consumption path $\{c_t\}$ satisfying {eq}`cake_feasible` where $x_0 = \bar x$ is called **feasible**.
107+
A consumption path $\{c_t\}$ satisfying {eq}`cake_feasible` and $x_0 = \bar x$ is called **feasible**.
109108

110109
In this problem, the following terminology is standard:
111110

@@ -131,10 +130,19 @@ The reasoning given above suggests that the discount factor $\beta$ and the curv
131130

132131
Here's an educated guess as to what impact these parameters will have.
133132

134-
1. Higher $\beta$ implies less discounting, and hence the agent is more patient, which should reduce the rate of consumption.
135-
2. Higher $\gamma$ implies that marginal utility $u'(c) = c^{-\gamma}$ falls faster with $c$.
133+
1. Higher $\beta$ implies less discounting, and hence the agent is more patient,
134+
which should reduce the rate of consumption.
135+
2. Higher $\gamma$ implies more curvature in $u$,
136+
more desire for consumption smoothing, and hence a lower rate of consumption.
136137

137-
This suggests more smoothing, and hence a lower rate of consumption.
138+
```{note}
139+
More formally, higher $\gamma$ implies a lower intertemporal elasticity of substitution, since
140+
IES = $1/\gamma$.
141+
142+
This means the consumer is less willing to substitute consumption between periods.
143+
144+
This stronger preference for consumption smoothing results in a lower consumption rate.
145+
```
138146

139147
In summary, we expect the rate of consumption to be decreasing in both parameters.
140148

@@ -256,16 +264,16 @@ Now that we have the value function, it is straightforward to calculate the opti
256264
We should choose consumption to maximize the right hand side of the Bellman equation {eq}`bellman-cep`.
257265

258266
$$
259-
c^* = \arg \max_{c} \{u(c) + \beta v(x - c)\}
267+
c^* = \argmax_{0 \leq c \leq x} \{u(c) + \beta v(x - c)\}
260268
$$
261269

262-
We can think of this optimal choice as a function of the state $x$, in which case we call it the **optimal policy**.
270+
We can think of this optimal choice as a *function* of the state $x$, in which case we call it the **optimal policy**.
263271

264272
We denote the optimal policy by $\sigma^*$, so that
265273

266274
$$
267275
\sigma^*(x) := \arg \max_{c} \{u(c) + \beta v(x - c)\}
268-
\quad \text{for all } x
276+
\quad \text{for all } \; x \geq 0
269277
$$
270278

271279
If we plug the analytical expression {eq}`crra_vstar` for the value function
@@ -330,7 +338,7 @@ The Euler equation for the present problem can be stated as
330338
u^{\prime} (c^*_{t})=\beta u^{\prime}(c^*_{t+1})
331339
```
332340

333-
This is a necessary condition for the optimal path.
341+
This is a necessary condition for an optimal consumption path $\{c^*_t\}_{t \geq 0}$.
334342

335343
It says that, along the optimal path, marginal rewards are equalized across time, after appropriate discounting.
336344

@@ -342,8 +350,7 @@ We can also state the Euler equation in terms of the policy function.
342350
A **feasible consumption policy** is a map $x \mapsto \sigma(x)$
343351
satisfying $0 \leq \sigma(x) \leq x$.
344352

345-
The last restriction says that we cannot consume more than the remaining
346-
quantity of cake.
353+
(The last restriction says that we cannot consume more than the remaining quantity of cake.)
347354

348355
A feasible consumption policy $\sigma$ is said to **satisfy the Euler equation** if, for
349356
all $x > 0$,

lectures/cake_eating_numerical.md

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Let's put these algorithm and code optimizations to one side for now.
4545

4646
We will use the following imports:
4747

48-
```{code-cell} ipython
48+
```{code-cell} python3
4949
import matplotlib.pyplot as plt
5050
import numpy as np
5151
from scipy.optimize import minimize_scalar, bisect
@@ -142,14 +142,16 @@ The process looks like this:
142142
1. Begin with an array of values $\{ v_0, \ldots, v_I \}$ representing
143143
the values of some initial function $v$ on the grid points $\{ x_0, \ldots, x_I \}$.
144144
1. Build a function $\hat v$ on the state space $\mathbb R_+$ by
145-
linear interpolation, based on these data points.
146-
1. Obtain and record the value $T \hat v(x_i)$ on each grid point
147-
$x_i$ by repeatedly solving the maximization problem in the Bellman
148-
equation.
145+
interpolation, based on the interpolation points $\{(x_i, v_i)\}$.
146+
1. Insert $\hat v$ into the right hand side of the Bellman equation and
147+
obtain and record the value $T \hat v(x_i)$ on each grid point
148+
$x_i$.
149149
1. Unless some stopping condition is satisfied, set
150150
$\{ v_0, \ldots, v_I \} = \{ T \hat v(x_0), \ldots, T \hat v(x_I) \}$ and go to step 2.
151151

152-
In step 2 we'll use continuous piecewise linear interpolation.
152+
In step 2 we'll use piecewise linear interpolation.
153+
154+
153155

154156
### Implementation
155157

@@ -177,26 +179,31 @@ def maximize(g, a, b, args):
177179
We'll store the parameters $\beta$ and $\gamma$ and the grid in a
178180
`NamedTuple` called `Model`.
179181

182+
We'll also create a helper function called `create_cake_eating_model` to store
183+
default parameters and build an instance of `Model`.
184+
180185
```{code-cell} python3
181-
# Create model data structure
182186
class Model(NamedTuple):
183187
β: float
184188
γ: float
185189
x_grid: np.ndarray
186190
187-
def create_cake_eating_model(β=0.96, # discount factor
188-
γ=1.5, # degree of relative risk aversion
189-
x_grid_min=1e-3, # exclude zero for numerical stability
190-
x_grid_max=2.5, # size of cake
191-
x_grid_size=120):
191+
def create_cake_eating_model(
192+
β: float = 0.96, # discount factor
193+
γ: float = 1.5, # degree of relative risk aversion
194+
x_grid_min: float = 1e-3, # exclude zero for numerical stability
195+
x_grid_max: float = 2.5, # size of cake
196+
x_grid_size: int = 120
197+
):
192198
"""
193199
Creates an instance of the cake eating model.
200+
194201
"""
195202
x_grid = np.linspace(x_grid_min, x_grid_max, x_grid_size)
196-
return Model(β, γ=γ, x_grid=x_grid)
203+
return Model(β, γ, x_grid)
197204
```
198205

199-
Now we define utility functions that operate on the model:
206+
Here's the CRRA utility function.
200207

201208
```{code-cell} python3
202209
def u(c, γ):
@@ -207,33 +214,42 @@ def u(c, γ):
207214
return np.log(c)
208215
else:
209216
return (c ** (1 - γ)) / (1 - γ)
217+
```
210218

211-
def u_prime(c, γ):
212-
"""
213-
First derivative of utility function.
214-
"""
215-
return c ** (-γ)
219+
The next function is the unmaximized right hand side of the Bellman equation.
220+
221+
The array `v` is the current guess of $v$, stored as an array on the grid
222+
points.
216223

217-
def state_action_value(c, x, v_array, model):
224+
```{code-cell} python3
225+
def state_action_value(
226+
c: float, # current consumption
227+
x: float, # the current state (remaining cake)
228+
v: np.ndarray, # current guess of the value function
229+
model: Model # instance of cake eating model
230+
):
218231
"""
219232
Right hand side of the Bellman equation given x and c.
233+
220234
"""
235+
# Unpack
221236
β, γ, x_grid = model.β, model.γ, model.x_grid
222-
v = lambda x: np.interp(x, x_grid, v_array)
223-
224-
return u(c, γ) + β * v(x - c)
237+
# Convert array into function
238+
vf = lambda x: np.interp(x, x_grid, v)
239+
# Return unmaximmized RHS of Bellman equation
240+
return u(c, γ) + β * vf(x - c)
225241
```
226242

227243
We now define the Bellman operation:
228244

229245
```{code-cell} python3
230-
def T(v, model):
246+
def T(
247+
v: np.ndarray, # current guess of the value function
248+
model: Model # instance of cake eating model
249+
):
231250
"""
232251
The Bellman operator. Updates the guess of the value function.
233252
234-
* model is an instance of Model
235-
* v is an array representing a guess of the value function
236-
237253
"""
238254
v_new = np.empty_like(v)
239255
@@ -261,34 +277,42 @@ $x$ grid point.
261277
x_grid = model.x_grid
262278
v = u(x_grid, model.γ) # Initial guess
263279
n = 12 # Number of iterations
264-
265280
fig, ax = plt.subplots()
266281
282+
# Initial plot
267283
ax.plot(x_grid, v, color=plt.cm.jet(0),
268284
lw=2, alpha=0.6, label='Initial guess')
269285
286+
# Iterate
270287
for i in range(n):
271288
v = T(v, model) # Apply the Bellman operator
272289
ax.plot(x_grid, v, color=plt.cm.jet(i / n), lw=2, alpha=0.6)
273290
291+
# One last update and plot
292+
v = T(v, model)
293+
ax.plot(x_grid, v, color=plt.cm.jet(0),
294+
lw=2, alpha=0.6, label='Final guess')
295+
274296
ax.legend()
275297
ax.set_ylabel('value', fontsize=12)
276298
ax.set_xlabel('cake size $x$', fontsize=12)
277299
ax.set_title('Value function iterations')
278-
279300
plt.show()
280301
```
281302

282-
To do this more systematically, we introduce a wrapper function called
283-
`compute_value_function` that iterates until some convergence conditions are
284-
satisfied.
303+
To iterate more systematically, we introduce a wrapper function called
304+
`compute_value_function`.
305+
306+
It's task is to iterate using $T$ until some convergence conditions are satisfied.
285307

286308
```{code-cell} python3
287-
def compute_value_function(model,
288-
tol=1e-4,
289-
max_iter=1000,
290-
verbose=True,
291-
print_skip=25):
309+
def compute_value_function(
310+
model: Model,
311+
tol: float = 1e-4,
312+
max_iter: int = 1_000,
313+
verbose: bool = True,
314+
print_skip: int = 25
315+
):
292316
293317
# Set up loop
294318
v = np.zeros(len(model.x_grid)) # Initial guess
@@ -367,6 +391,9 @@ working with policy functions.
367391
We will see that value function iteration can be avoided by iterating on a guess
368392
of the policy function instead.
369393
394+
The policy function has less curvature and hence is easier to interpolate than
395+
the value function.
396+
370397
These ideas will be explored over the next few lectures.
371398
```
372399

@@ -398,14 +425,14 @@ above.
398425
Here's the function:
399426

400427
```{code-cell} python3
401-
def σ(model, v):
428+
def σ(
429+
v: np.ndarray, # current guess of the value function
430+
model: Model # instance of cake eating model
431+
):
402432
"""
403433
The optimal policy function. Given the value function,
404434
it finds optimal consumption in each state.
405435
406-
* model is an instance of Model
407-
* v is a value function array
408-
409436
"""
410437
c = np.empty_like(v)
411438
@@ -420,7 +447,7 @@ def σ(model, v):
420447
Now let's pass the approximate value function and compute optimal consumption:
421448

422449
```{code-cell} python3
423-
c = σ(model, v)
450+
c = σ(v, model)
424451
```
425452

426453
(pol_an)=

0 commit comments

Comments
 (0)