Coverage for HARK/ConsumptionSaving/ConsHealthModel.py: 97%

176 statements  

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

1""" 

2Classes to represent consumers who make decisions about health investment. The 

3first model here is adapted from White (2015). 

4""" 

5 

6import numpy as np 

7from HARK.core import AgentType 

8from HARK.distributions import ( 

9 expected, 

10 combine_indep_dstns, 

11 Uniform, 

12 DiscreteDistribution, 

13 DiscreteDistributionLabeled, 

14) 

15from HARK.Calibration.Income.IncomeProcesses import construct_lognormal_wage_dstn 

16from HARK.rewards import CRRAutility, CRRAutility_inv 

17from HARK.interpolation import Curvilinear2DInterp 

18from HARK.utilities import make_assets_grid 

19from HARK.ConsumptionSaving.ConsIndShockModel import make_lognormal_kNrm_init_dstn 

20 

21############################################################################### 

22 

23 

24# Define a function that yields health produced from investment 

25def eval_health_prod(n, alpha, gamma): 

26 return (gamma / alpha) * n**alpha 

27 

28 

29# Define a function that yields health produced from investment 

30def eval_marg_health_prod(n, alpha, gamma): 

31 return gamma * n ** (alpha - 1.0) 

32 

33 

34# Define a function for computing expectations over next period's (marginal) value 

35# from the perspective of end-of-period states, conditional on survival 

36def calc_exp_next(shock, a, H, R, rho, alpha, gamma, funcs): 

37 m_next = R * a + shock["WageRte"] * H 

38 h_next = (1.0 - shock["DeprRte"]) * H 

39 vNvrs_next, c_next, n_next = funcs(m_next, h_next) 

40 dvdm_next = c_next**-rho 

41 dvdh_next = dvdm_next / (gamma * n_next ** (alpha - 1.0)) 

42 v_next = CRRAutility(vNvrs_next, rho=rho) 

43 dvda = R * dvdm_next 

44 dvdH = (1.0 - shock["DeprRte"]) * (shock["WageRte"] * dvdm_next + dvdh_next) 

45 return v_next, dvda, dvdH 

46 

47 

48############################################################################### 

49 

50 

51def solve_one_period_ConsBasicHealth( 

52 solution_next, 

53 DiscFac, 

54 Rfree, 

55 CRRA, 

56 HealthProdExp, 

57 HealthProdFac, 

58 DieProbMax, 

59 ShockDstn, 

60 aLvlGrid, 

61 HLvlGrid, 

62 constrained_N, 

63): 

64 """ 

65 Solve one period of the basic health investment / consumption-saving model 

66 using the endogenous grid method. Policy functions are the consumption function 

67 cFunc and the health investment function nFunc. 

68 

69 Parameters 

70 ---------- 

71 solution_next : Curvilinear2DInterp 

72 Solution to the succeeding period's problem, represented as a multi-function 

73 interpolant with entries vNvrsFunc, cFunc, and nFunc. 

74 DiscFac : float 

75 Intertemporal discount factor, representing beta. 

76 Rfree : float 

77 Risk-free rate of return on retained assets. 

78 CRRA : float 

79 Coefficient of relative risk aversion, representing rho. Assumed to be 

80 constant across periods. Should be strictly between 0 and 1. 

81 HealthProdExp : float 

82 Exponent in health production function; should be strictly b/w 0 and 1. 

83 This corresponds to alpha in White (2015). 

84 HealthProdFac : float 

85 Scaling factor in health production function; should be strictly positive. 

86 This corresponds to gamma in White (2015). 

87 DieProbMax : float 

88 Maximum death probability at the end of this period, if HLvl were exactly zero. 

89 ShockDstn : DiscreteDistribution 

90 Joint distribution of income and depreciation values that could realize 

91 at the start of the next period. 

92 aLvlGrid : np.array 

93 Grid of end-of-period assets (after all actions are accomplished). 

94 HLvlGrid : np.array 

95 Grid of end-of-period post-investment health. 

96 constrained_N : int 

97 Number of additional interpolation nodes to put in the mLvl dimension 

98 on the liquidity-constrained portion of the consumption function. 

99 

100 Returns 

101 ------- 

102 solution_now : dict 

103 Solution to this period's problem, including policy functions cFunc and 

104 nFunc, as well as (marginal) value functions vFunc, dvdmFunc, and dvdhFunc. 

105 """ 

106 # Determine whether there is a liquidity-constrained portion of the policy functions 

107 WageRte_min = np.min(ShockDstn.atoms[0]) 

108 constrained = WageRte_min > 0.0 

109 

110 # Adjust the assets grid if liquidity constraint will bind somewhere 

111 aLvlGrid_temp = np.insert(aLvlGrid, 0, 0.0) if constrained else aLvlGrid 

112 

113 # Make meshes of end-of-period states aLvl and HLvl 

114 (aLvl, HLvl) = np.meshgrid(aLvlGrid_temp, HLvlGrid, indexing="ij") 

115 

116 # Calculate expected (marginal) value conditional on survival 

117 v_next_exp, dvdm_next_exp, dvdh_next_exp = expected( 

118 calc_exp_next, 

119 ShockDstn, 

120 args=(aLvl, HLvl, Rfree, CRRA, HealthProdExp, HealthProdFac, solution_next), 

121 ) 

122 

123 # Calculate (marginal) survival probabilities 

124 LivPrb = 1.0 - DieProbMax / (1.0 + HLvl) 

125 MargLivPrb = DieProbMax / (1.0 + HLvl) ** 2.0 

126 

127 # Calculate end-of-period expectations 

128 EndOfPrd_v = DiscFac * (LivPrb * v_next_exp) 

129 EndOfPrd_dvda = DiscFac * (LivPrb * dvdm_next_exp) 

130 EndOfPrd_dvdH = DiscFac * (LivPrb * dvdh_next_exp + MargLivPrb * v_next_exp) 

131 vP_ratio = EndOfPrd_dvda / EndOfPrd_dvdH 

132 

133 # Invert the first order conditions to find optimal controls when unconstrained 

134 cLvl = EndOfPrd_dvda ** (-1.0 / CRRA) 

135 nLvl = (vP_ratio / HealthProdFac) ** (1.0 / (HealthProdExp - 1.0)) 

136 

137 # If there is a liquidity constrained portion, find additional controls on it 

138 if constrained: 

139 # Make the grid of constrained health investment by scaling cusp values 

140 N = constrained_N # to shorten next line 

141 frac_grid = np.reshape(np.linspace(0.01, 0.99, num=N), (N, 1)) 

142 nLvl_at_cusp = np.reshape(nLvl[0, :], (1, HLvlGrid.size)) 

143 nLvl_cnst = frac_grid * nLvl_at_cusp 

144 

145 # Solve intraperiod FOC to get constrained consumption 

146 marg_health_cnst = eval_marg_health_prod( 

147 nLvl_cnst, HealthProdExp, HealthProdFac 

148 ) 

149 cLvl_cnst = (EndOfPrd_dvdH[0, :] * marg_health_cnst) ** (-1.0 / CRRA) 

150 

151 # Define "constrained end of period states" and continuation value 

152 aLvl_cnst = np.zeros((N, HLvlGrid.size)) 

153 HLvl_cnst = np.tile(np.reshape(HLvlGrid, (1, HLvlGrid.size)), (N, 1)) 

154 EndOfPrd_v_cnst = np.tile( 

155 np.reshape(EndOfPrd_v[0, :], (1, HLvlGrid.size)), (N, 1) 

156 ) 

157 

158 # Combine constrained and unconstrained arrays 

159 aLvl = np.concatenate([aLvl_cnst, aLvl], axis=0) 

160 HLvl = np.concatenate([HLvl_cnst, HLvl], axis=0) 

161 cLvl = np.concatenate([cLvl_cnst, cLvl], axis=0) 

162 nLvl = np.concatenate([nLvl_cnst, nLvl], axis=0) 

163 EndOfPrd_v = np.concatenate([EndOfPrd_v_cnst, EndOfPrd_v], axis=0) 

164 

165 # Invert intratemporal transitions to find endogenous gridpoints 

166 mLvl = aLvl + cLvl + nLvl 

167 hLvl = HLvl - eval_health_prod(nLvl, HealthProdExp, HealthProdFac) 

168 

169 # Calculate (pseudo-inverse) value as of decision-time 

170 Value = CRRAutility(cLvl, rho=CRRA) + EndOfPrd_v 

171 vNvrs = CRRAutility_inv(Value, rho=CRRA) 

172 

173 # Add points at the lower boundary of mLvl for each function 

174 Zeros = np.zeros((1, HLvlGrid.size)) 

175 mLvl = np.concatenate((Zeros, mLvl), axis=0) 

176 hLvl = np.concatenate((np.reshape(hLvl[0, :], (1, HLvlGrid.size)), hLvl), axis=0) 

177 cLvl = np.concatenate((Zeros, cLvl), axis=0) 

178 nLvl = np.concatenate((Zeros, nLvl), axis=0) 

179 vNvrs = np.concatenate((Zeros, vNvrs), axis=0) 

180 

181 # Construct solution as a multi-interpolation 

182 solution_now = Curvilinear2DInterp([vNvrs, cLvl, nLvl], mLvl, hLvl) 

183 return solution_now 

184 

185 

186############################################################################### 

187 

188 

189def make_solution_terminal_ConsBasicHealth(): 

190 """ 

191 Constructor for the terminal period solution for the basic health investment 

192 model. The trivial solution is to consume all market resources and invest 

193 nothing in health. Takes no parameters because CRRA is irrelevant: pseudo-inverse 

194 value is returned rather than value, and the former is just cLvl = mLvl. 

195 

196 The solution representation for this model is a multiple output function that 

197 takes market resources and health capital level as inputs and returns pseudo- 

198 inverse value, consumption level, and health investment level in that order. 

199 """ 

200 return lambda mLvl, hLvl: (mLvl, mLvl, np.zeros_like(mLvl)) 

201 

202 

203def make_health_grid(hLvlMin, hLvlMax, hLvlCount): 

204 """ 

205 Make a uniform grid of health capital levels. 

206 

207 Parameters 

208 ---------- 

209 hLvlMin : float 

210 Lower bound on health capital level; should almost surely be zero. 

211 hLvlMax : float 

212 Upper bound on health capital level. 

213 hLvlCount : int 

214 Number of points in uniform health capital level grid. 

215 

216 Returns 

217 ------- 

218 hLvlGrid : np.array 

219 Uniform grid of health capital levels 

220 """ 

221 return np.linspace(hLvlMin, hLvlMax, hLvlCount) 

222 

223 

224def make_uniform_depreciation_dstn( 

225 T_cycle, DeprRteMean, DeprRteSpread, DeprRteCount, RNG 

226): 

227 """ 

228 Constructor for DeprRteDstn that makes uniform distributions that vary by age. 

229 

230 Parameters 

231 ---------- 

232 T_cycle : int 

233 Number of periods in the agent's sequence or cycle. 

234 DeprRteMean : [float] 

235 Age-varying list (or array) of mean depreciation rates. 

236 DeprRteSpread : [float] 

237 Age-varying list (or array) of half-widths of depreciate rate distribution. 

238 DeprRteCount : int 

239 Number of equiprobable nodes in each distribution. 

240 RNG : np.random.RandomState 

241 Agent's internal random number generator. 

242 

243 Returns 

244 ------- 

245 DeprRteDstn : [DiscreteDistribution] 

246 List of age-dependent discrete approximations to the depreciate rate distribution. 

247 """ 

248 if len(DeprRteMean) != T_cycle: 

249 raise ValueError("DeprRteMean must have length T_cycle!") 

250 if len(DeprRteSpread) != T_cycle: 

251 raise ValueError("DeprRteSpread must have length T_cycle!") 

252 

253 DeprRteDstn = [] 

254 probs = DeprRteCount**-1.0 * np.ones(DeprRteCount) 

255 for t in range(T_cycle): 

256 bot = DeprRteMean[t] - DeprRteSpread[t] 

257 top = DeprRteMean[t] + DeprRteSpread[t] 

258 vals = np.linspace(bot, top, DeprRteCount) 

259 DeprRteDstn.append( 

260 DiscreteDistribution( 

261 pmv=probs, 

262 atoms=vals, 

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

264 ) 

265 ) 

266 return DeprRteDstn 

267 

268 

269def combine_indep_wage_and_depr_dstns(T_cycle, WageRteDstn, DeprRteDstn, RNG): 

270 """ 

271 Combine univariate distributions of wage rate realizations and depreciation 

272 rate realizations at each age, treating them as independent. 

273 

274 Parameters 

275 ---------- 

276 T_cycle : int 

277 Number of periods in the agent's sequence of periods (cycle). 

278 WageRteDstn : [DiscreteDistribution] 

279 Age-dependent list of wage rate realizations; should have length T_cycle. 

280 DeprRteDstn : [DiscreteDistribution] 

281 Age-dependent list of health depreciation rate realizatiosn; should have 

282 length T_cycle. 

283 RNG : np.random.RandomState 

284 Internal random number generator for the AgentType instance. 

285 

286 Returns 

287 ------- 

288 ShockDstn : [DiscreteDistribution] 

289 Age-dependent bivariate distribution with joint realizations of income 

290 and health depreciation rates. 

291 """ 

292 if len(WageRteDstn) != T_cycle: 

293 raise ValueError( 

294 "IncShkDstn must be a list of distributions of length T_cycle!" 

295 ) 

296 if len(DeprRteDstn) != T_cycle: 

297 raise ValueError( 

298 "DeprRteDstn must be a list of distributions of length T_cycle!" 

299 ) 

300 

301 ShockDstn = [] 

302 for t in range(T_cycle): 

303 temp_dstn = combine_indep_dstns( 

304 WageRteDstn[t], DeprRteDstn[t], seed=RNG.integers(0, 2**31 - 1) 

305 ) 

306 temp_dstn_alt = DiscreteDistributionLabeled.from_unlabeled( 

307 dist=temp_dstn, 

308 name="wage and depreciation shock distribution", 

309 var_names=["WageRte", "DeprRte"], 

310 ) 

311 ShockDstn.append(temp_dstn_alt) 

312 return ShockDstn 

313 

314 

315def make_logistic_polynomial_die_prob(T_cycle, DieProbMaxCoeffs): 

316 """ 

317 Constructor for DieProbMax, the age-varying list of maximum death probabilities 

318 (if health is zero). Builds the list as the logistic function evaluated on a 

319 polynomial of model age, given polynomial coefficients. Logistic function is 

320 applied to ensure probabilities are always between zero and one. 

321 

322 Parameters 

323 ---------- 

324 T_cycle : int 

325 Number of periods in the agent's sequence of periods (cycle). 

326 DieProbMaxCoeffs : np.array 

327 List or vector of polynomial coefficients for maximum death probability. 

328 

329 Returns 

330 ------- 

331 DieProbMax : [float] 

332 Age-varying list of maximum death probabilities (if health were zero). 

333 """ 

334 age_vec = np.arange(T_cycle) 

335 DieProbMax = (1.0 + np.exp(-np.polyval(DieProbMaxCoeffs, age_vec))) ** (-1.0) 

336 return DieProbMax.tolist() 

337 

338 

339def make_uniform_HLvl_init_dstn(HLvlInitMin, HLvlInitMax, HLvlInitCount, RNG): 

340 """ 

341 Constructor for HLvlInitDstn that builds a uniform distribution for initial 

342 health capital at model birth. 

343 

344 Parameters 

345 ---------- 

346 HLvlInitMin : float 

347 Lower bound of initial health capital distribution. 

348 HLvlInitMax : float 

349 Upper bound of initial health capital distribution 

350 HLvlInitCount : int 

351 Number of discretized nodes in initial health capital distribution. 

352 RNG : np.random.RandomState 

353 Agent's internal RNG. 

354 

355 Returns 

356 ------- 

357 HLvlInitDstn : DiscreteDistribution 

358 Discretized uniform distribution of initial health capital level. 

359 """ 

360 dstn = Uniform(bot=HLvlInitMin, top=HLvlInitMax, seed=RNG.integers(0, 2**31 - 1)) 

361 HLvlInitDstn = dstn.discretize(HLvlInitCount, endpoints=True) 

362 return HLvlInitDstn 

363 

364 

365############################################################################### 

366 

367# Make a dictionary of default constructor functions 

368basic_health_constructors = { 

369 "WageRteDstn": construct_lognormal_wage_dstn, 

370 "DeprRteDstn": make_uniform_depreciation_dstn, 

371 "ShockDstn": combine_indep_wage_and_depr_dstns, 

372 "aLvlGrid": make_assets_grid, 

373 "HLvlGrid": make_health_grid, 

374 "DieProbMax": make_logistic_polynomial_die_prob, 

375 "HLvlInitDstn": make_uniform_HLvl_init_dstn, 

376 "kLvlInitDstn": make_lognormal_kNrm_init_dstn, 

377 "solution_terminal": make_solution_terminal_ConsBasicHealth, 

378} 

379 

380# Make a dictionary of default parameters for depreciation rate distribution 

381default_DeprRteDstn_params = { 

382 "DeprRteMean": [0.05], # Mean of uniform depreciation distribution 

383 "DeprRteSpread": [0.05], # Half-width of uniform depreciation distribution 

384 "DeprRteCount": 7, # Number of nodes in discrete approximation 

385} 

386 

387# Make a dictionary of default parameters for wage rate distribution 

388default_WageRteDstn_params = { 

389 "WageRteMean": [0.1], # Age-varying mean of wage rate 

390 "WageRteStd": [0.1], # Age-varying stdev of wage rate 

391 "WageRteCount": 7, # Number of nodes to use in discrete approximation 

392 "UnempPrb": 0.07, # Probability of unemployment 

393 "IncUnemp": 0.3, # Income when unemployed 

394} 

395 

396# Make a dictionary of default parameters for assets grid 

397default_aLvlGrid_params = { 

398 "aXtraMin": 1e-5, # Minimum value of end-of-period assets grid 

399 "aXtraMax": 100.0, # Maximum value of end-of-period assets grid 

400 "aXtraCount": 44, # Number of nodes in base assets grid 

401 "aXtraNestFac": 1, # Level of exponential nesting for assets grid 

402 "aXtraExtra": [3e-5, 1e-4, 3e-4, 1e-3, 3e-3, 1e-2, 3e-2], # Extra assets nodes 

403} 

404 

405# Make a dictionary of default parameters for health capital grid 

406default_hLvlGrid_params = { 

407 "hLvlMin": 0.0, # Minimum value of health capital grid (leave at zero) 

408 "hLvlMax": 50.0, # Maximum value of health capital grid 

409 "hLvlCount": 50, # Number of nodes in health capital grid 

410} 

411 

412# Make a dictionary of default parameters for maximum death probability 

413default_DieProbMax_params = { 

414 "DieProbMaxCoeffs": [0.0], # Logistic-polynomial coefficients on age 

415} 

416 

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

418default_kLvlInitDstn_params = { 

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

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

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

422} 

423 

424# Make a dictionary with parameters for the default constructor for HLvlInitDstn 

425default_HLvlInitDstn_params = { 

426 "HLvlInitMin": 1.0, # Lower bound of initial health capital 

427 "HLvlInitMax": 2.0, # Upper bound of initial health capital 

428 "HLvlInitCount": 15, # Number of points in initial health capital discretization 

429} 

430 

431# Make a dictionary of default parameters for the health investment model 

432basic_health_simple_params = { 

433 "constructors": basic_health_constructors, 

434 "DiscFac": 0.95, # Intertemporal discount factor 

435 "Rfree": [1.03], # Risk-free asset return factor 

436 "CRRA": 0.5, # Coefficient of relative risk aversion 

437 "HealthProdExp": 0.35, # Exponent on health production function 

438 "HealthProdFac": 1.0, # Factor on health production function 

439 "constrained_N": 7, # Number of points on liquidity constrained portion 

440 "T_cycle": 1, # Number of periods in default cycle 

441 "cycles": 1, # Number of cycles 

442 "T_age": None, # Maximum lifetime length override 

443 "AgentCount": 10000, # Number of agents to simulate 

444} 

445 

446# Assemble the default parameters dictionary 

447init_basic_health = {} 

448init_basic_health.update(basic_health_simple_params) 

449init_basic_health.update(default_DeprRteDstn_params) 

450init_basic_health.update(default_WageRteDstn_params) 

451init_basic_health.update(default_aLvlGrid_params) 

452init_basic_health.update(default_hLvlGrid_params) 

453init_basic_health.update(default_DieProbMax_params) 

454init_basic_health.update(default_kLvlInitDstn_params) 

455init_basic_health.update(default_HLvlInitDstn_params) 

456 

457 

458class BasicHealthConsumerType(AgentType): 

459 r""" 

460 A class to represent consumers who can save in a risk-free asset and invest 

461 in health capital via a health production function. The model is a slight 

462 alteration of the one from White (2015), which was in turn lifted from Ludwig 

463 and Schoen. In this variation, survival probability depends on post-investment 

464 health capital, rather than next period's health capital realization. 

465 

466 Each period, the agent chooses consumption $c_t$ and health investment $n_t$. 

467 Consumption yields utility via CRRA function, while investment yields additional 

468 health capital via production function $f(n_t)$. The agent faces a mortality 

469 risk that depends on their post-investment health $H_t = h_t + g(n_t)$, as 

470 well as income risk through wage rate $\omega_t$ and health capital depreciation 

471 rate $\delta_t$. Health capital also serves as human capital in the sense that 

472 the agent earns more income when $h_t$ is higher. 

473 

474 Unlike most other HARK models, this one is *not* normalized with respect to 

475 permanent income-- indeed, there is no "permanent income" in this simple model. 

476 As parametric restrictions, the solver requires that $\rho < 1$ so that utility 

477 is positive everywhere. This restriction ensures that the first order conditions 

478 are necessary and sufficient to characterize the solution when not liquidity- 

479 constrained. The liquidity constrained portion of the policy function is handled. 

480 

481 .. math:: 

482 \newcommand{\CRRA}{\rho} 

483 \newcommand{\DiePrb}{\mathsf{D}} 

484 \newcommand{\PermGroFac}{\Gamma} 

485 \newcommand{\Rfree}{\mathsf{R}} 

486 \newcommand{\DiscFac}{\beta} 

487 \newcommand{\DeprRte}{\delta} 

488 \newcommand{\WageRte}{\omega} 

489 \begin{align*} 

490 v_t(m_t, h_t) &= \max_{c_t, n_t}u(c_t) + \DiscFac (1 - \DiePrb_{t}) v_{t+1}(m_{t+1}, h_{t+1}), \\ 

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

492 H_t &= h_t + g(n_t), \\ 

493 a_t &= m_t - c_t - n_t, \\ 

494 \DiePrb_t = \phi_t / (1 + H_t), \\ 

495 h_{t+1} &= (1-\DeprRte_{t+1}) H_t, \\ 

496 y_{t+1} &= \omega_{t+1} h_{t+1}, \\ 

497 m_{t+1} &= \Rfree_{t+1} a_t + y_{t+1}, \\ 

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

499 g(n) = (\gamma / \alpha) n^{\alpha}, \\ 

500 (\WageRte_{t+1}, \DeprRte_{t+1}) \sim F_{t+1}. 

501 \end{align*} 

502 

503 Solving Parameters 

504 ------------------ 

505 cycles: int 

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

507 T_cycle: int 

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

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

510 Coefficient of Relative Risk Aversion. 

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

512 Risk-free interest rate by age. 

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

514 Intertemporal discount factor. 

515 DieProbMax: list[float], time varying, :math:`\phi` 

516 Maximum death probability by age, if $H_t=0$. 

517 HealthProdExp : float, :math:`\alpha` 

518 Exponent in health production function; should be strictly b/w 0 and 1. 

519 HealthProdFac : float, :math:`\gamma` 

520 Scaling factor in health production function; should be strictly positive. 

521 ShockDstn : DiscreteDistribution, time varying 

522 Joint distribution of income and depreciation values that could realize 

523 at the start of the next period. 

524 aLvlGrid : np.array 

525 Grid of end-of-period assets (after all actions are accomplished). 

526 HLvlGrid : np.array 

527 Grid of end-of-period post-investment health. 

528 

529 

530 Simulation Parameters 

531 --------------------- 

532 AgentCount: int 

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

534 T_age: int 

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

536 T_sim: int, required for simulation 

537 Number of periods to simulate. 

538 track_vars: list[strings] 

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

540 For this agent, the options are 'kLvl', 'yLvl', 'mLvl', 'hLvl', 'cLvl', 

541 'nLvl', 'WageRte', 'DeprRte', 'aLvl', 'HLvl'. 

542 

543 kLvl : Beginning-of-period capital holdings, equivalent to aLvl_{t-1} 

544 

545 yLvl : Labor income, the wage rate times health capital. 

546 

547 mLvl : Market resources, the interest factor times capital holdings, plus labor income. 

548 

549 hLvl : Health or human capital level at decision-time. 

550 

551 cLvl : Consumption level. 

552 

553 nLvl : Health investment level. 

554 

555 WageRte : Wage rate this period. 

556 

557 DeprRte : Health capital depreciation rate this period. 

558 

559 aLvl : End-of-period assets: market resources less consumption and investment. 

560 

561 HLvl : End-of-period health capital: health capital plus produced health. 

562 

563 Attributes 

564 ---------- 

565 solution: list[Consumer solution object] 

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

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

568 

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

570 history: Dict[Array] 

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

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

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

574 """ 

575 

576 default_ = { 

577 "params": init_basic_health, 

578 "solver": solve_one_period_ConsBasicHealth, 

579 } 

580 time_vary_ = ["Rfree", "DieProbMax", "ShockDstn"] 

581 time_inv_ = [ 

582 "DiscFac", 

583 "CRRA", 

584 "HealthProdExp", 

585 "HealthProdFac", 

586 "aLvlGrid", 

587 "HLvlGrid", 

588 "constrained_N", 

589 ] 

590 state_vars = ["kLvl", "yLvl", "mLvl", "hLvl", "aLvl", "HLvl"] 

591 shock_vars_ = ["WageRte", "DeprRte"] 

592 distributions = ["ShockDstn", "kLvlInitDstn", "HLvlInitDstn"] 

593 

594 def sim_death(self): 

595 """ 

596 Draw mortality shocks for all agents, marking some for death and replacement. 

597 

598 Returns 

599 ------- 

600 which_agents : np.array 

601 Boolean array of size AgentCount, indicating who dies now. 

602 """ 

603 # Calculate agent-specific death probability 

604 phi = np.array(self.DieProbMax)[self.t_cycle] 

605 DieProb = phi / (1.0 + self.state_now["hLvl"]) 

606 

607 # Draw mortality shocks and mark who dies 

608 N = self.AgentCount 

609 DeathShks = self.RNG.random(N) 

610 which_agents = DeathShks < DieProb 

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

612 too_old = self.t_age >= self.T_age 

613 which_agents = np.logical_or(which_agents, too_old) 

614 return which_agents 

615 

616 def sim_birth(self, which_agents): 

617 """ 

618 Makes new consumers for the given indices. Initialized variables include 

619 kLvl and HLvl, as well as time variables t_age and t_cycle. 

620 

621 Parameters 

622 ---------- 

623 which_agents : np.array(Bool) 

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

625 

626 Returns 

627 ------- 

628 None 

629 """ 

630 N = np.sum(which_agents) 

631 kLvl_newborns = self.kLvlInitDstn.draw(N) 

632 HLvl_newborns = self.HLvlInitDstn.draw(N) 

633 self.state_now["aLvl"][which_agents] = kLvl_newborns 

634 self.state_now["HLvl"][which_agents] = HLvl_newborns 

635 self.t_age[which_agents] = 0 

636 self.t_cycle[which_agents] = 0 

637 

638 def get_shocks(self): 

639 """ 

640 Draw wage and depreciation rate shocks for all simulated agents. 

641 """ 

642 WageRte_now = np.empty(self.AgentCount) 

643 DeprRte_now = np.empty(self.AgentCount) 

644 for t in range(self.T_cycle): 

645 these = self.t_cycle == t 

646 dstn = self.ShockDstn[t - 1] 

647 N = np.sum(these) 

648 Shocks = dstn.draw(N) 

649 WageRte_now[these] = Shocks[0, :] 

650 DeprRte_now[these] = Shocks[1, :] 

651 self.shocks["WageRte"] = WageRte_now 

652 self.shocks["DeprRte"] = DeprRte_now 

653 

654 def transition(self): 

655 """ 

656 Find current market resources and health capital from prior health capital 

657 and the drawn shocks. 

658 """ 

659 kLvlNow = self.state_prev["aLvl"] 

660 HLvlPrev = self.state_prev["HLvl"] 

661 RfreeNow = np.array(self.Rfree)[self.t_cycle - 1] 

662 hLvlNow = (1.0 - self.shocks["DeprRte"]) * HLvlPrev 

663 yLvlNow = self.shocks["WageRte"] * hLvlNow 

664 mLvlNow = RfreeNow * kLvlNow + yLvlNow 

665 return kLvlNow, yLvlNow, mLvlNow, hLvlNow 

666 

667 def get_controls(self): 

668 """ 

669 Evaluate consumption and health investment functions conditional on 

670 current state and model age, yielding controls cLvl and nLvl. 

671 """ 

672 # This intentionally has the same bug with cycles > 1 as all our other 

673 # models. It will be fixed all in one PR. 

674 mLvl = self.state_now["mLvl"] 

675 hLvl = self.state_now["hLvl"] 

676 cLvl = np.empty(self.AgentCount) 

677 nLvl = np.empty(self.AgentCount) 

678 for t in range(self.T_cycle): 

679 these = self.t_cycle == t 

680 func_t = self.solution[t] 

681 trash, cLvl[these], nLvl[these] = func_t(mLvl[these], hLvl[these]) 

682 self.controls["cLvl"] = cLvl 

683 self.controls["nLvl"] = nLvl 

684 

685 def get_poststates(self): 

686 """ 

687 Calculate end-of-period retained assets and post-investment health. 

688 """ 

689 self.state_now["aLvl"] = ( 

690 self.state_now["mLvl"] - self.controls["cLvl"] - self.controls["nLvl"] 

691 ) 

692 self.state_now["HLvl"] = ( 

693 self.state_now["hLvl"] 

694 + (self.HealthProdFac / self.HealthProdExp) 

695 * self.controls["nLvl"] ** self.HealthProdExp 

696 )