In [1]:
    
from __future__ import division
import numpy as np
import quantecon as qe
    
Exercises using Hamilton's Markov chain:
In [2]:
    
P = [[0.971, 0.029, 0    ],
     [0.145, 0.778, 0.077],
     [0    , 0.508, 0.492]]
mc_H = qe.MarkovChain(P)
    
In [3]:
    
mc_H.P
    
    Out[3]:
In [4]:
    
states = {'NG': 0, 'MR': 1, 'SR': 2}
    
Let us use the following function from the previous exercise set:
In [5]:
    
def cross_sectional_dist(mc, init, T, num_reps=100):
    """
    Return a distribution of visits at time T across num_reps sample paths
    of mc with an initial state init.
    
    """
    x = np.empty(num_reps, dtype=int)
    for i in range(num_reps):
        x[i] = mc.simulate(init=init, sample_size=T+1)[-1]
    bins = np.arange(mc.n+1)
    hist, bin_edges = np.histogram(x, bins=bins)
    dist = hist/len(x)
    return dist
    
In [6]:
    
psi = [0, 0, 1]
psi_10 = cross_sectional_dist(mc_H, init=psi, T=10)
print psi_10
    
    
In [7]:
    
psi_10[states['NG']]
    
    Out[7]:
In [8]:
    
profits = [1000, 0, -1000]
    
In [9]:
    
t = 5
exp_profits_vec = np.linalg.matrix_power(mc_H.P, t).dot(profits)
print exp_profits_vec
    
    
The expected profit when the economy starts in NG:
In [10]:
    
exp_profits_vec[states['NG']]
    
    Out[10]:
The change in profits when the economy starts in SR:
In [11]:
    
exp_profits_vec[states['SR']] - exp_profits_vec[states['NG']]
    
    Out[11]:
In [12]:
    
t = 1000
exp_profits_vec_4213 = np.linalg.matrix_power(mc_H.P, t).dot(profits)
print exp_profits_vec_4213
    
    
In [13]:
    
psi = np.array([0.2, 0.2, 0.6])
t = 5
exp_profits = psi.dot(np.linalg.matrix_power(mc_H.P, t)).dot(profits)
print exp_profits
    
    
In [14]:
    
def path_prob(P, psi, X):
    prob = psi[X[0]]
    for t in range(len(X)-1):
        prob *= P[X[t], X[t+1]]
    return prob
    
In [15]:
    
psi = [0.2, 0.2, 0.6]
X = [states['NG'], states['MR'], states['NG']]
path_prob(mc_H.P, psi, X)
    
    Out[15]:
A recursion version of the path_prob function:
In [16]:
    
def path_prob_recursion(P, psi, X):
    if len(X) == 1:
        return psi[X[0]]
    return path_prob_recursion(P, psi, X[:-1]) * P[X[-2], X[-1]]
    
Note that, given a list X = [x^0, ..., x^{T-1}, x^T],
the path [x^0, ..., x^{T-1}] can be obtained by X[:-1]
(or equivalently, X[0:-1]).
In [17]:
    
path_prob_recursion(mc_H.P, psi, X)
    
    Out[17]:
In [18]:
    
recession_states = [states[s] for s in states.keys() if s not in ['NG']]
print recession_states
    
    
We want to obtain the Cartesian product
recession_states x recession_states x recession_states.
One way is to use
cartesian
from quantecon:
In [19]:
    
from quantecon.cartesian import cartesian
paths = cartesian([recession_states for t in range(3)])
print paths
    
    
(Refer to this issue.)
Another is to use product
from itertools:
In [20]:
    
from itertools import product
paths = product(*[recession_states for t in range(3)])
for path in paths:
    print path
    
    
In [21]:
    
paths = cartesian([recession_states for t in range(3)]).astype(int)
# paths = product(*[recession_states for t in range(3)])
prob = 0
for path in paths:
    prob += path_prob(mc_H.P, psi, path)
print prob
    
    
Suppose that the path was [2, 0, 1]:
In [22]:
    
path = [2, 0, 1]
    
You can determine whether the path contains state NG by the not in operator:
In [23]:
    
states['NG'] not in path
    
    Out[23]:
Count the number of True's out of 10,000 observations:
In [24]:
    
sample_size = 3
num_obs = 10000
is_recession = np.empty(num_obs, dtype=bool)
for i in range(num_obs):
    path = mc_H.simulate(init=psi, sample_size=sample_size)
    is_recession[i] = (states['NG'] not in path)
frequency = is_recession.sum()/num_obs
print frequency
    
    
You may use the mean method instead:
In [25]:
    
is_recession.mean()
    
    Out[25]:
Recall:
In [26]:
    
profits
    
    Out[26]:
In [27]:
    
psi
    
    Out[27]:
In [28]:
    
def discounted_profit(h, r, X):
    profit = 0
    for t in range(len(X)):
        profit += (1/(1+r)**t) * h[X[t]]
    return profit
    
In [29]:
    
T = 2
r = 0.05
paths = cartesian([states.values() for t in range(T+1)]).astype(int)
NPV = 0
for path in paths:
    NPV += discounted_profit(profits, r, path) * path_prob(mc_H.P, psi, path)
print NPV
    
    
In [30]:
    
num_obs = 10000
discounted_profit_values = np.empty(num_obs)
for i in range(num_obs):
    path = mc_H.simulate(init=psi, sample_size=sample_size)
    discounted_profit_values[i] = discounted_profit(profits, r, path)
avg_profit = discounted_profit_values.sum()/num_obs
print avg_profit
    
    
You may use the mean method instead:
In [31]:
    
discounted_profit_values.mean()
    
    Out[31]:
In [32]:
    
def NPV_matrix_power(P, psi, h, r, T):
    psi = np.asarray(psi)  # To use the method dot from NumPy
    NPV = 0
    values = np.empty(T+1)
    for t in range(T+1):
        values[t] = \
            (1/(1+r)**t) * psi.dot(np.linalg.matrix_power(P, t)).dot(h)
    return values.sum()
    
In [33]:
    
NPV_matrix_power(mc_H.P, psi, profits, r, T)
    
    Out[33]:
Another implementation starategy is to use recursion:
In [34]:
    
def NPV_recursion(P, psi, h, r, T):
    if T == 0:
        return np.dot(psi, h)
    NPV = np.dot(psi, h) + \
        (1/(1+r)) * NPV_recursion(P, np.dot(psi, P), h, r, T-1)
    return NPV
    
In [35]:
    
NPV_recursion(mc_H.P, psi, profits, r, T)
    
    Out[35]:
Plot NPVs against T:
In [36]:
    
%matplotlib inline
import matplotlib.pyplot as plt
    
In [37]:
    
ts = 7
NPV_values = np.empty(ts)
for t in range(ts):
    NPV_values[t] = NPV_recursion(mc_H.P, psi, profits, r, t)
    
In [38]:
    
fig, ax = plt.subplots()
ax.plot(NPV_values)
ax.set_xlabel(r'$T$')
ax.set_ylabel('NPV')
ax.axhline(color='k', linewidth=0.5)
ax.grid(True)
plt.show()
    
    
In [39]:
    
mc_H.num_recurrent_classes
    
    Out[39]:
mc_H has only one recurrent class, and hence has only one stationary distribution.
In [40]:
    
psi_star = mc_H.stationary_distributions[0]
    
In [41]:
    
print psi_star
    
    
In [42]:
    
extected_profit_stationary = psi_star.dot(profits)
print extected_profit_stationary
    
    
Recall from Exercise 4.2.13:
In [43]:
    
print exp_profits_vec_4213
    
    
In [44]:
    
mc_H.is_aperiodic
    
    Out[44]:
mc_H is aperiodic, so that the marginal distribution converges to a stationary distribution,
which is unique.
In [45]:
    
psi_20 = cross_sectional_dist(mc_H, init=psi_star, T=20, num_reps=1000)
print psi_20
    
    
In [46]:
    
def sample_return_time(mc, state):
    t = 0
    x = state
    while True:
        t += 1
        x = mc.simulate(init=x, sample_size=2)[-1]
        if x == state:
            break
    return t
    
Or, alternatively:
In [47]:
    
def sample_return_time2(mc, state):
    t = 1
    x = mc.simulate(init=state, sample_size=2)[-1]
    while (x != state):
        t += 1
        x = mc.simulate(init=x, sample_size=2)[-1]
    return t
    
In [48]:
    
def simulated_mean_return_time(mc, state, num_reps):
    return_times = np.empty(num_reps)
    for i in range(num_reps):
        return_times[i] = sample_return_time(mc, state)
    return return_times.mean()
    
In [49]:
    
mc_H.is_irreducible
    
    Out[49]:
mc_H is irreducible, so that every state is recurrent, and therefore for any initial state, the chain returns to it in finite time with probability one.
In [50]:
    
nums = [10**i for i in range(1, 5)]
mean_return_time_vec = np.empty(mc_H.n)
for num_reps in nums:
    for state in range(mc_H.n):
        mean_return_time_vec[state] = \
            simulated_mean_return_time(mc_H, state, num_reps)
    print 'num_reps = {0}:'.format(num_reps)
    print '  mean return times = {0}'.format(mean_return_time_vec)
    
    
Compare:
In [51]:
    
psi_star ** (-1)
    
    Out[51]:
In [52]:
    
h = np.zeros(mc_H.n)
h[states['NG']] = 1
print h
    
    
In [53]:
    
def h_mean(mc, init, h, sample_size):
    path = mc.simulate(init=init, sample_size=sample_size)
    h_sum = 0
    for t in range(sample_size):
        h_sum += h[path[t]]
    return h_sum/sample_size
    
In [54]:
    
sample_size = 50000
init = 0
h_mean(mc_H, init=init, h=h, sample_size=sample_size)
    
    Out[54]:
In [55]:
    
psi_star.dot(h)
    
    Out[55]:
Recall:
In [56]:
    
profits
    
    Out[56]:
In [57]:
    
h_mean(mc_H, init=init, h=profits, sample_size=sample_size)
    
    Out[57]:
Recall from Exercise 4.3.9:
In [58]:
    
extected_profit_stationary
    
    Out[58]:
In [58]: