Coverage for HARK/ConsumptionSaving/TractableBufferStockModel.py: 83%

152 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-02 05:14 +0000

1""" 

2Defines and solves the Tractable Buffer Stock model described in lecture notes 

3for "A Tractable Model of Buffer Stock Saving" (henceforth, TBS) available at 

4https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/consumption/TractableBufferStock 

5The model concerns an agent with constant relative risk aversion utility making 

6decisions over consumption and saving. He is subject to only a very particular 

7sort of risk: the possibility that he will become permanently unemployed until 

8the day he dies; barring this, his income is certain and grows at a constant rate. 

9 

10The model has an infinite horizon, but is not solved by backward iteration in a 

11traditional sense. Because of the very specific assumptions about risk, it is 

12possible to find the agent's steady state or target level of market resources 

13when employed, as well as information about the optimal consumption rule at this 

14target level. The full consumption function can then be constructed by "back- 

15shooting", inverting the Euler equation to find what consumption *must have been* 

16in the previous period. The consumption function is thus constructed by repeat- 

17edly adding "stable arm" points to either end of a growing list until specified 

18bounds are exceeded. 

19 

20Despite the non-standard solution method, the iterative process can be embedded 

21in the HARK framework, as shown below. 

22""" 

23 

24from copy import copy 

25 

26import numpy as np 

27from scipy.optimize import brentq, newton 

28 

29from HARK import AgentType, NullFunc 

30from HARK.distributions import Bernoulli, Lognormal 

31from HARK.interpolation import LinearInterp, CubicInterp 

32 

33# Import the HARK library. 

34from HARK.metric import MetricObject 

35from HARK.rewards import ( 

36 CRRAutility, 

37 CRRAutility_inv, 

38 CRRAutility_invP, 

39 CRRAutilityP, 

40 CRRAutilityP_inv, 

41 CRRAutilityPP, 

42 CRRAutilityPPP, 

43 CRRAutilityPPPP, 

44) 

45 

46__all__ = ["TractableConsumerSolution", "TractableConsumerType"] 

47 

48# If you want to run the "tractable" version of cstwMPC, use cstwMPCagent from 

49# cstwMPC REMARK and have TractableConsumerType inherit from cstwMPCagent rather than AgentType 

50 

51# Define utility function and its derivatives (plus inverses) 

52utility = CRRAutility 

53utilityP = CRRAutilityP 

54utilityPP = CRRAutilityPP 

55utilityPPP = CRRAutilityPPP 

56utilityPPPP = CRRAutilityPPPP 

57utilityP_inv = CRRAutilityP_inv 

58utility_invP = CRRAutility_invP 

59utility_inv = CRRAutility_inv 

60 

61 

62class TractableConsumerSolution(MetricObject): 

63 """ 

64 A class representing the solution to a tractable buffer saving problem. 

65 Attributes include a list of money points mNrm_list, a list of consumption points 

66 cNrm_list, a list of MPCs MPC_list, a perfect foresight consumption function 

67 while employed, and a perfect foresight consumption function while unemployed. 

68 The solution includes a consumption function constructed from the lists. 

69 

70 Parameters 

71 ---------- 

72 mNrm_list : [float] 

73 List of normalized market resources points on the stable arm. 

74 cNrm_list : [float] 

75 List of normalized consumption points on the stable arm. 

76 MPC_list : [float] 

77 List of marginal propensities to consume on the stable arm, corres- 

78 ponding to the (mNrm,cNrm) points. 

79 cFunc_U : function 

80 The (linear) consumption function when permanently unemployed. 

81 cFunc : function 

82 The consumption function when employed. 

83 """ 

84 

85 def __init__( 

86 self, 

87 mNrm_list=None, 

88 cNrm_list=None, 

89 MPC_list=None, 

90 cFunc_U=NullFunc, 

91 cFunc=NullFunc, 

92 ): 

93 self.mNrm_list = mNrm_list if mNrm_list is not None else list() 

94 self.cNrm_list = cNrm_list if cNrm_list is not None else list() 

95 self.MPC_list = MPC_list if MPC_list is not None else list() 

96 self.cFunc_U = cFunc_U 

97 self.cFunc = cFunc 

98 self.distance_criteria = ["PointCount"] 

99 # The distance between two solutions is the difference in the number of 

100 # stable arm points in each. This is a very crude measure of distance 

101 # that captures the notion that the process is over when no points are added. 

102 

103 

104def find_next_point( 

105 DiscFac, 

106 Rfree, 

107 CRRA, 

108 PermGroFacCmp, 

109 UnempPrb, 

110 Rnrm, 

111 Beth, 

112 cNext, 

113 mNext, 

114 MPCnext, 

115 PFMPC, 

116): 

117 """ 

118 Calculates what consumption, market resources, and the marginal propensity 

119 to consume must have been in the previous period given model parameters and 

120 values of market resources, consumption, and MPC today. 

121 

122 Parameters 

123 ---------- 

124 DiscFac : float 

125 Intertemporal discount factor on future utility. 

126 Rfree : float 

127 Risk free interest factor on end-of-period assets. 

128 PermGroFacCmp : float 

129 Permanent income growth factor, compensated for the possibility of 

130 permanent unemployment. 

131 UnempPrb : float 

132 Probability of becoming permanently unemployed. 

133 Rnrm : float 

134 Interest factor normalized by compensated permanent income growth factor. 

135 Beth : float 

136 Composite effective discount factor for reverse shooting solution; defined 

137 in appendix "Numerical Solution/The Consumption Function" in TBS 

138 lecture notes 

139 cNext : float 

140 Normalized consumption in the succeeding period. 

141 mNext : float 

142 Normalized market resources in the succeeding period. 

143 MPCnext : float 

144 The marginal propensity to consume in the succeeding period. 

145 PFMPC : float 

146 The perfect foresight MPC; also the MPC when permanently unemployed. 

147 

148 Returns 

149 ------- 

150 mNow : float 

151 Normalized market resources this period. 

152 cNow : float 

153 Normalized consumption this period. 

154 MPCnow : float 

155 Marginal propensity to consume this period. 

156 """ 

157 

158 def uPP(x): 

159 return utilityPP(x, rho=CRRA) 

160 

161 cNow = ( 

162 PermGroFacCmp 

163 * (DiscFac * Rfree) ** (-1.0 / CRRA) 

164 * cNext 

165 * (1 + UnempPrb * ((cNext / (PFMPC * (mNext - 1.0))) ** CRRA - 1.0)) 

166 ** (-1.0 / CRRA) 

167 ) 

168 mNow = (PermGroFacCmp / Rfree) * (mNext - 1.0) + cNow 

169 cUNext = PFMPC * (mNow - cNow) * Rnrm 

170 # See TBS Appendix "E.1 The Consumption Function" 

171 natural = ( 

172 Beth 

173 * Rnrm 

174 * (1.0 / uPP(cNow)) 

175 * ((1.0 - UnempPrb) * uPP(cNext) * MPCnext + UnempPrb * uPP(cUNext) * PFMPC) 

176 ) # Convenience variable 

177 MPCnow = natural / (natural + 1) 

178 return mNow, cNow, MPCnow 

179 

180 

181def add_to_stable_arm_points( 

182 solution_next, 

183 DiscFac, 

184 Rfree, 

185 CRRA, 

186 PermGroFacCmp, 

187 UnempPrb, 

188 PFMPC, 

189 Rnrm, 

190 Beth, 

191 mLowerBnd, 

192 mUpperBnd, 

193): 

194 """ 

195 Adds a one point to the bottom and top of the list of stable arm points if 

196 the bounding levels of mLowerBnd (lower) and mUpperBnd (upper) have not yet 

197 been met by a stable arm point in mNrm_list. This acts as the "one period 

198 solver" / solve_one_period in the tractable buffer stock model. 

199 

200 Parameters 

201 ---------- 

202 solution_next : TractableConsumerSolution 

203 The solution object from the previous iteration of the backshooting 

204 procedure. Not the "next period" solution per se. 

205 DiscFac : float 

206 Intertemporal discount factor on future utility. 

207 Rfree : float 

208 Risk free interest factor on end-of-period assets. 

209 CRRA : float 

210 Coefficient of relative risk aversion. 

211 PermGroFacCmp : float 

212 Permanent income growth factor, compensated for the possibility of 

213 permanent unemployment. 

214 UnempPrb : float 

215 Probability of becoming permanently unemployed. 

216 PFMPC : float 

217 The perfect foresight MPC; also the MPC when permanently unemployed. 

218 Rnrm : float 

219 Interest factor normalized by compensated permanent income growth factor. 

220 Beth : float 

221 Damned if I know. 

222 mLowerBnd : float 

223 Lower bound on market resources for the backshooting process. If 

224 min(solution_next.mNrm_list) < mLowerBnd, no new bottom point is found. 

225 mUpperBnd : float 

226 Upper bound on market resources for the backshooting process. If 

227 max(solution_next.mNrm_list) > mUpperBnd, no new top point is found. 

228 

229 Returns: 

230 --------- 

231 solution_now : TractableConsumerSolution 

232 A new solution object with new points added to the top and bottom. If 

233 no new points were added, then the backshooting process is about to end. 

234 """ 

235 # Unpack the lists of Euler points 

236 mNrm_list = copy(solution_next.mNrm_list) 

237 cNrm_list = copy(solution_next.cNrm_list) 

238 MPC_list = copy(solution_next.MPC_list) 

239 

240 # Check whether to add a stable arm point to the top 

241 mNext = mNrm_list[-1] 

242 if mNext < mUpperBnd: 

243 # Get the rest of the data for the previous top point 

244 cNext = solution_next.cNrm_list[-1] 

245 MPCNext = solution_next.MPC_list[-1] 

246 

247 # Calculate employed levels of c, m, and MPC from next period's values 

248 mNow, cNow, MPCnow = find_next_point( 

249 DiscFac, 

250 Rfree, 

251 CRRA, 

252 PermGroFacCmp, 

253 UnempPrb, 

254 Rnrm, 

255 Beth, 

256 cNext, 

257 mNext, 

258 MPCNext, 

259 PFMPC, 

260 ) 

261 

262 # Add this point to the top of the stable arm list 

263 mNrm_list.append(mNow) 

264 cNrm_list.append(cNow) 

265 MPC_list.append(MPCnow) 

266 

267 # Check whether to add a stable arm point to the bottom 

268 mNext = mNrm_list[0] 

269 if mNext > mLowerBnd: 

270 # Get the rest of the data for the previous bottom point 

271 cNext = solution_next.cNrm_list[0] 

272 MPCNext = solution_next.MPC_list[0] 

273 

274 # Calculate employed levels of c, m, and MPC from next period's values 

275 mNow, cNow, MPCnow = find_next_point( 

276 DiscFac, 

277 Rfree, 

278 CRRA, 

279 PermGroFacCmp, 

280 UnempPrb, 

281 Rnrm, 

282 Beth, 

283 cNext, 

284 mNext, 

285 MPCNext, 

286 PFMPC, 

287 ) 

288 

289 # Add this point to the top of the stable arm list 

290 mNrm_list.insert(0, mNow) 

291 cNrm_list.insert(0, cNow) 

292 MPC_list.insert(0, MPCnow) 

293 

294 # Construct and return this period's solution 

295 solution_now = TractableConsumerSolution( 

296 mNrm_list=mNrm_list, cNrm_list=cNrm_list, MPC_list=MPC_list 

297 ) 

298 solution_now.PointCount = len(mNrm_list) 

299 return solution_now 

300 

301 

302############################################################################### 

303 

304# Define a dictionary for the tractable buffer stock model 

305init_tractable = { 

306 "cycles": 0, # infinite horizon 

307 "UnempPrb": 0.00625, # Probability of becoming permanently unemployed 

308 "DiscFac": 0.975, # Intertemporal discount factor 

309 "Rfree": 1.01, # Risk-free interest factor on assets 

310 "PermGroFac": 1.0025, # Permanent income growth factor (uncompensated) 

311 "CRRA": 1.0, # Coefficient of relative risk aversion 

312} 

313 

314 

315class TractableConsumerType(AgentType): 

316 """ 

317 Parameters 

318 ---------- 

319 Same as AgentType 

320 """ 

321 

322 time_inv_ = [ 

323 "DiscFac", 

324 "Rfree", 

325 "CRRA", 

326 "PermGroFacCmp", 

327 "UnempPrb", 

328 "PFMPC", 

329 "Rnrm", 

330 "Beth", 

331 "mLowerBnd", 

332 "mUpperBnd", 

333 ] 

334 shock_vars_ = ["eStateNow"] 

335 state_vars = ["bLvl", "mLvl", "aLvl"] 

336 poststate_vars = ["aLvl", "eStateNow"] # For simulation 

337 default_ = {"params": init_tractable, "solver": add_to_stable_arm_points} 

338 

339 def pre_solve(self): 

340 """ 

341 Calculates all of the solution objects that can be obtained before con- 

342 ducting the backshooting routine, including the target levels, the per- 

343 fect foresight solution, (marginal) consumption at m=0, and the small 

344 perturbations around the steady state. 

345 

346 TODO: This should probably all be moved to a constructor function. 

347 

348 Parameters 

349 ---------- 

350 none 

351 

352 Returns 

353 ------- 

354 none 

355 """ 

356 

357 # Define utility functions 

358 def uPP(x): 

359 return utilityPP(x, rho=self.CRRA) 

360 

361 def uPPP(x): 

362 return utilityPPP(x, rho=self.CRRA) 

363 

364 def uPPPP(x): 

365 return utilityPPPP(x, rho=self.CRRA) 

366 

367 # Define some useful constants from model primitives 

368 self.PermGroFacCmp = self.PermGroFac / ( 

369 1.0 - self.UnempPrb 

370 ) # "uncertainty compensated" wage growth factor 

371 self.Rnrm = ( 

372 self.Rfree / self.PermGroFacCmp 

373 ) # net interest factor (Rfree normalized by wage growth) 

374 self.PFMPC = 1.0 - (self.Rfree ** (-1.0)) * (self.Rfree * self.DiscFac) ** ( 

375 1.0 / self.CRRA 

376 ) # MPC for a perfect forsight consumer 

377 self.Beth = self.Rnrm * self.DiscFac * self.PermGroFacCmp ** (1.0 - self.CRRA) 

378 

379 # Verify that this consumer is impatient 

380 PatFacGrowth = (self.Rfree * self.DiscFac) ** ( 

381 1.0 / self.CRRA 

382 ) / self.PermGroFacCmp 

383 PatFacReturn = (self.Rfree * self.DiscFac) ** (1.0 / self.CRRA) / self.Rfree 

384 if PatFacReturn >= 1.0: 

385 raise Exception("Employed consumer not return impatient, cannot solve!") 

386 if PatFacGrowth >= 1.0: 

387 raise Exception("Employed consumer not growth impatient, cannot solve!") 

388 

389 # Find target money and consumption 

390 # See TBS Appendix "B.2 A Target Always Exists When Human Wealth Is Infinite" 

391 Pi = (1 + (PatFacGrowth ** (-self.CRRA) - 1.0) / self.UnempPrb) ** ( 

392 1 / self.CRRA 

393 ) 

394 self.h = 1.0 / (1.0 - self.PermGroFac / self.Rfree) 

395 zeta = ( 

396 self.Rnrm * self.PFMPC * Pi 

397 ) # See TBS Appendix "C The Exact Formula for target m" 

398 self.mTarg = 1.0 + ( 

399 self.Rfree / (self.PermGroFacCmp + zeta * self.PermGroFacCmp - self.Rfree) 

400 ) 

401 self.cTarg = (1.0 - self.Rnrm ** (-1.0)) * self.mTarg + self.Rnrm ** (-1.0) 

402 mTargU = (self.mTarg - self.cTarg) * self.Rnrm 

403 cTargU = mTargU * self.PFMPC 

404 self.SSperturbance = self.mTarg * 0.1 

405 

406 # Find the MPC, MMPC, and MMMPC at the target 

407 def mpcTargFixedPointFunc(k): 

408 return k * uPP(self.cTarg) - self.Beth * ( 

409 (1.0 - self.UnempPrb) * (1.0 - k) * k * self.Rnrm * uPP(self.cTarg) 

410 + self.PFMPC * self.UnempPrb * (1.0 - k) * self.Rnrm * uPP(cTargU) 

411 ) 

412 

413 self.MPCtarg = newton(mpcTargFixedPointFunc, 0) 

414 

415 def mmpcTargFixedPointFunc(kk): 

416 return ( 

417 kk * uPP(self.cTarg) 

418 + self.MPCtarg**2.0 * uPPP(self.cTarg) 

419 - self.Beth 

420 * ( 

421 -(1.0 - self.UnempPrb) 

422 * self.MPCtarg 

423 * kk 

424 * self.Rnrm 

425 * uPP(self.cTarg) 

426 + (1.0 - self.UnempPrb) 

427 * (1.0 - self.MPCtarg) ** 2.0 

428 * kk 

429 * self.Rnrm**2.0 

430 * uPP(self.cTarg) 

431 - self.PFMPC * self.UnempPrb * kk * self.Rnrm * uPP(cTargU) 

432 + (1.0 - self.UnempPrb) 

433 * (1.0 - self.MPCtarg) ** 2.0 

434 * self.MPCtarg**2.0 

435 * self.Rnrm**2.0 

436 * uPPP(self.cTarg) 

437 + self.PFMPC**2.0 

438 * self.UnempPrb 

439 * (1.0 - self.MPCtarg) ** 2.0 

440 * self.Rnrm**2.0 

441 * uPPP(cTargU) 

442 ) 

443 ) 

444 

445 self.MMPCtarg = newton(mmpcTargFixedPointFunc, 0) 

446 

447 def mmmpcTargFixedPointFunc(kkk): 

448 return ( 

449 kkk * uPP(self.cTarg) 

450 + 3 * self.MPCtarg * self.MMPCtarg * uPPP(self.cTarg) 

451 + self.MPCtarg**3 * uPPPP(self.cTarg) 

452 - self.Beth 

453 * ( 

454 -(1 - self.UnempPrb) 

455 * self.MPCtarg 

456 * kkk 

457 * self.Rnrm 

458 * uPP(self.cTarg) 

459 - 3 

460 * (1 - self.UnempPrb) 

461 * (1 - self.MPCtarg) 

462 * self.MMPCtarg**2 

463 * self.Rnrm**2 

464 * uPP(self.cTarg) 

465 + (1 - self.UnempPrb) 

466 * (1 - self.MPCtarg) ** 3 

467 * kkk 

468 * self.Rnrm**3 

469 * uPP(self.cTarg) 

470 - self.PFMPC * self.UnempPrb * kkk * self.Rnrm * uPP(cTargU) 

471 - 3 

472 * (1 - self.UnempPrb) 

473 * (1 - self.MPCtarg) 

474 * self.MPCtarg**2 

475 * self.MMPCtarg 

476 * self.Rnrm**2 

477 * uPPP(self.cTarg) 

478 + 3 

479 * (1 - self.UnempPrb) 

480 * (1 - self.MPCtarg) ** 3 

481 * self.MPCtarg 

482 * self.MMPCtarg 

483 * self.Rnrm**3 

484 * uPPP(self.cTarg) 

485 - 3 

486 * self.PFMPC**2 

487 * self.UnempPrb 

488 * (1 - self.MPCtarg) 

489 * self.MMPCtarg 

490 * self.Rnrm**2 

491 * uPPP(cTargU) 

492 + (1 - self.UnempPrb) 

493 * (1 - self.MPCtarg) ** 3 

494 * self.MPCtarg**3 

495 * self.Rnrm**3 

496 * uPPPP(self.cTarg) 

497 + self.PFMPC**3 

498 * self.UnempPrb 

499 * (1 - self.MPCtarg) ** 3 

500 * self.Rnrm**3 

501 * uPPPP(cTargU) 

502 ) 

503 ) 

504 

505 self.MMMPCtarg = newton(mmmpcTargFixedPointFunc, 0) 

506 

507 # Find the MPC at m=0 

508 def f_temp(k): 

509 return ( 

510 self.Beth 

511 * self.Rnrm 

512 * self.UnempPrb 

513 * (self.PFMPC * self.Rnrm * ((1.0 - k) / k)) ** (-self.CRRA - 1.0) 

514 * self.PFMPC 

515 ) 

516 

517 def mpcAtZeroFixedPointFunc(k): 

518 return k - f_temp(k) / (1 + f_temp(k)) 

519 

520 # self.MPCmax = newton(mpcAtZeroFixedPointFunc,0.5) 

521 self.MPCmax = brentq( 

522 mpcAtZeroFixedPointFunc, self.PFMPC, 0.99, xtol=0.00000001, rtol=0.00000001 

523 ) 

524 

525 # Make the initial list of Euler points: target and perturbation to either side 

526 mNrm_list = [ 

527 self.mTarg - self.SSperturbance, 

528 self.mTarg, 

529 self.mTarg + self.SSperturbance, 

530 ] 

531 c_perturb_lo = ( 

532 self.cTarg 

533 - self.SSperturbance * self.MPCtarg 

534 + 0.5 * self.SSperturbance**2.0 * self.MMPCtarg 

535 - (1.0 / 6.0) * self.SSperturbance**3.0 * self.MMMPCtarg 

536 ) 

537 c_perturb_hi = ( 

538 self.cTarg 

539 + self.SSperturbance * self.MPCtarg 

540 + 0.5 * self.SSperturbance**2.0 * self.MMPCtarg 

541 + (1.0 / 6.0) * self.SSperturbance**3.0 * self.MMMPCtarg 

542 ) 

543 cNrm_list = [c_perturb_lo, self.cTarg, c_perturb_hi] 

544 MPC_perturb_lo = ( 

545 self.MPCtarg 

546 - self.SSperturbance * self.MMPCtarg 

547 + 0.5 * self.SSperturbance**2.0 * self.MMMPCtarg 

548 ) 

549 MPC_perturb_hi = ( 

550 self.MPCtarg 

551 + self.SSperturbance * self.MMPCtarg 

552 + 0.5 * self.SSperturbance**2.0 * self.MMMPCtarg 

553 ) 

554 MPC_list = [MPC_perturb_lo, self.MPCtarg, MPC_perturb_hi] 

555 

556 # Set bounds for money (stable arm construction stops when these are exceeded) 

557 self.mLowerBnd = 1.0 

558 self.mUpperBnd = 2.0 * self.mTarg 

559 

560 # Make the terminal period solution 

561 solution_terminal = TractableConsumerSolution( 

562 mNrm_list=mNrm_list, cNrm_list=cNrm_list, MPC_list=MPC_list 

563 ) 

564 self.solution_terminal = solution_terminal 

565 

566 # Make two linear steady state functions 

567 self.cSSfunc = lambda m: m * ( 

568 (self.Rnrm * self.PFMPC * Pi) / (1.0 + self.Rnrm * self.PFMPC * Pi) 

569 ) 

570 self.mSSfunc = ( 

571 lambda m: (self.PermGroFacCmp / self.Rfree) 

572 + (1.0 - self.PermGroFacCmp / self.Rfree) * m 

573 ) 

574 

575 def post_solve(self): 

576 """ 

577 This method adds consumption at m=0 to the list of stable arm points, 

578 then constructs the consumption function as a cubic interpolation over 

579 those points. Should be run after the backshooting routine is complete. 

580 

581 Parameters 

582 ---------- 

583 none 

584 

585 Returns 

586 ------- 

587 none 

588 """ 

589 # Add bottom point to the stable arm points 

590 self.solution[0].mNrm_list.insert(0, 0.0) 

591 self.solution[0].cNrm_list.insert(0, 0.0) 

592 self.solution[0].MPC_list.insert(0, self.MPCmax) 

593 

594 # Construct an interpolation of the consumption function from the stable arm points 

595 self.solution[0].cFunc = CubicInterp( 

596 self.solution[0].mNrm_list, 

597 self.solution[0].cNrm_list, 

598 self.solution[0].MPC_list, 

599 self.PFMPC * (self.h - 1.0), 

600 self.PFMPC, 

601 ) 

602 self.solution[0].cFunc_U = LinearInterp([0.0, 1.0], [0.0, self.PFMPC]) 

603 

604 def sim_birth(self, which_agents): 

605 """ 

606 Makes new consumers for the given indices. Initialized variables include aNrm, as 

607 well as time variables t_age and t_cycle. Normalized assets are drawn from a lognormal 

608 distributions given by aLvlInitMean and aLvlInitStd. 

609 

610 Parameters 

611 ---------- 

612 which_agents : np.array(Bool) 

613 Boolean array of size self.AgentCount indicating which agents should be "born". 

614 

615 Returns 

616 ------- 

617 None 

618 """ 

619 # Get and store states for newly born agents 

620 N = np.sum(which_agents) # Number of new consumers to make 

621 self.state_now["aLvl"][which_agents] = Lognormal( 

622 self.aLvlInitMean, 

623 sigma=self.aLvlInitStd, 

624 seed=self.RNG.integers(0, 2**31 - 1), 

625 ).draw(N) 

626 self.shocks["eStateNow"] = np.zeros(self.AgentCount) # Initialize shock array 

627 # Agents are born employed 

628 self.shocks["eStateNow"][which_agents] = 1.0 

629 # How many periods since each agent was born 

630 self.t_age[which_agents] = 0 

631 self.t_cycle[which_agents] = ( 

632 0 # Which period of the cycle each agent is currently in 

633 ) 

634 return None 

635 

636 def sim_death(self): 

637 """ 

638 Trivial function that returns boolean array of all False, as there is no death. 

639 

640 Parameters 

641 ---------- 

642 None 

643 

644 Returns 

645 ------- 

646 which_agents : np.array(bool) 

647 Boolean array of size AgentCount indicating which agents die. 

648 """ 

649 # Nobody dies in this model 

650 which_agents = np.zeros(self.AgentCount, dtype=bool) 

651 return which_agents 

652 

653 def get_shocks(self): 

654 """ 

655 Determine which agents switch from employment to unemployment. All unemployed agents remain 

656 unemployed until death. 

657 

658 Parameters 

659 ---------- 

660 None 

661 

662 Returns 

663 ------- 

664 None 

665 """ 

666 employed = self.shocks["eStateNow"] == 1.0 

667 N = int(np.sum(employed)) 

668 newly_unemployed = Bernoulli( 

669 self.UnempPrb, seed=self.RNG.integers(0, 2**31 - 1) 

670 ).draw(N) 

671 self.shocks["eStateNow"][employed] = 1.0 - newly_unemployed 

672 

673 def transition(self): 

674 """ 

675 Calculate market resources for all agents this period. 

676 

677 Parameters 

678 ---------- 

679 None 

680 

681 Returns 

682 ------- 

683 None 

684 """ 

685 bLvlNow = self.Rfree * self.state_prev["aLvl"] 

686 mLvlNow = bLvlNow + self.shocks["eStateNow"] 

687 

688 return bLvlNow, mLvlNow 

689 

690 def get_controls(self): 

691 """ 

692 Calculate consumption for each agent this period. 

693 

694 Parameters 

695 ---------- 

696 None 

697 

698 Returns 

699 ------- 

700 None 

701 """ 

702 employed = self.shocks["eStateNow"] == 1.0 

703 unemployed = np.logical_not(employed) 

704 cLvlNow = np.zeros(self.AgentCount) 

705 cLvlNow[employed] = self.solution[0].cFunc(self.state_now["mLvl"][employed]) 

706 cLvlNow[unemployed] = self.solution[0].cFunc_U( 

707 self.state_now["mLvl"][unemployed] 

708 ) 

709 self.controls["cLvlNow"] = cLvlNow 

710 

711 def get_poststates(self): 

712 """ 

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

714 

715 Parameters 

716 ---------- 

717 None 

718 

719 Returns 

720 ------- 

721 None 

722 """ 

723 self.state_now["aLvl"] = self.state_now["mLvl"] - self.controls["cLvlNow"] 

724 return None