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:

  1. Explain why heterogeneous-agent (HA) models are central to modern macro
  2. Describe Young’s histogram method for tracking the wealth distribution
  3. Understand the Krusell–Smith algorithm and its limitations
  4. See how DEQNs scale HA models beyond what grids can handle

# 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}")

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.