def solve_ConsLaborIntMarg( solution_next, PermShkDstn, TranShkDstn, LivPrb, DiscFac, CRRA, Rfree, PermGroFac, BoroCnstArt, aXtraGrid, TranShkGrid, vFuncBool, CubicBool, WageRte, LbrCost, ): """ Solves one period of the consumption-saving model with endogenous labor supply on the intensive margin by using the endogenous grid method to invert the first order conditions for optimal composite consumption and between consumption and leisure, obviating any search for optimal controls. Parameters ---------- solution_next : ConsumerLaborSolution The solution to the next period's problem; must have the attributes vPfunc and bNrmMinFunc representing marginal value of bank balances and minimum (normalized) bank balances as a function of the transitory shock. PermShkDstn: [np.array] Discrete distribution of permanent productivity shocks. TranShkDstn: [np.array] Discrete distribution of transitory productivity shocks. LivPrb : float Survival probability; likelihood of being alive at the beginning of the succeeding period. DiscFac : float Intertemporal discount factor. CRRA : float Coefficient of relative risk aversion over the composite good. Rfree : float Risk free interest rate on assets retained at the end of the period. PermGroFac : float Expected permanent income growth factor for next period. BoroCnstArt: float or None Borrowing constraint for the minimum allowable assets to end the period with. Currently not handled, must be None. aXtraGrid: np.array Array of "extra" end-of-period asset values-- assets above the absolute minimum acceptable level. TranShkGrid: np.array Grid of transitory shock values to use as a state grid for interpolation. vFuncBool: boolean An indicator for whether the value function should be computed and included in the reported solution. Not yet handled, must be False. CubicBool: boolean An indicator for whether the solver should use cubic or linear interpolation. Cubic interpolation is not yet handled, must be False. WageRte: float Wage rate per unit of labor supplied. LbrCost: float Cost parameter for supplying labor: u_t = U(x_t), x_t = c_t*z_t^LbrCost, where z_t is leisure = 1 - Lbr_t. Returns ------- solution_now : ConsumerLaborSolution The solution to this period's problem, including a consumption function cFunc, a labor supply function LbrFunc, and a marginal value function vPfunc; each are defined over normalized bank balances and transitory prod shock. Also includes bNrmMinNow, the minimum permissible bank balances as a function of the transitory productivity shock. """ # Make sure the inputs for this period are valid: CRRA > LbrCost/(1+LbrCost) # and CubicBool = False. CRRA condition is met automatically when CRRA >= 1. frac = 1.0 / (1.0 + LbrCost) if CRRA <= frac * LbrCost: print( "Error: make sure CRRA coefficient is strictly greater than alpha/(1+alpha)." ) sys.exit() if BoroCnstArt is not None: print( "Error: Model cannot handle artificial borrowing constraint yet. ") sys.exit() if vFuncBool or CubicBool is True: print("Error: Model cannot handle cubic interpolation yet.") sys.exit() # Unpack next period's solution and the productivity shock distribution, and define the inverse (marginal) utilty function vPfunc_next = solution_next.vPfunc TranShkPrbs = TranShkDstn.pmf TranShkVals = TranShkDstn.X.flatten() PermShkPrbs = PermShkDstn.pmf PermShkVals = PermShkDstn.X.flatten() TranShkCount = TranShkPrbs.size PermShkCount = PermShkPrbs.size uPinv = lambda X: CRRAutilityP_inv(X, gam=CRRA) # Make tiled versions of the grid of a_t values and the components of the shock distribution aXtraCount = aXtraGrid.size bNrmGrid = aXtraGrid # Next period's bank balances before labor income # Replicated axtraGrid of b_t values (bNowGrid) for each transitory (productivity) shock bNrmGrid_rep = np.tile(np.reshape(bNrmGrid, (aXtraCount, 1)), (1, TranShkCount)) # Replicated transitory shock values for each a_t state TranShkVals_rep = np.tile(np.reshape(TranShkVals, (1, TranShkCount)), (aXtraCount, 1)) # Replicated transitory shock probabilities for each a_t state TranShkPrbs_rep = np.tile(np.reshape(TranShkPrbs, (1, TranShkCount)), (aXtraCount, 1)) # Construct a function that gives marginal value of next period's bank balances *just before* the transitory shock arrives # Next period's marginal value at every transitory shock and every bank balances gridpoint vPNext = vPfunc_next(bNrmGrid_rep, TranShkVals_rep) # Integrate out the transitory shocks (in TranShkVals direction) to get expected vP just before the transitory shock vPbarNext = np.sum(vPNext * TranShkPrbs_rep, axis=1) # Transformed marginal value through the inverse marginal utility function to "decurve" it vPbarNvrsNext = uPinv(vPbarNext) # Linear interpolation over b_{t+1}, adding a point at minimal value of b = 0. vPbarNvrsFuncNext = LinearInterp(np.insert(bNrmGrid, 0, 0.0), np.insert(vPbarNvrsNext, 0, 0.0)) # "Recurve" the intermediate marginal value function through the marginal utility function vPbarFuncNext = MargValueFuncCRRA(vPbarNvrsFuncNext, CRRA) # Get next period's bank balances at each permanent shock from each end-of-period asset values # Replicated grid of a_t values for each permanent (productivity) shock aNrmGrid_rep = np.tile(np.reshape(aXtraGrid, (aXtraCount, 1)), (1, PermShkCount)) # Replicated permanent shock values for each a_t value PermShkVals_rep = np.tile(np.reshape(PermShkVals, (1, PermShkCount)), (aXtraCount, 1)) # Replicated permanent shock probabilities for each a_t value PermShkPrbs_rep = np.tile(np.reshape(PermShkPrbs, (1, PermShkCount)), (aXtraCount, 1)) bNrmNext = (Rfree / (PermGroFac * PermShkVals_rep)) * aNrmGrid_rep # Calculate marginal value of end-of-period assets at each a_t gridpoint # Get marginal value of bank balances next period at each shock vPbarNext = (PermGroFac * PermShkVals_rep)**(-CRRA) * vPbarFuncNext(bNrmNext) # Take expectation across permanent income shocks EndOfPrdvP = (DiscFac * Rfree * LivPrb * np.sum(vPbarNext * PermShkPrbs_rep, axis=1, keepdims=True)) # Compute scaling factor for each transitory shock TranShkScaleFac_temp = (frac * (WageRte * TranShkGrid)**(LbrCost * frac) * (LbrCost**(-LbrCost * frac) + LbrCost**frac)) # Flip it to be a row vector TranShkScaleFac = np.reshape(TranShkScaleFac_temp, (1, TranShkGrid.size)) # Use the first order condition to compute an array of "composite good" x_t values corresponding to (a_t,theta_t) values xNow = (np.dot(EndOfPrdvP, TranShkScaleFac))**(-1.0 / (CRRA - LbrCost * frac)) # Transform the composite good x_t values into consumption c_t and leisure z_t values TranShkGrid_rep = np.tile(np.reshape(TranShkGrid, (1, TranShkGrid.size)), (aXtraCount, 1)) xNowPow = xNow**frac # Will use this object multiple times in math below # Find optimal consumption from optimal composite good cNrmNow = (( (WageRte * TranShkGrid_rep) / LbrCost)**(LbrCost * frac)) * xNowPow # Find optimal leisure from optimal composite good LsrNow = (LbrCost / (WageRte * TranShkGrid_rep))**frac * xNowPow # The zero-th transitory shock is TranShk=0, and the solution is to not work: Lsr = 1, Lbr = 0. cNrmNow[:, 0] = uPinv(EndOfPrdvP.flatten()) LsrNow[:, 0] = 1.0 # Agent cannot choose to work a negative amount of time. When this occurs, set # leisure to one and recompute consumption using simplified first order condition. # Find where labor would be negative if unconstrained violates_labor_constraint = LsrNow > 1.0 EndOfPrdvP_temp = np.tile(np.reshape(EndOfPrdvP, (aXtraCount, 1)), (1, TranShkCount)) cNrmNow[violates_labor_constraint] = uPinv( EndOfPrdvP_temp[violates_labor_constraint]) LsrNow[violates_labor_constraint] = 1.0 # Set up z=1, upper limit # Calculate the endogenous bNrm states by inverting the within-period transition aNrmNow_rep = np.tile(np.reshape(aXtraGrid, (aXtraCount, 1)), (1, TranShkGrid.size)) bNrmNow = (aNrmNow_rep - WageRte * TranShkGrid_rep + cNrmNow + WageRte * TranShkGrid_rep * LsrNow) # Add an extra gridpoint at the absolute minimal valid value for b_t for each TranShk; # this corresponds to working 100% of the time and consuming nothing. bNowArray = np.concatenate((np.reshape(-WageRte * TranShkGrid, (1, TranShkGrid.size)), bNrmNow), axis=0) # Consume nothing cNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), cNrmNow), axis=0) # And no leisure! LsrNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), LsrNow), axis=0) LsrNowArray[0, 0] = 1.0 # Don't work at all if TranShk=0, even if bNrm=0 LbrNowArray = 1.0 - LsrNowArray # Labor is the complement of leisure # Get (pseudo-inverse) marginal value of bank balances using end of period # marginal value of assets (envelope condition), adding a column of zeros # zeros on the left edge, representing the limit at the minimum value of b_t. vPnvrsNowArray = np.concatenate((np.zeros( (1, TranShkGrid.size)), uPinv(EndOfPrdvP_temp))) # Construct consumption and marginal value functions for this period bNrmMinNow = LinearInterp(TranShkGrid, bNowArray[0, :]) # Loop over each transitory shock and make a linear interpolation to get lists # of optimal consumption, labor and (pseudo-inverse) marginal value by TranShk cFuncNow_list = [] LbrFuncNow_list = [] vPnvrsFuncNow_list = [] for j in range(TranShkGrid.size): # Adjust bNrmNow for this transitory shock, so bNrmNow_temp[0] = 0 bNrmNow_temp = bNowArray[:, j] - bNowArray[0, j] # Make consumption function for this transitory shock cFuncNow_list.append(LinearInterp(bNrmNow_temp, cNowArray[:, j])) # Make labor function for this transitory shock LbrFuncNow_list.append(LinearInterp(bNrmNow_temp, LbrNowArray[:, j])) # Make pseudo-inverse marginal value function for this transitory shock vPnvrsFuncNow_list.append( LinearInterp(bNrmNow_temp, vPnvrsNowArray[:, j])) # Make linear interpolation by combining the lists of consumption, labor and marginal value functions cFuncNowBase = LinearInterpOnInterp1D(cFuncNow_list, TranShkGrid) LbrFuncNowBase = LinearInterpOnInterp1D(LbrFuncNow_list, TranShkGrid) vPnvrsFuncNowBase = LinearInterpOnInterp1D(vPnvrsFuncNow_list, TranShkGrid) # Construct consumption, labor, pseudo-inverse marginal value functions with # bNrmMinNow as the lower bound. This removes the adjustment in the loop above. cFuncNow = VariableLowerBoundFunc2D(cFuncNowBase, bNrmMinNow) LbrFuncNow = VariableLowerBoundFunc2D(LbrFuncNowBase, bNrmMinNow) vPnvrsFuncNow = VariableLowerBoundFunc2D(vPnvrsFuncNowBase, bNrmMinNow) # Construct the marginal value function by "recurving" its pseudo-inverse vPfuncNow = MargValueFuncCRRA(vPnvrsFuncNow, CRRA) # Make a solution object for this period and return it solution = ConsumerLaborSolution(cFunc=cFuncNow, LbrFunc=LbrFuncNow, vPfunc=vPfuncNow, bNrmMin=bNrmMinNow) return solution
# Model parameters # Parameters that need to be fixed # Relative risk aversion. This is fixed at 2 in order to mantain # the analytical solution that we use, from Carroll (2000) CRRA = 2 # Parameters that can be changed. w = 1 # Deterministic wage per period. willCstFac = 0.35 # Fraction of resources charged by lawyer for writing a will. DiscFac = 0.98 # Time-discount factor. # Define utility (and related) functions u = lambda x: CRRAutility(x,CRRA) uP = lambda x: CRRAutilityP(x, CRRA) uPinv = lambda x: CRRAutilityP_inv(x, CRRA) # Create a grid for market resources mGrid = (aGrid-aGrid[0])*1.5 mGridPlots = np.linspace(w,10*w,100) mGridPlotsC = np.insert(mGridPlots,0,0) # Transformations for value funtion interpolation vTransf = lambda x: np.exp(x) vUntransf = lambda x: np.log(x) # %% [markdown] # # The third (last) period of life # # In the last period of life, the agent's problem is determined by his total amount of resources $m_3$ and a state variable $W$ that indicates whether he wrote a will ($W=1$) or not ($W=0$). #
def update_solution_terminal(self): """ Updates the terminal period solution and solves for optimal consumption and labor when there is no future. Parameters ---------- None Returns ------- None """ t = -1 TranShkGrid = self.TranShkGrid[t] LbrCost = self.LbrCost[t] WageRte = self.WageRte[t] bNrmGrid = np.insert( self.aXtraGrid, 0, 0.0 ) # Add a point at b_t = 0 to make sure that bNrmGrid goes down to 0 bNrmCount = bNrmGrid.size # 201 TranShkCount = TranShkGrid.size # = (7,) bNrmGridTerm = np.tile( np.reshape(bNrmGrid, (bNrmCount, 1)), (1, TranShkCount )) # Replicated bNrmGrid for each transitory shock theta_t TranShkGridTerm = np.tile( TranShkGrid, (bNrmCount, 1) ) # Tile the grid of transitory shocks for the terminal solution. (201,7) # Array of labor (leisure) values for terminal solution LsrTerm = np.minimum( (LbrCost / (1.0 + LbrCost)) * (bNrmGridTerm / (WageRte * TranShkGridTerm) + 1.0), 1.0, ) LsrTerm[0, 0] = 1.0 LbrTerm = 1.0 - LsrTerm # Calculate market resources in terminal period, which is consumption mNrmTerm = bNrmGridTerm + LbrTerm * WageRte * TranShkGridTerm cNrmTerm = mNrmTerm # Consume everything we have # Make a bilinear interpolation to represent the labor and consumption functions LbrFunc_terminal = BilinearInterp(LbrTerm, bNrmGrid, TranShkGrid) cFunc_terminal = BilinearInterp(cNrmTerm, bNrmGrid, TranShkGrid) # Compute the effective consumption value using consumption value and labor value at the terminal solution xEffTerm = LsrTerm**LbrCost * cNrmTerm vNvrsFunc_terminal = BilinearInterp(xEffTerm, bNrmGrid, TranShkGrid) vFunc_terminal = ValueFuncCRRA(vNvrsFunc_terminal, self.CRRA) # Using the envelope condition at the terminal solution to estimate the marginal value function vPterm = LsrTerm**LbrCost * CRRAutilityP(xEffTerm, gam=self.CRRA) vPnvrsTerm = CRRAutilityP_inv( vPterm, gam=self.CRRA ) # Evaluate the inverse of the CRRA marginal utility function at a given marginal value, vP vPnvrsFunc_terminal = BilinearInterp(vPnvrsTerm, bNrmGrid, TranShkGrid) vPfunc_terminal = MargValueFuncCRRA( vPnvrsFunc_terminal, self.CRRA) # Get the Marginal Value function bNrmMin_terminal = ConstantFunction( 0.0 ) # Trivial function that return the same real output for any input self.solution_terminal = ConsumerLaborSolution( cFunc=cFunc_terminal, LbrFunc=LbrFunc_terminal, vFunc=vFunc_terminal, vPfunc=vPfunc_terminal, bNrmMin=bNrmMin_terminal, )