Skip to content

Commit 1ccfd8d

Browse files
authored
Merge pull request #282 from QuantEcon/sl/rouwenhorst
Add rouwenhorst method for approx AR(1) with MC
2 parents 9bbe567 + 4ebb7f5 commit 1ccfd8d

File tree

4 files changed

+147
-4
lines changed

4 files changed

+147
-4
lines changed

quantecon/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from .matrix_eqn import solve_discrete_lyapunov, solve_discrete_riccati
3131
from .quadsums import var_quadratic_sum, m_quadratic_sum
3232
#->Propose Delete From Top Level
33-
from .markov import MarkovChain, random_markov_chain, random_stochastic_matrix, gth_solve, tauchen #Promote to keep current examples working
33+
from .markov import MarkovChain, random_markov_chain, random_stochastic_matrix, gth_solve, tauchen, rouwenhorst #Promote to keep current examples working
3434
from .markov import mc_compute_stationary, mc_sample_path #Imports that Should be Deprecated with markov package
3535
#<-
3636
from .rank_nullspace import rank_est, nullspace

quantecon/markov/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
from .gth_solve import gth_solve
88
from .random import random_markov_chain, random_stochastic_matrix, \
99
random_discrete_dp
10-
from .approximation import tauchen
10+
from .approximation import tauchen, rouwenhorst
1111
from .ddp import DiscreteDP, backward_induction
1212
from .utilities import sa_indices

quantecon/markov/approximation.py

+97
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,103 @@
1616
from numba import njit
1717

1818

19+
def rouwenhorst(n, ybar, sigma, rho):
20+
"""
21+
Takes as inputs n, p, q, psi. It will then construct a markov chain
22+
that estimates an AR(1) process of:
23+
y_t = \bar{y} + \rho y_{t-1} + \varepsilon_t
24+
where \varepsilon_t is i.i.d. normal of mean 0, std dev of sigma
25+
26+
The Rouwenhorst approximation uses the following recursive defintion
27+
for approximating a distribution:
28+
29+
theta_2 = [p , 1 - p]
30+
[1 - q, q ]
31+
32+
theta_{n+1} = p [theta_n, 0] + (1 - p) [0, theta_n]
33+
[0 , 0] [0, 0]
34+
+ q [0 , 0] + (1 - q) [0, ]
35+
[theta_n , 0] [0, theta_n]
36+
37+
Parameters
38+
----------
39+
n : int
40+
The number of points to approximate the distribution
41+
42+
ybar : float
43+
The value \bar{y} in the process. Note that the mean of this
44+
AR(1) process, y, is simply ybar/(1 - rho)
45+
46+
sigma : float
47+
The value of the standard deviation of the \varepsilon process
48+
49+
rho : float
50+
By default this will be 0, but if you are approximating an AR(1)
51+
process then this is the autocorrelation across periods
52+
53+
Returns
54+
-------
55+
56+
mc : MarkovChain
57+
An instance of the MarkovChain class that stores the transition
58+
matrix and state values returned by the discretization method
59+
60+
"""
61+
62+
# Get the standard deviation of y
63+
y_sd = sqrt(sigma**2 / (1 - rho**2))
64+
65+
# Given the moments of our process we can find the right values
66+
# for p, q, psi because there are analytical solutions as shown in
67+
# Gianluca Violante's notes on computational methods
68+
p = (1 + rho) / 2
69+
q = p
70+
psi = y_sd * np.sqrt(n - 1)
71+
72+
# Find the states
73+
ubar = psi
74+
lbar = -ubar
75+
76+
bar = np.linspace(lbar, ubar, n)
77+
78+
def row_build_mat(n, p, q):
79+
"""
80+
This method uses the values of p and q to build the transition
81+
matrix for the rouwenhorst method
82+
"""
83+
84+
if n == 2:
85+
theta = np.array([[p, 1 - p], [1 - q, q]])
86+
87+
elif n > 2:
88+
p1 = np.zeros((n, n))
89+
p2 = np.zeros((n, n))
90+
p3 = np.zeros((n, n))
91+
p4 = np.zeros((n, n))
92+
93+
new_mat = row_build_mat(n - 1, p, q)
94+
95+
p1[:n - 1, :n - 1] = p * new_mat
96+
p2[:n - 1, 1:] = (1 - p) * new_mat
97+
p3[1:, :-1] = (1 - q) * new_mat
98+
p4[1:, 1:] = q * new_mat
99+
100+
theta = p1 + p2 + p3 + p4
101+
theta[1:n - 1, :] = theta[1:n - 1, :] / 2
102+
103+
else:
104+
raise ValueError("The number of states must be positive " +
105+
"and greater than or equal to 2")
106+
107+
return theta
108+
109+
theta = row_build_mat(n, p, q)
110+
111+
bar += ybar / (1 - rho)
112+
113+
return MarkovChain(theta, bar)
114+
115+
19116
def tauchen(rho, sigma_u, m=3, n=7):
20117
"""
21118
Computes a Markov chain associated with a discretized version of

quantecon/markov/tests/test_approximation.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import unittest
1212
import numpy as np
1313
from numpy.testing import assert_allclose
14-
from quantecon.markov import tauchen
14+
from quantecon.markov import tauchen, rouwenhorst
15+
#from quantecon.markov.approximation import rouwenhorst
1516

1617

1718
class TestTauchen(unittest.TestCase):
@@ -50,7 +51,52 @@ def test_states_sum_0(self):
5051
self.assertTrue(abs(np.sum(self.x)) < self.tol)
5152

5253

54+
class TestRouwenhorst(unittest.TestCase):
55+
56+
def setUp(self):
57+
self.rho, self.sigma = np.random.uniform(0,1, size=2)
58+
self.n = np.random.random_integers(3,25)
59+
self.ybar = np.random.random_integers(0,10)
60+
self.tol = 1e-12
61+
62+
mc = rouwenhorst(self.n, self.ybar, self.sigma, self.rho)
63+
self.x, self.P = mc.state_values, mc.P
64+
65+
def tearDown(self):
66+
del self.x
67+
del self.P
68+
69+
def testShape(self):
70+
i, j = self.P.shape
71+
72+
self.assertTrue(i == j)
73+
74+
def testDim(self):
75+
dim_x = self.x.ndim
76+
dim_P = self.P.ndim
77+
self.assertTrue(dim_x == 1 and dim_P == 2)
78+
79+
def test_transition_mat_row_sum_1(self):
80+
self.assertTrue(np.allclose(np.sum(self.P, axis=1), 1, atol=self.tol))
81+
82+
def test_positive_probs(self):
83+
self.assertTrue(np.all(self.P) > -self.tol)
84+
85+
def test_states_sum_0(self):
86+
tol = self.tol + self.n*(self.ybar/(1 - self.rho))
87+
self.assertTrue(abs(np.sum(self.x)) < tol)
88+
89+
def test_control_case(self):
90+
n = 3; ybar = 1; sigma = 0.5; rho = 0.8;
91+
mc_rouwenhorst = rouwenhorst(n, ybar, sigma, rho)
92+
mc_rouwenhorst.x, mc_rouwenhorst.P = mc_rouwenhorst.state_values, mc_rouwenhorst.P
93+
sigma_y = np.sqrt(sigma**2 / (1-rho**2))
94+
psi = sigma_y * np.sqrt(n-1)
95+
known_x = np.array([-psi+5.0, 5., psi+5.0])
96+
known_P = np.array([[0.81, 0.18, 0.01], [0.09, 0.82, 0.09], [0.01, 0.18, 0.81]])
97+
self.assertTrue(sum(mc_rouwenhorst.x - known_x) < self.tol and sum(sum(mc_rouwenhorst.P - known_P)) < self.tol)
98+
5399

54100
if __name__ == '__main__':
55-
suite = unittest.TestLoader().loadTestsFromTestCase(TestTauchen)
101+
suite = unittest.TestLoader().loadTestsFromTestCase([TestTauchen, TestRouwenhorst])
56102
unittest.TextTestRunner(verbosity=2, stream=sys.stderr).run(suite)

0 commit comments

Comments
 (0)