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

356 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-02 05:14 +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 

13from copy import deepcopy 

14 

15import numpy as np 

16 

17from HARK import NullFunc 

18from HARK.Calibration.Income.IncomeProcesses import ( 

19 construct_lognormal_income_process_unemployment, 

20 get_PermShkDstn_from_IncShkDstn, 

21 get_TranShkDstn_from_IncShkDstn, 

22) 

23from HARK.ConsumptionSaving.ConsIndShockModel import ( 

24 ConsumerSolution, 

25 IndShockConsumerType, 

26 make_basic_CRRA_solution_terminal, 

27 make_lognormal_kNrm_init_dstn, 

28 make_lognormal_pLvl_init_dstn, 

29) 

30from HARK.Calibration.Assets.AssetProcesses import ( 

31 make_lognormal_RiskyDstn, 

32 combine_IncShkDstn_and_RiskyDstn, 

33 calc_ShareLimit_for_CRRA, 

34) 

35from HARK.ConsumptionSaving.ConsPortfolioModel import ( 

36 PortfolioConsumerType, 

37 PortfolioSolution, 

38 make_portfolio_solution_terminal, 

39 make_AdjustDstn, 

40) 

41from HARK.ConsumptionSaving.ConsRiskyAssetModel import make_simple_ShareGrid 

42from HARK.distributions import expected 

43from HARK.interpolation import ( 

44 BilinearInterp, 

45 ConstantFunction, 

46 CubicInterp, 

47 IdentityFunction, 

48 LinearInterp, 

49 LinearInterpOnInterp1D, 

50 LowerEnvelope, 

51 MargMargValueFuncCRRA, 

52 MargValueFuncCRRA, 

53 ValueFuncCRRA, 

54) 

55from HARK.rewards import UtilityFuncCRRA, UtilityFuncStoneGeary 

56from HARK.utilities import make_assets_grid 

57 

58 

59def make_bequest_solution_terminal( 

60 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

61): 

62 """ 

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

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

65 then the terminal period solution is identical to ConsIndShock. 

66 

67 Parameters 

68 ---------- 

69 CRRA : float 

70 Coefficient on relative risk aversion over consumption. 

71 BeqCRRATerm : float 

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

73 BeqFacTerm : float 

74 Scaling factor for the terminal warm glow bequest motive. 

75 BeqShiftTerm : float 

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

77 aXtraGrid : np.array 

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

79 

80 Returns 

81 ------- 

82 solution_terminal : ConsumerSolution 

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

84 """ 

85 if BeqFacTerm == 0.0: # No terminal bequest 

86 solution_terminal = make_basic_CRRA_solution_terminal(CRRA) 

87 return solution_terminal 

88 

89 utility = UtilityFuncCRRA(CRRA) 

90 warm_glow = UtilityFuncStoneGeary( 

91 BeqCRRATerm, 

92 factor=BeqFacTerm, 

93 shifter=BeqShiftTerm, 

94 ) 

95 

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

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

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

99 cNrmGridW0 = np.append(0.0, cNrmGrid) 

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

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

102 

103 cFunc_term = LinearInterp(mNrmGridW0, cNrmGridW0) 

104 vNvrsFunc_term = LinearInterp(mNrmGridW0, vNvrsGridW0) 

105 vFunc_term = ValueFuncCRRA(vNvrsFunc_term, CRRA) 

106 vPfunc_term = MargValueFuncCRRA(cFunc_term, CRRA) 

107 vPPfunc_term = MargMargValueFuncCRRA(cFunc_term, CRRA) 

108 

109 solution_terminal = ConsumerSolution( 

110 cFunc=cFunc_term, 

111 vFunc=vFunc_term, 

112 vPfunc=vPfunc_term, 

113 vPPfunc=vPPfunc_term, 

114 mNrmMin=0.0, 

115 hNrm=0.0, 

116 MPCmax=1.0, 

117 MPCmin=1.0, 

118 ) 

119 return solution_terminal 

120 

121 

122def make_warmglow_portfolio_solution_terminal( 

123 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

124): 

125 """ 

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

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

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

129 

130 Parameters 

131 ---------- 

132 CRRA : float 

133 Coefficient on relative risk aversion over consumption. 

134 BeqCRRATerm : float 

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

136 BeqFacTerm : float 

137 Scaling factor for the terminal warm glow bequest motive. 

138 BeqShiftTerm : float 

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

140 aXtraGrid : np.array 

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

142 

143 Returns 

144 ------- 

145 solution_terminal : ConsumerSolution 

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

147 """ 

148 if BeqFacTerm == 0.0: # No terminal bequest 

149 solution_terminal = make_portfolio_solution_terminal(CRRA) 

150 return solution_terminal 

151 

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

153 solution_terminal_no_port = make_bequest_solution_terminal( 

154 CRRA, BeqCRRATerm, BeqFacTerm, BeqShiftTerm, aXtraGrid 

155 ) 

156 

157 # Take consumption function from the no portfolio choice solution 

158 cFuncAdj_terminal = solution_terminal_no_port.cFunc 

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

160 

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

162 ShareFuncAdj_terminal = ConstantFunction(0.0) 

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

164 

165 # Value function is simply utility from consuming market resources 

166 vFuncAdj_terminal = solution_terminal_no_port.vFunc 

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

168 

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

170 vPfuncAdj_terminal = solution_terminal_no_port.vPfunc 

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

172 # No future, no marg value of Share 

173 dvdsFuncFxd_terminal = ConstantFunction(0.0) 

174 

175 # Construct the terminal period solution 

176 solution_terminal = PortfolioSolution( 

177 cFuncAdj=cFuncAdj_terminal, 

178 ShareFuncAdj=ShareFuncAdj_terminal, 

179 vFuncAdj=vFuncAdj_terminal, 

180 vPfuncAdj=vPfuncAdj_terminal, 

181 cFuncFxd=cFuncFxd_terminal, 

182 ShareFuncFxd=ShareFuncFxd_terminal, 

183 vFuncFxd=vFuncFxd_terminal, 

184 dvdmFuncFxd=dvdmFuncFxd_terminal, 

185 dvdsFuncFxd=dvdsFuncFxd_terminal, 

186 ) 

187 return solution_terminal 

188 

189 

190############################################################################### 

191 

192 

193def solve_one_period_ConsWarmBequest( 

194 solution_next, 

195 IncShkDstn, 

196 LivPrb, 

197 DiscFac, 

198 CRRA, 

199 Rfree, 

200 PermGroFac, 

201 BoroCnstArt, 

202 aXtraGrid, 

203 BeqCRRA, 

204 BeqFac, 

205 BeqShift, 

206 CubicBool, 

207 vFuncBool, 

208): 

209 """ 

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

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

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

213 utility based on their terminal wealth upon death. 

214 

215 Parameters 

216 ---------- 

217 solution_next : ConsumerSolution 

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

219 IncShkDstn : distribution.Distribution 

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

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

222 LivPrb : float 

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

224 the succeeding period. 

225 DiscFac : float 

226 Intertemporal discount factor for future utility. 

227 CRRA : float 

228 Coefficient of relative risk aversion. 

229 Rfree : float 

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

231 PermGroFac : float 

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

233 BoroCnstArt: float or None 

234 Borrowing constraint for the minimum allowable assets to end the 

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

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

237 rowing constraint. 

238 aXtraGrid : np.array 

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

240 absolute minimum acceptable level. 

241 BeqCRRA : float 

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

243 BeqFac : float 

244 Multiplicative intensity factor for the warm glow bequest motive. 

245 BeqShift : float 

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

247 CubicBool : bool 

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

249 vFuncBool: boolean 

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

251 included in the reported solution. 

252 

253 Returns 

254 ------- 

255 solution_now : ConsumerSolution 

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

257 """ 

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

259 uFunc = UtilityFuncCRRA(CRRA) 

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

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

262 warm_glow = UtilityFuncStoneGeary(BeqCRRA, BeqFacEff, BeqShift) 

263 

264 # Unpack next period's income shock distribution 

265 ShkPrbsNext = IncShkDstn.pmv 

266 PermShkValsNext = IncShkDstn.atoms[0] 

267 TranShkValsNext = IncShkDstn.atoms[1] 

268 PermShkMinNext = np.min(PermShkValsNext) 

269 TranShkMinNext = np.min(TranShkValsNext) 

270 

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

272 IncNext = PermShkValsNext * TranShkValsNext 

273 WorstIncNext = PermShkMinNext * TranShkMinNext 

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

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

276 

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

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

279 vPfuncNext = solution_next.vPfunc 

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

281 

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

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

284 try: 

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

286 except: 

287 MPCminNow = 0.0 

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

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

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

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

292 cFuncLimitIntercept = MPCminNow * hNrmNow 

293 cFuncLimitSlope = MPCminNow 

294 

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

296 PermGroFacEffMin = (PermGroFac * PermShkMinNext) / Rfree 

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

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

299 

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

301 # and artificial borrowing constraints 

302 if BoroCnstArt is None: 

303 mNrmMinNow = BoroCnstNat 

304 else: 

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

306 

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

308 # or artificial borrowing constraint actually binds 

309 if BoroCnstNat < mNrmMinNow: 

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

311 else: 

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

313 

314 # Define the borrowing-constrained consumption function 

315 cFuncNowCnst = LinearInterp( 

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

317 ) 

318 

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

320 aNrmNow = np.asarray(aXtraGrid) + BoroCnstNat 

321 

322 # Define local functions for taking future expectations 

323 def calc_mNrmNext(S, a, R): 

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

325 

326 def calc_vNext(S, a, R): 

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

328 calc_mNrmNext(S, a, R) 

329 ) 

330 

331 def calc_vPnext(S, a, R): 

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

333 

334 def calc_vPPnext(S, a, R): 

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

336 

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

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

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

340 EndOfPrdvP += warm_glow.der(aNrmNow) 

341 

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

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

344 mNrmNow = cNrmNow + aNrmNow # Endogenous mNrm gridpoints 

345 

346 # Limiting consumption is zero as m approaches mNrmMin 

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

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

349 

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

351 if CubicBool: 

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

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

354 EndOfPrdvPP = vPPfacEff * expected( 

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

356 ) 

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

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

359 MPC = dcda / (dcda + 1.0) 

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

361 

362 # Construct the unconstrained consumption function as a cubic interpolation 

363 cFuncNowUnc = CubicInterp( 

364 m_for_interpolation, 

365 c_for_interpolation, 

366 MPC_for_interpolation, 

367 cFuncLimitIntercept, 

368 cFuncLimitSlope, 

369 ) 

370 else: 

371 # Construct the unconstrained consumption function as a linear interpolation 

372 cFuncNowUnc = LinearInterp( 

373 m_for_interpolation, 

374 c_for_interpolation, 

375 cFuncLimitIntercept, 

376 cFuncLimitSlope, 

377 ) 

378 

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

380 # LowerEnvelope should only be used when BoroCnstArt is True 

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

382 

383 # Make the marginal value function 

384 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

385 

386 # Define this period's marginal marginal value function 

387 if CubicBool: 

388 vPPfuncNow = MargMargValueFuncCRRA(cFuncNow, CRRA) 

389 else: 

390 vPPfuncNow = NullFunc() # Dummy object 

391 

392 # Construct this period's value function if requested 

393 if vFuncBool: 

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

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

396 EndOfPrdv += warm_glow(aNrmNow) 

397 EndOfPrdvNvrs = uFunc.inv(EndOfPrdv) 

398 # value transformed through inverse utility 

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

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

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

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

403 

404 # Construct the end-of-period value function 

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

406 EndOfPrd_vNvrsFunc = CubicInterp(aNrm_temp, EndOfPrdvNvrs, EndOfPrdvNvrsP) 

407 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

408 

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

410 mNrm_temp = mNrmMinNow + aXtraGrid 

411 cNrm_temp = cFuncNow(mNrm_temp) 

412 aNrm_temp = mNrm_temp - cNrm_temp 

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

414 vP_temp = uFunc.der(cNrm_temp) 

415 

416 # Construct the beginning-of-period value function 

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

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

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

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

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

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

423 vNvrsFuncNow = CubicInterp( 

424 mNrm_temp, vNvrs_temp, vNvrsP_temp, MPCminNvrs * hNrmNow, MPCminNvrs 

425 ) 

426 vFuncNow = ValueFuncCRRA(vNvrsFuncNow, CRRA) 

427 else: 

428 vFuncNow = NullFunc() # Dummy object 

429 

430 # Create and return this period's solution 

431 solution_now = ConsumerSolution( 

432 cFunc=cFuncNow, 

433 vFunc=vFuncNow, 

434 vPfunc=vPfuncNow, 

435 vPPfunc=vPPfuncNow, 

436 mNrmMin=mNrmMinNow, 

437 hNrm=hNrmNow, 

438 MPCmin=MPCminNow, 

439 MPCmax=MPCmaxEff, 

440 ) 

441 return solution_now 

442 

443 

444############################################################################### 

445 

446 

447def solve_one_period_ConsPortfolioWarmGlow( 

448 solution_next, 

449 IncShkDstn, 

450 RiskyDstn, 

451 LivPrb, 

452 DiscFac, 

453 CRRA, 

454 Rfree, 

455 PermGroFac, 

456 BoroCnstArt, 

457 aXtraGrid, 

458 ShareGrid, 

459 AdjustPrb, 

460 ShareLimit, 

461 vFuncBool, 

462 DiscreteShareBool, 

463 BeqCRRA, 

464 BeqFac, 

465 BeqShift, 

466): 

467 """ 

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

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

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

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

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

473 

474 Parameters 

475 ---------- 

476 solution_next : PortfolioSolution 

477 Solution to next period's problem. 

478 ShockDstn : Distribution 

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

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

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

482 independent. 

483 IncShkDstn : Distribution 

484 Discrete distribution of permanent income shocks and transitory income 

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

486 that income and return distributions are independent. 

487 RiskyDstn : Distribution 

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

489 IndepDstnBool is True, indicating that income and return distributions 

490 are independent. 

491 LivPrb : float 

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

493 the succeeding period. 

494 DiscFac : float 

495 Intertemporal discount factor for future utility. 

496 CRRA : float 

497 Coefficient of relative risk aversion. 

498 Rfree : float 

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

500 PermGroFac : float 

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

502 BoroCnstArt: float or None 

503 Borrowing constraint for the minimum allowable assets to end the 

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

505 aXtraGrid: np.array 

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

507 absolute minimum acceptable level. 

508 ShareGrid : np.array 

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

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

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

512 AdjustPrb : float 

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

514 ShareLimit : float 

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

516 vFuncBool: boolean 

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

518 included in the reported solution. 

519 DiscreteShareBool : bool 

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

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

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

523 vFuncBool must also be True. 

524 IndepDstnBool : bool 

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

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

527 BeqCRRA : float 

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

529 BeqFac : float 

530 Multiplicative intensity factor for the warm glow bequest motive. 

531 BeqShift : float 

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

533 

534 Returns 

535 ------- 

536 solution_now : PortfolioSolution 

537 Solution to this period's problem. 

538 """ 

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

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

541 if BoroCnstArt != 0.0: 

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

543 

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

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

546 if DiscreteShareBool and (not vFuncBool): 

547 raise ValueError( 

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

549 ) 

550 

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

552 uFunc = UtilityFuncCRRA(CRRA) 

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

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

555 warm_glow = UtilityFuncStoneGeary(BeqCRRA, BeqFacEff, BeqShift) 

556 

557 # Unpack next period's solution for easier access 

558 vPfuncAdj_next = solution_next.vPfuncAdj 

559 dvdmFuncFxd_next = solution_next.dvdmFuncFxd 

560 dvdsFuncFxd_next = solution_next.dvdsFuncFxd 

561 vFuncAdj_next = solution_next.vFuncAdj 

562 vFuncFxd_next = solution_next.vFuncFxd 

563 

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

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

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

567 BeqFac != 0.0 and BeqShift == 0.0 

568 ) 

569 

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

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

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

573 # experience next period. 

574 

575 # Unpack the risky return shock distribution 

576 Risky_next = RiskyDstn.atoms 

577 RiskyMax = np.max(Risky_next) 

578 RiskyMin = np.min(Risky_next) 

579 

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

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

582 if BoroCnstNat_iszero: 

583 aNrmGrid = aXtraGrid 

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

585 else: 

586 # Add an asset point at exactly zero 

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

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

589 

590 # Get grid and shock sizes, for easier indexing 

591 aNrmCount = aNrmGrid.size 

592 ShareCount = ShareGrid.size 

593 

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

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

596 

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

598 def calc_mNrm_next(S, b): 

599 """ 

600 Calculate future realizations of market resources mNrm from the income 

601 shock distribution S and normalized bank balances b. 

602 """ 

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

604 

605 def calc_dvdm_next(S, b, z): 

606 """ 

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

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

609 values of the risky share z. 

610 """ 

611 mNrm_next = calc_mNrm_next(S, b) 

612 dvdmAdj_next = vPfuncAdj_next(mNrm_next) 

613 

614 if AdjustPrb < 1.0: 

615 # Expand to the same dimensions as mNrm 

616 Share_next_expanded = z + np.zeros_like(mNrm_next) 

617 dvdmFxd_next = dvdmFuncFxd_next(mNrm_next, Share_next_expanded) 

618 # Combine by adjustment probability 

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

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

621 dvdm_next = dvdmAdj_next 

622 

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

624 return dvdm_next 

625 

626 def calc_dvds_next(S, b, z): 

627 """ 

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

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

630 the risky share z. 

631 """ 

632 mNrm_next = calc_mNrm_next(S, b) 

633 

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

635 dvdsAdj_next = np.zeros_like(mNrm_next) 

636 

637 if AdjustPrb < 1.0: 

638 # Expand to the same dimensions as mNrm 

639 Share_next_expanded = z + np.zeros_like(mNrm_next) 

640 dvdsFxd_next = dvdsFuncFxd_next(mNrm_next, Share_next_expanded) 

641 # Combine by adjustment probability 

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

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

644 dvds_next = dvdsAdj_next 

645 

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

647 return dvds_next 

648 

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

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

651 # values across income and risky return shocks. 

652 

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

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

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

656 dvdbNvrsFunc_intermed = BilinearInterp(dvdbNvrs_intermed, bNrmGrid, ShareGrid) 

657 dvdbFunc_intermed = MargValueFuncCRRA(dvdbNvrsFunc_intermed, CRRA) 

658 

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

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

661 dvdsFunc_intermed = BilinearInterp(dvds_intermed, bNrmGrid, ShareGrid) 

662 

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

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

665 

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

667 def calc_EndOfPrd_dvda(S, a, z): 

668 """ 

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

670 on risky asset return S and risky share z. 

671 """ 

672 # Calculate future realizations of bank balances bNrm 

673 Rxs = S - Rfree # Excess returns 

674 Rport = Rfree + z * Rxs # Portfolio return 

675 bNrm_next = Rport * a 

676 

677 # Ensure shape concordance 

678 z_rep = z + np.zeros_like(bNrm_next) 

679 

680 # Calculate and return dvda 

681 EndOfPrd_dvda = Rport * dvdbFunc_intermed(bNrm_next, z_rep) 

682 return EndOfPrd_dvda 

683 

684 def EndOfPrddvds_dist(S, a, z): 

685 """ 

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

687 on risky asset return S and risky share z. 

688 """ 

689 # Calculate future realizations of bank balances bNrm 

690 Rxs = S - Rfree # Excess returns 

691 Rport = Rfree + z * Rxs # Portfolio return 

692 bNrm_next = Rport * a 

693 

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

695 z_rep = z + np.zeros_like(bNrm_next) 

696 

697 # Calculate and return dvds 

698 EndOfPrd_dvds = Rxs * a * dvdbFunc_intermed( 

699 bNrm_next, z_rep 

700 ) + dvdsFunc_intermed(bNrm_next, z_rep) 

701 return EndOfPrd_dvds 

702 

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

704 

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

706 EndOfPrd_dvda = DiscFacEff * expected( 

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

708 ) 

709 warm_glow_der = warm_glow.der(aNrmNow) 

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

711 EndOfPrd_dvdaNvrs = uFunc.derinv(EndOfPrd_dvda) 

712 

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

714 EndOfPrd_dvds = DiscFacEff * expected( 

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

716 ) 

717 

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

719 if vFuncBool: 

720 

721 def calc_v_intermed(S, b, z): 

722 """ 

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

724 income shocks S, and the risky asset share. 

725 """ 

726 mNrm_next = calc_mNrm_next(S, b) 

727 

728 vAdj_next = vFuncAdj_next(mNrm_next) 

729 if AdjustPrb < 1.0: 

730 vFxd_next = vFuncFxd_next(mNrm_next, z) 

731 # Combine by adjustment probability 

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

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

734 v_next = vAdj_next 

735 

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

737 return v_intermed 

738 

739 # Calculate intermediate value by taking expectations over income shocks 

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

741 

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

743 vNvrs_intermed = uFunc.inv(v_intermed) 

744 vNvrsFunc_intermed = BilinearInterp(vNvrs_intermed, bNrmGrid, ShareGrid) 

745 vFunc_intermed = ValueFuncCRRA(vNvrsFunc_intermed, CRRA) 

746 

747 def calc_EndOfPrd_v(S, a, z): 

748 # Calculate future realizations of bank balances bNrm 

749 Rxs = S - Rfree 

750 Rport = Rfree + z * Rxs 

751 bNrm_next = Rport * a 

752 

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

754 # that the function can be vectorized 

755 z_rep = z + np.zeros_like(bNrm_next) 

756 

757 EndOfPrd_v = vFunc_intermed(bNrm_next, z_rep) 

758 return EndOfPrd_v 

759 

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

761 EndOfPrd_v = DiscFacEff * expected( 

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

763 ) 

764 EndOfPrd_v += warm_glow(aNrmNow) 

765 EndOfPrd_vNvrs = uFunc.inv(EndOfPrd_v) 

766 

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

768 EndOfPrd_vNvrsFunc = BilinearInterp(EndOfPrd_vNvrs, aNrmGrid, ShareGrid) 

769 EndOfPrd_vFunc = ValueFuncCRRA(EndOfPrd_vNvrsFunc, CRRA) 

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

771 

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

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

774 if DiscreteShareBool: 

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

776 # the one with highest value for each aNrm gridpoint 

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

778 ShareAdj_now = ShareGrid[opt_idx] 

779 

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

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

782 

783 else: 

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

785 # order condition EndOfPrd_dvds == 0. 

786 FOC_s = EndOfPrd_dvds # Relabel for convenient typing 

787 

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

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

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

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

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

793 

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

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

796 a_idx = np.arange(aNrmCount) 

797 bot_s = ShareGrid[share_idx] 

798 top_s = ShareGrid[share_idx + 1] 

799 bot_f = FOC_s[a_idx, share_idx] 

800 top_f = FOC_s[a_idx, share_idx + 1] 

801 bot_c = EndOfPrd_dvdaNvrs[a_idx, share_idx] 

802 top_c = EndOfPrd_dvdaNvrs[a_idx, share_idx + 1] 

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

804 

805 # Calculate the continuous optimal risky share and optimal consumption 

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

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

808 

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

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

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

812 constrained_bot = FOC_s[:, 0] < 0.0 

813 

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

815 # constraint should never be relevant) 

816 ShareAdj_now[constrained_top] = 1.0 

817 ShareAdj_now[constrained_bot] = 0.0 

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

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

820 

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

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

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

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

825 if not BoroCnstNat_iszero: 

826 ShareAdj_now[0] = 1.0 

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

828 

829 # Construct functions characterizing the solution for this period 

830 

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

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

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

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

835 cFuncAdj_now = LinearInterp(mNrmAdj_now, cNrmAdj_now) 

836 

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

838 vPfuncAdj_now = MargValueFuncCRRA(cFuncAdj_now, CRRA) 

839 

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

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

842 cFuncFxd_by_Share = [] 

843 dvdsFuncFxd_by_Share = [] 

844 for j in range(ShareCount): 

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

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

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

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

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

850 cFuncFxd_now = LinearInterpOnInterp1D(cFuncFxd_by_Share, ShareGrid) 

851 dvdsFuncFxd_now = LinearInterpOnInterp1D(dvdsFuncFxd_by_Share, ShareGrid) 

852 

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

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

855 

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

857 dvdmFuncFxd_now = MargValueFuncCRRA(cFuncFxd_now, CRRA) 

858 

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

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

861 if DiscreteShareBool: 

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

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

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

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

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

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

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

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

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

871 ShareFuncAdj_now = LinearInterp(mNrmAdj_comb, Share_comb) 

872 

873 else: 

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

875 if BoroCnstNat_iszero: 

876 Share_lower_bound = ShareLimit 

877 else: 

878 Share_lower_bound = 1.0 

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

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

881 

882 # This is a point at which (a,c,share) have consistent length. Take the 

883 # snapshot for storing the grid and values in the solution. 

884 save_points = { 

885 "a": deepcopy(aNrmGrid), 

886 "eop_dvda_adj": uFunc.der(cNrmAdj_now), 

887 "share_adj": deepcopy(ShareAdj_now), 

888 "share_grid": deepcopy(ShareGrid), 

889 "eop_dvda_fxd": uFunc.der(EndOfPrd_dvda), 

890 "eop_dvds_fxd": EndOfPrd_dvds, 

891 } 

892 

893 # Add the value function if requested 

894 if vFuncBool: 

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

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

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

898 

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

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

901 cNrm_temp = cFuncAdj_now(mNrm_temp) 

902 aNrm_temp = mNrm_temp - cNrm_temp 

903 Share_temp = ShareFuncAdj_now(mNrm_temp) 

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

905 vNvrs_temp = uFunc.inv(v_temp) 

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

907 vNvrsFuncAdj = CubicInterp( 

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

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

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

911 ) 

912 # Re-curve the pseudo-inverse value function 

913 vFuncAdj_now = ValueFuncCRRA(vNvrsFuncAdj, CRRA) 

914 

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

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

917 cNrm_temp = cFuncFxd_now(mNrm_temp, Share_temp) 

918 aNrm_temp = mNrm_temp - cNrm_temp 

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

920 vNvrs_temp = uFunc.inv(v_temp) 

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

922 vNvrsFuncFxd_by_Share = [] 

923 for j in range(ShareCount): 

924 vNvrsFuncFxd_by_Share.append( 

925 CubicInterp( 

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

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

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

929 ) 

930 ) 

931 vNvrsFuncFxd = LinearInterpOnInterp1D(vNvrsFuncFxd_by_Share, ShareGrid) 

932 vFuncFxd_now = ValueFuncCRRA(vNvrsFuncFxd, CRRA) 

933 

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

935 vFuncAdj_now = NullFunc() 

936 vFuncFxd_now = NullFunc() 

937 

938 # Package and return the solution 

939 solution_now = PortfolioSolution( 

940 cFuncAdj=cFuncAdj_now, 

941 ShareFuncAdj=ShareFuncAdj_now, 

942 vPfuncAdj=vPfuncAdj_now, 

943 vFuncAdj=vFuncAdj_now, 

944 cFuncFxd=cFuncFxd_now, 

945 ShareFuncFxd=ShareFuncFxd_now, 

946 dvdmFuncFxd=dvdmFuncFxd_now, 

947 dvdsFuncFxd=dvdsFuncFxd_now, 

948 vFuncFxd=vFuncFxd_now, 

949 AdjPrb=AdjustPrb, 

950 # WHAT IS THIS STUFF FOR?? 

951 aGrid=save_points["a"], 

952 Share_adj=save_points["share_adj"], 

953 EndOfPrddvda_adj=save_points["eop_dvda_adj"], 

954 ShareGrid=save_points["share_grid"], 

955 EndOfPrddvda_fxd=save_points["eop_dvda_fxd"], 

956 EndOfPrddvds_fxd=save_points["eop_dvds_fxd"], 

957 ) 

958 return solution_now 

959 

960 

961############################################################################### 

962 

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

964warmglow_constructor_dict = { 

965 "IncShkDstn": construct_lognormal_income_process_unemployment, 

966 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

967 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

968 "aXtraGrid": make_assets_grid, 

969 "solution_terminal": make_bequest_solution_terminal, 

970 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

971 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

972} 

973 

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

975default_kNrmInitDstn_params = { 

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

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

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

979} 

980 

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

982default_pLvlInitDstn_params = { 

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

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

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

986} 

987 

988# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

989default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

999} 

1000 

1001# Default parameters to make aXtraGrid using make_assets_grid 

1002default_aXtraGrid_params = { 

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

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

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

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

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

1008} 

1009 

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

1011init_warm_glow = { 

1012 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1015 "constructors": warmglow_constructor_dict, # See dictionary above 

1016 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1019 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

1022 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

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

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

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

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

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

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

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

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

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

1032 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1038 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1042} 

1043init_warm_glow.update(default_IncShkDstn_params) 

1044init_warm_glow.update(default_aXtraGrid_params) 

1045init_warm_glow.update(default_kNrmInitDstn_params) 

1046init_warm_glow.update(default_pLvlInitDstn_params) 

1047 

1048# Make a dictionary with bequest motives turned off 

1049init_accidental_bequest = init_warm_glow.copy() 

1050init_accidental_bequest["BeqFac"] = 0.0 

1051init_accidental_bequest["BeqShift"] = 0.0 

1052init_accidental_bequest["BeqFacTerm"] = 0.0 

1053init_accidental_bequest["BeqShiftTerm"] = 0.0 

1054 

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

1056init_warm_glow_terminal_only = init_warm_glow.copy() 

1057init_warm_glow_terminal_only["BeqFac"] = 0.0 

1058init_warm_glow_terminal_only["BeqShift"] = 0.0 

1059 

1060 

1061class BequestWarmGlowConsumerType(IndShockConsumerType): 

1062 r""" 

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

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

1065 

1066 .. math:: 

1067 \newcommand{\CRRA}{\rho} 

1068 \newcommand{\DiePrb}{\mathsf{D}} 

1069 \newcommand{\PermGroFac}{\Gamma} 

1070 \newcommand{\Rfree}{\mathsf{R}} 

1071 \newcommand{\DiscFac}{\beta} 

1072 \begin{align*} 

1073 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], \\ 

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

1075 a_t &= m_t - c_t, \\ 

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

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

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

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

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

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

1082 \end{align*} 

1083 

1084 

1085 Constructors 

1086 ------------ 

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

1088 The agent's income shock distributions. 

1089 

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

1091 aXtraGrid: Constructor 

1092 The agent's asset grid. 

1093 

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

1095 

1096 Solving Parameters 

1097 ------------------ 

1098 cycles: int 

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

1100 T_cycle: int 

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

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

1103 Coefficient of Relative Risk Aversion. 

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

1105 Coefficient of Relative Risk Aversion for the bequest motive. 

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

1107 This may cause unintented behavior. 

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

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

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

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

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

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

1114 This may cause unintented behavior. 

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

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

1117 In most cases this should be the same as beqShift 

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

1119 The weight for the bequest's utility function. 

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

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

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

1123 Intertemporal discount factor. 

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

1125 Survival probability after each period. 

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

1127 Permanent income growth factor. 

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

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

1130 vFuncBool: bool 

1131 Whether to calculate the value function during solution. 

1132 CubicBool: bool 

1133 Whether to use cubic spline interpoliation. 

1134 

1135 Simulation Parameters 

1136 --------------------- 

1137 AgentCount: int 

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

1139 T_age: int 

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

1141 T_sim: int, required for simulation 

1142 Number of periods to simulate. 

1143 track_vars: list[strings] 

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

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

1146 

1147 PermShk is the agent's permanent income shock 

1148 

1149 TranShk is the agent's transitory income shock 

1150 

1151 aLvl is the nominal asset level 

1152 

1153 aNrm is the normalized assets 

1154 

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

1156 

1157 cNrm is the normalized consumption 

1158 

1159 mNrm is the normalized market resources 

1160 

1161 pLvl is the permanent income level 

1162 

1163 who_dies is the array of which agents died 

1164 aNrmInitMean: float 

1165 Mean of Log initial Normalized Assets. 

1166 aNrmInitStd: float 

1167 Std of Log initial Normalized Assets. 

1168 pLvlInitMean: float 

1169 Mean of Log initial permanent income. 

1170 pLvlInitStd: float 

1171 Std of Log initial permanent income. 

1172 PermGroFacAgg: float 

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

1174 PerfMITShk: boolean 

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

1176 NewbornTransShk: boolean 

1177 Whether Newborns have transitory shock. 

1178 

1179 Attributes 

1180 ---------- 

1181 solution: list[Consumer solution object] 

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

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

1184 

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

1186 history: Dict[Array] 

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

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

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

1190 """ 

1191 

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

1193 default_ = { 

1194 "params": init_accidental_bequest, 

1195 "solver": solve_one_period_ConsWarmBequest, 

1196 "model": "ConsIndShock.yaml", 

1197 } 

1198 

1199 

1200############################################################################### 

1201 

1202 

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

1204portfolio_bequest_constructor_dict = { 

1205 "IncShkDstn": construct_lognormal_income_process_unemployment, 

1206 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

1207 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

1208 "aXtraGrid": make_assets_grid, 

1209 "RiskyDstn": make_lognormal_RiskyDstn, 

1210 "ShockDstn": combine_IncShkDstn_and_RiskyDstn, 

1211 "ShareLimit": calc_ShareLimit_for_CRRA, 

1212 "ShareGrid": make_simple_ShareGrid, 

1213 "AdjustDstn": make_AdjustDstn, 

1214 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

1215 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

1216 "solution_terminal": make_warmglow_portfolio_solution_terminal, 

1217} 

1218 

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

1220default_kNrmInitDstn_params = { 

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

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

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

1224} 

1225 

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

1227default_pLvlInitDstn_params = { 

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

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

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

1231} 

1232 

1233 

1234# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

1235default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

1245} 

1246 

1247# Default parameters to make aXtraGrid using make_assets_grid 

1248default_aXtraGrid_params = { 

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

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

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

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

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

1254} 

1255 

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

1257default_RiskyDstn_and_ShareGrid_params = { 

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

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

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

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

1262} 

1263 

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

1265init_portfolio_bequest = { 

1266 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1269 "constructors": portfolio_bequest_constructor_dict, # See dictionary above 

1270 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

1273 "DiscFac": 0.90, # Intertemporal discount factor 

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

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

1276 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1293 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

1299 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

1303} 

1304init_portfolio_bequest.update(default_kNrmInitDstn_params) 

1305init_portfolio_bequest.update(default_pLvlInitDstn_params) 

1306init_portfolio_bequest.update(default_IncShkDstn_params) 

1307init_portfolio_bequest.update(default_aXtraGrid_params) 

1308init_portfolio_bequest.update(default_RiskyDstn_and_ShareGrid_params) 

1309 

1310 

1311class BequestWarmGlowPortfolioType(PortfolioConsumerType): 

1312 r""" 

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

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

1315 

1316 .. math:: 

1317 \newcommand{\CRRA}{\rho} 

1318 \newcommand{\DiePrb}{\mathsf{D}} 

1319 \newcommand{\PermGroFac}{\Gamma} 

1320 \newcommand{\Rfree}{\mathsf{R}} 

1321 \newcommand{\DiscFac}{\beta} 

1322 \begin{align*} 

1323 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], \\ 

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

1325 a_t &= m_t - c_t, \\ 

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

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

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

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

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

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

1332 \end{cases}\\ 

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

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

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

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

1337 \end{align*} 

1338 

1339 

1340 Constructors 

1341 ------------ 

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

1343 The agent's income shock distributions. 

1344 

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

1346 aXtraGrid: Constructor 

1347 The agent's asset grid. 

1348 

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

1350 ShareGrid: Constructor 

1351 The agent's risky asset share grid 

1352 

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

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

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

1356 

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

1358 

1359 Solving Parameters 

1360 ------------------ 

1361 cycles: int 

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

1363 T_cycle: int 

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

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

1366 Coefficient of Relative Risk Aversion. 

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

1368 Coefficient of Relative Risk Aversion for the bequest motive. 

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

1370 This may cause unintented behavior. 

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

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

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

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

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

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

1377 This may cause unintented behavior. 

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

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

1380 In most cases this should be the same as beqShift 

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

1382 The weight for the bequest's utility function. 

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

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

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

1386 Intertemporal discount factor. 

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

1388 Survival probability after each period. 

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

1390 Permanent income growth factor. 

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

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

1393 vFuncBool: bool 

1394 Whether to calculate the value function during solution. 

1395 CubicBool: bool 

1396 Whether to use cubic spline interpoliation. 

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

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

1399 

1400 Simulation Parameters 

1401 --------------------- 

1402 sim_common_Rrisky: Boolean 

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

1404 AgentCount: int 

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

1406 T_age: int 

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

1408 T_sim: int, required for simulation 

1409 Number of periods to simulate. 

1410 track_vars: list[strings] 

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

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

1413 

1414 Adjust is the array of which agents can adjust 

1415 

1416 PermShk is the agent's permanent income shock 

1417 

1418 Risky is the agent's risky asset shock 

1419 

1420 TranShk is the agent's transitory income shock 

1421 

1422 aLvl is the nominal asset level 

1423 

1424 aNrm is the normalized assets 

1425 

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

1427 

1428 cNrm is the normalized consumption 

1429 

1430 mNrm is the normalized market resources 

1431 

1432 pLvl is the permanent income level 

1433 

1434 who_dies is the array of which agents died 

1435 aNrmInitMean: float 

1436 Mean of Log initial Normalized Assets. 

1437 aNrmInitStd: float 

1438 Std of Log initial Normalized Assets. 

1439 pLvlInitMean: float 

1440 Mean of Log initial permanent income. 

1441 pLvlInitStd: float 

1442 Std of Log initial permanent income. 

1443 PermGroFacAgg: float 

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

1445 PerfMITShk: boolean 

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

1447 NewbornTransShk: boolean 

1448 Whether Newborns have transitory shock. 

1449 

1450 Attributes 

1451 ---------- 

1452 solution: list[Consumer solution object] 

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

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

1455 

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

1457 

1458 history: Dict[Array] 

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

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

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

1462 """ 

1463 

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

1465 default_ = { 

1466 "params": init_portfolio_bequest, 

1467 "solver": solve_one_period_ConsPortfolioWarmGlow, 

1468 "model": "ConsRiskyAsset.yaml", 

1469 }