def test_spli_shape(self): n, a, b = 35, -3, 3 basis = BasisSpline(n, a, b) assert_equal(basis.nodes.size, n) assert_equal(basis.Phi().shape, (n, n)) assert_equal(basis._diff(0, 2).shape, (n - 2, n)) assert_equal(basis._diff(0, -3).shape, (n + 3, n)) assert_equal(basis().shape, (n, )) nx = 24 x = np.linspace(a, b, nx) assert_equal(basis.Phi(x).shape, (nx, n)) assert_equal(basis.Phi(x, 1).shape, (nx, n)) assert_equal(basis.Phi(x, -1).shape, (nx, n)) assert_equal(basis(x).shape, (nx, ))
def test_cheb_2d(self): n, a, b = [5, 9], -3, 3 s = 2 nn = n[0] * n[1] basis = BasisSpline(n, a, b, s=s) assert_equal(basis.nodes.shape, (len(n), nn)) assert_equal(basis.Phi().shape, (nn, nn)) assert_equal(basis._diff(0, 2).shape, (n[0] - 2, n[0])) assert_equal(basis._diff(1, -3).shape, (n[1] + 3, n[1])) assert_equal(basis().shape, (s, nn)) nx = [15, 16] nnx = nx[0] * nx[1] x = gridmake([np.linspace(a, b, j) for j in nx]) assert_equal(basis.Phi(x).shape, (nnx, nn)) assert_equal(basis.Phi(x, [[1], [0]]).shape, (nnx, nn)) assert_equal(basis.Phi(x, [[0], [-1]]).shape, (nnx, nn)) assert_equal(basis(x).shape, (s, nnx))
# The critical exercise price is the price at which the value of exercising the option $K-\exp(p)$ equals the discounted expected value of keeping the option one more period $\delta E_\epsilon V(p + \epsilon)$. To find it, we set it as a nonlinear rootfinding problem by using the ```NLP``` class; here we assume that the option strike price is $K=1$ and that the discount factor is $\delta=0.9998$ # In[5]: K = 1.0 delta = 0.9998 f = NLP(lambda p: K - np.exp(p) - delta * Value(p)) # Notice that we have not defined the ```Value(p)``` function yet. This function is unknown, so we are going to approximate it with a cubic spline, setting 500 nodes between -1 and 1. Since the basis is expressed in terms of log-prices, this interval corresponds to prices between 0.3679 and 2.7183. # In[6]: n = 500 pmin = -1 # minimum log price pmax = 1 # maximum log price Value = BasisSpline(n, pmin, pmax, labels=['logprice'], l=['value']) print(Value) # In the last expression, by passing the option `l` with a one-element list we are telling the ```BasisSpline``` class that we a single function named "value". On creation, the function will be set by default to $V(p)=0$ for all values of $p$, which conveniently corresponds to the terminal condition of this problem. # ## Finding the critical exercise prices # # We are going to find the prices recursively, starting form a option in the expiration date. Notice that the solution to this problem is trivial: since next-period value is zero, the exercise price is $K$. Either way, we can find it numerically by calling the ```zero``` method on the `f` object. # In[7]: pcrit[0] = f.zero(0.0) # Next, for each possible price shock, we compute next period log-price by adding the shock to current log-prices (the nodes of the Value object). Then, we use each next-period price to compute the expected value of an option with one-period to maturity (save the values in ```v```). We update the value function to reflect the new time-to-maturity and use ```broyden``` to solve for the critical value. We repeat this procedure until we reach the $T=300$ horizon. # In[8]:
# Model Parameters pbar = 1.0 # long-run mean profit gamma = 0.7 # profit autoregressive coefficient kappa = 10 # cost of reopenning idle firm sigma = 1.0 # standard deviation of profit shock delta = 0.9 # discount factor # Continuous State Shock Distribution m = 5 # number of profit shocks [e, w] = qnwnorm(m, 0, sigma**2) # profit shocks and probabilities # Approximation Structure n = 250 # number of collocation nodes pmin = -20 # minimum profit pmax = 20 # maximum profit basis = BasisSpline(n, pmin, pmax) # basis functions def profit(p, x, i, j): return p * j - kappa * (1 - i) * j def transition(p, x, i, j, in_, e): return pbar + gamma * (p - pbar) + e # Model model = DPmodel(BasisSpline(n, pmin, pmax, labels=['profit']), profit, transition, i=['idle', 'active'],
# In[5]: sigma = 5 m = 15 e, w = qnwnorm(m, 0, sigma**2) # ### Approximation Structure # # To discretize the continuous state variable, we use a cubic spline basis with $n=150$ nodes between $w_\min=0$ and $w_\max=200$. # In[6]: n = 150 wmin = 0 wmax = 200 basis = BasisSpline(n, wmin, wmax, labels=['wage']) # ## SOLUTION # # To represent the model, we create an instance of ```DPmodel```. Here, we assume a discout factor of $\delta=0.95$. # In[7]: model = DPmodel(basis, reward, transition, i=['unemployed', 'employed'], j=['idle', 'active'], discount=0.95, e=e, w=w,
from compecon import BasisSpline, DPmodel from compecon.quad import qnwlogn from demos.setup import np, plt, demo ## FORMULATION # Model Parameters a = [6, 0.8] # demand function parameters b = [7, 1.0] # cost function parameters delta = 0.9 # discount factor # Approximation Structure n = 51 # number of collocation nodes smin = 0 # minimum state smax = 10 # maximum state basis = BasisSpline(n, smin, smax, labels=['stock']) # basis functions def bounds(s, i, j): return np.zeros_like(s), s.copy() def reward(s, q, i, j): a0, a1 = a b0, b1 = b f = (a0 - b0 + b1 * s) * q - (a1 + b1 / 2) * q**2 fx = (a0 - b0 + b1 * s) - 2 * (a1 + b1 / 2) * q fxx = -2 * (a1 + b1 / 2) * np.ones_like(s) return f, fx, fxx
Phi = np.array([x**j for j in np.arange(n)]) basisplot(x, Phi, 'Monomial Basis Functions on [0,1]') # ### % Plot Chebychev basis functions and nodes # In[7]: B = BasisChebyshev(n, a, b) basisplot(x, B.Phi(x).T, 'Chebychev Polynomial Basis Functions on [0,1]') nodeplot(B.nodes, 'Chebychev Nodes on [0,1]') # ### % Plot linear spline basis functions and nodes # In[8]: L = BasisSpline(n, a, b, k=1) basisplot(x, L.Phi(x).T.toarray(), 'Linear Spline Basis Functions on [0,1]') nodeplot(L.nodes, 'Linear Spline Standard Nodes on [0,1]') # ### % Plot cubic spline basis functions and nodes # In[9]: C = BasisSpline(n, a, b, k=3) basisplot(x, C.Phi(x).T.toarray(), 'Cubic Spline Basis Functions on [0,1]') nodeplot(C.nodes, 'Cubic Spline Standard Nodes on [0,1]') # In[10]: demo.savefig(figures)
off = 0.05 xlims = [xmin - off, xmax + off] n = 401 x = np.linspace(xmin, xmax, n) y = f(x) ymin, ymax = y.min(), y.max() ywid = ymax - ymin ylims = [ymin - 0.5*ywid, ymax + 0.1*ywid] # In[4]: figs = [] for nnode in 3, 5, 9: F = BasisSpline(nnode, xmin, xmax, k=1, f=f) xnodes = F.nodes[0] xx = np.r_[x, xnodes] xx.sort() figs.append(demo.figure('Linear Spline with %d nodes' % nnode, '', '', xlims, ylims, figsize=[10,5])) plt.plot(xx, f(xx), lw=3) # true function plt.plot(xx, F(xx), 'r', lw=1) # approximation plt.yticks(ylims, ['', '']) xe = ['$x_{%d}$' % k for k in range(nnode)] xe[0], xe[-1] = '$x_0=a$', '$x_{%d}=b$' % (nnode-1) plt.xticks(xnodes, xe, fontsize=18) for i, xi in enumerate(xnodes): plt.vlines(xi, ylims[0], F(xi), 'gray','--')
delta = 0.9 # discount factor # Deterministic Steady-State sstar = (pbar - beta[0]) / beta[1] # deterministic steady-state state # Continuous State Shock Distribution m = 3 # number of market price shocks mu = np.log(pbar) - sigma**2 / 2 # mean log price p, w = qnwlogn(m, mu, sigma**2) # market price shocks and probabilities q = np.tile(w, (m, 1)) # market price transition probabilities # Approximation Structure n = 50 # number of collocation nodes smin = 0 # minimum state smax = 20 # maximum state basis = BasisSpline(n, smin, smax, labels=['lagged production']) # basis functions def bounds(s, i, j): return np.zeros_like(s), np.full(s.shape, np.inf) def reward(s, q, i, j): f = p[i] * q - (beta[0] * q + 0.5 * beta[1] * q**2) - 0.5 * alpha * ( (q - s)**2) fx = p[i] - beta[0] - beta[1] * q - alpha * (q - s) fxx = (-beta[1] - alpha) * np.ones_like(s) return f, fx, fxx def transition(s, q, i, j, in_, e):
# In[3]: def h(s): return np.array(s + gamma*(smax - s)) # ## SOLUTION # # ### Code the approximant and the residual # In[4]: ns = 200 vhat = BasisSpline(ns,0,smax,k=3) # In[5]: def vhat1(s): return price*s - kappa + delta * vhat(h(0)) def vhat0(s): return delta * vhat(h(s)) # In[6]: def resid(c,s=vhat.nodes): vhat.c = c return vhat(s) - np.maximum(vhat0(s), vhat1(s))
n = 10 # order of interpolatioin # Construct refined uniform grid for error ploting x = nodeunif(1001, a, b) # Compute actual and fitted values on grid y, d, s = f(x) # actual # Construct and evaluate Chebychev interpolant C = BasisChebyshev(n, a, b, f=f) # chose basis functions yc = C(x) # values dc = C(x, 1) # first derivative sc = C(x, 2) # second derivative # Construct and evaluate cubic spline interpolant S = BasisSpline(n, a, b, f=f) # chose basis functions ys = S(x) # values ds = S(x, 1) # first derivative ss = S(x, 2) # second derivative # Plot function approximation error plt.figure() plt.subplot(2, 1, 1), plt.plot(x, y - yc[0]) plt.ylabel('Chebychev') plt.title('Function Approximation Error') plt.subplot(2, 1, 2) plt.plot(x, y - ys[0]) plt.ylabel('Cubic Spline') plt.xlabel('x')
demo.figure('Chebychev Approximation Error for exp(-x)', 'x', 'Error') plt.plot(xgrid, yapp - yact) plt.plot(xgrid, np.zeros(ngrid), 'k--', linewidth=2) # The plot indicates that an order 10 Chebychev approximation scheme, produces approximation errors # no bigger in magnitude than 6x10^-10. The approximation error exhibits the "Chebychev equioscillation # property", oscilating relatively uniformly throughout the approximation domain. # # This commonly occurs when function being approximated is very smooth, as is the case here but should not # be expected when the function is not smooth. Further notice how the approximation error is exactly 0 at the # approximation nodes --- which is true by contruction. # Let us repeat the approximation exercise, this time constructing a # 21-function cubic spline approximant: n = 21 # order of approximation S = BasisSpline(n, a, b, f=f) # define basis yapp = S(xgrid) # approximant values at grid nodes demo.figure('Cubic Spline Approximation Error for exp(-x)', 'x', 'Error') plt.plot(xgrid, yapp - yact) plt.plot(xgrid, np.zeros(ngrid), 'k--', linewidth=2) # The plot indicates that an order 21 cubic spline approximation scheme produces approximation errors # no bigger in magnitude than 1.2x10^-6, about four orders of magnitude worse than with Chebychev polynomials. # Let us repeat the approximation exercise, this time constructing a # 31-function linear spline approximant: n = 31 # order of approximation L = BasisSpline(n, a, b, k=1, f=f) # define basis functions yapp = L(xgrid) # fitted values at grid nodes demo.figure('Linear Spline Approximation Error for exp(-x)', 'x',
smax = 0.5 gamma = 0.1 delta = 0.9 # ### State Space # The state variable is the stand biomass, $s\in [0,s_{\max}]$. # # Here, we represent it with a cubic spline basis, with $n=200$ nodes. # # In[3]: n = 200 basis = BasisSpline(n, 0, smax, labels=['biomass']) # ### Action Space # The action variable is $j\in\{0:\text{'keep'},\quad 1:\text{'clear cut'}\}$. # # In[4]: options = ['keep', 'clear-cut'] # ### Reward function # If the farmer clears the stand, the profit is the value of selling the biomass $ps$ minus the cost of clearing and replanting $\kappa$, otherwise the profit is zero.
x = nodeunif(2001, a, b) def subfig(k, x, y, xlim, ylim, title): plt.subplot(2, 2, k) plt.plot(x, y) plt.xlim(xlim) plt.ylim(ylim) plt.title(title) for ifunc, ff in enumerate(funcs): # Construct interpolants C = BasisChebyshev(n, a, b, f=ff) S = BasisSpline(n, a, b, f=ff) L = BasisSpline(n, a, b, k=1, f=ff) # Compute actual and fitted values on grid y = ff(x) # actual yc = C(x) # Chebychev approximant ys = S(x) # cubic spline approximant yl = L(x) # linear spline approximant # Plot function approximations plt.figure() ymin = np.floor(y.min()) ymax = np.ceil(y.max()) xlim = [a, b] ylim = [-0.2, 1.2] if ifunc == 2 else [ymin, ymax]
# # \begin{equation} # \sum_{j=1}^{n} c_j\varphi_j(\pi_i,d_i) = \max_{x\in\{0,1\}}\left\{\pi_i x − K_1(1−d_i)x − K_0 d_i(1−x) + \delta\sum_{k=1}^{m}\sum_{j=1}^{n}w_k c_j \varphi_j(\hat{\pi}_{ik},x)\right\} # \end{equation} # # where $\hat\pi_{ik}=g(\pi_i,\epsilon_k)$ and where $\epsilon_k$ and $w_k$ represent quadrature nodes and weights for # the normal shock. # # For the approximation, we use a cubic spline basis with $n=250$ nodes between $p_{\min}=-20$ and $p_\max=20$. # In[5]: n = 250 pmin = -20 pmax = 20 basis = BasisSpline(n, pmin, pmax, labels=['profit']) print(basis) # Discrete states and discrete actions are # In[6]: dstates = ['idle', 'active'] dactions = ['close', 'open'] # The Bellman equation is represeted by a ```DPmodel``` object, where we assume a discount factor of $\delta=0.9$. Notice that the discrete state transition is deterministic, with transition matrix # \begin{equation} # h=\begin{bmatrix}0&0\\1&1\end{bmatrix} # \end{equation} # In[7]:
# smax stand carrying capacity # gamma biomass growth parameter # delta discount factor ## FORMULATION # Model Parameters price = 1.0 / 2 # unit price of biomass kappa = 0.2 # clearcut-replant cost smax = 0.5 # stand carrying capacity gamma = 0.1 # biomass growth parameter delta = 0.9 # discount factor # Approximation Structure n = 200 # number of collocation nodes basis = BasisSpline(n, 0, smax, labels=['stand biomass']) # basis functions # Model Structure def reward(s, x, i, j): return (price * s - kappa) * j def transition(s, x, i, j, in_, e): if j: return np.full_like(s, gamma * smax) else: return s + gamma * (smax - s)
cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_zlabel('error') plt.title('Chebychev Approximation Error') # The plot indicates that an order 11 by 11 Chebychev approximation scheme # produces approximation errors no bigger in magnitude than 1x10^-10. # Let us repeat the approximation exercise, this time constructing an order # 21 by 21 cubic spline approximation scheme: n = [21, 21] # order of approximation S = BasisSpline(n, a, b, f=f) yapp = S(X) # approximant values at grid nodes error = (yapp - yact).reshape(nplot) ax = fig.add_subplot(1, 2, 2, projection='3d') ax.plot_surface(X1, X2, error, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False) ax.set_xlabel('$x_1$') ax.set_ylabel('$x_2$') ax.set_zlabel('error') plt.title('Cubic Spline Approximation Error')
sigma = 0.15 # standard deviation of unit profit shock delta = 0.9 # discount factor # Continuous State Shock Distribution m = 5 # number of unit profit shocks [e,w] = qnwnorm(m,0,sigma ** 2) # unit profit shocks and probabilities # Deterministic Discrete State Transitions h = np.zeros((2, A)) h[0, :-1] = np.arange(1, A) # Approximation Structure n = 200 # number of collocation nodes pmin = 0 # minimum unit profit pmax = 2 # maximum unit profit basis = BasisSpline(n, pmin, pmax, labels=['unit profit']) # basis functions # Model Structure def profit(p, x, i, j): a = i + 1 if j or a == A: return p * 50 - kappa else: return p * (alpha[0] + alpha[1] * a + alpha[2] * a ** 2 ) def transition(p, x, i, j, in_, e): return pbar + gamma * (p - pbar) + e model = DPmodel(basis, profit, transition, # i=['a={}'.format(a+1) for a in range(A)],
m = 15 # number of wage shocks e, w = qnwnorm(m, 0, sigma**2) # wage shocks # Stochastic Discrete State Transition Probabilities q = np.zeros((2, 2, 2)) q[1, 0, 1] = p0 q[1, 1, 1] = p1 q[:, :, 0] = 1 - q[:, :, 1] # Model Structure # Approximation Structure n = 150 # number of collocation nodes wmin = 0 # minimum wage wmax = 200 # maximum wage basis = BasisSpline(n, wmin, wmax, labels=['wage']) # basis functions def reward(w, x, employed, active): if active: return w.copy() if employed else np.full_like( w, u ) # the copy is critical!!! otherwise it passes the pointer to w!! else: return np.full_like(w, v) def transition(w, x, i, j, in_, e): return wbar + gamma * (w - wbar) + e