def test_same_length(self): linear = LinearInterp(self.x_list, self.z_list) self.assertEqual(linear(1.5), 3.5) linear = LinearInterp(self.x_array, self.z_array) self.assertEqual(linear(1.5), 3.5) linear = LinearInterp(self.x_array_t, self.z_array_t) self.assertEqual(linear(1.5), 3.5)
def post_solve(self): self.solution_fast = deepcopy(self.solution) if self.cycles == 0: terminal = 1 else: terminal = self.cycles self.solution[terminal] = self.solution_terminal_cs for i in range(terminal): solution = self.solution[i] # Construct the consumption function as a linear interpolation. cFunc = LinearInterp(solution.mNrm, solution.cNrm) """ Defines the value and marginal value functions for this period. Uses the fact that for a perfect foresight CRRA utility problem, if the MPC in period t is :math:`\kappa_{t}`, and relative risk aversion :math:`\rho`, then the inverse value vFuncNvrs has a constant slope of :math:`\kappa_{t}^{-\rho/(1-\rho)}` and vFuncNvrs has value of zero at the lower bound of market resources mNrmMin. See PerfForesightConsumerType.ipynb documentation notebook for a brief explanation and the links below for a fuller treatment. https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/consumption/PerfForesightCRRA/#vFuncAnalytical https://www.econ2.jhu.edu/people/ccarroll/SolvingMicroDSOPs/#vFuncPF """ vFuncNvrs = LinearInterp( np.array([solution.mNrmMin, solution.mNrmMin + 1.0]), np.array([0.0, solution.vFuncNvrsSlope]), ) vFunc = ValueFuncCRRA(vFuncNvrs, self.CRRA) vPfunc = MargValueFuncCRRA(cFunc, self.CRRA) consumer_solution = ConsumerSolution( cFunc=cFunc, vFunc=vFunc, vPfunc=vPfunc, mNrmMin=solution.mNrmMin, hNrm=solution.hNrm, MPCmin=solution.MPCmin, MPCmax=solution.MPCmax, ) Ex_IncNext = 1.0 # Perfect foresight income of 1 # Add mNrmStE to the solution and return it consumer_solution.mNrmStE = _add_mNrmStENumba( self.Rfree, self.PermGroFac[i], solution.mNrm, solution.cNrm, solution.mNrmMin, Ex_IncNext, _find_mNrmStE, ) self.solution[i] = consumer_solution
def use_points_for_interpolation(self, cNrm, mNrm, interpolator): """ Make a basic solution object with a consumption function and marginal value function (unconditional on the preference shock). Parameters ---------- cNrm : np.array Consumption points for interpolation. mNrm : np.array Corresponding market resource points for interpolation. interpolator : function A function that constructs and returns a consumption function. Returns ------- solution_now : ConsumerSolution The solution to this period's consumption-saving problem, with a consumption function, marginal value function, and minimum m. """ # Make the preference-shock specific consumption functions PrefShkCount = self.PrefShkVals.size cFunc_list = [] for j in range(PrefShkCount): MPCmin_j = self.MPCminNow * self.PrefShkVals[j]**(1.0 / self.CRRA) cFunc_this_shock = LowerEnvelope( LinearInterp( mNrm[j, :], cNrm[j, :], intercept_limit=self.hNrmNow * MPCmin_j, slope_limit=MPCmin_j, ), self.cFuncNowCnst, ) cFunc_list.append(cFunc_this_shock) # Combine the list of consumption functions into a single interpolation cFuncNow = LinearInterpOnInterp1D(cFunc_list, self.PrefShkVals) # Make the ex ante marginal value function (before the preference shock) m_grid = self.aXtraGrid + self.mNrmMinNow vP_vec = np.zeros_like(m_grid) for j in range( PrefShkCount): # numeric integration over the preference shock vP_vec += (self.uP(cFunc_list[j](m_grid)) * self.PrefShkPrbs[j] * self.PrefShkVals[j]) vPnvrs_vec = self.uPinv(vP_vec) vPfuncNow = MargValueFuncCRRA(LinearInterp(m_grid, vPnvrs_vec), self.CRRA) # Store the results in a solution object and return it solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=self.mNrmMinNow) return solution_now
def def_BoroCnst(self, BoroCnstArt): """ Defines the constrained portion of the consumption function as cFuncNowCnst, an attribute of self. Parameters ---------- BoroCnstArt : float or None Borrowing constraint for the minimum allowable assets to end the period with. If it is less than the natural borrowing constraint, then it is irrelevant; BoroCnstArt=None indicates no artificial bor- rowing constraint. Returns ------- None """ # Make temporary grids of income shocks and next period income values ShkCount = self.TranShkValsNext.size pLvlCount = self.pLvlGrid.size PermShkVals_temp = np.tile( np.reshape(self.PermShkValsNext, (1, ShkCount)), (pLvlCount, 1)) TranShkVals_temp = np.tile( np.reshape(self.TranShkValsNext, (1, ShkCount)), (pLvlCount, 1)) pLvlNext_temp = (np.tile( np.reshape(self.pLvlNextFunc(self.pLvlGrid), (pLvlCount, 1)), (1, ShkCount), ) * PermShkVals_temp) # Find the natural borrowing constraint for each persistent income level aLvlMin_candidates = (self.mLvlMinNext(pLvlNext_temp) - TranShkVals_temp * pLvlNext_temp) / self.Rfree aLvlMinNow = np.max(aLvlMin_candidates, axis=1) self.BoroCnstNat = LinearInterp(np.insert(self.pLvlGrid, 0, 0.0), np.insert(aLvlMinNow, 0, 0.0)) # Define the minimum allowable mLvl by pLvl as the greater of the natural and artificial borrowing constraints if self.BoroCnstArt is not None: self.BoroCnstArt = LinearInterp(np.array([0.0, 1.0]), np.array([0.0, self.BoroCnstArt])) self.mLvlMinNow = UpperEnvelope(self.BoroCnstArt, self.BoroCnstNat) else: self.mLvlMinNow = self.BoroCnstNat # Define the constrained consumption function as "consume all" shifted by mLvlMin cFuncNowCnstBase = BilinearInterp( np.array([[0.0, 0.0], [1.0, 1.0]]), np.array([0.0, 1.0]), np.array([0.0, 1.0]), ) self.cFuncNowCnst = VariableLowerBoundFunc2D(cFuncNowCnstBase, self.mLvlMinNow)
def makeEndOfPrdvPfuncCond(self): ''' Construct the end-of-period marginal value function conditional on next period's state. Parameters ---------- None Returns ------- EndofPrdvPfunc_cond : MargValueFunc The end-of-period marginal value function conditional on a particular state occuring in the succeeding period. ''' # Get data to construct the end-of-period marginal value function (conditional on next state) self.aNrm_cond = self.prepareToCalcEndOfPrdvP() self.EndOfPrdvP_cond= self.calcEndOfPrdvPcond() EndOfPrdvPnvrs_cond = self.uPinv(self.EndOfPrdvP_cond) # "decurved" marginal value if self.CubicBool: EndOfPrdvPP_cond = self.calcEndOfPrdvPP() EndOfPrdvPnvrsP_cond = EndOfPrdvPP_cond*self.uPinvP(self.EndOfPrdvP_cond) # "decurved" marginal marginal value # Construct the end-of-period marginal value function conditional on the next state. if self.CubicBool: EndOfPrdvPnvrsFunc_cond = CubicInterp(self.aNrm_cond,EndOfPrdvPnvrs_cond, EndOfPrdvPnvrsP_cond,lower_extrap=True) else: EndOfPrdvPnvrsFunc_cond = LinearInterp(self.aNrm_cond,EndOfPrdvPnvrs_cond, lower_extrap=True) EndofPrdvPfunc_cond = MargValueFunc(EndOfPrdvPnvrsFunc_cond,self.CRRA) # "recurve" the interpolated marginal value function return EndofPrdvPfunc_cond
def priceOnePeriod(self, Pfunc_next, logDGrid): # Create 'tiled arrays' rows are state today, columns are value of # tomorrow's shock dGrid_N = len(logDGrid) shock_N = self.DivProcess.nApprox # Today's dividends logD_now = np.tile(np.reshape(logDGrid, (dGrid_N,1)), (1,shock_N)) d_now = np.exp(logD_now) # Tomorrow's dividends Shk_next = np.tile(self.DivProcess.ShkAppDstn.X, (dGrid_N, 1)) Shk_next_pmf = np.tile(self.DivProcess.ShkAppDstn.pmf, (dGrid_N, 1)) logD_next = self.DivProcess.alpha * logD_now + Shk_next d_next = np.exp(logD_next) # Tomorrow's prices P_next = Pfunc_next(logD_next) # Compute the RHS of the pricing equation, pre-expectation SDF = self.DiscFac * self.uP(d_next / d_now) Payoff_next = P_next + d_next # Take expectation and discount P_now = np.sum(SDF*Payoff_next*Shk_next_pmf, axis = 1, keepdims=True) # Create new interpolating price function Pfunc_now = LinearInterp(logDGrid, P_now.flatten(), lower_extrap=True) return(Pfunc_now)
def priceOnePeriod(self, Pfunc_next, logDGrid): # Create a function that, given current dividends # and the value of next period's shock, returns # the discounted value derived from the asset next period. def discounted_value(shock, log_d_now): # Find dividends d_now = np.exp(log_d_now) log_d_next = self.DivProcess.α * log_d_now + shock d_next = np.exp(log_d_next) # Payoff and sdf payoff_next = Pfunc_next(log_d_next) + d_next SDF = self.DiscFac * self.uP(d_next / d_now) return SDF * payoff_next # The price at a given d_t is the expectation of the discounted value. # Compute it at every d in our grid. The expectation is taken over next # period's shocks prices_now = calc_expectation(self.DivProcess.ShkAppDstn, discounted_value, logDGrid) # Create new interpolating price function Pfunc_now = LinearInterp(logDGrid, prices_now, lower_extrap=True) return (Pfunc_now)
def solveWorkingDeaton(solution_next, aXtraGrid, mGrid, EGMVector, par, Util, UtilP, UtilP_inv, TranInc, TranIncWeights): choice = 2 choiceCount = len(solution_next.ChoiceSols) # Next-period initial wealth given exogenous aXtraGrid # This needs to be made more general like the rest of the code mrs_tp1 = par.Rfree * numpy.expand_dims(aXtraGrid, axis=1) + par.YWork * TranInc.T mws_tp1 = par.Rfree * numpy.expand_dims(aXtraGrid, axis=1) + par.YWork * TranInc.T m_tp1s = (mrs_tp1, mws_tp1) # Prepare variables for EGM step C_tp1s = tuple(solution_next.ChoiceSols[d].CFunc(m_tp1s[d]) for d in range(choiceCount)) Vs = tuple( numpy.divide(-1.0, solution_next.ChoiceSols[d].V_TFunc(m_tp1s[d])) for d in range(choiceCount)) # Due to the transformation on V being monotonic increasing, we can just as # well use the transformed values to do this discrete envelope step. V, ChoiceProb_tp1 = calcLogSumChoiceProbs(numpy.stack(Vs), par.sigma) # Calculate the expected marginal utility and expected value function PUtilPsum = sum(ChoiceProb_tp1[i, :] * UtilP(C_tp1s[i], i + 1) for i in range(choiceCount)) EUtilP_tp1 = par.Rfree * numpy.dot(PUtilPsum, TranIncWeights.T) EV_tp1 = numpy.squeeze( numpy.dot(numpy.expand_dims(V, axis=1), TranIncWeights.T)) # EGM step m_t, C_t, Ev = calcEGMStep(EGMVector, aXtraGrid, EV_tp1, EUtilP_tp1, par, Util, UtilP, UtilP_inv, choice) V_T = numpy.divide(-1.0, Util(C_t, choice) + par.DiscFac * Ev) # We do the envelope step in transformed value space for accuracy. The values # keep their monotonicity under our transformation. m_t, C_t, V_T = calcMultilineEnvelope(m_t, C_t, V_T, mGrid) # The solution is the working specific consumption function and value function # specifying lower_extrap=True for C is easier than explicitly adding a 0, # as it'll be linear in the constrained interval anyway. CFunc = LinearInterp(m_t, C_t, lower_extrap=True) V_TFunc = LinearInterp(m_t, V_T, lower_extrap=True) return ChoiceSpecificSolution(m_t, C_t, CFunc, V_T, V_TFunc)
def calcExtraSaves(saveCommon, rs, ws, par, mGrid): if saveCommon: # To save the pre-discrete choice expected consumption and value function, # we need to interpolate onto the same grid between the two. We know that # ws.C and ws.V_T are on the ws.m grid, so we use that to interpolate. Crs = LinearInterp(rs.m, rs.C)(mGrid) V_rs = numpy.divide(-1, LinearInterp(rs.m, rs.V_T)(mGrid)) Cws = ws.C V_ws = numpy.divide(-1, ws.V_T) V, P = calcLogSumChoiceProbs(numpy.stack((V_rs, V_ws)), par.sigma) C = (P*numpy.stack((Crs, Cws))).sum(axis=0) else: C, V_T, P = None, None, None return C, numpy.divide(-1.0, V), P
def make_linear_cFunc(self, mLvl, pLvl, cLvl): """ Makes a quasi-bilinear interpolation to represent the (unconstrained) consumption function. Parameters ---------- mLvl : np.array Market resource points for interpolation. pLvl : np.array Persistent income level points for interpolation. cLvl : np.array Consumption points for interpolation. Returns ------- cFuncUnc : LinearInterp The unconstrained consumption function for this period. """ cFunc_by_pLvl_list = [] # list of consumption functions for each pLvl for j in range(pLvl.shape[0]): pLvl_j = pLvl[j, 0] m_temp = mLvl[j, :] - self.BoroCnstNat(pLvl_j) c_temp = cLvl[ j, :] # Make a linear consumption function for this pLvl if pLvl_j > 0: cFunc_by_pLvl_list.append( LinearInterp( m_temp, c_temp, lower_extrap=True, slope_limit=self.MPCminNow, intercept_limit=self.MPCminNow * self.hLvlNow(pLvl_j), )) else: cFunc_by_pLvl_list.append( LinearInterp(m_temp, c_temp, lower_extrap=True)) pLvl_list = pLvl[:, 0] cFuncUncBase = LinearInterpOnInterp1D( cFunc_by_pLvl_list, pLvl_list) # Combine all linear cFuncs cFuncUnc = VariableLowerBoundFunc2D( cFuncUncBase, self.BoroCnstNat ) # Re-adjust for natural borrowing constraint (as lower bound) return cFuncUnc
def solveRetiredDeaton(solution_next, aXtraGrid, EGMVector, par, Util, UtilP, UtilP_inv): choice = 1 rs_tp1 = solution_next.ChoiceSols[0] # Next-period initial wealth given exogenous aXtraGrid m_tp1 = par.Rfree*aXtraGrid + par.YRet # Prepare variables for EGM step EC_tp1 = rs_tp1.CFunc(m_tp1) EV_T_tp1 = rs_tp1.V_TFunc(m_tp1) EV_tp1 = numpy.divide(-1.0, EV_T_tp1) EUtilP_tp1 = par.Rfree*UtilP(EC_tp1, choice) m_t, C_t, Ev = calcEGMStep(EGMVector, aXtraGrid, EV_tp1, EUtilP_tp1, par, Util, UtilP, UtilP_inv, choice) V_T = numpy.divide(-1.0, Util(C_t, choice) + par.DiscFac*Ev) CFunc = LinearInterp(m_t, C_t) V_TFunc = LinearInterp(m_t, V_T) return ChoiceSpecificSolution(m_t, C_t, CFunc, V_T, V_TFunc)
def solveLastChoiceSpecific(self, curChoice): """ Solves the last period of a working agent. Parameters ---------- none Returns ------- none """ m, C = self.mGrid.copy(), self.mGrid.copy() # consume everything V_T = numpy.divide(-1.0, self.Util(self.mGrid, curChoice)) # Interpolants CFunc = lambda coh: LinearInterp(m, C)(coh) V_TFunc = lambda coh: LinearInterp(m, V_T)(coh) return ChoiceSpecificSolution(m, C, CFunc, V_T, V_TFunc)
def set_and_update_values(self, solution_next, IncShkDstn, LivPrb, DiscFac): """ Unpacks some of the inputs (and calculates simple objects based on them), storing the results in self for use by other methods. These include: income shocks and probabilities, next period's marginal value function (etc), the probability of getting the worst income shock next period, the patience factor, human wealth, and the bounding MPCs. Human wealth is stored as a function of persistent income. Parameters ---------- solution_next : ConsumerSolution The solution to next period's one period problem. IncShkDstn : distribution.Distribution A discrete approximation to the income process between the period being solved and the one immediately following (in solution_next). LivPrb : float Survival probability; likelihood of being alive at the beginning of the succeeding period. DiscFac : float Intertemporal discount factor for future utility. Returns ------- None """ # Run basic version of this method ConsIndShockSetup.set_and_update_values(self, solution_next, IncShkDstn, LivPrb, DiscFac) self.mLvlMinNext = solution_next.mLvlMin # Replace normalized human wealth (scalar) with human wealth level as function of persistent income self.hNrmNow = 0.0 pLvlCount = self.pLvlGrid.size IncShkCount = self.PermShkValsNext.size pLvlNext = (np.tile(self.pLvlNextFunc(self.pLvlGrid), (IncShkCount, 1)) * np.tile(self.PermShkValsNext, (pLvlCount, 1)).transpose()) hLvlGrid = (1.0 / self.Rfree * np.sum( (np.tile(self.TranShkValsNext, (pLvlCount, 1)).transpose() * pLvlNext + solution_next.hLvl(pLvlNext)) * np.tile(self.ShkPrbsNext, (pLvlCount, 1)).transpose(), axis=0, )) self.hLvlNow = LinearInterp(np.insert(self.pLvlGrid, 0, 0.0), np.insert(hLvlGrid, 0, 0.0))
def update_pLvlNextFunc(self): """ A dummy method that creates a trivial pLvlNextFunc attribute that has no persistent income dynamics. This method should be overwritten by subclasses in order to make (e.g.) an AR1 income process. Parameters ---------- None Returns ------- None """ pLvlNextFuncBasic = LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])) self.pLvlNextFunc = self.T_cycle * [pLvlNextFuncBasic] self.add_to_time_vary("pLvlNextFunc")
def makeLinearcFunc(self,mNrm,cNrm): ''' Make a linear interpolation to represent the (unconstrained) consumption function conditional on the current period state. Parameters ---------- mNrm : np.array Array of normalized market resource values for interpolation. cNrm : np.array Array of normalized consumption values for interpolation. Returns ------- cFuncUnc: an instance of HARK.interpolation.LinearInterp ''' cFuncUnc = LinearInterp(mNrm,cNrm,self.MPCminNow_j*self.hNrmNow_j,self.MPCminNow_j) return cFuncUnc
def updateSolutionTerminal(self): ''' Update the terminal period solution. Parameters ---------- none Returns ------- none ''' #self.solution_terminal.vFunc = ValueFunc(self.cFunc_terminal_,self.CRRA) terminal_instance = LinearInterp(np.array([0.0, 100.0]), np.array([0.01, 0.0]), lower_extrap=True) self.solution_terminal.vPfunc = BilinearInterpOnInterp1D( [[terminal_instance, terminal_instance], [terminal_instance, terminal_instance]], np.array([0.0, 2.0]), np.array([0.0, 2.0]))
def update_pLvlNextFunc(self): """ A method that creates the pLvlNextFunc attribute as a sequence of linear functions, indicating constant expected permanent income growth across permanent income levels. Draws on the attribute PermGroFac, and installs a special retirement function when it exists. Parameters ---------- None Returns ------- None """ pLvlNextFunc = [] for t in range(self.T_cycle): pLvlNextFunc.append( LinearInterp(np.array([0.0, 1.0]), np.array([0.0, self.PermGroFac[t]]))) self.pLvlNextFunc = pLvlNextFunc self.add_to_time_vary("pLvlNextFunc")
# For ease of exposition we consider the case $\rho = 2$, where [Carroll (2000)](http://www.econ2.jhu.edu/people/ccarroll/Why.pdf) shows that the optimal consumption level is given by # # \begin{equation} # c_3(m_3, W=1) = \min \left[m_3, \frac{-1 + \sqrt{1 + 4(m_3+1)}}{2} \right]. # \end{equation} # # The consumption function shows that $m_3=1$ is the level of resources at which an important change of behavior occurs: agents leave bequests only for $m_3 > 1$. Since an important change of behavior happens at this point, we call it a 'kink-point' and add it to our grids. # %% # Agent without a will mGrid3_no = mGrid cGrid3_no = mGrid vGrid3_no = u(cGrid3_no) # Create functions c3_no = LinearInterp(mGrid3_no, cGrid3_no) # (0,0) is already here. vT3_no = LinearInterp(mGrid3_no, vTransf(vGrid3_no), lower_extrap = True) v3_no = lambda x: vUntransf(vT3_no(x)) # Agent with a will # Define an auxiliary function with the analytical consumption expression c3will = lambda m: np.minimum(m, -0.5 + 0.5*np.sqrt(1+4*(m+1))) # Find the kink point mKink = 1.0 indBelw = mGrid < mKink indAbve = mGrid > mKink mGrid3_wi = np.concatenate([mGrid[indBelw], np.array([mKink]),
def make_cubic_cFunc(self, mLvl, pLvl, cLvl): """ Makes a quasi-cubic spline interpolation of the unconstrained consumption function for this period. Function is cubic splines with respect to mLvl, but linear in pLvl. Parameters ---------- mLvl : np.array Market resource points for interpolation. pLvl : np.array Persistent income level points for interpolation. cLvl : np.array Consumption points for interpolation. Returns ------- cFuncUnc : CubicInterp The unconstrained consumption function for this period. """ # Calculate the MPC at each gridpoint EndOfPrdvPP = (self.DiscFacEff * self.Rfree * self.Rfree * np.sum( self.vPPfuncNext(self.mLvlNext, self.pLvlNext) * self.ShkPrbs_temp, axis=0, )) dcda = EndOfPrdvPP / self.uPP(np.array(cLvl[1:, 1:])) MPC = dcda / (dcda + 1.0) MPC = np.concatenate((np.reshape(MPC[:, 0], (MPC.shape[0], 1)), MPC), axis=1) # Stick an extra MPC value at bottom; MPCmax doesn't work MPC = np.concatenate((self.MPCminNow * np.ones( (1, self.aXtraGrid.size + 1)), MPC), axis=0) # Make cubic consumption function with respect to mLvl for each persistent income level cFunc_by_pLvl_list = [] # list of consumption functions for each pLvl for j in range(pLvl.shape[0]): pLvl_j = pLvl[j, 0] m_temp = mLvl[j, :] - self.BoroCnstNat(pLvl_j) c_temp = cLvl[ j, :] # Make a cubic consumption function for this pLvl MPC_temp = MPC[j, :] if pLvl_j > 0: cFunc_by_pLvl_list.append( CubicInterp( m_temp, c_temp, MPC_temp, lower_extrap=True, slope_limit=self.MPCminNow, intercept_limit=self.MPCminNow * self.hLvlNow(pLvl_j), )) else: # When pLvl=0, cFunc is linear cFunc_by_pLvl_list.append( LinearInterp(m_temp, c_temp, lower_extrap=True)) pLvl_list = pLvl[:, 0] cFuncUncBase = LinearInterpOnInterp1D( cFunc_by_pLvl_list, pLvl_list) # Combine all linear cFuncs cFuncUnc = VariableLowerBoundFunc2D(cFuncUncBase, self.BoroCnstNat) # Re-adjust for lower bound of natural borrowing constraint return cFuncUnc
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
def mkCpol(self): #load the income process Matlabdict = loadmat('inc_process.mat') data = list(Matlabdict.items()) data_array = np.asarray(data) x = data_array[3, 1] Pr = data_array[4, 1] pr = data_array[5, 1] theta = np.concatenate( (np.array([1e-10]).reshape(1, 1), np.exp(x).reshape(1, 12)), axis=1).reshape(13, 1) fin = 0.8820 #job-finding probability sep = 0.0573 #separation probability cmin = 1e-6 # lower bound on consumption #constructing transition Matrix G = np.array([1 - fin]).reshape(1, 1) A = np.concatenate((G, fin * pr), axis=1) K = sep**np.ones(12).reshape(12, 1) D = np.concatenate((K, np.multiply((1 - sep), Pr)), axis=1) Pr = np.concatenate((A, D)) # find new invariate distribution pr = np.concatenate([np.array([0]).reshape(1, 1), pr], axis=1) dif = 1 while dif > 1e-5: pri = pr.dot(Pr) dif = np.amax(np.absolute(pri - pr)) pr = pri fac = ((pssi / theta)**(1 / eta)).reshape(13, 1) tau = (nu * pr[0, 0] + (Rfree - 1) / (Rfree) * B) / (1 - pr[0, 0]) # labor tax z = np.insert(-tau * np.ones(12), 0, nu).T #this needs to be specified differently to incorporate choice of phi and interest rate Matlabcl = loadmat('cl') cldata = list(Matlabcl.items()) cldata = np.array(cldata) cl = cldata[3, 1].reshape(13, 1) Matlabgrid = loadmat('Bgrid') griddata = list(Matlabgrid.items()) datagrid_array = np.array(griddata) Bgrid_uc = datagrid_array[3, 1] #setup grid based on constraint phi gameta = self.CRRA / self.eta Bgrid = [] for i in range(200): if Bgrid_uc[0, i] > -self.phi: Bgrid.append(Bgrid_uc[0, i]) Bgrid = np.array(Bgrid).reshape(1, len(Bgrid)) #next period's solution Cnext = self.solution_next.Cpol phi = D1_4Y * NE * 2 expUtil = np.dot(Pr, (Cnext**(-CRRA))) Cnow = [] Nnow = [] Bnow = [] self.Cnowpol = [] #unconstrained for i in range(13): Cnow.append( np.array(((self.Rfree) * self.DiscFac) * (expUtil[i]**(-1 / self.CRRA)))) #euler equation Nnow.append( np.array( np.maximum( (1 - fac[i] * ((Cnow[i])**(self.CRRA / self.eta))), 0))) #labor supply FOC Bnow.append( np.array(Bgrid[0] / (self.Rfree) + Cnow[i] - theta[i] * Nnow[i] - z[i])) #Budget Constraint #constrained, constructing c pts between -phi and Bnow[i][0] if Bnow[i][0] > -self.phi: c_c = np.linspace(cl[i, 0], Cnow[i][0], 100) n_c = np.maximum(1 - fac[i] * (c_c**gameta), 0) # labor supply b_c = -self.phi / self.Rfree + c_c - theta[i]**n_c - z[ i] # budget Bnow[i] = np.concatenate([b_c[0:98], Bnow[i]]) Cnow[i] = np.concatenate([c_c[0:98], Cnow[i]]) self.Cnowpol.append(LinearInterp(Bnow[i], Cnow[i])) self.Cnowpol = np.array(self.Cnowpol).reshape(13, 1) self.Cpol = [] for i in range(13): self.Cpol.append(self.Cnowpol[i, 0].y_list) self.Cpol = np.array(self.Cpol)
def solveConsPortfolio(solution_next, ShockDstn, IncomeDstn, RiskyDstn, LivPrb, DiscFac, CRRA, Rfree, PermGroFac, BoroCnstArt, aXtraGrid, ShareGrid, vFuncBool, AdjustPrb, DiscreteShareBool, ShareLimit, IndepDstnBool): ''' Solve the one period problem for a portfolio-choice consumer. Parameters ---------- solution_next : PortfolioSolution Solution to next period's problem. ShockDstn : [np.array] List with four arrays: discrete probabilities, permanent income shocks, transitory income shocks, and risky returns. This is only used if the input IndepDstnBool is False, indicating that income and return distributions can't be assumed to be independent. IncomeDstn : [np.array] List with three arrays: discrete probabilities, permanent income shocks, and transitory income shocks. This is only used if the input IndepDsntBool is True, indicating that income and return distributions are independent. RiskyDstn : [np.array] List with two arrays: discrete probabilities and risky asset returns. This is only used if the input IndepDstnBool is True, indicating that income and return distributions are independent. LivPrb : float Survival probability; likelihood of being alive at the beginning of the succeeding period. DiscFac : float Intertemporal discount factor for future utility. CRRA : float Coefficient of relative risk aversion. Rfree : float Risk free interest factor on end-of-period assets. PermGroFac : float Expected permanent income growth factor at the end of this period. BoroCnstArt: float or None Borrowing constraint for the minimum allowable assets to end the period with. In this model, it is *required* to be zero. aXtraGrid: np.array Array of "extra" end-of-period asset values-- assets above the absolute minimum acceptable level. ShareGrid : np.array Array of risky portfolio shares on which to define the interpolation of the consumption function when Share is fixed. vFuncBool: boolean An indicator for whether the value function should be computed and included in the reported solution. AdjustPrb : float Probability that the agent will be able to update his portfolio share. DiscreteShareBool : bool Indicator for whether risky portfolio share should be optimized on the continuous [0,1] interval using the FOC (False), or instead only selected from the discrete set of values in ShareGrid (True). If True, then vFuncBool must also be True. ShareLimit : float Limiting lower bound of risky portfolio share as mNrm approaches infinity. IndepDstnBool : bool Indicator for whether the income and risky return distributions are in- dependent of each other, which can speed up the expectations step. Returns ------- solution_now : PortfolioSolution The solution to the single period consumption-saving with portfolio choice problem. Includes two consumption and risky share functions: one for when the agent can adjust his portfolio share (Adj) and when he can't (Fxd). ''' # Make sure the individual is liquidity constrained. Allowing a consumer to # borrow *and* invest in an asset with unbounded (negative) returns is a bad mix. if BoroCnstArt != 0.0: raise ValueError('PortfolioConsumerType must have BoroCnstArt=0.0!') # Make sure that if risky portfolio share is optimized only discretely, then # the value function is also constructed (else this task would be impossible). if (DiscreteShareBool and (not vFuncBool)): raise ValueError( 'PortfolioConsumerType requires vFuncBool to be True when DiscreteShareBool is True!' ) # Define temporary functions for utility and its derivative and inverse u = lambda x: utility(x, CRRA) uP = lambda x: utilityP(x, CRRA) uPinv = lambda x: utilityP_inv(x, CRRA) n = lambda x: utility_inv(x, CRRA) nP = lambda x: utility_invP(x, CRRA) # Unpack next period's solution vPfuncAdj_next = solution_next.vPfuncAdj dvdmFuncFxd_next = solution_next.dvdmFuncFxd dvdsFuncFxd_next = solution_next.dvdsFuncFxd vFuncAdj_next = solution_next.vFuncAdj vFuncFxd_next = solution_next.vFuncFxd # Major method fork: (in)dependent risky asset return and income distributions if IndepDstnBool: # If the distributions ARE independent... # Unpack the shock distribution IncPrbs_next = IncomeDstn.pmf PermShks_next = IncomeDstn.X[0] TranShks_next = IncomeDstn.X[1] Rprbs_next = RiskyDstn.pmf Risky_next = RiskyDstn.X zero_bound = ( np.min(TranShks_next) == 0. ) # Flag for whether the natural borrowing constraint is zero RiskyMax = np.max(Risky_next) # bNrm represents R*a, balances after asset return shocks but before income. # This just uses the highest risky return as a rough shifter for the aXtraGrid. if zero_bound: aNrmGrid = aXtraGrid bNrmGrid = np.insert(RiskyMax * aXtraGrid, 0, np.min(Risky_next) * aXtraGrid[0]) else: aNrmGrid = np.insert(aXtraGrid, 0, 0.0) # Add an asset point at exactly zero bNrmGrid = RiskyMax * np.insert(aXtraGrid, 0, 0.0) # Get grid and shock sizes, for easier indexing aNrm_N = aNrmGrid.size bNrm_N = bNrmGrid.size Share_N = ShareGrid.size Income_N = IncPrbs_next.size Risky_N = Rprbs_next.size # Make tiled arrays to calculate future realizations of mNrm and Share when integrating over IncomeDstn bNrm_tiled = np.tile(np.reshape(bNrmGrid, (bNrm_N, 1, 1)), (1, Share_N, Income_N)) Share_tiled = np.tile(np.reshape(ShareGrid, (1, Share_N, 1)), (bNrm_N, 1, Income_N)) IncPrbs_tiled = np.tile(np.reshape(IncPrbs_next, (1, 1, Income_N)), (bNrm_N, Share_N, 1)) PermShks_tiled = np.tile(np.reshape(PermShks_next, (1, 1, Income_N)), (bNrm_N, Share_N, 1)) TranShks_tiled = np.tile(np.reshape(TranShks_next, (1, 1, Income_N)), (bNrm_N, Share_N, 1)) # Calculate future realizations of market resources mNrm_next = bNrm_tiled / (PermShks_tiled * PermGroFac) + TranShks_tiled Share_next = Share_tiled # Evaluate realizations of marginal value of market resources next period dvdmAdj_next = vPfuncAdj_next(mNrm_next) if AdjustPrb < 1.: dvdmFxd_next = dvdmFuncFxd_next(mNrm_next, Share_next) dvdm_next = AdjustPrb * dvdmAdj_next + ( 1. - AdjustPrb) * dvdmFxd_next # Combine by adjustment probability else: # Don't bother evaluating if there's no chance that portfolio share is fixed dvdm_next = dvdmAdj_next # Evaluate realizations of marginal value of risky share next period dvdsAdj_next = np.zeros_like( mNrm_next) # No marginal value of Share if it's a free choice! if AdjustPrb < 1.: dvdsFxd_next = dvdsFuncFxd_next(mNrm_next, Share_next) dvds_next = AdjustPrb * dvdsAdj_next + ( 1. - AdjustPrb) * dvdsFxd_next # Combine by adjustment probability else: # Don't bother evaluating if there's no chance that portfolio share is fixed dvds_next = dvdsAdj_next # If the value function has been requested, evaluate realizations of value if vFuncBool: vAdj_next = vFuncAdj_next(mNrm_next) if AdjustPrb < 1.: vFxd_next = vFuncFxd_next(mNrm_next, Share_next) v_next = AdjustPrb * vAdj_next + (1. - AdjustPrb) * vFxd_next else: # Don't bother evaluating if there's no chance that portfolio share is fixed v_next = vAdj_next else: v_next = np.zeros_like(dvdm_next) # Trivial array # Calculate intermediate marginal value of bank balances by taking expectations over income shocks temp_fac_A = uP(PermShks_tiled * PermGroFac) # Will use this in a couple places dvdb_intermed = np.sum(IncPrbs_tiled * temp_fac_A * dvdm_next, axis=2) dvdbNvrs_intermed = uPinv(dvdb_intermed) dvdbNvrsFunc_intermed = BilinearInterp(dvdbNvrs_intermed, bNrmGrid, ShareGrid) dvdbFunc_intermed = MargValueFunc2D(dvdbNvrsFunc_intermed, CRRA) # Calculate intermediate value by taking expectations over income shocks temp_fac_B = (PermShks_tiled * PermGroFac)**(1. - CRRA ) # Will use this below if vFuncBool: v_intermed = np.sum(IncPrbs_tiled * temp_fac_B * v_next, axis=2) vNvrs_intermed = n(v_intermed) vNvrsFunc_intermed = BilinearInterp(vNvrs_intermed, bNrmGrid, ShareGrid) vFunc_intermed = ValueFunc2D(vNvrsFunc_intermed, CRRA) # Calculate intermediate marginal value of risky portfolio share by taking expectations dvds_intermed = np.sum(IncPrbs_tiled * temp_fac_B * dvds_next, axis=2) dvdsFunc_intermed = BilinearInterp(dvds_intermed, bNrmGrid, ShareGrid) # Make tiled arrays to calculate future realizations of bNrm and Share when integrating over RiskyDstn aNrm_tiled = np.tile(np.reshape(aNrmGrid, (aNrm_N, 1, 1)), (1, Share_N, Risky_N)) Share_tiled = np.tile(np.reshape(ShareGrid, (1, Share_N, 1)), (aNrm_N, 1, Risky_N)) Rprbs_tiled = np.tile(np.reshape(Rprbs_next, (1, 1, Risky_N)), (aNrm_N, Share_N, 1)) Risky_tiled = np.tile(np.reshape(Risky_next, (1, 1, Risky_N)), (aNrm_N, Share_N, 1)) # Calculate future realizations of bank balances bNrm Share_next = Share_tiled Rxs = Risky_tiled - Rfree Rport = Rfree + Share_next * Rxs bNrm_next = Rport * aNrm_tiled # Evaluate realizations of value and marginal value after asset returns are realized dvdb_next = dvdbFunc_intermed(bNrm_next, Share_next) dvds_next = dvdsFunc_intermed(bNrm_next, Share_next) if vFuncBool: v_next = vFunc_intermed(bNrm_next, Share_next) else: v_next = np.zeros_like(dvdb_next) # Calculate end-of-period marginal value of assets by taking expectations EndOfPrddvda = DiscFac * LivPrb * np.sum( Rprbs_tiled * Rport * dvdb_next, axis=2) EndOfPrddvdaNvrs = uPinv(EndOfPrddvda) # Calculate end-of-period value by taking expectations if vFuncBool: EndOfPrdv = DiscFac * LivPrb * np.sum(Rprbs_tiled * v_next, axis=2) EndOfPrdvNvrs = n(EndOfPrdv) # Calculate end-of-period marginal value of risky portfolio share by taking expectations EndOfPrddvds = DiscFac * LivPrb * np.sum( Rprbs_tiled * (Rxs * aNrm_tiled * dvdb_next + dvds_next), axis=2) else: # If the distributions are NOT independent... # Unpack the shock distribution ShockPrbs_next = ShockDstn[0] PermShks_next = ShockDstn[1] TranShks_next = ShockDstn[2] Risky_next = ShockDstn[3] zero_bound = ( np.min(TranShks_next) == 0. ) # Flag for whether the natural borrowing constraint is zero # Make tiled arrays to calculate future realizations of mNrm and Share; dimension order: mNrm, Share, shock if zero_bound: aNrmGrid = aXtraGrid else: aNrmGrid = np.insert(aXtraGrid, 0, 0.0) # Add an asset point at exactly zero aNrm_N = aNrmGrid.size Share_N = ShareGrid.size Shock_N = ShockPrbs_next.size aNrm_tiled = np.tile(np.reshape(aNrmGrid, (aNrm_N, 1, 1)), (1, Share_N, Shock_N)) Share_tiled = np.tile(np.reshape(ShareGrid, (1, Share_N, 1)), (aNrm_N, 1, Shock_N)) ShockPrbs_tiled = np.tile(np.reshape(ShockPrbs_next, (1, 1, Shock_N)), (aNrm_N, Share_N, 1)) PermShks_tiled = np.tile(np.reshape(PermShks_next, (1, 1, Shock_N)), (aNrm_N, Share_N, 1)) TranShks_tiled = np.tile(np.reshape(TranShks_next, (1, 1, Shock_N)), (aNrm_N, Share_N, 1)) Risky_tiled = np.tile(np.reshape(Risky_next, (1, 1, Shock_N)), (aNrm_N, Share_N, 1)) # Calculate future realizations of market resources Rport = (1. - Share_tiled) * Rfree + Share_tiled * Risky_tiled mNrm_next = Rport * aNrm_tiled / (PermShks_tiled * PermGroFac) + TranShks_tiled Share_next = Share_tiled # Evaluate realizations of marginal value of market resources next period dvdmAdj_next = vPfuncAdj_next(mNrm_next) if AdjustPrb < 1.: dvdmFxd_next = dvdmFuncFxd_next(mNrm_next, Share_next) dvdm_next = AdjustPrb * dvdmAdj_next + ( 1. - AdjustPrb) * dvdmFxd_next # Combine by adjustment probability else: # Don't bother evaluating if there's no chance that portfolio share is fixed dvdm_next = dvdmAdj_next # Evaluate realizations of marginal value of risky share next period dvdsAdj_next = np.zeros_like( mNrm_next) # No marginal value of Share if it's a free choice! if AdjustPrb < 1.: dvdsFxd_next = dvdsFuncFxd_next(mNrm_next, Share_next) dvds_next = AdjustPrb * dvdsAdj_next + ( 1. - AdjustPrb) * dvdsFxd_next # Combine by adjustment probability else: # Don't bother evaluating if there's no chance that portfolio share is fixed dvds_next = dvdsAdj_next # If the value function has been requested, evaluate realizations of value if vFuncBool: vAdj_next = vFuncAdj_next(mNrm_next) if AdjustPrb < 1.: vFxd_next = vFuncFxd_next(mNrm_next, Share_next) v_next = AdjustPrb * vAdj_next + (1. - AdjustPrb) * vFxd_next else: # Don't bother evaluating if there's no chance that portfolio share is fixed v_next = vAdj_next else: v_next = np.zeros_like(dvdm_next) # Trivial array # Calculate end-of-period marginal value of assets by taking expectations temp_fac_A = uP(PermShks_tiled * PermGroFac) # Will use this in a couple places EndOfPrddvda = DiscFac * LivPrb * np.sum( ShockPrbs_tiled * Rport * temp_fac_A * dvdm_next, axis=2) EndOfPrddvdaNvrs = uPinv(EndOfPrddvda) # Calculate end-of-period value by taking expectations temp_fac_B = (PermShks_tiled * PermGroFac)**(1. - CRRA ) # Will use this below if vFuncBool: EndOfPrdv = DiscFac * LivPrb * np.sum( ShockPrbs_tiled * temp_fac_B * v_next, axis=2) EndOfPrdvNvrs = n(EndOfPrdv) # Calculate end-of-period marginal value of risky portfolio share by taking expectations Rxs = Risky_tiled - Rfree EndOfPrddvds = DiscFac * LivPrb * np.sum( ShockPrbs_tiled * (Rxs * aNrm_tiled * temp_fac_A * dvdm_next + temp_fac_B * dvds_next), axis=2) # Major method fork: discrete vs continuous choice of risky portfolio share if DiscreteShareBool: # Optimization of Share on the discrete set ShareGrid opt_idx = np.argmax(EndOfPrdv, axis=1) Share_now = ShareGrid[ opt_idx] # Best portfolio share is one with highest value cNrmAdj_now = EndOfPrddvdaNvrs[np.arange( aNrm_N), opt_idx] # Take cNrm at that index as well if not zero_bound: Share_now[ 0] = 1. # aNrm=0, so there's no way to "optimize" the portfolio cNrmAdj_now[0] = EndOfPrddvdaNvrs[ 0, -1] # Consumption when aNrm=0 does not depend on Share else: # Optimization of Share on continuous interval [0,1] # For values of aNrm at which the agent wants to put more than 100% into risky asset, constrain them FOC_s = EndOfPrddvds Share_now = np.zeros_like( aNrmGrid) # Initialize to putting everything in safe asset cNrmAdj_now = np.zeros_like(aNrmGrid) constrained = FOC_s[:, -1] > 0. # If agent wants to put more than 100% into risky asset, he is constrained Share_now[constrained] = 1.0 if not zero_bound: Share_now[ 0] = 1. # aNrm=0, so there's no way to "optimize" the portfolio cNrmAdj_now[0] = EndOfPrddvdaNvrs[ 0, -1] # Consumption when aNrm=0 does not depend on Share cNrmAdj_now[constrained] = EndOfPrddvdaNvrs[ constrained, -1] # Get consumption when share-constrained # For each value of aNrm, find the value of Share such that FOC-Share == 0. # This loop can probably be eliminated, but it's such a small step that it won't speed things up much. crossing = np.logical_and(FOC_s[:, 1:] <= 0., FOC_s[:, :-1] >= 0.) for j in range(aNrm_N): if Share_now[j] == 0.: try: idx = np.argwhere(crossing[j, :])[0][0] bot_s = ShareGrid[idx] top_s = ShareGrid[idx + 1] bot_f = FOC_s[j, idx] top_f = FOC_s[j, idx + 1] bot_c = EndOfPrddvdaNvrs[j, idx] top_c = EndOfPrddvdaNvrs[j, idx + 1] alpha = 1. - top_f / (top_f - bot_f) Share_now[j] = (1. - alpha) * bot_s + alpha * top_s cNrmAdj_now[j] = (1. - alpha) * bot_c + alpha * top_c except: print('No optimal controls found for a=' + str(aNrmGrid[j])) # Calculate the endogenous mNrm gridpoints when the agent adjusts his portfolio mNrmAdj_now = aNrmGrid + cNrmAdj_now # Construct the risky share function when the agent can adjust if DiscreteShareBool: mNrmAdj_mid = (mNrmAdj_now[1:] + mNrmAdj_now[:-1]) / 2 mNrmAdj_plus = mNrmAdj_mid * (1. + 1e-12) mNrmAdj_comb = (np.transpose(np.vstack( (mNrmAdj_mid, mNrmAdj_plus)))).flatten() mNrmAdj_comb = np.append(np.insert(mNrmAdj_comb, 0, 0.0), mNrmAdj_now[-1]) Share_comb = (np.transpose(np.vstack( (Share_now, Share_now)))).flatten() ShareFuncAdj_now = LinearInterp(mNrmAdj_comb, Share_comb) else: if zero_bound: Share_lower_bound = ShareLimit else: Share_lower_bound = 1.0 Share_now = np.insert(Share_now, 0, Share_lower_bound) ShareFuncAdj_now = LinearInterp(np.insert(mNrmAdj_now, 0, 0.0), Share_now, intercept_limit=ShareLimit, slope_limit=0.0) # Construct the consumption function when the agent can adjust cNrmAdj_now = np.insert(cNrmAdj_now, 0, 0.0) cFuncAdj_now = LinearInterp(np.insert(mNrmAdj_now, 0, 0.0), cNrmAdj_now) # Construct the marginal value (of mNrm) function when the agent can adjust vPfuncAdj_now = MargValueFunc(cFuncAdj_now, CRRA) # Construct the consumption function when the agent *can't* adjust the risky share, as well # as the marginal value of Share function cFuncFxd_by_Share = [] dvdsFuncFxd_by_Share = [] for j in range(Share_N): cNrmFxd_temp = EndOfPrddvdaNvrs[:, j] mNrmFxd_temp = aNrmGrid + cNrmFxd_temp cFuncFxd_by_Share.append( LinearInterp(np.insert(mNrmFxd_temp, 0, 0.0), np.insert(cNrmFxd_temp, 0, 0.0))) dvdsFuncFxd_by_Share.append( LinearInterp(np.insert(mNrmFxd_temp, 0, 0.0), np.insert(EndOfPrddvds[:, j], 0, EndOfPrddvds[0, j]))) cFuncFxd_now = LinearInterpOnInterp1D(cFuncFxd_by_Share, ShareGrid) dvdsFuncFxd_now = LinearInterpOnInterp1D(dvdsFuncFxd_by_Share, ShareGrid) # The share function when the agent can't adjust his portfolio is trivial ShareFuncFxd_now = IdentityFunction(i_dim=1, n_dims=2) # Construct the marginal value of mNrm function when the agent can't adjust his share dvdmFuncFxd_now = MargValueFunc2D(cFuncFxd_now, CRRA) # If the value function has been requested, construct it now if vFuncBool: # First, make an end-of-period value function over aNrm and Share EndOfPrdvNvrsFunc = BilinearInterp(EndOfPrdvNvrs, aNrmGrid, ShareGrid) EndOfPrdvFunc = ValueFunc2D(EndOfPrdvNvrsFunc, CRRA) # Construct the value function when the agent can adjust his portfolio mNrm_temp = aXtraGrid # Just use aXtraGrid as our grid of mNrm values cNrm_temp = cFuncAdj_now(mNrm_temp) aNrm_temp = mNrm_temp - cNrm_temp Share_temp = ShareFuncAdj_now(mNrm_temp) v_temp = u(cNrm_temp) + EndOfPrdvFunc(aNrm_temp, Share_temp) vNvrs_temp = n(v_temp) vNvrsP_temp = uP(cNrm_temp) * nP(v_temp) vNvrsFuncAdj = CubicInterp( np.insert(mNrm_temp, 0, 0.0), # x_list np.insert(vNvrs_temp, 0, 0.0), # f_list np.insert(vNvrsP_temp, 0, vNvrsP_temp[0])) # dfdx_list vFuncAdj_now = ValueFunc( vNvrsFuncAdj, CRRA) # Re-curve the pseudo-inverse value function # Construct the value function when the agent *can't* adjust his portfolio mNrm_temp = np.tile(np.reshape(aXtraGrid, (aXtraGrid.size, 1)), (1, Share_N)) Share_temp = np.tile(np.reshape(ShareGrid, (1, Share_N)), (aXtraGrid.size, 1)) cNrm_temp = cFuncFxd_now(mNrm_temp, Share_temp) aNrm_temp = mNrm_temp - cNrm_temp v_temp = u(cNrm_temp) + EndOfPrdvFunc(aNrm_temp, Share_temp) vNvrs_temp = n(v_temp) vNvrsP_temp = uP(cNrm_temp) * nP(v_temp) vNvrsFuncFxd_by_Share = [] for j in range(Share_N): vNvrsFuncFxd_by_Share.append( CubicInterp( np.insert(mNrm_temp[:, 0], 0, 0.0), # x_list np.insert(vNvrs_temp[:, j], 0, 0.0), # f_list np.insert(vNvrsP_temp[:, j], 0, vNvrsP_temp[j, 0]))) #dfdx_list vNvrsFuncFxd = LinearInterpOnInterp1D(vNvrsFuncFxd_by_Share, ShareGrid) vFuncFxd_now = ValueFunc2D(vNvrsFuncFxd, CRRA) else: # If vFuncBool is False, fill in dummy values vFuncAdj_now = None vFuncFxd_now = None # Create and return this period's solution return PortfolioSolution(cFuncAdj=cFuncAdj_now, ShareFuncAdj=ShareFuncAdj_now, vPfuncAdj=vPfuncAdj_now, vFuncAdj=vFuncAdj_now, cFuncFxd=cFuncFxd_now, ShareFuncFxd=ShareFuncFxd_now, dvdmFuncFxd=dvdmFuncFxd_now, dvdsFuncFxd=dvdsFuncFxd_now, vFuncFxd=vFuncFxd_now)
def solveFashion(solution_next,DiscFac,conformUtilityFunc,punk_utility,jock_utility,switchcost_J2P,switchcost_P2J,pGrid,pEvolution,pref_shock_mag): ''' Solves a single period of the fashion victim model. Parameters ---------- solution_next: FashionSolution A representation of the solution to the subsequent period's problem. DiscFac: float The intertemporal discount factor. conformUtilityFunc: function Utility as a function of the proportion of the population who wears the same style as the agent. punk_utility: float Direct utility from wearing the punk style this period. jock_utility: float Direct utility from wearing the jock style this period. switchcost_J2P: float Utility cost of switching from jock to punk this period. switchcost_P2J: float Utility cost of switching from punk to jock this period. pGrid: np.array 1D array of "proportion of punks" states spanning [0,1], representing the fraction of agents *currently* wearing punk style. pEvolution: np.array 2D array representing the distribution of next period's "proportion of punks". The pEvolution[i,:] contains equiprobable values of p for next period if p = pGrid[i] today. pref_shock_mag: float Standard deviation of T1EV preference shocks over style. Returns ------- solution_now: FashionSolution A representation of the solution to this period's problem. ''' # Unpack next period's solution VfuncPunkNext = solution_next.VfuncPunk VfuncJockNext = solution_next.VfuncJock # Calculate end-of-period expected value for each style at points on the pGrid EndOfPrdVpunk = DiscFac*np.mean(VfuncPunkNext(pEvolution),axis=1) EndOfPrdVjock = DiscFac*np.mean(VfuncJockNext(pEvolution),axis=1) # Get current period utility flow from each style (without switching cost) Upunk = punk_utility + conformUtilityFunc(pGrid) Ujock = jock_utility + conformUtilityFunc(1.0 - pGrid) # Calculate choice-conditional value for each combination of current and next styles (at each) V_J2J = Ujock + EndOfPrdVjock V_J2P = Upunk - switchcost_J2P + EndOfPrdVpunk V_P2J = Ujock - switchcost_P2J + EndOfPrdVjock V_P2P = Upunk + EndOfPrdVpunk # Calculate the beginning-of-period expected value of each p-state when punk Vboth_P = np.vstack((V_P2J,V_P2P)) Vbest_P = np.max(Vboth_P,axis=0) Vnorm_P = Vboth_P - np.tile(np.reshape(Vbest_P,(1,pGrid.size)),(2,1)) ExpVnorm_P = np.exp(Vnorm_P/pref_shock_mag) SumExpVnorm_P = np.sum(ExpVnorm_P,axis=0) V_P = np.log(SumExpVnorm_P)*pref_shock_mag + Vbest_P switch_P = ExpVnorm_P[0,:]/SumExpVnorm_P # Calculate the beginning-of-period expected value of each p-state when jock Vboth_J = np.vstack((V_J2J,V_J2P)) Vbest_J = np.max(Vboth_J,axis=0) Vnorm_J = Vboth_J - np.tile(np.reshape(Vbest_J,(1,pGrid.size)),(2,1)) ExpVnorm_J = np.exp(Vnorm_J/pref_shock_mag) SumExpVnorm_J = np.sum(ExpVnorm_J,axis=0) V_J = np.log(SumExpVnorm_J)*pref_shock_mag + Vbest_J switch_J = ExpVnorm_J[1,:]/SumExpVnorm_J # Make value and policy functions for each style VfuncPunkNow = LinearInterp(pGrid,V_P) VfuncJockNow = LinearInterp(pGrid,V_J) switchFuncPunkNow = LinearInterp(pGrid,switch_P) switchFuncJockNow = LinearInterp(pGrid,switch_J) # Make and return this period's solution solution_now = FashionSolution(VfuncJock=VfuncJockNow, VfuncPunk=VfuncPunkNow, switchFuncJock=switchFuncJockNow, switchFuncPunk=switchFuncPunkNow) return solution_now
class FashionVictimType(AgentType): ''' A class for representing types of agents in the fashion victim model. Agents make a binary decision over which style to wear (jock or punk), subject to switching costs. They receive utility directly from their chosen style and from the proportion of the population that wears the same style. ''' _solution_terminal = FashionSolution(VfuncJock=LinearInterp(np.array([0.0, 1.0]),np.array([0.0,0.0])), VfuncPunk=LinearInterp(np.array([0.0, 1.0]),np.array([0.0,0.0])), switchFuncJock=NullFunc(), switchFuncPunk=NullFunc()) def __init__(self,**kwds): ''' Instantiate a new FashionVictim with given data. Parameters ---------- **kwds : keyword arguments Any number of keyword arguments key=value; each value will be assigned to the attribute key in the new instance. Returns ------- new instance of FashionVictimType ''' # Initialize a basic AgentType AgentType.__init__(self,solution_terminal=FashionVictimType._solution_terminal,cycles=0,pseudo_terminal=True,**kwds) # Add class-specific features self.time_inv = ['DiscFac','conformUtilityFunc','punk_utility','jock_utility','switchcost_J2P','switchcost_P2J','pGrid','pEvolution','pref_shock_mag'] self.time_vary = [] self.solveOnePeriod = solveFashion self.update() def updateEvolution(self): ''' Updates the "population punk proportion" evolution array. Fasion victims believe that the proportion of punks in the subsequent period is a linear function of the proportion of punks this period, subject to a uniform shock. Given attributes of self pNextIntercept, pNextSlope, pNextCount, pNextWidth, and pGrid, this method generates a new array for the attri- bute pEvolution, representing a discrete approximation of next period states for each current period state in pGrid. Parameters ---------- none Returns ------- none ''' self.pEvolution = np.zeros((self.pCount,self.pNextCount)) for j in range(self.pCount): pNow = self.pGrid[j] pNextMean = self.pNextIntercept + self.pNextSlope*pNow dist = approxUniform(N=self.pNextCount,bot=pNextMean-self.pNextWidth,top=pNextMean+self.pNextWidth)[1] self.pEvolution[j,:] = dist def update(self): ''' Updates the non-primitive objects needed for solution, using primitive attributes. This includes defining a "utility from conformity" function conformUtilityFunc, a grid of population punk proportions, and an array of future punk proportions (for each value in the grid). Results are stored as attributes of self. Parameters ---------- none Returns ------- none ''' self.conformUtilityFunc = lambda x : stats.beta.pdf(x,self.uParamA,self.uParamB) self.pGrid = np.linspace(0.0001,0.9999,self.pCount) self.updateEvolution() def reset(self): ''' Resets this agent type to prepare it for a new simulation run. This includes resetting the random number generator and initializing the style of each agent of this type. ''' self.resetRNG() sNow = np.zeros(self.pop_size) Shk = self.RNG.rand(self.pop_size) sNow[Shk < self.p_init] = 1 self.sNow = sNow def preSolve(self): ''' Updates the punk proportion evolution array by calling self.updateEvolution(). Parameters ---------- none Returns ------- none ''' # This step is necessary in the general equilibrium framework, where a # new evolution rule is calculated after each simulation run, but only # the sufficient statistics describing it are sent back to agents. self.updateEvolution() def postSolve(self): ''' Unpack the behavioral and value functions for more parsimonious access. Parameters ---------- none Returns ------- none ''' self.switchFuncPunk = self.solution[0].switchFuncPunk self.switchFuncJock = self.solution[0].switchFuncJock self.VfuncPunk = self.solution[0].VfuncPunk self.VfuncJock = self.solution[0].VfuncJock def simOnePrd(self): ''' Simulate one period of the fashion victom model for this type. Each agent receives an idiosyncratic preference shock and chooses whether to change styles (using the optimal decision rule). Parameters ---------- none Returns ------- none ''' pNow = self.pNow sPrev = self.sNow J2Pprob = self.switchFuncJock(pNow) P2Jprob = self.switchFuncPunk(pNow) Shks = self.RNG.rand(self.pop_size) J2P = np.logical_and(sPrev == 0,Shks < J2Pprob) P2J = np.logical_and(sPrev == 1,Shks < P2Jprob) sNow = copy(sPrev) sNow[J2P] = 1 sNow[P2J] = 0 self.sNow = sNow def marketAction(self): ''' The "market action" for a FashionType in the general equilibrium setting. Simulates a single period using self.simOnePrd(). Parameters ---------- none Returns ------- none ''' self.simOnePrd()
def post_solve(self): self.solution_fast = deepcopy(self.solution) if self.cycles == 0: cycles = 1 else: cycles = self.cycles self.solution[-1] = self.solution_terminal_cs for i in range(cycles): for j in range(self.T_cycle): solution = self.solution[i * self.T_cycle + j] # Define the borrowing constraint (limiting consumption function) cFuncNowCnst = LinearInterp( np.array([solution.mNrmMin, solution.mNrmMin + 1]), np.array([0.0, 1.0]), ) """ Constructs a basic solution for this period, including the consumption function and marginal value function. """ if self.CubicBool: # Makes a cubic spline interpolation of the unconstrained consumption # function for this period. cFuncNowUnc = CubicInterp( solution.mNrm, solution.cNrm, solution.MPC, solution.cFuncLimitIntercept, solution.cFuncLimitSlope, ) else: # Makes a linear interpolation to represent the (unconstrained) consumption function. # Construct the unconstrained consumption function cFuncNowUnc = LinearInterp( solution.mNrm, solution.cNrm, solution.cFuncLimitIntercept, solution.cFuncLimitSlope, ) # Combine the constrained and unconstrained functions into the true consumption function cFuncNow = LowerEnvelope(cFuncNowUnc, cFuncNowCnst) # Make the marginal value function and the marginal marginal value function vPfuncNow = MargValueFuncCRRA(cFuncNow, self.CRRA) # Pack up the solution and return it consumer_solution = ConsumerSolution( cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=solution.mNrmMin, hNrm=solution.hNrm, MPCmin=solution.MPCmin, MPCmax=solution.MPCmax, ) if self.vFuncBool: vNvrsFuncNow = CubicInterp( solution.mNrmGrid, solution.vNvrs, solution.vNvrsP, solution.MPCminNvrs * solution.hNrm, solution.MPCminNvrs, ) vFuncNow = ValueFuncCRRA(vNvrsFuncNow, self.CRRA) consumer_solution.vFunc = vFuncNow if self.CubicBool or self.vFuncBool: _searchFunc = ( _find_mNrmStECubic if self.CubicBool else _find_mNrmStELinear ) # Add mNrmStE to the solution and return it consumer_solution.mNrmStE = _add_mNrmStEIndNumba( self.PermGroFac[j], self.Rfree, solution.Ex_IncNext, solution.mNrmMin, solution.mNrm, solution.cNrm, solution.MPC, solution.MPCmin, solution.hNrm, _searchFunc, ) self.solution[i * self.T_cycle + j] = consumer_solution
def solveConsMarkovALT(solution_next,IncomeDstn,LivPrb,DiscFac,CRRA,Rfree,PermGroFac,uPfac, MrkvArray,BoroCnstArt,aXtraGrid,vFuncBool,CubicBool): ''' Solves a single period consumption-saving problem with risky income and stochastic transitions between discrete states, in a Markov fashion. Has identical inputs as solveConsIndShock, except for a discrete Markov transitionrule MrkvArray. Markov states can differ in their interest factor, permanent growth factor, and income distribution, so the inputs Rfree, PermGroFac, and IncomeDstn are arrays or lists specifying those values in each (succeeding) Markov state. Parameters ---------- solution_next : ConsumerSolution The solution to next period's one period problem. IncomeDstn : DiscreteDistribution A representation of permanent and transitory income shocks that might arrive at the beginning of next period. LivPrb : float Survival probability; likelihood of being alive at the beginning of the succeeding period. DiscFac : float Intertemporal discount factor for future utility. CRRA : float Coefficient of relative risk aversion. Rfree : np.array Risk free interest factor on end-of-period assets for each Markov state in the succeeding period. PermGroFac : np.array Expected permanent income growth factor at the end of this period for each Markov state in the succeeding period. uPfac : np.array Scaling factor for (marginal) utility in each current Markov state. MrkvArray : np.array An NxN array representing a Markov transition matrix between discrete states. The i,j-th element of MrkvArray is the probability of moving from state i in period t to state j in period t+1. BoroCnstArt: float or None Borrowing constraint for the minimum allowable assets to end the period with. If it is less than the natural borrowing constraint, then it is irrelevant; BoroCnstArt=None indicates no artificial bor- rowing constraint. aXtraGrid: np.array Array of "extra" end-of-period asset values-- assets above the absolute minimum acceptable level. vFuncBool: boolean An indicator for whether the value function should be computed and included in the reported solution. Not used. CubicBool: boolean An indicator for whether the solver should use cubic or linear inter- polation. Not used. Returns ------- solution : ConsumerSolution The solution to the single period consumption-saving problem. Includes a consumption function cFunc (using cubic or linear splines), a marg- inal value function vPfunc, a minimum acceptable level of normalized market resources mNrmMin. All of these attributes are lists or arrays, with elements corresponding to the current Markov state. E.g. solution.cFunc[0] is the consumption function when in the i=0 Markov state this period. ''' # Get sizes of grids aCount = aXtraGrid.size StateCount = MrkvArray.shape[0] # Loop through next period's states, assuming we reach each one at a time. # Construct EndOfPrdvP_cond functions for each state. BoroCnstNat_cond = [] EndOfPrdvPfunc_cond = [] for j in range(StateCount): # Unpack next period's solution vPfuncNext = solution_next.vPfunc[j] mNrmMinNext = solution_next.mNrmMin[j] # Unpack the income shocks ShkPrbsNext = IncomeDstn[j].pmf PermShkValsNext = IncomeDstn[j].X[0] TranShkValsNext = IncomeDstn[j].X[1] ShkCount = ShkPrbsNext.size aXtra_tiled = np.tile(np.reshape(aXtraGrid, (aCount, 1)), (1, ShkCount)) # Make tiled versions of the income shocks # Dimension order: aNow, Shk ShkPrbsNext_tiled = np.tile(np.reshape(ShkPrbsNext, (1, ShkCount)), (aCount, 1)) PermShkValsNext_tiled = np.tile(np.reshape(PermShkValsNext, (1, ShkCount)), (aCount, 1)) TranShkValsNext_tiled = np.tile(np.reshape(TranShkValsNext, (1, ShkCount)), (aCount, 1)) # Find the natural borrowing constraint aNrmMin_candidates = PermGroFac[j]*PermShkValsNext_tiled/Rfree[j]*(mNrmMinNext - TranShkValsNext_tiled[0, :]) aNrmMin = np.max(aNrmMin_candidates) BoroCnstNat_cond.append(aNrmMin) # Calculate market resources next period (and a constant array of capital-to-labor ratio) aNrmNow_tiled = aNrmMin + aXtra_tiled mNrmNext_array = Rfree[j]*aNrmNow_tiled/PermShkValsNext_tiled + TranShkValsNext_tiled # Find marginal value next period at every income shock realization and every aggregate market resource gridpoint vPnext_array = Rfree[j]*PermShkValsNext_tiled**(-CRRA)*vPfuncNext(mNrmNext_array) # Calculate expectated marginal value at the end of the period at every asset gridpoint EndOfPrdvP = DiscFac*np.sum(vPnext_array*ShkPrbsNext_tiled, axis=1) # Make the conditional end-of-period marginal value function EndOfPrdvPnvrs = EndOfPrdvP**(-1./CRRA) EndOfPrdvPnvrsFunc = LinearInterp(np.insert(aNrmMin + aXtraGrid, 0, aNrmMin), np.insert(EndOfPrdvPnvrs, 0, 0.0)) EndOfPrdvPfunc_cond.append(MargValueFunc(EndOfPrdvPnvrsFunc, CRRA)) # Now loop through *this* period's discrete states, calculating end-of-period # marginal value (weighting across state transitions), then construct consumption # and marginal value function for each state. cFuncNow = [] vPfuncNow = [] mNrmMinNow = [] for i in range(StateCount): # Find natural borrowing constraint for this state aNrmMin_candidates = np.zeros(StateCount) + np.nan for j in range(StateCount): if MrkvArray[i, j] > 0.: # Irrelevant if transition is impossible aNrmMin_candidates[j] = BoroCnstNat_cond[j] aNrmMin = np.nanmax(aNrmMin_candidates) # Find the minimum allowable market resources if BoroCnstArt is not None: mNrmMin = np.maximum(BoroCnstArt, aNrmMin) else: mNrmMin = aNrmMin mNrmMinNow.append(mNrmMin) # Make tiled grid of aNrm aNrmNow = aNrmMin + aXtraGrid # Loop through feasible transitions and calculate end-of-period marginal value EndOfPrdvP = np.zeros(aCount) for j in range(StateCount): if MrkvArray[i, j] > 0.: temp = MrkvArray[i, j]*EndOfPrdvPfunc_cond[j](aNrmNow) EndOfPrdvP += temp EndOfPrdvP *= LivPrb[i] # Account for survival out of the current state # Calculate consumption and the endogenous mNrm gridpoints for this state cNrmNow = (EndOfPrdvP/uPfac[i])**(-1./CRRA) mNrmNow = aNrmNow + cNrmNow # Make a piecewise linear consumption function c_temp = np.insert(cNrmNow, 0, 0.0) # Add point at bottom m_temp = np.insert(mNrmNow, 0, aNrmMin) cFuncUnc = LinearInterp(m_temp, c_temp) cFuncCnst = LinearInterp(np.array([mNrmMin, mNrmMin+1.0]), np.array([0.0, 1.0])) cFuncNow.append(LowerEnvelope(cFuncUnc,cFuncCnst)) # Construct the marginal value function using the envelope condition m_temp = aXtraGrid + mNrmMin c_temp = cFuncNow[i](m_temp) uP = uPfac[i]*c_temp**(-CRRA) vPnvrs = uP**(-1./CRRA) vPnvrsFunc = LinearInterp(np.insert(m_temp, 0, mNrmMin), np.insert(vPnvrs, 0, 0.0)) vPfuncNow.append(MargValueFunc(vPnvrsFunc, CRRA)) # Pack up and return the solution solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow) return solution_now
def make_solution(self, cNrm, mNrm): """ Construct an object representing the solution to this period's problem. Parameters ---------- cNrm : np.array Array of normalized consumption values for interpolation. Each row corresponds to a Markov state for this period. mNrm : np.array Array of normalized market resource values for interpolation. Each row corresponds to a Markov state for this period. Returns ------- solution : ConsumerSolution The solution to the single period consumption-saving problem. Includes a consumption function cFunc (using cubic or linear splines), a marg- inal value function vPfunc, a minimum acceptable level of normalized market resources mNrmMin, normalized human wealth hNrm, and bounding MPCs MPCmin and MPCmax. It might also have a value function vFunc and marginal marginal value function vPPfunc. All of these attributes are lists or arrays, with elements corresponding to the current Markov state. E.g. solution.cFunc[0] is the consumption function when in the i=0 Markov state this period. """ solution = ( ConsumerSolution() ) # An empty solution to which we'll add state-conditional solutions # Calculate the MPC at each market resource gridpoint in each state (if desired) if self.CubicBool: dcda = self.EndOfPrdvPP / self.uPP(np.array(self.cNrmNow)) MPC = dcda / (dcda + 1.0) self.MPC_temp = np.hstack( (np.reshape(self.MPCmaxNow, (self.StateCount, 1)), MPC) ) interpfunc = self.make_cubic_cFunc else: interpfunc = self.make_linear_cFunc # Loop through each current period state and add its solution to the overall solution for i in range(self.StateCount): # Set current-period-conditional human wealth and MPC bounds self.hNrmNow_j = self.hNrmNow[i] self.MPCminNow_j = self.MPCminNow[i] if self.CubicBool: self.MPC_temp_j = self.MPC_temp[i, :] # Construct the consumption function by combining the constrained and unconstrained portions self.cFuncNowCnst = LinearInterp( [self.mNrmMin_list[i], self.mNrmMin_list[i] + 1.0], [0.0, 1.0] ) cFuncNowUnc = interpfunc(mNrm[i, :], cNrm[i, :]) cFuncNow = LowerEnvelope(cFuncNowUnc, self.cFuncNowCnst) # Make the marginal value function and pack up the current-state-conditional solution vPfuncNow = MargValueFuncCRRA(cFuncNow, self.CRRA) solution_cond = ConsumerSolution( cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=self.mNrmMinNow ) if ( self.CubicBool ): # Add the state-conditional marginal marginal value function (if desired) solution_cond = self.add_vPPfunc(solution_cond) # Add the current-state-conditional solution to the overall period solution solution.append_solution(solution_cond) # Add the lower bounds of market resources, MPC limits, human resources, # and the value functions to the overall solution solution.mNrmMin = self.mNrmMin_list solution = self.add_MPC_and_human_wealth(solution) if self.vFuncBool: vFuncNow = self.make_vFunc(solution) solution.vFunc = vFuncNow # Return the overall solution to this period return solution
def calcMultilineEnvelope(M, C, V_T, commonM): """ Do the envelope step of the DCEGM algorithm. Takes in market ressources, consumption levels, and inverse values from the EGM step. These represent (m, c) pairs that solve the necessary first order conditions. This function calculates the optimal (m, c, v_t) pairs on the commonM grid. Parameters ---------- M : np.array market ressources from EGM step C : np.array consumption from EGM step V_T : np.array transformed values at the EGM grid commonM : np.array common grid to do upper envelope calculations on Returns ------- """ m_len = len(commonM) rise, fall = calcSegments(M, V_T) num_kinks = len(fall) # number of kinks / falling EGM grids # Use these segments to sequentially find upper envelopes. commonVARNAME # means the VARNAME evaluated on the common grid with a cloumn for each kink # discovered in calcSegments. This means that commonVARNAME is a matrix # common grid length-by-number of segments to consider. In the end, we'll # use nanargmax over the columns to pick out the best (transformed) values. # This is why we fill the arrays with np.nan's. commonV_T = np.empty((m_len, num_kinks)) commonV_T[:] = np.nan commonC = np.empty((m_len, num_kinks)) commonC[:] = np.nan # Now, loop over all segments as defined by the "kinks" or the combination # of "rise" and "fall" indeces. These (rise[j], fall[j]) pairs define regions for j in range(num_kinks): # Find points in the common grid that are in the range of the points in # the interval defined by (rise[j], fall[j]). below = M[rise[j]] >= commonM # boolean array of bad indeces below above = M[fall[j]] <= commonM # boolen array of bad indeces above in_range = below + above == 0 # pick out elements that are neither # create range of indeces in the input arrays idxs = range(rise[j], fall[j] + 1) # grab ressource values at the relevant indeces m_idx_j = M[idxs] # based in in_range, find the relevant ressource values to interpolate m_eval = commonM[in_range] # re-interpolate to common grid commonV_T[in_range, j] = LinearInterp(m_idx_j, V_T[idxs], lower_extrap=True)(m_eval) # NOQA commonC[in_range, j] = LinearInterp( m_idx_j, C[idxs], lower_extrap=True )(m_eval) # NOQA Interpolat econsumption also. May not be nesserary # for each row in the commonV_T matrix, see if all entries are np.nan. This # would mean that we have no valid value here, so we want to use this boolean # vector to filter out irrelevant entries of commonV_T. row_all_nan = np.array([np.all(np.isnan(row)) for row in commonV_T]) # Now take the max of all these line segments. idx_max = np.zeros(commonM.size, dtype=int) idx_max[row_all_nan == False] = np.nanargmax( commonV_T[row_all_nan == False], axis=1) # prefix with upper for variable that are "upper enveloped" upperV_T = np.zeros(m_len) # Set the non-nan rows to the maximum over columns upperV_T[row_all_nan == False] = np.nanmax( commonV_T[row_all_nan == False, :], axis=1) # Set the rest to nan upperV_T[row_all_nan] = np.nan # Add the zero point in the bottom if np.isnan(upperV_T[0]): # in transformed space space, utility of zero-consumption (-inf) is 0.0 upperV_T[0] = 0.0 # commonM[0] is typically 0, so this is safe, but maybe it should be 0.0 commonC[0] = commonM[0] # Extrapolate if NaNs are introduced due to the common grid # going outside all the sub-line segments IsNaN = np.isnan(upperV_T) upperV_T[IsNaN] = LinearInterp(commonM[IsNaN == False], upperV_T[IsNaN == False])(commonM[IsNaN]) LastBeforeNaN = np.append(np.diff(IsNaN) > 0, 0) LastId = LastBeforeNaN * idx_max # Find last id-number idx_max[IsNaN] = LastId[IsNaN] # Linear index used to get optimal consumption based on "id" from max ncols = commonC.shape[1] rowidx = np.cumsum(ncols * np.ones(len(commonM), dtype=int)) - ncols idx_linear = np.unravel_index(rowidx + idx_max, commonC.shape) upperC = commonC[idx_linear] upperC[IsNaN] = LinearInterp(commonM[IsNaN == 0], upperC[IsNaN == 0])(commonM[IsNaN]) # TODO calculate cross points of line segments to get the true vertical drops upperM = commonM.copy() # anticipate this TODO return upperM, upperC, upperV_T
def solveConsRepAgent(solution_next,DiscFac,CRRA,IncomeDstn,CapShare,DeprFac,PermGroFac,aXtraGrid): ''' Solve one period of the simple representative agent consumption-saving model. Parameters ---------- solution_next : ConsumerSolution Solution to the next period's problem (i.e. previous iteration). DiscFac : float Intertemporal discount factor for future utility. CRRA : float Coefficient of relative risk aversion. IncomeDstn : [np.array] A list containing three arrays of floats, representing a discrete approximation to the income process between the period being solved and the one immediately following (in solution_next). Order: event probabilities, permanent shocks, transitory shocks. CapShare : float Capital's share of income in Cobb-Douglas production function. DeprFac : float Depreciation rate of capital. PermGroFac : float Expected permanent income growth factor at the end of this period. aXtraGrid : np.array Array of "extra" end-of-period asset values-- assets above the absolute minimum acceptable level. In this model, the minimum acceptable level is always zero. Returns ------- solution_now : ConsumerSolution Solution to this period's problem (new iteration). ''' # Unpack next period's solution and the income distribution vPfuncNext = solution_next.vPfunc ShkPrbsNext = IncomeDstn.pmf PermShkValsNext = IncomeDstn.X[0] TranShkValsNext = IncomeDstn.X[1] # Make tiled versions of end-of-period assets, shocks, and probabilities aNrmNow = aXtraGrid aNrmCount = aNrmNow.size ShkCount = ShkPrbsNext.size aNrm_tiled = np.tile(np.reshape(aNrmNow,(aNrmCount,1)),(1,ShkCount)) # Tile arrays of the income shocks and put them into useful shapes PermShkVals_tiled = np.tile(np.reshape(PermShkValsNext,(1,ShkCount)),(aNrmCount,1)) TranShkVals_tiled = np.tile(np.reshape(TranShkValsNext,(1,ShkCount)),(aNrmCount,1)) ShkPrbs_tiled = np.tile(np.reshape(ShkPrbsNext,(1,ShkCount)),(aNrmCount,1)) # Calculate next period's capital-to-permanent-labor ratio under each combination # of end-of-period assets and shock realization kNrmNext = aNrm_tiled/(PermGroFac*PermShkVals_tiled) # Calculate next period's market resources KtoLnext = kNrmNext/TranShkVals_tiled RfreeNext = 1. - DeprFac + CapShare*KtoLnext**(CapShare-1.) wRteNext = (1.-CapShare)*KtoLnext**CapShare mNrmNext = RfreeNext*kNrmNext + wRteNext*TranShkVals_tiled # Calculate end-of-period marginal value of assets for the RA vPnext = vPfuncNext(mNrmNext) EndOfPrdvP = DiscFac*np.sum(RfreeNext*(PermGroFac*PermShkVals_tiled)**(-CRRA)*vPnext*ShkPrbs_tiled,axis=1) # Invert the first order condition to get consumption, then find endogenous gridpoints cNrmNow = EndOfPrdvP**(-1./CRRA) mNrmNow = aNrmNow + cNrmNow # Construct the consumption function and the marginal value function cFuncNow = LinearInterp(np.insert(mNrmNow,0,0.0),np.insert(cNrmNow,0,0.0)) vPfuncNow = MargValueFunc(cFuncNow,CRRA) # Construct and return the solution for this period solution_now = ConsumerSolution(cFunc=cFuncNow,vPfunc=vPfuncNow) return solution_now
def solveConsRepAgentMarkov(solution_next,MrkvArray,DiscFac,CRRA,IncomeDstn,CapShare,DeprFac,PermGroFac,aXtraGrid): ''' Solve one period of the simple representative agent consumption-saving model. This version supports a discrete Markov process. Parameters ---------- solution_next : ConsumerSolution Solution to the next period's problem (i.e. previous iteration). MrkvArray : np.array Markov transition array between this period and next period. DiscFac : float Intertemporal discount factor for future utility. CRRA : float Coefficient of relative risk aversion. IncomeDstn : [[np.array]] A list of lists containing three arrays of floats, representing a discrete approximation to the income process between the period being solved and the one immediately following (in solution_next). Order: event probabilities, permanent shocks, transitory shocks. CapShare : float Capital's share of income in Cobb-Douglas production function. DeprFac : float Depreciation rate of capital. PermGroFac : [float] Expected permanent income growth factor for each state we could be in next period. aXtraGrid : np.array Array of "extra" end-of-period asset values-- assets above the absolute minimum acceptable level. In this model, the minimum acceptable level is always zero. Returns ------- solution_now : ConsumerSolution Solution to this period's problem (new iteration). ''' # Define basic objects StateCount = MrkvArray.shape[0] aNrmNow = aXtraGrid aNrmCount = aNrmNow.size EndOfPrdvP_cond = np.zeros((StateCount,aNrmCount)) + np.nan # Loop over *next period* states, calculating conditional EndOfPrdvP for j in range(StateCount): # Define next-period-state conditional objects vPfuncNext = solution_next.vPfunc[j] ShkPrbsNext = IncomeDstn[j].pmf PermShkValsNext = IncomeDstn[j].X[0] TranShkValsNext = IncomeDstn[j].X[1] # Make tiled versions of end-of-period assets, shocks, and probabilities ShkCount = ShkPrbsNext.size aNrm_tiled = np.tile(np.reshape(aNrmNow,(aNrmCount,1)),(1,ShkCount)) # Tile arrays of the income shocks and put them into useful shapes PermShkVals_tiled = np.tile(np.reshape(PermShkValsNext,(1,ShkCount)),(aNrmCount,1)) TranShkVals_tiled = np.tile(np.reshape(TranShkValsNext,(1,ShkCount)),(aNrmCount,1)) ShkPrbs_tiled = np.tile(np.reshape(ShkPrbsNext,(1,ShkCount)),(aNrmCount,1)) # Calculate next period's capital-to-permanent-labor ratio under each combination # of end-of-period assets and shock realization kNrmNext = aNrm_tiled/(PermGroFac[j]*PermShkVals_tiled) # Calculate next period's market resources KtoLnext = kNrmNext/TranShkVals_tiled RfreeNext = 1. - DeprFac + CapShare*KtoLnext**(CapShare-1.) wRteNext = (1.-CapShare)*KtoLnext**CapShare mNrmNext = RfreeNext*kNrmNext + wRteNext*TranShkVals_tiled # Calculate end-of-period marginal value of assets for the RA vPnext = vPfuncNext(mNrmNext) EndOfPrdvP_cond[j,:] = DiscFac*np.sum(RfreeNext*(PermGroFac[j]*PermShkVals_tiled)**(-CRRA)*vPnext*ShkPrbs_tiled,axis=1) # Apply the Markov transition matrix to get unconditional end-of-period marginal value EndOfPrdvP = np.dot(MrkvArray,EndOfPrdvP_cond) # Construct the consumption function and marginal value function for each discrete state cFuncNow_list = [] vPfuncNow_list = [] for i in range(StateCount): # Invert the first order condition to get consumption, then find endogenous gridpoints cNrmNow = EndOfPrdvP[i,:]**(-1./CRRA) mNrmNow = aNrmNow + cNrmNow # Construct the consumption function and the marginal value function cFuncNow_list.append(LinearInterp(np.insert(mNrmNow,0,0.0),np.insert(cNrmNow,0,0.0))) vPfuncNow_list.append(MargValueFunc(cFuncNow_list[-1],CRRA)) # Construct and return the solution for this period solution_now = ConsumerSolution(cFunc=cFuncNow_list,vPfunc=vPfuncNow_list) return solution_now