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 update_solution_terminal(self): """ Update the terminal period solution. This method should be run when a new AgentType is created or when CRRA changes. """ self.solution_terminal_cs = ConsumerSolution( cFunc=self.cFunc_terminal_, vFunc=ValueFuncCRRA(self.cFunc_terminal_, self.CRRA), vPfunc=MargValueFuncCRRA(self.cFunc_terminal_, self.CRRA), vPPfunc=MargMargValueFuncCRRA(self.cFunc_terminal_, self.CRRA), mNrmMin=0.0, hNrm=0.0, MPCmin=1.0, MPCmax=1.0, )
def use_points_for_interpolation(self, cLvl, mLvl, pLvl, interpolator): """ Constructs a basic solution for this period, including the consumption function and marginal value function. Parameters ---------- cLvl : np.array Consumption points for interpolation. mLvl : np.array Corresponding market resource points for interpolation. pLvl : np.array Corresponding persistent income level 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. """ # Construct the unconstrained consumption function cFuncNowUnc = interpolator(mLvl, pLvl, cLvl) # Combine the constrained and unconstrained functions into the true consumption function cFuncNow = LowerEnvelope2D(cFuncNowUnc, self.cFuncNowCnst) # Make the marginal value function vPfuncNow = self.make_vPfunc(cFuncNow) # Pack up the solution and return it solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=0.0) return solution_now
class GenIncProcessConsumerType(IndShockConsumerType): """ A consumer type with idiosyncratic shocks to persistent and transitory income. His problem is defined by a sequence of income distributions, survival prob- abilities, and persistent income growth functions, as well as time invariant values for risk aversion, discount factor, the interest rate, the grid of end-of-period assets, and an artificial borrowing constraint. See init_explicit_perm_inc for a dictionary of the keywords that should be passed to the constructor. Parameters ---------- cycles : int Number of times the sequence of periods should be solved. """ cFunc_terminal_ = BilinearInterp(np.array([[0.0, 0.0], [1.0, 1.0]]), np.array([0.0, 1.0]), np.array([0.0, 1.0])) solution_terminal_ = ConsumerSolution(cFunc=cFunc_terminal_, mNrmMin=0.0, hNrm=0.0, MPCmin=1.0, MPCmax=1.0) state_vars = ['pLvl', "mLvl", 'aLvl'] def __init__(self, **kwds): params = init_explicit_perm_inc.copy() params.update(kwds) # Initialize a basic ConsumerType IndShockConsumerType.__init__(self, **params) self.solve_one_period = make_one_period_oo_solver( ConsGenIncProcessSolver) # a poststate? self.state_now['aLvl'] = None self.state_prev['aLvl'] = None # better way to do this... self.state_now["mLvl"] = None self.state_prev["mLvl"] = None def pre_solve(self): # AgentType.pre_solve() self.update_solution_terminal() def update(self): """ Update the income process, the assets grid, the persistent income grid, and the terminal solution. Parameters ---------- None Returns ------- None """ IndShockConsumerType.update(self) self.update_pLvlNextFunc() self.update_pLvlGrid() def update_solution_terminal(self): """ Update the terminal period solution. This method should be run when a new AgentType is created or when CRRA changes. Parameters ---------- None Returns ------- None """ self.solution_terminal.vFunc = ValueFuncCRRA(self.cFunc_terminal_, self.CRRA) self.solution_terminal.vPfunc = MargValueFuncCRRA( self.cFunc_terminal_, self.CRRA) self.solution_terminal.vPPfunc = MargMargValueFuncCRRA( self.cFunc_terminal_, self.CRRA) self.solution_terminal.hNrm = 0.0 # Don't track normalized human wealth self.solution_terminal.hLvl = lambda p: np.zeros_like(p) # But do track absolute human wealth by persistent income self.solution_terminal.mLvlMin = lambda p: np.zeros_like(p) # And minimum allowable market resources by perm inc 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 install_retirement_func(self): """ Installs a special pLvlNextFunc representing retirement in the correct element of self.pLvlNextFunc. Draws on the attributes T_retire and pLvlNextFuncRet. If T_retire is zero or pLvlNextFuncRet does not exist, this method does nothing. Should only be called from within the method update_pLvlNextFunc, which ensures that time is flowing forward. Parameters ---------- None Returns ------- None """ if (not hasattr(self, "pLvlNextFuncRet")) or self.T_retire == 0: return t = self.T_retire self.pLvlNextFunc[t] = self.pLvlNextFuncRet def update_pLvlGrid(self): """ Update the grid of persistent income levels. Currently only works for infinite horizon models (cycles=0) and lifecycle models (cycles=1). Not clear what to do about cycles>1 because the distribution of persistent income will be different within a period depending on how many cycles have elapsed. This method uses a simulation approach to generate the pLvlGrid at each period of the cycle, drawing on the initial distribution of persistent income, the pLvlNextFuncs, and the attribute pLvlPctiles. Parameters ---------- None Returns ------- None """ LivPrbAll = np.array(self.LivPrb) # Simulate the distribution of persistent income levels by t_cycle in a lifecycle model if self.cycles == 1: pLvlNow = Lognormal(self.pLvlInitMean, sigma=self.pLvlInitStd, seed=31382).draw(self.AgentCount) pLvlGrid = [] # empty list of time-varying persistent income grids # Calculate distribution of persistent income in each period of lifecycle for t in range(len(self.PermShkStd)): if t > 0: PermShkNow = self.PermShkDstn[t - 1].draw(N=self.AgentCount) pLvlNow = self.pLvlNextFunc[t - 1](pLvlNow) * PermShkNow pLvlGrid.append( get_percentiles(pLvlNow, percentiles=self.pLvlPctiles)) # Calculate "stationary" distribution in infinite horizon (might vary across periods of cycle) elif self.cycles == 0: T_long = 1000 # Number of periods to simulate to get to "stationary" distribution pLvlNow = Lognormal(mu=self.pLvlInitMean, sigma=self.pLvlInitStd, seed=31382).draw(self.AgentCount) t_cycle = np.zeros(self.AgentCount, dtype=int) for t in range(T_long): LivPrb = LivPrbAll[ t_cycle] # Determine who dies and replace them with newborns draws = Uniform(seed=t).draw(self.AgentCount) who_dies = draws > LivPrb pLvlNow[who_dies] = Lognormal(self.pLvlInitMean, self.pLvlInitStd, seed=t + 92615).draw( np.sum(who_dies)) t_cycle[who_dies] = 0 for j in range(self.T_cycle): # Update persistent income these = t_cycle == j PermShkTemp = self.PermShkDstn[j].draw(N=np.sum(these)) pLvlNow[these] = self.pLvlNextFunc[j]( pLvlNow[these]) * PermShkTemp t_cycle = t_cycle + 1 t_cycle[t_cycle == self.T_cycle] = 0 # We now have a "long run stationary distribution", extract percentiles pLvlGrid = [] # empty list of time-varying persistent income grids for t in range(self.T_cycle): these = t_cycle == t pLvlGrid.append( get_percentiles(pLvlNow[these], percentiles=self.pLvlPctiles)) # Throw an error if cycles>1 else: assert False, "Can only handle cycles=0 or cycles=1!" # Store the result and add attribute to time_vary self.pLvlGrid = pLvlGrid self.add_to_time_vary("pLvlGrid") def sim_birth(self, which_agents): """ Makes new consumers for the given indices. Initialized variables include aNrm and pLvl, as well as time variables t_age and t_cycle. Normalized assets and persistent income levels are drawn from lognormal distributions given by aNrmInitMean and aNrmInitStd (etc). Parameters ---------- which_agents : np.array(Bool) Boolean array of size self.AgentCount indicating which agents should be "born". Returns ------- None """ # Get and store states for newly born agents N = np.sum(which_agents) # Number of new consumers to make aNrmNow_new = Lognormal(self.aNrmInitMean, self.aNrmInitStd, seed=self.RNG.randint(0, 2**31 - 1)).draw(N) self.state_now['pLvl'][which_agents] = Lognormal( self.pLvlInitMean, self.pLvlInitStd, seed=self.RNG.randint(0, 2**31 - 1)).draw(N) self.state_now['aLvl'][ which_agents] = aNrmNow_new * self.state_now['pLvl'][which_agents] self.t_age[ which_agents] = 0 # How many periods since each agent was born self.t_cycle[ which_agents] = 0 # Which period of the cycle each agent is currently in def transition(self): """ Calculates updated values of normalized market resources and persistent income level for each agent. Uses pLvlNow, aLvlNow, PermShkNow, TranShkNow. Parameters ---------- None Returns ------- pLvlNow mLvlNow """ aLvlPrev = self.state_prev['aLvl'] RfreeNow = self.get_Rfree() # Calculate new states: normalized market resources # and persistent income level pLvlNow = np.zeros_like(aLvlPrev) for t in range(self.T_cycle): these = t == self.t_cycle pLvlNow[these] = ( self.pLvlNextFunc[t - 1](self.state_prev['pLvl'][these]) * self.shocks['PermShk'][these]) #state value bLvlNow = RfreeNow * aLvlPrev # Bank balances before labor income # Market resources after income - state value mLvlNow = bLvlNow + \ self.shocks['TranShk'] * \ pLvlNow return (pLvlNow, mLvlNow) def get_controls(self): """ Calculates consumption for each consumer of this type using the consumption functions. Parameters ---------- None Returns ------- None """ cLvlNow = np.zeros(self.AgentCount) + np.nan MPCnow = np.zeros(self.AgentCount) + np.nan for t in range(self.T_cycle): these = t == self.t_cycle cLvlNow[these] = self.solution[t].cFunc( self.state_now["mLvl"][these], self.state_now['pLvl'][these]) MPCnow[these] = self.solution[t].cFunc.derivativeX( self.state_now["mLvl"][these], self.state_now['pLvl'][these]) self.controls["cLvl"] = cLvlNow self.MPCnow = MPCnow def get_poststates(self): """ Calculates end-of-period assets for each consumer of this type. Identical to version in IndShockConsumerType but uses Lvl rather than Nrm variables. Parameters ---------- None Returns ------- None """ self.state_now['aLvl'] = self.state_now["mLvl"] - self.controls["cLvl"] # moves now to prev AgentType.get_poststates(self)
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 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
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
class PrefLaborConsumerType(IndShockConsumerType): ''' A consumer type with idiosyncratic shocks to permanent and transitory income, as well as shocks to preferences ''' # Define some universal values for all consumer types cFunc_terminal_ = BilinearInterpOnInterp1D( [[ LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])), LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])) ], [ LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])), LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])) ]], np.array([0.0, 1.0]), np.array([0.0, 1.0])) # c=m in terminal period vFunc_terminal_ = BilinearInterpOnInterp1D( [[ LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])), LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])) ], [ LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])), LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])) ]], np.array([0.0, 1.0]), np.array([0.0, 1.0])) # This is overwritten solution_terminal_ = ConsumerSolution(cFunc=cFunc_terminal_, vFunc=vFunc_terminal_) time_inv_ = PerfForesightConsumerType.time_inv_ + [ 'LaborElas', 'PrefShkVals', 'WageShkVals' ] shock_vars_ = ['PermShkNow', 'TranShkNow', 'PrefShkNow'] def __init__(self, cycles=1, time_flow=True, **kwds): ''' Instantiate a new ConsumerType with given data. See ConsumerParameters.init_idiosyncratic_shocks for a dictionary of the keywords that should be passed to the constructor. Parameters ---------- cycles : int Number of times the sequence of periods should be solved. time_flow : boolean Whether time is currently "flowing" forward for this instance. Returns ------- None ''' # Initialize a basic AgentType IndShockConsumerType.__init__(self, cycles=cycles, time_flow=time_flow, **kwds) # Add consumer-type specific objects, copying to create independent versions self.solveOnePeriod = solvePrefLaborShock # idiosyncratic shocks solver self.update() # Make assets grid, income process, terminal solution def reset(self): self.initializeSim() self.t_age = drawDiscrete(self.AgentCount, P=self.AgeDstn, X=np.arange(self.AgeDstn.size), exact_match=False, seed=self.RNG.randint(0, 2**31 - 1)).astype(int) self.t_cycle = copy(self.t_age) def marketAction(self): self.simulate(1) def updateIncomeProcess(self): ''' Updates this agent's income process based on his own attributes. The function that generates the discrete income process can be swapped out for a different process. Parameters ---------- none Returns: ----------- none ''' original_time = self.time_flow self.timeFwd() IncomeAndPrefDstn, PermShkDstn, TranShkDstn, PrefShkDstn = constructLognormalIncomeAndPreferenceProcess( self) self.IncomeAndPrefDstn = IncomeAndPrefDstn self.PermShkDstn = PermShkDstn self.TranShkDstn = TranShkDstn self.PrefShkDstn = PrefShkDstn self.PrefShkVals = PrefShkDstn[0][1] self.WageShkVals = TranShkDstn[0][1] self.addToTimeVary('IncomeAndPrefDstn', 'PermShkDstn', 'TranShkDstn', 'PrefShkDstn') if not original_time: self.timeRev() 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])) #self.solution_terminal.vPPfunc = MargMargValueFunc(self.cFunc_terminal_,self.CRRA) def getShocks(self): ''' Gets permanent and transitory income shocks for this period. Samples from IncomeDstn for each period in the cycle. Parameters ---------- None Returns ------- None ''' PermShkNow = np.zeros(self.AgentCount) # Initialize shock arrays TranShkNow = np.zeros(self.AgentCount) PrefShkNow = np.zeros(self.AgentCount) newborn = self.t_age == 0 for t in range(self.T_cycle): these = t == self.t_cycle N = np.sum(these) if N > 0: IncomeDstnNow = self.IncomeAndPrefDstn[ t - 1] # set current income distribution PermGroFacNow = self.PermGroFac[ t - 1] # and permanent growth factor Indices = np.arange( IncomeDstnNow[0].size) # just a list of integers # Get random draws of income shocks from the discrete distribution EventDraws = drawDiscrete(N, X=Indices, P=IncomeDstnNow[0], exact_match=False, seed=self.RNG.randint(0, 2**31 - 1)) PermShkNow[these] = IncomeDstnNow[1][ EventDraws] * PermGroFacNow # permanent "shock" includes expected growth TranShkNow[these] = IncomeDstnNow[2][EventDraws] PrefShkNow[these] = IncomeDstnNow[3][EventDraws] # That procedure used the *last* period in the sequence for newborns, but that's not right # Redraw shocks for newborns, using the *first* period in the sequence. Approximation. N = np.sum(newborn) if N > 0: these = newborn IncomeDstnNow = self.IncomeAndPrefDstn[ 0] # set current income distribution PermGroFacNow = self.PermGroFac[0] # and permanent growth factor Indices = np.arange( IncomeDstnNow[0].size) # just a list of integers # Get random draws of income shocks from the discrete distribution EventDraws = drawDiscrete(N, X=Indices, P=IncomeDstnNow[0], exact_match=False, seed=self.RNG.randint(0, 2**31 - 1)) PermShkNow[these] = IncomeDstnNow[1][ EventDraws] * PermGroFacNow # permanent "shock" includes expected growth TranShkNow[these] = IncomeDstnNow[2][EventDraws] PrefShkNow[these] = IncomeDstnNow[3][EventDraws] # PermShkNow[newborn] = 1.0 TranShkNow[newborn] = 1.0 # Store the shocks in self self.EmpNow = np.ones(self.AgentCount, dtype=bool) self.EmpNow[TranShkNow == self.IncUnemp] = False self.PermShkNow = PermShkNow self.TranShkNow = TranShkNow self.PrefShkNow = PrefShkNow def getStates(self): ''' Calculates updated values of normalized market resources and permanent income level for each agent. Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow. Parameters ---------- None Returns ------- None ''' pLvlPrev = self.pLvlNow aNrmPrev = self.aNrmNow RfreeNow = self.getRfree() # Calculate new states: normalized market resources and permanent income level self.pLvlNow = pLvlPrev * self.PermShkNow # Updated permanent income level ReffNow = RfreeNow / self.PermShkNow # "Effective" interest factor on normalized assets self.bNrmNow = ReffNow * aNrmPrev # Bank balances before labor income return None def getControls(self): ''' Calculates consumption for each consumer of this type using the consumption functions. Parameters ---------- None Returns ------- None ''' cNrmNow = np.zeros(self.AgentCount) + np.nan MPCnow = np.zeros(self.AgentCount) + np.nan lNow = np.zeros(self.AgentCount) + np.nan for t in range(self.T_cycle): these = t == self.t_cycle cNrmNow[these] = self.solution[t].cFunc(self.bNrmNow[these], self.TranShkNow, self.PrefShkNow) MPCnow[these] = self.solution[t].cFunc.derivativeX( self.bNrmNow[these], self.TranShkNow, self.PrefShkNow) lNow[these] = self.solution[t].lFunc(self.bNrmNow[these], self.TranShkNow, self.PrefShkNow) self.cNrmNow = cNrmNow self.MPCnow = MPCnow self.lNow = lNow self.lIncomeLvl = lNow * self.TranShkNow * self.pLvlNow self.cLvlNow = cNrmNow * self.pLvlNow return None def getPostStates(self): ''' Calculates end-of-period assets for each consumer of this type. Parameters ---------- None Returns ------- None ''' self.aNrmNow = self.bNrmNow + self.lNow * self.TranShkNow - self.cNrmNow self.aLvlNow = self.aNrmNow * self.pLvlNow # Useful in some cases to precalculate asset level return None
def makeBasicSolution(self, EndOfPrdvP, aNrm, wageShkVals, prefShkVals): ''' Given end of period assets and end of period marginal value, construct the basic solution for this period. Parameters ---------- EndOfPrdvP : np.array Array of end-of-period marginal values. aNrm : np.array Array of end-of-period asset values that yield the marginal values in EndOfPrdvP. wageShkVals : np.array Array of this period transitory wage shock values. prefShkVals : np.array Array of this period preference shock values. Returns ------- solution_now : ConsumerSolution The solution to this period's consumption-saving problem, with a consumption function, marginal value function. ''' num_pref_shocks = len(prefShkVals) num_wage_shocks = len(wageShkVals) cFuncBaseByPref_list = [] vPFuncBaseByPref_list = [] lFuncBaseByPref_list = [] for i in range(num_wage_shocks): cFuncBaseByPref_list.append([]) vPFuncBaseByPref_list.append([]) lFuncBaseByPref_list.append([]) for j in range(num_pref_shocks): c_temp = self.uPinv(EndOfPrdvP / prefShkVals[j]) l_temp = self.LabSupply(wageShkVals[i] * EndOfPrdvP) b_temp = c_temp + aNrm - l_temp * wageShkVals[i] if wageShkVals[i] == 0.0: c_temp = np.insert(c_temp, 0, 0., axis=-1) l_temp = np.insert(l_temp, 0, 0.0, axis=-1) b_temp = np.insert(b_temp, 0, 0.0, axis=-1) lFuncBaseByPref_list[i].append( LinearInterp(b_temp, l_temp, lower_extrap=True)) cFunc1 = LinearInterp(b_temp, c_temp, lower_extrap=True) cFunc2 = LinearInterp(b_temp, l_temp * wageShkVals[i] + b_temp, lower_extrap=True) cFuncBaseByPref_list[i].append(LowerEnvelope(cFunc1, cFunc2)) pseudo_inverse_vPfunc1 = LinearInterp( b_temp, prefShkVals[j]**(-1.0 / self.CRRA) * c_temp, lower_extrap=True) pseudo_inverse_vPfunc2 = LinearInterp( b_temp, prefShkVals[j]**(-1.0 / self.CRRA) * (l_temp * wageShkVals[i] + b_temp), lower_extrap=True) pseudo_inverse_vPfunc = LowerEnvelope(pseudo_inverse_vPfunc1, pseudo_inverse_vPfunc2) vPFuncBaseByPref_list[i].append( MargValueFunc(pseudo_inverse_vPfunc, self.CRRA)) cFuncNow = BilinearInterpOnInterp1D(cFuncBaseByPref_list, wageShkVals, prefShkVals) vPfuncNow = BilinearInterpOnInterp1D(vPFuncBaseByPref_list, wageShkVals, prefShkVals) lFuncNow = BilinearInterpOnInterp1D(lFuncBaseByPref_list, wageShkVals, prefShkVals) # Pack up and return the solution solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow) solution_now.lFunc = lFuncNow return solution_now