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

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 

12import sys 

13from copy import copy 

14 

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 

40 

41plt.ion() 

42 

43 

44class ConsumerLaborSolution(MetricObject): 

45 """ 

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

47 

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

66 

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

68 

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 

80 

81 

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. 

86 

87 .. math:: 

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

89 

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. 

97 

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 

110 

111 

112############################################################################### 

113 

114 

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. 

121 

122 Parameters 

123 ---------- 

124 None 

125 

126 Returns 

127 ------- 

128 None 

129 """ 

130 t = -1 

131 TranShkGrid_T = TranShkGrid[t] 

132 LbrCost_T = LbrCost[t] 

133 WageRte_T = WageRte[t] 

134 

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 

139 

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. 

144 

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 

153 

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

155 mNrmTerm = bNrmGridTerm + LbrTerm * WageRte_T * TranShkGridTerm 

156 cNrmTerm = mNrmTerm # Consume everything we have 

157 

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) 

161 

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) 

166 

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 

171 

172 # Get the Marginal Value function 

173 vPnvrsFunc_terminal = BilinearInterp(vPnvrsTerm, bNrmGrid, TranShkGrid_T) 

174 vPfunc_terminal = MargValueFuncCRRA(vPnvrsFunc_terminal, CRRA) 

175 

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

177 bNrmMin_terminal = ConstantFunction(0.0) 

178 

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 

188 

189 

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. 

212 

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

253 

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

277 

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 

286 

287 def uPinv(X): 

288 return CRRAutilityP_inv(X, rho=CRRA) 

289 

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 

293 

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

296 

297 # Replicated transitory shock values for each a_t state 

298 TranShkVals_rep = np.tile( 

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

300 ) 

301 

302 # Replicated transitory shock probabilities for each a_t state 

303 TranShkPrbs_rep = np.tile( 

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

305 ) 

306 

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) 

310 

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) 

313 

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

315 vPbarNvrsNext = uPinv(vPbarNext) 

316 

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 ) 

321 

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

323 vPbarFuncNext = MargValueFuncCRRA(vPbarNvrsFuncNext, CRRA) 

324 

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

328 

329 # Replicated permanent shock values for each a_t value 

330 PermShkVals_rep = np.tile( 

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

332 ) 

333 

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 

339 

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) 

343 

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 ) 

351 

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 ) 

358 

359 # Flip it to be a row vector 

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

361 

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

364 

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 

370 

371 # Find optimal consumption from optimal composite good 

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

373 

374 # Find optimal leisure from optimal composite good 

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

376 

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 

380 

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 

392 

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 ) 

401 

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 

413 

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 ) 

420 

421 # Construct consumption and marginal value functions for this period 

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

423 

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] 

432 

433 # Make consumption function for this transitory shock 

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

435 

436 # Make labor function for this transitory shock 

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

438 

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

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

441 

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) 

446 

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) 

452 

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

454 vPfuncNow = MargValueFuncCRRA(vPnvrsFuncNow, CRRA) 

455 

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 

461 

462 

463############################################################################### 

464 

465 

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} 

478 

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} 

485 

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} 

492 

493 

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} 

506 

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} 

515 

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} 

522 

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 

563 

564 

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. 

571 

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*} 

588 

589 

590 Constructors 

591 ------------ 

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

593 The agent's income shock distributions. 

594 

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. 

598 

599 It's default constructor is :func:`HARK.utilities.make_assets_grid` 

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

601 The agent's labor cost function. 

602 

603 It's default constructor is :func:`HARK.ConsumptionSaving.ConsLaborModel.make_log_polynomial_LbrCost` 

604 

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. 

623 

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

635 

636 PermShk is the agent's permanent income shock 

637 

638 TranShk is the agent's transitory income shock 

639 

640 aLvl is the nominal asset level 

641 

642 aNrm is the normalized assets 

643 

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

645 

646 cNrm is the normalized consumption 

647 

648 mNrm is the normalized market resources 

649 

650 pLvl is the permanent income level 

651 

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

653 

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. 

669 

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. 

675 

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

677 

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

683 

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 

689 

690 default_ = { 

691 "params": LaborIntMargConsumerType_default, 

692 "solver": solve_ConsLaborIntMarg, 

693 "model": "ConsLaborIntMarg.yaml", 

694 } 

695 

696 time_vary_ = copy(IndShockConsumerType.time_vary_) 

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

698 time_inv_ = copy(IndShockConsumerType.time_inv_) 

699 

700 def calc_bounding_values(self): 

701 """ 

702 NOT YET IMPLEMENTED FOR THIS CLASS 

703 """ 

704 raise NotImplementedError() 

705 

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

711 

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. 

718 

719 Parameters 

720 ---------- 

721 None 

722 

723 Returns 

724 ------- 

725 None 

726 """ 

727 IndShockConsumerType.get_states(self) 

728 # Delete market resource calculation 

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

730 

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. 

735 

736 Parameters 

737 ---------- 

738 None 

739 

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 

761 

762 def get_poststates(self): 

763 """ 

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

765 

766 Parameters 

767 ---------- 

768 None 

769 

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

783 

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

789 

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. 

793 

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. 

807 

808 Returns 

809 ------- 

810 None 

811 """ 

812 if ShkSet is None: 

813 ShkSet = self.TranShkGrid[t] 

814 

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 

825 

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) 

834 

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. 

838 

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. 

852 

853 Returns 

854 ------- 

855 None 

856 """ 

857 if ShkSet is None: 

858 ShkSet = self.TranShkGrid[t] 

859 

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 

870 

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) 

879 

880 

881############################################################################### 

882 

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)