import matplotlib.pyplot as plt import numpy as np from numba import njit def v_star(y, α, β, μ): """ True value function """ c1 = np.log(1 - α * β) / (1 - β) c2 = (μ + α * np.log(α * β)) / (1 - α) c3 = 1 / (1 - β) c4 = 1 / (1 - α * β) return c1 + c2 * (c3 - c4) + c4 * np.log(y) def σ_star(y, α, β): """ True optimal policy """ return (1 - α * β) * y from numba import float64 from numba.experimental import jitclass opt_growth_data = [ ('α', float64), # Production parameter ('β', float64), # Discount factor ('μ', float64), # Shock location parameter ('s', float64), # Shock scale parameter ('grid', float64[:]), # Grid (array) ('shocks', float64[:]) # Shock draws (array) ] @jitclass(opt_growth_data) class OptimalGrowthModel: def __init__(self, α=0.4, β=0.96, μ=0, s=0.1, grid_max=4, grid_size=120, shock_size=250, seed=1234): self.α, self.β, self.μ, self.s = α, β, μ, s # Set up grid self.grid = np.linspace(1e-5, grid_max, grid_size) # Store shocks (with a seed, so results are reproducible) np.random.seed(seed) self.shocks = np.exp(μ + s * np.random.randn(shock_size)) def f(self, k): "The production function" return k**self.α def u(self, c): "The utility function" return np.log(c) def f_prime(self, k): "Derivative of f" return self.α * (k**(self.α - 1)) def u_prime(self, c): "Derivative of u" return 1/c def u_prime_inv(self, c): "Inverse of u'" return 1/c @njit def K(σ_array, og): """ The Coleman-Reffett operator using EGM """ # Simplify names f, β = og.f, og.β f_prime, u_prime = og.f_prime, og.u_prime u_prime_inv = og.u_prime_inv grid, shocks = og.grid, og.shocks # Determine endogenous grid y = grid + σ_array # y_i = k_i + c_i # Linear interpolation of policy using endogenous grid σ = lambda x: np.interp(x, y, σ_array) # Allocate memory for new consumption array c = np.empty_like(grid) # Solve for updated consumption value for i, k in enumerate(grid): vals = u_prime(σ(f(k) * shocks)) * f_prime(k) * shocks c[i] = u_prime_inv(β * np.mean(vals)) return c og = OptimalGrowthModel() grid = og.grid def solve_model_time_iter(model, # Class with model information σ, # Initial condition tol=1e-4, max_iter=1000, verbose=True, print_skip=25): # Set up loop i = 0 error = tol + 1 while i < max_iter and error > tol: σ_new = K(σ, model) error = np.max(np.abs(σ - σ_new)) i += 1 if verbose and i % print_skip == 0: print(f"Error at iteration {i} is {error}.") σ = σ_new if error > tol: print("Failed to converge!") elif verbose: print(f"\nConverged in {i} iterations.") return σ_new σ_init = np.copy(grid) σ = solve_model_time_iter(og, σ_init) y = grid + σ # y_i = k_i + c_i fig, ax = plt.subplots() ax.plot(y, σ, lw=2, alpha=0.8, label='approximate policy function') ax.plot(y, σ_star(y, og.α, og.β), 'k--', lw=2, alpha=0.8, label='true policy function') ax.legend() plt.show() np.max(np.abs(σ - σ_star(y, og.α, og.β))) %%timeit -n 3 -r 1 σ = solve_model_time_iter(og, σ_init, verbose=False)