Skip to content

Instantly share code, notes, and snippets.

@floswald
Created April 1, 2019 07:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save floswald/25d8184e5582d8d7d6f9dc0dd8cd6f5a to your computer and use it in GitHub Desktop.
Save floswald/25d8184e5582d8d7d6f9dc0dd8cd6f5a to your computer and use it in GitHub Desktop.
using BasisMatrices, Optim, QuantEcon, Parameters
using BasisMatrices: Degree, Derivative
using Printf, Random
using LinearAlgebra
"""
The stochastic Neoclassical growth model type contains parameters
which define the model
* α: Capital share in output
* β: Discount factor
* δ: Depreciation rate
* γ: Risk aversion
* ρ: Persistence of the log of the productivity level
* σ: Standard deviation of shocks to log productivity level
* A: Coefficient on C-D production function
* kgrid: Grid over capital
* zgrid: Grid over productivity
* grid: Grid of (k, z) pairs
* eps_nodes: Nodes used to integrate
* weights: Weights used to integrate
* z1: A grid of the possible z1s tomorrow given eps_nodes and zgrid
"""
@with_kw struct NeoclassicalGrowth
# Parameters
α::Float64 = 0.36
β::Float64 = 0.99
δ::Float64 = 0.02
γ::Float64 = 2.0
ρ::Float64 = 0.95
σ::Float64 = 0.01
A::Float64 = (1.0/β - (1 - δ)) / α
# Grids
kgrid::Vector{Float64} = collect(range(0.9, stop = 1.1,length = 10))
zgrid::Vector{Float64} = collect(range(0.9, stop = 1.1,length = 10))
grid::Matrix{Float64} = gridmake(kgrid, zgrid)
eps_nodes::Vector{Float64} = qnwnorm(5, 0.0, σ^2)[1]
weights::Vector{Float64} = qnwnorm(5, 0.0, σ^2)[2]
z1::Matrix{Float64} = (zgrid.^(ρ))' .* exp.(eps_nodes)
end
# Helper functions
f(ncgm::NeoclassicalGrowth, k, z) = z .* (ncgm.A*k.^ncgm.α)
df(ncgm::NeoclassicalGrowth, k, z) = ncgm.α * z .* (ncgm.A * k.^(ncgm.α-1.0))
u(ncgm::NeoclassicalGrowth, c) = c > 1e-10 ? (c^(1-ncgm.γ)-1)/(1-ncgm.γ) : -1e10
du(ncgm::NeoclassicalGrowth, c) = c > 1e-10 ? c^(-ncgm.γ) : 1e10
duinv(ncgm::NeoclassicalGrowth, u) = u .^ (-1 / ncgm.γ)
expendables_t(ncgm::NeoclassicalGrowth, k, z) = (1-ncgm.δ)*k + f(ncgm, k, z)
# Types for solution methods
abstract type SolutionMethod end
struct IterateOnPolicy <: SolutionMethod end
struct VFI_ECM <: SolutionMethod end
struct VFI_EGM <: SolutionMethod end
struct VFI <: SolutionMethod end
struct PFI_ECM <: SolutionMethod end
struct PFI <: SolutionMethod end
struct dVFI_ECM <: SolutionMethod end
struct EulEq <: SolutionMethod end
#
# Type for Approximating Value and Policy
#
mutable struct ValueCoeffs{T<:SolutionMethod,D<:Degree}
d::D
v_coeffs::Vector{Float64}
k_coeffs::Vector{Float64}
end
function ValueCoeffs(::Type{Val{d}}, method::T) where {T <: SolutionMethod, d}
# Initialize two vectors of zeros
deg = Degree{d}()
n = n_complete(2, deg)
v_coeffs = zeros(n)
k_coeffs = zeros(n)
return ValueCoeffs{T,Degree{d}}(deg, v_coeffs, k_coeffs)
end
function ValueCoeffs(ncgm::NeoclassicalGrowth, ::Type{Val{d}}, method::T) where {T <: SolutionMethod, d}
# Initialize with vector of zeros
deg = Degree{d}()
n = n_complete(2, deg)
v_coeffs = zeros(n)
# Policy guesses based on k and z
k, z = ncgm.grid[:, 1], ncgm.grid[:, 2]
css = ncgm.A - ncgm.δ
yss = ncgm.A
c_pol = f(ncgm, k, z) * (css/yss)
# Figure out what kp is
k_pol = expendables_t(ncgm, k, z) - c_pol
k_coeffs = complete_polynomial(ncgm.grid, d) \ k_pol
return ValueCoeffs{T,Degree{d}}(deg, v_coeffs, k_coeffs)
end
function solutionmethod(::ValueCoeffs{T}) where T <: SolutionMethod
return T
end
# solutionmethod{T<:SolutionMethod}(::ValueCoeffs{T}) = T
# A few copy methods to make life easier
Base.copy(vp::ValueCoeffs{T, D}) where {T, D} =
ValueCoeffs{T,D}(vp.d, vp.v_coeffs, vp.k_coeffs)
Base.copy(vp::ValueCoeffs{T1, D}, ::T2) where {T1, D, T2 <: SolutionMethod} =
ValueCoeffs{T2,D}(vp.d, vp.v_coeffs, vp.k_coeffs)
function Base.copy(ncgm::NeoclassicalGrowth, vp::ValueCoeffs{T}, ::Type{Val{new_degree}}) where {T, new_degree}
# Build Value and policy matrix
deg = Degree{new_degree}()
V = build_V(ncgm, vp)
k = build_k(ncgm, vp)
# Build new Phi
Phi = complete_polynomial(ncgm.grid, deg)
v_coeffs = Phi \ V
k_coeffs = Phi \ k
return ValueCoeffs{T,Degree{new_degree}}(deg, v_coeffs, k_coeffs)
end
"""
Updates the coefficients for the value function inplace in `vp`
"""
function update_v!(vp::ValueCoeffs, new_coeffs::Vector{Float64}, dampen::Float64)
vp.v_coeffs = (1-dampen)*vp.v_coeffs + dampen*new_coeffs
end
"""
Updates the coefficients for the policy function inplace in `vp`
"""
function update_k!(vp::ValueCoeffs, new_coeffs::Vector{Float64}, dampen::Float64)
vp.k_coeffs = (1-dampen)*vp.k_coeffs + dampen*new_coeffs
end
"""
Builds either V or dV depending on the solution method that is given. If it
is a solution method that iterates on the derivative of the value function
then it will return derivative of the value function, otherwise the
value function itself
"""
build_V_or_dV(ncgm::NeoclassicalGrowth, vp::ValueCoeffs) =
build_V_or_dV(ncgm, vp, solutionmethod(vp)())
build_V_or_dV(ncgm, vp::ValueCoeffs, ::SolutionMethod) = build_V(ncgm, vp)
build_V_or_dV(ncgm, vp::ValueCoeffs, T::dVFI_ECM) = build_dV(ncgm, vp)
function build_dV(ncgm::NeoclassicalGrowth, vp::ValueCoeffs)
Φ = complete_polynomial(ncgm.grid, vp.d, Derivative{1}())
Φ*vp.v_coeffs
end
function build_V(ncgm::NeoclassicalGrowth, vp::ValueCoeffs)
Φ = complete_polynomial(ncgm.grid, vp.d)
Φ*vp.v_coeffs
end
function build_k(ncgm::NeoclassicalGrowth, vp::ValueCoeffs)
Φ = complete_polynomial(ncgm.grid, vp.d)
Φ*vp.k_coeffs
end
function compute_EV!(cp_kpzp::Vector{Float64}, ncgm::NeoclassicalGrowth,
vp::ValueCoeffs, kp, iz)
# Pull out information from types
z1, weightsz = ncgm.z1, ncgm.weights
# Get number nodes
nzp = length(weightsz)
EV = 0.0
for izp in 1:nzp
zp = z1[izp, iz]
complete_polynomial!(cp_kpzp, [kp, zp], vp.d)
EV += weightsz[izp] * dot(vp.v_coeffs, cp_kpzp)
end
return EV
end
function compute_EV(ncgm::NeoclassicalGrowth, vp::ValueCoeffs, kp, iz)
cp_kpzp = Array{Float64}(undef, n_complete(2, vp.d))
return compute_EV!(cp_kpzp, ncgm, vp, kp, iz)
end
function compute_EV(ncgm::NeoclassicalGrowth, vp::ValueCoeffs)
# Get length of k and z grids
kgrid, zgrid = ncgm.kgrid, ncgm.zgrid
nk, nz = length(kgrid), length(zgrid)
temp = Array{Float64}(undef, n_complete(2, vp.d))
# Allocate space to store EV
EV = Array{Float64}(undef, nk*nz)
for ik in 1:nk, iz in 1:nz
# Pull out states
k = kgrid[ik]
z = zgrid[iz]
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
# Pass to scalar EV
complete_polynomial!(temp, [k, z], vp.d)
kp = dot(vp.k_coeffs, temp)
EV[ikiz_index] = compute_EV!(temp, ncgm, vp, kp, iz)
end
return EV
end
function compute_dEV!(cp_dkpzp::Vector, ncgm::NeoclassicalGrowth,
vp::ValueCoeffs, kp, iz)
# Pull out information from types
z1, weightsz = ncgm.z1, ncgm.weights
# Get number nodes
nzp = length(weightsz)
dEV = 0.0
for izp in 1:nzp
zp = z1[izp, iz]
complete_polynomial!(cp_dkpzp, [kp, zp], vp.d, Derivative{1}())
dEV += weightsz[izp] * dot(vp.v_coeffs, cp_dkpzp)
end
return dEV
end
function compute_dEV(ncgm::NeoclassicalGrowth, vp::ValueCoeffs, kp, iz)
compute_dEV!(Array{Float64}(undef, n_complete(2, vp.d)), ncgm, vp, kp, iz)
end
## solver
function solve(ncgm::NeoclassicalGrowth, vp::ValueCoeffs{T};
tol::Float64 = 1e-06, maxiter::Int = 5000, dampen::Float64 = 1,
nskipprint::Int = 1, verbose::Bool = true) where {T <: SolutionMethod}
# Get number of k and z on grid
nk, nz = length(ncgm.kgrid), length(ncgm.zgrid)
# Build basis matrix and value function
dPhi = complete_polynomial(ncgm.grid, vp.d, Derivative{1}())
Phi = complete_polynomial(ncgm.grid, vp.d)
V = build_V_or_dV(ncgm, vp)
k = build_k(ncgm, vp)
Vnew = copy(V)
knew = copy(k)
# Print column names
if verbose
@printf("| Iteration | Distance V | Distance K |\n")
end
# Iterate to convergence
dist, iter = 10.0, 0
while (tol < dist) & (iter < maxiter)
# Update the value function using appropriate update methods
update!(Vnew, knew, ncgm, vp, Phi, dPhi)
# Compute distance and update all relevant elements
iter += 1
dist_v = maximum(abs, 1.0 .- Vnew./V)
dist_k = maximum(abs, 1.0 .- knew./k)
copyto!(V, Vnew)
copyto!(k, knew)
# If we are iterating on a policy, use the difference of values
# otherwise use the distance on policy
dist = ifelse(solutionmethod(vp) == IterateOnPolicy, dist_v, dist_k)
# Print status update
if verbose && (iter%nskipprint == 0)
@printf("|%-11d|%-12e|%-12e|\n", iter, dist_v, dist_k)
end
end
# Update value and policy functions one last time as long as the
# solution method isn't IterateOnPolicy
if ~(solutionmethod(vp) == IterateOnPolicy)
# Update capital policy after finished
kp = env_condition_kp(ncgm, vp)
update_k!(vp, complete_polynomial(ncgm.grid, vp.d) \ kp, 1.0)
# Update value function according to specified policy
vp_igp = copy(vp, IterateOnPolicy())
solve(ncgm, vp_igp; tol=1e-10, maxiter=5000, verbose=false)
update_v!(vp, vp_igp.v_coeffs, 1.0)
end
return vp
end
## updaters
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{IterateOnPolicy},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid;
nk, nz = length(kgrid), length(zgrid)
# Iterate over all states
for ik in 1:nk, iz in 1:nz
# Pull out states
k = kgrid[ik]
z = zgrid[iz]
# Pull out policy and evaluate consumption
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
k1 = kpol[ikiz_index]
c = expendables_t(ncgm, k, z) - k1
# New value
EV = compute_EV(ncgm, vp, k1, iz)
V[ikiz_index] = u(ncgm, c) + ncgm.β*EV
end
# Update coefficients
update_v!(vp, Φ \ V, 1.0)
update_k!(vp, Φ \ kpol, 1.0)
return V
end
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{VFI},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid
nk, nz = length(kgrid), length(zgrid)
# Iterate over all states
temp = Array{Float64}(undef,n_complete(2, vp.d))
for iz=1:nz, ik=1:nk
k = kgrid[ik]; z = zgrid[iz]
# Define an objective function (negative for minimization)
y = expendables_t(ncgm, k, z)
solme(kp) = du(ncgm, y - kp) - ncgm.β*compute_dEV!(temp, ncgm, vp, kp, iz)
# Find sol to foc
kp = brent(solme, 1e-8, y-1e-8; rtol=1e-12)
c = expendables_t(ncgm, k, z) - kp
# New value
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
# ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
EV = compute_EV!(temp, ncgm, vp, kp, iz)
V[ikiz_index] = u(ncgm, c) + ncgm.β*EV
kpol[ikiz_index] = kp
end
# Update coefficients
update_v!(vp, Φ \ V, 1.0)
update_k!(vp, Φ \ kpol, 1.0)
return V
end
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{VFI_EGM},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid; grid = ncgm.grid;
nk, nz = length(kgrid), length(zgrid)
# Iterate
temp = Array{Float64}(undef, n_complete(2, vp.d))
for iz=1:nz, ik=1:nk
# In EGM we use the grid points as if they were our
# policy for yesterday and find implied kt
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
k1 = kgrid[ik];z = zgrid[iz];
# Compute the derivative of expected values
dEV = compute_dEV!(temp, ncgm, vp, k1, iz)
# Compute optimal consumption
c = duinv(ncgm, ncgm.β*dEV)
# Need to find corresponding kt for optimal c
obj(kt) = expendables_t(ncgm, kt, z) - c - k1
kt_star = brent(obj, 0.0, 2.0, xtol=1e-10)
# New value
EV = compute_EV!(temp, ncgm, vp, k1, iz)
V[ikiz_index] = u(ncgm, c) + ncgm.β*EV
kpol[ikiz_index] = kt_star
end
# New Φ (has our new "kt_star" and z points)
Φ_egm = complete_polynomial([kpol grid[:, 2]], vp.d)
# Update coefficients
update_v!(vp, Φ_egm \ V, 1.0)
update_k!(vp, Φ_egm \ grid[:, 1], 1.0)
# Update V and kpol to be value and policy corresponding
# to our grid again
copyto!(V, Φ*vp.v_coeffs)
copyto!(kpol, Φ*vp.k_coeffs)
return V
end
function env_condition_kp!(cp_out::Vector{Float64}, ncgm::NeoclassicalGrowth,
vp::ValueCoeffs, k::Float64, z::Float64)
# Compute derivative of VF
dV = dot(vp.v_coeffs, complete_polynomial!(cp_out, [k, z], vp.d, Derivative{1}()))
# Consumption is then computed as
c = duinv(ncgm, dV / (1 - ncgm.δ + df(ncgm, k, z)))
return expendables_t(ncgm, k, z) - c
end
function env_condition_kp(ncgm::NeoclassicalGrowth, vp::ValueCoeffs,
k::Float64, z::Float64)
cp_out = Array{Float64}(undef, n_complete(2, vp.d))
env_condition_kp!(cp_out, ncgm, vp, k, z)
end
function env_condition_kp(ncgm::NeoclassicalGrowth, vp::ValueCoeffs)
# Pull out k and z from grid
k = ncgm.grid[:, 1]
z = ncgm.grid[:, 2]
# Create basis matrix for entire grid
dPhi = complete_polynomial(ncgm.grid, vp.d, Derivative{1}())
# Compute consumption
c = duinv(ncgm, (dPhi*vp.v_coeffs) ./ (1 .- ncgm.δ .+ df(ncgm, k, z)))
return expendables_t(ncgm, k, z) .- c
end
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{VFI_ECM},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid;
nk, nz = length(kgrid), length(zgrid)
# Iterate over all states
temp = Array{Float64}(undef, n_complete(2, vp.d))
for ik in 1:nk, iz in 1:nz
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
k = kgrid[ik]
z = zgrid[iz]
# Policy from envelope condition
kp = env_condition_kp!(temp, ncgm, vp, k, z)
c = expendables_t(ncgm, k, z) - kp
kpol[ikiz_index] = kp
# New value
EV = compute_EV!(temp, ncgm, vp, kp, iz)
V[ikiz_index] = u(ncgm, c) + ncgm.β*EV
end
# Update coefficients
update_v!(vp, Φ \ V, 1.0)
update_k!(vp, Φ \ kpol, 1.0)
return V
end
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{PFI},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid; grid = ncgm.grid;
nk, nz = length(kgrid), length(zgrid)
# Copy valuecoeffs object and use to iterate to
# convergence given a policy
vp_igp = copy(vp, IterateOnPolicy())
solve(ncgm, vp_igp; nskipprint=1000, maxiter=5000, verbose=false)
# Update the policy and values
temp = Array{Float64}(undef, n_complete(2, vp.d))
for ik in 1:nk, iz in 1:nz
k = kgrid[ik]; z = zgrid[iz];
# Define an objective function (negative for minimization)
y = expendables_t(ncgm, k, z)
solme(kp) = du(ncgm, y - kp) - ncgm.β*compute_dEV!(temp, ncgm, vp, kp, iz)
# Find minimum of objective
kp = brent(solme, 1e-8, y-1e-8; rtol=1e-12)
# Update policy function
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
kpol[ikiz_index] = kp
end
# Get new coeffs
update_k!(vp, Φ \ kpol, 1.0)
update_v!(vp, vp_igp.v_coeffs, 1.0)
# Update all elements of value
copyto!(V, Φ*vp.v_coeffs)
return V
end
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{PFI_ECM},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Copy valuecoeffs object and use to iterate to
# convergence given a policy
vp_igp = copy(vp, IterateOnPolicy())
solve(ncgm, vp_igp; nskipprint=1000, maxiter=5000, verbose=false)
# Update the policy and values
kp = env_condition_kp(ncgm, vp)
update_k!(vp, Φ \ kp, 1.0)
update_v!(vp, vp_igp.v_coeffs, 1.0)
# Update all elements of value
copyto!(V, Φ*vp.v_coeffs)
copyto!(kpol, kp)
return V
end
function update!(dV::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{dVFI_ECM},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid; grid = ncgm.grid;
nk, nz, ns = length(kgrid), length(zgrid), size(grid, 1)
# Iterate over all states
temp = Array{Float64}(undef, n_complete(2, vp.d))
for iz=1:nz, ik=1:nk
k = kgrid[ik]; z = zgrid[iz];
# Envelope condition implies optimal kp
kp = env_condition_kp!(temp, ncgm, vp, k, z)
c = expendables_t(ncgm, k, z) - kp
# New value
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
dEV = compute_dEV!(temp, ncgm, vp, kp, iz)
dV[ikiz_index] = (1-ncgm.δ+df(ncgm, k, z))*ncgm.β*dEV
kpol[ikiz_index] = kp
end
# Get new coeffs
update_k!(vp, Φ \ kpol, 1.0)
update_v!(vp, dΦ \ dV, 1.0)
return dV
end
# Conventional Euler equation method
function update!(V::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{EulEq},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
@unpack kgrid, zgrid, weights, z1 = ncgm
nz1, nz = size(z1)
nk = length(kgrid)
# Iterate over all states
temp = Array{Float64}(undef, n_complete(2, vp.d))
for iz in 1:nz, ik in 1:nk
k = kgrid[ik]; z = zgrid[iz];
# Create current polynomial
complete_polynomial!(temp, [k, z], vp.d)
# Compute what capital will be tomorrow according to policy
kp = dot(temp, vp.k_coeffs)
# Compute RHS of EE
rhs_ee = 0.0
for iz1 in 1:nz1
# Possible z in t+1
zp = z1[iz1, iz]
# Policy for k_{t+2}
complete_polynomial!(temp, [kp, zp], vp.d)
kpp = dot(temp, vp.k_coeffs)
# Implied t+1 consumption
cp = expendables_t(ncgm, kp, zp) - kpp
# Add to running expectation
rhs_ee += ncgm.β*weights[iz1]*du(ncgm, cp)*(1-ncgm.δ+df(ncgm, kp, zp))
end
# The rhs of EE implies consumption and investment in t
c = duinv(ncgm, rhs_ee)
kp_star = expendables_t(ncgm, k, z) - c
# New value
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
EV = compute_EV!(temp, ncgm, vp, kp_star, iz)
V[ikiz_index] = u(ncgm, c) + ncgm.β*EV
kpol[ikiz_index] = kp_star
end
# Update coefficients
update_v!(vp, Φ \ V, 1.0)
update_k!(vp, Φ \ kpol, 1.0)
return V
end
function update!(dV::Vector{Float64}, kpol::Vector{Float64},
ncgm::NeoclassicalGrowth, vp::ValueCoeffs{dVFI_ECM},
Φ::Matrix{Float64}, dΦ::Matrix{Float64})
# Get sizes and allocate for complete_polynomial
kgrid = ncgm.kgrid; zgrid = ncgm.zgrid; grid = ncgm.grid;
nk, nz, ns = length(kgrid), length(zgrid), size(grid, 1)
# Iterate over all states
temp = Array{Float64}(undef, n_complete(2, vp.d))
for iz=1:nz, ik=1:nk
k = kgrid[ik]; z = zgrid[iz];
# Envelope condition implies optimal kp
kp = env_condition_kp!(temp, ncgm, vp, k, z)
c = expendables_t(ncgm, k, z) - kp
# New value
ikiz_index = LinearIndices((nk,nz))[(ik, iz)...]
dEV = compute_dEV!(temp, ncgm, vp, kp, iz)
dV[ikiz_index] = (1-ncgm.δ+df(ncgm, k, z))*ncgm.β*dEV
kpol[ikiz_index] = kp
end
# Get new coeffs
update_k!(vp, Φ \ kpol, 1.0)
update_v!(vp, dΦ \ dV, 1.0)
return dV
end
"""
Simulates the neoclassical growth model for a given set of solution
coefficients. It simulates for `capT` periods and discards first
`nburn` observations.
"""
function simulate(ncgm::NeoclassicalGrowth, vp::ValueCoeffs,
shocks::Vector{Float64}; capT::Int=10_000,
nburn::Int=200)
# Unpack parameters
kp = 0.0 # Policy holder
temp = Array{Float64}(undef, n_complete(2, vp.d))
# Allocate space for k and z
ksim = Array{Float64}(undef, capT+nburn)
zsim = Array{Float64}(undef, capT+nburn)
# Initialize both k and z at 1
ksim[1] = 1.0
zsim[1] = 1.0
# Simulate
temp = Array{Float64}(undef, n_complete(2, vp.d))
for t in 2:capT+nburn
# Evaluate k_t given yesterday's (k_{t-1}, z_{t-1})
kp = env_condition_kp!(temp, ncgm, vp, ksim[t-1], zsim[t-1])
# Draw new z and update k using policy above
zsim[t] = zsim[t-1]^ncgm.ρ * exp(ncgm.σ*shocks[t])
ksim[t] = kp
end
return ksim[nburn+1:end], zsim[nburn+1:end]
end
function simulate(ncgm::NeoclassicalGrowth, vp::ValueCoeffs;
capT::Int=10_000, nburn::Int=200, seed=42)
srand(seed) # Set specific seed
shocks = randn(capT + nburn)
return simulate(ncgm, vp, shocks; capT=capT, nburn=nburn)
end
"""
This function evaluates the Euler Equation residual for a single point (k, z)
"""
function EulerEquation!(out::Vector{Float64}, ncgm::NeoclassicalGrowth,
vp::ValueCoeffs, k::Float64, z::Float64,
nodes::Vector{Float64}, weights::Vector{Float64})
# Evaluate consumption today
k1 = env_condition_kp!(out, ncgm, vp, k, z)
c = expendables_t(ncgm, k, z) - k1
LHS = du(ncgm, c)
# For each of realizations tomorrow, evaluate expectation on RHS
RHS = 0.0
for (eps, w) in zip(nodes, weights)
# Compute ztp1
z1 = z^ncgm.ρ * exp(eps)
# Evaluate the ktp2
ktp2 = env_condition_kp!(out, ncgm, vp, k1, z1)
# Get c1
c1 = expendables_t(ncgm, k1, z1) - ktp2
# Update RHS of equation
RHS = RHS + w*du(ncgm, c1)*(1 - ncgm.δ + df(ncgm, k1, z1))
end
return abs(ncgm.β*RHS/LHS - 1.0)
end
"""
Given simulations for k and z, it computes the euler equation residuals
along the entire simulation. It reports the mean and max values in
log10.
"""
function ee_residuals(ncgm::NeoclassicalGrowth, vp::ValueCoeffs,
ksim::Vector{Float64}, zsim::Vector{Float64}; Qn::Int=10)
# Figure out how many periods we simulated for and make sure k and z
# are same length
capT = length(ksim)
@assert length(zsim) == capT
# Finer integration nodes
eps_nodes, weight_nodes = qnwnorm(Qn, 0.0, ncgm.σ^2)
temp = Array{Float64}(undef, n_complete(2, vp.d))
# Compute EE for each period
EE_resid = Array{Float64}(undef, capT)
for t=1:capT
# Pull out current state
k, z = ksim[t], zsim[t]
# Compute residual of Euler Equation
EE_resid[t] = EulerEquation!(temp, ncgm, vp, k, z, eps_nodes, weight_nodes)
end
return EE_resid
end
function ee_residuals(ncgm::NeoclassicalGrowth, vp::ValueCoeffs; Qn::Int=10)
# Simulate and then call other ee_residuals method
ksim, zsim = simulate(ncgm, vp)
return ee_residuals(ncgm, vp, ksim, zsim; Qn=Qn)
end
## main
function main(sm::SolutionMethod, nd::Int=5, shocks=randn(capT+nburn);
capT=10_000, nburn=200, tol=1e-9, maxiter=2500,
nskipprint=25, verbose=true)
# Create model
ncgm = NeoclassicalGrowth()
# Create initial quadratic guess
vp = ValueCoeffs(ncgm, Val{2}, IterateOnPolicy())
solve(ncgm, vp, tol=1e-6, verbose=true)
# solve(ncgm, 1.0)
# Allocate memory for timings
times = Array{Float64}(undef,nd-1)
sols = Array{ValueCoeffs}(undef,nd-1)
mean_ees = Array{Float64}(undef,nd-1)
max_ees = Array{Float64}(undef,nd-1)
# Solve using the solution method for degree 2 to 5
vp = copy(vp, sm)
for d in 2:nd
# Change degree of solution method
vp = copy(ncgm, vp, Val{d})
# Time the current method
start_time = time()
solve(ncgm, vp; tol=tol, maxiter=maxiter, nskipprint=nskipprint,
verbose=verbose)
end_time = time()
# Save the time and solution
times[d-1] = end_time - start_time
sols[d-1] = vp
# Simulate and compute EE
ks, zs = simulate(ncgm, vp, shocks; capT=capT, nburn=nburn)
resids = ee_residuals(ncgm, vp, ks, zs; Qn=10)
mean_ees[d-1] = log10.(mean(abs.(resids)))
max_ees[d-1] = log10.(maximum(abs, resids))
end
return sols, times, mean_ees, max_ees
end
Random.seed!(52)
shocks = randn(10200)
for sol_method in [VFI(), VFI_EGM(), VFI_ECM(), dVFI_ECM(),
PFI(), PFI_ECM(), EulEq()]
# Make sure everything is compiled
main(sol_method, 5, shocks; maxiter=2, verbose=false)
# Run for real
s_sm, t_sm, mean_eem, max_eem = main(sol_method, 5, shocks;
tol=1e-8, verbose=false)
println("Solution Method: $sol_method")
for (d, t) in zip([2, 3, 4, 5], t_sm)
println("\tDegree $d took time $t")
println("\tMean & Max EE are" *
"$(round(mean_eem[d-1], digits = 3)) & $(round(max_eem[d-1], digits = 3))")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment