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

160 statements  

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

1""" 

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

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

4bequest motive, and income shocks 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. #todo 

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 deepcopy 

17 

18import numpy as np 

19from interpolation import interp 

20from numba import njit 

21from quantecon.optimize import newton_secant 

22 

23from HARK import make_one_period_oo_solver 

24from HARK.ConsumptionSaving.ConsIndShockModel import ( 

25 ConsumerSolution, 

26 IndShockConsumerType, 

27 PerfForesightConsumerType, 

28 init_perfect_foresight, 

29 init_idiosyncratic_shocks, 

30) 

31from HARK.ConsumptionSaving.LegacyOOsolvers import ( 

32 ConsIndShockSolverBasic, 

33 ConsPerfForesightSolver, 

34) 

35from HARK.interpolation import ( 

36 CubicInterp, 

37 LinearInterp, 

38 LowerEnvelope, 

39 MargValueFuncCRRA, 

40 MargMargValueFuncCRRA, 

41 ValueFuncCRRA, 

42) 

43from HARK.metric import MetricObject 

44from HARK.numba_tools import ( 

45 CRRAutility, 

46 CRRAutility_inv, 

47 CRRAutility_invP, 

48 CRRAutilityP, 

49 CRRAutilityP_inv, 

50 CRRAutilityP_invP, 

51 CRRAutilityPP, 

52 cubic_interp_fast, 

53 linear_interp_deriv_fast, 

54 linear_interp_fast, 

55) 

56from HARK.utilities import NullFunc 

57 

58__all__ = [ 

59 "PerfForesightSolution", 

60 "IndShockSolution", 

61 "PerfForesightConsumerTypeFast", 

62 "IndShockConsumerTypeFast", 

63] 

64 

65utility = CRRAutility 

66utilityP = CRRAutilityP 

67utilityPP = CRRAutilityPP 

68utilityP_inv = CRRAutilityP_inv 

69utility_invP = CRRAutility_invP 

70utility_inv = CRRAutility_inv 

71utilityP_invP = CRRAutilityP_invP 

72 

73 

74# ===================================================================== 

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

76# ===================================================================== 

77 

78 

79class PerfForesightSolution(MetricObject): 

80 r""" 

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

82 perfect foresight problem. 

83 

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

85 by permanent income. 

86 

87 Parameters 

88 ---------- 

89 mNrm: np.array 

90 (Normalized) corresponding market resource points for interpolation. 

91 cNrm : np.array 

92 (Normalized) consumption points for interpolation. 

93 vFuncNvrsSlope: float 

94 Constant slope of inverse value vFuncNvrs 

95 mNrmMin : float 

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

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

98 hNrm : float 

99 Human wealth after receiving income this period: PDV of all future 

100 income, ignoring mortality. 

101 MPCmin : float 

102 Infimum of the marginal propensity to consume this period. 

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

104 MPCmax : float 

105 Supremum of the marginal propensity to consume this period. 

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

107 """ 

108 

109 distance_criteria = ["cNrm", "mNrm"] 

110 

111 def __init__( 

112 self, 

113 mNrm=np.array([0.0, 1.0]), 

114 cNrm=np.array([0.0, 1.0]), 

115 vFuncNvrsSlope=0.0, 

116 mNrmMin=0.0, 

117 hNrm=0.0, 

118 MPCmin=1.0, 

119 MPCmax=1.0, 

120 ): 

121 self.mNrm = mNrm 

122 self.cNrm = cNrm 

123 self.vFuncNvrsSlope = vFuncNvrsSlope 

124 self.mNrmMin = mNrmMin 

125 self.hNrm = hNrm 

126 self.MPCmin = MPCmin 

127 self.MPCmax = MPCmax 

128 

129 

130class IndShockSolution(MetricObject): 

131 """ 

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

133 idiosyncratic shocks to permanent and transitory income problem. 

134 

135 Parameters 

136 ---------- 

137 mNrm: np.array 

138 (Normalized) corresponding market resource points for interpolation. 

139 cNrm : np.array 

140 (Normalized) consumption points for interpolation. 

141 vFuncNvrsSlope: float 

142 Constant slope of inverse value ``vFuncNvrs`` 

143 mNrmMin : float 

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

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

146 hNrm : float 

147 Human wealth after receiving income this period: PDV of all future 

148 income, ignoring mortality. 

149 MPCmin : float 

150 Infimum of the marginal propensity to consume this period. 

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

152 MPCmax : float 

153 Supremum of the marginal propensity to consume this period. 

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

155 """ 

156 

157 distance_criteria = ["cNrm", "mNrm", "mNrmMin"] 

158 

159 def __init__( 

160 self, 

161 mNrm=np.linspace(0, 1), 

162 cNrm=np.linspace(0, 1), 

163 cFuncLimitIntercept=None, 

164 cFuncLimitSlope=None, 

165 mNrmMin=0.0, 

166 hNrm=0.0, 

167 MPCmin=1.0, 

168 MPCmax=1.0, 

169 Ex_IncNext=0.0, 

170 MPC=None, 

171 mNrmGrid=None, 

172 vNvrs=None, 

173 vNvrsP=None, 

174 MPCminNvrs=None, 

175 ): 

176 self.mNrm = mNrm 

177 self.cNrm = cNrm 

178 self.cFuncLimitIntercept = cFuncLimitIntercept 

179 self.cFuncLimitSlope = cFuncLimitSlope 

180 self.mNrmMin = mNrmMin 

181 self.hNrm = hNrm 

182 self.MPCmin = MPCmin 

183 self.MPCmax = MPCmax 

184 self.Ex_IncNext = Ex_IncNext 

185 self.mNrmGrid = mNrmGrid 

186 self.vNvrs = vNvrs 

187 self.MPCminNvrs = MPCminNvrs 

188 self.MPC = MPC 

189 self.vNvrsP = vNvrsP 

190 

191 

192# ===================================================================== 

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

194# ===================================================================== 

195 

196 

197def make_solution_terminal_fast(solution_terminal_class, CRRA): 

198 solution_terminal = solution_terminal_class() 

199 cFunc_terminal = LinearInterp([0.0, 1.0], [0.0, 1.0]) 

200 solution_terminal.cFunc = cFunc_terminal # c=m at t=T 

201 solution_terminal.vFunc = ValueFuncCRRA(cFunc_terminal, CRRA) 

202 solution_terminal.vPfunc = MargValueFuncCRRA(cFunc_terminal, CRRA) 

203 solution_terminal.vPPfunc = MargMargValueFuncCRRA(cFunc_terminal, CRRA) 

204 solution_terminal.MPC = np.array([1.0, 1.0]) 

205 solution_terminal.MPCminNvrs = 0.0 

206 solution_terminal.vNvrs = utility(np.linspace(0.0, 1.0), CRRA) 

207 solution_terminal.vNvrsP = utilityP(np.linspace(0.0, 1.0), CRRA) 

208 solution_terminal.mNrmGrid = np.linspace(0.0, 1.0) 

209 return solution_terminal 

210 

211 

212@njit(cache=True) 

213def _find_mNrmStE(m, Rfree, PermGroFac, mNrm, cNrm, Ex_IncNext): # pragma: nocover 

214 # Make a linear function of all combinations of c and m that yield mNext = mNow 

215 mZeroChange = (1.0 - PermGroFac / Rfree) * m + (PermGroFac / Rfree) * Ex_IncNext 

216 

217 # Find the steady state level of market resources 

218 res = interp(mNrm, cNrm, m) - mZeroChange 

219 # A zero of this is SS market resources 

220 return res 

221 

222 

223# @njit(cache=True) can't cache because of use of globals, perhaps newton_secant? 

224@njit 

225def _add_mNrmStENumba( 

226 Rfree, PermGroFac, mNrm, cNrm, mNrmMin, Ex_IncNext, _find_mNrmStE 

227): # pragma: nocover 

228 """ 

229 Finds steady state (normalized) market resources and adds it to the 

230 solution. This is the level of market resources such that the expectation 

231 of market resources in the next period is unchanged. This value doesn't 

232 necessarily exist. 

233 """ 

234 

235 # Minimum market resources plus next income is okay starting guess 

236 m_init_guess = mNrmMin + Ex_IncNext 

237 

238 mNrmStE = newton_secant( 

239 _find_mNrmStE, 

240 m_init_guess, 

241 args=(Rfree, PermGroFac, mNrm, cNrm, Ex_IncNext), 

242 disp=False, 

243 ) 

244 

245 if mNrmStE.converged: 

246 return mNrmStE.root 

247 else: 

248 return None 

249 

250 

251@njit(cache=True, parallel=True) 

252def _solveConsPerfForesightNumba( 

253 DiscFac, 

254 LivPrb, 

255 CRRA, 

256 Rfree, 

257 PermGroFac, 

258 BoroCnstArt, 

259 MaxKinks, 

260 mNrmNext, 

261 cNrmNext, 

262 hNrmNext, 

263 MPCminNext, 

264): # pragma: nocover 

265 """ 

266 Makes the (linear) consumption function for this period. 

267 """ 

268 

269 DiscFacEff = DiscFac * LivPrb 

270 

271 # Calculate human wealth this period 

272 hNrmNow = (PermGroFac / Rfree) * (hNrmNext + 1.0) 

273 

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

275 APF = ((Rfree * DiscFacEff) ** (1.0 / CRRA)) / Rfree 

276 MPCmin = 1.0 / (1.0 + APF / MPCminNext) 

277 

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

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

280 mNrmNext = mNrmNext[:-1] 

281 cNrmNext = cNrmNext[:-1] 

282 

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

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

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

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

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

288 mNrmNow = aNrmNow + cNrmNow 

289 

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

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

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

293 cNrmNow = np.append(cNrmNow, cNrmNow[-1] + MPCmin) 

294 

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

296 # unconstrained consumption functions. 

297 if BoroCnstArt > mNrmNow[0]: 

298 # Find the highest index where constraint binds 

299 cNrmCnst = mNrmNow - BoroCnstArt 

300 CnstBinds = cNrmCnst < cNrmNow 

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

302 

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

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

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

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

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

308 m0 = mNrmNow[idx] 

309 m1 = mNrmNow[idx + 1] 

310 alpha = d0 / (d0 + d1) 

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

312 

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

314 cCrit = mCrit - BoroCnstArt 

315 mNrmNow = np.concatenate( 

316 (np.array([BoroCnstArt, mCrit]), mNrmNow[(idx + 1) :]) 

317 ) 

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

319 

320 else: 

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

322 # that characterize the consumption function: the artificial borrowing 

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

324 mXtra = (cNrmNow[-1] - cNrmCnst[-1]) / (1.0 - MPCmin) 

325 mCrit = mNrmNow[-1] + mXtra 

326 cCrit = mCrit - BoroCnstArt 

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

328 cNrmNow = np.array([0.0, cCrit, cCrit + MPCmin]) 

329 

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

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

332 if mNrmNow.size > MaxKinks: 

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

334 cNrmNow = np.concatenate((cNrmNow[:-2], np.array([cNrmNow[-3] + MPCmin]))) 

335 

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

337 MPCmax = (cNrmNow[1] - cNrmNow[0]) / (mNrmNow[1] - mNrmNow[0]) 

338 

339 # Add attributes to enable calculation of steady state market resources. 

340 # Relabeling for compatibility with add_mNrmStE 

341 mNrmMinNow = mNrmNow[0] 

342 

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

344 vFuncNvrsSlope = MPCmin ** (-CRRA / (1.0 - CRRA)) 

345 

346 return ( 

347 mNrmNow, 

348 cNrmNow, 

349 vFuncNvrsSlope, 

350 mNrmMinNow, 

351 hNrmNow, 

352 MPCmin, 

353 MPCmax, 

354 ) 

355 

356 

357class ConsPerfForesightSolverFast(ConsPerfForesightSolver): 

358 """ 

359 A class for solving a one period perfect foresight consumption-saving problem. 

360 An instance of this class is created by the function solvePerfForesight in each period. 

361 """ 

362 

363 def solve(self): 

364 """ 

365 Solves the one period perfect foresight consumption-saving problem. 

366 

367 Parameters 

368 ---------- 

369 None 

370 

371 Returns 

372 ------- 

373 solution : PerfForesightSolution 

374 The solution to this period's problem. 

375 """ 

376 

377 # Use a local value of BoroCnstArt to prevent comparing None and float below. 

378 if self.BoroCnstArt is None: 

379 BoroCnstArt = -np.inf 

380 else: 

381 BoroCnstArt = self.BoroCnstArt 

382 

383 ( 

384 self.mNrmNow, 

385 self.cNrmNow, 

386 self.vFuncNvrsSlope, 

387 self.mNrmMinNow, 

388 self.hNrmNow, 

389 self.MPCmin, 

390 self.MPCmax, 

391 ) = _solveConsPerfForesightNumba( 

392 self.DiscFac, 

393 self.LivPrb, 

394 self.CRRA, 

395 self.Rfree, 

396 self.PermGroFac, 

397 BoroCnstArt, 

398 self.MaxKinks, 

399 self.solution_next.mNrm, 

400 self.solution_next.cNrm, 

401 self.solution_next.hNrm, 

402 self.solution_next.MPCmin, 

403 ) 

404 

405 solution = PerfForesightSolution( 

406 self.mNrmNow, 

407 self.cNrmNow, 

408 self.vFuncNvrsSlope, 

409 self.mNrmMinNow, 

410 self.hNrmNow, 

411 self.MPCmin, 

412 self.MPCmax, 

413 ) 

414 return solution 

415 

416 

417@njit(cache=True) 

418def _np_tile(A, reps): # pragma: nocover 

419 return A.repeat(reps[0]).reshape(A.size, -1).transpose() 

420 

421 

422@njit(cache=True) 

423def _np_insert(arr, obj, values, axis=-1): # pragma: nocover 

424 return np.append(np.array(values), arr) 

425 

426 

427@njit(cache=True, parallel=True) 

428def _prepare_to_solveConsIndShockNumba( 

429 DiscFac, 

430 LivPrb, 

431 CRRA, 

432 Rfree, 

433 PermGroFac, 

434 BoroCnstArt, 

435 aXtraGrid, 

436 hNrmNext, 

437 mNrmMinNext, 

438 MPCminNext, 

439 MPCmaxNext, 

440 PermShkValsNext, 

441 TranShkValsNext, 

442 ShkPrbsNext, 

443): # pragma: nocover 

444 """ 

445 Unpacks some of the inputs (and calculates simple objects based on them), 

446 storing the results in self for use by other methods. These include: 

447 income shocks and probabilities, next period's marginal value function 

448 (etc), the probability of getting the worst income shock next period, 

449 the patience factor, human wealth, and the bounding MPCs. 

450 

451 Defines the constrained portion of the consumption function as cFuncNowCnst, 

452 an attribute of self. Uses the artificial and natural borrowing constraints. 

453 

454 """ 

455 

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

457 PermShkMinNext = np.min(PermShkValsNext) 

458 TranShkMinNext = np.min(TranShkValsNext) 

459 WorstIncPrb = np.sum( 

460 ShkPrbsNext[ 

461 (PermShkValsNext * TranShkValsNext) == (PermShkMinNext * TranShkMinNext) 

462 ] 

463 ) 

464 

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

466 APF = ((Rfree * DiscFacEff) ** (1.0 / CRRA)) / Rfree 

467 MPCminNow = 1.0 / (1.0 + APF / MPCminNext) 

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

469 hNrmNow = PermGroFac / Rfree * (Ex_IncNext + hNrmNext) 

470 MPCmaxNow = 1.0 / (1.0 + (WorstIncPrb ** (1.0 / CRRA)) * APF / MPCmaxNext) 

471 

472 cFuncLimitIntercept = MPCminNow * hNrmNow 

473 cFuncLimitSlope = MPCminNow 

474 

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

476 BoroCnstNat = (mNrmMinNext - TranShkMinNext) * (PermGroFac * PermShkMinNext) / Rfree 

477 

478 # Note: need to be sure to handle BoroCnstArt==None appropriately. 

479 # In Py2, this would evaluate to 5.0: np.max([None, 5.0]). 

480 # However in Py3, this raises a TypeError. Thus here we need to directly 

481 # address the situation in which BoroCnstArt == None: 

482 if BoroCnstArt is None: 

483 mNrmMinNow = BoroCnstNat 

484 else: 

485 mNrmMinNow = np.max(np.array([BoroCnstNat, BoroCnstArt])) 

486 if BoroCnstNat < mNrmMinNow: 

487 MPCmaxEff = 1.0 # If actually constrained, MPC near limit is 1 

488 else: 

489 MPCmaxEff = MPCmaxNow 

490 

491 """ 

492 Prepare to calculate end-of-period marginal value by creating an array 

493 of market resources that the agent could have next period, considering 

494 the grid of end-of-period assets and the distribution of shocks he might 

495 experience next period. 

496 """ 

497 

498 # We define aNrmNow all the way from BoroCnstNat up to max(self.aXtraGrid) 

499 # even if BoroCnstNat < BoroCnstArt, so we can construct the consumption 

500 # function as the lower envelope of the (by the artificial borrowing con- 

501 # straint) uconstrained consumption function, and the artificially con- 

502 # strained consumption function. 

503 aNrmNow = np.asarray(aXtraGrid) + BoroCnstNat 

504 ShkCount = TranShkValsNext.size 

505 aNrm_temp = _np_tile(aNrmNow, (ShkCount, 1)) 

506 

507 # Tile arrays of the income shocks and put them into useful shapes 

508 aNrmCount = aNrmNow.shape[0] 

509 PermShkVals_temp = (_np_tile(PermShkValsNext, (aNrmCount, 1))).transpose() 

510 TranShkVals_temp = (_np_tile(TranShkValsNext, (aNrmCount, 1))).transpose() 

511 ShkPrbs_temp = (_np_tile(ShkPrbsNext, (aNrmCount, 1))).transpose() 

512 

513 # Get cash on hand next period 

514 mNrmNext = Rfree / (PermGroFac * PermShkVals_temp) * aNrm_temp + TranShkVals_temp 

515 # CDC 20191205: This should be divided by LivPrb[0] for Blanchard insurance 

516 

517 return ( 

518 DiscFacEff, 

519 BoroCnstNat, 

520 cFuncLimitIntercept, 

521 cFuncLimitSlope, 

522 mNrmMinNow, 

523 hNrmNow, 

524 MPCminNow, 

525 MPCmaxNow, 

526 MPCmaxEff, 

527 Ex_IncNext, 

528 mNrmNext, 

529 PermShkVals_temp, 

530 ShkPrbs_temp, 

531 aNrmNow, 

532 ) 

533 

534 

535@njit(cache=True, parallel=True) 

536def _solveConsIndShockLinearNumba( 

537 mNrmMinNext, 

538 mNrmNext, 

539 CRRA, 

540 mNrmUnc, 

541 cNrmUnc, 

542 DiscFacEff, 

543 Rfree, 

544 PermGroFac, 

545 PermShkVals_temp, 

546 ShkPrbs_temp, 

547 aNrmNow, 

548 BoroCnstNat, 

549 cFuncInterceptNext, 

550 cFuncSlopeNext, 

551): # pragma: nocover 

552 """ 

553 Calculate end-of-period marginal value of assets at each point in aNrmNow. 

554 Does so by taking a weighted sum of next period marginal values across 

555 income shocks (in a preconstructed grid self.mNrmNext). 

556 """ 

557 

558 mNrmCnst = np.array([mNrmMinNext, mNrmMinNext + 1]) 

559 cNrmCnst = np.array([0.0, 1.0]) 

560 cFuncNextCnst = linear_interp_fast(mNrmNext.flatten(), mNrmCnst, cNrmCnst) 

561 cFuncNextUnc = linear_interp_fast( 

562 mNrmNext.flatten(), mNrmUnc, cNrmUnc, cFuncInterceptNext, cFuncSlopeNext 

563 ) 

564 cFuncNext = np.minimum(cFuncNextCnst, cFuncNextUnc) 

565 vPfuncNext = utilityP(cFuncNext, CRRA).reshape(mNrmNext.shape) 

566 

567 EndOfPrdvP = ( 

568 DiscFacEff 

569 * Rfree 

570 * PermGroFac ** (-CRRA) 

571 * np.sum(PermShkVals_temp ** (-CRRA) * vPfuncNext * ShkPrbs_temp, axis=0) 

572 ) 

573 

574 # Finds interpolation points (c,m) for the consumption function. 

575 

576 cNrmNow = utilityP_inv(EndOfPrdvP, CRRA) 

577 mNrmNow = cNrmNow + aNrmNow 

578 

579 # Limiting consumption is zero as m approaches mNrmMin 

580 cNrm = _np_insert(cNrmNow, 0, 0.0, axis=-1) 

581 mNrm = _np_insert(mNrmNow, 0, BoroCnstNat, axis=-1) 

582 

583 return (cNrm, mNrm, EndOfPrdvP) 

584 

585 

586class ConsIndShockSolverBasicFast(ConsIndShockSolverBasic): 

587 """ 

588 This class solves a single period of a standard consumption-saving problem, 

589 using linear interpolation and without the ability to calculate the value 

590 function. ConsIndShockSolver inherits from this class and adds the ability 

591 to perform cubic interpolation and to calculate the value function. 

592 

593 Note that this class does not have its own initializing method. It initial- 

594 izes the same problem in the same way as ConsIndShockSetup, from which it 

595 inherits. 

596 """ 

597 

598 def prepare_to_solve(self): 

599 """ 

600 Perform preparatory work before calculating the unconstrained consumption 

601 function. 

602 Parameters 

603 ---------- 

604 none 

605 Returns 

606 ------- 

607 none 

608 """ 

609 

610 self.ShkPrbsNext = self.IncShkDstn.pmv 

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

612 self.TranShkValsNext = self.IncShkDstn.atoms[1] 

613 

614 ( 

615 self.DiscFacEff, 

616 self.BoroCnstNat, 

617 self.cFuncLimitIntercept, 

618 self.cFuncLimitSlope, 

619 self.mNrmMinNow, 

620 self.hNrmNow, 

621 self.MPCminNow, 

622 self.MPCmaxNow, 

623 self.MPCmaxEff, 

624 self.Ex_IncNext, 

625 self.mNrmNext, 

626 self.PermShkVals_temp, 

627 self.ShkPrbs_temp, 

628 self.aNrmNow, 

629 ) = _prepare_to_solveConsIndShockNumba( 

630 self.DiscFac, 

631 self.LivPrb, 

632 self.CRRA, 

633 self.Rfree, 

634 self.PermGroFac, 

635 self.BoroCnstArt, 

636 self.aXtraGrid, 

637 self.solution_next.hNrm, 

638 self.solution_next.mNrmMin, 

639 self.solution_next.MPCmin, 

640 self.solution_next.MPCmax, 

641 self.PermShkValsNext, 

642 self.TranShkValsNext, 

643 self.ShkPrbsNext, 

644 ) 

645 

646 def solve(self): 

647 """ 

648 Solves a one period consumption saving problem with risky income. 

649 Parameters 

650 ---------- 

651 None 

652 Returns 

653 ------- 

654 solution : ConsumerSolution 

655 The solution to the one period problem. 

656 """ 

657 

658 self.cNrm, self.mNrm, self.EndOfPrdvP = _solveConsIndShockLinearNumba( 

659 self.solution_next.mNrmMin, 

660 self.mNrmNext, 

661 self.CRRA, 

662 self.solution_next.mNrm, 

663 self.solution_next.cNrm, 

664 self.DiscFacEff, 

665 self.Rfree, 

666 self.PermGroFac, 

667 self.PermShkVals_temp, 

668 self.ShkPrbs_temp, 

669 self.aNrmNow, 

670 self.BoroCnstNat, 

671 self.solution_next.cFuncLimitIntercept, 

672 self.solution_next.cFuncLimitSlope, 

673 ) 

674 

675 # Pack up the solution and return it 

676 solution = IndShockSolution( 

677 self.mNrm, 

678 self.cNrm, 

679 self.cFuncLimitIntercept, 

680 self.cFuncLimitSlope, 

681 self.mNrmMinNow, 

682 self.hNrmNow, 

683 self.MPCminNow, 

684 self.MPCmaxEff, 

685 self.Ex_IncNext, 

686 ) 

687 

688 return solution 

689 

690 

691@njit(cache=True, parallel=True) 

692def _solveConsIndShockCubicNumba( 

693 mNrmMinNext, 

694 mNrmNext, 

695 mNrmUnc, 

696 cNrmUnc, 

697 MPCNext, 

698 cFuncInterceptNext, 

699 cFuncSlopeNext, 

700 CRRA, 

701 DiscFacEff, 

702 Rfree, 

703 PermGroFac, 

704 PermShkVals_temp, 

705 ShkPrbs_temp, 

706 aNrmNow, 

707 BoroCnstNat, 

708 MPCmaxNow, 

709): # pragma: nocover 

710 mNrmCnst = np.array([mNrmMinNext, mNrmMinNext + 1]) 

711 cNrmCnst = np.array([0.0, 1.0]) 

712 cFuncNextCnst, MPCNextCnst = linear_interp_deriv_fast( 

713 mNrmNext.flatten(), mNrmCnst, cNrmCnst 

714 ) 

715 cFuncNextUnc, MPCNextUnc = cubic_interp_fast( 

716 mNrmNext.flatten(), 

717 mNrmUnc, 

718 cNrmUnc, 

719 MPCNext, 

720 cFuncInterceptNext, 

721 cFuncSlopeNext, 

722 ) 

723 

724 cFuncNext = np.where(cFuncNextCnst <= cFuncNextUnc, cFuncNextCnst, cFuncNextUnc) 

725 

726 vPfuncNext = utilityP(cFuncNext, CRRA).reshape(mNrmNext.shape) 

727 

728 EndOfPrdvP = ( 

729 DiscFacEff 

730 * Rfree 

731 * PermGroFac ** (-CRRA) 

732 * np.sum(PermShkVals_temp ** (-CRRA) * vPfuncNext * ShkPrbs_temp, axis=0) 

733 ) 

734 # Finds interpolation points (c,m) for the consumption function. 

735 

736 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA) 

737 mNrmNow = cNrmNow + aNrmNow 

738 

739 # Limiting consumption is zero as m approaches mNrmMin 

740 cNrm = _np_insert(cNrmNow, 0, 0.0, axis=-1) 

741 mNrm = _np_insert(mNrmNow, 0, BoroCnstNat, axis=-1) 

742 

743 """ 

744 Makes a cubic spline interpolation of the unconstrained consumption 

745 function for this period. 

746 """ 

747 

748 MPCinterpNext = np.where(cFuncNextCnst <= cFuncNextUnc, MPCNextCnst, MPCNextUnc) 

749 vPPfuncNext = (MPCinterpNext * utilityPP(cFuncNext, CRRA)).reshape(mNrmNext.shape) 

750 

751 EndOfPrdvPP = ( 

752 DiscFacEff 

753 * Rfree 

754 * Rfree 

755 * PermGroFac ** (-CRRA - 1.0) 

756 * np.sum(PermShkVals_temp ** (-CRRA - 1.0) * vPPfuncNext * ShkPrbs_temp, axis=0) 

757 ) 

758 dcda = EndOfPrdvPP / utilityPP(cNrm[1:], CRRA) 

759 MPC = dcda / (dcda + 1.0) 

760 MPC = _np_insert(MPC, 0, MPCmaxNow) 

761 

762 return cNrm, mNrm, MPC, EndOfPrdvP 

763 

764 

765@njit(cache=True) 

766def _cFuncCubic( 

767 aXtraGrid, mNrmMinNow, mNrmNow, cNrmNow, MPCNow, MPCminNow, hNrmNow 

768): # pragma: nocover 

769 mNrmGrid = mNrmMinNow + aXtraGrid 

770 mNrmCnst = np.array([mNrmMinNow, mNrmMinNow + 1]) 

771 cNrmCnst = np.array([0.0, 1.0]) 

772 cFuncNowCnst = linear_interp_fast(mNrmGrid.flatten(), mNrmCnst, cNrmCnst) 

773 cFuncNowUnc, MPCNowUnc = cubic_interp_fast( 

774 mNrmGrid.flatten(), mNrmNow, cNrmNow, MPCNow, MPCminNow * hNrmNow, MPCminNow 

775 ) 

776 

777 cNrmNow = np.where(cFuncNowCnst <= cFuncNowUnc, cFuncNowCnst, cFuncNowUnc) 

778 

779 return cNrmNow, mNrmGrid 

780 

781 

782@njit(cache=True) 

783def _cFuncLinear( 

784 aXtraGrid, mNrmMinNow, mNrmNow, cNrmNow, MPCminNow, hNrmNow 

785): # pragma: nocover 

786 mNrmGrid = mNrmMinNow + aXtraGrid 

787 mNrmCnst = np.array([mNrmMinNow, mNrmMinNow + 1]) 

788 cNrmCnst = np.array([0.0, 1.0]) 

789 cFuncNowCnst = linear_interp_fast(mNrmGrid.flatten(), mNrmCnst, cNrmCnst) 

790 cFuncNowUnc = linear_interp_fast( 

791 mNrmGrid.flatten(), mNrmNow, cNrmNow, MPCminNow * hNrmNow, MPCminNow 

792 ) 

793 

794 cNrmNow = np.where(cFuncNowCnst <= cFuncNowUnc, cFuncNowCnst, cFuncNowUnc) 

795 

796 return cNrmNow, mNrmGrid 

797 

798 

799@njit(cache=True) 

800def _add_vFuncNumba( 

801 mNrmNext, 

802 mNrmGridNext, 

803 vNvrsNext, 

804 vNvrsPNext, 

805 MPCminNvrsNext, 

806 hNrmNext, 

807 CRRA, 

808 PermShkVals_temp, 

809 PermGroFac, 

810 DiscFacEff, 

811 ShkPrbs_temp, 

812 EndOfPrdvP, 

813 aNrmNow, 

814 BoroCnstNat, 

815 mNrmGrid, 

816 cFuncNow, 

817 mNrmMinNow, 

818 MPCmaxEff, 

819 MPCminNow, 

820): # pragma: nocover 

821 """ 

822 Construct the end-of-period value function for this period, storing it 

823 as an attribute of self for use by other methods. 

824 """ 

825 

826 # vFunc always cubic 

827 

828 vNvrsFuncNow, _ = cubic_interp_fast( 

829 mNrmNext.flatten(), 

830 mNrmGridNext, 

831 vNvrsNext, 

832 vNvrsPNext, 

833 MPCminNvrsNext * hNrmNext, 

834 MPCminNvrsNext, 

835 ) 

836 

837 vFuncNext = utility(vNvrsFuncNow, CRRA).reshape(mNrmNext.shape) 

838 

839 VLvlNext = ( 

840 PermShkVals_temp ** (1.0 - CRRA) * PermGroFac ** (1.0 - CRRA) 

841 ) * vFuncNext 

842 EndOfPrdv = DiscFacEff * np.sum(VLvlNext * ShkPrbs_temp, axis=0) 

843 

844 # value transformed through inverse utility 

845 EndOfPrdvNvrs = utility_inv(EndOfPrdv, CRRA) 

846 EndOfPrdvNvrsP = EndOfPrdvP * utility_invP(EndOfPrdv, CRRA) 

847 EndOfPrdvNvrs = _np_insert(EndOfPrdvNvrs, 0, 0.0) 

848 

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

850 EndOfPrdvNvrsP = _np_insert(EndOfPrdvNvrsP, 0, EndOfPrdvNvrsP[0]) 

851 aNrm_temp = _np_insert(aNrmNow, 0, BoroCnstNat) 

852 

853 """ 

854 Creates the value function for this period, defined over market resources m. 

855 self must have the attribute EndOfPrdvFunc in order to execute. 

856 """ 

857 

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

859 

860 aNrmNow = mNrmGrid - cFuncNow 

861 

862 EndOfPrdvNvrsFunc, _ = cubic_interp_fast( 

863 aNrmNow, aNrm_temp, EndOfPrdvNvrs, EndOfPrdvNvrsP 

864 ) 

865 EndOfPrdvFunc = utility(EndOfPrdvNvrsFunc, CRRA) 

866 

867 vNrmNow = utility(cFuncNow, CRRA) + EndOfPrdvFunc 

868 vPnow = utilityP(cFuncNow, CRRA) 

869 

870 # Construct the beginning-of-period value function 

871 vNvrs = utility_inv(vNrmNow, CRRA) # value transformed through inverse utility 

872 vNvrsP = vPnow * utility_invP(vNrmNow, CRRA) 

873 mNrmGrid = _np_insert(mNrmGrid, 0, mNrmMinNow) 

874 vNvrs = _np_insert(vNvrs, 0, 0.0) 

875 vNvrsP = _np_insert(vNvrsP, 0, MPCmaxEff ** (-CRRA / (1.0 - CRRA))) 

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

877 

878 return ( 

879 mNrmGrid, 

880 vNvrs, 

881 vNvrsP, 

882 MPCminNvrs, 

883 ) 

884 

885 

886@njit 

887def _add_mNrmStEIndNumba( 

888 PermGroFac, 

889 Rfree, 

890 Ex_IncNext, 

891 mNrmMin, 

892 mNrm, 

893 cNrm, 

894 MPC, 

895 MPCmin, 

896 hNrm, 

897 _searchfunc, 

898): # pragma: nocover 

899 """ 

900 Finds steady state (normalized) market resources and adds it to the 

901 solution. This is the level of market resources such that the expectation 

902 of market resources in the next period is unchanged. This value doesn't 

903 necessarily exist. 

904 """ 

905 

906 # Minimum market resources plus next income is okay starting guess 

907 m_init_guess = mNrmMin + Ex_IncNext 

908 

909 mNrmStE = newton_secant( 

910 _searchfunc, 

911 m_init_guess, 

912 args=(PermGroFac, Rfree, Ex_IncNext, mNrmMin, mNrm, cNrm, MPC, MPCmin, hNrm), 

913 disp=False, 

914 ) 

915 

916 if mNrmStE.converged: 

917 return mNrmStE.root 

918 else: 

919 return None 

920 

921 

922@njit(cache=True) 

923def _find_mNrmStELinear( 

924 m, PermGroFac, Rfree, Ex_IncNext, mNrmMin, mNrm, cNrm, MPC, MPCmin, hNrm 

925): # pragma: nocover 

926 # Make a linear function of all combinations of c and m that yield mNext = mNow 

927 mZeroChange = (1.0 - PermGroFac / Rfree) * m + (PermGroFac / Rfree) * Ex_IncNext 

928 

929 mNrmCnst = np.array([mNrmMin, mNrmMin + 1]) 

930 cNrmCnst = np.array([0.0, 1.0]) 

931 cFuncNowCnst = linear_interp_fast(np.array([m]), mNrmCnst, cNrmCnst) 

932 cFuncNowUnc = linear_interp_fast(np.array([m]), mNrm, cNrm, MPCmin * hNrm, MPCmin) 

933 

934 cNrmNow = np.where(cFuncNowCnst <= cFuncNowUnc, cFuncNowCnst, cFuncNowUnc) 

935 

936 # Find the steady state level of market resources 

937 res = cNrmNow[0] - mZeroChange 

938 # A zero of this is SS market resources 

939 return res 

940 

941 

942@njit(cache=True) 

943def _find_mNrmStECubic( 

944 m, PermGroFac, Rfree, Ex_IncNext, mNrmMin, mNrm, cNrm, MPC, MPCmin, hNrm 

945): # pragma: nocover 

946 # Make a linear function of all combinations of c and m that yield mNext = mNow 

947 mZeroChange = (1.0 - PermGroFac / Rfree) * m + (PermGroFac / Rfree) * Ex_IncNext 

948 

949 mNrmCnst = np.array([mNrmMin, mNrmMin + 1]) 

950 cNrmCnst = np.array([0.0, 1.0]) 

951 cFuncNowCnst = linear_interp_fast(np.array([m]), mNrmCnst, cNrmCnst) 

952 cFuncNowUnc, MPCNowUnc = cubic_interp_fast( 

953 np.array([m]), mNrm, cNrm, MPC, MPCmin * hNrm, MPCmin 

954 ) 

955 

956 cNrmNow = np.where(cFuncNowCnst <= cFuncNowUnc, cFuncNowCnst, cFuncNowUnc) 

957 

958 # Find the steady state level of market resources 

959 res = cNrmNow[0] - mZeroChange 

960 # A zero of this is SS market resources 

961 return res 

962 

963 

964class ConsIndShockSolverFast(ConsIndShockSolverBasicFast): 

965 r""" 

966 This class solves a single period of a standard consumption-saving problem. 

967 It inherits from ConsIndShockSolverBasic, adding the ability to perform cubic 

968 interpolation and to calculate the value function. 

969 """ 

970 

971 def solve(self): 

972 """ 

973 Solves a one period consumption saving problem with risky income. 

974 Parameters 

975 ---------- 

976 None 

977 Returns 

978 ------- 

979 solution : ConsumerSolution 

980 The solution to the one period problem. 

981 """ 

982 

983 if self.CubicBool: 

984 ( 

985 self.cNrm, 

986 self.mNrm, 

987 self.MPC, 

988 self.EndOfPrdvP, 

989 ) = _solveConsIndShockCubicNumba( 

990 self.solution_next.mNrmMin, 

991 self.mNrmNext, 

992 self.solution_next.mNrm, 

993 self.solution_next.cNrm, 

994 self.solution_next.MPC, 

995 self.solution_next.cFuncLimitIntercept, 

996 self.solution_next.cFuncLimitSlope, 

997 self.CRRA, 

998 self.DiscFacEff, 

999 self.Rfree, 

1000 self.PermGroFac, 

1001 self.PermShkVals_temp, 

1002 self.ShkPrbs_temp, 

1003 self.aNrmNow, 

1004 self.BoroCnstNat, 

1005 self.MPCmaxNow, 

1006 ) 

1007 # Pack up the solution and return it 

1008 solution = IndShockSolution( 

1009 self.mNrm, 

1010 self.cNrm, 

1011 self.cFuncLimitIntercept, 

1012 self.cFuncLimitSlope, 

1013 self.mNrmMinNow, 

1014 self.hNrmNow, 

1015 self.MPCminNow, 

1016 self.MPCmaxEff, 

1017 self.Ex_IncNext, 

1018 self.MPC, 

1019 ) 

1020 else: 

1021 self.cNrm, self.mNrm, self.EndOfPrdvP = _solveConsIndShockLinearNumba( 

1022 self.solution_next.mNrmMin, 

1023 self.mNrmNext, 

1024 self.CRRA, 

1025 self.solution_next.mNrm, 

1026 self.solution_next.cNrm, 

1027 self.DiscFacEff, 

1028 self.Rfree, 

1029 self.PermGroFac, 

1030 self.PermShkVals_temp, 

1031 self.ShkPrbs_temp, 

1032 self.aNrmNow, 

1033 self.BoroCnstNat, 

1034 self.solution_next.cFuncLimitIntercept, 

1035 self.solution_next.cFuncLimitSlope, 

1036 ) 

1037 

1038 # Pack up the solution and return it 

1039 solution = IndShockSolution( 

1040 self.mNrm, 

1041 self.cNrm, 

1042 self.cFuncLimitIntercept, 

1043 self.cFuncLimitSlope, 

1044 self.mNrmMinNow, 

1045 self.hNrmNow, 

1046 self.MPCminNow, 

1047 self.MPCmaxEff, 

1048 self.Ex_IncNext, 

1049 ) 

1050 

1051 if self.vFuncBool: 

1052 if self.CubicBool: 

1053 self.cFuncNow, self.mNrmGrid = _cFuncCubic( 

1054 self.aXtraGrid, 

1055 self.mNrmMinNow, 

1056 self.mNrm, 

1057 self.cNrm, 

1058 self.MPC, 

1059 self.MPCminNow, 

1060 self.hNrmNow, 

1061 ) 

1062 else: 

1063 self.cFuncNow, self.mNrmGrid = _cFuncLinear( 

1064 self.aXtraGrid, 

1065 self.mNrmMinNow, 

1066 self.mNrm, 

1067 self.cNrm, 

1068 self.MPCminNow, 

1069 self.hNrmNow, 

1070 ) 

1071 

1072 self.mNrmGrid, self.vNvrs, self.vNvrsP, self.MPCminNvrs = _add_vFuncNumba( 

1073 self.mNrmNext, 

1074 self.solution_next.mNrmGrid, 

1075 self.solution_next.vNvrs, 

1076 self.solution_next.vNvrsP, 

1077 self.solution_next.MPCminNvrs, 

1078 self.solution_next.hNrm, 

1079 self.CRRA, 

1080 self.PermShkVals_temp, 

1081 self.PermGroFac, 

1082 self.DiscFacEff, 

1083 self.ShkPrbs_temp, 

1084 self.EndOfPrdvP, 

1085 self.aNrmNow, 

1086 self.BoroCnstNat, 

1087 self.mNrmGrid, 

1088 self.cFuncNow, 

1089 self.mNrmMinNow, 

1090 self.MPCmaxEff, 

1091 self.MPCminNow, 

1092 ) 

1093 

1094 # Pack up the solution and return it 

1095 

1096 solution.mNrmGrid = self.mNrmGrid 

1097 solution.vNvrs = self.vNvrs 

1098 solution.vNvrsP = self.vNvrsP 

1099 solution.MPCminNvrs = self.MPCminNvrs 

1100 

1101 return solution 

1102 

1103 

1104# ============================================================================ 

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

1106# ============================================================================ 

1107 

1108 

1109init_perfect_foresight_fast = init_perfect_foresight.copy() 

1110perf_foresight_constructor_dict = init_perfect_foresight["constructors"].copy() 

1111perf_foresight_constructor_dict["solution_terminal"] = make_solution_terminal_fast 

1112init_perfect_foresight_fast["constructors"] = perf_foresight_constructor_dict 

1113 

1114 

1115class PerfForesightConsumerTypeFast(PerfForesightConsumerType): 

1116 r""" 

1117 A version of the perfect foresight consumer type speed up by numba. 

1118 """ 

1119 

1120 solution_terminal_class = PerfForesightSolution 

1121 default_ = { 

1122 "params": init_perfect_foresight_fast, 

1123 "solver": make_one_period_oo_solver(ConsPerfForesightSolverFast), 

1124 "model": "ConsPerfForesight.yaml", 

1125 } 

1126 

1127 def post_solve(self): 

1128 self.solution_fast = deepcopy(self.solution) 

1129 

1130 if self.cycles == 0: 

1131 terminal = 1 

1132 else: 

1133 terminal = self.cycles 

1134 self.solution[terminal] = self.solution_terminal 

1135 

1136 for i in range(terminal): 

1137 solution = self.solution[i] 

1138 

1139 # Construct the consumption function as a linear interpolation. 

1140 cFunc = LinearInterp(solution.mNrm, solution.cNrm) 

1141 

1142 """ 

1143 Defines the value and marginal value functions for this period. 

1144 Uses the fact that for a perfect foresight CRRA utility problem, 

1145 if the MPC in period t is :math:`\\kappa_{t}`, and relative risk 

1146 aversion :math:`\rho`, then the inverse value vFuncNvrs has a 

1147 constant slope of :math:`\\kappa_{t}^{-\rho/(1-\rho)}` and 

1148 vFuncNvrs has value of zero at the lower bound of market resources 

1149 mNrmMin. See PerfForesightConsumerType.ipynb documentation notebook 

1150 for a brief explanation and the links below for a fuller treatment. 

1151 

1152 https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/consumption/PerfForesightCRRA/#vFuncAnalytical 

1153 https://www.econ2.jhu.edu/people/ccarroll/SolvingMicroDSOPs/#vFuncPF 

1154 """ 

1155 

1156 vFuncNvrs = LinearInterp( 

1157 np.array([solution.mNrmMin, solution.mNrmMin + 1.0]), 

1158 np.array([0.0, solution.vFuncNvrsSlope]), 

1159 ) 

1160 vFunc = ValueFuncCRRA(vFuncNvrs, self.CRRA) 

1161 vPfunc = MargValueFuncCRRA(cFunc, self.CRRA) 

1162 

1163 consumer_solution = ConsumerSolution( 

1164 cFunc=cFunc, 

1165 vFunc=vFunc, 

1166 vPfunc=vPfunc, 

1167 mNrmMin=solution.mNrmMin, 

1168 hNrm=solution.hNrm, 

1169 MPCmin=solution.MPCmin, 

1170 MPCmax=solution.MPCmax, 

1171 ) 

1172 

1173 Ex_IncNext = 1.0 # Perfect foresight income of 1 

1174 

1175 # Add mNrmStE to the solution and return it 

1176 consumer_solution.mNrmStE = _add_mNrmStENumba( 

1177 self.Rfree[i], 

1178 self.PermGroFac[i], 

1179 solution.mNrm, 

1180 solution.cNrm, 

1181 solution.mNrmMin, 

1182 Ex_IncNext, 

1183 _find_mNrmStE, 

1184 ) 

1185 

1186 self.solution[i] = consumer_solution 

1187 

1188 

1189############################################################################### 

1190 

1191 

1192def select_fast_solver(CubicBool, vFuncBool): 

1193 if (not CubicBool) and (not vFuncBool): 

1194 solver = ConsIndShockSolverBasicFast 

1195 else: # Use the "advanced" solver if either is requested 

1196 solver = ConsIndShockSolverFast 

1197 solve_one_period = make_one_period_oo_solver(solver) 

1198 return solve_one_period 

1199 

1200 

1201init_idiosyncratic_shocks_fast = init_idiosyncratic_shocks.copy() 

1202ind_shock_fast_constructor_dict = init_idiosyncratic_shocks["constructors"].copy() 

1203ind_shock_fast_constructor_dict["solution_terminal"] = make_solution_terminal_fast 

1204ind_shock_fast_constructor_dict["solve_one_period"] = select_fast_solver 

1205init_idiosyncratic_shocks_fast["constructors"] = ind_shock_fast_constructor_dict 

1206 

1207 

1208class IndShockConsumerTypeFast(IndShockConsumerType, PerfForesightConsumerTypeFast): 

1209 r""" 

1210 A version of the idiosyncratic shock consumer type sped up by numba. 

1211 

1212 If CubicBool and vFuncBool are both set to false it's further optimized. 

1213 """ 

1214 

1215 solution_terminal_class = IndShockSolution 

1216 default_ = { 

1217 "params": init_idiosyncratic_shocks_fast, 

1218 "solver": NullFunc(), 

1219 "model": "ConsIndShock.yaml", 

1220 } 

1221 

1222 def post_solve(self): 

1223 self.solution_fast = deepcopy(self.solution) 

1224 

1225 if self.cycles == 0: 

1226 cycles = 1 

1227 else: 

1228 cycles = self.cycles 

1229 self.solution[-1] = init_idiosyncratic_shocks["constructors"][ 

1230 "solution_terminal" 

1231 ](self.CRRA) 

1232 

1233 for i in range(cycles): 

1234 for j in range(self.T_cycle): 

1235 solution = self.solution[i * self.T_cycle + j] 

1236 

1237 # Define the borrowing constraint (limiting consumption function) 

1238 cFuncNowCnst = LinearInterp( 

1239 np.array([solution.mNrmMin, solution.mNrmMin + 1]), 

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

1241 ) 

1242 

1243 """ 

1244 Constructs a basic solution for this period, including the consumption 

1245 function and marginal value function. 

1246 """ 

1247 

1248 if self.CubicBool: 

1249 # Makes a cubic spline interpolation of the unconstrained consumption 

1250 # function for this period. 

1251 cFuncNowUnc = CubicInterp( 

1252 solution.mNrm, 

1253 solution.cNrm, 

1254 solution.MPC, 

1255 solution.cFuncLimitIntercept, 

1256 solution.cFuncLimitSlope, 

1257 ) 

1258 else: 

1259 # Makes a linear interpolation to represent the (unconstrained) consumption function. 

1260 # Construct the unconstrained consumption function 

1261 cFuncNowUnc = LinearInterp( 

1262 solution.mNrm, 

1263 solution.cNrm, 

1264 solution.cFuncLimitIntercept, 

1265 solution.cFuncLimitSlope, 

1266 ) 

1267 

1268 # Combine the constrained and unconstrained functions into the true consumption function 

1269 cFuncNow = LowerEnvelope(cFuncNowUnc, cFuncNowCnst) 

1270 

1271 # Make the marginal value function and the marginal marginal value function 

1272 vPfuncNow = MargValueFuncCRRA(cFuncNow, self.CRRA) 

1273 

1274 # Pack up the solution and return it 

1275 consumer_solution = ConsumerSolution( 

1276 cFunc=cFuncNow, 

1277 vPfunc=vPfuncNow, 

1278 mNrmMin=solution.mNrmMin, 

1279 hNrm=solution.hNrm, 

1280 MPCmin=solution.MPCmin, 

1281 MPCmax=solution.MPCmax, 

1282 ) 

1283 

1284 if self.vFuncBool: 

1285 vNvrsFuncNow = CubicInterp( 

1286 solution.mNrmGrid, 

1287 solution.vNvrs, 

1288 solution.vNvrsP, 

1289 solution.MPCminNvrs * solution.hNrm, 

1290 solution.MPCminNvrs, 

1291 ) 

1292 vFuncNow = ValueFuncCRRA(vNvrsFuncNow, self.CRRA) 

1293 

1294 consumer_solution.vFunc = vFuncNow 

1295 

1296 if self.CubicBool or self.vFuncBool: 

1297 _searchFunc = ( 

1298 _find_mNrmStECubic if self.CubicBool else _find_mNrmStELinear 

1299 ) 

1300 # Add mNrmStE to the solution and return it 

1301 consumer_solution.mNrmStE = _add_mNrmStEIndNumba( 

1302 self.PermGroFac[j], 

1303 self.Rfree[j], 

1304 solution.Ex_IncNext, 

1305 solution.mNrmMin, 

1306 solution.mNrm, 

1307 solution.cNrm, 

1308 solution.MPC, 

1309 solution.MPCmin, 

1310 solution.hNrm, 

1311 _searchFunc, 

1312 ) 

1313 

1314 self.solution[i * self.T_cycle + j] = consumer_solution 

1315 

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

1317 self.calc_stable_points(force=True)