Coverage for HARK / ConsumptionSaving / ConsIndShockModel.py: 99%

892 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-08 05:31 +0000

1""" 

2Classes to solve canonical consumption-saving models with idiosyncratic shocks 

3to income. All models here assume CRRA utility with geometric discounting, no 

4bequest motive, and income shocks that are fully transitory or fully permanent. 

5 

6It currently solves three types of models: 

7 1) A very basic "perfect foresight" consumption-savings model with no uncertainty. 

8 2) A consumption-savings model with risk over transitory and permanent income shocks. 

9 3) The model described in (2), with an interest rate for debt that differs 

10 from the interest rate for savings. 

11 

12See NARK https://github.com/econ-ark/HARK/blob/master/docs/NARK/NARK.pdf for information on variable naming conventions. 

13See HARK documentation for mathematical descriptions of the models being solved. 

14""" 

15 

16from copy import copy 

17 

18import numpy as np 

19from HARK.Calibration.Income.IncomeTools import ( 

20 Cagetti_income, 

21 parse_income_spec, 

22 parse_time_params, 

23) 

24from HARK.Calibration.Income.IncomeProcesses import ( 

25 construct_lognormal_income_process_unemployment, 

26 get_PermShkDstn_from_IncShkDstn, 

27 get_TranShkDstn_from_IncShkDstn, 

28) 

29from HARK.Calibration.life_tables.us_ssa.SSATools import parse_ssa_life_table 

30from HARK.Calibration.SCF.WealthIncomeDist.SCFDistTools import ( 

31 income_wealth_dists_from_scf, 

32) 

33from HARK.distributions import ( 

34 Lognormal, 

35 MeanOneLogNormal, 

36 Uniform, 

37 add_discrete_outcome_constant_mean, 

38 combine_indep_dstns, 

39 expected, 

40) 

41from HARK.interpolation import ( 

42 LinearInterp, 

43 LowerEnvelope, 

44 MargMargValueFuncCRRA, 

45 MargValueFuncCRRA, 

46 ValueFuncCRRA, 

47) 

48from HARK.interpolation import CubicHermiteInterp as CubicInterp 

49from HARK.metric import MetricObject 

50from HARK.rewards import ( 

51 CRRAutility, 

52 CRRAutility_inv, 

53 CRRAutility_invP, 

54 CRRAutilityP, 

55 CRRAutilityP_inv, 

56 CRRAutilityP_invP, 

57 CRRAutilityPP, 

58 UtilityFuncCRRA, 

59) 

60from HARK.utilities import make_assets_grid 

61from scipy.optimize import newton 

62 

63from HARK import ( 

64 AgentType, 

65 NullFunc, 

66 _log, 

67 set_verbosity_level, 

68) 

69 

70__all__ = [ 

71 "ConsumerSolution", 

72 "PerfForesightConsumerType", 

73 "IndShockConsumerType", 

74 "KinkedRconsumerType", 

75 "init_perfect_foresight", 

76 "init_idiosyncratic_shocks", 

77 "init_kinked_R", 

78 "init_lifecycle", 

79 "init_cyclical", 

80 "buffer_stock_lifecycle_unskilled", 

81 "buffer_stock_lifecycle_operative", 

82 "buffer_stock_lifecycle_manager", 

83] 

84 

85utility = CRRAutility 

86utilityP = CRRAutilityP 

87utilityPP = CRRAutilityPP 

88utilityP_inv = CRRAutilityP_inv 

89utility_invP = CRRAutility_invP 

90utility_inv = CRRAutility_inv 

91utilityP_invP = CRRAutilityP_invP 

92 

93 

94# ===================================================================== 

95# === Classes that help solve consumption-saving models === 

96# ===================================================================== 

97 

98 

99class ConsumerSolution(MetricObject): 

100 r""" 

101 A class representing the solution of a single period of a consumption-saving 

102 problem. The solution must include a consumption function and marginal 

103 value function. 

104 

105 Here and elsewhere in the code, Nrm indicates that variables are normalized 

106 by permanent income. 

107 

108 Parameters 

109 ---------- 

110 cFunc : function 

111 The consumption function for this period, defined over normalized market 

112 resources: cNrm = cFunc(mNrm). 

113 vFunc : function 

114 The beginning-of-period value function for this period, defined over 

115 normalized market resources: vNrm = vFunc(mNrm). 

116 vPfunc : function 

117 The beginning-of-period marginal value function for this period, 

118 defined over normalized market resources: vNrmP = vPfunc(mNrm). 

119 vPPfunc : function 

120 The beginning-of-period marginal marginal value function for this 

121 period, defined over normalized market resources: vNrmPP = vPPfunc(mNrm). 

122 mNrmMin : float 

123 The minimum allowable normalized market resources for this period; the consump- 

124 tion function (etc) are undefined for m < mNrmMin. 

125 hNrm : float 

126 Normalized human wealth after receiving income this period: PDV of all future 

127 income, ignoring mortality. 

128 MPCmin : float 

129 Infimum of the marginal propensity to consume this period. 

130 MPC --> MPCmin as m --> infinity. 

131 MPCmax : float 

132 Supremum of the marginal propensity to consume this period. 

133 MPC --> MPCmax as m --> mNrmMin. 

134 

135 """ 

136 

137 distance_criteria = ["vPfunc"] 

138 

139 def __init__( 

140 self, 

141 cFunc=None, 

142 vFunc=None, 

143 vPfunc=None, 

144 vPPfunc=None, 

145 mNrmMin=None, 

146 hNrm=None, 

147 MPCmin=None, 

148 MPCmax=None, 

149 ): 

150 # Change any missing function inputs to NullFunc 

151 self.cFunc = cFunc if cFunc is not None else NullFunc() 

152 self.vFunc = vFunc if vFunc is not None else NullFunc() 

153 self.vPfunc = vPfunc if vPfunc is not None else NullFunc() 

154 # vPFunc = NullFunc() if vPfunc is None else vPfunc 

155 self.vPPfunc = vPPfunc if vPPfunc is not None else NullFunc() 

156 self.mNrmMin = mNrmMin 

157 self.hNrm = hNrm 

158 self.MPCmin = MPCmin 

159 self.MPCmax = MPCmax 

160 

161 def append_solution(self, new_solution): 

162 """ 

163 Appends one solution to another to create a ConsumerSolution whose 

164 attributes are lists. Used in ConsMarkovModel, where we append solutions 

165 *conditional* on a particular value of a Markov state to each other in 

166 order to get the entire solution. 

167 

168 Parameters 

169 ---------- 

170 new_solution : ConsumerSolution 

171 The solution to a consumption-saving problem; each attribute is a 

172 list representing state-conditional values or functions. 

173 

174 Returns 

175 ------- 

176 None 

177 """ 

178 if type(self.cFunc) != list: 

179 # Then we assume that self is an empty initialized solution instance. 

180 # Begin by checking this is so. 

181 assert NullFunc().distance(self.cFunc) == 0, ( 

182 "append_solution called incorrectly!" 

183 ) 

184 

185 # We will need the attributes of the solution instance to be lists. Do that here. 

186 self.cFunc = [new_solution.cFunc] 

187 self.vFunc = [new_solution.vFunc] 

188 self.vPfunc = [new_solution.vPfunc] 

189 self.vPPfunc = [new_solution.vPPfunc] 

190 self.mNrmMin = [new_solution.mNrmMin] 

191 else: 

192 self.cFunc.append(new_solution.cFunc) 

193 self.vFunc.append(new_solution.vFunc) 

194 self.vPfunc.append(new_solution.vPfunc) 

195 self.vPPfunc.append(new_solution.vPPfunc) 

196 self.mNrmMin.append(new_solution.mNrmMin) 

197 

198 

199# ===================================================================== 

200# == Functions for initializing newborns in consumption-saving models = 

201# ===================================================================== 

202 

203 

204def make_lognormal_kNrm_init_dstn(kLogInitMean, kLogInitStd, kNrmInitCount, RNG): 

205 """ 

206 Construct a lognormal distribution for (normalized) initial capital holdings 

207 of newborns, kNrm. This is the default constructor for kNrmInitDstn. 

208 

209 Parameters 

210 ---------- 

211 kLogInitMean : float 

212 Mean of log capital holdings for newborns. 

213 kLogInitStd : float 

214 Stdev of log capital holdings for newborns. 

215 kNrmInitCount : int 

216 Number of points in the discretization. 

217 RNG : np.random.RandomState 

218 Agent's internal RNG. 

219 

220 Returns 

221 ------- 

222 kNrmInitDstn : DiscreteDistribution 

223 Discretized distribution of initial capital holdings for newborns. 

224 """ 

225 dstn = Lognormal( 

226 mu=kLogInitMean, 

227 sigma=kLogInitStd, 

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

229 ) 

230 kNrmInitDstn = dstn.discretize(kNrmInitCount) 

231 return kNrmInitDstn 

232 

233 

234def make_lognormal_pLvl_init_dstn(pLogInitMean, pLogInitStd, pLvlInitCount, RNG): 

235 """ 

236 Construct a lognormal distribution for initial permanent income level of 

237 newborns, pLvl. This is the default constructor for pLvlInitDstn. 

238 

239 Parameters 

240 ---------- 

241 pLogInitMean : float 

242 Mean of log permanent income for newborns. 

243 pLogInitStd : float 

244 Stdev of log capital holdings for newborns. 

245 pLvlInitCount : int 

246 Number of points in the discretization. 

247 RNG : np.random.RandomState 

248 Agent's internal RNG. 

249 

250 Returns 

251 ------- 

252 pLvlInitDstn : DiscreteDistribution 

253 Discretized distribution of initial permanent income for newborns. 

254 """ 

255 dstn = Lognormal( 

256 mu=pLogInitMean, 

257 sigma=pLogInitStd, 

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

259 ) 

260 pLvlInitDstn = dstn.discretize(pLvlInitCount) 

261 return pLvlInitDstn 

262 

263 

264# ===================================================================== 

265# === Classes and functions that solve consumption-saving models === 

266# ===================================================================== 

267 

268 

269def calc_human_wealth(h_nrm_next, perm_gro_fac, rfree, ex_inc_next): 

270 """Calculate human wealth this period given human wealth next period. 

271 

272 Args: 

273 h_nrm_next (float): Normalized human wealth next period. 

274 perm_gro_fac (float): Permanent income growth factor. 

275 rfree (float): Risk free interest factor. 

276 ex_inc_next (float): Expected income next period. 

277 """ 

278 return (perm_gro_fac / rfree) * (h_nrm_next + ex_inc_next) 

279 

280 

281def calc_patience_factor(rfree, disc_fac_eff, crra): 

282 """Calculate the patience factor for the agent. 

283 

284 Args: 

285 rfree (float): Risk free interest factor. 

286 disc_fac_eff (float): Effective discount factor. 

287 crra (float): Coefficient of relative risk aversion. 

288 

289 """ 

290 return ((rfree * disc_fac_eff) ** (1.0 / crra)) / rfree 

291 

292 

293def calc_mpc_min(mpc_min_next, pat_fac): 

294 """Calculate the lower bound of the marginal propensity to consume. 

295 

296 Args: 

297 mpc_min_next (float): Lower bound of the marginal propensity to 

298 consume next period. 

299 pat_fac (float): Patience factor. 

300 """ 

301 return 1.0 / (1.0 + pat_fac / mpc_min_next) 

302 

303 

304def solve_one_period_ConsPF( 

305 solution_next, 

306 DiscFac, 

307 LivPrb, 

308 CRRA, 

309 Rfree, 

310 PermGroFac, 

311 BoroCnstArt, 

312 MaxKinks, 

313): 

314 """Solves one period of a basic perfect foresight consumption-saving model with 

315 a single risk free asset and permanent income growth. 

316 

317 Parameters 

318 ---------- 

319 solution_next : ConsumerSolution 

320 The solution to next period's one-period problem. 

321 DiscFac : float 

322 Intertemporal discount factor for future utility. 

323 LivPrb : float 

324 Survival probability; likelihood of being alive at the beginning of 

325 the next period. 

326 CRRA : float 

327 Coefficient of relative risk aversion. 

328 Rfree : float 

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

330 PermGroFac : float 

331 Expected permanent income growth factor at the end of this period. 

332 BoroCnstArt : float or None 

333 Artificial borrowing constraint, as a multiple of permanent income. 

334 Can be None, indicating no artificial constraint. 

335 MaxKinks : int 

336 Maximum number of kink points to allow in the consumption function; 

337 additional points will be thrown out. Only relevant in infinite 

338 horizon model with artificial borrowing constraint. 

339 

340 Returns 

341 ------- 

342 solution_now : ConsumerSolution 

343 Solution to the current period of a perfect foresight consumption-saving 

344 problem. 

345 

346 """ 

347 # Define the utility function and effective discount factor 

348 uFunc = UtilityFuncCRRA(CRRA) 

349 DiscFacEff = DiscFac * LivPrb # Effective = pure x LivPrb 

350 

351 # Prevent comparing None and float if there is no borrowing constraint 

352 # Can borrow as much as we want 

353 BoroCnstArt = -np.inf if BoroCnstArt is None else BoroCnstArt 

354 

355 # Calculate human wealth this period 

356 hNrmNow = calc_human_wealth(solution_next.hNrm, PermGroFac, Rfree, 1.0) 

357 

358 # Calculate the lower bound of the marginal propensity to consume 

359 PatFac = calc_patience_factor(Rfree, DiscFacEff, CRRA) 

360 MPCminNow = calc_mpc_min(solution_next.MPCmin, PatFac) 

361 

362 # Extract the discrete kink points in next period's consumption function; 

363 # don't take the last one, as it only defines the extrapolation and is not a kink. 

364 mNrmNext = solution_next.cFunc.x_list[:-1] 

365 cNrmNext = solution_next.cFunc.y_list[:-1] 

366 vFuncNvrsNext = solution_next.vFunc.vFuncNvrs.y_list[:-1] 

367 EndOfPrdv = DiscFacEff * PermGroFac ** (1.0 - CRRA) * uFunc(vFuncNvrsNext) 

368 

369 # Calculate the end-of-period asset values that would reach those kink points 

370 # next period, then invert the first order condition to get consumption. Then 

371 # find the endogenous gridpoint (kink point) today that corresponds to each kink 

372 aNrmNow = (PermGroFac / Rfree) * (mNrmNext - 1.0) 

373 cNrmNow = (DiscFacEff * Rfree) ** (-1.0 / CRRA) * (PermGroFac * cNrmNext) 

374 mNrmNow = aNrmNow + cNrmNow 

375 

376 # Calculate (pseudo-inverse) value at each consumption kink point 

377 vNow = uFunc(cNrmNow) + EndOfPrdv 

378 vNvrsNow = uFunc.inverse(vNow) 

379 vNvrsSlopeMin = MPCminNow ** (-CRRA / (1.0 - CRRA)) 

380 

381 # Add an additional point to the list of gridpoints for the extrapolation, 

382 # using the new value of the lower bound of the MPC. 

383 mNrmNow = np.append(mNrmNow, mNrmNow[-1] + 1.0) 

384 cNrmNow = np.append(cNrmNow, cNrmNow[-1] + MPCminNow) 

385 vNvrsNow = np.append(vNvrsNow, vNvrsNow[-1] + vNvrsSlopeMin) 

386 

387 # If the artificial borrowing constraint binds, combine the constrained and 

388 # unconstrained consumption functions. 

389 if BoroCnstArt > mNrmNow[0]: 

390 # Find the highest index where constraint binds 

391 cNrmCnst = mNrmNow - BoroCnstArt 

392 CnstBinds = cNrmCnst < cNrmNow 

393 idx = np.where(CnstBinds)[0][-1] 

394 

395 if idx < (mNrmNow.size - 1): 

396 # If it is not the *very last* index, find the the critical level 

397 # of mNrm where the artificial borrowing contraint begins to bind. 

398 d0 = cNrmNow[idx] - cNrmCnst[idx] 

399 d1 = cNrmCnst[idx + 1] - cNrmNow[idx + 1] 

400 m0 = mNrmNow[idx] 

401 m1 = mNrmNow[idx + 1] 

402 alpha = d0 / (d0 + d1) 

403 mCrit = m0 + alpha * (m1 - m0) 

404 

405 # Adjust the grids of mNrm and cNrm to account for the borrowing constraint. 

406 cCrit = mCrit - BoroCnstArt 

407 mNrmNow = np.concatenate(([BoroCnstArt, mCrit], mNrmNow[(idx + 1) :])) 

408 cNrmNow = np.concatenate(([0.0, cCrit], cNrmNow[(idx + 1) :])) 

409 

410 # Adjust the vNvrs grid to account for the borrowing constraint 

411 v0 = vNvrsNow[idx] 

412 v1 = vNvrsNow[idx + 1] 

413 vNvrsCrit = v0 + alpha * (v1 - v0) 

414 vNvrsNow = np.concatenate(([0.0, vNvrsCrit], vNvrsNow[(idx + 1) :])) 

415 

416 else: 

417 # If it *is* the very last index, then there are only three points 

418 # that characterize the consumption function: the artificial borrowing 

419 # constraint, the constraint kink, and the extrapolation point. 

420 mXtra = (cNrmNow[-1] - cNrmCnst[-1]) / (1.0 - MPCminNow) 

421 mCrit = mNrmNow[-1] + mXtra 

422 cCrit = mCrit - BoroCnstArt 

423 mNrmNow = np.array([BoroCnstArt, mCrit, mCrit + 1.0]) 

424 cNrmNow = np.array([0.0, cCrit, cCrit + MPCminNow]) 

425 

426 # Adjust vNvrs grid for this three node structure 

427 mNextCrit = BoroCnstArt * Rfree + 1.0 

428 vNextCrit = PermGroFac ** (1.0 - CRRA) * solution_next.vFunc(mNextCrit) 

429 vCrit = uFunc(cCrit) + DiscFacEff * vNextCrit 

430 vNvrsCrit = uFunc.inverse(vCrit) 

431 vNvrsNow = np.array([0.0, vNvrsCrit, vNvrsCrit + vNvrsSlopeMin]) 

432 

433 # If the mNrm and cNrm grids have become too large, throw out the last 

434 # kink point, being sure to adjust the extrapolation. 

435 if mNrmNow.size > MaxKinks: 

436 mNrmNow = np.concatenate((mNrmNow[:-2], [mNrmNow[-3] + 1.0])) 

437 cNrmNow = np.concatenate((cNrmNow[:-2], [cNrmNow[-3] + MPCminNow])) 

438 vNvrsNow = np.concatenate((vNvrsNow[:-2], [vNvrsNow[-3] + vNvrsSlopeMin])) 

439 

440 # Construct the consumption function as a linear interpolation. 

441 cFuncNow = LinearInterp(mNrmNow, cNrmNow) 

442 

443 # Calculate the upper bound of the MPC as the slope of the bottom segment. 

444 MPCmaxNow = (cNrmNow[1] - cNrmNow[0]) / (mNrmNow[1] - mNrmNow[0]) 

445 mNrmMinNow = mNrmNow[0] 

446 

447 # Construct the (marginal) value function for this period 

448 # See the PerfForesightConsumerType.ipynb documentation notebook for the derivations 

449 vFuncNvrs = LinearInterp(mNrmNow, vNvrsNow) 

450 vFuncNow = ValueFuncCRRA(vFuncNvrs, CRRA) 

451 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

452 

453 # Construct and return the solution 

454 solution_now = ConsumerSolution( 

455 cFunc=cFuncNow, 

456 vFunc=vFuncNow, 

457 vPfunc=vPfuncNow, 

458 mNrmMin=mNrmMinNow, 

459 hNrm=hNrmNow, 

460 MPCmin=MPCminNow, 

461 MPCmax=MPCmaxNow, 

462 ) 

463 return solution_now 

464 

465 

466def calc_worst_inc_prob(inc_shk_dstn, use_infimum=False): 

467 """Calculate the probability of the worst income shock. 

468 

469 Args: 

470 inc_shk_dstn (DiscreteDistribution): Distribution of shocks to income. 

471 use_infimum (bool): Indicator for whether to try to use the infimum of the limiting (true) income distribution. 

472 """ 

473 probs = inc_shk_dstn.pmv 

474 perm, tran = inc_shk_dstn.atoms 

475 income = perm * tran 

476 if use_infimum: 

477 worst_inc = np.prod(inc_shk_dstn.limit["infimum"]) 

478 else: 

479 worst_inc = np.min(income) 

480 return np.sum(probs[income == worst_inc]) 

481 

482 

483def calc_boro_const_nat( 

484 m_nrm_min_next, inc_shk_dstn, rfree, perm_gro_fac, use_infimum=False 

485): 

486 """Calculate the natural borrowing constraint. 

487 

488 Args: 

489 m_nrm_min_next (float): Minimum normalized market resources next period. 

490 inc_shk_dstn (DiscreteDstn): Distribution of shocks to income. 

491 rfree (float): Risk free interest factor. 

492 perm_gro_fac (float): Permanent income growth factor. 

493 use_infimum (bool): Indicator for whether to use the infimum of the limiting (true) income distribution 

494 """ 

495 if use_infimum: 

496 perm_min, tran_min = inc_shk_dstn.limit["infimum"] 

497 else: 

498 perm, tran = inc_shk_dstn.atoms 

499 perm_min = np.min(perm) 

500 tran_min = np.min(tran) 

501 

502 temp_fac = (perm_gro_fac * perm_min) / rfree 

503 boro_cnst_nat = (m_nrm_min_next - tran_min) * temp_fac 

504 return boro_cnst_nat 

505 

506 

507def calc_m_nrm_min(boro_const_art, boro_const_nat): 

508 """Calculate the minimum normalized market resources this period. 

509 

510 Args: 

511 boro_const_art (float): Artificial borrowing constraint. 

512 boro_const_nat (float): Natural borrowing constraint. 

513 """ 

514 return ( 

515 boro_const_nat 

516 if boro_const_art is None 

517 else max(boro_const_nat, boro_const_art) 

518 ) 

519 

520 

521def calc_mpc_max( 

522 mpc_max_next, worst_inc_prob, crra, pat_fac, boro_const_nat, boro_const_art 

523): 

524 """Calculate the upper bound of the marginal propensity to consume. 

525 

526 Args: 

527 mpc_max_next (float): Upper bound of the marginal propensity to 

528 consume next period. 

529 worst_inc_prob (float): Probability of the worst income shock. 

530 crra (float): Coefficient of relative risk aversion. 

531 pat_fac (float): Patience factor. 

532 boro_const_nat (float): Natural borrowing constraint. 

533 boro_const_art (float): Artificial borrowing constraint. 

534 """ 

535 temp_fac = (worst_inc_prob ** (1.0 / crra)) * pat_fac 

536 return 1.0 / (1.0 + temp_fac / mpc_max_next) 

537 

538 

539def calc_m_nrm_next(shock, a, rfree, perm_gro_fac): 

540 """Calculate normalized market resources next period. 

541 

542 Args: 

543 shock (float): Realization of shocks to income. 

544 a (np.ndarray): Exogenous grid of end-of-period assets. 

545 rfree (float): Risk free interest factor. 

546 perm_gro_fac (float): Permanent income growth factor. 

547 """ 

548 return rfree / (perm_gro_fac * shock["PermShk"]) * a + shock["TranShk"] 

549 

550 

551def calc_v_next(shock, a, rfree, crra, perm_gro_fac, vfunc_next): 

552 """Calculate continuation value function with respect to 

553 end-of-period assets. 

554 

555 Args: 

556 shock (float): Realization of shocks to income. 

557 a (np.ndarray): Exogenous grid of end-of-period assets. 

558 rfree (float): Risk free interest factor. 

559 crra (float): Coefficient of relative risk aversion. 

560 perm_gro_fac (float): Permanent income growth factor. 

561 vfunc_next (Callable): Value function next period. 

562 """ 

563 return ( 

564 shock["PermShk"] ** (1.0 - crra) * perm_gro_fac ** (1.0 - crra) 

565 ) * vfunc_next(calc_m_nrm_next(shock, a, rfree, perm_gro_fac)) 

566 

567 

568def calc_vp_next(shock, a, rfree, crra, perm_gro_fac, vp_func_next): 

569 """Calculate the continuation marginal value function with respect to 

570 end-of-period assets. 

571 

572 Args: 

573 shock (float): Realization of shocks to income. 

574 a (np.ndarray): Exogenous grid of end-of-period assets. 

575 rfree (float): Risk free interest factor. 

576 crra (float): Coefficient of relative risk aversion. 

577 perm_gro_fac (float): Permanent income growth factor. 

578 vp_func_next (Callable): Marginal value function next period. 

579 """ 

580 return shock["PermShk"] ** (-crra) * vp_func_next( 

581 calc_m_nrm_next(shock, a, rfree, perm_gro_fac), 

582 ) 

583 

584 

585def calc_vpp_next(shock, a, rfree, crra, perm_gro_fac, vppfunc_next): 

586 """Calculate the continuation marginal marginal value function 

587 with respect to end-of-period assets. 

588 

589 Args: 

590 shock (float): Realization of shocks to income. 

591 a (np.ndarray): Exogenous grid of end-of-period assets. 

592 rfree (float): Risk free interest factor. 

593 crra (float): Coefficient of relative risk aversion. 

594 perm_gro_fac (float): Permanent income growth factor. 

595 vppfunc_next (Callable): Marginal marginal value function next period. 

596 """ 

597 return shock["PermShk"] ** (-crra - 1.0) * vppfunc_next( 

598 calc_m_nrm_next(shock, a, rfree, perm_gro_fac), 

599 ) 

600 

601 

602def solve_one_period_ConsIndShock( 

603 solution_next, 

604 IncShkDstn, 

605 LivPrb, 

606 DiscFac, 

607 CRRA, 

608 Rfree, 

609 PermGroFac, 

610 BoroCnstArt, 

611 aXtraGrid, 

612 vFuncBool, 

613 CubicBool, 

614): 

615 """Solves one period of a consumption-saving model with idiosyncratic shocks to 

616 permanent and transitory income, with one risk free asset and CRRA utility. 

617 

618 Parameters 

619 ---------- 

620 solution_next : ConsumerSolution 

621 The solution to next period's one period problem. 

622 IncShkDstn : distribution.Distribution 

623 A discrete approximation to the income process between the period being 

624 solved and the one immediately following (in solution_next). 

625 LivPrb : float 

626 Survival probability; likelihood of being alive at the beginning of 

627 the succeeding period. 

628 DiscFac : float 

629 Intertemporal discount factor for future utility. 

630 CRRA : float 

631 Coefficient of relative risk aversion. 

632 Rfree : float 

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

634 PermGroFac : float 

635 Expected permanent income growth factor at the end of this period. 

636 BoroCnstArt: float or None 

637 Borrowing constraint for the minimum allowable assets to end the 

638 period with. If it is less than the natural borrowing constraint, 

639 then it is irrelevant; BoroCnstArt=None indicates no artificial bor- 

640 rowing constraint. 

641 aXtraGrid: np.array 

642 Array of "extra" end-of-period asset values-- assets above the 

643 absolute minimum acceptable level. 

644 vFuncBool: boolean 

645 An indicator for whether the value function should be computed and 

646 included in the reported solution. 

647 CubicBool: boolean 

648 An indicator for whether the solver should use cubic or linear interpolation. 

649 

650 Returns 

651 ------- 

652 solution_now : ConsumerSolution 

653 Solution to this period's consumption-saving problem with income risk. 

654 

655 """ 

656 # Define the current period utility function and effective discount factor 

657 uFunc = UtilityFuncCRRA(CRRA) 

658 DiscFacEff = DiscFac * LivPrb # "effective" discount factor 

659 

660 # Calculate the probability that we get the worst possible income draw 

661 WorstIncPrb = calc_worst_inc_prob(IncShkDstn) 

662 Ex_IncNext = expected(lambda x: x["PermShk"] * x["TranShk"], IncShkDstn) 

663 hNrmNow = calc_human_wealth(solution_next.hNrm, PermGroFac, Rfree, Ex_IncNext) 

664 

665 # Unpack next period's (marginal) value function 

666 vFuncNext = solution_next.vFunc # This is None when vFuncBool is False 

667 vPfuncNext = solution_next.vPfunc 

668 vPPfuncNext = solution_next.vPPfunc # This is None when CubicBool is False 

669 

670 # Calculate the minimum allowable value of money resources in this period 

671 BoroCnstNat = calc_boro_const_nat( 

672 solution_next.mNrmMin, IncShkDstn, Rfree, PermGroFac 

673 ) 

674 # Set the minimum allowable (normalized) market resources based on the natural 

675 # and artificial borrowing constraints 

676 mNrmMinNow = calc_m_nrm_min(BoroCnstArt, BoroCnstNat) 

677 

678 # Update the bounding MPCs and PDV of human wealth: 

679 PatFac = calc_patience_factor(Rfree, DiscFacEff, CRRA) 

680 MPCminNow = calc_mpc_min(solution_next.MPCmin, PatFac) 

681 # Set the upper limit of the MPC (at mNrmMinNow) based on whether the natural 

682 # or artificial borrowing constraint actually binds 

683 MPCmaxUnc = calc_mpc_max( 

684 solution_next.MPCmax, WorstIncPrb, CRRA, PatFac, BoroCnstNat, BoroCnstArt 

685 ) 

686 MPCmaxNow = 1.0 if BoroCnstNat < mNrmMinNow else MPCmaxUnc 

687 

688 cFuncLimitIntercept = MPCminNow * hNrmNow 

689 cFuncLimitSlope = MPCminNow 

690 

691 # Define the borrowing-constrained consumption function 

692 cFuncNowCnst = LinearInterp( 

693 np.array([mNrmMinNow, mNrmMinNow + 1.0]), 

694 np.array([0.0, 1.0]), 

695 ) 

696 

697 # Construct the assets grid by adjusting aXtra by the natural borrowing constraint 

698 aNrmNow = np.asarray(aXtraGrid) + BoroCnstNat 

699 

700 # Calculate end-of-period marginal value of assets at each gridpoint 

701 vPfacEff = DiscFacEff * Rfree * PermGroFac ** (-CRRA) 

702 EndOfPrdvP = vPfacEff * expected( 

703 calc_vp_next, 

704 IncShkDstn, 

705 args=(aNrmNow, Rfree, CRRA, PermGroFac, vPfuncNext), 

706 ) 

707 

708 # Invert the first order condition to find optimal cNrm from each aNrm gridpoint 

709 cNrmNow = uFunc.derinv(EndOfPrdvP, order=(1, 0)) 

710 mNrmNow = cNrmNow + aNrmNow # Endogenous mNrm gridpoints 

711 

712 # Limiting consumption is zero as m approaches mNrmMin 

713 c_for_interpolation = np.insert(cNrmNow, 0, 0.0) 

714 m_for_interpolation = np.insert(mNrmNow, 0, BoroCnstNat) 

715 

716 # Construct the consumption function as a cubic or linear spline interpolation 

717 if CubicBool: 

718 # Calculate end-of-period marginal marginal value of assets at each gridpoint 

719 vPPfacEff = DiscFacEff * Rfree * Rfree * PermGroFac ** (-CRRA - 1.0) 

720 EndOfPrdvPP = vPPfacEff * expected( 

721 calc_vpp_next, 

722 IncShkDstn, 

723 args=(aNrmNow, Rfree, CRRA, PermGroFac, vPPfuncNext), 

724 ) 

725 dcda = EndOfPrdvPP / uFunc.der(np.array(cNrmNow), order=2) 

726 MPC = dcda / (dcda + 1.0) 

727 MPC_for_interpolation = np.insert(MPC, 0, MPCmaxUnc) 

728 

729 # Construct the unconstrained consumption function as a cubic interpolation 

730 cFuncNowUnc = CubicInterp( 

731 m_for_interpolation, 

732 c_for_interpolation, 

733 MPC_for_interpolation, 

734 cFuncLimitIntercept, 

735 cFuncLimitSlope, 

736 ) 

737 else: 

738 # Construct the unconstrained consumption function as a linear interpolation 

739 cFuncNowUnc = LinearInterp( 

740 m_for_interpolation, 

741 c_for_interpolation, 

742 cFuncLimitIntercept, 

743 cFuncLimitSlope, 

744 ) 

745 

746 # Combine the constrained and unconstrained functions into the true consumption function. 

747 # LowerEnvelope should only be used when BoroCnstArt is True 

748 cFuncNow = LowerEnvelope(cFuncNowUnc, cFuncNowCnst, nan_bool=False) 

749 

750 # Make the marginal value function and the marginal marginal value function 

751 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

752 

753 # Define this period's marginal marginal value function 

754 if CubicBool: 

755 vPPfuncNow = MargMargValueFuncCRRA(cFuncNow, CRRA) 

756 else: 

757 vPPfuncNow = NullFunc() # Dummy object 

758 

759 # Construct this period's value function if requested 

760 if vFuncBool: 

761 # Calculate end-of-period value, its derivative, and their pseudo-inverse 

762 EndOfPrdv = DiscFacEff * expected( 

763 calc_v_next, 

764 IncShkDstn, 

765 args=(aNrmNow, Rfree, CRRA, PermGroFac, vFuncNext), 

766 ) 

767 EndOfPrdvNvrs = uFunc.inv( 

768 EndOfPrdv, 

769 ) # value transformed through inverse utility 

770 EndOfPrdvNvrsP = EndOfPrdvP * uFunc.derinv(EndOfPrdv, order=(0, 1)) 

771 EndOfPrdvNvrs = np.insert(EndOfPrdvNvrs, 0, 0.0) 

772 EndOfPrdvNvrsP = np.insert(EndOfPrdvNvrsP, 0, EndOfPrdvNvrsP[0]) 

773 # This is a very good approximation, vNvrsPP = 0 at the asset minimum 

774 

775 # Construct the end-of-period value function 

776 aNrm_temp = np.insert(aNrmNow, 0, BoroCnstNat) 

777 EndOfPrd_vNvrsFunc = CubicInterp(aNrm_temp, EndOfPrdvNvrs, EndOfPrdvNvrsP) 

778 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

779 

780 # Compute expected value and marginal value on a grid of market resources 

781 mNrm_temp = mNrmMinNow + aXtraGrid 

782 cNrm_temp = cFuncNow(mNrm_temp) 

783 aNrm_temp = mNrm_temp - cNrm_temp 

784 v_temp = uFunc(cNrm_temp) + EndOfPrd_vFunc(aNrm_temp) 

785 vP_temp = uFunc.der(cNrm_temp) 

786 

787 # Construct the beginning-of-period value function 

788 vNvrs_temp = uFunc.inv(v_temp) # value transformed through inv utility 

789 vNvrsP_temp = vP_temp * uFunc.derinv(v_temp, order=(0, 1)) 

790 mNrm_temp = np.insert(mNrm_temp, 0, mNrmMinNow) 

791 vNvrs_temp = np.insert(vNvrs_temp, 0, 0.0) 

792 vNvrsP_temp = np.insert(vNvrsP_temp, 0, MPCmaxNow ** (-CRRA / (1.0 - CRRA))) 

793 MPCminNvrs = MPCminNow ** (-CRRA / (1.0 - CRRA)) 

794 vNvrsFuncNow = CubicInterp( 

795 mNrm_temp, 

796 vNvrs_temp, 

797 vNvrsP_temp, 

798 MPCminNvrs * hNrmNow, 

799 MPCminNvrs, 

800 ) 

801 vFuncNow = ValueFuncCRRA(vNvrsFuncNow, CRRA) 

802 else: 

803 vFuncNow = NullFunc() # Dummy object 

804 

805 # Create and return this period's solution 

806 solution_now = ConsumerSolution( 

807 cFunc=cFuncNow, 

808 vFunc=vFuncNow, 

809 vPfunc=vPfuncNow, 

810 vPPfunc=vPPfuncNow, 

811 mNrmMin=mNrmMinNow, 

812 hNrm=hNrmNow, 

813 MPCmin=MPCminNow, 

814 MPCmax=MPCmaxNow, 

815 ) 

816 return solution_now 

817 

818 

819def solve_one_period_ConsKinkedR( 

820 solution_next, 

821 IncShkDstn, 

822 LivPrb, 

823 DiscFac, 

824 CRRA, 

825 Rboro, 

826 Rsave, 

827 PermGroFac, 

828 BoroCnstArt, 

829 aXtraGrid, 

830 vFuncBool, 

831 CubicBool, 

832): 

833 """Solves one period of a consumption-saving model with idiosyncratic shocks to 

834 permanent and transitory income, with a risk free asset and CRRA utility. 

835 In this variation, the interest rate on borrowing Rboro exceeds the interest 

836 rate on saving Rsave. 

837 

838 Parameters 

839 ---------- 

840 solution_next : ConsumerSolution 

841 The solution to next period's one period problem. 

842 IncShkDstn : distribution.Distribution 

843 A discrete approximation to the income process between the period being 

844 solved and the one immediately following (in solution_next). 

845 LivPrb : float 

846 Survival probability; likelihood of being alive at the beginning of 

847 the succeeding period. 

848 DiscFac : float 

849 Intertemporal discount factor for future utility. 

850 CRRA : float 

851 Coefficient of relative risk aversion. 

852 Rboro: float 

853 Interest factor on assets between this period and the succeeding 

854 period when assets are negative. 

855 Rsave: float 

856 Interest factor on assets between this period and the succeeding 

857 period when assets are positive. 

858 PermGroFac : float 

859 Expected permanent income growth factor at the end of this period. 

860 BoroCnstArt: float or None 

861 Borrowing constraint for the minimum allowable assets to end the 

862 period with. If it is less than the natural borrowing constraint, 

863 then it is irrelevant; BoroCnstArt=None indicates no artificial bor- 

864 rowing constraint. 

865 aXtraGrid: np.array 

866 Array of "extra" end-of-period asset values-- assets above the 

867 absolute minimum acceptable level. 

868 vFuncBool: boolean 

869 An indicator for whether the value function should be computed and 

870 included in the reported solution. 

871 CubicBool: boolean 

872 An indicator for whether the solver should use cubic or linear interpolation. 

873 

874 Returns 

875 ------- 

876 solution_now : ConsumerSolution 

877 Solution to this period's consumption-saving problem with income risk. 

878 

879 """ 

880 # Verifiy that there is actually a kink in the interest factor 

881 assert Rboro >= Rsave, ( 

882 "Interest factor on debt less than interest factor on savings!" 

883 ) 

884 # If the kink is in the wrong direction, code should break here. If there's 

885 # no kink at all, then just use the ConsIndShockModel solver. 

886 if Rboro == Rsave: 

887 solution_now = solve_one_period_ConsIndShock( 

888 solution_next, 

889 IncShkDstn, 

890 LivPrb, 

891 DiscFac, 

892 CRRA, 

893 Rboro, 

894 PermGroFac, 

895 BoroCnstArt, 

896 aXtraGrid, 

897 vFuncBool, 

898 CubicBool, 

899 ) 

900 return solution_now 

901 

902 # Define the current period utility function and effective discount factor 

903 uFunc = UtilityFuncCRRA(CRRA) 

904 DiscFacEff = DiscFac * LivPrb # "effective" discount factor 

905 

906 # Calculate the probability that we get the worst possible income draw 

907 WorstIncPrb = calc_worst_inc_prob(IncShkDstn, use_infimum=False) 

908 # WorstIncPrb is the "Weierstrass p" concept: the odds we get the WORST thing 

909 Ex_IncNext = expected(lambda x: x["PermShk"] * x["TranShk"], IncShkDstn) 

910 hNrmNow = calc_human_wealth(solution_next.hNrm, PermGroFac, Rsave, Ex_IncNext) 

911 

912 # Unpack next period's (marginal) value function 

913 vFuncNext = solution_next.vFunc # This is None when vFuncBool is False 

914 vPfuncNext = solution_next.vPfunc 

915 vPPfuncNext = solution_next.vPPfunc # This is None when CubicBool is False 

916 

917 # Calculate the minimum allowable value of money resources in this period 

918 BoroCnstNat = calc_boro_const_nat( 

919 solution_next.mNrmMin, 

920 IncShkDstn, 

921 Rboro, 

922 PermGroFac, 

923 use_infimum=False, 

924 ) 

925 # Set the minimum allowable (normalized) market resources based on the natural 

926 # and artificial borrowing constraints 

927 mNrmMinNow = calc_m_nrm_min(BoroCnstArt, BoroCnstNat) 

928 

929 # Update the bounding MPCs and PDV of human wealth: 

930 PatFacSave = calc_patience_factor(Rsave, DiscFacEff, CRRA) 

931 PatFacBoro = calc_patience_factor(Rboro, DiscFacEff, CRRA) 

932 MPCminNow = calc_mpc_min(solution_next.MPCmin, PatFacSave) 

933 # Set the upper limit of the MPC (at mNrmMinNow) based on whether the natural 

934 # or artificial borrowing constraint actually binds 

935 MPCmaxUnc = calc_mpc_max( 

936 solution_next.MPCmax, WorstIncPrb, CRRA, PatFacBoro, BoroCnstNat, BoroCnstArt 

937 ) 

938 MPCmaxNow = 1.0 if BoroCnstNat < mNrmMinNow else MPCmaxUnc 

939 

940 cFuncLimitIntercept = MPCminNow * hNrmNow 

941 cFuncLimitSlope = MPCminNow 

942 

943 # Define the borrowing-constrained consumption function 

944 cFuncNowCnst = LinearInterp( 

945 np.array([mNrmMinNow, mNrmMinNow + 1.0]), 

946 np.array([0.0, 1.0]), 

947 ) 

948 

949 # Construct the assets grid by adjusting aXtra by the natural borrowing constraint 

950 aNrmNow = np.sort( 

951 np.hstack((np.asarray(aXtraGrid) + mNrmMinNow, np.array([0.0, 1e-15]))), 

952 ) 

953 

954 # Make a 1D array of the interest factor at each asset gridpoint 

955 Rfree = Rsave * np.ones_like(aNrmNow) 

956 Rfree[aNrmNow <= 0] = Rboro 

957 i_kink = np.argwhere(aNrmNow == 0.0)[0][0] 

958 

959 # Calculate end-of-period marginal value of assets at each gridpoint 

960 vPfacEff = DiscFacEff * Rfree * PermGroFac ** (-CRRA) 

961 EndOfPrdvP = vPfacEff * expected( 

962 calc_vp_next, 

963 IncShkDstn, 

964 args=(aNrmNow, Rfree, CRRA, PermGroFac, vPfuncNext), 

965 ) 

966 

967 # Invert the first order condition to find optimal cNrm from each aNrm gridpoint 

968 cNrmNow = uFunc.derinv(EndOfPrdvP, order=(1, 0)) 

969 mNrmNow = cNrmNow + aNrmNow # Endogenous mNrm gridpoints 

970 

971 # Limiting consumption is zero as m approaches mNrmMin 

972 c_for_interpolation = np.insert(cNrmNow, 0, 0.0) 

973 m_for_interpolation = np.insert(mNrmNow, 0, BoroCnstNat) 

974 

975 # Construct the consumption function as a cubic or linear spline interpolation 

976 if CubicBool: 

977 # Calculate end-of-period marginal marginal value of assets at each gridpoint 

978 vPPfacEff = DiscFacEff * Rfree * Rfree * PermGroFac ** (-CRRA - 1.0) 

979 EndOfPrdvPP = vPPfacEff * expected( 

980 calc_vpp_next, 

981 IncShkDstn, 

982 args=(aNrmNow, Rfree, CRRA, PermGroFac, vPPfuncNext), 

983 ) 

984 dcda = EndOfPrdvPP / uFunc.der(np.array(cNrmNow), order=2) 

985 MPC = dcda / (dcda + 1.0) 

986 MPC_for_interpolation = np.insert(MPC, 0, MPCmaxUnc) 

987 

988 # Construct the unconstrained consumption function as a cubic interpolation 

989 cFuncNowUnc = CubicInterp( 

990 m_for_interpolation, 

991 c_for_interpolation, 

992 MPC_for_interpolation, 

993 cFuncLimitIntercept, 

994 cFuncLimitSlope, 

995 ) 

996 # Adjust the coefficients on the kinked portion of the cFunc 

997 cFuncNowUnc.coeffs[i_kink + 2] = [ 

998 c_for_interpolation[i_kink + 1], 

999 m_for_interpolation[i_kink + 2] - m_for_interpolation[i_kink + 1], 

1000 0.0, 

1001 0.0, 

1002 ] 

1003 else: 

1004 # Construct the unconstrained consumption function as a linear interpolation 

1005 cFuncNowUnc = LinearInterp( 

1006 m_for_interpolation, 

1007 c_for_interpolation, 

1008 cFuncLimitIntercept, 

1009 cFuncLimitSlope, 

1010 ) 

1011 

1012 # Combine the constrained and unconstrained functions into the true consumption function. 

1013 # LowerEnvelope should only be used when BoroCnstArt is True 

1014 cFuncNow = LowerEnvelope(cFuncNowUnc, cFuncNowCnst, nan_bool=False) 

1015 

1016 # Make the marginal value function and the marginal marginal value function 

1017 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

1018 

1019 # Define this period's marginal marginal value function 

1020 if CubicBool: 

1021 vPPfuncNow = MargMargValueFuncCRRA(cFuncNow, CRRA) 

1022 else: 

1023 vPPfuncNow = NullFunc() # Dummy object 

1024 

1025 # Construct this period's value function if requested 

1026 if vFuncBool: 

1027 # Calculate end-of-period value, its derivative, and their pseudo-inverse 

1028 EndOfPrdv = DiscFacEff * expected( 

1029 calc_v_next, 

1030 IncShkDstn, 

1031 args=(aNrmNow, Rfree, CRRA, PermGroFac, vFuncNext), 

1032 ) 

1033 EndOfPrdvNvrs = uFunc.inv( 

1034 EndOfPrdv, 

1035 ) # value transformed through inverse utility 

1036 EndOfPrdvNvrsP = EndOfPrdvP * uFunc.derinv(EndOfPrdv, order=(0, 1)) 

1037 EndOfPrdvNvrs = np.insert(EndOfPrdvNvrs, 0, 0.0) 

1038 EndOfPrdvNvrsP = np.insert(EndOfPrdvNvrsP, 0, EndOfPrdvNvrsP[0]) 

1039 # This is a very good approximation, vNvrsPP = 0 at the asset minimum 

1040 

1041 # Construct the end-of-period value function 

1042 aNrm_temp = np.insert(aNrmNow, 0, BoroCnstNat) 

1043 EndOfPrdvNvrsFunc = CubicInterp(aNrm_temp, EndOfPrdvNvrs, EndOfPrdvNvrsP) 

1044 EndOfPrdvFunc = ValueFuncCRRA(EndOfPrdvNvrsFunc, CRRA) 

1045 

1046 # Compute expected value and marginal value on a grid of market resources 

1047 mNrm_temp = mNrmMinNow + aXtraGrid 

1048 cNrm_temp = cFuncNow(mNrm_temp) 

1049 aNrm_temp = mNrm_temp - cNrm_temp 

1050 v_temp = uFunc(cNrm_temp) + EndOfPrdvFunc(aNrm_temp) 

1051 vP_temp = uFunc.der(cNrm_temp) 

1052 

1053 # Construct the beginning-of-period value function 

1054 vNvrs_temp = uFunc.inv(v_temp) # value transformed through inv utility 

1055 vNvrsP_temp = vP_temp * uFunc.derinv(v_temp, order=(0, 1)) 

1056 mNrm_temp = np.insert(mNrm_temp, 0, mNrmMinNow) 

1057 vNvrs_temp = np.insert(vNvrs_temp, 0, 0.0) 

1058 vNvrsP_temp = np.insert(vNvrsP_temp, 0, MPCmaxNow ** (-CRRA / (1.0 - CRRA))) 

1059 MPCminNvrs = MPCminNow ** (-CRRA / (1.0 - CRRA)) 

1060 vNvrsFuncNow = CubicInterp( 

1061 mNrm_temp, 

1062 vNvrs_temp, 

1063 vNvrsP_temp, 

1064 MPCminNvrs * hNrmNow, 

1065 MPCminNvrs, 

1066 ) 

1067 vFuncNow = ValueFuncCRRA(vNvrsFuncNow, CRRA) 

1068 else: 

1069 vFuncNow = NullFunc() # Dummy object 

1070 

1071 # Create and return this period's solution 

1072 solution_now = ConsumerSolution( 

1073 cFunc=cFuncNow, 

1074 vFunc=vFuncNow, 

1075 vPfunc=vPfuncNow, 

1076 vPPfunc=vPPfuncNow, 

1077 mNrmMin=mNrmMinNow, 

1078 hNrm=hNrmNow, 

1079 MPCmin=MPCminNow, 

1080 MPCmax=MPCmaxNow, 

1081 ) 

1082 return solution_now 

1083 

1084 

1085def make_basic_CRRA_solution_terminal(CRRA): 

1086 """ 

1087 Construct the terminal period solution for a consumption-saving model with 

1088 CRRA utility and only one state variable. 

1089 

1090 Parameters 

1091 ---------- 

1092 CRRA : float 

1093 Coefficient of relative risk aversion. This is the only relevant parameter. 

1094 

1095 Returns 

1096 ------- 

1097 solution_terminal : ConsumerSolution 

1098 Terminal period solution for someone with the given CRRA. 

1099 """ 

1100 cFunc_terminal = LinearInterp([0.0, 1.0], [0.0, 1.0]) # c=m at t=T 

1101 vFunc_terminal = ValueFuncCRRA(cFunc_terminal, CRRA) 

1102 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA) 

1103 vPPfunc_terminal = MargMargValueFuncCRRA(cFunc_terminal, CRRA) 

1104 solution_terminal = ConsumerSolution( 

1105 cFunc=cFunc_terminal, 

1106 vFunc=vFunc_terminal, 

1107 vPfunc=vPfunc_terminal, 

1108 vPPfunc=vPPfunc_terminal, 

1109 mNrmMin=0.0, 

1110 hNrm=0.0, 

1111 MPCmin=1.0, 

1112 MPCmax=1.0, 

1113 ) 

1114 return solution_terminal 

1115 

1116 

1117# ============================================================================ 

1118# == Classes for representing types of consumer agents (and things they do) == 

1119# ============================================================================ 

1120 

1121# Make a dictionary of constructors (very simply for perfect foresight model) 

1122PerfForesightConsumerType_constructors_default = { 

1123 "solution_terminal": make_basic_CRRA_solution_terminal, 

1124 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

1125 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

1126} 

1127 

1128# Make a dictionary with parameters for the default constructor for kNrmInitDstn 

1129PerfForesightConsumerType_kNrmInitDstn_default = { 

1130 "kLogInitMean": -12.0, # Mean of log initial capital 

1131 "kLogInitStd": 0.0, # Stdev of log initial capital 

1132 "kNrmInitCount": 15, # Number of points in initial capital discretization 

1133} 

1134 

1135# Make a dictionary with parameters for the default constructor for pLvlInitDstn 

1136PerfForesightConsumerType_pLvlInitDstn_default = { 

1137 "pLogInitMean": 0.0, # Mean of log permanent income 

1138 "pLogInitStd": 0.0, # Stdev of log permanent income 

1139 "pLvlInitCount": 15, # Number of points in initial capital discretization 

1140} 

1141 

1142# Make a dictionary to specify a perfect foresight consumer type 

1143PerfForesightConsumerType_solving_defaults = { 

1144 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

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

1148 "constructors": PerfForesightConsumerType_constructors_default, # See dictionary above 

1149 # PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1152 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

1155 "BoroCnstArt": None, # Artificial borrowing constraint 

1156 "MaxKinks": 400, # Maximum number of grid points to allow in cFunc 

1157} 

1158PerfForesightConsumerType_simulation_defaults = { 

1159 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

1164 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

1167} 

1168PerfForesightConsumerType_defaults = {} 

1169PerfForesightConsumerType_defaults.update(PerfForesightConsumerType_solving_defaults) 

1170PerfForesightConsumerType_defaults.update( 

1171 PerfForesightConsumerType_kNrmInitDstn_default 

1172) 

1173PerfForesightConsumerType_defaults.update( 

1174 PerfForesightConsumerType_pLvlInitDstn_default 

1175) 

1176PerfForesightConsumerType_defaults.update(PerfForesightConsumerType_simulation_defaults) 

1177init_perfect_foresight = PerfForesightConsumerType_defaults 

1178 

1179 

1180class PerfForesightConsumerType(AgentType): 

1181 r""" 

1182 A perfect foresight consumer type who has no uncertainty other than mortality. 

1183 Their problem is defined by a coefficient of relative risk aversion (:math:`\rho`), intertemporal 

1184 discount factor (:math:`\beta`), interest factor (:math:`\mathsf{R}`), an optional artificial borrowing constraint (:math:`\underline{a}`) 

1185 and time sequences of the permanent income growth rate (:math:`\Gamma`) and survival probability (:math:`1-\mathsf{D}`). 

1186 Their assets and income are normalized by permanent income. 

1187 

1188 .. math:: 

1189 \newcommand{\CRRA}{\rho} 

1190 \newcommand{\DiePrb}{\mathsf{D}} 

1191 \newcommand{\PermGroFac}{\Gamma} 

1192 \newcommand{\Rfree}{\mathsf{R}} 

1193 \newcommand{\DiscFac}{\beta} 

1194 \begin{align*} 

1195 v_t(m_t) &= \max_{c_t}u(c_t) + \DiscFac (1 - \DiePrb_{t+1}) \PermGroFac_{t+1}^{1-\CRRA} v_{t+1}(m_{t+1}), \\ 

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

1197 a_t &= m_t - c_t, \\ 

1198 a_t &\geq \underline{a}, \\ 

1199 m_{t+1} &= \Rfree_{t+1} a_t/\PermGroFac_{t+1} + 1, \\ 

1200 u(c) &= \frac{c^{1-\CRRA}}{1-\CRRA} 

1201 \end{align*} 

1202 

1203 

1204 Solving Parameters 

1205 ------------------ 

1206 cycles: int 

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

1208 T_cycle: int 

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

1210 CRRA: float, :math:`\rho` 

1211 Coefficient of Relative Risk Aversion. 

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

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

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

1215 Intertemporal discount factor. 

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

1217 Survival probability after each period. 

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

1219 Permanent income growth factor. 

1220 BoroCnstArt: float, :math:`\underline{a}` 

1221 The minimum Asset/Perminant Income ratio, None to ignore. 

1222 MaxKinks: int 

1223 Maximum number of gridpoints to allow in cFunc. 

1224 

1225 Simulation Parameters 

1226 --------------------- 

1227 AgentCount: int 

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

1229 T_age: int 

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

1231 T_sim: int, required for simulation 

1232 Number of periods to simulate. 

1233 track_vars: list[strings] 

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

1235 For this agent, the options are 'kNrm', 'aLvl', 'aNrm', 'bNrm', 'cNrm', 'mNrm', 'pLvl', and 'who_dies'. 

1236 

1237 kNrm is beginning-of-period capital holdings (last period's assets) 

1238 

1239 aLvl is the nominal asset level 

1240 

1241 aNrm is the normalized assets 

1242 

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

1244 

1245 cNrm is the normalized consumption 

1246 

1247 mNrm is the normalized market resources 

1248 

1249 pLvl is the permanent income level 

1250 

1251 who_dies is the array of which agents died 

1252 kLogInitMean: float 

1253 Mean of Log initial Normalized Assets. 

1254 kLogInitStd: float 

1255 Std of Log initial Normalized Assets. 

1256 pLogInitMean: float 

1257 Mean of Log initial permanent income. 

1258 pLogInitStd: float 

1259 Std of Log initial permanent income. 

1260 PermGroFacAgg: float 

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

1262 PerfMITShk: boolean 

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

1264 

1265 Attributes 

1266 ---------- 

1267 solution: list[Consumer solution object] 

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

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

1270 

1271 Visit :class:`HARK.ConsumptionSaving.ConsIndShockModel.ConsumerSolution` for more information about the solution. 

1272 history: Dict[Array] 

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

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

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

1276 """ 

1277 

1278 solving_defaults = PerfForesightConsumerType_solving_defaults 

1279 simulation_defaults = PerfForesightConsumerType_simulation_defaults 

1280 

1281 default_ = { 

1282 "params": PerfForesightConsumerType_defaults, 

1283 "solver": solve_one_period_ConsPF, 

1284 "model": "ConsPerfForesight.yaml", 

1285 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

1286 } 

1287 

1288 time_vary_ = ["LivPrb", "PermGroFac", "Rfree"] 

1289 time_inv_ = ["CRRA", "DiscFac", "MaxKinks", "BoroCnstArt"] 

1290 state_vars = ["kNrm", "pLvl", "bNrm", "mNrm", "aNrm", "aLvl"] 

1291 shock_vars_ = [] 

1292 distributions = ["kNrmInitDstn", "pLvlInitDstn"] 

1293 

1294 def pre_solve(self): 

1295 """ 

1296 Method that is run automatically just before solution by backward iteration. 

1297 Solves the (trivial) terminal period and does a quick check on the borrowing 

1298 constraint and MaxKinks attribute (only relevant in constrained, infinite 

1299 horizon problems). 

1300 """ 

1301 self.check_restrictions() 

1302 self.construct("solution_terminal") # Solve the terminal period problem 

1303 self.check_conditions(verbose=self.verbose) 

1304 

1305 def post_solve(self): 

1306 """ 

1307 Method that is run automatically at the end of a call to solve. Here, it 

1308 simply calls calc_stable_points() if appropriate: an infinite horizon 

1309 problem with a single repeated period in its cycle. 

1310 

1311 Parameters 

1312 ---------- 

1313 None 

1314 

1315 Returns 

1316 ------- 

1317 None 

1318 """ 

1319 if (self.cycles == 0) and (self.T_cycle == 1): 

1320 self.calc_stable_points() 

1321 

1322 def check_restrictions(self): 

1323 """ 

1324 A method to check that various restrictions are met for the model class. 

1325 """ 

1326 if self.DiscFac < 0: 

1327 raise ValueError("DiscFac is below zero with value: " + str(self.DiscFac)) 

1328 

1329 def initialize_sim(self): 

1330 self.PermShkAggNow = self.PermGroFacAgg # This never changes during simulation 

1331 self.state_now["PlvlAgg"] = 1.0 

1332 super().initialize_sim() 

1333 

1334 def sim_birth(self, which_agents): 

1335 """ 

1336 Makes new consumers for the given indices. Initialized variables include aNrm and pLvl, as 

1337 well as time variables t_age and t_cycle. Normalized assets and permanent income levels 

1338 are drawn from lognormal distributions given by kLogInitMean and kLogInitStd (etc). 

1339 

1340 Parameters 

1341 ---------- 

1342 which_agents : np.array(Bool) 

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

1344 

1345 Returns 

1346 ------- 

1347 None 

1348 """ 

1349 # Get and store states for newly born agents 

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

1351 self.state_now["aNrm"][which_agents] = self.kNrmInitDstn.draw(N) 

1352 self.state_now["pLvl"][which_agents] = self.pLvlInitDstn.draw(N) 

1353 self.state_now["pLvl"][which_agents] *= self.state_now["PlvlAgg"] 

1354 self.t_age[which_agents] = 0 # How many periods since each agent was born 

1355 

1356 # Because of the timing of the simulation system, kNrm gets written to 

1357 # the *previous* period's aNrm after that aNrm has already been copied 

1358 # to the history array (if it's being tracked). It will be loaded into 

1359 # the simulation as kNrm, however, when the period is simulated. 

1360 

1361 # If PerfMITShk not specified, let it be False 

1362 if not hasattr(self, "PerfMITShk"): 

1363 self.PerfMITShk = False 

1364 if not self.PerfMITShk: 

1365 # If True, Newborns inherit t_cycle of agent they replaced (i.e. t_cycles are not reset). 

1366 self.t_cycle[which_agents] = 0 

1367 # Which period of the cycle each agent is currently in 

1368 

1369 def sim_death(self): 

1370 """ 

1371 Determines which agents die this period and must be replaced. Uses the sequence in LivPrb 

1372 to determine survival probabilities for each agent. 

1373 

1374 Parameters 

1375 ---------- 

1376 None 

1377 

1378 Returns 

1379 ------- 

1380 which_agents : np.array(bool) 

1381 Boolean array of size AgentCount indicating which agents die. 

1382 """ 

1383 # Determine who dies 

1384 DiePrb_by_t_cycle = 1.0 - np.asarray(self.LivPrb) 

1385 DiePrb = DiePrb_by_t_cycle[ 

1386 self.t_cycle - 1 if self.cycles == 1 else self.t_cycle 

1387 ] # Time has already advanced, so look back one 

1388 

1389 # In finite-horizon problems the previous line gives newborns the 

1390 # survival probability of the last non-terminal period. This is okay, 

1391 # however, since they will be instantly replaced by new newborns if 

1392 # they die. 

1393 # See: https://github.com/econ-ark/HARK/pull/981 

1394 

1395 DeathShks = Uniform(seed=self.RNG.integers(0, 2**31 - 1)).draw( 

1396 N=self.AgentCount 

1397 ) 

1398 which_agents = DeathShks < DiePrb 

1399 if self.T_age is not None: # Kill agents that have lived for too many periods 

1400 too_old = self.t_age >= self.T_age 

1401 which_agents = np.logical_or(which_agents, too_old) 

1402 return which_agents 

1403 

1404 def get_shocks(self): 

1405 """ 

1406 Finds permanent and transitory income "shocks" for each agent this period. As this is a 

1407 perfect foresight model, there are no stochastic shocks: PermShkNow = PermGroFac for each 

1408 agent (according to their t_cycle) and TranShkNow = 1.0 for all agents. 

1409 

1410 Parameters 

1411 ---------- 

1412 None 

1413 

1414 Returns 

1415 ------- 

1416 None 

1417 """ 

1418 PermGroFac = np.array(self.PermGroFac) 

1419 # Cycle time has already been advanced 

1420 self.shocks["PermShk"] = PermGroFac[self.t_cycle - 1] 

1421 # self.shocks["PermShk"][self.t_cycle == 0] = 1. # Add this at some point 

1422 self.shocks["TranShk"] = np.ones(self.AgentCount) 

1423 

1424 def get_Rport(self): 

1425 """ 

1426 Returns an array of size self.AgentCount with Rfree in every entry, 

1427 representing the risk-free portfolio return 

1428 

1429 Parameters 

1430 ---------- 

1431 None 

1432 

1433 Returns 

1434 ------- 

1435 RfreeNow : np.array 

1436 Array of size self.AgentCount with risk free interest rate for each agent. 

1437 """ 

1438 Rfree_array = np.array(self.Rfree) 

1439 return Rfree_array[self.t_cycle - 1] 

1440 

1441 def transition(self): 

1442 pLvlPrev = self.state_prev["pLvl"] 

1443 kNrm = self.state_prev["aNrm"] 

1444 RportNow = self.get_Rport() 

1445 

1446 # Calculate new states: normalized market resources and permanent income level 

1447 # Updated permanent income level 

1448 pLvlNow = pLvlPrev * self.shocks["PermShk"] 

1449 # "Effective" interest factor on normalized assets 

1450 ReffNow = RportNow / self.shocks["PermShk"] 

1451 bNrmNow = ReffNow * kNrm # Bank balances before labor income 

1452 # Market resources after income 

1453 mNrmNow = bNrmNow + self.shocks["TranShk"] 

1454 

1455 return kNrm, pLvlNow, bNrmNow, mNrmNow, None 

1456 

1457 def get_controls(self): 

1458 """ 

1459 Calculates consumption for each consumer of this type using the consumption functions. 

1460 

1461 Parameters 

1462 ---------- 

1463 None 

1464 

1465 Returns 

1466 ------- 

1467 None 

1468 """ 

1469 cNrmNow = np.full(self.AgentCount, np.nan) 

1470 MPCnow = np.full(self.AgentCount, np.nan) 

1471 for t in np.unique(self.t_cycle): 

1472 idx = self.t_cycle == t 

1473 if np.any(idx): 

1474 cNrmNow[idx], MPCnow[idx] = self.solution[t].cFunc.eval_with_derivative( 

1475 self.state_now["mNrm"][idx] 

1476 ) 

1477 self.controls["cNrm"] = cNrmNow 

1478 

1479 # MPCnow is not really a control 

1480 self.MPCnow = MPCnow 

1481 

1482 def get_poststates(self): 

1483 """ 

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

1485 

1486 Parameters 

1487 ---------- 

1488 None 

1489 

1490 Returns 

1491 ------- 

1492 None 

1493 """ 

1494 self.state_now["aNrm"] = self.state_now["mNrm"] - self.controls["cNrm"] 

1495 self.state_now["aLvl"] = self.state_now["aNrm"] * self.state_now["pLvl"] 

1496 # Update aggregate permanent productivity level 

1497 self.state_now["PlvlAgg"] = self.state_prev["PlvlAgg"] * self.PermShkAggNow 

1498 

1499 def log_condition_result(self, name, result, message, verbose): 

1500 """ 

1501 Records the result of one condition check in the attribute condition_report 

1502 of the bilt dictionary, and in the message log. 

1503 

1504 Parameters 

1505 ---------- 

1506 name : string or None 

1507 Name for the condition; if None, no test result is added to conditions. 

1508 result : bool 

1509 An indicator for whether the condition was passed. 

1510 message : str 

1511 The messages to record about the condition check. 

1512 verbose : bool 

1513 Indicator for whether verbose messages should be included in the report. 

1514 """ 

1515 if name is not None: 

1516 self.conditions[name] = result 

1517 set_verbosity_level((4 - verbose) * 10) 

1518 _log.info(message) 

1519 self.bilt["conditions_report"] += message + "\n" 

1520 

1521 def check_AIC(self, verbose=None): 

1522 """ 

1523 Evaluate and report on the Absolute Impatience Condition. 

1524 """ 

1525 name = "AIC" 

1526 APFac = self.bilt["APFac"] 

1527 result = APFac < 1.0 

1528 

1529 messages = { 

1530 True: f"APFac={APFac:.5f} : The Absolute Patience Factor satisfies the Absolute Impatience Condition (AIC) Þ < 1.", 

1531 False: f"APFac={APFac:.5f} : The Absolute Patience Factor violates the Absolute Impatience Condition (AIC) Þ < 1.", 

1532 } 

1533 verbose = self.verbose if verbose is None else verbose 

1534 self.log_condition_result(name, result, messages[result], verbose) 

1535 

1536 def check_GICRaw(self, verbose=None): 

1537 """ 

1538 Evaluate and report on the Growth Impatience Condition for the Perfect Foresight model. 

1539 """ 

1540 name = "GICRaw" 

1541 GPFacRaw = self.bilt["GPFacRaw"] 

1542 result = GPFacRaw < 1.0 

1543 

1544 messages = { 

1545 True: f"GPFacRaw={GPFacRaw:.5f} : The Growth Patience Factor satisfies the Growth Impatience Condition (GICRaw) Þ/G < 1.", 

1546 False: f"GPFacRaw={GPFacRaw:.5f} : The Growth Patience Factor violates the Growth Impatience Condition (GICRaw) Þ/G < 1.", 

1547 } 

1548 verbose = self.verbose if verbose is None else verbose 

1549 self.log_condition_result(name, result, messages[result], verbose) 

1550 

1551 def check_RIC(self, verbose=None): 

1552 """ 

1553 Evaluate and report on the Return Impatience Condition. 

1554 """ 

1555 name = "RIC" 

1556 RPFac = self.bilt["RPFac"] 

1557 result = RPFac < 1.0 

1558 

1559 messages = { 

1560 True: f"RPFac={RPFac:.5f} : The Return Patience Factor satisfies the Return Impatience Condition (RIC) Þ/R < 1.", 

1561 False: f"RPFac={RPFac:.5f} : The Return Patience Factor violates the Return Impatience Condition (RIC) Þ/R < 1.", 

1562 } 

1563 verbose = self.verbose if verbose is None else verbose 

1564 self.log_condition_result(name, result, messages[result], verbose) 

1565 

1566 def check_FHWC(self, verbose=None): 

1567 """ 

1568 Evaluate and report on the Finite Human Wealth Condition. 

1569 """ 

1570 name = "FHWC" 

1571 FHWFac = self.bilt["FHWFac"] 

1572 result = FHWFac < 1.0 

1573 

1574 messages = { 

1575 True: f"FHWFac={FHWFac:.5f} : The Finite Human Wealth Factor satisfies the Finite Human Wealth Condition (FHWC) G/R < 1.", 

1576 False: f"FHWFac={FHWFac:.5f} : The Finite Human Wealth Factor violates the Finite Human Wealth Condition (FHWC) G/R < 1.", 

1577 } 

1578 verbose = self.verbose if verbose is None else verbose 

1579 self.log_condition_result(name, result, messages[result], verbose) 

1580 

1581 def check_FVAC(self, verbose=None): 

1582 """ 

1583 Evaluate and report on the Finite Value of Autarky Condition under perfect foresight. 

1584 """ 

1585 name = "PFFVAC" 

1586 PFVAFac = self.bilt["PFVAFac"] 

1587 result = PFVAFac < 1.0 

1588 

1589 messages = { 

1590 True: f"PFVAFac={PFVAFac:.5f} : The Finite Value of Autarky Factor satisfies the Finite Value of Autarky Condition βG^(1-ρ) < 1.", 

1591 False: f"PFVAFac={PFVAFac:.5f} : The Finite Value of Autarky Factor violates the Finite Value of Autarky Condition βG^(1-ρ) < 1.", 

1592 } 

1593 verbose = self.verbose if verbose is None else verbose 

1594 self.log_condition_result(name, result, messages[result], verbose) 

1595 

1596 def describe_parameters(self): 

1597 """ 

1598 Make a string describing this instance's parameter values, including their 

1599 representation in code and symbolically. 

1600 

1601 Returns 

1602 ------- 

1603 param_desc : str 

1604 Description of parameters as a unicode string. 

1605 """ 

1606 params_to_describe = [ 

1607 # [name, description, symbol, time varying] 

1608 ["DiscFac", "intertemporal discount factor", "β", False], 

1609 ["Rfree", "risk free interest factor", "R", True], 

1610 ["PermGroFac", "permanent income growth factor", "G", True], 

1611 ["CRRA", "coefficient of relative risk aversion", "ρ", False], 

1612 ["LivPrb", "survival probability", "ℒ", True], 

1613 ["APFac", "absolute patience factor", "Þ=(βℒR)^(1/ρ)", False], 

1614 ] 

1615 

1616 param_desc = "" 

1617 for j in range(len(params_to_describe)): 

1618 this_entry = params_to_describe[j] 

1619 if this_entry[3]: 

1620 val = getattr(self, this_entry[0])[0] 

1621 else: 

1622 try: 

1623 val = getattr(self, this_entry[0]) 

1624 except: 

1625 val = self.bilt[this_entry[0]] 

1626 this_line = ( 

1627 this_entry[2] 

1628 + f"={val:.5f} : " 

1629 + this_entry[1] 

1630 + " (" 

1631 + this_entry[0] 

1632 + ")\n" 

1633 ) 

1634 param_desc += this_line 

1635 

1636 return param_desc 

1637 

1638 def calc_limiting_values(self): 

1639 """ 

1640 Compute various scalar values that are relevant to characterizing the 

1641 solution to an infinite horizon problem. This method should only be called 

1642 when T_cycle=1 and cycles=0, otherwise the values generated are meaningless. 

1643 This method adds the following values to the instance in the dictionary 

1644 attribute called bilt. 

1645 

1646 APFac : Absolute Patience Factor 

1647 GPFacRaw : Growth Patience Factor 

1648 FHWFac : Finite Human Wealth Factor 

1649 RPFac : Return Patience Factor 

1650 PFVAFac : Perfect Foresight Value of Autarky Factor 

1651 cNrmPDV : Present Discounted Value of Autarky Consumption 

1652 MPCmin : Limiting minimum MPC as market resources go to infinity 

1653 MPCmax : Limiting maximum MPC as market resources approach minimum level. 

1654 hNrm : Human wealth divided by permanent income. 

1655 Delta_mNrm_ZeroFunc : Linear consumption function where expected change in market resource ratio is zero 

1656 BalGroFunc : Linear consumption function where the level of market resources grows at the same rate as permanent income 

1657 

1658 Returns 

1659 ------- 

1660 None 

1661 """ 

1662 aux_dict = self.bilt 

1663 aux_dict["APFac"] = (self.Rfree[0] * self.DiscFac * self.LivPrb[0]) ** ( 

1664 1 / self.CRRA 

1665 ) 

1666 aux_dict["GPFacRaw"] = aux_dict["APFac"] / self.PermGroFac[0] 

1667 aux_dict["FHWFac"] = self.PermGroFac[0] / self.Rfree[0] 

1668 aux_dict["RPFac"] = aux_dict["APFac"] / self.Rfree[0] 

1669 aux_dict["PFVAFac"] = (self.DiscFac * self.LivPrb[0]) * self.PermGroFac[0] ** ( 

1670 1.0 - self.CRRA 

1671 ) 

1672 aux_dict["cNrmPDV"] = 1.0 / (1.0 - aux_dict["RPFac"]) 

1673 aux_dict["MPCmin"] = np.maximum(1.0 - aux_dict["RPFac"], 0.0) 

1674 constrained = ( 

1675 hasattr(self, "BoroCnstArt") 

1676 and (self.BoroCnstArt is not None) 

1677 and (self.BoroCnstArt > -np.inf) 

1678 ) 

1679 

1680 if constrained: 

1681 aux_dict["MPCmax"] = 1.0 

1682 else: 

1683 aux_dict["MPCmax"] = aux_dict["MPCmin"] 

1684 if aux_dict["FHWFac"] < 1.0: 

1685 aux_dict["hNrm"] = 1.0 / (1.0 - aux_dict["FHWFac"]) 

1686 else: 

1687 aux_dict["hNrm"] = np.inf 

1688 

1689 # Generate the "Delta m = 0" function, which is used to find target market resources 

1690 Ex_Rnrm = self.Rfree[0] / self.PermGroFac[0] 

1691 aux_dict["Delta_mNrm_ZeroFunc"] = ( 

1692 lambda m: (1.0 - 1.0 / Ex_Rnrm) * m + 1.0 / Ex_Rnrm 

1693 ) 

1694 

1695 # Generate the "E[M_tp1 / M_t] = G" function, which is used to find balanced growth market resources 

1696 PF_Rnrm = self.Rfree[0] / self.PermGroFac[0] 

1697 aux_dict["BalGroFunc"] = lambda m: (1.0 - 1.0 / PF_Rnrm) * m + 1.0 / PF_Rnrm 

1698 

1699 self.bilt = aux_dict 

1700 

1701 def check_conditions(self, verbose=None): 

1702 """ 

1703 This method checks whether the instance's type satisfies the 

1704 Absolute Impatience Condition (AIC), the Return Impatience Condition (RIC), 

1705 the Finite Human Wealth Condition (FHWC), the perfect foresight model's 

1706 Growth Impatience Condition (GICRaw) and Perfect Foresight Finite Value 

1707 of Autarky Condition (FVACPF). Depending on the configuration of parameter 

1708 values, somecombination of these conditions must be satisfied in order 

1709 for the problem to have a nondegenerate solution. To check which conditions 

1710 are required, in the verbose mode a reference to the relevant theoretical 

1711 literature is made. 

1712 

1713 Parameters 

1714 ---------- 

1715 verbose : boolean 

1716 Specifies different levels of verbosity of feedback. When False, it 

1717 only reports whether the instance's type fails to satisfy a particular 

1718 condition. When True, it reports all results, i.e. the factor values 

1719 for all conditions. 

1720 

1721 Returns 

1722 ------- 

1723 None 

1724 """ 

1725 self.conditions = {} 

1726 self.bilt["conditions_report"] = "" 

1727 self.degenerate = False 

1728 verbose = self.verbose if verbose is None else verbose 

1729 

1730 # This method only checks for the conditions for infinite horizon models 

1731 # with a 1 period cycle. If these conditions are not met, we exit early. 

1732 if self.cycles != 0 or self.T_cycle > 1: 

1733 trivial_message = "No conditions report was produced because this functionality is only supported for infinite horizon models with a cycle length of 1." 

1734 self.log_condition_result(None, None, trivial_message, verbose) 

1735 if not self.quiet: 

1736 _log.info(self.bilt["conditions_report"]) 

1737 return 

1738 

1739 # Calculate some useful quantities that will be used in the condition checks 

1740 self.calc_limiting_values() 

1741 param_desc = self.describe_parameters() 

1742 self.log_condition_result(None, None, param_desc, verbose) 

1743 

1744 # Check individual conditions and add their results to the report 

1745 self.check_AIC(verbose) 

1746 self.check_RIC(verbose) 

1747 self.check_GICRaw(verbose) 

1748 self.check_FVAC(verbose) 

1749 self.check_FHWC(verbose) 

1750 constrained = ( 

1751 hasattr(self, "BoroCnstArt") 

1752 and (self.BoroCnstArt is not None) 

1753 and (self.BoroCnstArt > -np.inf) 

1754 ) 

1755 

1756 # Exit now if verbose output was not requested. 

1757 if not verbose: 

1758 if not self.quiet: 

1759 _log.info(self.bilt["conditions_report"]) 

1760 return 

1761 

1762 # Report on the degeneracy of the consumption function solution 

1763 if not constrained: 

1764 if self.conditions["FHWC"]: 

1765 RIC_message = "\nBecause the FHWC is satisfied, the solution is not c(m)=Infinity." 

1766 if self.conditions["RIC"]: 

1767 RIC_message += " Because the RIC is also satisfied, the solution is also not c(m)=0 for all m, so a non-degenerate linear solution exists." 

1768 degenerate = False 

1769 else: 

1770 RIC_message += " However, because the RIC is violated, the solution is degenerate at c(m) = 0 for all m." 

1771 degenerate = True 

1772 else: 

1773 RIC_message = "\nBecause the FHWC condition is violated and the consumer is not constrained, the solution is degenerate at c(m)=Infinity." 

1774 degenerate = True 

1775 else: 

1776 if self.conditions["RIC"]: 

1777 RIC_message = "\nBecause the RIC is satisfied and the consumer is constrained, the solution is not c(m)=0 for all m." 

1778 if self.conditions["GICRaw"]: 

1779 RIC_message += " Because the GICRaw is also satisfied, the solution is non-degenerate. It is piecewise linear with an infinite number of kinks, approaching the unconstrained solution as m goes to infinity." 

1780 degenerate = False 

1781 else: 

1782 RIC_message += " Because the GICRaw is violated, the solution is non-degenerate. It is piecewise linear with a single kink at some 0 < m < 1; it equals the unconstrained solution above that kink point and has c(m) = m below it." 

1783 degenerate = False 

1784 else: 

1785 if self.conditions["GICRaw"]: 

1786 RIC_message = "\nBecause the RIC is violated but the GIC is satisfied, the FHWC is necessarily also violated. In this case, the consumer's pathological patience is offset by his infinite human wealth, against which he cannot borrow arbitrarily; a non-degenerate solution exists." 

1787 degenerate = False 

1788 else: 

1789 RIC_message = "\nBecause the RIC is violated but the FHWC is satisfied, the solution is degenerate at c(m)=0 for all m." 

1790 degenerate = True 

1791 self.log_condition_result(None, None, RIC_message, verbose) 

1792 

1793 if ( 

1794 degenerate 

1795 ): # All of the other checks are meaningless if the solution is degenerate 

1796 if not self.quiet: 

1797 _log.info(self.bilt["conditions_report"]) 

1798 return 

1799 

1800 # Report on the consequences of the Absolute Impatience Condition 

1801 if self.conditions["AIC"]: 

1802 AIC_message = "\nBecause the AIC is satisfied, the absolute amount of consumption is expected to fall over time." 

1803 else: 

1804 AIC_message = "\nBecause the AIC is violated, the absolute amount of consumption is expected to grow over time." 

1805 self.log_condition_result(None, None, AIC_message, verbose) 

1806 

1807 # Report on the consequences of the Growth Impatience Condition 

1808 if self.conditions["GICRaw"]: 

1809 GIC_message = "\nBecause the GICRaw is satisfed, the ratio of individual wealth to permanent income is expected to fall indefinitely." 

1810 elif self.conditions["FHWC"]: 

1811 GIC_message = "\nBecause the GICRaw is violated but the FHWC is satisfied, the ratio of individual wealth to permanent income is expected to rise toward infinity." 

1812 else: 

1813 pass # pragma: nocover 

1814 # This can never be reached! If GICRaw and FHWC both fail, then the RIC also fails, and we would have exited by this point. 

1815 self.log_condition_result(None, None, GIC_message, verbose) 

1816 

1817 if not self.quiet: 

1818 _log.info(self.bilt["conditions_report"]) 

1819 

1820 def calc_stable_points(self, force=False): 

1821 """ 

1822 If the problem is one that satisfies the conditions required for target ratios of different 

1823 variables to permanent income to exist, and has been solved to within the self-defined 

1824 tolerance, this method calculates the target values of market resources. 

1825 

1826 Parameters 

1827 ---------- 

1828 force : bool 

1829 Indicator for whether the method should be forced to be run even if 

1830 the agent seems to be the wrong type. Default is False. 

1831 

1832 Returns 

1833 ------- 

1834 None 

1835 """ 

1836 # Child classes should not run this method 

1837 is_perf_foresight = type(self) is PerfForesightConsumerType 

1838 is_ind_shock = type(self) is IndShockConsumerType 

1839 if not (is_perf_foresight or is_ind_shock or force): 

1840 return 

1841 

1842 infinite_horizon = self.cycles == 0 

1843 single_period = self.T_cycle == 1 

1844 if not infinite_horizon: 

1845 raise ValueError( 

1846 "The calc_stable_points method works only for infinite horizon models." 

1847 ) 

1848 if not single_period: 

1849 raise ValueError( 

1850 "The calc_stable_points method works only with a single infinitely repeated period." 

1851 ) 

1852 if not hasattr(self, "conditions"): 

1853 raise ValueError( 

1854 "The check_conditions method must be run before the calc_stable_points method." 

1855 ) 

1856 if not hasattr(self, "solution"): 

1857 raise ValueError( 

1858 "The solve method must be run before the calc_stable_points method." 

1859 ) 

1860 

1861 # Extract balanced growth and delta m_t+1 = 0 functions 

1862 BalGroFunc = self.bilt["BalGroFunc"] 

1863 Delta_mNrm_ZeroFunc = self.bilt["Delta_mNrm_ZeroFunc"] 

1864 

1865 # If the GICRaw holds, then there is a balanced growth market resources ratio 

1866 if self.conditions["GICRaw"]: 

1867 cFunc = self.solution[0].cFunc 

1868 func_to_zero = lambda m: BalGroFunc(m) - cFunc(m) 

1869 m0 = 1.0 

1870 try: 

1871 mNrmStE = newton(func_to_zero, m0) 

1872 except: 

1873 mNrmStE = np.nan 

1874 

1875 # A target level of assets *might* exist even if the GICMod fails, so check no matter what 

1876 func_to_zero = lambda m: Delta_mNrm_ZeroFunc(m) - cFunc(m) 

1877 m0 = 1.0 if np.isnan(mNrmStE) else mNrmStE 

1878 try: 

1879 mNrmTrg = newton(func_to_zero, m0, maxiter=200) 

1880 except: 

1881 mNrmTrg = np.nan 

1882 else: 

1883 mNrmStE = np.nan 

1884 mNrmTrg = np.nan 

1885 

1886 self.solution[0].mNrmStE = mNrmStE 

1887 self.solution[0].mNrmTrg = mNrmTrg 

1888 self.bilt["mNrmStE"] = mNrmStE 

1889 self.bilt["mNrmTrg"] = mNrmTrg 

1890 

1891 

1892############################################################################### 

1893 

1894# Make a dictionary of constructors for the idiosyncratic income shocks model 

1895IndShockConsumerType_constructors_default = { 

1896 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

1897 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

1898 "IncShkDstn": construct_lognormal_income_process_unemployment, 

1899 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

1900 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

1901 "aXtraGrid": make_assets_grid, 

1902 "solution_terminal": make_basic_CRRA_solution_terminal, 

1903} 

1904 

1905# Make a dictionary with parameters for the default constructor for kNrmInitDstn 

1906IndShockConsumerType_kNrmInitDstn_default = { 

1907 "kLogInitMean": -12.0, # Mean of log initial capital 

1908 "kLogInitStd": 0.0, # Stdev of log initial capital 

1909 "kNrmInitCount": 15, # Number of points in initial capital discretization 

1910} 

1911 

1912# Make a dictionary with parameters for the default constructor for pLvlInitDstn 

1913IndShockConsumerType_pLvlInitDstn_default = { 

1914 "pLogInitMean": 0.0, # Mean of log permanent income 

1915 "pLogInitStd": 0.0, # Stdev of log permanent income 

1916 "pLvlInitCount": 15, # Number of points in initial capital discretization 

1917} 

1918 

1919# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

1920IndShockConsumerType_IncShkDstn_default = { 

1921 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks 

1922 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks 

1923 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks 

1924 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks 

1925 "UnempPrb": 0.05, # Probability of unemployment while working 

1926 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working 

1927 "T_retire": 0, # Period of retirement (0 --> no retirement) 

1928 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired 

1929 "IncUnempRet": 0.0, # "Unemployment" benefits when retired 

1930} 

1931 

1932# Default parameters to make aXtraGrid using make_assets_grid 

1933IndShockConsumerType_aXtraGrid_default = { 

1934 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value 

1935 "aXtraMax": 20, # Maximum end-of-period "assets above minimum" value 

1936 "aXtraNestFac": 3, # Exponential nesting factor for aXtraGrid 

1937 "aXtraCount": 48, # Number of points in the grid of "assets above minimum" 

1938 "aXtraExtra": None, # Additional other values to add in grid (optional) 

1939} 

1940 

1941# Make a dictionary to specify an idiosyncratic income shocks consumer type 

1942IndShockConsumerType_solving_default = { 

1943 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

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

1947 "constructors": IndShockConsumerType_constructors_default, # See dictionary above 

1948 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1951 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

1954 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

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

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

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

1958} 

1959IndShockConsumerType_simulation_default = { 

1960 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1966 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1970} 

1971 

1972IndShockConsumerType_defaults = {} 

1973IndShockConsumerType_defaults.update(IndShockConsumerType_IncShkDstn_default) 

1974IndShockConsumerType_defaults.update(IndShockConsumerType_kNrmInitDstn_default) 

1975IndShockConsumerType_defaults.update(IndShockConsumerType_pLvlInitDstn_default) 

1976IndShockConsumerType_defaults.update(IndShockConsumerType_aXtraGrid_default) 

1977IndShockConsumerType_defaults.update(IndShockConsumerType_solving_default) 

1978IndShockConsumerType_defaults.update(IndShockConsumerType_simulation_default) 

1979init_idiosyncratic_shocks = IndShockConsumerType_defaults # Here so that other models which use the old convention don't break 

1980 

1981 

1982class IndShockConsumerType(PerfForesightConsumerType): 

1983 r""" 

1984 A consumer type with idiosyncratic shocks to permanent and transitory income. 

1985 Their problem is defined by a sequence of income distributions, survival probabilities 

1986 (:math:`\mathsf{S}`), and permanent income growth rates (:math:`\Gamma`), as well 

1987 as time invariant values for risk aversion (:math:`\rho`), discount factor (:math:`\beta`), 

1988 the interest rate (:math:`\mathsf{R}`), the grid of end-of-period assets, and an artificial 

1989 borrowing constraint (:math:`\underline{a}`). 

1990 

1991 .. math:: 

1992 \newcommand{\CRRA}{\rho} 

1993 \newcommand{\LivPrb}{\mathsf{S}} 

1994 \newcommand{\PermGroFac}{\Gamma} 

1995 \newcommand{\Rfree}{\mathsf{R}} 

1996 \newcommand{\DiscFac}{\beta} 

1997 \begin{align*} 

1998 v_t(m_t) &= \max_{c_t}u(c_t) + \DiscFac \LivPrb_t \mathbb{E}_{t} \left[ (\PermGroFac_{t+1} \psi_{t+1})^{1-\CRRA} v_{t+1}(m_{t+1}) \right], \\ 

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

2000 a_t &= m_t - c_t, \\ 

2001 a_t &\geq \underline{a}, \\ 

2002 m_{t+1} &= a_t \Rfree_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}) + \theta_{t+1}, \\ 

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

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

2005 u(c) &= \frac{c^{1-\CRRA}}{1-\CRRA}. 

2006 \end{align*} 

2007 

2008 

2009 Constructors 

2010 ------------ 

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

2012 The agent's income shock distributions. 

2013 

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

2015 aXtraGrid: Constructor 

2016 The agent's asset grid. 

2017 

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

2019 

2020 Solving Parameters 

2021 ------------------ 

2022 cycles: int 

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

2024 T_cycle: int 

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

2026 CRRA: float, :math:`\rho` 

2027 Coefficient of Relative Risk Aversion. 

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

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

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

2031 Intertemporal discount factor. 

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

2033 Survival probability after each period. 

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

2035 Permanent income growth factor. 

2036 BoroCnstArt: float, :math:`\underline{a}` 

2037 The minimum Asset/Perminant Income ratio, None to ignore. 

2038 vFuncBool: bool 

2039 Whether to calculate the value function during solution. 

2040 CubicBool: bool 

2041 Whether to use cubic spline interpoliation. 

2042 

2043 Simulation Parameters 

2044 --------------------- 

2045 AgentCount: int 

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

2047 T_age: int 

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

2049 T_sim: int, required for simulation 

2050 Number of periods to simulate. 

2051 track_vars: list[strings] 

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

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

2054 

2055 PermShk is the agent's permanent income shock 

2056 

2057 TranShk is the agent's transitory income shock 

2058 

2059 aLvl is the nominal asset level 

2060 

2061 aNrm is the normalized assets 

2062 

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

2064 

2065 cNrm is the normalized consumption 

2066 

2067 mNrm is the normalized market resources 

2068 

2069 pLvl is the permanent income level 

2070 

2071 who_dies is the array of which agents died 

2072 kLogInitMean: float 

2073 Mean of Log initial Normalized Assets. 

2074 kLogInitStd: float 

2075 Std of Log initial Normalized Assets. 

2076 pLogInitMean: float 

2077 Mean of Log initial permanent income. 

2078 pLogInitStd: float 

2079 Std of Log initial permanent income. 

2080 PermGroFacAgg: float 

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

2082 PerfMITShk: boolean 

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

2084 NewbornTransShk: boolean 

2085 Whether Newborns have transitory shock. 

2086 

2087 Attributes 

2088 ---------- 

2089 solution: list[Consumer solution object] 

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

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

2092 

2093 Visit :class:`HARK.ConsumptionSaving.ConsIndShockModel.ConsumerSolution` for more information about the solution. 

2094 history: Dict[Array] 

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

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

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

2098 """ 

2099 

2100 IncShkDstn_defaults = IndShockConsumerType_IncShkDstn_default 

2101 aXtraGrid_defaults = IndShockConsumerType_aXtraGrid_default 

2102 solving_defaults = IndShockConsumerType_solving_default 

2103 simulation_defaults = IndShockConsumerType_simulation_default 

2104 default_ = { 

2105 "params": IndShockConsumerType_defaults, 

2106 "solver": solve_one_period_ConsIndShock, 

2107 "model": "ConsIndShock.yaml", 

2108 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

2109 } 

2110 

2111 time_inv_ = PerfForesightConsumerType.time_inv_ + [ 

2112 "vFuncBool", 

2113 "CubicBool", 

2114 "aXtraGrid", 

2115 ] 

2116 time_vary_ = PerfForesightConsumerType.time_vary_ + [ 

2117 "IncShkDstn", 

2118 "PermShkDstn", 

2119 "TranShkDstn", 

2120 ] 

2121 # This is in the PerfForesight model but not ConsIndShock 

2122 time_inv_.remove("MaxKinks") 

2123 shock_vars_ = ["PermShk", "TranShk"] 

2124 distributions = [ 

2125 "IncShkDstn", 

2126 "PermShkDstn", 

2127 "TranShkDstn", 

2128 "kNrmInitDstn", 

2129 "pLvlInitDstn", 

2130 ] 

2131 

2132 def update_income_process(self): 

2133 self.update("IncShkDstn", "PermShkDstn", "TranShkDstn") 

2134 

2135 def get_shocks(self): 

2136 """ 

2137 Gets permanent and transitory income shocks for this period. Samples from IncShkDstn for 

2138 each period in the cycle. 

2139 

2140 Parameters 

2141 ---------- 

2142 NewbornTransShk : boolean, optional 

2143 Whether Newborns have transitory shock. The default is False. 

2144 

2145 Returns 

2146 ------- 

2147 None 

2148 """ 

2149 # Whether Newborns have transitory shock. The default is False. 

2150 NewbornTransShk = self.NewbornTransShk 

2151 

2152 PermShkNow = np.zeros(self.AgentCount) # Initialize shock arrays 

2153 TranShkNow = np.zeros(self.AgentCount) 

2154 newborn = self.t_age == 0 

2155 for t in np.unique(self.t_cycle): 

2156 idx = self.t_cycle == t 

2157 

2158 # temporary, see #1022 

2159 if self.cycles == 1: 

2160 t = t - 1 

2161 

2162 N = np.sum(idx) 

2163 if N > 0: 

2164 # set current income distribution 

2165 IncShkDstnNow = self.IncShkDstn[t] 

2166 # and permanent growth factor 

2167 PermGroFacNow = self.PermGroFac[t] 

2168 # Get random draws of income shocks from the discrete distribution 

2169 IncShks = IncShkDstnNow.draw(N) 

2170 

2171 PermShkNow[idx] = ( 

2172 IncShks[0, :] * PermGroFacNow 

2173 ) # permanent "shock" includes expected growth 

2174 TranShkNow[idx] = IncShks[1, :] 

2175 

2176 # That procedure used the *last* period in the sequence for newborns, but that's not right 

2177 # Redraw shocks for newborns, using the *first* period in the sequence. Approximation. 

2178 N = np.sum(newborn) 

2179 if N > 0: 

2180 idx = newborn 

2181 # set current income distribution 

2182 IncShkDstnNow = self.IncShkDstn[0] 

2183 PermGroFacNow = self.PermGroFac[0] # and permanent growth factor 

2184 

2185 # Get random draws of income shocks from the discrete distribution 

2186 EventDraws = IncShkDstnNow.draw_events(N) 

2187 PermShkNow[idx] = ( 

2188 IncShkDstnNow.atoms[0][EventDraws] * PermGroFacNow 

2189 ) # permanent "shock" includes expected growth 

2190 TranShkNow[idx] = IncShkDstnNow.atoms[1][EventDraws] 

2191 

2192 # Whether Newborns have transitory shock. The default is False. 

2193 if not NewbornTransShk: 

2194 TranShkNow[newborn] = 1.0 

2195 

2196 # Store the shocks in self 

2197 self.shocks["PermShk"] = PermShkNow 

2198 self.shocks["TranShk"] = TranShkNow 

2199 

2200 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): 

2201 """ 

2202 Creates a "normalized Euler error" function for this instance, mapping 

2203 from market resources to "consumption error per dollar of consumption." 

2204 Stores result in attribute eulerErrorFunc as an interpolated function. 

2205 Has option to use approximate income distribution stored in self.IncShkDstn 

2206 or to use a (temporary) very dense approximation. 

2207 

2208 Only works on (one period) infinite horizon models at this time, will 

2209 be generalized later. 

2210 

2211 Parameters 

2212 ---------- 

2213 mMax : float 

2214 Maximum normalized market resources for the Euler error function. 

2215 approx_inc_dstn : Boolean 

2216 Indicator for whether to use the approximate discrete income distri- 

2217 bution stored in self.IncShkDstn[0], or to use a very accurate 

2218 discrete approximation instead. When True, uses approximation in 

2219 IncShkDstn; when False, makes and uses a very dense approximation. 

2220 

2221 Returns 

2222 ------- 

2223 None 

2224 

2225 Notes 

2226 ----- 

2227 This method is not used by any other code in the library. Rather, it is here 

2228 for expository and benchmarking purposes. 

2229 """ 

2230 # Get the income distribution (or make a very dense one) 

2231 if approx_inc_dstn: 

2232 IncShkDstn = self.IncShkDstn[0] 

2233 else: 

2234 TranShkDstn = MeanOneLogNormal(sigma=self.TranShkStd[0]).discretize( 

2235 N=200, 

2236 method="equiprobable", 

2237 tail_N=50, 

2238 tail_order=1.3, 

2239 tail_bound=[0.05, 0.95], 

2240 ) 

2241 TranShkDstn = add_discrete_outcome_constant_mean( 

2242 TranShkDstn, p=self.UnempPrb, x=self.IncUnemp 

2243 ) 

2244 PermShkDstn = MeanOneLogNormal(sigma=self.PermShkStd[0]).discretize( 

2245 N=200, 

2246 method="equiprobable", 

2247 tail_N=50, 

2248 tail_order=1.3, 

2249 tail_bound=[0.05, 0.95], 

2250 ) 

2251 IncShkDstn = combine_indep_dstns(PermShkDstn, TranShkDstn) 

2252 

2253 # Make a grid of market resources 

2254 mNowMin = self.solution[0].mNrmMin + 10 ** ( 

2255 -15 

2256 ) # add tiny bit to get around 0/0 problem 

2257 mNowMax = mMax 

2258 mNowGrid = np.linspace(mNowMin, mNowMax, 1000) 

2259 

2260 # Get the consumption function this period and the marginal value function 

2261 # for next period. Note that this part assumes a one period cycle. 

2262 cFuncNow = self.solution[0].cFunc 

2263 vPfuncNext = self.solution[0].vPfunc 

2264 

2265 # Calculate consumption this period at each gridpoint (and assets) 

2266 cNowGrid = cFuncNow(mNowGrid) 

2267 aNowGrid = mNowGrid - cNowGrid 

2268 

2269 # Tile the grids for fast computation 

2270 ShkCount = IncShkDstn.pmv.size 

2271 aCount = aNowGrid.size 

2272 aNowGrid_tiled = np.tile(aNowGrid, (ShkCount, 1)) 

2273 PermShkVals_tiled = (np.tile(IncShkDstn.atoms[0], (aCount, 1))).transpose() 

2274 TranShkVals_tiled = (np.tile(IncShkDstn.atoms[1], (aCount, 1))).transpose() 

2275 ShkPrbs_tiled = (np.tile(IncShkDstn.pmv, (aCount, 1))).transpose() 

2276 

2277 # Calculate marginal value next period for each gridpoint and each shock 

2278 mNextArray = ( 

2279 self.Rfree[0] / (self.PermGroFac[0] * PermShkVals_tiled) * aNowGrid_tiled 

2280 + TranShkVals_tiled 

2281 ) 

2282 vPnextArray = vPfuncNext(mNextArray) 

2283 

2284 # Calculate expected marginal value and implied optimal consumption 

2285 ExvPnextGrid = ( 

2286 self.DiscFac 

2287 * self.Rfree[0] 

2288 * self.LivPrb[0] 

2289 * self.PermGroFac[0] ** (-self.CRRA) 

2290 * np.sum( 

2291 PermShkVals_tiled ** (-self.CRRA) * vPnextArray * ShkPrbs_tiled, axis=0 

2292 ) 

2293 ) 

2294 cOptGrid = ExvPnextGrid ** ( 

2295 -1.0 / self.CRRA 

2296 ) # This is the 'Endogenous Gridpoints' step 

2297 

2298 # Calculate Euler error and store an interpolated function 

2299 EulerErrorNrmGrid = (cNowGrid - cOptGrid) / cOptGrid 

2300 eulerErrorFunc = LinearInterp(mNowGrid, EulerErrorNrmGrid) 

2301 self.eulerErrorFunc = eulerErrorFunc 

2302 

2303 def pre_solve(self): 

2304 self.check_restrictions() 

2305 self.construct("solution_terminal") 

2306 if not self.quiet: 

2307 self.check_conditions(verbose=self.verbose) 

2308 

2309 def describe_parameters(self): 

2310 """ 

2311 Generate a string describing the primitive model parameters that will 

2312 be used to calculating limiting values and factors. 

2313 

2314 Parameters 

2315 ---------- 

2316 None 

2317 

2318 Returns 

2319 ------- 

2320 param_desc : str 

2321 Description of primitive parameters. 

2322 """ 

2323 # Get parameter description from the perfect foresight model 

2324 param_desc = super().describe_parameters() 

2325 

2326 # Make a new entry for weierstrass-p (the weird formatting here is to 

2327 # make it easier to adapt into the style of the superclass if we add more 

2328 # parameter reports later) 

2329 this_entry = [ 

2330 "WorstPrb", 

2331 "probability of worst income shock realization", 

2332 "℘", 

2333 False, 

2334 ] 

2335 try: 

2336 val = getattr(self, this_entry[0]) 

2337 except: 

2338 val = self.bilt[this_entry[0]] 

2339 this_line = ( 

2340 this_entry[2] 

2341 + f"={val:.5f} : " 

2342 + this_entry[1] 

2343 + " (" 

2344 + this_entry[0] 

2345 + ")\n" 

2346 ) 

2347 

2348 # Add in the new entry and return it 

2349 param_desc += this_line 

2350 return param_desc 

2351 

2352 def calc_limiting_values(self): 

2353 """ 

2354 Compute various scalar values that are relevant to characterizing the 

2355 solution to an infinite horizon problem. This method should only be called 

2356 when T_cycle=1 and cycles=0, otherwise the values generated are meaningless. 

2357 This method adds the following values to this instance in the dictionary 

2358 attribute called bilt. 

2359 

2360 APFac : Absolute Patience Factor 

2361 GPFacRaw : Growth Patience Factor 

2362 GPFacMod : Risk-Modified Growth Patience Factor 

2363 GPFacLiv : Mortality-Adjusted Growth Patience Factor 

2364 GPFacLivMod : Modigliani Mortality-Adjusted Growth Patience Factor 

2365 GPFacSdl : Szeidl Growth Patience Factor 

2366 FHWFac : Finite Human Wealth Factor 

2367 RPFac : Return Patience Factor 

2368 WRPFac : Weak Return Patience Factor 

2369 PFVAFac : Perfect Foresight Value of Autarky Factor 

2370 VAFac : Value of Autarky Factor 

2371 cNrmPDV : Present Discounted Value of Autarky Consumption 

2372 MPCmin : Limiting minimum MPC as market resources go to infinity 

2373 MPCmax : Limiting maximum MPC as market resources approach minimum level 

2374 hNrm : Human wealth divided by permanent income. 

2375 ELogPermShk : Expected log permanent income shock 

2376 WorstPrb : Probability of worst income shock realization 

2377 Delta_mNrm_ZeroFunc : Linear locus where expected change in market resource ratio is zero 

2378 BalGroFunc : Linear consumption function where the level of market resources grows at the same rate as permanent income 

2379 

2380 Returns 

2381 ------- 

2382 None 

2383 """ 

2384 super().calc_limiting_values() 

2385 aux_dict = self.bilt 

2386 

2387 # Calculate the risk-modified growth impatience factor 

2388 PermShkDstn = self.PermShkDstn[0] 

2389 inv_func = lambda x: x ** (-1.0) 

2390 Ex_PermShkInv = expected(inv_func, PermShkDstn)[0] 

2391 GroCompPermShk = Ex_PermShkInv ** (-1.0) 

2392 aux_dict["GPFacMod"] = aux_dict["APFac"] / (self.PermGroFac[0] * GroCompPermShk) 

2393 

2394 # Calculate the mortality-adjusted growth impatience factor (and version 

2395 # with Modigiliani bequests) 

2396 aux_dict["GPFacLiv"] = aux_dict["GPFacRaw"] * self.LivPrb[0] 

2397 aux_dict["GPFacLivMod"] = aux_dict["GPFacLiv"] * self.LivPrb[0] 

2398 

2399 # Calculate the risk-modified value of autarky factor 

2400 if self.CRRA == 1.0: 

2401 UtilCompPermShk = np.exp(expected(np.log, PermShkDstn)[0]) 

2402 else: 

2403 CRRAfunc = lambda x: x ** (1.0 - self.CRRA) 

2404 UtilCompPermShk = expected(CRRAfunc, PermShkDstn)[0] ** ( 

2405 1 / (1.0 - self.CRRA) 

2406 ) 

2407 aux_dict["VAFac"] = self.DiscFac * (self.PermGroFac[0] * UtilCompPermShk) ** ( 

2408 1.0 - self.CRRA 

2409 ) 

2410 

2411 # Calculate the expected log permanent income shock, which will be used 

2412 # for the Szeidl variation of the Growth Impatience condition 

2413 aux_dict["ELogPermShk"] = expected(np.log, PermShkDstn)[0] 

2414 

2415 # Calculate the Harmenberg permanent income neutral expected log permanent 

2416 # shock and the Harmenberg Growth Patience Factor 

2417 Hrm_func = lambda x: x * np.log(x) 

2418 PermShk_Hrm = np.exp(expected(Hrm_func, PermShkDstn)[0]) 

2419 aux_dict["GPFacHrm"] = aux_dict["GPFacRaw"] / PermShk_Hrm 

2420 

2421 # Calculate the probability of the worst income shock realization 

2422 PermShkValsNext = self.IncShkDstn[0].atoms[0] 

2423 TranShkValsNext = self.IncShkDstn[0].atoms[1] 

2424 ShkPrbsNext = self.IncShkDstn[0].pmv 

2425 Ex_IncNext = np.dot(ShkPrbsNext, PermShkValsNext * TranShkValsNext) 

2426 PermShkMinNext = np.min(PermShkValsNext) 

2427 TranShkMinNext = np.min(TranShkValsNext) 

2428 WorstIncNext = PermShkMinNext * TranShkMinNext 

2429 WorstIncPrb = np.sum( 

2430 ShkPrbsNext[(PermShkValsNext * TranShkValsNext) == WorstIncNext] 

2431 ) 

2432 aux_dict["WorstPrb"] = WorstIncPrb 

2433 

2434 # Calculate the weak return patience factor 

2435 aux_dict["WRPFac"] = WorstIncPrb ** (1.0 / self.CRRA) * aux_dict["RPFac"] 

2436 

2437 # Calculate human wealth and the infinite horizon natural borrowing constraint 

2438 if aux_dict["FHWFac"] < 1.0: 

2439 hNrm = Ex_IncNext / (1.0 - aux_dict["FHWFac"]) 

2440 else: 

2441 hNrm = np.inf 

2442 temp = PermShkMinNext * aux_dict["FHWFac"] 

2443 BoroCnstNat = -TranShkMinNext * temp / (1.0 - temp) 

2444 

2445 # Find the upper bound of the MPC as market resources approach the minimum 

2446 BoroCnstArt = -np.inf if self.BoroCnstArt is None else self.BoroCnstArt 

2447 if BoroCnstNat < BoroCnstArt: 

2448 MPCmax = 1.0 # if natural borrowing constraint is overridden by artificial one, MPCmax is 1 

2449 else: 

2450 MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * aux_dict["RPFac"] 

2451 MPCmax = np.maximum(MPCmax, 0.0) 

2452 

2453 # Store maximum MPC and human wealth 

2454 aux_dict["hNrm"] = hNrm 

2455 aux_dict["MPCmax"] = MPCmax 

2456 

2457 # Generate the "Delta m = 0" function, which is used to find target market resources 

2458 # This overwrites the function generated by the perfect foresight version 

2459 Ex_Rnrm = self.Rfree[0] / self.PermGroFac[0] * Ex_PermShkInv 

2460 aux_dict["Delta_mNrm_ZeroFunc"] = ( 

2461 lambda m: (1.0 - 1.0 / Ex_Rnrm) * m + 1.0 / Ex_Rnrm 

2462 ) 

2463 

2464 self.bilt = aux_dict 

2465 

2466 self.bilt = aux_dict 

2467 

2468 def check_GICMod(self, verbose=None): 

2469 """ 

2470 Evaluate and report on the Risk-Modified Growth Impatience Condition. 

2471 """ 

2472 name = "GICMod" 

2473 GPFacMod = self.bilt["GPFacMod"] 

2474 result = GPFacMod < 1.0 

2475 

2476 messages = { 

2477 True: f"GPFacMod={GPFacMod:.5f} : The Risk-Modified Growth Patience Factor satisfies the Risk-Modified Growth Impatience Condition (GICMod) Þ/(G‖Ψ‖_(-1)) < 1.", 

2478 False: f"GPFacMod={GPFacMod:.5f} : The Risk-Modified Growth Patience Factor violates the Risk-Modified Growth Impatience Condition (GICMod) Þ/(G‖Ψ‖_(-1)) < 1.", 

2479 } 

2480 verbose = self.verbose if verbose is None else verbose 

2481 self.log_condition_result(name, result, messages[result], verbose) 

2482 

2483 def check_GICSdl(self, verbose=None): 

2484 """ 

2485 Evaluate and report on the Szeidl variation of the Growth Impatience Condition. 

2486 """ 

2487 name = "GICSdl" 

2488 ELogPermShk = self.bilt["ELogPermShk"] 

2489 result = np.log(self.bilt["GPFacRaw"]) < ELogPermShk 

2490 

2491 messages = { 

2492 True: f"E[log Ψ]={ELogPermShk:.5f} : The expected log permanent income shock satisfies the Szeidl Growth Impatience Condition (GICSdl) log(Þ/G) < E[log Ψ].", 

2493 False: f"E[log Ψ]={ELogPermShk:.5f} : The expected log permanent income shock violates the Szeidl Growth Impatience Condition (GICSdl) log(Þ/G) < E[log Ψ].", 

2494 } 

2495 verbose = self.verbose if verbose is None else verbose 

2496 self.log_condition_result(name, result, messages[result], verbose) 

2497 

2498 def check_GICHrm(self, verbose=None): 

2499 """ 

2500 Evaluate and report on the Harmenberg variation of the Growth Impatience Condition. 

2501 """ 

2502 name = "GICHrm" 

2503 GPFacHrm = self.bilt["GPFacHrm"] 

2504 result = GPFacHrm < 1.0 

2505 

2506 messages = { 

2507 True: f"GPFacHrm={GPFacHrm:.5f} : The Harmenberg Expected Growth Patience Factor satisfies the Harmenberg Growth Normalized Impatience Condition (GICHrm) Þ/G < exp(E[Ψlog Ψ]).", 

2508 False: f"GPFacHrm={GPFacHrm:.5f} : The Harmenberg Expected Growth Patience Factor violates the Harmenberg Growth Normalized Impatience Condition (GICHrm) Þ/G < exp(E[Ψlog Ψ]).", 

2509 } 

2510 verbose = self.verbose if verbose is None else verbose 

2511 self.log_condition_result(name, result, messages[result], verbose) 

2512 

2513 def check_GICLiv(self, verbose=None): 

2514 """ 

2515 Evaluate and report on the Mortality-Adjusted Growth Impatience Condition. 

2516 """ 

2517 name = "GICLiv" 

2518 GPFacLiv = self.bilt["GPFacLiv"] 

2519 result = GPFacLiv < 1.0 

2520 

2521 messages = { 

2522 True: f"GPFacLiv={GPFacLiv:.5f} : The Mortality-Adjusted Growth Patience Factor satisfies the Mortality-Adjusted Growth Impatience Condition (GICLiv) ℒÞ/G < 1.", 

2523 False: f"GPFacLiv={GPFacLiv:.5f} : The Mortality-Adjusted Growth Patience Factor violates the Mortality-Adjusted Growth Impatience Condition (GICLiv) ℒÞ/G < 1.", 

2524 } 

2525 verbose = self.verbose if verbose is None else verbose 

2526 self.log_condition_result(name, result, messages[result], verbose) 

2527 

2528 def check_FVAC(self, verbose=None): 

2529 """ 

2530 Evaluate and report on the Finite Value of Autarky condition in the presence of income risk. 

2531 """ 

2532 name = "FVAC" 

2533 VAFac = self.bilt["VAFac"] 

2534 result = VAFac < 1.0 

2535 

2536 messages = { 

2537 True: f"VAFac={VAFac:.5f} : The Risk-Modified Finite Value of Autarky Factor satisfies the Risk-Modified Finite Value of Autarky Condition β(G‖Ψ‖_(1-ρ))^(1-ρ) < 1.", 

2538 False: f"VAFac={VAFac:.5f} : The Risk-Modified Finite Value of Autarky Factor violates the Risk-Modified Finite Value of Autarky Condition β(G‖Ψ‖_(1-ρ))^(1-ρ) < 1.", 

2539 } 

2540 verbose = self.verbose if verbose is None else verbose 

2541 self.log_condition_result(name, result, messages[result], verbose) 

2542 

2543 def check_WRIC(self, verbose=None): 

2544 """ 

2545 Evaluate and report on the Weak Return Impatience Condition. 

2546 """ 

2547 name = "WRIC" 

2548 WRPFac = self.bilt["WRPFac"] 

2549 result = WRPFac < 1.0 

2550 

2551 messages = { 

2552 True: f"WRPFac={WRPFac:.5f} : The Weak Return Patience Factor satisfies the Weak Return Impatience Condition (WRIC) ℘ Þ/R < 1.", 

2553 False: f"WRPFac={WRPFac:.5f} : The Weak Return Patience Factor violates the Weak Return Impatience Condition (WRIC) ℘ Þ/R < 1.", 

2554 } 

2555 verbose = self.verbose if verbose is None else verbose 

2556 self.log_condition_result(name, result, messages[result], verbose) 

2557 

2558 def check_conditions(self, verbose=None): 

2559 """ 

2560 This method checks whether the instance's type satisfies various conditions. 

2561 When combinations of these conditions are satisfied, the solution to the 

2562 problem exhibits different characteristics. (For an exposition of the 

2563 conditions, see https://econ-ark.github.io/BufferStockTheory/) 

2564 

2565 Parameters 

2566 ---------- 

2567 verbose : boolean 

2568 Specifies different levels of verbosity of feedback. When False, it only reports whether the 

2569 instance's type fails to satisfy a particular condition. When True, it reports all results, i.e. 

2570 the factor values for all conditions. 

2571 

2572 Returns 

2573 ------- 

2574 None 

2575 """ 

2576 self.conditions = {} 

2577 self.bilt["conditions_report"] = "" 

2578 self.degenerate = False 

2579 verbose = self.verbose if verbose is None else verbose 

2580 

2581 # This method only checks for the conditions for infinite horizon models 

2582 # with a 1 period cycle. If these conditions are not met, we exit early. 

2583 if self.cycles != 0 or self.T_cycle > 1: 

2584 trivial_message = "No conditions report was produced because this functionality is only supported for infinite horizon models with a cycle length of 1." 

2585 self.log_condition_result(None, None, trivial_message, verbose) 

2586 if not self.quiet: 

2587 _log.info(self.bilt["conditions_report"]) 

2588 return 

2589 

2590 # Calculate some useful quantities that will be used in the condition checks 

2591 self.calc_limiting_values() 

2592 param_desc = self.describe_parameters() 

2593 self.log_condition_result(None, None, param_desc, verbose) 

2594 

2595 # Check individual conditions and add their results to the report 

2596 self.check_AIC(verbose) 

2597 self.check_RIC(verbose) 

2598 self.check_WRIC(verbose) 

2599 self.check_GICRaw(verbose) 

2600 self.check_GICMod(verbose) 

2601 self.check_GICLiv(verbose) 

2602 self.check_GICSdl(verbose) 

2603 self.check_GICHrm(verbose) 

2604 super().check_FVAC(verbose) 

2605 self.check_FVAC(verbose) 

2606 self.check_FHWC(verbose) 

2607 

2608 # Exit now if verbose output was not requested. 

2609 if not verbose: 

2610 if not self.quiet: 

2611 _log.info(self.bilt["conditions_report"]) 

2612 return 

2613 

2614 # Report on the degeneracy of the consumption function solution 

2615 if self.conditions["WRIC"] and self.conditions["FVAC"]: 

2616 degen_message = "\nBecause both the WRIC and FVAC are satisfied, the recursive solution to the infinite horizon problem represents a contraction mapping on the consumption function. Thus a non-degenerate solution exists." 

2617 degenerate = False 

2618 elif not self.conditions["WRIC"]: 

2619 degen_message = "\nBecause the WRIC is violated, the consumer is so pathologically patient that they will never consume at all. Thus the solution will be degenerate at c(m) = 0 for all m.\n" 

2620 degenerate = True 

2621 elif not self.conditions["FVAC"]: 

2622 degen_message = "\nBecause the FVAC is violated, the recursive solution to the infinite horizon problem might not be a contraction mapping, so the produced solution might not be valid. Proceed with caution." 

2623 degenerate = False 

2624 self.log_condition_result(None, None, degen_message, verbose) 

2625 self.degenerate = degenerate 

2626 

2627 # Stop here if the solution is degenerate 

2628 if degenerate: 

2629 if not self.quiet: 

2630 _log.info(self.bilt["conditions_report"]) 

2631 return 

2632 

2633 # Report on the limiting behavior of the consumption function as m goes to infinity 

2634 if self.conditions["RIC"]: 

2635 if self.conditions["FHWC"]: 

2636 RIC_message = "\nBecause both the RIC and FHWC condition are satisfied, the consumption function will approach the linear perfect foresight solution as m becomes arbitrarily large." 

2637 else: 

2638 RIC_message = "\nBecause the RIC is satisfied but the FHWC is violated, the GIC is satisfied." 

2639 else: 

2640 RIC_message = "\nBecause the RIC is violated, the FHWC condition is also violated. The consumer is pathologically impatient but has infinite expected future earnings. Thus the consumption function will not approach any linear limit as m becomes arbitrarily large, and the MPC will asymptote to zero." 

2641 self.log_condition_result(None, None, RIC_message, verbose) 

2642 

2643 # Report on whether a pseudo-steady-state exists at the individual level 

2644 if self.conditions["GICRaw"]: 

2645 GIC_message = "\nBecause the GICRaw is satisfied, there exists a pseudo-steady-state wealth ratio at which the level of wealth is expected to grow at the same rate as permanent income." 

2646 else: 

2647 GIC_message = "\nBecause the GICRaw is violated, there might not exist a pseudo-steady-state wealth ratio at which the level of wealth is expected to grow at the same rate as permanent income." 

2648 self.log_condition_result(None, None, GIC_message, verbose) 

2649 

2650 # Report on whether a target wealth ratio exists at the individual level 

2651 if self.conditions["GICMod"]: 

2652 GICMod_message = "\nBecause the GICMod is satisfied, expected growth of the ratio of market resources to permanent income is less than one as market resources become arbitrarily large. Hence the consumer has a target ratio of market resources to permanent income." 

2653 else: 

2654 GICMod_message = "\nBecause the GICMod is violated, expected growth of the ratio of market resources to permanent income exceeds one as market resources go to infinity. Hence the consumer might not have a target ratio of market resources to permanent income." 

2655 self.log_condition_result(None, None, GICMod_message, verbose) 

2656 

2657 # Report on whether a target level of wealth exists at the aggregate level 

2658 if self.conditions["GICLiv"]: 

2659 GICLiv_message = "\nBecause the GICLiv is satisfied, a target ratio of aggregate market resources to aggregate permanent income exists." 

2660 else: 

2661 GICLiv_message = "\nBecause the GICLiv is violated, a target ratio of aggregate market resources to aggregate permanent income might not exist." 

2662 self.log_condition_result(None, None, GICLiv_message, verbose) 

2663 

2664 # Report on whether invariant distributions exist 

2665 if self.conditions["GICSdl"]: 

2666 GICSdl_message = "\nBecause the GICSdl is satisfied, there exist invariant distributions of permanent income-normalized variables." 

2667 else: 

2668 GICSdl_message = "\nBecause the GICSdl is violated, there do not exist invariant distributions of permanent income-normalized variables." 

2669 self.log_condition_result(None, None, GICSdl_message, verbose) 

2670 

2671 # Report on whether blah blah 

2672 if self.conditions["GICHrm"]: 

2673 GICHrm_message = "\nBecause the GICHrm is satisfied, there exists a target ratio of the individual market resources to permanent income, under the permanent-income-neutral measure." 

2674 else: 

2675 GICHrm_message = "\nBecause the GICHrm is violated, there does not exist a target ratio of the individual market resources to permanent income, under the permanent-income-neutral measure.." 

2676 self.log_condition_result(None, None, GICHrm_message, verbose) 

2677 

2678 if not self.quiet: 

2679 _log.info(self.bilt["conditions_report"]) 

2680 

2681 

2682############################################################################### 

2683 

2684# Specify default parameters used in "kinked R" model 

2685 

2686KinkedRconsumerType_IncShkDstn_default = IndShockConsumerType_IncShkDstn_default.copy() 

2687KinkedRconsumerType_aXtraGrid_default = IndShockConsumerType_aXtraGrid_default.copy() 

2688KinkedRconsumerType_kNrmInitDstn_default = ( 

2689 IndShockConsumerType_kNrmInitDstn_default.copy() 

2690) 

2691KinkedRconsumerType_pLvlInitDstn_default = ( 

2692 IndShockConsumerType_pLvlInitDstn_default.copy() 

2693) 

2694 

2695KinkedRconsumerType_solving_default = IndShockConsumerType_solving_default.copy() 

2696KinkedRconsumerType_solving_default.update( 

2697 { 

2698 "Rboro": 1.20, # Interest factor on assets when borrowing, a < 0 

2699 "Rsave": 1.02, # Interest factor on assets when saving, a > 0 

2700 "BoroCnstArt": None, # Kinked R only matters if borrowing is allowed 

2701 } 

2702) 

2703del KinkedRconsumerType_solving_default["Rfree"] 

2704 

2705KinkedRconsumerType_simulation_default = IndShockConsumerType_simulation_default.copy() 

2706 

2707KinkedRconsumerType_defaults = {} 

2708KinkedRconsumerType_defaults.update( 

2709 KinkedRconsumerType_IncShkDstn_default 

2710) # Fill with some parameters 

2711KinkedRconsumerType_defaults.update(KinkedRconsumerType_pLvlInitDstn_default) 

2712KinkedRconsumerType_defaults.update(KinkedRconsumerType_kNrmInitDstn_default) 

2713KinkedRconsumerType_defaults.update(KinkedRconsumerType_aXtraGrid_default) 

2714KinkedRconsumerType_defaults.update(KinkedRconsumerType_solving_default) 

2715KinkedRconsumerType_defaults.update(KinkedRconsumerType_simulation_default) 

2716init_kinked_R = KinkedRconsumerType_defaults 

2717 

2718 

2719class KinkedRconsumerType(IndShockConsumerType): 

2720 r""" 

2721 A consumer type based on IndShockConsumerType, with different 

2722 interest rates for saving (:math:`\mathsf{R}_{save}`) and borrowing 

2723 (:math:`\mathsf{R}_{boro}`). 

2724 

2725 .. math:: 

2726 \newcommand{\CRRA}{\rho} 

2727 \newcommand{\DiePrb}{\mathsf{D}} 

2728 \newcommand{\PermGroFac}{\Gamma} 

2729 \newcommand{\Rfree}{\mathsf{R}} 

2730 \newcommand{\DiscFac}{\beta} 

2731 \begin{align*} 

2732 v_t(m_t) &= \max_{c_t} u(c_t) + \DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[(\PermGroFac_{t+1}\psi_{t+1})^{1-\CRRA} v_{t+1}(m_{t+1}) \right], \\ 

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

2734 a_t &= m_t - c_t, \\ 

2735 a_t &\geq \underline{a}, \\ 

2736 m_{t+1} &= \Rfree_t/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}, \\ 

2737 \Rfree_t &= \begin{cases} 

2738 \Rfree_{boro} & \text{if } a_t < 0\\ 

2739 \Rfree_{save} & \text{if } a_t \geq 0, 

2740 \end{cases}\\ 

2741 \Rfree_{boro} &> \Rfree_{save}, \\ 

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

2743 \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1.\\ 

2744 u(c) &= \frac{c^{1-\CRRA}}{1-\CRRA} \\ 

2745 \end{align*} 

2746 

2747 Constructors 

2748 ------------ 

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

2750 The agent's income shock distributions. 

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

2752 aXtraGrid: Constructor 

2753 The agent's asset grid. 

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

2755 

2756 Solving Parameters 

2757 ------------------ 

2758 cycles: int 

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

2760 T_cycle: int 

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

2762 CRRA: float, :math:`\rho` 

2763 Coefficient of Relative Risk Aversion. 

2764 Rboro: float, :math:`\mathsf{R}_{boro}` 

2765 Risk Free interest rate when assets are negative. 

2766 Rsave: float, :math:`\mathsf{R}_{save}` 

2767 Risk Free interest rate when assets are positive. 

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

2769 Intertemporal discount factor. 

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

2771 Survival probability after each period. 

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

2773 Permanent income growth factor. 

2774 BoroCnstArt: float, :math:`\underline{a}` 

2775 The minimum Asset/Perminant Income ratio, None to ignore. 

2776 vFuncBool: bool 

2777 Whether to calculate the value function during solution. 

2778 CubicBool: bool 

2779 Whether to use cubic spline interpoliation. 

2780 

2781 Simulation Parameters 

2782 --------------------- 

2783 AgentCount: int 

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

2785 T_age: int 

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

2787 T_sim: int, required for simulation 

2788 Number of periods to simulate. 

2789 track_vars: list[strings] 

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

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

2792 

2793 PermShk is the agent's permanent income shock 

2794 

2795 TranShk is the agent's transitory income shock 

2796 

2797 aLvl is the nominal asset level 

2798 

2799 aNrm is the normalized assets 

2800 

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

2802 

2803 cNrm is the normalized consumption 

2804 

2805 mNrm is the normalized market resources 

2806 

2807 pLvl is the permanent income level 

2808 

2809 who_dies is the array of which agents died 

2810 kLogInitMean: float 

2811 Mean of Log initial Normalized Assets. 

2812 kLogInitStd: float 

2813 Std of Log initial Normalized Assets. 

2814 pLogInitMean: float 

2815 Mean of Log initial permanent income. 

2816 pLogInitStd: float 

2817 Std of Log initial permanent income. 

2818 PermGroFacAgg: float 

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

2820 PerfMITShk: boolean 

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

2822 NewbornTransShk: boolean 

2823 Whether Newborns have transitory shock. 

2824 

2825 Attributes 

2826 ---------- 

2827 solution: list[Consumer solution object] 

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

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

2830 

2831 Visit :class:`HARK.ConsumptionSaving.ConsIndShockModel.ConsumerSolution` for more information about the solution. 

2832 history: Dict[Array] 

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

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

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

2836 """ 

2837 

2838 IncShkDstn_defaults = KinkedRconsumerType_IncShkDstn_default 

2839 aXtraGrid_defaults = KinkedRconsumerType_aXtraGrid_default 

2840 solving_defaults = KinkedRconsumerType_solving_default 

2841 simulation_defaults = KinkedRconsumerType_simulation_default 

2842 default_ = { 

2843 "params": KinkedRconsumerType_defaults, 

2844 "solver": solve_one_period_ConsKinkedR, 

2845 "model": "ConsKinkedR.yaml", 

2846 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

2847 } 

2848 

2849 time_inv_ = copy(IndShockConsumerType.time_inv_) 

2850 time_inv_ += ["Rboro", "Rsave"] 

2851 

2852 def calc_bounding_values(self): 

2853 """ 

2854 Calculate human wealth plus minimum and maximum MPC in an infinite 

2855 horizon model with only one period repeated indefinitely. Store results 

2856 as attributes of self. Human wealth is the present discounted value of 

2857 expected future income after receiving income this period, ignoring mort- 

2858 ality. The maximum MPC is the limit of the MPC as m --> mNrmMin. The 

2859 minimum MPC is the limit of the MPC as m --> infty. This version deals 

2860 with the different interest rates on borrowing vs saving. 

2861 

2862 Parameters 

2863 ---------- 

2864 None 

2865 

2866 Returns 

2867 ------- 

2868 None 

2869 """ 

2870 # Unpack the income distribution and get average and worst outcomes 

2871 PermShkValsNext = self.IncShkDstn[0].atoms[0] 

2872 TranShkValsNext = self.IncShkDstn[0].atoms[1] 

2873 ShkPrbsNext = self.IncShkDstn[0].pmv 

2874 IncNext = PermShkValsNext * TranShkValsNext 

2875 Ex_IncNext = np.dot(ShkPrbsNext, IncNext) 

2876 PermShkMinNext = np.min(PermShkValsNext) 

2877 TranShkMinNext = np.min(TranShkValsNext) 

2878 WorstIncNext = PermShkMinNext * TranShkMinNext 

2879 WorstIncPrb = np.sum(ShkPrbsNext[IncNext == WorstIncNext]) 

2880 # TODO: Check the math above. I think it fails for non-independent shocks 

2881 

2882 BoroCnstArt = np.inf if self.BoroCnstArt is None else self.BoroCnstArt 

2883 

2884 # Calculate human wealth and the infinite horizon natural borrowing constraint 

2885 hNrm = (Ex_IncNext * self.PermGroFac[0] / self.Rsave) / ( 

2886 1.0 - self.PermGroFac[0] / self.Rsave 

2887 ) 

2888 temp = self.PermGroFac[0] * PermShkMinNext / self.Rboro 

2889 BoroCnstNat = -TranShkMinNext * temp / (1.0 - temp) 

2890 

2891 PatFacTop = (self.DiscFac * self.LivPrb[0] * self.Rsave) ** ( 

2892 1.0 / self.CRRA 

2893 ) / self.Rsave 

2894 PatFacBot = (self.DiscFac * self.LivPrb[0] * self.Rboro) ** ( 

2895 1.0 / self.CRRA 

2896 ) / self.Rboro 

2897 if BoroCnstNat < BoroCnstArt: 

2898 MPCmax = 1.0 # if natural borrowing constraint is overridden by artificial one, MPCmax is 1 

2899 else: 

2900 MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * PatFacBot 

2901 MPCmin = 1.0 - PatFacTop 

2902 

2903 # Store the results as attributes of self 

2904 self.hNrm = hNrm 

2905 self.MPCmin = MPCmin 

2906 self.MPCmax = MPCmax 

2907 

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

2909 """ 

2910 Creates a "normalized Euler error" function for this instance, mapping 

2911 from market resources to "consumption error per dollar of consumption." 

2912 Stores result in attribute eulerErrorFunc as an interpolated function. 

2913 Has option to use approximate income distribution stored in self.IncShkDstn 

2914 or to use a (temporary) very dense approximation. 

2915 

2916 SHOULD BE INHERITED FROM ConsIndShockModel 

2917 

2918 Parameters 

2919 ---------- 

2920 mMax : float 

2921 Maximum normalized market resources for the Euler error function. 

2922 approx_inc_dstn : Boolean 

2923 Indicator for whether to use the approximate discrete income distri- 

2924 bution stored in self.IncShkDstn[0], or to use a very accurate 

2925 discrete approximation instead. When True, uses approximation in 

2926 IncShkDstn; when False, makes and uses a very dense approximation. 

2927 

2928 Returns 

2929 ------- 

2930 None 

2931 """ 

2932 raise NotImplementedError() 

2933 

2934 def get_Rport(self): 

2935 """ 

2936 Returns an array of size self.AgentCount with self.Rboro or self.Rsave in 

2937 each entry, based on whether self.aNrmNow >< 0. This represents the risk- 

2938 free portfolio return in this model. 

2939 

2940 Parameters 

2941 ---------- 

2942 None 

2943 

2944 Returns 

2945 ------- 

2946 RfreeNow : np.array 

2947 Array of size self.AgentCount with risk free interest rate for each agent. 

2948 """ 

2949 RfreeNow = self.Rboro * np.ones(self.AgentCount) 

2950 RfreeNow[self.state_prev["aNrm"] > 0] = self.Rsave 

2951 return RfreeNow 

2952 

2953 def check_conditions(self, verbose): 

2954 """ 

2955 This empty method overwrites the version inherited from its parent class, 

2956 IndShockConsumerType. The condition checks are not appropriate when Rfree 

2957 has multiple values. 

2958 

2959 Parameters 

2960 ---------- 

2961 None 

2962 

2963 Returns 

2964 ------- 

2965 None 

2966 """ 

2967 pass 

2968 

2969 

2970############################################################################### 

2971 

2972# Make a dictionary to specify a lifecycle consumer with a finite horizon 

2973 

2974# Main calibration characteristics 

2975birth_age = 25 

2976death_age = 90 

2977adjust_infl_to = 1992 

2978# Use income estimates from Cagetti (2003) for High-school graduates 

2979education = "HS" 

2980income_calib = Cagetti_income[education] 

2981 

2982# Income specification 

2983income_params = parse_income_spec( 

2984 age_min=birth_age, 

2985 age_max=death_age, 

2986 adjust_infl_to=adjust_infl_to, 

2987 **income_calib, 

2988 SabelhausSong=True, 

2989) 

2990 

2991# Initial distribution of wealth and permanent income 

2992dist_params = income_wealth_dists_from_scf( 

2993 base_year=adjust_infl_to, age=birth_age, education=education, wave=1995 

2994) 

2995 

2996# We need survival probabilities only up to death_age-1, because survival 

2997# probability at death_age is 1. 

2998liv_prb = parse_ssa_life_table( 

2999 female=False, cross_sec=True, year=2004, age_min=birth_age, age_max=death_age 

3000) 

3001 

3002# Parameters related to the number of periods implied by the calibration 

3003time_params = parse_time_params(age_birth=birth_age, age_death=death_age) 

3004 

3005# Update all the new parameters 

3006init_lifecycle = copy(init_idiosyncratic_shocks) 

3007del init_lifecycle["constructors"] 

3008init_lifecycle.update(time_params) 

3009init_lifecycle.update(dist_params) 

3010# Note the income specification overrides the pLvlInitMean from the SCF. 

3011init_lifecycle.update(income_params) 

3012init_lifecycle.update({"LivPrb": liv_prb}) 

3013init_lifecycle["Rfree"] = init_lifecycle["T_cycle"] * init_lifecycle["Rfree"] 

3014 

3015# Make a dictionary to specify an infinite consumer with a four period cycle 

3016init_cyclical = copy(init_idiosyncratic_shocks) 

3017init_cyclical["PermGroFac"] = [1.1, 1.082251, 2.8, 0.3] 

3018init_cyclical["PermShkStd"] = [0.1, 0.1, 0.1, 0.1] 

3019init_cyclical["TranShkStd"] = [0.1, 0.1, 0.1, 0.1] 

3020init_cyclical["LivPrb"] = 4 * [0.98] 

3021init_cyclical["Rfree"] = 4 * [1.03] 

3022init_cyclical["T_cycle"] = 4 

3023 

3024# Make dictionaries based on Carroll QJE (1997) lifecycle specifications 

3025buffer_stock_lifecycle_base = { 

3026 "CRRA": 2.0, 

3027 "DiscFac": 0.96, 

3028 "PermGroFacAgg": 1.02, 

3029 "kLogInitMean": -1000.0, 

3030 "kLogInitStd": 0.0, 

3031 "pLogInitStd": 0.0, 

3032 "Rfree": 49 * [1.00], 

3033 "PermShkStd": 40 * [0.1] + 9 * [0.0], 

3034 "TranShkStd": 40 * [0.1] + 9 * [0.0], 

3035 "UnempPrb": 0.005, 

3036 "IncUnemp": 0.0, 

3037 "UnempPrbRet": 0.0005, 

3038 "IncUnempRet": 0.0, 

3039 "LivPrb": 49 * [1.0], 

3040 "T_cycle": 49, 

3041 "T_retire": 40, 

3042 "T_age": 50, 

3043 "AgentCount": 10000, 

3044 "cycles": 1, 

3045} 

3046 

3047unskilled_update = { 

3048 "pLogInitMean": np.log(1 / 1.03), 

3049 "PermGroFac": 14 * [1.03] + 25 * [1.0] + [0.7] + 9 * [1.0], 

3050} 

3051 

3052operative_update = { 

3053 "pLogInitMean": np.log(1 / 1.025), 

3054 "PermGroFac": 24 * [1.025] + 15 * [1.01] + [0.7] + 9 * [1.0], 

3055} 

3056 

3057manager_update = { 

3058 "pLogInitMean": np.log(1 / 1.03), 

3059 "PermGroFac": 29 * [1.03] + 10 * [0.99] + [0.7] + 9 * [1.0], 

3060} 

3061 

3062# Carroll QJE (1997) life-cycle calibration for unskilled workers 

3063buffer_stock_lifecycle_unskilled = copy(buffer_stock_lifecycle_base) 

3064buffer_stock_lifecycle_unskilled.update(unskilled_update) 

3065 

3066# Carroll QJE (1997) life-cycle calibration for operative workers 

3067buffer_stock_lifecycle_operative = copy(buffer_stock_lifecycle_base) 

3068buffer_stock_lifecycle_operative.update(operative_update) 

3069 

3070# Carroll QJE (1997) life-cycle calibration for managerial workers 

3071buffer_stock_lifecycle_manager = copy(buffer_stock_lifecycle_base) 

3072buffer_stock_lifecycle_manager.update(manager_update)