Skip to content

Commit 7c0a00b

Browse files
jstacclaude
andcommitted
Improve code explanations in ifp_advanced lecture
- Add bridging text connecting mathematical equations to code implementation - Add detailed code walkthrough for Coleman-Reffett operator - Add explanation of solver function and convergence - Add economic interpretation of default parameters - Expand interpretation of consumption policy results - Fix grammatical errors (comma splice, missing period) - Rename variables for clarity: a_in→ae_vals, σ_in→c_vals, a_out→ae_out, σ_out→c_out 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a532c78 commit 7c0a00b

File tree

1 file changed

+60
-27
lines changed

1 file changed

+60
-27
lines changed

lectures/ifp_advanced.md

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ does not grow too quickly.
127127

128128
When $\{R_t\}$ was constant we required that $\beta R < 1$.
129129

130-
Now it is stochastic, we require that
130+
Since it is now stochastic, we require that
131131

132132
```{math}
133133
:label: fpbc2
@@ -139,7 +139,7 @@ G_R := \lim_{n \to \infty}
139139
```
140140

141141
Notice that, when $\{R_t\}$ takes some constant value $R$, this
142-
reduces to the previous restriction $\beta R < 1$
142+
reduces to the previous restriction $\beta R < 1$.
143143

144144
The value $G_R$ can be thought of as the long run (geometric) average
145145
gross rate of return.
@@ -414,16 +414,26 @@ class IFP:
414414

415415
Here's the Coleman-Reffett operator based on EGM:
416416

417+
### Implementation Details
418+
419+
The implementation of operator $K$ maps directly to equation {eq}`k_opr`.
420+
421+
The left side $u'(\xi)$ becomes `u_prime_inv(β * Ez)` after solving for $\xi$.
422+
423+
The expectation term $\mathbb E_z \hat{R} (u' \circ \sigma)[\hat{R}(a - \xi) + \hat{Y}, \hat{Z}]$ is computed via Monte Carlo averaging over future states and shocks.
424+
425+
The max with $u'(a)$ is handled implicitly—the endogenous grid method naturally handles the liquidity constraint since we only solve for interior consumption where $c < a$.
426+
417427
```{code-cell} ipython
418428
@jit
419-
def K(a_in, σ_in, ifp):
429+
def K(ae_vals, c_vals, ifp):
420430
"""
421431
The Coleman--Reffett operator for the income fluctuation problem,
422432
using the endogenous grid method.
423433
424434
* ifp is an instance of IFP
425-
* a_in[i, z] is an asset grid
426-
* σ_in[i, z] is consumption at a_in[i, z]
435+
* ae_vals[i, z] is an asset grid
436+
* c_vals[i, z] is consumption at ae_vals[i, z]
427437
"""
428438
429439
# Simplify names
@@ -433,12 +443,12 @@ def K(a_in, σ_in, ifp):
433443
n = len(P)
434444
435445
# Create consumption function by linear interpolation
436-
σ = lambda a, z: np.interp(a, a_in[:, z], σ_in[:, z])
446+
σ = lambda a, z: np.interp(a, ae_vals[:, z], c_vals[:, z])
437447
438448
# Allocate memory
439-
σ_out = np.empty_like(σ_in)
449+
c_out = np.empty_like(c_vals)
440450
441-
# Obtain c_i at each s_i, z, store in σ_out[i, z], computing
451+
# Obtain c_i at each s_i, z, store in c_out[i, z], computing
442452
# the expectation term by Monte Carlo
443453
for i, s in enumerate(s_grid):
444454
for z in range(n):
@@ -452,20 +462,28 @@ def K(a_in, σ_in, ifp):
452462
U = u_prime(σ(R_hat * s + Y_hat, z_hat))
453463
Ez += R_hat * U * P[z, z_hat]
454464
Ez = Ez / (len(η_draws) * len(ζ_draws))
455-
σ_out[i, z] = u_prime_inv(β * Ez)
465+
c_out[i, z] = u_prime_inv(β * Ez)
456466
457467
# Calculate endogenous asset grid
458-
a_out = np.empty_like(σ_out)
468+
ae_out = np.empty_like(c_out)
459469
for z in range(n):
460-
a_out[:, z] = s_grid + σ_out[:, z]
470+
ae_out[:, z] = s_grid + c_out[:, z]
461471
462472
# Fixing a consumption-asset pair at (0, 0) improves interpolation
463-
σ_out[0, :] = 0
464-
a_out[0, :] = 0
473+
c_out[0, :] = 0
474+
ae_out[0, :] = 0
465475
466-
return a_out, σ_out
476+
return ae_out, c_out
467477
```
468478

479+
### Code Walkthrough
480+
481+
The operator creates a consumption function `σ` by interpolating the input policy, then uses triple nested loops to compute the expectation via Monte Carlo averaging over savings grid points, current states, future states, and shock realizations.
482+
483+
After computing optimal consumption $c_i$ at each savings level $s_i$ by inverting marginal utility, we construct the endogenous asset grid using $a_i = s_i + c_i$.
484+
485+
Setting consumption and assets to zero at the origin ensures smooth interpolation near zero assets, where the household consumes everything.
486+
469487
The next function solves for an approximation of the optimal consumption policy via time iteration.
470488

471489
```{code-cell} ipython
@@ -497,12 +515,24 @@ def solve_model_time_iter(model, # Class with model information
497515
return a_new, σ_new
498516
```
499517

518+
This function implements fixed-point iteration by repeatedly applying the operator $K$ until the policy converges.
519+
520+
Convergence is measured by the maximum absolute change in consumption across all states.
521+
522+
The operator is guaranteed to converge due to the contraction property discussed earlier.
523+
500524
Now we are ready to create an instance at the default parameters.
501525

502526
```{code-cell} ipython
503527
ifp = IFP()
504528
```
505529

530+
The default parameters represent a calibration with moderate risk aversion ($\gamma = 1.5$, CRRA utility) and a quarterly discount factor ($\beta = 0.96$, corresponding to roughly 4% annual discounting).
531+
532+
The Markov chain has high persistence (90% probability of staying in the current state), while returns have 10% volatility around a zero mean log return ($a_r = 0.1$, $b_r = 0.0$).
533+
534+
Labor income is state-dependent: $Y_t = \exp(0.2 \eta_t + 0.5 Z_t)$ implies higher expected income in the good state ($Z_t = 1$) compared to the bad state ($Z_t = 0$).
535+
506536
Next we set up an initial condition, which corresponds to consuming all
507537
assets.
508538

@@ -537,8 +567,11 @@ Notice that we consume all assets in the lower range of the asset space.
537567

538568
This is because we anticipate income $Y_{t+1}$ tomorrow, which makes the need to save less urgent.
539569

540-
Can you explain why consuming all assets ends earlier (for lower values of
541-
assets) when $z=0$?
570+
Observe that consuming all assets ends earlier (at lower asset levels) when $z=0$ compared to $z=1$.
571+
572+
This occurs because expected future income is lower in the bad state ($z=0$), so the household begins precautionary saving at lower wealth levels.
573+
574+
In contrast, when $z=1$ (good state), higher expected future income allows the household to consume all assets up to a higher threshold before savings become optimal.
542575

543576
### Law of Motion
544577

@@ -684,14 +717,14 @@ Here's the Coleman-Reffett operator using JAX:
684717

685718
```{code-cell} ipython
686719
@jax_jit
687-
def K_jax(a_in, σ_in, ifp):
720+
def K_jax(ae_vals, c_vals, ifp):
688721
"""
689722
The Coleman--Reffett operator for the income fluctuation problem,
690723
using the endogenous grid method with JAX.
691724
692725
* ifp is an instance of IFP_JAX
693-
* a_in[i, z] is an asset grid
694-
* σ_in[i, z] is consumption at a_in[i, z]
726+
* ae_vals[i, z] is an asset grid
727+
* c_vals[i, z] is consumption at ae_vals[i, z]
695728
"""
696729
697730
# Extract parameters from ifp
@@ -701,9 +734,9 @@ def K_jax(a_in, σ_in, ifp):
701734
n = len(P)
702735
703736
# Allocate memory
704-
σ_out = jnp.empty_like(σ_in)
737+
c_out = jnp.empty_like(c_vals)
705738
706-
# Obtain c_i at each s_i, z, store in σ_out[i, z], computing
739+
# Obtain c_i at each s_i, z, store in c_out[i, z], computing
707740
# the expectation term by Monte Carlo
708741
def compute_expectation(s, z):
709742
"""Compute expectation for given s and z"""
@@ -714,7 +747,7 @@ def K_jax(a_in, σ_in, ifp):
714747
Y_hat = Y(z_hat, η, a_y, b_y)
715748
a_val = R_hat * s + Y_hat
716749
# Interpolate consumption
717-
c_interp = jnp.interp(a_val, a_in[:, z_hat], σ_in[:, z_hat])
750+
c_interp = jnp.interp(a_val, ae_vals[:, z_hat], c_vals[:, z_hat])
718751
U = u_prime(c_interp, γ)
719752
return R_hat * U
720753
@@ -728,17 +761,17 @@ def K_jax(a_in, σ_in, ifp):
728761
return u_prime_inv(β * Ez, γ)
729762
730763
# Vectorize over s_grid and z
731-
σ_out = vmap(vmap(compute_expectation, in_axes=(None, 0)),
764+
c_out = vmap(vmap(compute_expectation, in_axes=(None, 0)),
732765
in_axes=(0, None))(s_grid, jnp.arange(n))
733766
734767
# Calculate endogenous asset grid
735-
a_out = s_grid[:, None] + σ_out
768+
ae_out = s_grid[:, None] + c_out
736769
737770
# Fixing a consumption-asset pair at (0, 0) improves interpolation
738-
σ_out = σ_out.at[0, :].set(0)
739-
a_out = a_out.at[0, :].set(0)
771+
c_out = c_out.at[0, :].set(0)
772+
ae_out = ae_out.at[0, :].set(0)
740773
741-
return a_out, σ_out
774+
return ae_out, c_out
742775
```
743776

744777
The next function solves for an approximation of the optimal consumption policy via time iteration using JAX:

0 commit comments

Comments
 (0)