Coverage for HARK / ConsumptionSaving / ConsLaborModel.py: 100%

232 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 06:00 +0000

1""" 

2Subclasses of AgentType representing consumers who make decisions about how much 

3labor to supply, as well as a consumption-saving decision. 

4 

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""" 

11 

12from copy import copy 

13 

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 

39 

40plt.ion() 

41 

42 

43class ConsumerLaborSolution(MetricObject): 

44 """ 

45 A class for representing one period of the solution to a Consumer Labor problem. 

46 

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 """ 

65 

66 distance_criteria = ["cFunc", "LbrFunc"] 

67 

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 

79 

80 

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. 

85 

86 .. math:: 

87 \text{LbrCost}_{t}=\exp(\sum \text{LbrCostCoeffs}_n t^{n}) 

88 

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. 

96 

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 

109 

110 

111############################################################################### 

112 

113 

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. 

120 

121 Parameters 

122 ---------- 

123 None 

124 

125 Returns 

126 ------- 

127 None 

128 """ 

129 t = -1 

130 TranShkGrid_T = TranShkGrid[t] 

131 LbrCost_T = LbrCost[t] 

132 WageRte_T = WageRte[t] 

133 

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 

138 

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. 

143 

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 

152 

153 # Calculate market resources in terminal period, which is consumption 

154 mNrmTerm = bNrmGridTerm + LbrTerm * WageRte_T * TranShkGridTerm 

155 cNrmTerm = mNrmTerm # Consume everything we have 

156 

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) 

160 

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) 

165 

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 

170 

171 # Get the Marginal Value function 

172 vPnvrsFunc_terminal = BilinearInterp(vPnvrsTerm, bNrmGrid, TranShkGrid_T) 

173 vPfunc_terminal = MargValueFuncCRRA(vPnvrsFunc_terminal, CRRA) 

174 

175 # Trivial function that return the same real output for any input 

176 bNrmMin_terminal = ConstantFunction(0.0) 

177 

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 

187 

188 

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. 

211 

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`. 

252 

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.") 

273 

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 

282 

283 def uPinv(X): 

284 return CRRAutilityP_inv(X, rho=CRRA) 

285 

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 

289 

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)) 

292 

293 # Replicated transitory shock values for each a_t state 

294 TranShkVals_rep = np.tile( 

295 np.reshape(TranShkVals, (1, TranShkCount)), (aXtraCount, 1) 

296 ) 

297 

298 # Replicated transitory shock probabilities for each a_t state 

299 TranShkPrbs_rep = np.tile( 

300 np.reshape(TranShkPrbs, (1, TranShkCount)), (aXtraCount, 1) 

301 ) 

302 

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) 

306 

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) 

309 

310 # Transformed marginal value through the inverse marginal utility function to "decurve" it 

311 vPbarNvrsNext = uPinv(vPbarNext) 

312 

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 ) 

317 

318 # "Recurve" the intermediate marginal value function through the marginal utility function 

319 vPbarFuncNext = MargValueFuncCRRA(vPbarNvrsFuncNext, CRRA) 

320 

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)) 

324 

325 # Replicated permanent shock values for each a_t value 

326 PermShkVals_rep = np.tile( 

327 np.reshape(PermShkVals, (1, PermShkCount)), (aXtraCount, 1) 

328 ) 

329 

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 

335 

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) 

339 

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 ) 

347 

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 ) 

354 

355 # Flip it to be a row vector 

356 TranShkScaleFac = np.reshape(TranShkScaleFac_temp, (1, TranShkGrid.size)) 

357 

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)) 

360 

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 

366 

367 # Find optimal consumption from optimal composite good 

368 cNrmNow = (((WageRte * TranShkGrid_rep) / LbrCost) ** (LbrCost * frac)) * xNowPow 

369 

370 # Find optimal leisure from optimal composite good 

371 LsrNow = (LbrCost / (WageRte * TranShkGrid_rep)) ** frac * xNowPow 

372 

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 

376 

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 

388 

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 ) 

397 

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 

409 

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 ) 

416 

417 # Construct consumption and marginal value functions for this period 

418 bNrmMinNow = LinearInterp(TranShkGrid, bNowArray[0, :]) 

419 

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] 

428 

429 # Make consumption function for this transitory shock 

430 cFuncNow_list.append(LinearInterp(bNrmNow_temp, cNowArray[:, j])) 

431 

432 # Make labor function for this transitory shock 

433 LbrFuncNow_list.append(LinearInterp(bNrmNow_temp, LbrNowArray[:, j])) 

434 

435 # Make pseudo-inverse marginal value function for this transitory shock 

436 vPnvrsFuncNow_list.append(LinearInterp(bNrmNow_temp, vPnvrsNowArray[:, j])) 

437 

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) 

442 

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) 

448 

449 # Construct the marginal value function by "recurving" its pseudo-inverse 

450 vPfuncNow = MargValueFuncCRRA(vPnvrsFuncNow, CRRA) 

451 

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 

457 

458 

459############################################################################### 

460 

461 

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} 

474 

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} 

481 

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} 

488 

489 

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} 

502 

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} 

511 

512# Default parameter to make LbrCost using make_log_polynomial_LbrCost 

513LaborIntMargConsumerType_LbrCost_default = { 

514 "LbrCostCoeffs": [-1.0] # Polynomial age coeffs on log labor utility cost 

515} 

516 

517# Make a dictionary to specify an intensive margin labor supply choice consumer type 

518LaborIntMargConsumerType_solving_default = { 

519 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

520 "cycles": 1, # Finite, non-cyclic model 

521 "T_cycle": 1, # Number of periods in the cycle for this agent type 

522 "pseudo_terminal": False, # Terminal period really does exist 

523 "constructors": LaborIntMargConsumerType_constructors_default, # See dictionary above 

524 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

525 "CRRA": 2.0, # Coefficient of relative risk aversion 

526 "Rfree": [1.03], # Interest factor on retained assets 

527 "DiscFac": 0.96, # Intertemporal discount factor 

528 "LivPrb": [0.98], # Survival probability after each period 

529 "PermGroFac": [1.01], # Permanent income growth factor 

530 "WageRte": [1.0], # Wage rate paid on labor income 

531 "BoroCnstArt": None, # Artificial borrowing constraint 

532 "vFuncBool": False, # Whether to calculate the value function during solution 

533 "CubicBool": False, # Whether to use cubic spline interpolation when True 

534 # (Uses linear spline interpolation for cFunc when False) 

535} 

536LaborIntMargConsumerType_simulation_default = { 

537 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

538 "AgentCount": 10000, # Number of agents of this type 

539 "T_age": None, # Age after which simulated agents are automatically killed 

540 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor 

541 # (The portion of PermGroFac attributable to aggregate productivity growth) 

542 "NewbornTransShk": False, # Whether Newborns have transitory shock 

543 # ADDITIONAL OPTIONAL PARAMETERS 

544 "PerfMITShk": False, # Do Perfect Foresight MIT Shock 

545 # (Forces Newborns to follow solution path of the agent they replaced if True) 

546 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021) 

547} 

548LaborIntMargConsumerType_default = {} 

549LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_IncShkDstn_default) 

550LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_aXtraGrid_default) 

551LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_LbrCost_default) 

552LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_solving_default) 

553LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_simulation_default) 

554LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_kNrmInitDstn_default) 

555LaborIntMargConsumerType_default.update(LaborIntMargConsumerType_pLvlInitDstn_default) 

556init_labor_intensive = LaborIntMargConsumerType_default 

557 

558 

559class LaborIntMargConsumerType(IndShockConsumerType): 

560 r""" 

561 A class representing agents who make a decision each period about how much 

562 to consume vs save and how much labor to supply (as a fraction of their time). 

563 They get CRRA utility from a composite good :math:`x_t = c_t*z_t^alpha`, and discount 

564 future utility flows at a constant factor. 

565 

566 .. math:: 

567 \newcommand{\CRRA}{\rho} 

568 \newcommand{\DiePrb}{\mathsf{D}} 

569 \newcommand{\PermGroFac}{\Gamma} 

570 \newcommand{\Rfree}{\mathsf{R}} 

571 \newcommand{\DiscFac}{\beta} 

572 \begin{align*} 

573 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], \\ 

574 & \text{s.t.} \\ 

575 m_{t} &= b_{t} + L_{t}\theta_{t} \text{WageRte}_{t}, \\ 

576 a_t &= m_t - c_t, \\ 

577 b_{t+1} &= a_t \Rfree_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}), \\ 

578 (\psi_{t+1},\theta_{t+1}) &\sim F_{t+1}, \\ 

579 \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1, \\ 

580 u_{t}(c,L) &= \frac{(c (1-L)^{\alpha_t})^{1-\CRRA}}{1-\CRRA} \\ 

581 \end{align*} 

582 

583 

584 Constructors 

585 ------------ 

586 IncShkDstn: Constructor, :math:`\psi`, :math:`\theta` 

587 The agent's income shock distributions. 

588 

589 Its default constructor is :func:`HARK.Calibration.Income.IncomeProcesses.construct_lognormal_income_process_unemployment` 

590 aXtraGrid: Constructor 

591 The agent's asset grid. 

592 

593 Its default constructor is :func:`HARK.utilities.make_assets_grid` 

594 LbrCost: Constructor, :math:`\alpha` 

595 The agent's labor cost function. 

596 

597 Its default constructor is :func:`HARK.ConsumptionSaving.ConsLaborModel.make_log_polynomial_LbrCost` 

598 

599 Solving Parameters 

600 ------------------ 

601 cycles: int 

602 0 specifies an infinite horizon model, 1 specifies a finite model. 

603 T_cycle: int 

604 Number of periods in the cycle for this agent type. 

605 CRRA: float, default=2.0, :math:`\rho` 

606 Coefficient of Relative Risk Aversion. Must be greater than :math:`\max_{t}({\frac{\alpha_t}{\alpha_t+1}})` 

607 Rfree: float or list[float], time varying, :math:`\mathsf{R}` 

608 Risk Free interest rate. Pass a list of floats to make Rfree time varying. 

609 DiscFac: float, :math:`\beta` 

610 Intertemporal discount factor. 

611 LivPrb: list[float], time varying, :math:`1-\mathsf{D}` 

612 Survival probability after each period. 

613 WageRte: list[float], time varying 

614 Wage rate paid on labor income. 

615 PermGroFac: list[float], time varying, :math:`\Gamma` 

616 Permanent income growth factor. 

617 

618 Simulation Parameters 

619 --------------------- 

620 AgentCount: int 

621 Number of agents of this kind that are created during simulations. 

622 T_age: int 

623 Age after which to automatically kill agents, None to ignore. 

624 T_sim: int, required for simulation 

625 Number of periods to simulate. 

626 track_vars: list[strings] 

627 List of variables that should be tracked when running the simulation. 

628 For this agent, the options are 'Lbr', 'PermShk', 'TranShk', 'aLvl', 'aNrm', 'bNrm', 'cNrm', 'mNrm', 'pLvl', and 'who_dies'. 

629 

630 PermShk is the agent's permanent income shock 

631 

632 TranShk is the agent's transitory income shock 

633 

634 aLvl is the nominal asset level 

635 

636 aNrm is the normalized assets 

637 

638 bNrm is the normalized resources without this period's labor income 

639 

640 cNrm is the normalized consumption 

641 

642 mNrm is the normalized market resources 

643 

644 pLvl is the permanent income level 

645 

646 Lbr is the share of the agent's time spent working 

647 

648 who_dies is the array of which agents died 

649 aNrmInitMean: float 

650 Mean of Log initial Normalized Assets. 

651 aNrmInitStd: float 

652 Std of Log initial Normalized Assets. 

653 pLvlInitMean: float 

654 Mean of Log initial permanent income. 

655 pLvlInitStd: float 

656 Std of Log initial permanent income. 

657 PermGroFacAgg: float 

658 Aggregate permanent income growth factor (The portion of PermGroFac attributable to aggregate productivity growth). 

659 PerfMITShk: boolean 

660 Do Perfect Foresight MIT Shock (Forces Newborns to follow solution path of the agent they replaced if True). 

661 NewbornTransShk: boolean 

662 Whether Newborns have transitory shock. 

663 

664 Attributes 

665 ---------- 

666 solution: list[Consumer solution object] 

667 Created by the :func:`.solve` method. Finite horizon models create a list with T_cycle+1 elements, for each period in the solution. 

668 Infinite horizon solutions return a list with T_cycle elements for each period in the cycle. 

669 

670 Visit :class:`HARK.ConsumptionSaving.ConsLaborModel.ConsumerLaborSolution` for more information about the solution. 

671 

672 history: Dict[Array] 

673 Created by running the :func:`.simulate()` method. 

674 Contains the variables in track_vars. Each item in the dictionary is an array with the shape (T_sim,AgentCount). 

675 Visit :class:`HARK.core.AgentType.simulate` for more information. 

676 """ 

677 

678 IncShkDstn_default = LaborIntMargConsumerType_IncShkDstn_default 

679 aXtraGrid_default = LaborIntMargConsumerType_aXtraGrid_default 

680 LbrCost_default = LaborIntMargConsumerType_LbrCost_default 

681 solving_default = LaborIntMargConsumerType_solving_default 

682 simulation_default = LaborIntMargConsumerType_simulation_default 

683 

684 default_ = { 

685 "params": LaborIntMargConsumerType_default, 

686 "solver": solve_ConsLaborIntMarg, 

687 "model": "ConsLaborIntMarg.yaml", 

688 "track_vars": ["aNrm", "cNrm", "Lbr", "mNrm", "pLvl"], 

689 } 

690 

691 time_vary_ = copy(IndShockConsumerType.time_vary_) 

692 time_vary_ += ["WageRte", "LbrCost", "TranShkGrid"] 

693 time_inv_ = copy(IndShockConsumerType.time_inv_) 

694 

695 def pre_solve(self): 

696 self.construct("solution_terminal") 

697 

698 def calc_bounding_values(self): # pragma: nocover 

699 """ 

700 NOT YET IMPLEMENTED FOR THIS CLASS 

701 """ 

702 raise NotImplementedError() 

703 

704 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): # pragma: nocover 

705 """ 

706 NOT YET IMPLEMENTED FOR THIS CLASS 

707 """ 

708 raise NotImplementedError() 

709 

710 def get_states(self): 

711 """ 

712 Calculates updated values of normalized bank balances and permanent income 

713 level for each agent. Uses pLvlNow, aNrmNow, PermShkNow. Calls the get_states 

714 method for the parent class, then erases mNrmNow, which cannot be calculated 

715 until after get_controls in this model. 

716 

717 Parameters 

718 ---------- 

719 None 

720 

721 Returns 

722 ------- 

723 None 

724 """ 

725 IndShockConsumerType.get_states(self) 

726 # Delete market resource calculation 

727 self.state_now["mNrm"][:] = np.nan 

728 

729 def get_controls(self): 

730 """ 

731 Calculates consumption and labor supply for each consumer of this type 

732 using the consumption and labor functions in each period of the cycle. 

733 

734 Parameters 

735 ---------- 

736 None 

737 

738 Returns 

739 ------- 

740 None 

741 """ 

742 cNrmNow = np.zeros(self.AgentCount) + np.nan 

743 MPCnow = np.zeros(self.AgentCount) + np.nan 

744 LbrNow = np.zeros(self.AgentCount) + np.nan 

745 for t in range(self.T_cycle): 

746 these = t == self.t_cycle 

747 cNrmNow[these] = self.solution[t].cFunc( 

748 self.state_now["bNrm"][these], self.shocks["TranShk"][these] 

749 ) # Assign consumption values 

750 MPCnow[these] = self.solution[t].cFunc.derivativeX( 

751 self.state_now["bNrm"][these], self.shocks["TranShk"][these] 

752 ) # Assign marginal propensity to consume values (derivative) 

753 LbrNow[these] = self.solution[t].LbrFunc( 

754 self.state_now["bNrm"][these], self.shocks["TranShk"][these] 

755 ) # Assign labor supply 

756 self.controls["cNrm"] = cNrmNow 

757 self.MPCnow = MPCnow 

758 self.controls["Lbr"] = LbrNow 

759 

760 def get_poststates(self): 

761 """ 

762 Calculates end-of-period assets for each consumer of this type. 

763 

764 Parameters 

765 ---------- 

766 None 

767 

768 Returns 

769 ------- 

770 None 

771 """ 

772 # Make an array of wage rates by age 

773 Wage = np.zeros(self.AgentCount) 

774 for t in range(self.T_cycle): 

775 these = t == self.t_cycle 

776 Wage[these] = self.WageRte[t] 

777 LbrEff = self.controls["Lbr"] * self.shocks["TranShk"] 

778 yNrmNow = LbrEff * Wage 

779 mNrmNow = self.state_now["bNrm"] + yNrmNow 

780 aNrmNow = mNrmNow - self.controls["cNrm"] 

781 

782 self.state_now["LbrEff"] = LbrEff 

783 self.state_now["mNrm"] = mNrmNow 

784 self.state_now["aNrm"] = aNrmNow 

785 self.state_now["yNrm"] = yNrmNow 

786 super().get_poststates() 

787 

788 def plot_cFunc(self, t, bMin=None, bMax=None, ShkSet=None): 

789 """ 

790 Plot the consumption function by bank balances at a given set of transitory shocks. 

791 

792 Parameters 

793 ---------- 

794 t : int 

795 Time index of the solution for which to plot the consumption function. 

796 bMin : float or None 

797 Minimum value of bNrm at which to begin the plot. If None, defaults 

798 to the minimum allowable value of bNrm for each transitory shock. 

799 bMax : float or None 

800 Maximum value of bNrm at which to end the plot. If None, defaults 

801 to bMin + 20. 

802 ShkSet : [float] or None 

803 Array or list of transitory shocks at which to plot the consumption 

804 function. If None, defaults to the TranShkGrid for this time period. 

805 

806 Returns 

807 ------- 

808 None 

809 """ 

810 if ShkSet is None: 

811 ShkSet = self.TranShkGrid[t] 

812 

813 for j in range(len(ShkSet)): 

814 TranShk = ShkSet[j] 

815 bMin_temp = self.solution[t].bNrmMin(TranShk) if bMin is None else bMin 

816 bMax_temp = bMin_temp + 20.0 if bMax is None else bMax 

817 

818 B = np.linspace(bMin_temp, bMax_temp, 300) 

819 C = self.solution[t].cFunc(B, TranShk * np.ones_like(B)) 

820 plt.plot(B, C) 

821 plt.xlabel(r"Beginning of period normalized bank balances $b_t$") 

822 plt.ylabel(r"Normalized consumption level $c_t$") 

823 plt.ylim([0.0, None]) 

824 plt.xlim(bMin, bMax) 

825 plt.show(block=False) 

826 

827 def plot_LbrFunc(self, t, bMin=None, bMax=None, ShkSet=None): 

828 """ 

829 Plot the labor supply function by bank balances at a given set of transitory shocks. 

830 

831 Parameters 

832 ---------- 

833 t : int 

834 Time index of the solution for which to plot the labor supply function. 

835 bMin : float or None 

836 Minimum value of bNrm at which to begin the plot. If None, defaults 

837 to the minimum allowable value of bNrm for each transitory shock. 

838 bMax : float or None 

839 Maximum value of bNrm at which to end the plot. If None, defaults 

840 to bMin + 20. 

841 ShkSet : [float] or None 

842 Array or list of transitory shocks at which to plot the labor supply 

843 function. If None, defaults to the TranShkGrid for this time period. 

844 

845 Returns 

846 ------- 

847 None 

848 """ 

849 if ShkSet is None: 

850 ShkSet = self.TranShkGrid[t] 

851 

852 for j in range(len(ShkSet)): 

853 TranShk = ShkSet[j] 

854 bMin_temp = self.solution[t].bNrmMin(TranShk) if bMin is None else bMin 

855 bMax_temp = bMin_temp + 20.0 if bMax is None else bMax 

856 

857 B = np.linspace(bMin_temp, bMax_temp, 300) 

858 L = self.solution[t].LbrFunc(B, TranShk * np.ones_like(B)) 

859 plt.plot(B, L) 

860 plt.xlabel(r"Beginning of period normalized bank balances $b_t$") 

861 plt.ylabel(r"Labor supply $\ell_t$") 

862 plt.ylim([-0.001, 1.001]) 

863 plt.xlim(bMin, bMax) 

864 plt.show(block=False) 

865 

866 def check_conditions(self, verbose=None): 

867 raise NotImplementedError() # pragma: nocover 

868 

869 def calc_limiting_values(self): 

870 raise NotImplementedError() # pragma: nocover 

871 

872 

873############################################################################### 

874 

875# Make a dictionary for intensive margin labor supply model with finite lifecycle 

876init_labor_lifecycle = init_labor_intensive.copy() 

877init_labor_lifecycle["PermGroFac"] = [ 

878 1.01, 

879 1.01, 

880 1.01, 

881 1.01, 

882 1.01, 

883 1.02, 

884 1.02, 

885 1.02, 

886 1.02, 

887 1.02, 

888] 

889init_labor_lifecycle["PermShkStd"] = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] 

890init_labor_lifecycle["TranShkStd"] = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] 

891init_labor_lifecycle["LivPrb"] = [ 

892 0.99, 

893 0.9, 

894 0.8, 

895 0.7, 

896 0.6, 

897 0.5, 

898 0.4, 

899 0.3, 

900 0.2, 

901 0.1, 

902] # Living probability decreases as time moves forward. 

903init_labor_lifecycle["WageRte"] = [ 

904 1.0, 

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] # Wage rate in a lifecycle 

915init_labor_lifecycle["Rfree"] = 10 * [1.03] 

916# Assume labor cost coeffs is a polynomial of degree 1 

917init_labor_lifecycle["LbrCostCoeffs"] = np.array([-2.0, 0.4]) 

918init_labor_lifecycle["T_cycle"] = 10 

919# init_labor_lifecycle['T_retire'] = 7 # IndexError at line 774 in interpolation.py. 

920init_labor_lifecycle["T_age"] = ( 

921 11 # Make sure that old people die at terminal age and don't turn into newborns! 

922)