# 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 3: Automatic Differentiation for Economics
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 the difference between numerical, symbolic, and automatic differentiation
- Use PyTorch’s
autogradto compute gradients, Jacobians, and Hessians - Apply autodiff to compute marginal utilities, Euler-equation residuals, and other economic quantities
- Understand why autodiff is the key enabler for DEQNs and PINNs
1. Three ways to differentiate
When you solve economic models, you constantly need derivatives: marginal utility \(u'(c)\), marginal product \(f'(k)\), Jacobians of equilibrium systems.
| Method | How it works | Pros | Cons |
|---|---|---|---|
| Numerical | \((f(x+h) - f(x-h)) / 2h\) | Simple | Slow, imprecise, scales badly |
| Symbolic | Algebraic rules (like Mathematica) | Exact formulas | Exponential expression growth |
| Automatic | Chain rule on computation graph | Exact, fast, scales | Requires framework support |
Automatic differentiation (autodiff) is what makes DEQNs possible. It computes exact derivatives at machine precision, automatically, for any function you can code.
# Comparing the three methods
# Target: d/dx [sin(x²) · exp(-x)] at x = 1.5
import torch
x_val = 1.5
# 1. Analytical (by hand)
# d/dx [sin(x²) exp(-x)] = [2x cos(x²) - sin(x²)] exp(-x)
analytical = (2 * x_val * np.cos(x_val**2) - np.sin(x_val**2)) * np.exp(-x_val)
# 2. Numerical (finite differences)
h = 1e-7
f = lambda x: np.sin(x**2) * np.exp(-x)
numerical = (f(x_val + h) - f(x_val - h)) / (2 * h)
# 3. Automatic (PyTorch)
x = torch.tensor(x_val, requires_grad=True)
y = torch.sin(x**2) * torch.exp(-x)
y.backward()
autodiff = x.grad.item()
print(f"Analytical: {analytical:.12f}")
print(f"Numerical: {numerical:.12f} (error: {abs(numerical - analytical):.2e})")
print(f"Autodiff: {autodiff:.12f} (error: {abs(autodiff - analytical):.2e})")
print(f"\n→ Autodiff matches the analytical result to machine precision.")2. PyTorch autograd in depth
Computing gradients
torch.autograd.grad() gives you fine-grained control — important for computing Euler-equation residuals where you need \(\partial c / \partial k\).
# Computing economic derivatives with autograd
# Marginal utility with CRRA: u'(c) = c^(-γ)
gamma = 2.0
c = torch.tensor(1.5, requires_grad=True)
u = c ** (1 - gamma) / (1 - gamma) # CRRA utility
u_prime = torch.autograd.grad(u, c, create_graph=True)[0]
print(f"u(c) = c^(1-γ)/(1-γ) at c = {c.item()}")
print(f"u'(c) = c^(-γ) = {u_prime.item():.6f}")
print(f"Check: c^(-γ) = {c.item()**(-gamma):.6f}")
# Second derivative (for risk aversion)
u_double_prime = torch.autograd.grad(u_prime, c)[0]
print(f"\nu''(c) = -γ c^(-γ-1) = {u_double_prime.item():.6f}")
print(f"Check: -γ c^(-γ-1) = {-gamma * c.item()**(-gamma-1):.6f}")
# Arrow-Pratt coefficient of absolute risk aversion
ara = -u_double_prime / u_prime
print(f"\nArrow-Pratt ARA = -u''(c)/u'(c) = {ara.item():.6f}")
print(f"Check: γ/c = {gamma / c.item():.6f}")3. Jacobians and Hessians
For multi-dimensional models (multiple state variables, multiple policy functions), we need Jacobians and Hessians:
# Jacobian of a 2-equation system
def equilibrium_system(x):
"""A toy 2-equation system: market clearing + Euler equation."""
k, c = x[0], x[1]
eq1 = k**0.33 - c - (k - 0.5) # goods market clearing
eq2 = c**(-2) - 0.95 * 0.33 * k**(-0.67) * c**(-2) # Euler equation
return torch.stack([eq1, eq2])
x = torch.tensor([1.0, 0.5], requires_grad=True)
F = equilibrium_system(x)
# Compute Jacobian
J = torch.zeros(2, 2)
for i in range(2):
grad = torch.autograd.grad(F[i], x, retain_graph=True)[0]
J[i] = grad
print("Jacobian of the equilibrium system:")
print(J.numpy().round(4))
print(f"\nDeterminant: {torch.det(J).item():.4f}")
print("(Non-zero determinant → system has a locally unique solution)")4. Why autodiff matters for economic models
Three applications that are central to this course:
4.1 Euler-equation residuals (Weeks 4–6)
The DEQN loss requires differentiating through the policy network and through the model equations. Autodiff does both simultaneously.
4.2 Physics-informed neural networks (Week 7)
PINNs solve PDEs by computing \(\frac{\partial^2 V}{\partial k^2}\) and \(\frac{\partial V}{\partial t}\) directly from the network — autodiff gives these for free.
4.3 Comparative statics
Given a solved model, autodiff computes \(\frac{\partial c^*}{\partial \alpha}\) (how the optimal policy changes with parameters) without resolving the model.
Exercises
Exercise 1: Compute the gradient, Hessian, and Laplacian of \(f(x, y) = x^2 y + \sin(xy)\) at \((1, 2)\) using PyTorch autograd.
Exercise 2: Write a function that takes a neural network and computes its Jacobian at a given input point using torch.autograd.functional.jacobian(). Apply it to the policy network from Week 1.
Next week: Deep Equilibrium Networks I — solving the growth model without knowing the answer.