Completed
Push — master ( 7899a5...4a3912 )
by David R.
01:19
created

pratt_arrow_risk_aversion()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
c 2
b 0
f 0
dl 0
loc 3
rs 10
1
"""
2
Test case using the Ramsey-Cass-Koopmans model of optimal savings.
3
4
Model assumes Cobb-Douglas production technology and Constant Relative Risk
5
Aversion (CRRA) preferences.  I then generate random parameter values
6
consistent with a constant consumption-output ratio (i.e., constant savings
7
rate).
8
9
"""
10
from nose import tools
11
12
import numpy as np
13
from scipy import stats
14
15
from . import models
16
from .. import basis_functions
17
from .. import solvers
18
19
20
def analytic_solution(t, A0, alpha, delta, g, K0, n, N0, theta, **params):
21
    """Analytic solution for model with Cobb-Douglas production."""
22
    k0 = K0 / (A0 * N0)
23
    lmbda = (g + n + delta) * (1 - alpha)
24
    ks = ((k0**(1 - alpha) * np.exp(-lmbda * t) +
25
          (1 / (theta * (g + n + delta))) * (1 - np.exp(-lmbda * t)))**(1 / (1 - alpha)))
26
    ys = cobb_douglas_output(ks, alpha, **params)
27
    cs = ((theta - 1) / theta) * ys
28
29
    return [ks, cs]
30
31
32
def cobb_douglas_output(k, alpha, **params):
33
    """Cobb-Douglas output (per unit effective labor supply)."""
34
    return k**alpha
35
36
37
def cobb_douglas_mpk(k, alpha, **params):
38
    """Marginal product of capital with Cobb-Douglas production."""
39
    return alpha * k**(alpha - 1)
40
41
42
def equilibrium_capital(alpha, delta, g, n, rho, theta, **params):
43
    """Steady state value for capital stock (per unit effective labor)."""
44
    return (alpha / (delta + rho + theta * g))**(1 / (1 - alpha))
45
46
47
def generate_random_params(scale, seed):
48
    np.random.seed(seed)
49
50
    # random g, n, delta such that sum of these params is positive
51
    g, n = stats.norm.rvs(0.05, scale, size=2)
52
    delta, = stats.lognorm.rvs(scale, loc=g + n, size=1)
53
    assert g + n + delta > 0
54
55
    # choose alpha consistent with theta > 1
56
    upper_bound = 1 if (g + delta) < 0 else min(1, (g + delta) / (g + n + delta))
57
    alpha, = stats.uniform.rvs(scale=upper_bound, size=1)
58
    assert 0 < alpha < upper_bound
59
60
    lower_bound = delta / (alpha * (g + n + delta) - g)
61
    theta, = stats.lognorm.rvs(scale, loc=lower_bound, size=1)
62
    rho = alpha * theta * (g + n + delta) - (delta * theta * g)
63
    assert rho > 0
64
65
    # normalize A0 and N0
66
    A0 = 1.0
67
    N0 = A0
68
69
    # choose K0 so that it is not too far from balanced growth path
70
    kstar = equilibrium_capital(g, n, alpha, delta, rho, theta)
71
    K0, = stats.uniform.rvs(0.5 * kstar, 1.5 * kstar, size=1)
72
    assert K0 > 0
73
74
    params = {'g': g, 'n': n, 'delta': delta, 'rho': rho, 'alpha': alpha,
75
              'theta': theta, 'K0': K0, 'A0': A0, 'N0': N0}
76
77
    return params
78
79
80
def initial_mesh(t, T, num, problem):
81
    # compute equilibrium values
82
    cstar = problem.equilibrium_consumption(**problem.params)
83
    kstar = problem.equilibrium_capital(**problem.params)
84
    ystar = problem.intensive_output(kstar, **problem.params)
85
86
    # create the mesh for capital
87
    ts = np.linspace(t, T, num)
88
    k0 = problem.params['K0'] / (problem.params['A0'] * problem.params['N0'])
89
    ks = kstar - (kstar - k0) * np.exp(-ts)
90
91
    # create the mesh for consumption
92
    s = 1 - (cstar / ystar)
93
    y0 = cobb_douglas_output(k0, **problem.params)
94
    c0 = (1 - s) * y0
95
    cs = cstar - (cstar - c0) * np.exp(-ts)
96
97
    return ts, ks, cs
98
99
100
def pratt_arrow_risk_aversion(t, c, theta, **params):
101
    """Assume constant relative risk aversion"""
102
    return theta / c
103
104
105
random_seed = np.random.randint(2147483647)
106
random_params = generate_random_params(0.1, random_seed)
107
test_problem = models.RamseyCassKoopmansModel(pratt_arrow_risk_aversion,
108
                                              cobb_douglas_output,
109
                                              equilibrium_capital,
110
                                              cobb_douglas_mpk,
111
                                              random_params)
112
113
polynomial_basis = basis_functions.PolynomialBasis()
114
solver = solvers.Solver(polynomial_basis)
115
116
117
def _test_polynomial_collocation(basis_kwargs, boundary_points, num=1000):
118
    """Helper function for testing various kinds of polynomial collocation."""
119
    ts, ks, cs = initial_mesh(*boundary_points, num=num, problem=test_problem)
120
    k_poly = polynomial_basis.fit(ts, ks, **basis_kwargs)
121
    c_poly = polynomial_basis.fit(ts, cs, **basis_kwargs)
122
    initial_coefs = np.hstack([k_poly.coef, c_poly.coef])
123
    nodes = polynomial_basis.roots(**basis_kwargs)
124
125
    solution = solver.solve(basis_kwargs, boundary_points, initial_coefs,
126
                            nodes, test_problem)
127
128
    # check that solver terminated successfully
129
    msg = "Solver failed!\nSeed: {}\nModel params: {}\n".format(random_seed, test_problem.params)
130
    tools.assert_true(solution.result.success, msg=msg)
131
132
    # compute the residuals
133
    normed_residuals = solution.normalize_residuals(ts)
134
135
    # check that residuals are close to zero on average
136
    for normed_residual in normed_residuals:
137
        tools.assert_true(np.mean(normed_residual) < 1e-6, msg=msg)
138
139
    # check that the numerical and analytic solutions are close
140
    numeric_solns = solution.evaluate_solution(ts)
141
    analytic_solns = analytic_solution(ts, **test_problem.params)
142
143
    for numeric_soln, analytic_soln in zip(numeric_solns, analytic_solns):
144
        error = numeric_soln - analytic_soln
145
        tools.assert_true(np.mean(error) < 1e-6, msg=msg)
146
147
148
def test_chebyshev_collocation():
149
    """Test collocation solver using Chebyshev polynomials for basis."""
150
    boundary_points = (0, 100)
151
    basis_kwargs = {'kind': 'Chebyshev', 'degree': 50, 'domain': boundary_points}
152
    _test_polynomial_collocation(basis_kwargs, boundary_points)
153
154
155
def test_legendre_collocation():
156
    """Test collocation solver using Legendre polynomials for basis."""
157
    boundary_points = (0, 100)
158
    basis_kwargs = {'kind': 'Legendre', 'degree': 50, 'domain': boundary_points}
159
    _test_polynomial_collocation(basis_kwargs, boundary_points)
160