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 [ ]: