# Setup
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
torch.manual_seed(42)
np.random.seed(42)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"PyTorch {torch.__version__} on {device}")Week 8: Heterogeneous Agents
Deep Learning for Macroeconomics — Honours, The University of Edinburgh
Instructor: Juan Zurita · juan.zurita@ed.ac.uk
Learning objectives
By the end of this notebook you will be able to:
- Explain why heterogeneous-agent (HA) models are central to modern macro
- Describe Young’s histogram method for tracking the wealth distribution
- Understand the Krusell–Smith algorithm and its limitations
- See how DEQNs scale HA models beyond what grids can handle
1. Why heterogeneity matters
Representative-agent models miss distribution effects that matter for policy: - Fiscal stimulus: who spends the cheque? (High-MPC households vs savers) - Monetary policy: rate changes affect borrowers and savers differently - Capital requirements: which banks adjust lending? (Your JMP!)
The challenge: tracking the entire wealth distribution \(\Gamma_t\) as a state variable — it is infinite-dimensional.
2. Young’s histogram method
Instead of tracking the full distribution, approximate it as a histogram over a grid:
\[\Gamma_t \approx (\pi_1, \pi_2, \ldots, \pi_M) \quad \text{where } \pi_i = \Pr(a_t \in [a_i, a_{i+1}))\]
Update rule: given individual policy \(a' = g(a, z; \Gamma)\) and transition matrix \(\Pi^z\), the next-period histogram follows from the law of motion.
# Simple illustration of Young's method
# Aiyagari-style model: agents save/borrow subject to income shocks
# Simplified version for illustration
N_AGENTS = 10000
N_PERIODS = 500
r = 0.04 # interest rate
beta = 0.96
sigma = 0.3 # income shock std dev
# Simulate agents forward
assets = np.ones(N_AGENTS) * 1.0 # initial assets
asset_history = []
for t in range(N_PERIODS):
income = np.exp(np.random.normal(0, sigma, N_AGENTS))
# Simple consumption rule (not optimal — just illustrative)
savings_rate = 0.3
consumption = (1 - savings_rate) * ((1 + r) * assets + income)
assets = (1 + r) * assets + income - consumption
assets = np.maximum(assets, 0) # borrowing constraint
if t >= N_PERIODS - 100:
asset_history.append(assets.copy())
# Plot final wealth distribution
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].hist(assets, bins=50, color='#7a2318', edgecolor='white', alpha=0.8, density=True)
axes[0].set_xlabel('Assets', fontsize=11)
axes[0].set_ylabel('Density', fontsize=11)
axes[0].set_title('Stationary Wealth Distribution', fontsize=12)
axes[0].axvline(np.mean(assets), color='black', linestyle='--', label=f'Mean: {np.mean(assets):.2f}')
axes[0].axvline(np.median(assets), color='#2a6e3f', linestyle='--', label=f'Median: {np.median(assets):.2f}')
axes[0].legend()
# Lorenz curve
sorted_assets = np.sort(assets)
cum_share = np.cumsum(sorted_assets) / np.sum(sorted_assets)
pop_share = np.arange(1, N_AGENTS + 1) / N_AGENTS
gini = 1 - 2 * np.trapz(cum_share, pop_share)
axes[1].plot(pop_share, cum_share, color='#7a2318', linewidth=2, label=f'Lorenz (Gini = {gini:.3f})')
axes[1].plot([0, 1], [0, 1], 'k--', alpha=0.5, label='Perfect equality')
axes[1].fill_between(pop_share, cum_share, pop_share, alpha=0.1, color='#7a2318')
axes[1].set_xlabel('Population share', fontsize=11)
axes[1].set_ylabel('Wealth share', fontsize=11)
axes[1].set_title('Lorenz Curve', fontsize=12)
axes[1].legend()
plt.tight_layout(); plt.show()
print(f"Gini coefficient: {gini:.3f}")
print(f"Top 10% wealth share: {1 - cum_share[int(0.9*N_AGENTS)]:.1%}")3. Krusell–Smith and its limitations
Krusell & Smith (1998) showed that the mean of the distribution is often a sufficient statistic — you can approximate \(\Gamma_t\) with just its first moment. But this breaks down when: - Distribution shape matters (skewness, fat tails) - Constraints bind for some agents (bimodal distributions) - Multiple asset types (high-dimensional distributions)
The DEQN alternative
Instead of summarising \(\Gamma_t\) with moments, parameterise the individual policy function directly:
\[a'(a, z; \theta) = \text{NN}(a, z; \theta)\]
and use Young’s method inside the training loop to compute aggregate prices. This approach scales to much larger state spaces than moment-based methods.
Exercises
Exercise 1: Implement a simplified Aiyagari model: solve for the individual policy function \(a'(a, z)\) using a DEQN, then use Young’s method to find the stationary distribution. Compare the Gini coefficient with the simulation-based approach above.
Exercise 2: How does the wealth distribution change when you tighten the borrowing constraint from \(\underline{a} = 0\) to \(\underline{a} = -1\)? Solve both versions and compare the Lorenz curves.
Next week: Surrogates, Estimation, and Climate Economics — connecting models to data.