Lecture 12: Canonical Economic Models
You will learn how to solve two canonical economic models:
- The overlapping generations (OLG) model
- The Ramsey model
Main take-away: Hopefully inspiration to analyze such models on your own.
%load_ext autoreload
%autoreload 2
import numpy as np
from scipy import optimize
# plotting
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
plt.rcParams.update({'font.size': 12})
# models
from OLGModel import OLGModelClass
from RamseyModel import RamseyModelClass
1.1 Model description
Time: Discrete and indexed by .
Demographics: Population is constant. A life consists of two periods, young and old.
Households: As young a household supplies labor exogenously, , and earns a after tax wage . Consumption as young and old are denoted by and . The after-tax return on saving is . Utility is
The problem is formulated in terms of the saving rate .
Firms: Firms rent capital at the rental rate , and hires labor at the wage rate . Firms have access to the production function
Profits are
Government: Choose public consumption, , and tax rates and . Total tax revenue is
Government debt accumulates according to
A balanced budget implies .
Capital: Depreciates with a rate of .
Equilibrium:
- Households maximize utility
- Firms maximize profits
No-arbitrage between bonds and capital
Labor market clears:
- Goods market clears:
- Asset market clears:
- Capital follows its law of motion:
For more details on the OLG model: See chapter 3-4 here.
1.2 Solution and simulation
Implication of profit maximization: From FOCs
Implication of utility maximization: From FOC
Simulation algorithm: At the beginning of period , the economy can be summarized in the state variables and . Before is known, we can calculate:
After is known we can calculate:
Solution algorithm: Simulate forward choosing so that we always have
Implementation:
- Use a bisection root-finder to determine
- Low : A lot of consumption today. Low marginal utility. LHS < RHS.
- High : Little consumption today. High marginal utility. LHS > RHS.
- Problem: Too low might not be feasible if .
Note: Never errors in the Euler-equation due to perfect foresight.
Question: Are all the requirements for the equilibrium satisfied?
1.3 Test case
- Production is Cobb-Douglas ()
- Utility is logarithmic ()
- The government is not doing anything (, and )
Analytical steady state: It can be proven
Setup:
model = OLGModelClass()
par = model.par # SimpeNamespace
sim = model.sim # SimpeNamespace
# a. production
par.production_function = 'cobb-douglas'
par.theta = 0.0
# b. households
par.sigma = 1.0
# c. government
par.tau_w = 0.0
par.tau_r = 0.0
sim.balanced_budget[:] = True # G changes to achieve this
# d. initial values
K_ss = ((1-par.alpha)/((1+1.0/par.beta)))**(1/(1-par.alpha))
par.K_lag_ini = 0.1*K_ss
initializing the model:
calling .setup()
calling .allocate()
Simulate first period manually
from OLGModel import simulate_before_s, simulate_after_s, find_s_bracket, calc_euler_error
Make a guess:
s_guess = 0.41
Evaluate first period:
# a. initialize
sim.K_lag[0] = par.K_lag_ini
sim.B_lag[0] = par.B_lag_ini
simulate_before_s(par,sim,t=0)
print(f'{sim.C2[0] = : .4f}')
simulate_after_s(par,sim,s=s_guess,t=0)
print(f'{sim.C1[0] = : .4f}')
simulate_before_s(par,sim,t=1)
print(f'{sim.C2[1] = : .4f}')
print(f'{sim.rt[1] = : .4f}')
LHS_Euler = sim.C1[0]**(-par.sigma)
RHS_Euler = (1+sim.rt[1])*par.beta * sim.C2[1]**(-par.sigma)
print(f'euler-error = {LHS_Euler-RHS_Euler:.8f}')
sim.C2[0] = 0.0973
sim.C1[0] = 0.1221
sim.C2[1] = 0.1855
sim.rt[1] = 1.1871
euler-error = -0.22834540
Implemented as function:
euler_error = calc_euler_error(s_guess,par,sim,t=0)
print(f'euler-error = {euler_error:.8f}')
euler-error = -0.22834540
Find bracket to search in:
s_min,s_max = find_s_bracket(par,sim,t=0,do_print=True);
euler-error for s = 0.99999999 = 483321577.17005599
euler-error for s = 0.50000000 = 2.76183762
euler-error for s = 0.25000001 = -7.36489999
bracket to search in with opposite signed errors:
[ 0.25000001- 0.50000000]
Call root-finder:
obj = lambda s: calc_euler_error(s,par,sim,t=0)
result = optimize.root_scalar(obj,bracket=(s_min,s_max),method='bisect')
print(result)
converged: True
flag: 'converged'
function_calls: 39
iterations: 37
root: 0.41666666666653274
Check result:
euler_error = calc_euler_error(result.root,par,sim,t=0)
print(f'euler-error = {euler_error:.8f}')
euler-error = -0.00000000
Full simulation
model.simulate()
simulation done in 0.06 secs
Check euler-errors:
for t in range(5):
LHS_Euler = sim.C1[t]**(-par.sigma)
RHS_Euler = (1+sim.rt[t+1])*par.beta * sim.C2[t+1]**(-par.sigma)
print(f't = {t:2d}: euler-error = {LHS_Euler-RHS_Euler:.8f}')
t = 0: euler-error = -0.00000000
t = 1: euler-error = -0.00000000
t = 2: euler-error = -0.00000000
t = 3: euler-error = -0.00000000
t = 4: euler-error = -0.00000000
Plot and check with analytical solution:
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(model.sim.K_lag,label=r'$K_{t-1}$')
ax.axhline(K_ss,ls='--',color='black',label='analytical steady state')
ax.legend(frameon=True)
fig.tight_layout()
K_lag_old = model.sim.K_lag.copy()
Task: Test if the starting point matters?
Additional check: Not much should change with only small parameter changes.
# a. production (close to cobb-douglas)
par.production_function = 'ces'
par.theta = 0.001
# b. household (close to logarithmic)
par.sigma = 1.1
# c. goverment (weakly active)
par.tau_w = 0.001
par.tau_r = 0.001
# d. simulate
model.simulate()
simulation done in 0.06 secs
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(model.sim.K_lag,label=r'$K_{t-1}$')
ax.plot(K_lag_old,label=r'$K_{t-1}$ ($\theta = 0.0, \sigma = 1.0$, inactive government)')
ax.axhline(K_ss,ls='--',color='black',label='analytical steady state (wrong)')
ax.legend(frameon=True)
fig.tight_layout()
1.4 Active government
model = OLGModelClass()
par = model.par
sim = model.sim
initializing the model:
calling .setup()
calling .allocate()
Baseline:
model.simulate()
simulation done in 0.02 secs
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(sim.K_lag/(sim.Y),label=r'$\frac{K_{t-1}}{Y_t}$')
ax.plot(sim.B_lag/(sim.Y),label=r'$\frac{B_{t-1}}{Y_t}$')
ax.legend(frameon=True)
fig.tight_layout()
Remember steady state:
K_ss = sim.K_lag[-1]
B_ss = sim.B_lag[-1]
G_ss = sim.G[-1]
Spending spree of 5% in periods:
# a. start from steady state
par.K_lag_ini = K_ss
par.B_lag_ini = B_ss
# b. spending spree
T0 = 0
dT = 3
sim.G[T0:T0+dT] = 1.05*G_ss
sim.balanced_budget[:T0] = True #G adjusts
sim.balanced_budget[T0:T0+dT] = False # B adjusts
sim.balanced_budget[T0+dT:] = True # G adjusts
Simulate:
model.simulate()
simulation done in 0.07 secs
Crowding-out of capital:
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(sim.K/(sim.Y),label=r'$\frac{K_{t-1}}{Y_t}$')
ax.plot(sim.B/(sim.Y),label=r'$\frac{B_{t-1}}{Y_t}$')
ax.legend(frameon=True)
fig.tight_layout()
Question: Would the households react today if the spending spree is say 10 periods in the future?
1.5 Getting an overview
- Spend 3 minutes looking at
OLGModel.py
- Write one question at https://b.socrative.com/login/student/ with
ROOM=NUMECON
1.6 Potential analysis and extension
Potential analysis:
- Over-accumulation of capital relative to golden rule?
- Calibration to actual data
- Generational inequality
- Multiple equilibria
Extensions:
- Add population and technology growth
- More detailed tax and transfer system
- Utility and productive effect of government consumption/investment
- Endogenous labor supply
- Bequest motive
- Uncertain returns on capital
- Additional assets (e.g. housing)
- More than two periods in the life-cycle (life-cycle)
- More than one dynasty (cross-sectional inequality dynamics)
... also called the Ramsey-Cass-Koopman model.
2.1 Model descripton
Time: Discrete and indexed by .
Demographics:: Population is constant. Everybody lives forever.
Household: Households supply labor exogenously, , and earns a wage . The return on saving is . Utility is
where is cash-on-hand and is end-of-period assets.
Firms: Firms rent capital at the rental rate and hires labor at the wage rate . Firms have access to the production function
Profits are
Equilibrium:
- Households maximize utility
- Firms maximize profits
- Labor market clear:
- Goods market clear:
- Asset market clear: and
- Capital follows its law of motion:
Implication of profit maximization: From FOCs
Implication of utility maximization: From FOCs
Solution algorithm:
We can summarize the model in the non-linear equation system
where , , and
Path: We refer to and as transition paths.
Implementation: We solve this equation system in two steps:
- Assume all variables are in steady state after some truncation horizon.
- Calculate the numerical jacobian of wrt. and around the steady state
- Solve the equation system using a hand-written Broyden-solver
Note: The equation system can also be solved directly using scipy.optimize.root
.
Remember: The jacobian is just a gradient. I.e. the matrix of what the implied errors are in when a single or change.
2.2 Solution
model = RamseyModelClass()
par = model.par
ss = model.ss
path = model.path
initializing the model:
calling .setup()
calling .allocate()
Find steady state:
- Target steady-state capital-output ratio, of 4.0.
- Force steady-state output .
- Adjust and to achieve this.
model.find_steady_state(KY_ss=4.0)
Y_ss = 1.0000
K_ss/Y_ss = 4.0000
rk_ss = 0.0750
r_ss = 0.0250
w_ss = 0.7000
A = 0.6598
beta = 0.9756
Test that errors and the path are 0:
# a. set initial value
par.K_lag_ini = ss.K
# b. set path
path.A[:] = ss.A
path.C[:] = ss.C
path.K[:] = ss.K
# c. check errors
errors_ss = model.evaluate_path_errors()
assert np.allclose(errors_ss,0.0)
model.calculate_jacobian()
Solve:
par.K_lag_ini = 0.50*ss.K # start away from steady state
model.solve() # find transition path
it = 0 -> max. abs. error = 2.08774760
it = 1 -> max. abs. error = 0.03407048
it = 2 -> max. abs. error = 0.04084472
it = 3 -> max. abs. error = 0.00495803
it = 4 -> max. abs. error = 0.01354190
it = 5 -> max. abs. error = 0.01209108
it = 6 -> max. abs. error = 0.00397825
it = 7 -> max. abs. error = 0.00192043
it = 8 -> max. abs. error = 0.00097483
it = 9 -> max. abs. error = 0.00009018
it = 10 -> max. abs. error = 0.00010485
it = 11 -> max. abs. error = 0.00000476
it = 12 -> max. abs. error = 0.00000737
it = 13 -> max. abs. error = 0.00000045
it = 14 -> max. abs. error = 0.00000038
it = 15 -> max. abs. error = 0.00000002
it = 16 -> max. abs. error = 0.00000002
it = 17 -> max. abs. error = 0.00000000
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(path.K_lag,label=r'$K_{t-1}$')
ax.legend(frameon=True)
fig.tight_layout()
2.3 Comparison with scipy solution
Note: scipy computes the jacobian internally
model_scipy = RamseyModelClass()
model_scipy.par.solver = 'scipy'
model_scipy.find_steady_state(KY_ss=4.0)
model_scipy.par.K_lag_ini = 0.50*model_scipy.ss.K
model_scipy.path.A[:] = model_scipy.ss.A
model_scipy.solve()
initializing the model:
calling .setup()
calling .allocate()
Y_ss = 1.0000
K_ss/Y_ss = 4.0000
rk_ss = 0.0750
r_ss = 0.0250
w_ss = 0.7000
A = 0.6598
beta = 0.9756
fig = plt.figure(figsize=(6,6/1.5))
ax = fig.add_subplot(1,1,1)
ax.plot(path.K_lag,label=r'$K_{t-1}$, broyden')
ax.plot(model_scipy.path.K_lag,ls='--',label=r'$K_{t-1}$, scipy')
ax.legend(frameon=True)
fig.tight_layout()
2.4 Persistent technology shock
Shock:
par.K_lag_ini = ss.K # start from steady state
path.A[:] = 0.95**np.arange(par.Tpath)*0.1*ss.A + ss.A # shock path
Terminology: This is called an MIT-shock. Households do not expect shocks. Know the full path of the shock when it arrives. Continue to believe no future shocks will happen.
Solve:
model.solve()
it = 0 -> max. abs. error = 0.10000000
it = 1 -> max. abs. error = 0.00096551
it = 2 -> max. abs. error = 0.00004937
it = 3 -> max. abs. error = 0.00000248
it = 4 -> max. abs. error = 0.00000040
it = 5 -> max. abs. error = 0.00000006
it = 6 -> max. abs. error = 0.00000000
fig = plt.figure(figsize=(2*6,6/1.5))
ax = fig.add_subplot(1,2,1)
ax.set_title('Capital, $K_{t-1}$')
ax.plot(path.K_lag)
ax = fig.add_subplot(1,2,2)
ax.plot(path.A)
ax.set_title('Technology, $A_t$')
fig.tight_layout()
Question: Could a much more persistent shock be problematic?
2.5 Future persistent technology shock
Shock happing after period :
par.K_lag_ini = ss.K # start from steady state
# shock
H = 50
path.A[:] = ss.A
path.A[H:] = 0.95**np.arange(par.Tpath-H)*0.1*ss.A + ss.A
Solve:
model.solve()
it = 0 -> max. abs. error = 0.10000000
it = 1 -> max. abs. error = 0.00267237
it = 2 -> max. abs. error = 0.00015130
it = 3 -> max. abs. error = 0.00000241
it = 4 -> max. abs. error = 0.00000025
it = 5 -> max. abs. error = 0.00000002
it = 6 -> max. abs. error = 0.00000000
fig = plt.figure(figsize=(2*6,6/1.5))
ax = fig.add_subplot(1,2,1)
ax.set_title('Capital, $K_{t-1}$')
ax.plot(path.K_lag)
ax = fig.add_subplot(1,2,2)
ax.plot(path.A)
ax.set_title('Technology, $A_t$')
fig.tight_layout()
par.K_lag_ini = path.K[30]
path.A[:] = ss.A
model.solve()
it = 0 -> max. abs. error = 0.05301739
it = 1 -> max. abs. error = 0.00001569
it = 2 -> max. abs. error = 0.00000010
it = 3 -> max. abs. error = 0.00000000
Take-away: Households are forward looking and responds before the shock hits.
2.6 Getting an overview
- Spend 3 minutes looking at
RamseyModel.py
- Write one question at https://b.socrative.com/login/student/ with
ROOM=NUMECON
2.7 Potential analysis and extension
Potential analysis:
- Different shocks (e.g. discount factor)
- Multiple shocks
- Permanent shocks ( convergence to new steady state)
- Transition speed
Extensions:
- Add a government and taxation
- Endogenous labor supply
- Additional assets (e.g. housing)
- Add nominal rigidities (New Keynesian)
The next steps beyond this course:
The Bewley-Huggett-Aiyagari model. A multi-period OLG model or Ramsey model with households making decisions under uncertainty and borrowing constraints as in lecture 11 under "dynamic optimization". Such heterogenous agent models are used in state-of-the-art research, see Quantitative Macroeconomics with Heterogeneous Households.
Further adding nominal rigidities this is called a Heterogenous Agent New Keynesian (HANK) model. See Macroeconomics with HANK models.
This extends the Representative Agent New Keynesian (RANK) model, which itself is a Ramsey model extended with nominal rigidities.
The final frontier is including aggregate risk, which either requires linearization or using a Krussell-Smith method. Solving the model in sequence-space as we did with the Ramsey model is a frontier method (see here).
Next lecture: Agent Based Models