Coverage for HARK / ConsumptionSaving / ConsBequestModel.py: 96%

360 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-07 05:16 +0000

1""" 

2Classes to solve consumption-saving models with a bequest motive and 

3idiosyncratic shocks to income and wealth. All models here assume 

4separable CRRA utility of consumption and Stone-Geary utility of 

5savings with geometric discounting of the continuation value and 

6shocks to income that have transitory and/or permanent components. 

7 

8It currently solves two types of models: 

9 1) A standard lifecycle model with a terminal and/or accidental bequest motive. 

10 2) A portfolio choice model with a terminal and/or accidental bequest motive. 

11""" 

12 

13import numpy as np 

14 

15from HARK import NullFunc 

16from HARK.Calibration.Income.IncomeProcesses import ( 

17 construct_lognormal_income_process_unemployment, 

18 get_PermShkDstn_from_IncShkDstn, 

19 get_TranShkDstn_from_IncShkDstn, 

20) 

21from HARK.ConsumptionSaving.ConsIndShockModel import ( 

22 ConsumerSolution, 

23 IndShockConsumerType, 

24 make_basic_CRRA_solution_terminal, 

25 make_lognormal_kNrm_init_dstn, 

26 make_lognormal_pLvl_init_dstn, 

27) 

28from HARK.Calibration.Assets.AssetProcesses import ( 

29 make_lognormal_RiskyDstn, 

30 combine_IncShkDstn_and_RiskyDstn, 

31 calc_ShareLimit_for_CRRA, 

32) 

33from HARK.ConsumptionSaving.ConsPortfolioModel import ( 

34 PortfolioConsumerType, 

35 PortfolioSolution, 

36 make_portfolio_solution_terminal, 

37 make_AdjustDstn, 

38) 

39from HARK.ConsumptionSaving.ConsRiskyAssetModel import make_simple_ShareGrid 

40from HARK.distributions import expected 

41from HARK.interpolation import ( 

42 BilinearInterp, 

43 ConstantFunction, 

44 CubicInterp, 

45 IdentityFunction, 

46 LinearInterp, 

47 LinearInterpOnInterp1D, 

48 LowerEnvelope, 

49 MargMargValueFuncCRRA, 

50 MargValueFuncCRRA, 

51 ValueFuncCRRA, 

52) 

53from HARK.rewards import UtilityFuncCRRA, UtilityFuncStoneGeary 

54from HARK.utilities import make_assets_grid 

55 

56 

57def make_bequest_solution_terminal( 

58 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

59): 

60 """ 

61 Make the terminal period solution when there is a warm glow bequest motive with 

62 Stone-Geary form utility. If there is no warm glow bequest motive (BeqFacTerm = 0), 

63 then the terminal period solution is identical to ConsIndShock. 

64 

65 Parameters 

66 ---------- 

67 CRRA : float 

68 Coefficient on relative risk aversion over consumption. 

69 BeqCRRATerm : float 

70 Coefficient on relative risk aversion in the terminal warm glow bequest motive. 

71 BeqFacTerm : float 

72 Scaling factor for the terminal warm glow bequest motive. 

73 BeqShiftTerm : float 

74 Stone-Geary shifter term for the terminal warm glow bequest motive. 

75 aXtraGrid : np.array 

76 Set of assets-above-minimum to be used in the solution. 

77 

78 Returns 

79 ------- 

80 solution_terminal : ConsumerSolution 

81 Terminal period solution when there is a warm glow bequest. 

82 """ 

83 if BeqFacTerm == 0.0: # No terminal bequest 

84 solution_terminal = make_basic_CRRA_solution_terminal(CRRA) 

85 return solution_terminal 

86 

87 utility = UtilityFuncCRRA(CRRA) 

88 warm_glow = UtilityFuncStoneGeary( 

89 BeqCRRATerm, 

90 factor=BeqFacTerm, 

91 shifter=BeqShiftTerm, 

92 ) 

93 

94 aNrmGrid = np.append(0.0, aXtraGrid) if BeqShiftTerm != 0.0 else aXtraGrid 

95 cNrmGrid = utility.derinv(warm_glow.der(aNrmGrid)) 

96 vGrid = utility(cNrmGrid) + warm_glow(aNrmGrid) 

97 cNrmGridW0 = np.append(0.0, cNrmGrid) 

98 mNrmGridW0 = np.append(0.0, aNrmGrid + cNrmGrid) 

99 vNvrsGridW0 = np.append(0.0, utility.inv(vGrid)) 

100 

101 cFunc_term = LinearInterp(mNrmGridW0, cNrmGridW0) 

102 vNvrsFunc_term = LinearInterp(mNrmGridW0, vNvrsGridW0) 

103 vFunc_term = ValueFuncCRRA(vNvrsFunc_term, CRRA) 

104 vPfunc_term = MargValueFuncCRRA(cFunc_term, CRRA) 

105 vPPfunc_term = MargMargValueFuncCRRA(cFunc_term, CRRA) 

106 

107 solution_terminal = ConsumerSolution( 

108 cFunc=cFunc_term, 

109 vFunc=vFunc_term, 

110 vPfunc=vPfunc_term, 

111 vPPfunc=vPPfunc_term, 

112 mNrmMin=0.0, 

113 hNrm=0.0, 

114 MPCmax=1.0, 

115 MPCmin=1.0, 

116 ) 

117 return solution_terminal 

118 

119 

120def make_warmglow_portfolio_solution_terminal( 

121 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

122): 

123 """ 

124 Make the terminal period solution when there is a warm glow bequest motive with 

125 Stone-Geary form utility and portfolio choice. If there is no warm glow bequest 

126 motive (BeqFacTerm = 0), then the terminal period solution is identical to ConsPortfolio. 

127 

128 Parameters 

129 ---------- 

130 CRRA : float 

131 Coefficient on relative risk aversion over consumption. 

132 BeqCRRATerm : float 

133 Coefficient on relative risk aversion in the terminal warm glow bequest motive. 

134 BeqFacTerm : float 

135 Scaling factor for the terminal warm glow bequest motive. 

136 BeqShiftTerm : float 

137 Stone-Geary shifter term for the terminal warm glow bequest motive. 

138 aXtraGrid : np.array 

139 Set of assets-above-minimum to be used in the solution. 

140 

141 Returns 

142 ------- 

143 solution_terminal : ConsumerSolution 

144 Terminal period solution when there is a warm glow bequest and portfolio choice. 

145 """ 

146 if BeqFacTerm == 0.0: # No terminal bequest 

147 solution_terminal = make_portfolio_solution_terminal(CRRA) 

148 return solution_terminal 

149 

150 # Solve the terminal period problem when there is no portfolio choice 

151 solution_terminal_no_port = make_bequest_solution_terminal( 

152 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

153 ) 

154 

155 # Take consumption function from the no portfolio choice solution 

156 cFuncAdj_terminal = solution_terminal_no_port.cFunc 

157 cFuncFxd_terminal = lambda m, s: solution_terminal_no_port(m) 

158 

159 # Risky share is irrelevant-- no end-of-period assets; set to zero 

160 ShareFuncAdj_terminal = ConstantFunction(0.0) 

161 ShareFuncFxd_terminal = IdentityFunction(i_dim=1, n_dims=2) 

162 

163 # Value function is simply utility from consuming market resources 

164 vFuncAdj_terminal = solution_terminal_no_port.vFunc 

165 vFuncFxd_terminal = lambda m, s: solution_terminal_no_port.cFunc(m) 

166 

167 # Marginal value of market resources is marg utility at the consumption function 

168 vPfuncAdj_terminal = solution_terminal_no_port.vPfunc 

169 dvdmFuncFxd_terminal = lambda m, s: solution_terminal_no_port.vPfunc(m) 

170 # No future, no marg value of Share 

171 dvdsFuncFxd_terminal = ConstantFunction(0.0) 

172 

173 # Construct the terminal period solution 

174 solution_terminal = PortfolioSolution( 

175 cFuncAdj=cFuncAdj_terminal, 

176 ShareFuncAdj=ShareFuncAdj_terminal, 

177 vFuncAdj=vFuncAdj_terminal, 

178 vPfuncAdj=vPfuncAdj_terminal, 

179 cFuncFxd=cFuncFxd_terminal, 

180 ShareFuncFxd=ShareFuncFxd_terminal, 

181 vFuncFxd=vFuncFxd_terminal, 

182 dvdmFuncFxd=dvdmFuncFxd_terminal, 

183 dvdsFuncFxd=dvdsFuncFxd_terminal, 

184 ) 

185 return solution_terminal 

186 

187 

188############################################################################### 

189 

190 

191def solve_one_period_ConsWarmBequest( 

192 solution_next, 

193 IncShkDstn, 

194 LivPrb, 

195 DiscFac, 

196 CRRA, 

197 Rfree, 

198 PermGroFac, 

199 BoroCnstArt, 

200 aXtraGrid, 

201 BeqCRRA, 

202 BeqFac, 

203 BeqShift, 

204 CubicBool, 

205 vFuncBool, 

206): 

207 """ 

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

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

210 The consumer also has a "warm glow" bequest motive in which they gain additional 

211 utility based on their terminal wealth upon death. 

212 

213 Parameters 

214 ---------- 

215 solution_next : ConsumerSolution 

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

217 IncShkDstn : distribution.Distribution 

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

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

220 LivPrb : float 

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

222 the succeeding period. 

223 DiscFac : float 

224 Intertemporal discount factor for future utility. 

225 CRRA : float 

226 Coefficient of relative risk aversion. 

227 Rfree : float 

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

229 PermGroFac : float 

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

231 BoroCnstArt: float or None 

232 Borrowing constraint for the minimum allowable assets to end the 

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

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

235 rowing constraint. 

236 aXtraGrid : np.array 

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

238 absolute minimum acceptable level. 

239 BeqCRRA : float 

240 Coefficient of relative risk aversion for warm glow bequest motive. 

241 BeqFac : float 

242 Multiplicative intensity factor for the warm glow bequest motive. 

243 BeqShift : float 

244 Stone-Geary shifter in the warm glow bequest motive. 

245 CubicBool : bool 

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

247 vFuncBool: boolean 

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

249 included in the reported solution. 

250 

251 Returns 

252 ------- 

253 solution_now : ConsumerSolution 

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

255 """ 

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

257 uFunc = UtilityFuncCRRA(CRRA) 

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

259 BeqFacEff = (1.0 - LivPrb) * BeqFac # "effective" bequest factor 

260 warm_glow = UtilityFuncStoneGeary(BeqCRRA, BeqFacEff, BeqShift) 

261 

262 # Unpack next period's income shock distribution 

263 ShkPrbsNext = IncShkDstn.pmv 

264 PermShkValsNext = IncShkDstn.atoms[0] 

265 TranShkValsNext = IncShkDstn.atoms[1] 

266 PermShkMinNext = np.min(PermShkValsNext) 

267 TranShkMinNext = np.min(TranShkValsNext) 

268 

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

270 IncNext = PermShkValsNext * TranShkValsNext 

271 WorstIncNext = PermShkMinNext * TranShkMinNext 

272 WorstIncPrb = np.sum(ShkPrbsNext[IncNext == WorstIncNext]) 

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

274 

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

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

277 vPfuncNext = solution_next.vPfunc 

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

279 

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

281 PatFac = ((Rfree * DiscFacEff) ** (1.0 / CRRA)) / Rfree 

282 try: 

283 MPCminNow = 1.0 / (1.0 + PatFac / solution_next.MPCmin) 

284 except: 

285 MPCminNow = 0.0 

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

287 hNrmNow = PermGroFac / Rfree * (Ex_IncNext + solution_next.hNrm) 

288 temp_fac = (WorstIncPrb ** (1.0 / CRRA)) * PatFac 

289 MPCmaxNow = 1.0 / (1.0 + temp_fac / solution_next.MPCmax) 

290 cFuncLimitIntercept = MPCminNow * hNrmNow 

291 cFuncLimitSlope = MPCminNow 

292 

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

294 PermGroFacEffMin = (PermGroFac * PermShkMinNext) / Rfree 

295 BoroCnstNat = (solution_next.mNrmMin - TranShkMinNext) * PermGroFacEffMin 

296 BoroCnstNat = np.max([BoroCnstNat, -BeqShift]) 

297 

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

299 # and artificial borrowing constraints 

300 if BoroCnstArt is None: 

301 mNrmMinNow = BoroCnstNat 

302 else: 

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

304 

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

306 # or artificial borrowing constraint actually binds 

307 if BoroCnstNat < mNrmMinNow: 

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

309 else: 

310 MPCmaxEff = MPCmaxNow # Otherwise, it's the MPC calculated above 

311 

312 # Define the borrowing-constrained consumption function 

313 cFuncNowCnst = LinearInterp( 

314 np.array([mNrmMinNow, mNrmMinNow + 1.0]), np.array([0.0, 1.0]) 

315 ) 

316 

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

318 aNrmNow = np.asarray(aXtraGrid) + BoroCnstNat 

319 

320 # Define local functions for taking future expectations 

321 def calc_mNrmNext(S, a, R): 

322 return R / (PermGroFac * S["PermShk"]) * a + S["TranShk"] 

323 

324 def calc_vNext(S, a, R): 

325 return (S["PermShk"] ** (1.0 - CRRA) * PermGroFac ** (1.0 - CRRA)) * vFuncNext( 

326 calc_mNrmNext(S, a, R) 

327 ) 

328 

329 def calc_vPnext(S, a, R): 

330 return S["PermShk"] ** (-CRRA) * vPfuncNext(calc_mNrmNext(S, a, R)) 

331 

332 def calc_vPPnext(S, a, R): 

333 return S["PermShk"] ** (-CRRA - 1.0) * vPPfuncNext(calc_mNrmNext(S, a, R)) 

334 

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

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

337 EndOfPrdvP = vPfacEff * expected(calc_vPnext, IncShkDstn, args=(aNrmNow, Rfree)) 

338 EndOfPrdvP += warm_glow.der(aNrmNow) 

339 

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

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

342 mNrmNow = cNrmNow + aNrmNow # Endogenous mNrm gridpoints 

343 

344 # Limiting consumption is zero as m approaches mNrmMin 

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

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

347 

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

349 if CubicBool: 

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

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

352 EndOfPrdvPP = vPPfacEff * expected( 

353 calc_vPPnext, IncShkDstn, args=(aNrmNow, Rfree) 

354 ) 

355 EndOfPrdvPP += warm_glow.der(aNrmNow, order=2) 

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

357 MPC = dcda / (dcda + 1.0) 

358 MPC_for_interpolation = np.insert(MPC, 0, MPCmaxNow) 

359 

360 # Construct the unconstrained consumption function as a cubic interpolation 

361 cFuncNowUnc = CubicInterp( 

362 m_for_interpolation, 

363 c_for_interpolation, 

364 MPC_for_interpolation, 

365 cFuncLimitIntercept, 

366 cFuncLimitSlope, 

367 ) 

368 else: 

369 # Construct the unconstrained consumption function as a linear interpolation 

370 cFuncNowUnc = LinearInterp( 

371 m_for_interpolation, 

372 c_for_interpolation, 

373 cFuncLimitIntercept, 

374 cFuncLimitSlope, 

375 ) 

376 

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

378 # LowerEnvelope should only be used when BoroCnstArt is True 

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

380 

381 # Make the marginal value function 

382 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

383 

384 # Define this period's marginal marginal value function 

385 if CubicBool: 

386 vPPfuncNow = MargMargValueFuncCRRA(cFuncNow, CRRA) 

387 else: 

388 vPPfuncNow = NullFunc() # Dummy object 

389 

390 # Construct this period's value function if requested 

391 if vFuncBool: 

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

393 EndOfPrdv = DiscFacEff * expected(calc_vNext, IncShkDstn, args=(aNrmNow, Rfree)) 

394 EndOfPrdv += warm_glow(aNrmNow) 

395 EndOfPrdvNvrs = uFunc.inv(EndOfPrdv) 

396 # value transformed through inverse utility 

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

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

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

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

401 

402 # Construct the end-of-period value function 

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

404 EndOfPrd_vNvrsFunc = CubicInterp(aNrm_temp, EndOfPrdvNvrs, EndOfPrdvNvrsP) 

405 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

406 

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

408 mNrm_temp = mNrmMinNow + aXtraGrid 

409 cNrm_temp = cFuncNow(mNrm_temp) 

410 aNrm_temp = mNrm_temp - cNrm_temp 

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

412 vP_temp = uFunc.der(cNrm_temp) 

413 

414 # Construct the beginning-of-period value function 

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

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

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

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

419 vNvrsP_temp = np.insert(vNvrsP_temp, 0, MPCmaxEff ** (-CRRA / (1.0 - CRRA))) 

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

421 vNvrsFuncNow = CubicInterp( 

422 mNrm_temp, vNvrs_temp, vNvrsP_temp, MPCminNvrs * hNrmNow, MPCminNvrs 

423 ) 

424 vFuncNow = ValueFuncCRRA(vNvrsFuncNow, CRRA) 

425 else: 

426 vFuncNow = NullFunc() # Dummy object 

427 

428 # Create and return this period's solution 

429 solution_now = ConsumerSolution( 

430 cFunc=cFuncNow, 

431 vFunc=vFuncNow, 

432 vPfunc=vPfuncNow, 

433 vPPfunc=vPPfuncNow, 

434 mNrmMin=mNrmMinNow, 

435 hNrm=hNrmNow, 

436 MPCmin=MPCminNow, 

437 MPCmax=MPCmaxEff, 

438 ) 

439 return solution_now 

440 

441 

442############################################################################### 

443 

444 

445def solve_one_period_ConsPortfolioWarmGlow( 

446 solution_next, 

447 IncShkDstn, 

448 RiskyDstn, 

449 LivPrb, 

450 DiscFac, 

451 CRRA, 

452 Rfree, 

453 PermGroFac, 

454 BoroCnstArt, 

455 aXtraGrid, 

456 ShareGrid, 

457 AdjustPrb, 

458 ShareLimit, 

459 vFuncBool, 

460 DiscreteShareBool, 

461 BeqCRRA, 

462 BeqFac, 

463 BeqShift, 

464): 

465 """ 

466 Solve one period of a consumption-saving problem with portfolio allocation 

467 between a riskless and risky asset. This function handles various sub-cases 

468 or variations on the problem, including the possibility that the agent does 

469 not necessarily get to update their portfolio share in every period, or that 

470 they must choose a discrete rather than continuous risky share. 

471 

472 Parameters 

473 ---------- 

474 solution_next : PortfolioSolution 

475 Solution to next period's problem. 

476 ShockDstn : Distribution 

477 Joint distribution of permanent income shocks, transitory income shocks, 

478 and risky returns. This is only used if the input IndepDstnBool is False, 

479 indicating that income and return distributions can't be assumed to be 

480 independent. 

481 IncShkDstn : Distribution 

482 Discrete distribution of permanent income shocks and transitory income 

483 shocks. This is only used if the input IndepDstnBool is True, indicating 

484 that income and return distributions are independent. 

485 RiskyDstn : Distribution 

486 Distribution of risky asset returns. This is only used if the input 

487 IndepDstnBool is True, indicating that income and return distributions 

488 are independent. 

489 LivPrb : float 

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

491 the succeeding period. 

492 DiscFac : float 

493 Intertemporal discount factor for future utility. 

494 CRRA : float 

495 Coefficient of relative risk aversion. 

496 Rfree : float 

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

498 PermGroFac : float 

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

500 BoroCnstArt: float or None 

501 Borrowing constraint for the minimum allowable assets to end the 

502 period with. In this model, it is *required* to be zero. 

503 aXtraGrid: np.array 

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

505 absolute minimum acceptable level. 

506 ShareGrid : np.array 

507 Array of risky portfolio shares on which to define the interpolation 

508 of the consumption function when Share is fixed. Also used when the 

509 risky share choice is specified as discrete rather than continuous. 

510 AdjustPrb : float 

511 Probability that the agent will be able to update his portfolio share. 

512 ShareLimit : float 

513 Limiting lower bound of risky portfolio share as mNrm approaches infinity. 

514 vFuncBool: boolean 

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

516 included in the reported solution. 

517 DiscreteShareBool : bool 

518 Indicator for whether risky portfolio share should be optimized on the 

519 continuous [0,1] interval using the FOC (False), or instead only selected 

520 from the discrete set of values in ShareGrid (True). If True, then 

521 vFuncBool must also be True. 

522 IndepDstnBool : bool 

523 Indicator for whether the income and risky return distributions are in- 

524 dependent of each other, which can speed up the expectations step. 

525 BeqCRRA : float 

526 Coefficient of relative risk aversion for warm glow bequest motive. 

527 BeqFac : float 

528 Multiplicative intensity factor for the warm glow bequest motive. 

529 BeqShift : float 

530 Stone-Geary shifter in the warm glow bequest motive. 

531 

532 Returns 

533 ------- 

534 solution_now : PortfolioSolution 

535 Solution to this period's problem. 

536 """ 

537 # Make sure the individual is liquidity constrained. Allowing a consumer to 

538 # borrow *and* invest in an asset with unbounded (negative) returns is a bad mix. 

539 if BoroCnstArt != 0.0: 

540 raise ValueError("PortfolioConsumerType must have BoroCnstArt=0.0!") 

541 

542 # Make sure that if risky portfolio share is optimized only discretely, then 

543 # the value function is also constructed (else this task would be impossible). 

544 if DiscreteShareBool and (not vFuncBool): 

545 raise ValueError( 

546 "PortfolioConsumerType requires vFuncBool to be True when DiscreteShareBool is True!" 

547 ) 

548 

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

550 uFunc = UtilityFuncCRRA(CRRA) 

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

552 BeqFacEff = (1.0 - LivPrb) * BeqFac # "effective" bequest factor 

553 warm_glow = UtilityFuncStoneGeary(BeqCRRA, BeqFacEff, BeqShift) 

554 

555 # Unpack next period's solution for easier access 

556 vPfuncAdj_next = solution_next.vPfuncAdj 

557 dvdmFuncFxd_next = solution_next.dvdmFuncFxd 

558 dvdsFuncFxd_next = solution_next.dvdsFuncFxd 

559 vFuncAdj_next = solution_next.vFuncAdj 

560 vFuncFxd_next = solution_next.vFuncFxd 

561 

562 # Set a flag for whether the natural borrowing constraint is zero, which 

563 # depends on whether the smallest transitory income shock is zero 

564 BoroCnstNat_iszero = (np.min(IncShkDstn.atoms[1]) == 0.0) or ( 

565 BeqFac != 0.0 and BeqShift == 0.0 

566 ) 

567 

568 # Prepare to calculate end-of-period marginal values by creating an array 

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

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

571 # experience next period. 

572 

573 # Unpack the risky return shock distribution 

574 Risky_next = RiskyDstn.atoms 

575 RiskyMax = np.max(Risky_next) 

576 RiskyMin = np.min(Risky_next) 

577 

578 # bNrm represents R*a, balances after asset return shocks but before income. 

579 # This just uses the highest risky return as a rough shifter for the aXtraGrid. 

580 if BoroCnstNat_iszero: 

581 aNrmGrid = aXtraGrid 

582 bNrmGrid = np.insert(RiskyMax * aXtraGrid, 0, RiskyMin * aXtraGrid[0]) 

583 else: 

584 # Add an asset point at exactly zero 

585 aNrmGrid = np.insert(aXtraGrid, 0, 0.0) 

586 bNrmGrid = RiskyMax * np.insert(aXtraGrid, 0, 0.0) 

587 

588 # Get grid and shock sizes, for easier indexing 

589 aNrmCount = aNrmGrid.size 

590 ShareCount = ShareGrid.size 

591 

592 # Make tiled arrays to calculate future realizations of mNrm and Share when integrating over IncShkDstn 

593 bNrmNext, ShareNext = np.meshgrid(bNrmGrid, ShareGrid, indexing="ij") 

594 

595 # Define functions that are used internally to evaluate future realizations 

596 def calc_mNrm_next(S, b): 

597 """ 

598 Calculate future realizations of market resources mNrm from the income 

599 shock distribution S and normalized bank balances b. 

600 """ 

601 return b / (S["PermShk"] * PermGroFac) + S["TranShk"] 

602 

603 def calc_dvdm_next(S, b, z): 

604 """ 

605 Evaluate realizations of marginal value of market resources next period, 

606 based on the income distribution S, values of bank balances bNrm, and 

607 values of the risky share z. 

608 """ 

609 mNrm_next = calc_mNrm_next(S, b) 

610 dvdmAdj_next = vPfuncAdj_next(mNrm_next) 

611 

612 if AdjustPrb < 1.0: 

613 # Expand to the same dimensions as mNrm 

614 Share_next_expanded = z + np.zeros_like(mNrm_next) 

615 dvdmFxd_next = dvdmFuncFxd_next(mNrm_next, Share_next_expanded) 

616 # Combine by adjustment probability 

617 dvdm_next = AdjustPrb * dvdmAdj_next + (1.0 - AdjustPrb) * dvdmFxd_next 

618 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

619 dvdm_next = dvdmAdj_next 

620 

621 dvdm_next = (S["PermShk"] * PermGroFac) ** (-CRRA) * dvdm_next 

622 return dvdm_next 

623 

624 def calc_dvds_next(S, b, z): 

625 """ 

626 Evaluate realizations of marginal value of risky share next period, based 

627 on the income distribution S, values of bank balances bNrm, and values of 

628 the risky share z. 

629 """ 

630 mNrm_next = calc_mNrm_next(S, b) 

631 

632 # No marginal value of Share if it's a free choice! 

633 dvdsAdj_next = np.zeros_like(mNrm_next) 

634 

635 if AdjustPrb < 1.0: 

636 # Expand to the same dimensions as mNrm 

637 Share_next_expanded = z + np.zeros_like(mNrm_next) 

638 dvdsFxd_next = dvdsFuncFxd_next(mNrm_next, Share_next_expanded) 

639 # Combine by adjustment probability 

640 dvds_next = AdjustPrb * dvdsAdj_next + (1.0 - AdjustPrb) * dvdsFxd_next 

641 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

642 dvds_next = dvdsAdj_next 

643 

644 dvds_next = (S["PermShk"] * PermGroFac) ** (1.0 - CRRA) * dvds_next 

645 return dvds_next 

646 

647 # Calculate end-of-period marginal value of assets and shares at each point 

648 # in aNrm and ShareGrid. Does so by taking expectation of next period marginal 

649 # values across income and risky return shocks. 

650 

651 # Calculate intermediate marginal value of bank balances by taking expectations over income shocks 

652 dvdb_intermed = expected(calc_dvdm_next, IncShkDstn, args=(bNrmNext, ShareNext)) 

653 dvdbNvrs_intermed = uFunc.derinv(dvdb_intermed, order=(1, 0)) 

654 dvdbNvrsFunc_intermed = BilinearInterp(dvdbNvrs_intermed, bNrmGrid, ShareGrid) 

655 dvdbFunc_intermed = MargValueFuncCRRA(dvdbNvrsFunc_intermed, CRRA) 

656 

657 # Calculate intermediate marginal value of risky portfolio share by taking expectations over income shocks 

658 dvds_intermed = expected(calc_dvds_next, IncShkDstn, args=(bNrmNext, ShareNext)) 

659 dvdsFunc_intermed = BilinearInterp(dvds_intermed, bNrmGrid, ShareGrid) 

660 

661 # Make tiled arrays to calculate future realizations of bNrm and Share when integrating over RiskyDstn 

662 aNrmNow, ShareNext = np.meshgrid(aNrmGrid, ShareGrid, indexing="ij") 

663 

664 # Define functions for calculating end-of-period marginal value 

665 def calc_EndOfPrd_dvda(S, a, z): 

666 """ 

667 Compute end-of-period marginal value of assets at values a, conditional 

668 on risky asset return S and risky share z. 

669 """ 

670 # Calculate future realizations of bank balances bNrm 

671 Rxs = S - Rfree # Excess returns 

672 Rport = Rfree + z * Rxs # Portfolio return 

673 bNrm_next = Rport * a 

674 

675 # Ensure shape concordance 

676 z_rep = z + np.zeros_like(bNrm_next) 

677 

678 # Calculate and return dvda 

679 EndOfPrd_dvda = Rport * dvdbFunc_intermed(bNrm_next, z_rep) 

680 return EndOfPrd_dvda 

681 

682 def EndOfPrddvds_dist(S, a, z): 

683 """ 

684 Compute end-of-period marginal value of risky share at values a, conditional 

685 on risky asset return S and risky share z. 

686 """ 

687 # Calculate future realizations of bank balances bNrm 

688 Rxs = S - Rfree # Excess returns 

689 Rport = Rfree + z * Rxs # Portfolio return 

690 bNrm_next = Rport * a 

691 

692 # Make the shares match the dimension of b, so that it can be vectorized 

693 z_rep = z + np.zeros_like(bNrm_next) 

694 

695 # Calculate and return dvds 

696 EndOfPrd_dvds = Rxs * a * dvdbFunc_intermed( 

697 bNrm_next, z_rep 

698 ) + dvdsFunc_intermed(bNrm_next, z_rep) 

699 return EndOfPrd_dvds 

700 

701 # Evaluate realizations of value and marginal value after asset returns are realized 

702 

703 # Calculate end-of-period marginal value of assets by taking expectations 

704 EndOfPrd_dvda = DiscFacEff * expected( 

705 calc_EndOfPrd_dvda, RiskyDstn, args=(aNrmNow, ShareNext) 

706 ) 

707 warm_glow_der = warm_glow.der(aNrmNow) 

708 EndOfPrd_dvda += np.where(np.isnan(warm_glow_der), 0.0, warm_glow_der) 

709 EndOfPrd_dvdaNvrs = uFunc.derinv(EndOfPrd_dvda) 

710 

711 # Calculate end-of-period marginal value of risky portfolio share by taking expectations 

712 EndOfPrd_dvds = DiscFacEff * expected( 

713 EndOfPrddvds_dist, RiskyDstn, args=(aNrmNow, ShareNext) 

714 ) 

715 

716 # Make the end-of-period value function if the value function is requested 

717 if vFuncBool: 

718 

719 def calc_v_intermed(S, b, z): 

720 """ 

721 Calculate "intermediate" value from next period's bank balances, the 

722 income shocks S, and the risky asset share. 

723 """ 

724 mNrm_next = calc_mNrm_next(S, b) 

725 

726 vAdj_next = vFuncAdj_next(mNrm_next) 

727 if AdjustPrb < 1.0: 

728 vFxd_next = vFuncFxd_next(mNrm_next, z) 

729 # Combine by adjustment probability 

730 v_next = AdjustPrb * vAdj_next + (1.0 - AdjustPrb) * vFxd_next 

731 else: # Don't bother evaluating if there's no chance that portfolio share is fixed 

732 v_next = vAdj_next 

733 

734 v_intermed = (S["PermShk"] * PermGroFac) ** (1.0 - CRRA) * v_next 

735 return v_intermed 

736 

737 # Calculate intermediate value by taking expectations over income shocks 

738 v_intermed = expected(calc_v_intermed, IncShkDstn, args=(bNrmNext, ShareNext)) 

739 

740 # Construct the "intermediate value function" for this period 

741 vNvrs_intermed = uFunc.inv(v_intermed) 

742 vNvrsFunc_intermed = BilinearInterp(vNvrs_intermed, bNrmGrid, ShareGrid) 

743 vFunc_intermed = ValueFuncCRRA(vNvrsFunc_intermed, CRRA) 

744 

745 def calc_EndOfPrd_v(S, a, z): 

746 # Calculate future realizations of bank balances bNrm 

747 Rxs = S - Rfree 

748 Rport = Rfree + z * Rxs 

749 bNrm_next = Rport * a 

750 

751 # Make an extended share_next of the same dimension as b_nrm so 

752 # that the function can be vectorized 

753 z_rep = z + np.zeros_like(bNrm_next) 

754 

755 EndOfPrd_v = vFunc_intermed(bNrm_next, z_rep) 

756 return EndOfPrd_v 

757 

758 # Calculate end-of-period value by taking expectations 

759 EndOfPrd_v = DiscFacEff * expected( 

760 calc_EndOfPrd_v, RiskyDstn, args=(aNrmNow, ShareNext) 

761 ) 

762 EndOfPrd_v += warm_glow(aNrmNow) 

763 EndOfPrd_vNvrs = uFunc.inv(EndOfPrd_v) 

764 

765 # Now make an end-of-period value function over aNrm and Share 

766 EndOfPrd_vNvrsFunc = BilinearInterp(EndOfPrd_vNvrs, aNrmGrid, ShareGrid) 

767 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

768 # This will be used later to make the value function for this period 

769 

770 # Find the optimal risky asset share either by choosing the best value among 

771 # the discrete grid choices, or by satisfying the FOC with equality (continuous) 

772 if DiscreteShareBool: 

773 # If we're restricted to discrete choices, then portfolio share is 

774 # the one with highest value for each aNrm gridpoint 

775 opt_idx = np.argmax(EndOfPrd_v, axis=1) 

776 ShareAdj_now = ShareGrid[opt_idx] 

777 

778 # Take cNrm at that index as well... and that's it! 

779 cNrmAdj_now = EndOfPrd_dvdaNvrs[np.arange(aNrmCount), opt_idx] 

780 

781 else: 

782 # Now find the optimal (continuous) risky share on [0,1] by solving the first 

783 # order condition EndOfPrd_dvds == 0. 

784 FOC_s = EndOfPrd_dvds # Relabel for convenient typing 

785 

786 # For each value of aNrm, find the value of Share such that FOC_s == 0 

787 crossing = np.logical_and(FOC_s[:, 1:] <= 0.0, FOC_s[:, :-1] >= 0.0) 

788 share_idx = np.argmax(crossing, axis=1) 

789 # This represents the index of the segment of the share grid where dvds flips 

790 # from positive to negative, indicating that there's a zero *on* the segment 

791 

792 # Calculate the fractional distance between those share gridpoints where the 

793 # zero should be found, assuming a linear function; call it alpha 

794 a_idx = np.arange(aNrmCount) 

795 bot_s = ShareGrid[share_idx] 

796 top_s = ShareGrid[share_idx + 1] 

797 bot_f = FOC_s[a_idx, share_idx] 

798 top_f = FOC_s[a_idx, share_idx + 1] 

799 bot_c = EndOfPrd_dvdaNvrs[a_idx, share_idx] 

800 top_c = EndOfPrd_dvdaNvrs[a_idx, share_idx + 1] 

801 alpha = 1.0 - top_f / (top_f - bot_f) 

802 

803 # Calculate the continuous optimal risky share and optimal consumption 

804 ShareAdj_now = (1.0 - alpha) * bot_s + alpha * top_s 

805 cNrmAdj_now = (1.0 - alpha) * bot_c + alpha * top_c 

806 

807 # If agent wants to put more than 100% into risky asset, he is constrained. 

808 # Likewise if he wants to put less than 0% into risky asset, he is constrained. 

809 constrained_top = FOC_s[:, -1] > 0.0 

810 constrained_bot = FOC_s[:, 0] < 0.0 

811 

812 # Apply those constraints to both risky share and consumption (but lower 

813 # constraint should never be relevant) 

814 ShareAdj_now[constrained_top] = 1.0 

815 ShareAdj_now[constrained_bot] = 0.0 

816 cNrmAdj_now[constrained_top] = EndOfPrd_dvdaNvrs[constrained_top, -1] 

817 cNrmAdj_now[constrained_bot] = EndOfPrd_dvdaNvrs[constrained_bot, 0] 

818 

819 # When the natural borrowing constraint is *not* zero, then aNrm=0 is in the 

820 # grid, but there's no way to "optimize" the portfolio if a=0, and consumption 

821 # can't depend on the risky share if it doesn't meaningfully exist. Apply 

822 # a small fix to the bottom gridpoint (aNrm=0) when this happens. 

823 if not BoroCnstNat_iszero: 

824 ShareAdj_now[0] = 1.0 

825 cNrmAdj_now[0] = EndOfPrd_dvdaNvrs[0, -1] 

826 

827 # Construct functions characterizing the solution for this period 

828 

829 # Calculate the endogenous mNrm gridpoints when the agent adjusts his portfolio, 

830 # then construct the consumption function when the agent can adjust his share 

831 mNrmAdj_now = np.insert(aNrmGrid + cNrmAdj_now, 0, 0.0) 

832 cNrmAdj_now = np.insert(cNrmAdj_now, 0, 0.0) 

833 cFuncAdj_now = LinearInterp(mNrmAdj_now, cNrmAdj_now) 

834 

835 # Construct the marginal value (of mNrm) function when the agent can adjust 

836 vPfuncAdj_now = MargValueFuncCRRA(cFuncAdj_now, CRRA) 

837 

838 # Construct the consumption function when the agent *can't* adjust the risky 

839 # share, as well as the marginal value of Share function 

840 cFuncFxd_by_Share = [] 

841 dvdsFuncFxd_by_Share = [] 

842 for j in range(ShareCount): 

843 cNrmFxd_temp = np.insert(EndOfPrd_dvdaNvrs[:, j], 0, 0.0) 

844 mNrmFxd_temp = np.insert(aNrmGrid + cNrmFxd_temp[1:], 0, 0.0) 

845 dvdsFxd_temp = np.insert(EndOfPrd_dvds[:, j], 0, EndOfPrd_dvds[0, j]) 

846 cFuncFxd_by_Share.append(LinearInterp(mNrmFxd_temp, cNrmFxd_temp)) 

847 dvdsFuncFxd_by_Share.append(LinearInterp(mNrmFxd_temp, dvdsFxd_temp)) 

848 cFuncFxd_now = LinearInterpOnInterp1D(cFuncFxd_by_Share, ShareGrid) 

849 dvdsFuncFxd_now = LinearInterpOnInterp1D(dvdsFuncFxd_by_Share, ShareGrid) 

850 

851 # The share function when the agent can't adjust his portfolio is trivial 

852 ShareFuncFxd_now = IdentityFunction(i_dim=1, n_dims=2) 

853 

854 # Construct the marginal value of mNrm function when the agent can't adjust his share 

855 dvdmFuncFxd_now = MargValueFuncCRRA(cFuncFxd_now, CRRA) 

856 

857 # Construct the optimal risky share function when adjusting is possible. 

858 # The interpolation method depends on whether the choice is discrete or continuous. 

859 if DiscreteShareBool: 

860 # If the share choice is discrete, the "interpolated" share function acts 

861 # like a step function, with jumps at the midpoints of mNrm gridpoints. 

862 # Because an actual step function would break our (assumed continuous) linear 

863 # interpolator, there's a *tiny* region with extremely high slope. 

864 mNrmAdj_mid = (mNrmAdj_now[2:] + mNrmAdj_now[1:-1]) / 2 

865 mNrmAdj_plus = mNrmAdj_mid * (1.0 + 1e-12) 

866 mNrmAdj_comb = (np.transpose(np.vstack((mNrmAdj_mid, mNrmAdj_plus)))).flatten() 

867 mNrmAdj_comb = np.append(np.insert(mNrmAdj_comb, 0, 0.0), mNrmAdj_now[-1]) 

868 Share_comb = (np.transpose(np.vstack((ShareAdj_now, ShareAdj_now)))).flatten() 

869 ShareFuncAdj_now = LinearInterp(mNrmAdj_comb, Share_comb) 

870 

871 else: 

872 # If the share choice is continuous, just make an ordinary interpolating function 

873 if BoroCnstNat_iszero: 

874 Share_lower_bound = ShareLimit 

875 else: 

876 Share_lower_bound = 1.0 

877 ShareAdj_now = np.insert(ShareAdj_now, 0, Share_lower_bound) 

878 ShareFuncAdj_now = LinearInterp(mNrmAdj_now, ShareAdj_now, ShareLimit, 0.0) 

879 

880 # Add the value function if requested 

881 if vFuncBool: 

882 # Create the value functions for this period, defined over market resources 

883 # mNrm when agent can adjust his portfolio, and over market resources and 

884 # fixed share when agent can not adjust his portfolio. 

885 

886 # Construct the value function when the agent can adjust his portfolio 

887 mNrm_temp = aXtraGrid # Just use aXtraGrid as our grid of mNrm values 

888 cNrm_temp = cFuncAdj_now(mNrm_temp) 

889 aNrm_temp = mNrm_temp - cNrm_temp 

890 Share_temp = ShareFuncAdj_now(mNrm_temp) 

891 v_temp = uFunc(cNrm_temp) + EndOfPrd_vFunc(aNrm_temp, Share_temp) 

892 vNvrs_temp = uFunc.inv(v_temp) 

893 vNvrsP_temp = uFunc.der(cNrm_temp) * uFunc.inverse(v_temp, order=(0, 1)) 

894 vNvrsFuncAdj = CubicInterp( 

895 np.insert(mNrm_temp, 0, 0.0), # x_list 

896 np.insert(vNvrs_temp, 0, 0.0), # f_list 

897 np.insert(vNvrsP_temp, 0, vNvrsP_temp[0]), # dfdx_list 

898 ) 

899 # Re-curve the pseudo-inverse value function 

900 vFuncAdj_now = ValueFuncCRRA(vNvrsFuncAdj, CRRA) 

901 

902 # Construct the value function when the agent *can't* adjust his portfolio 

903 mNrm_temp, Share_temp = np.meshgrid(aXtraGrid, ShareGrid) 

904 cNrm_temp = cFuncFxd_now(mNrm_temp, Share_temp) 

905 aNrm_temp = mNrm_temp - cNrm_temp 

906 v_temp = uFunc(cNrm_temp) + EndOfPrd_vFunc(aNrm_temp, Share_temp) 

907 vNvrs_temp = uFunc.inv(v_temp) 

908 vNvrsP_temp = uFunc.der(cNrm_temp) * uFunc.inverse(v_temp, order=(0, 1)) 

909 vNvrsFuncFxd_by_Share = [] 

910 for j in range(ShareCount): 

911 vNvrsFuncFxd_by_Share.append( 

912 CubicInterp( 

913 np.insert(mNrm_temp[:, 0], 0, 0.0), # x_list 

914 np.insert(vNvrs_temp[:, j], 0, 0.0), # f_list 

915 np.insert(vNvrsP_temp[:, j], 0, vNvrsP_temp[j, 0]), # dfdx_list 

916 ) 

917 ) 

918 vNvrsFuncFxd = LinearInterpOnInterp1D(vNvrsFuncFxd_by_Share, ShareGrid) 

919 vFuncFxd_now = ValueFuncCRRA(vNvrsFuncFxd, CRRA) 

920 

921 else: # If vFuncBool is False, fill in dummy values 

922 vFuncAdj_now = NullFunc() 

923 vFuncFxd_now = NullFunc() 

924 

925 # Package and return the solution 

926 solution_now = PortfolioSolution( 

927 cFuncAdj=cFuncAdj_now, 

928 ShareFuncAdj=ShareFuncAdj_now, 

929 vPfuncAdj=vPfuncAdj_now, 

930 vFuncAdj=vFuncAdj_now, 

931 cFuncFxd=cFuncFxd_now, 

932 ShareFuncFxd=ShareFuncFxd_now, 

933 dvdmFuncFxd=dvdmFuncFxd_now, 

934 dvdsFuncFxd=dvdsFuncFxd_now, 

935 vFuncFxd=vFuncFxd_now, 

936 AdjPrb=AdjustPrb, 

937 ) 

938 return solution_now 

939 

940 

941############################################################################### 

942 

943# Make a dictionary of constructors for the warm glow bequest model 

944warmglow_constructor_dict = { 

945 "IncShkDstn": construct_lognormal_income_process_unemployment, 

946 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

947 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

948 "aXtraGrid": make_assets_grid, 

949 "solution_terminal": make_bequest_solution_terminal, 

950 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

951 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

952} 

953 

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

955default_kNrmInitDstn_params = { 

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

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

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

959} 

960 

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

962default_pLvlInitDstn_params = { 

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

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

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

966} 

967 

968# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

969default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

979} 

980 

981# Default parameters to make aXtraGrid using make_assets_grid 

982default_aXtraGrid_params = { 

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

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

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

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

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

988} 

989 

990# Make a dictionary to specify awarm glow bequest consumer type 

991init_warm_glow = { 

992 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

995 "constructors": warmglow_constructor_dict, # See dictionary above 

996 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

997 "CRRA": 2.0, # Coefficient of relative risk aversion on consumption 

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

999 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

1002 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

1003 "BeqCRRA": 2.0, # Coefficient of relative risk aversion for bequest motive 

1004 "BeqFac": 40.0, # Scaling factor for bequest motive 

1005 "BeqShift": 0.0, # Stone-Geary shifter term for bequest motive 

1006 "BeqCRRATerm": 2.0, # Coefficient of relative risk aversion for bequest motive, terminal period only 

1007 "BeqFacTerm": 40.0, # Scaling factor for bequest motive, terminal period only 

1008 "BeqShiftTerm": 0.0, # Stone-Geary shifter term for bequest motive, terminal period only 

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

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

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

1012 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1018 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1022} 

1023init_warm_glow.update(default_IncShkDstn_params) 

1024init_warm_glow.update(default_aXtraGrid_params) 

1025init_warm_glow.update(default_kNrmInitDstn_params) 

1026init_warm_glow.update(default_pLvlInitDstn_params) 

1027 

1028# Make a dictionary with bequest motives turned off 

1029init_accidental_bequest = init_warm_glow.copy() 

1030init_accidental_bequest["BeqFac"] = 0.0 

1031init_accidental_bequest["BeqShift"] = 0.0 

1032init_accidental_bequest["BeqFacTerm"] = 0.0 

1033init_accidental_bequest["BeqShiftTerm"] = 0.0 

1034 

1035# Make a dictionary that has *only* a terminal period bequest 

1036init_warm_glow_terminal_only = init_warm_glow.copy() 

1037init_warm_glow_terminal_only["BeqFac"] = 0.0 

1038init_warm_glow_terminal_only["BeqShift"] = 0.0 

1039 

1040 

1041class BequestWarmGlowConsumerType(IndShockConsumerType): 

1042 r""" 

1043 A consumer type with based on IndShockConsumerType, with an additional bequest motive. 

1044 They gain utility for any wealth they leave when they die, according to a Stone-Geary utility. 

1045 

1046 .. math:: 

1047 \newcommand{\CRRA}{\rho} 

1048 \newcommand{\DiePrb}{\mathsf{D}} 

1049 \newcommand{\PermGroFac}{\Gamma} 

1050 \newcommand{\Rfree}{\mathsf{R}} 

1051 \newcommand{\DiscFac}{\beta} 

1052 \begin{align*} 

1053 v_t(m_t) &= \max_{c_t}u(c_t) + \DiePrb_{t+1} u_{Beq}(a_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], \\ 

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

1055 a_t &= m_t - c_t, \\ 

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

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

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

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

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

1061 u_{Beq} (a) &= \textbf{BeqFac} \frac{(a+\textbf{BeqShift})^{1-\CRRA_{Beq}}}{1-\CRRA_{Beq}} \\ 

1062 \end{align*} 

1063 

1064 

1065 Constructors 

1066 ------------ 

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

1068 The agent's income shock distributions. 

1069 

1070 It's default constructor is :func:`HARK.Calibration.Income.IncomeProcesses.construct_lognormal_income_process_unemployment` 

1071 aXtraGrid: Constructor 

1072 The agent's asset grid. 

1073 

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

1075 

1076 Solving Parameters 

1077 ------------------ 

1078 cycles: int 

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

1080 T_cycle: int 

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

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

1083 Coefficient of Relative Risk Aversion. 

1084 BeqCRRA: float, :math:`\rho_{Beq}` 

1085 Coefficient of Relative Risk Aversion for the bequest motive. 

1086 If this value isn't the same as CRRA, then the model can only be represented as a Bellman equation. 

1087 This may cause unintented behavior. 

1088 BeqCRRATerm: float, :math:`\rho_{Beq}` 

1089 The Coefficient of Relative Risk Aversion for the bequest motive, but only in the terminal period. 

1090 In most cases this should be the same as beqCRRA. 

1091 BeqShift: float, :math:`\textbf{BeqShift}` 

1092 The Shift term from the bequest motive's utility function. 

1093 If this value isn't 0, then the model can only be represented as a Bellman equation. 

1094 This may cause unintented behavior. 

1095 BeqShiftTerm: float, :math:`\textbf{BeqShift}` 

1096 The shift term from the bequest motive's utility function, in the terminal period. 

1097 In most cases this should be the same as beqShift 

1098 BeqFac: float, :math:`\textbf{BeqFac}` 

1099 The weight for the bequest's utility function. 

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

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

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

1103 Intertemporal discount factor. 

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

1105 Survival probability after each period. 

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

1107 Permanent income growth factor. 

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

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

1110 vFuncBool: bool 

1111 Whether to calculate the value function during solution. 

1112 CubicBool: bool 

1113 Whether to use cubic spline interpoliation. 

1114 

1115 Simulation Parameters 

1116 --------------------- 

1117 AgentCount: int 

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

1119 T_age: int 

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

1121 T_sim: int, required for simulation 

1122 Number of periods to simulate. 

1123 track_vars: list[strings] 

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

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

1126 

1127 PermShk is the agent's permanent income shock 

1128 

1129 TranShk is the agent's transitory income shock 

1130 

1131 aLvl is the nominal asset level 

1132 

1133 aNrm is the normalized assets 

1134 

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

1136 

1137 cNrm is the normalized consumption 

1138 

1139 mNrm is the normalized market resources 

1140 

1141 pLvl is the permanent income level 

1142 

1143 who_dies is the array of which agents died 

1144 aNrmInitMean: float 

1145 Mean of Log initial Normalized Assets. 

1146 aNrmInitStd: float 

1147 Std of Log initial Normalized Assets. 

1148 pLvlInitMean: float 

1149 Mean of Log initial permanent income. 

1150 pLvlInitStd: float 

1151 Std of Log initial permanent income. 

1152 PermGroFacAgg: float 

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

1154 PerfMITShk: boolean 

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

1156 NewbornTransShk: boolean 

1157 Whether Newborns have transitory shock. 

1158 

1159 Attributes 

1160 ---------- 

1161 solution: list[Consumer solution object] 

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

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

1164 

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

1166 history: Dict[Array] 

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

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

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

1170 """ 

1171 

1172 time_inv_ = IndShockConsumerType.time_inv_ + ["BeqCRRA", "BeqShift", "BeqFac"] 

1173 default_ = { 

1174 "params": init_accidental_bequest, 

1175 "solver": solve_one_period_ConsWarmBequest, 

1176 "model": "ConsIndShock.yaml", 

1177 } 

1178 

1179 def pre_solve(self): 

1180 self.construct("solution_terminal") 

1181 

1182 def check_conditions(self, verbose=None): 

1183 raise NotImplementedError() 

1184 

1185 def calc_limiting_values(self): 

1186 raise NotImplementedError() 

1187 

1188 

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

1190 

1191 

1192# Make a dictionary of constructors for the portfolio choice consumer type 

1193portfolio_bequest_constructor_dict = { 

1194 "IncShkDstn": construct_lognormal_income_process_unemployment, 

1195 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

1196 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

1197 "aXtraGrid": make_assets_grid, 

1198 "RiskyDstn": make_lognormal_RiskyDstn, 

1199 "ShockDstn": combine_IncShkDstn_and_RiskyDstn, 

1200 "ShareLimit": calc_ShareLimit_for_CRRA, 

1201 "ShareGrid": make_simple_ShareGrid, 

1202 "AdjustDstn": make_AdjustDstn, 

1203 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

1204 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

1205 "solution_terminal": make_warmglow_portfolio_solution_terminal, 

1206} 

1207 

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

1209default_kNrmInitDstn_params = { 

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

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

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

1213} 

1214 

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

1216default_pLvlInitDstn_params = { 

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

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

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

1220} 

1221 

1222 

1223# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

1224default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

1234} 

1235 

1236# Default parameters to make aXtraGrid using make_assets_grid 

1237default_aXtraGrid_params = { 

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

1239 "aXtraMax": 100, # Maximum end-of-period "assets above minimum" value 

1240 "aXtraNestFac": 1, # Exponential nesting factor for aXtraGrid 

1241 "aXtraCount": 200, # Number of points in the grid of "assets above minimum" 

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

1243} 

1244 

1245# Default parameters to make RiskyDstn with make_lognormal_RiskyDstn (and uniform ShareGrid) 

1246default_RiskyDstn_and_ShareGrid_params = { 

1247 "RiskyAvg": 1.08, # Mean return factor of risky asset 

1248 "RiskyStd": 0.18362634887, # Stdev of log returns on risky asset 

1249 "RiskyCount": 5, # Number of integration nodes to use in approximation of risky returns 

1250 "ShareCount": 25, # Number of discrete points in the risky share approximation 

1251} 

1252 

1253# Make a dictionary to specify a risky asset consumer type 

1254init_portfolio_bequest = { 

1255 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1258 "constructors": portfolio_bequest_constructor_dict, # See dictionary above 

1259 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

1260 "CRRA": 5.0, # Coefficient of relative risk aversion 

1261 "Rfree": [1.03], # Return factor on risk free asset 

1262 "DiscFac": 0.90, # Intertemporal discount factor 

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

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

1265 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

1266 "BeqCRRA": 2.0, # Coefficient of relative risk aversion for bequest motive 

1267 "BeqFac": 40.0, # Scaling factor for bequest motive 

1268 "BeqShift": 0.0, # Stone-Geary shifter term for bequest motive 

1269 "BeqCRRATerm": 2.0, # Coefficient of relative risk aversion for bequest motive, terminal period only 

1270 "BeqFacTerm": 40.0, # Scaling factor for bequest motive, terminal period only 

1271 "BeqShiftTerm": 0.0, # Stone-Geary shifter term for bequest motive, terminal period only 

1272 "DiscreteShareBool": False, # Whether risky asset share is restricted to discrete values 

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

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

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

1276 "IndepDstnBool": True, # Indicator for whether return & income shocks are independent 

1277 "PortfolioBool": True, # Whether this agent has portfolio choice 

1278 "PortfolioBisect": False, # What does this do? 

1279 "AdjustPrb": 1.0, # Probability that the agent can update their risky portfolio share each period 

1280 "RiskyShareFixed": None, # This just needs to exist because of inheritance, does nothing 

1281 "sim_common_Rrisky": True, # Whether risky returns have a shared/common value across agents 

1282 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1288 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1292} 

1293init_portfolio_bequest.update(default_kNrmInitDstn_params) 

1294init_portfolio_bequest.update(default_pLvlInitDstn_params) 

1295init_portfolio_bequest.update(default_IncShkDstn_params) 

1296init_portfolio_bequest.update(default_aXtraGrid_params) 

1297init_portfolio_bequest.update(default_RiskyDstn_and_ShareGrid_params) 

1298 

1299 

1300class BequestWarmGlowPortfolioType(PortfolioConsumerType): 

1301 r""" 

1302 A consumer type with based on PortfolioConsumerType, with an additional bequest motive. 

1303 They gain utility for any wealth they leave when they die, according to a Stone-Geary utility. 

1304 

1305 .. math:: 

1306 \newcommand{\CRRA}{\rho} 

1307 \newcommand{\DiePrb}{\mathsf{D}} 

1308 \newcommand{\PermGroFac}{\Gamma} 

1309 \newcommand{\Rfree}{\mathsf{R}} 

1310 \newcommand{\DiscFac}{\beta} 

1311 \begin{align*} 

1312 v_t(m_t,S_t) &= \max_{c_t,S^{*}_t} u(c_t) + \DiePrb_{t+1} u_{Beq}(a_t)+ \DiscFac (1-\DiePrb_{t+1}) \mathbb{E}_{t} \left[(\PermGroFac_{t+1}\psi_{t+1})^{1-\CRRA} v_{t+1}(m_{t+1},S_{t+1}) \right], \\ 

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

1314 a_t &= m_t - c_t, \\ 

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

1316 m_{t+1} &= \mathsf{R}_{t+1}/(\PermGroFac_{t+1} \psi_{t+1}) a_t + \theta_{t+1}, \\ 

1317 \mathsf{R}_{t+1} &=S_t\phi_{t+1}\mathbf{R}_{t+1}+ (1-S_t)\mathsf{R}_{t+1}, \\ 

1318 S_{t+1} &= \begin{cases} 

1319 S^{*}_t & \text{if } p_t < \wp\\ 

1320 S_t & \text{if } p_t \geq \wp, 

1321 \end{cases}\\ 

1322 (\psi_{t+1},\theta_{t+1},\phi_{t+1},p_t) &\sim F_{t+1}, \\ 

1323 \mathbb{E}[\psi]=\mathbb{E}[\theta] &= 1. \\ 

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

1325 u_{Beq} (a) &= \textbf{BeqFac} \frac{(a+\textbf{BeqShift})^{1-\CRRA_{Beq}}}{1-\CRRA_{Beq}} \\ 

1326 \end{align*} 

1327 

1328 

1329 Constructors 

1330 ------------ 

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

1332 The agent's income shock distributions. 

1333 

1334 It's default constructor is :func:`HARK.Calibration.Income.IncomeProcesses.construct_lognormal_income_process_unemployment` 

1335 aXtraGrid: Constructor 

1336 The agent's asset grid. 

1337 

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

1339 ShareGrid: Constructor 

1340 The agent's risky asset share grid 

1341 

1342 It's default constructor is :func:`HARK.ConsumptionSaving.ConsRiskyAssetModel.make_simple_ShareGrid` 

1343 RiskyDstn: Constructor, :math:`\phi` 

1344 The agent's asset shock distribution for risky assets. 

1345 

1346 It's default constructor is :func:`HARK.Calibration.Assets.AssetProcesses.make_lognormal_RiskyDstn` 

1347 

1348 Solving Parameters 

1349 ------------------ 

1350 cycles: int 

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

1352 T_cycle: int 

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

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

1355 Coefficient of Relative Risk Aversion. 

1356 BeqCRRA: float, :math:`\rho_{Beq}` 

1357 Coefficient of Relative Risk Aversion for the bequest motive. 

1358 If this value isn't the same as CRRA, then the model can only be represented as a Bellman equation. 

1359 This may cause unintented behavior. 

1360 BeqCRRATerm: float, :math:`\rho_{Beq}` 

1361 The Coefficient of Relative Risk Aversion for the bequest motive, but only in the terminal period. 

1362 In most cases this should be the same as beqCRRA. 

1363 BeqShift: float, :math:`\textbf{BeqShift}` 

1364 The Shift term from the bequest motive's utility function. 

1365 If this value isn't 0, then the model can only be represented as a Bellman equation. 

1366 This may cause unintented behavior. 

1367 BeqShiftTerm: float, :math:`\textbf{BeqShift}` 

1368 The shift term from the bequest motive's utility function, in the terminal period. 

1369 In most cases this should be the same as beqShift 

1370 BeqFac: float, :math:`\textbf{BeqFac}` 

1371 The weight for the bequest's utility function. 

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

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

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

1375 Intertemporal discount factor. 

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

1377 Survival probability after each period. 

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

1379 Permanent income growth factor. 

1380 BoroCnstArt: float, default=0.0, :math:`\underline{a}` 

1381 The minimum Asset/Perminant Income ratio. for this agent, BoroCnstArt must be 0. 

1382 vFuncBool: bool 

1383 Whether to calculate the value function during solution. 

1384 CubicBool: bool 

1385 Whether to use cubic spline interpoliation. 

1386 AdjustPrb: float or list[float], time varying 

1387 Must be between 0 and 1. Probability that the agent can update their risky portfolio share each period. Pass a list of floats to make AdjustPrb time varying. 

1388 

1389 Simulation Parameters 

1390 --------------------- 

1391 sim_common_Rrisky: Boolean 

1392 Whether risky returns have a shared/common value across agents. If True, Risky return's can't be time varying. 

1393 AgentCount: int 

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

1395 T_age: int 

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

1397 T_sim: int, required for simulation 

1398 Number of periods to simulate. 

1399 track_vars: list[strings] 

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

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

1402 

1403 Adjust is the array of which agents can adjust 

1404 

1405 PermShk is the agent's permanent income shock 

1406 

1407 Risky is the agent's risky asset shock 

1408 

1409 TranShk is the agent's transitory income shock 

1410 

1411 aLvl is the nominal asset level 

1412 

1413 aNrm is the normalized assets 

1414 

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

1416 

1417 cNrm is the normalized consumption 

1418 

1419 mNrm is the normalized market resources 

1420 

1421 pLvl is the permanent income level 

1422 

1423 who_dies is the array of which agents died 

1424 aNrmInitMean: float 

1425 Mean of Log initial Normalized Assets. 

1426 aNrmInitStd: float 

1427 Std of Log initial Normalized Assets. 

1428 pLvlInitMean: float 

1429 Mean of Log initial permanent income. 

1430 pLvlInitStd: float 

1431 Std of Log initial permanent income. 

1432 PermGroFacAgg: float 

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

1434 PerfMITShk: boolean 

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

1436 NewbornTransShk: boolean 

1437 Whether Newborns have transitory shock. 

1438 

1439 Attributes 

1440 ---------- 

1441 solution: list[Consumer solution object] 

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

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

1444 

1445 Visit :class:`HARK.ConsumptionSaving.ConsPortfolioModel.PortfolioSolution` for more information about the solution. 

1446 

1447 history: Dict[Array] 

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

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

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

1451 """ 

1452 

1453 time_inv_ = PortfolioConsumerType.time_inv_ + ["BeqCRRA", "BeqShift", "BeqFac"] 

1454 default_ = { 

1455 "params": init_portfolio_bequest, 

1456 "solver": solve_one_period_ConsPortfolioWarmGlow, 

1457 "model": "ConsRiskyAsset.yaml", 

1458 }