In [1]:
%load_ext autoreload

In [2]:
autoreload 2

In [3]:
%matplotlib inline

In [4]:
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sn

import pycollocation


## Example: Solow model with Cobb-Douglas production

The Solow model is a model of economic growth as a process of physical capital accumulation. By far the most common version of the Solow model assumes Cobb-Douglas functional form for intensive output:

$$f(k) = k^{\alpha}.$$
In [5]:
def cobb_douglas_output(k, alpha, **params):
return k**alpha


After a bit of algebra, the Solow model with Cobb-Douglas production can be reduced down to a single non-linear ordinary differential equation (ODE) and an initial condition for capital (per unit effective labor supply)...

$$\dot{k}(t) = s k(t)^{\alpha} - (g + n + \delta) k(t),\ k(0) = k_0$$

...the above equation says that the rate of change of the stock of physical capital (per unit effective labor supply), $\dot{k}(t)$, is the difference between the actual level of investment in physical capital, $sk(t)^{\alpha}$, and the amount of investment required to maintain the current level of physical capital, $(g + n + \delta) k(t)$.

In [6]:
def standard_solow_model(t, k, alpha, delta, g, n, s, **params):
return [s * cobb_douglas_output(k, alpha) - (g + n + delta) * k]

def initial_condition(t, k, k0, **params):
return [k - k0]


To complete the model we need to define some parameter values.

In [7]:
params = {'g': 0.02, 's': 0.1, 'n': 0.02, 'alpha': 0.15, 'delta': 0.04, 'k0': 1.0}


## Solving the model with pyCollocation

### Defining a pycollocation.IVP instance

In [8]:
pycollocation.problems.IVP?

In [9]:
standard_solow_ivp = pycollocation.problems.IVP(bcs_lower=initial_condition,
number_bcs_lower=1,
number_odes=1,
params=params,
rhs=standard_solow_model,
)


### Finding a good initial guess for $k(t)$¶

Theory tells us that, starting from some initial condition $k_0$, the solution to the Solow model converges monotonically toward its long run equilibrium value $k^*$. Our initial guess for the solution should preserve this property...

In [10]:
def equilibrium_capital(alpha, delta, g, n, s, **params):
"""Equilibrium value of capital (per unit effective labor supply)."""
return (s / (g + n + delta))**(1 / (1 - alpha))

In [11]:
def initial_mesh(t, T, num, problem):
ts = np.linspace(t, T, num=num)
kstar = equilibrium_capital(**problem.params)
ks = kstar - (kstar - params['k0']) * np.exp(-ts)
return ts, ks


### Solving the model¶

In [12]:
pycollocation.solvers.Solver?


### Orthogonal polynomial basis functions

In [57]:
# Choose your basis functions and create a solver
polynomial_basis = pycollocation.basis_functions.PolynomialBasis()
solver = pycollocation.solvers.Solver(polynomial_basis)

# compute the initial mesh
boundary_points = (0, 100)
ts, ks = initial_mesh(*boundary_points, num=1000, problem=standard_solow_ivp)

# compute the initial coefs guess
basis_kwargs = {'kind': 'Chebyshev', 'domain': boundary_points, 'degree': 15}
k_poly = polynomial_basis.fit(ts, ks, **basis_kwargs)
initial_coefs = k_poly.coef

# specify the collocation nodes
nodes = polynomial_basis.roots(**basis_kwargs)

# solve the model!
solution = solver.solve(basis_kwargs, boundary_points, initial_coefs,
nodes, standard_solow_ivp)

In [58]:
k_soln, = solution.evaluate_solution(ts)
plt.plot(ts, k_soln)
plt.show()

In [59]:
k_resids, = solution.evaluate_residual(ts)
plt.plot(ts, k_resids)
plt.show()

In [60]:
k_normalized_resids, = solution.normalize_residuals(ts)
plt.plot(ts, np.abs(k_normalized_resids))
plt.yscale('log')
plt.show()


### B-spline basis functions

In [66]:
bspline_basis = pycollocation.basis_functions.BSplineBasis()
solver = pycollocation.solvers.Solver(bspline_basis)

ts, ks = initial_mesh(*boundary_points, num=250, problem=standard_solow_ivp)

tck, u = bspline_basis.fit([ks], u=ts, k=5, s=0)
knots, coefs, k = tck
initial_coefs = np.hstack(coefs)

basis_kwargs = {'knots': knots, 'degree': k, 'ext': 2}
nodes = np.linspace(*boundary_points, num=249)
solution = solver.solve(basis_kwargs, boundary_points, initial_coefs,
nodes, standard_solow_ivp)

In [67]:
solution.result.success

Out[67]:
True
In [68]:
k_soln, = solution.evaluate_solution(ts)
plt.plot(ts, k_soln)
plt.show()

In [69]:
k_resids, = solution.evaluate_residual(ts)
plt.plot(ts, k_resids)
plt.show()

In [70]:
k_normalized_resids, = solution.normalize_residuals(ts)
plt.plot(ts, np.abs(k_normalized_resids))
plt.yscale('log')
plt.show()


# Example: Generic Solow model of economic growth

Can we refactor the code above so that it can be solve a Solow model for arbitrary intensive production $f$? Yes!

In [15]:
from pycollocation.tests import models


Example usage...

In [16]:
def ces_output(k, alpha, sigma, **params):
rho = (sigma - 1) / sigma
if rho == 0:
y = cobb_douglas_output(k, alpha)
else:
y = (alpha * k**rho + (1 - alpha))**(1 / rho)
return y

def ces_equilibrium_capital(g, n, s, alpha, delta, sigma, **params):
"""Steady state value for capital stock (per unit effective labor)."""
rho = (sigma - 1) / sigma
if rho == 0:
kss = (s / (g + n + delta))**(1 / (1 - alpha))
else:
kss = ((1 - alpha) / (((g + n + delta) / s)**rho - alpha))**(1 / rho)
return kss

ces_params = {'g': 0.02, 's': 0.1, 'n': 0.02, 'alpha': 0.15, 'delta': 0.04,
'sigma': 0.05, 'k0': 1.0}

In [17]:
generic_solow_ivp = models.SolowModel(ces_output,
ces_equilibrium_capital,
ces_params)

In [18]:
polynomial_basis = pycollocation.basis_functions.PolynomialBasis()
solver = pycollocation.solvers.Solver(polynomial_basis)

basis_kwargs = {'kind': 'Chebyshev', 'domain': [0, 100], 'degree': 15}

ts, ks = initial_mesh(basis_kwargs['domain'], 1000, standard_solow_ivp)
k_poly = polynomial_basis.fit(ts, ks, **basis_kwargs)
initial_coefs = k_poly.coef

solution = solver.solve(basis_kwargs, initial_coefs, standard_solow_ivp)

In [19]:
k_soln, = solution.evaluate_solution(ts)
plt.plot(ts, k_soln)
plt.show()

In [20]:
k_normalized_resids, = solution.normalize_residuals(ts)
plt.plot(ts, np.abs(k_normalized_resids))
plt.yscale('log')
plt.show()

In [ ]: