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