Coverage for HARK / ConsumptionSaving / ConsLaborModel.py: 99%
234 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-07 05:16 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-07 05:16 +0000
1"""
2Subclasses of AgentType representing consumers who make decisions about how much
3labor to supply, as well as a consumption-saving decision.
5It currently only has
6one model: labor supply on the intensive margin (unit interval) with CRRA utility
7from a composite good (of consumption and leisure), with transitory and permanent
8productivity shocks. Agents choose their quantities of labor and consumption after
9observing both of these shocks, so the transitory shock is a state variable.
10"""
12from copy import copy
14import matplotlib.pyplot as plt
15import numpy as np
16from HARK.Calibration.Income.IncomeProcesses import (
17 construct_lognormal_income_process_unemployment,
18 get_PermShkDstn_from_IncShkDstn,
19 get_TranShkDstn_from_IncShkDstn,
20 get_TranShkGrid_from_TranShkDstn,
21)
22from HARK.ConsumptionSaving.ConsIndShockModel import (
23 IndShockConsumerType,
24 make_lognormal_kNrm_init_dstn,
25 make_lognormal_pLvl_init_dstn,
26)
27from HARK.interpolation import (
28 BilinearInterp,
29 ConstantFunction,
30 LinearInterp,
31 LinearInterpOnInterp1D,
32 MargValueFuncCRRA,
33 ValueFuncCRRA,
34 VariableLowerBoundFunc2D,
35)
36from HARK.metric import MetricObject
37from HARK.rewards import CRRAutilityP, CRRAutilityP_inv
38from HARK.utilities import make_assets_grid
40plt.ion()
43class ConsumerLaborSolution(MetricObject):
44 """
45 A class for representing one period of the solution to a Consumer Labor problem.
47 Parameters
48 ----------
49 cFunc : function
50 The consumption function for this period, defined over normalized
51 bank balances and the transitory productivity shock: cNrm = cFunc(bNrm,TranShk).
52 LbrFunc : function
53 The labor supply function for this period, defined over normalized
54 bank balances: Lbr = LbrFunc(bNrm,TranShk).
55 vFunc : function
56 The beginning-of-period value function for this period, defined over
57 normalized bank balances: v = vFunc(bNrm,TranShk).
58 vPfunc : function
59 The beginning-of-period marginal value (of bank balances) function for
60 this period, defined over normalized bank balances: vP = vPfunc(bNrm,TranShk).
61 bNrmMin: float
62 The minimum allowable bank balances for this period, as a function of
63 the transitory shock. cFunc, LbrFunc, etc are undefined for bNrm < bNrmMin(TranShk).
64 """
66 distance_criteria = ["cFunc", "LbrFunc"]
68 def __init__(self, cFunc=None, LbrFunc=None, vFunc=None, vPfunc=None, bNrmMin=None):
69 if cFunc is not None:
70 self.cFunc = cFunc
71 if LbrFunc is not None:
72 self.LbrFunc = LbrFunc
73 if vFunc is not None:
74 self.vFunc = vFunc
75 if vPfunc is not None:
76 self.vPfunc = vPfunc
77 if bNrmMin is not None:
78 self.bNrmMin = bNrmMin
81def make_log_polynomial_LbrCost(T_cycle, LbrCostCoeffs):
82 r"""
83 Construct the age-varying cost of working LbrCost using polynomial coefficients
84 (over t_cycle) for (log) LbrCost.
86 .. math::
87 \text{LbrCost}_{t}=\exp(\sum \text{LbrCostCoeffs}_n t^{n})
89 Parameters
90 ----------
91 T_cycle : int
92 Number of non-terminal period's in the agent's problem.
93 LbrCostCoeffs : [float]
94 List or array of arbitrary length, representing polynomial coefficients
95 of t = 0,...,T_cycle, which determine (log) LbrCost.
97 Returns
98 -------
99 LbrCost : [float]
100 List of age-dependent labor utility cost parameters.
101 """
102 N = len(LbrCostCoeffs)
103 age_vec = np.arange(T_cycle)
104 LbrCostBase = np.zeros(T_cycle)
105 for n in range(N):
106 LbrCostBase += LbrCostCoeffs[n] * age_vec**n
107 LbrCost = np.exp(LbrCostBase).tolist()
108 return LbrCost
111###############################################################################
114def make_labor_intmarg_solution_terminal(
115 CRRA, aXtraGrid, LbrCost, WageRte, TranShkGrid
116):
117 """
118 Constructs the terminal period solution and solves for optimal consumption
119 and labor when there is no future.
121 Parameters
122 ----------
123 None
125 Returns
126 -------
127 None
128 """
129 t = -1
130 TranShkGrid_T = TranShkGrid[t]
131 LbrCost_T = LbrCost[t]
132 WageRte_T = WageRte[t]
134 # Add a point at b_t = 0 to make sure that bNrmGrid goes down to 0
135 bNrmGrid = np.insert(aXtraGrid, 0, 0.0)
136 bNrmCount = bNrmGrid.size
137 TranShkCount = TranShkGrid_T.size
139 # Replicated bNrmGrid for each transitory shock theta_t
140 bNrmGridTerm = np.tile(np.reshape(bNrmGrid, (bNrmCount, 1)), (1, TranShkCount))
141 TranShkGridTerm = np.tile(TranShkGrid_T, (bNrmCount, 1))
142 # Tile the grid of transitory shocks for the terminal solution.
144 # Array of labor (leisure) values for terminal solution
145 LsrTerm = np.minimum(
146 (LbrCost_T / (1.0 + LbrCost_T))
147 * (bNrmGridTerm / (WageRte_T * TranShkGridTerm) + 1.0),
148 1.0,
149 )
150 LsrTerm[0, 0] = 1.0
151 LbrTerm = 1.0 - LsrTerm
153 # Calculate market resources in terminal period, which is consumption
154 mNrmTerm = bNrmGridTerm + LbrTerm * WageRte_T * TranShkGridTerm
155 cNrmTerm = mNrmTerm # Consume everything we have
157 # Make a bilinear interpolation to represent the labor and consumption functions
158 LbrFunc_terminal = BilinearInterp(LbrTerm, bNrmGrid, TranShkGrid_T)
159 cFunc_terminal = BilinearInterp(cNrmTerm, bNrmGrid, TranShkGrid_T)
161 # Compute the effective consumption value using consumption value and labor value at the terminal solution
162 xEffTerm = LsrTerm**LbrCost_T * cNrmTerm
163 vNvrsFunc_terminal = BilinearInterp(xEffTerm, bNrmGrid, TranShkGrid_T)
164 vFunc_terminal = ValueFuncCRRA(vNvrsFunc_terminal, CRRA)
166 # Using the envelope condition at the terminal solution to estimate the marginal value function
167 vPterm = LsrTerm**LbrCost_T * CRRAutilityP(xEffTerm, rho=CRRA)
168 vPnvrsTerm = CRRAutilityP_inv(vPterm, rho=CRRA)
169 # Evaluate the inverse of the CRRA marginal utility function at a given marginal value, vP
171 # Get the Marginal Value function
172 vPnvrsFunc_terminal = BilinearInterp(vPnvrsTerm, bNrmGrid, TranShkGrid_T)
173 vPfunc_terminal = MargValueFuncCRRA(vPnvrsFunc_terminal, CRRA)
175 # Trivial function that return the same real output for any input
176 bNrmMin_terminal = ConstantFunction(0.0)
178 # Make and return the terminal period solution
179 solution_terminal = ConsumerLaborSolution(
180 cFunc=cFunc_terminal,
181 LbrFunc=LbrFunc_terminal,
182 vFunc=vFunc_terminal,
183 vPfunc=vPfunc_terminal,
184 bNrmMin=bNrmMin_terminal,
185 )
186 return solution_terminal
189def solve_ConsLaborIntMarg(
190 solution_next,
191 PermShkDstn,
192 TranShkDstn,
193 LivPrb,
194 DiscFac,
195 CRRA,
196 Rfree,
197 PermGroFac,
198 BoroCnstArt,
199 aXtraGrid,
200 TranShkGrid,
201 vFuncBool,
202 CubicBool,
203 WageRte,
204 LbrCost,
205):
206 """
207 Solves one period of the consumption-saving model with endogenous labor supply
208 on the intensive margin by using the endogenous grid method to invert the first
209 order conditions for optimal composite consumption and between consumption and
210 leisure, obviating any search for optimal controls.
212 Parameters
213 ----------
214 solution_next : ConsumerLaborSolution
215 The solution to the next period's problem; must have the attributes
216 vPfunc and bNrmMinFunc representing marginal value of bank balances and
217 minimum (normalized) bank balances as a function of the transitory shock.
218 PermShkDstn: [np.array]
219 Discrete distribution of permanent productivity shocks.
220 TranShkDstn: [np.array]
221 Discrete distribution of transitory productivity shocks.
222 LivPrb : float
223 Survival probability; likelihood of being alive at the beginning of
224 the succeeding period.
225 DiscFac : float
226 Intertemporal discount factor.
227 CRRA : float
228 Coefficient of relative risk aversion over the composite good.
229 Rfree : float
230 Risk free interest rate on assets retained at the end of the period.
231 PermGroFac : float
232 Expected permanent income growth factor for next period.
233 BoroCnstArt: float or None
234 Borrowing constraint for the minimum allowable assets to end the
235 period with. Currently not handled, must be None.
236 aXtraGrid: np.array
237 Array of "extra" end-of-period asset values-- assets above the
238 absolute minimum acceptable level.
239 TranShkGrid: np.array
240 Grid of transitory shock values to use as a state grid for interpolation.
241 vFuncBool: boolean
242 An indicator for whether the value function should be computed and
243 included in the reported solution. Not yet handled, must be False.
244 CubicBool: boolean
245 An indicator for whether the solver should use cubic or linear interpolation.
246 Cubic interpolation is not yet handled, must be False.
247 WageRte: float
248 Wage rate per unit of labor supplied.
249 LbrCost: float
250 Cost parameter for supplying labor: :math:`u_t = U(x_t)`, :math:`x_t = c_t z_t^{LbrCost}`,
251 where :math:`z_t` is leisure :math:`= 1 - Lbr_t`.
253 Returns
254 -------
255 solution_now : ConsumerLaborSolution
256 The solution to this period's problem, including a consumption function
257 cFunc, a labor supply function LbrFunc, and a marginal value function vPfunc;
258 each are defined over normalized bank balances and transitory prod shock.
259 Also includes bNrmMinNow, the minimum permissible bank balances as a function
260 of the transitory productivity shock.
261 """
262 # Make sure the inputs for this period are valid: CRRA > LbrCost/(1+LbrCost)
263 # and CubicBool = False. CRRA condition is met automatically when CRRA >= 1.
264 frac = 1.0 / (1.0 + LbrCost)
265 if CRRA <= frac * LbrCost:
266 raise ValueError("CRRA must be strictly greater than alpha/(1+alpha).")
267 if BoroCnstArt is not None:
268 raise ValueError("Model cannot handle artificial borrowing constraint yet.")
269 if CubicBool is True:
270 raise ValueError("Model cannot handle cubic interpolation yet.")
271 if vFuncBool is True:
272 raise ValueError("Model cannot compute the value function yet.")
274 # Unpack next period's solution and the productivity shock distribution, and define the inverse (marginal) utilty function
275 vPfunc_next = solution_next.vPfunc
276 TranShkPrbs = TranShkDstn.pmv
277 TranShkVals = TranShkDstn.atoms.flatten()
278 PermShkPrbs = PermShkDstn.pmv
279 PermShkVals = PermShkDstn.atoms.flatten()
280 TranShkCount = TranShkPrbs.size
281 PermShkCount = PermShkPrbs.size
283 def uPinv(X):
284 return CRRAutilityP_inv(X, rho=CRRA)
286 # Make tiled versions of the grid of a_t values and the components of the shock distribution
287 aXtraCount = aXtraGrid.size
288 bNrmGrid = aXtraGrid # Next period's bank balances before labor income
290 # Replicated axtraGrid of b_t values (bNowGrid) for each transitory (productivity) shock
291 bNrmGrid_rep = np.tile(np.reshape(bNrmGrid, (aXtraCount, 1)), (1, TranShkCount))
293 # Replicated transitory shock values for each a_t state
294 TranShkVals_rep = np.tile(
295 np.reshape(TranShkVals, (1, TranShkCount)), (aXtraCount, 1)
296 )
298 # Replicated transitory shock probabilities for each a_t state
299 TranShkPrbs_rep = np.tile(
300 np.reshape(TranShkPrbs, (1, TranShkCount)), (aXtraCount, 1)
301 )
303 # Construct a function that gives marginal value of next period's bank balances *just before* the transitory shock arrives
304 # Next period's marginal value at every transitory shock and every bank balances gridpoint
305 vPNext = vPfunc_next(bNrmGrid_rep, TranShkVals_rep)
307 # Integrate out the transitory shocks (in TranShkVals direction) to get expected vP just before the transitory shock
308 vPbarNext = np.sum(vPNext * TranShkPrbs_rep, axis=1)
310 # Transformed marginal value through the inverse marginal utility function to "decurve" it
311 vPbarNvrsNext = uPinv(vPbarNext)
313 # Linear interpolation over b_{t+1}, adding a point at minimal value of b = 0.
314 vPbarNvrsFuncNext = LinearInterp(
315 np.insert(bNrmGrid, 0, 0.0), np.insert(vPbarNvrsNext, 0, 0.0)
316 )
318 # "Recurve" the intermediate marginal value function through the marginal utility function
319 vPbarFuncNext = MargValueFuncCRRA(vPbarNvrsFuncNext, CRRA)
321 # Get next period's bank balances at each permanent shock from each end-of-period asset values
322 # Replicated grid of a_t values for each permanent (productivity) shock
323 aNrmGrid_rep = np.tile(np.reshape(aXtraGrid, (aXtraCount, 1)), (1, PermShkCount))
325 # Replicated permanent shock values for each a_t value
326 PermShkVals_rep = np.tile(
327 np.reshape(PermShkVals, (1, PermShkCount)), (aXtraCount, 1)
328 )
330 # Replicated permanent shock probabilities for each a_t value
331 PermShkPrbs_rep = np.tile(
332 np.reshape(PermShkPrbs, (1, PermShkCount)), (aXtraCount, 1)
333 )
334 bNrmNext = (Rfree / (PermGroFac * PermShkVals_rep)) * aNrmGrid_rep
336 # Calculate marginal value of end-of-period assets at each a_t gridpoint
337 # Get marginal value of bank balances next period at each shock
338 vPbarNext = (PermGroFac * PermShkVals_rep) ** (-CRRA) * vPbarFuncNext(bNrmNext)
340 # Take expectation across permanent income shocks
341 EndOfPrdvP = (
342 DiscFac
343 * Rfree
344 * LivPrb
345 * np.sum(vPbarNext * PermShkPrbs_rep, axis=1, keepdims=True)
346 )
348 # Compute scaling factor for each transitory shock
349 TranShkScaleFac_temp = (
350 frac
351 * (WageRte * TranShkGrid) ** (LbrCost * frac)
352 * (LbrCost ** (-LbrCost * frac) + LbrCost**frac)
353 )
355 # Flip it to be a row vector
356 TranShkScaleFac = np.reshape(TranShkScaleFac_temp, (1, TranShkGrid.size))
358 # Use the first order condition to compute an array of "composite good" x_t values corresponding to (a_t,theta_t) values
359 xNow = (np.dot(EndOfPrdvP, TranShkScaleFac)) ** (-1.0 / (CRRA - LbrCost * frac))
361 # Transform the composite good x_t values into consumption c_t and leisure z_t values
362 TranShkGrid_rep = np.tile(
363 np.reshape(TranShkGrid, (1, TranShkGrid.size)), (aXtraCount, 1)
364 )
365 xNowPow = xNow**frac # Will use this object multiple times in math below
367 # Find optimal consumption from optimal composite good
368 cNrmNow = (((WageRte * TranShkGrid_rep) / LbrCost) ** (LbrCost * frac)) * xNowPow
370 # Find optimal leisure from optimal composite good
371 LsrNow = (LbrCost / (WageRte * TranShkGrid_rep)) ** frac * xNowPow
373 # The zero-th transitory shock is TranShk=0, and the solution is to not work: Lsr = 1, Lbr = 0.
374 cNrmNow[:, 0] = uPinv(EndOfPrdvP.flatten())
375 LsrNow[:, 0] = 1.0
377 # Agent cannot choose to work a negative amount of time. When this occurs, set
378 # leisure to one and recompute consumption using simplified first order condition.
379 # Find where labor would be negative if unconstrained
380 violates_labor_constraint = LsrNow > 1.0
381 EndOfPrdvP_temp = np.tile(
382 np.reshape(EndOfPrdvP, (aXtraCount, 1)), (1, TranShkCount)
383 )
384 cNrmNow[violates_labor_constraint] = uPinv(
385 EndOfPrdvP_temp[violates_labor_constraint]
386 )
387 LsrNow[violates_labor_constraint] = 1.0 # Set up z=1, upper limit
389 # Calculate the endogenous bNrm states by inverting the within-period transition
390 aNrmNow_rep = np.tile(np.reshape(aXtraGrid, (aXtraCount, 1)), (1, TranShkGrid.size))
391 bNrmNow = (
392 aNrmNow_rep
393 - WageRte * TranShkGrid_rep
394 + cNrmNow
395 + WageRte * TranShkGrid_rep * LsrNow
396 )
398 # Add an extra gridpoint at the absolute minimal valid value for b_t for each TranShk;
399 # this corresponds to working 100% of the time and consuming nothing.
400 bNowArray = np.concatenate(
401 (np.reshape(-WageRte * TranShkGrid, (1, TranShkGrid.size)), bNrmNow), axis=0
402 )
403 # Consume nothing
404 cNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), cNrmNow), axis=0)
405 # And no leisure!
406 LsrNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), LsrNow), axis=0)
407 LsrNowArray[0, 0] = 1.0 # Don't work at all if TranShk=0, even if bNrm=0
408 LbrNowArray = 1.0 - LsrNowArray # Labor is the complement of leisure
410 # Get (pseudo-inverse) marginal value of bank balances using end of period
411 # marginal value of assets (envelope condition), adding a column of zeros
412 # zeros on the left edge, representing the limit at the minimum value of b_t.
413 vPnvrsNowArray = np.concatenate(
414 (np.zeros((1, TranShkGrid.size)), uPinv(EndOfPrdvP_temp))
415 )
417 # Construct consumption and marginal value functions for this period
418 bNrmMinNow = LinearInterp(TranShkGrid, bNowArray[0, :])
420 # Loop over each transitory shock and make a linear interpolation to get lists
421 # of optimal consumption, labor and (pseudo-inverse) marginal value by TranShk
422 cFuncNow_list = []
423 LbrFuncNow_list = []
424 vPnvrsFuncNow_list = []
425 for j in range(TranShkGrid.size):
426 # Adjust bNrmNow for this transitory shock, so bNrmNow_temp[0] = 0
427 bNrmNow_temp = bNowArray[:, j] - bNowArray[0, j]
429 # Make consumption function for this transitory shock
430 cFuncNow_list.append(LinearInterp(bNrmNow_temp, cNowArray[:, j]))
432 # Make labor function for this transitory shock
433 LbrFuncNow_list.append(LinearInterp(bNrmNow_temp, LbrNowArray[:, j]))
435 # Make pseudo-inverse marginal value function for this transitory shock
436 vPnvrsFuncNow_list.append(LinearInterp(bNrmNow_temp, vPnvrsNowArray[:, j]))
438 # Make linear interpolation by combining the lists of consumption, labor and marginal value functions
439 cFuncNowBase = LinearInterpOnInterp1D(cFuncNow_list, TranShkGrid)
440 LbrFuncNowBase = LinearInterpOnInterp1D(LbrFuncNow_list, TranShkGrid)
441 vPnvrsFuncNowBase = LinearInterpOnInterp1D(vPnvrsFuncNow_list, TranShkGrid)
443 # Construct consumption, labor, pseudo-inverse marginal value functions with
444 # bNrmMinNow as the lower bound. This removes the adjustment in the loop above.
445 cFuncNow = VariableLowerBoundFunc2D(cFuncNowBase, bNrmMinNow)
446 LbrFuncNow = VariableLowerBoundFunc2D(LbrFuncNowBase, bNrmMinNow)
447 vPnvrsFuncNow = VariableLowerBoundFunc2D(vPnvrsFuncNowBase, bNrmMinNow)
449 # Construct the marginal value function by "recurving" its pseudo-inverse
450 vPfuncNow = MargValueFuncCRRA(vPnvrsFuncNow, CRRA)
452 # Make a solution object for this period and return it
453 solution = ConsumerLaborSolution(
454 cFunc=cFuncNow, LbrFunc=LbrFuncNow, vPfunc=vPfuncNow, bNrmMin=bNrmMinNow
455 )
456 return solution
459###############################################################################
462# Make a dictionary of constructors for the intensive margin labor model
463LaborIntMargConsumerType_constructors_default = {
464 "IncShkDstn": construct_lognormal_income_process_unemployment,
465 "PermShkDstn": get_PermShkDstn_from_IncShkDstn,
466 "TranShkDstn": get_TranShkDstn_from_IncShkDstn,
467 "aXtraGrid": make_assets_grid,
468 "LbrCost": make_log_polynomial_LbrCost,
469 "TranShkGrid": get_TranShkGrid_from_TranShkDstn,
470 "solution_terminal": make_labor_intmarg_solution_terminal,
471 "kNrmInitDstn": make_lognormal_kNrm_init_dstn,
472 "pLvlInitDstn": make_lognormal_pLvl_init_dstn,
473}
475# Make a dictionary with parameters for the default constructor for kNrmInitDstn
476LaborIntMargConsumerType_kNrmInitDstn_default = {
477 "kLogInitMean": -12.0, # Mean of log initial capital
478 "kLogInitStd": 0.0, # Stdev of log initial capital
479 "kNrmInitCount": 15, # Number of points in initial capital discretization
480}
482# Make a dictionary with parameters for the default constructor for pLvlInitDstn
483LaborIntMargConsumerType_pLvlInitDstn_default = {
484 "pLogInitMean": 0.0, # Mean of log permanent income
485 "pLogInitStd": 0.0, # Stdev of log permanent income
486 "pLvlInitCount": 15, # Number of points in initial capital discretization
487}
490# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment
491LaborIntMargConsumerType_IncShkDstn_default = {
492 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks
493 "PermShkCount": 16, # Number of points in discrete approximation to permanent income shocks
494 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks
495 "TranShkCount": 15, # Number of points in discrete approximation to transitory income shocks
496 "UnempPrb": 0.05, # Probability of unemployment while working
497 "IncUnemp": 0.0, # Unemployment benefits replacement rate while working
498 "T_retire": 0, # Period of retirement (0 --> no retirement)
499 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired
500 "IncUnempRet": 0.0, # "Unemployment" benefits when retired
501}
503# Default parameters to make aXtraGrid using make_assets_grid
504LaborIntMargConsumerType_aXtraGrid_default = {
505 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value
506 "aXtraMax": 80.0, # Maximum end-of-period "assets above minimum" value
507 "aXtraNestFac": 3, # Exponential nesting factor for aXtraGrid
508 "aXtraCount": 200, # Number of points in the grid of "assets above minimum"
509 "aXtraExtra": None, # Additional other values to add in grid (optional)
510}
512# Default parameter to make LbrCost using make_log_polynomial_LbrCost
513LaborIntMargConsumerType_LbrCost_default = {
514 "LbrCostCoeffs": [
515 -1.0
516 ] # Polynomial coefficients (for age) on log labor utility cost
517}
519# Make a dictionary to specify an intensive margin labor supply choice consumer type
520LaborIntMargConsumerType_solving_default = {
521 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL
522 "cycles": 1, # Finite, non-cyclic model
523 "T_cycle": 1, # Number of periods in the cycle for this agent type
524 "pseudo_terminal": False, # Terminal period really does exist
525 "constructors": LaborIntMargConsumerType_constructors_default, # See dictionary above
526 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL
527 "CRRA": 2.0, # Coefficient of relative risk aversion
528 "Rfree": [1.03], # Interest factor on retained assets
529 "DiscFac": 0.96, # Intertemporal discount factor
530 "LivPrb": [0.98], # Survival probability after each period
531 "PermGroFac": [1.01], # Permanent income growth factor
532 "WageRte": [1.0], # Wage rate paid on labor income
533 "BoroCnstArt": None, # Artificial borrowing constraint
534 "vFuncBool": False, # Whether to calculate the value function during solution
535 "CubicBool": False, # Whether to use cubic spline interpolation when True
536 # (Uses linear spline interpolation for cFunc when False)
537}
538LaborIntMargConsumerType_simulation_default = {
539 # PARAMETERS REQUIRED TO SIMULATE THE MODEL
540 "AgentCount": 10000, # Number of agents of this type
541 "T_age": None, # Age after which simulated agents are automatically killed
542 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
543 # (The portion of PermGroFac attributable to aggregate productivity growth)
544 "NewbornTransShk": False, # Whether Newborns have transitory shock
545 # ADDITIONAL OPTIONAL PARAMETERS
546 "PerfMITShk": False, # Do Perfect Foresight MIT Shock
547 # (Forces Newborns to follow solution path of the agent they replaced if True)
548 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021)
549}
550LaborIntMargConsumerType_default = {}
551LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_IncShkDstn_default)
552LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_aXtraGrid_default)
553LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_LbrCost_default)
554LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_solving_default)
555LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_simulation_default)
556LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_kNrmInitDstn_default)
557LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_pLvlInitDstn_default)
558init_labor_intensive = LaborIntMargConsumerType_default
561class LaborIntMargConsumerType(IndShockConsumerType):
562 r"""
563 A class representing agents who make a decision each period about how much
564 to consume vs save and how much labor to supply (as a fraction of their time).
565 They get CRRA utility from a composite good :math:`x_t = c_t*z_t^alpha`, and discount
566 future utility flows at a constant factor.
568 .. math::
569 \newcommand{\CRRA}{\rho}
570 \newcommand{\DiePrb}{\mathsf{D}}
571 \newcommand{\PermGroFac}{\Gamma}
572 \newcommand{\Rfree}{\mathsf{R}}
573 \newcommand{\DiscFac}{\beta}
574 \begin{align*}
575 v_t(b_t,\theta_{t}) &= \max_{c_t,L_{t}}u_{t}(c_t,L_t) + \DiscFac (1 - \DiePrb_{t+1}) \mathbb{E}_{t} \left[ (\PermGroFac_{t+1} \psi_{t+1})^{1-\CRRA} v_{t+1}(b_{t+1},\theta_{t+1}) \right], \\
576 & \text{s.t.} \\
577 m_{t} &= b_{t} + L_{t}\theta_{t} \text{WageRte}_{t}, \\
578 a_t &= m_t - c_t, \\
579 b_{t+1} &= a_t \Rfree_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}), \\
580 (\psi_{t+1},\theta_{t+1}) &\sim F_{t+1}, \\
581 \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1, \\
582 u_{t}(c,L) &= \frac{(c (1-L)^{\alpha_t})^{1-\CRRA}}{1-\CRRA} \\
583 \end{align*}
586 Constructors
587 ------------
588 IncShkDstn: Constructor, :math:`\psi`, :math:`\theta`
589 The agent's income shock distributions.
591 Its default constructor is :func:`HARK.Calibration.Income.IncomeProcesses.construct_lognormal_income_process_unemployment`
592 aXtraGrid: Constructor
593 The agent's asset grid.
595 Its default constructor is :func:`HARK.utilities.make_assets_grid`
596 LbrCost: Constructor, :math:`\alpha`
597 The agent's labor cost function.
599 Its default constructor is :func:`HARK.ConsumptionSaving.ConsLaborModel.make_log_polynomial_LbrCost`
601 Solving Parameters
602 ------------------
603 cycles: int
604 0 specifies an infinite horizon model, 1 specifies a finite model.
605 T_cycle: int
606 Number of periods in the cycle for this agent type.
607 CRRA: float, default=2.0, :math:`\rho`
608 Coefficient of Relative Risk Aversion. Must be greater than :math:`\max_{t}({\frac{\alpha_t}{\alpha_t+1}})`
609 Rfree: float or list[float], time varying, :math:`\mathsf{R}`
610 Risk Free interest rate. Pass a list of floats to make Rfree time varying.
611 DiscFac: float, :math:`\beta`
612 Intertemporal discount factor.
613 LivPrb: list[float], time varying, :math:`1-\mathsf{D}`
614 Survival probability after each period.
615 WageRte: list[float], time varying
616 Wage rate paid on labor income.
617 PermGroFac: list[float], time varying, :math:`\Gamma`
618 Permanent income growth factor.
620 Simulation Parameters
621 ---------------------
622 AgentCount: int
623 Number of agents of this kind that are created during simulations.
624 T_age: int
625 Age after which to automatically kill agents, None to ignore.
626 T_sim: int, required for simulation
627 Number of periods to simulate.
628 track_vars: list[strings]
629 List of variables that should be tracked when running the simulation.
630 For this agent, the options are 'Lbr', 'PermShk', 'TranShk', 'aLvl', 'aNrm', 'bNrm', 'cNrm', 'mNrm', 'pLvl', and 'who_dies'.
632 PermShk is the agent's permanent income shock
634 TranShk is the agent's transitory income shock
636 aLvl is the nominal asset level
638 aNrm is the normalized assets
640 bNrm is the normalized resources without this period's labor income
642 cNrm is the normalized consumption
644 mNrm is the normalized market resources
646 pLvl is the permanent income level
648 Lbr is the share of the agent's time spent working
650 who_dies is the array of which agents died
651 aNrmInitMean: float
652 Mean of Log initial Normalized Assets.
653 aNrmInitStd: float
654 Std of Log initial Normalized Assets.
655 pLvlInitMean: float
656 Mean of Log initial permanent income.
657 pLvlInitStd: float
658 Std of Log initial permanent income.
659 PermGroFacAgg: float
660 Aggregate permanent income growth factor (The portion of PermGroFac attributable to aggregate productivity growth).
661 PerfMITShk: boolean
662 Do Perfect Foresight MIT Shock (Forces Newborns to follow solution path of the agent they replaced if True).
663 NewbornTransShk: boolean
664 Whether Newborns have transitory shock.
666 Attributes
667 ----------
668 solution: list[Consumer solution object]
669 Created by the :func:`.solve` method. Finite horizon models create a list with T_cycle+1 elements, for each period in the solution.
670 Infinite horizon solutions return a list with T_cycle elements for each period in the cycle.
672 Visit :class:`HARK.ConsumptionSaving.ConsLaborModel.ConsumerLaborSolution` for more information about the solution.
674 history: Dict[Array]
675 Created by running the :func:`.simulate()` method.
676 Contains the variables in track_vars. Each item in the dictionary is an array with the shape (T_sim,AgentCount).
677 Visit :class:`HARK.core.AgentType.simulate` for more information.
678 """
680 IncShkDstn_default = LaborIntMargConsumerType_IncShkDstn_default
681 aXtraGrid_default = LaborIntMargConsumerType_aXtraGrid_default
682 LbrCost_default = LaborIntMargConsumerType_LbrCost_default
683 solving_default = LaborIntMargConsumerType_solving_default
684 simulation_default = LaborIntMargConsumerType_simulation_default
686 default_ = {
687 "params": LaborIntMargConsumerType_default,
688 "solver": solve_ConsLaborIntMarg,
689 "model": "ConsLaborIntMarg.yaml",
690 }
692 time_vary_ = copy(IndShockConsumerType.time_vary_)
693 time_vary_ += ["WageRte", "LbrCost", "TranShkGrid"]
694 time_inv_ = copy(IndShockConsumerType.time_inv_)
696 def pre_solve(self):
697 self.construct("solution_terminal")
699 def calc_bounding_values(self): # pragma: nocover
700 """
701 NOT YET IMPLEMENTED FOR THIS CLASS
702 """
703 raise NotImplementedError()
705 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): # pragma: nocover
706 """
707 NOT YET IMPLEMENTED FOR THIS CLASS
708 """
709 raise NotImplementedError()
711 def get_states(self):
712 """
713 Calculates updated values of normalized bank balances and permanent income
714 level for each agent. Uses pLvlNow, aNrmNow, PermShkNow. Calls the get_states
715 method for the parent class, then erases mNrmNow, which cannot be calculated
716 until after get_controls in this model.
718 Parameters
719 ----------
720 None
722 Returns
723 -------
724 None
725 """
726 IndShockConsumerType.get_states(self)
727 # Delete market resource calculation
728 self.state_now["mNrm"][:] = np.nan
730 def get_controls(self):
731 """
732 Calculates consumption and labor supply for each consumer of this type
733 using the consumption and labor functions in each period of the cycle.
735 Parameters
736 ----------
737 None
739 Returns
740 -------
741 None
742 """
743 cNrmNow = np.zeros(self.AgentCount) + np.nan
744 MPCnow = np.zeros(self.AgentCount) + np.nan
745 LbrNow = np.zeros(self.AgentCount) + np.nan
746 for t in range(self.T_cycle):
747 these = t == self.t_cycle
748 cNrmNow[these] = self.solution[t].cFunc(
749 self.state_now["bNrm"][these], self.shocks["TranShk"][these]
750 ) # Assign consumption values
751 MPCnow[these] = self.solution[t].cFunc.derivativeX(
752 self.state_now["bNrm"][these], self.shocks["TranShk"][these]
753 ) # Assign marginal propensity to consume values (derivative)
754 LbrNow[these] = self.solution[t].LbrFunc(
755 self.state_now["bNrm"][these], self.shocks["TranShk"][these]
756 ) # Assign labor supply
757 self.controls["cNrm"] = cNrmNow
758 self.MPCnow = MPCnow
759 self.controls["Lbr"] = LbrNow
761 def get_poststates(self):
762 """
763 Calculates end-of-period assets for each consumer of this type.
765 Parameters
766 ----------
767 None
769 Returns
770 -------
771 None
772 """
773 # Make an array of wage rates by age
774 Wage = np.zeros(self.AgentCount)
775 for t in range(self.T_cycle):
776 these = t == self.t_cycle
777 Wage[these] = self.WageRte[t]
778 LbrEff = self.controls["Lbr"] * self.shocks["TranShk"]
779 yNrmNow = LbrEff * Wage
780 mNrmNow = self.state_now["bNrm"] + yNrmNow
781 aNrmNow = mNrmNow - self.controls["cNrm"]
783 self.state_now["LbrEff"] = LbrEff
784 self.state_now["mNrm"] = mNrmNow
785 self.state_now["aNrm"] = aNrmNow
786 self.state_now["yNrm"] = yNrmNow
787 super().get_poststates()
789 def plot_cFunc(self, t, bMin=None, bMax=None, ShkSet=None):
790 """
791 Plot the consumption function by bank balances at a given set of transitory shocks.
793 Parameters
794 ----------
795 t : int
796 Time index of the solution for which to plot the consumption function.
797 bMin : float or None
798 Minimum value of bNrm at which to begin the plot. If None, defaults
799 to the minimum allowable value of bNrm for each transitory shock.
800 bMax : float or None
801 Maximum value of bNrm at which to end the plot. If None, defaults
802 to bMin + 20.
803 ShkSet : [float] or None
804 Array or list of transitory shocks at which to plot the consumption
805 function. If None, defaults to the TranShkGrid for this time period.
807 Returns
808 -------
809 None
810 """
811 if ShkSet is None:
812 ShkSet = self.TranShkGrid[t]
814 for j in range(len(ShkSet)):
815 TranShk = ShkSet[j]
816 bMin_temp = self.solution[t].bNrmMin(TranShk) if bMin is None else bMin
817 bMax_temp = bMin_temp + 20.0 if bMax is None else bMax
819 B = np.linspace(bMin_temp, bMax_temp, 300)
820 C = self.solution[t].cFunc(B, TranShk * np.ones_like(B))
821 plt.plot(B, C)
822 plt.xlabel(r"Beginning of period normalized bank balances $b_t$")
823 plt.ylabel(r"Normalized consumption level $c_t$")
824 plt.ylim([0.0, None])
825 plt.xlim(bMin, bMax)
826 plt.show(block=False)
828 def plot_LbrFunc(self, t, bMin=None, bMax=None, ShkSet=None):
829 """
830 Plot the labor supply function by bank balances at a given set of transitory shocks.
832 Parameters
833 ----------
834 t : int
835 Time index of the solution for which to plot the labor supply function.
836 bMin : float or None
837 Minimum value of bNrm at which to begin the plot. If None, defaults
838 to the minimum allowable value of bNrm for each transitory shock.
839 bMax : float or None
840 Maximum value of bNrm at which to end the plot. If None, defaults
841 to bMin + 20.
842 ShkSet : [float] or None
843 Array or list of transitory shocks at which to plot the labor supply
844 function. If None, defaults to the TranShkGrid for this time period.
846 Returns
847 -------
848 None
849 """
850 if ShkSet is None:
851 ShkSet = self.TranShkGrid[t]
853 for j in range(len(ShkSet)):
854 TranShk = ShkSet[j]
855 bMin_temp = self.solution[t].bNrmMin(TranShk) if bMin is None else bMin
856 bMax_temp = bMin_temp + 20.0 if bMax is None else bMax
858 B = np.linspace(bMin_temp, bMax_temp, 300)
859 L = self.solution[t].LbrFunc(B, TranShk * np.ones_like(B))
860 plt.plot(B, L)
861 plt.xlabel(r"Beginning of period normalized bank balances $b_t$")
862 plt.ylabel(r"Labor supply $\ell_t$")
863 plt.ylim([-0.001, 1.001])
864 plt.xlim(bMin, bMax)
865 plt.show(block=False)
867 def check_conditions(self, verbose=None):
868 raise NotImplementedError()
870 def calc_limiting_values(self):
871 raise NotImplementedError()
874###############################################################################
876# Make a dictionary for intensive margin labor supply model with finite lifecycle
877init_labor_lifecycle = init_labor_intensive.copy()
878init_labor_lifecycle["PermGroFac"] = [
879 1.01,
880 1.01,
881 1.01,
882 1.01,
883 1.01,
884 1.02,
885 1.02,
886 1.02,
887 1.02,
888 1.02,
889]
890init_labor_lifecycle["PermShkStd"] = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
891init_labor_lifecycle["TranShkStd"] = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
892init_labor_lifecycle["LivPrb"] = [
893 0.99,
894 0.9,
895 0.8,
896 0.7,
897 0.6,
898 0.5,
899 0.4,
900 0.3,
901 0.2,
902 0.1,
903] # Living probability decreases as time moves forward.
904init_labor_lifecycle["WageRte"] = [
905 1.0,
906 1.0,
907 1.0,
908 1.0,
909 1.0,
910 1.0,
911 1.0,
912 1.0,
913 1.0,
914 1.0,
915] # Wage rate in a lifecycle
916init_labor_lifecycle["Rfree"] = 10 * [1.03]
917# Assume labor cost coeffs is a polynomial of degree 1
918init_labor_lifecycle["LbrCostCoeffs"] = np.array([-2.0, 0.4])
919init_labor_lifecycle["T_cycle"] = 10
920# init_labor_lifecycle['T_retire'] = 7 # IndexError at line 774 in interpolation.py.
921init_labor_lifecycle["T_age"] = (
922 11 # Make sure that old people die at terminal age and don't turn into newborns!
923)