#!/usr/bin/env python # coding: utf-8 # # Foundations of Computational Economics #30 # # by Fedor Iskhakov, ANU # # # ## Cake eating in discrete world # # # # # [https://youtu.be/IwKxNceuar4](https://youtu.be/IwKxNceuar4) # # Description: Cake eating problem setup. Solution “on the grid”. # ### Cake eating problem # # # # # - Cake of initial size $ W_0 $ # - How much of the cake to eat each period $ t $, $ c_t $? # - Time is discrete, $ t=1,2,\dots,\infty $ # - What is not eaten in period $ t $ is left for the future $ W_{t+1}=W_t-c_t $ # ### Model specification and parametrization # # - **Choices of the decision maker** # - How much cake to eat # - **State space of the problem** # - A full list of variables that are relevant to the choices in question # - **Preferences of the decision maker** # - Utility flow from cake consumption # - Discount factor # - **Beliefs of the decision agents about how the state will evolve** # - Transition density/probabilities of the states # - May be conditional on the choices # ### Preferences in the cake eating # # Let the flow utility be given by # # $$ # u(c_{t})=\log(c_t) # $$ # # Overall goal is to maximize the discounted expected utility # # $$ # \max_{\{c_{t}\}_{0}^{\infty}}\sum_{t=0}^{\infty}\beta^{t}u(c_{t}) # \longrightarrow \max # $$ # ### Value function # # **Value function** $ V(W_t) $ = the maximum attainable # value given the size of cake $ W_t $ (in period $ t $) # # - State space is given by single variable $ W_t $ # - Transition of the variable (**rather, beliefs**) depends on the choice # # # $$ # W_{t+1}=W_t-c_t # $$ # ### Bellman equation (recursive problem) # # $$ # \begin{eqnarray*} # V(W_{0}) & = & \max_{\{c_{t}\}_{0}^{\infty}}\sum_{t=0}^{\infty}\beta^{t}u(c_{t}) \\ # & = & \max_{0 \le c_{0}\le W_0}\{u(c_{0})+\beta\max_{\{c_{t}\}_{1}^{\infty}}\sum_{t=1}^{\infty}\beta^{t-1}u(c_{t})\} \\ # & = & \max_{0 \le c_{0}\le W_0}\{u(c_{0})+\beta V(W_{1})\} # \end{eqnarray*} # $$ # # $$ # V(W_{t})=\max_{0 \le c_{t} \le W_t}\big\{u(c_{t})+\beta V(\underset{=W_{t}-c_{t}}{\underbrace{W_{t+1}}})\big\} # $$ # ### Recap: components of the dynamic model # # - **State variables** — vector of variables that describe all relevant # information about the modeled decision process, $ W_t $ # - **Decision variables** — vector of variables describing the choices, # $ c_t $ # - **Instantaneous payoff** — utility function, $ u(c_t) $, with # time separable discounted utility # - **Motion rules** — agent’s beliefs of how state variable evolve # through time, conditional on choices, $ W_{t+1}=W_t-c_t $ # - **Value function** — maximum attainable utility, $ V(W_t) $ # - **Policy function** — mapping from state space to action space that # returns the optimal choice, $ c^{\star}(W_t) $ # ### Maybe we can find analytic solution? # # - Start with a (good) guess of $ V(W)=A+B\log W $ # $$ # \begin{eqnarray*} # V(W) & = & \max_{c}\big\{u(c)+\beta V(W-c)\big\} \\ # A+B\log W & = & \max_{c} \big\{\log c+\beta(A+B\log (W-c)) \big\} # \end{eqnarray*} # $$ # - Determine $ A $ and $ B $ and find the optimal rule for cake # consumption. # - This is only possible in **few** models! # F.O.C. for $ c $ # # $$ # \frac{1}{c} - \frac{\beta B}{W - c} = 0, \quad c = \frac {W} {1 + \beta B}, W - c = \frac {\beta B W} {1 + \beta B} # $$ # # Then we have # # $$ # A + B\log W = \log W + \log\frac{1}{1+\beta B} + # \beta A + \beta B \log W + \beta B \log\frac{\beta B}{1+\beta B} # $$ # # $$ # \begin{eqnarray*} # A &=& \beta A + \log\frac{1}{1+\beta B} + \beta B \log\frac{\beta B}{1+\beta B} \\ # B &=& 1 + \beta B # \end{eqnarray*} # $$ # After some algebra # # $$ # c^{\star}(W) = \frac {W} {1 + \beta B} = \frac {W} {1 + \frac{\beta}{1-\beta}} = (1-\beta)W # $$ # # $$ # V(W) = \frac{\log(W)}{1-\beta} + \frac{\log(1-\beta)}{1-\beta} + \frac{\beta \log(\beta)}{(1-\beta)^2} # $$ # ### Bellman operator # # The Bellman equation becomes operator in functional space # # $$ # T(V)(W) \equiv \max_{0 \le c \le W} \big[u(c)+\beta V(W-c)\big] # $$ # # The Bellman equations is then $ V(W) = T({V})(W) $, with the # solution given by the fixed point (solution to $ T({V}) = V $) # ### Value function iterations (VFI) # # - Start with an arbitrary guess $ V_0(W) $ # (will see next time that the initial guess is not important) # - At each iteration $ i $ compute # # # $$ # \begin{eqnarray*} # V_i(W) = T(V_{i-1})(W) &=& # \max_{0 \le c \le W} \big\{u(c)+\beta V_{i-1}(W-c) \big \} \\ # c_{i-1}(W) &=& # \underset{0 \le c \le W}{\arg\max} \big\{u(c)+\beta V_{i-1}(W-c) \big \} # \end{eqnarray*} # $$ # # - Repeat until convergence # ### Numerical implementation of the Bellman operator # # - Cake is continuous $ \rightarrow $ value function is a function # of continuous variable # - Solution: **discretize** $ W $ # Construct a *grid* (vector) of cake-sizes # $ \vec{W}\in\{0,\dots\overline{W}\} $ # # # $$ # V_{i}(\vec{W})=\max_{0 \le c \le \vec{W}}\{u(c)+\beta V_{i-1}(\vec{W}-c)\} # $$ # # - Compute value and policy function sequentially point-by-point # - May need to compute the value function *between grid points* # $ \Rightarrow $ Interpolation and function approximation # ### Can interpolation be avoided? # # - Note that conditional on $ W_t $, the choice of $ c $ defines # $ W_{t+1} $ # - Can replace $ c $ with $ W_{t+1} $ in Bellman equation so # that **next period cake size is the decision variable** # - Solving “on the grid” # ### Adjustment to the Bellman equation # # $$ # V_{i}(\vec{W})=\max_{0 \le \vec{W}' \le \vec{W}}\{u(\vec{W}-\vec{W}')+\beta V_{i-1}(\vec{W}')\} # $$ # # - Compute value and policy function sequentially point-by-point # - Note that grid $ \vec{W}\in\{0,\dots\overline{W}\} $ is used # twice: for state space and for decision space # # # *Can you spot the potential problem?* # In[1]: import numpy as np class cake_ongrid(): '''Simple class to implement cake eating problem on the grid''' def __init__(self,beta=.9, Wbar=10, ngrid=50): '''Initializer''' self.beta = beta # Discount factor self.Wbar = Wbar # Upper bound on cake size self.ngrid = ngrid # Number of grid points self.epsilon = np.finfo(float).eps # smallest positive float number self.grid = np.linspace(self.epsilon,Wbar,ngrid) # grid for both state and decision space def bellman(self,V0): '''Bellman operator, V0 is one-dim vector of values on grid''' c = self.grid - self.grid[:,np.newaxis] # current state in columns and choices in rows c[c==0] = self.epsilon # add small quantity to avoid log(0) mask = c>0 # mask off infeasible choices matV1 = np.full((self.ngrid,self.ngrid),-np.inf) # init V with -inf matV0 = np.repeat(V0.reshape(self.ngrid,1),self.ngrid,1) #current value function repeated in columns matV1[mask] = np.log(c[mask])+self.beta*matV0[mask] # maximand of the Bellman equation V1 = np.amax(matV1,axis=0,keepdims=False) # maximum in every column c1 = self.grid - self.grid[np.argmax(matV1,axis=0)] # consumption (index of maximum in every column) return V1, c1 def solve(self, maxiter=1000, tol=1e-4, callback=None): '''Solves the model using VFI (successive approximations)''' V0=np.log(self.grid) # on first iteration assume consuming everything for iter in range(maxiter): V1,c1=self.bellman(V0) if callback: callback(iter,self.grid,V1,c1) # callback for making plots if np.all(abs(V1-V0) < tol): break V0=V1 else: # when i went up to maxiter print('No convergence: maximum number of iterations achieved!') return V1,c1 # In[2]: model = cake_ongrid(beta=0.92,Wbar=10,ngrid=50) V,c = model.solve() print(c) # In[3]: import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') plt.rcParams['axes.autolimit_mode'] = 'round_numbers' plt.rcParams['axes.xmargin'] = 0 plt.rcParams['axes.ymargin'] = 0 plt.rcParams['patch.force_edgecolor'] = True from cycler import cycler plt.rcParams['axes.prop_cycle'] = cycler(color='bgrcmyk') fig1, ax1 = plt.subplots(figsize=(12,8)) plt.grid(b=True, which='both', color='0.65', linestyle='-') ax1.set_title('Value function convergence with VFI') ax1.set_xlabel('Cake size, W') ax1.set_ylabel('Value function') def callback(iter,grid,v,c): '''Callback function for DP solver''' if iter<5 or iter%10==0: ax1.plot(grid[1:],v[1:],label='iter={:3d}'.format(iter),linewidth=1.5) V,c = model.solve(callback=callback) plt.legend(loc=4) # plt.savefig('cake1value.eps', format='eps', dpi=300) plt.show() # ### How to measure numerical errors? # # - In our case there is an analytic solution # # # $$ # c^{\star}(W) = (1-\beta)W # $$ # # $$ # V(W) = \frac{\log(W)}{1-\beta} + \frac{\log(1-\beta)}{1-\beta} + \frac{\beta \log(\beta)}{(1-\beta)^2} # $$ # ### When there is no analytic solution # # We can find some **derived theoretical property** of the model # and check if it holds in the computed numerical solution # # - Typically very dense (slow) grid is used in place of true solution # - We’ll look at this in more detail later # ### Comparison of value function # In[4]: fun = lambda w: np.log(w)/(1 - model.beta) + np.log(1 - model.beta)/(1 - model.beta) + model.beta*np.log(model.beta)/((1 - model.beta)**2) grid = model.grid plt.plot(grid[1:],V[1:],linewidth=1.5) plt.plot(grid[1:],fun(grid[1:]),linewidth=1.5) # ### Comparison of policy function # In[5]: apol = lambda w: (1 - model.beta) * w grid = model.grid plt.plot(grid[1:],c[1:],linewidth=1.5) plt.plot(grid[1:],apol(grid[1:]),linewidth=1.5) # In[6]: m = cake_ongrid(beta=0.92,Wbar=10,ngrid=250) V,c = m.solve() plt.plot(m.grid[1:],c[1:],linewidth=1.5) plt.plot(m.grid[1:],apol(m.grid[1:]),linewidth=1.5) # ### Conclusion # # Solving “on the grid” allows to avoid interpolation of the value function, # but leads to huge inaccuracies for low levels of wealth! # #### Further learning resources # # - 📖 Adda and Russell Cooper “Dynamic Economics. Quantitative Methods and Applications.” *Chapters: 2, 3.3* # - QuantEcon DP section # [https://lectures.quantecon.org/py/index_dynamic_programming.html](https://lectures.quantecon.org/py/index_dynamic_programming.html)