This page was generated from examples/ConsNewKeynesianModel/KS-HARK-presentation.ipynb.
Interactive online version: Binder badge. Download notebook.

Solving Krusell Smith Model with HARK and SSJ#

By William Du

[2]:
import time

import matplotlib.pyplot as plt
import numpy as np
from scipy import optimize
from sequence_jacobian import create_model, simple  # functions
from sequence_jacobian.classes import JacobianDict, SteadyStateDict

from HARK.ConsumptionSaving.ConsNewKeynesianModel import NewKeynesianConsumerType

Calibration and setup#

This notebook uses the HARK toolkit to solve Krusell and Smith (1998), applying the Sequence Space Jacobian and SSJ toolkit.

Firm setup#

Collect calibration for the production economy in a dictionary.

[3]:
calibration = {
    "eis": 1,  # Elasticity of intertemporal substitution
    "delta": 0.025,  # Depreciation rate
    "alpha": 0.11,  # Capital share of income
    "L_ss": 1.0,  # Steady state labor
    "Y_ss": 1.0,  # Steady state output
    "r_ss": 0.01,  # Steady state real interest rate
}

Steady State Capital#

Find steady state capital (K_ss) and productivity (Z) implied by the firm’s first order condition, aggregate resource constraint and the above steady state values of labor (L_ss), the real interest rate (r_ss) and output (Y_ss).

[5]:
from scipy.optimize import root


def your_funcs(X):
    L_ss = calibration["L_ss"]
    alpha = calibration["alpha"]
    delta = calibration["delta"]
    r_ss = calibration["r_ss"]
    Y_ss = calibration["Y_ss"]

    K, Z = X

    # Firms first order condition and aggregate resource constraint
    f = [
        alpha * Z * (K / L_ss) ** (alpha - 1) - delta - r_ss,  # r_ss = MPK
        Z * K**alpha * L_ss ** (1 - alpha) - Y_ss,  # Y = Z*F(K,L)
    ]

    return f


sol = root(your_funcs, [1.0, 1.0])  # find roots

K_ss, Z_ss = sol.x

calibration["K_ss"] = K_ss
calibration["Z_ss"] = Z_ss
[6]:
print(sol)
 message: The solution converged.
 success: True
  status: 1
     fun: [ 1.749e-14 -5.218e-15]
       x: [ 3.143e+00  8.816e-01]
  method: hybr
    nfev: 14
    fjac: [[-3.675e-01  9.300e-01]
           [-9.300e-01 -3.675e-01]]
       r: [ 3.906e-02  1.143e+00 -3.236e-01]
     qtf: [-4.533e-12 -5.621e-12]

Double check the roots we find produce our chosen steady state values.

[7]:
def firm(
    K,
    Z,
    L_ss=calibration["L_ss"],
    alpha=calibration["alpha"],
    delta=calibration["delta"],
):
    r = alpha * Z * (K / L_ss) ** (alpha - 1) - delta
    w = (1 - alpha) * Z * (K / L_ss) ** alpha
    Y = Z * K**alpha * L_ss ** (1 - alpha)
    return r, w, Y


r_ss, w_ss, Y_ss = firm(sol.x[0], sol.x[1])

calibration["w_ss"] = w_ss
[8]:
print("--------------------------------------+------------")
print(f"Steady state capital                  | {K_ss:.4f}")
print(f"Steady state output                   | {Y_ss:.4f}")
print(f"Steady state real interest rate       | {r_ss:.4f}")
print(f"Steady state wage                     | {w_ss:.4f}")
print("--------------------------------------+------------")
0.010000000000017495 0.8899999999999955 0.9999999999999948

HARK agent#

HARK represents agent problems where aggregate variables can affect an individual income process as instances of NewKeynesianConsumerType, a subclass of AgentType (see A Gentle Introduction to HARK).

Instances of the NewKeynesianConsumerType class contain functions that solve policy functions of optimizing agents and compute Jacobian matrices in response to a policy shocks using the SSJ toolkit.

NOTE: NewKeynesianConsumerType is still a microeconomic agent solving an income fluctuation problem. The only difference from `IndShockConsumerType <https://docs.econ-ark.org/examples/HowWeSolveIndShockConsumerType/HowWeSolveIndShockConsumerType.html>`__ is that additional aggregate labour income variables are passed to the agent’s income process. This allows the agent’s problem to be defined within a general equilibrium framework such as (but not restricted to) HANK.

NOTE: Researchers can also create their own agent types by subclassing AgentType. See HARK’s documentation for more information.

To create an instance of NewKeynesianConsumerType, first specify parameters in a dictionary. To start, we use a discount factor of 0.98.

[9]:
L_ss = calibration["L_ss"]
w_ss = calibration["w_ss"]
r_ss = calibration["r_ss"]

HANK_Dict = {
    # Individual agent 'preferences' (shared with perfect foresight model)
    "CRRA": calibration["eis"],  # Coefficient of relative risk aversion
    "DiscFac": 0.98,  # Intertemporal discount factor
    "LivPrb": [0.99375],  # Survival probability
    # Individual lifcycle income process parameters
    "PermGroFac": [1.00],  # Permanent income growth factor
    "PermShkStd": [0.06],  # Standard deviation of log permanent shocks to income
    "PermShkCount": 5,  # Number of points in discrete approximation to permanent income shocks
    "TranShkStd": [0.2],  # Standard deviation of log transitory shocks to income
    "TranShkCount": 5,  # Number of points in discrete approximation to transitory income shocks
    # Parameters related to unemployment and retirement
    "UnempPrb": 0.0,  # Probability of unemployment while working
    "IncUnemp": 0.0,  # Unemployment benefits replacement rate
    "UnempPrbRet": 0.0000,  # Probability of "unemployment" while retired
    "IncUnempRet": 0.0,  # "Unemployment" benefits when retired
    "T_retire": 0.0,  # Period of retirement (0 --> no retirement)
    # Aggregates affecting the agent's decision
    "Rfree": (1 + r_ss),  # Interest factor for assets faced by agents
    "wage": [w_ss],  # Wage rate faced by agents
    "tax_rate": [
        0
    ],  # set to 0.0 because we are going to assume that labor here is actually after tax income
    "labor": [L_ss],  # Aggregate (mean) labor supply
    # Parameters for constructing "assets above minimum" grid
    "aXtraMin": 0.0001,  # Minimum end-of-period "assets above minimum" value
    "aXtraMax": 2000,  # Maximum end-of-period "assets above minimum" value
    "aXtraCount": 200,  # Number of points in the base grid of "assets above minimum"
    # Exponential nesting factor when constructing "assets above minimum" grid
    "aXtraNestFac": 3,
    "aXtraExtra": None,  # Additional values to add to aXtraGrid
    # Transition matrix simulation parameters
    "mCount": 200,
    "mMax": 2000,
    "mMin": 0.0001,
    "mFac": 3,
}

Let’s create a temporary instance of a NewKeynesianConsumerType agent.

[10]:
tempAgent = NewKeynesianConsumerType(**HANK_Dict)

Given a discount factor, the agent will supply a steady state capital stock A_ss.

[ ]:
A_ss = tempAgent.compute_steady_state()[0]
print(f"Steady state agent asset supply for beta = 0.98 is: {A_ss:.3f}")

Steady State Assets#

Since we are interested in computing a steady state equilibrium, we find discFac such that A_ss clears the asset market given that K_ss is the steady state firm capital demand.

[11]:
def A_ss_func(beta):
    HANK_Dict["DiscFac"] = beta

    # For a given value of beta, we need to re-create the Agent
    Agent_func = NewKeynesianConsumerType(**HANK_Dict, verbose=False)

    # And then solve for the steady state supply
    A_ss = Agent_func.compute_steady_state()[0]

    return A_ss


def ss_dif(beta):
    return A_ss_func(beta) - Asset_target


start = time.time()
Asset_target = K_ss

DiscFac = optimize.brentq(ss_dif, 0.8, 0.9999)

print(f"Time taken to solve for steady state {time.time() - start:.3f} secs.")
Time taken to solve for steady state 15.923938751220703

We can now create the steady state agent using the general equilibrium discount factor.

[12]:
# Create a new agent
HANK_Dict["DiscFac"] = DiscFac
steadyHANK = NewKeynesianConsumerType(**HANK_Dict, verbose=False)
A_ss, C_ss = steadyHANK.compute_steady_state()
[13]:
# To make sure goods and asset markets clear
print(
    "Final goods clearing:",
    calibration["Y_ss"] - C_ss - calibration["delta"] * calibration["K_ss"],
)
print("Asset clearing:", A_ss - calibration["K_ss"])
goods_clearing 0.01915180971061768
asset_clearing -4.0967496062194186e-10

Computing Jacobians using SSJ#

With the steady state agent in hand, we can compute the Jacobians of the steady state HANK agent’s policy with respect to the aggregate state variables.

The calc_jacobian method of our NewKeynesianConsumerType agent computes the Jacobians of the steady state aggregate consumption (CJACW) and assets (AJACW) with respect to a pertubation of a specified variable.

Recall CJACW[s,t] is the time \(t\) response to a shock at time \(s\).

Here is an example where we shock the wage rate.

[14]:
start = time.time()

CJACW, AJACW = steadyHANK.calc_jacobian("wage", 300)  # Wage jacobians

print(f"Time taken to compute wage Jacobians: {time.time() - start:.3f} seconds")
Time taken to compute jacobians 2.081505060195923
[15]:
plt.plot(CJACW.T[0], label="s=0")
plt.plot(CJACW.T[20], label="s=20")
plt.plot(CJACW.T[50], label="s=50")
plt.plot(CJACW.T[100], label="s=100")
plt.xlim(-2, 300)
plt.plot(np.arange(300), np.zeros(300), color="k")
plt.title("Consumption Jacobian (Wage) ")
plt.xlabel("Quarters")
plt.ylabel("Consumption response")
plt.legend()
plt.show()
../../_images/examples_ConsNewKeynesianModel_KS-HARK-presentation_23_0.png
[16]:
start = time.time()

CJACR, AJACR = steadyHANK.calc_jacobian("Rfree", 300)  # Rfree jacobians

print(
    f"Time taken to compute return factor Jacobians: {time.time() - start:.3f} seconds"
)
Time taken to compute jacobians 1.9681644439697266

Here is an example where we shock the return factor.

[17]:
plt.plot(CJACR.T[0], label="s =0")
plt.plot(CJACR.T[20], label="s=20")
plt.plot(CJACR.T[50], label="s=50")
plt.plot(CJACR.T[100], label="s=100")
plt.plot(np.arange(300), np.zeros(300), color="k")
plt.title("Consumption Jacobian (Return Factor)")
plt.xlabel("Quarters")
plt.ylabel("Consumption response")
plt.show()
../../_images/examples_ConsNewKeynesianModel_KS-HARK-presentation_26_0.png

Let’s store the Jacobians in a dictionary for later use.

[18]:
# Store Jacobians in JacobianDict Object
Jacobian_Dict = JacobianDict(
    {
        "C": {
            "w": CJACW,
            "r": CJACR,
        },
        "A": {
            "w": AJACW,
            "r": AJACR,
        },
    },
)

# Construct SteadyStateDict object
SteadyState_Dict = SteadyStateDict(
    {
        "asset_mkt": 0.0,
        "goods_mkt": 0.0,
        "r": r_ss,
        "Y": Y_ss,
        "A": K_ss,
        "C": C_ss,
        "Z": Z_ss,
        "delta": calibration["delta"],
        "alpha": calibration["alpha"],
        "L": L_ss,
        "K": K_ss,
        "w": w_ss,
    },
)

Impulse Response Functions and Simulations#

[19]:
@simple
def firm(K, L, Z, alpha, delta):
    r = alpha * Z * (K(-1) / L) ** (alpha - 1) - delta
    w = (1 - alpha) * Z * (K(-1) / L) ** alpha
    Y = Z * K(-1) ** alpha * L ** (1 - alpha)
    return r, w, Y


@simple
def mkt_clearing(K, A, Y, C, delta):
    asset_mkt = A - K
    goods_mkt = Y - C - delta * K
    return asset_mkt, goods_mkt
[20]:
ks = create_model([Jacobian_Dict, firm, mkt_clearing], name="Krusell-Smith")

Solving for Impulse Responses#

[21]:
T = 300  # Length of the IRF
rho_Z = 0.8  # Persistence of IRF shock
dZ = 0.001 * Z_ss * rho_Z ** np.arange(T)
shocks = {"Z": dZ}

inputs = ["Z"]
unknowns = ["K"]
targets = ["asset_mkt"]

irfs_Z = ks.solve_impulse_linear(SteadyState_Dict, unknowns, targets, shocks)
[22]:
def show_irfs(
    irfs_list,
    variables,
    labels=[" "],
    ylabel=r"Percentage point (dev. from ss)",
    T_plot=50,
    figsize=(18, 6),
):
    if len(irfs_list) != len(labels):
        labels = [" "] * len(irfs_list)
    n_var = len(variables)
    fig, ax = plt.subplots(1, n_var, figsize=figsize, sharex=True)
    for i in range(n_var):
        # plot all irfs
        for j, irf in enumerate(irfs_list):
            ax[i].plot(irf[variables[i]][:50] * 1e2, label=labels[j])
        ax[i].set_title(variables[i])
        ax[i].set_xlabel(r"Quarters")
        if i == 0:
            ax[i].set_ylabel(ylabel)
        # ax[i].legend()
        # ax[i].x

    plt.tight_layout()
    plt.show()
    # plt.savefig("irf.png")
[23]:
# Impulse Responses to Productivity Shock
show_irfs([irfs_Z], ["Y", "C", "Z", "K", "r"])
../../_images/examples_ConsNewKeynesianModel_KS-HARK-presentation_35_0.png

Simulating the model#

[24]:
from estimation.plots import plot_timeseries
from estimation.routines import simulate
[25]:
outputs = ["Y", "K", "r"]

sigmas = {"Z": 0.001}
rhos = {"Z": 0.8}
impulses = {}

for i in inputs:
    own_shock = {i: sigmas[i] * rhos[i] ** np.arange(T)}
    impulses[i] = ks.solve_impulse_linear(
        SteadyState_Dict,
        unknowns,
        targets,
        own_shock,
    )


T_sim = 156  # 39 years, as in the original SW (2007) sample
data_simul = simulate(list(impulses.values()), outputs, T_sim)
plot_timeseries(data_simul, (1, 3), figsize=(12, 4))
../../_images/examples_ConsNewKeynesianModel_KS-HARK-presentation_38_0.png