Coverage for HARK/ConsumptionSaving/ConsRepAgentModel.py: 100%

132 statements  

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

1""" 

2This module contains models for solving representative agent macroeconomic models. 

3This stands in contrast to all other model modules in HARK, which (unsurprisingly) 

4take a heterogeneous agents approach. In RA models, all attributes are either 

5time invariant or exist on a short cycle; models must be infinite horizon. 

6""" 

7 

8import numpy as np 

9from HARK.Calibration.Income.IncomeProcesses import ( 

10 construct_lognormal_income_process_unemployment, 

11 get_PermShkDstn_from_IncShkDstn, 

12 get_TranShkDstn_from_IncShkDstn, 

13) 

14from HARK.ConsumptionSaving.ConsIndShockModel import ( 

15 ConsumerSolution, 

16 IndShockConsumerType, 

17 make_basic_CRRA_solution_terminal, 

18 make_lognormal_kNrm_init_dstn, 

19 make_lognormal_pLvl_init_dstn, 

20) 

21from HARK.ConsumptionSaving.ConsMarkovModel import ( 

22 MarkovConsumerType, 

23 make_simple_binary_markov, 

24) 

25from HARK.distributions import MarkovProcess 

26from HARK.interpolation import LinearInterp, MargValueFuncCRRA 

27from HARK.utilities import make_assets_grid 

28 

29__all__ = ["RepAgentConsumerType", "RepAgentMarkovConsumerType"] 

30 

31 

32def make_repagent_markov_solution_terminal(CRRA, MrkvArray): 

33 """ 

34 Make the terminal period solution for a consumption-saving model with a discrete 

35 Markov state. Simply makes a basic terminal solution for IndShockConsumerType 

36 and then replicates the attributes N times for the N states in the terminal period. 

37 

38 Parameters 

39 ---------- 

40 CRRA : float 

41 Coefficient of relative risk aversion. 

42 MrkvArray : [np.array] 

43 List of Markov transition probabilities arrays. Only used to find the 

44 number of discrete states in the terminal period. 

45 

46 Returns 

47 ------- 

48 solution_terminal : ConsumerSolution 

49 Terminal period solution to the Markov consumption-saving problem. 

50 """ 

51 solution_terminal_basic = make_basic_CRRA_solution_terminal(CRRA) 

52 StateCount_T = MrkvArray.shape[1] 

53 N = StateCount_T # for shorter typing 

54 

55 # Make replicated terminal period solution: consume all resources, no human wealth, minimum m is 0 

56 solution_terminal = ConsumerSolution( 

57 cFunc=N * [solution_terminal_basic.cFunc], 

58 vFunc=N * [solution_terminal_basic.vFunc], 

59 vPfunc=N * [solution_terminal_basic.vPfunc], 

60 vPPfunc=N * [solution_terminal_basic.vPPfunc], 

61 mNrmMin=np.zeros(N), 

62 hNrm=np.zeros(N), 

63 MPCmin=np.ones(N), 

64 MPCmax=np.ones(N), 

65 ) 

66 return solution_terminal 

67 

68 

69def make_simple_binary_rep_markov(Mrkv_p11, Mrkv_p22): 

70 MrkvArray = make_simple_binary_markov(1, [Mrkv_p11], [Mrkv_p22])[0] 

71 return MrkvArray 

72 

73 

74############################################################################### 

75 

76 

77def solve_ConsRepAgent( 

78 solution_next, DiscFac, CRRA, IncShkDstn, CapShare, DeprFac, PermGroFac, aXtraGrid 

79): 

80 """ 

81 Solve one period of the simple representative agent consumption-saving model. 

82 

83 Parameters 

84 ---------- 

85 solution_next : ConsumerSolution 

86 Solution to the next period's problem (i.e. previous iteration). 

87 DiscFac : float 

88 Intertemporal discount factor for future utility. 

89 CRRA : float 

90 Coefficient of relative risk aversion. 

91 IncShkDstn : distribution.Distribution 

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

93 solved and the one immediately following (in solution_next). Order: 

94 permanent shocks, transitory shocks. 

95 CapShare : float 

96 Capital's share of income in Cobb-Douglas production function. 

97 DeprFac : float 

98 Depreciation rate for capital. 

99 PermGroFac : float 

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

101 aXtraGrid : np.array 

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

103 absolute minimum acceptable level. In this model, the minimum acceptable 

104 level is always zero. 

105 

106 Returns 

107 ------- 

108 solution_now : ConsumerSolution 

109 Solution to this period's problem (new iteration). 

110 """ 

111 # Unpack next period's solution and the income distribution 

112 vPfuncNext = solution_next.vPfunc 

113 ShkPrbsNext = IncShkDstn.pmv 

114 PermShkValsNext = IncShkDstn.atoms[0] 

115 TranShkValsNext = IncShkDstn.atoms[1] 

116 

117 # Make tiled versions of end-of-period assets, shocks, and probabilities 

118 aNrmNow = aXtraGrid 

119 aNrmCount = aNrmNow.size 

120 ShkCount = ShkPrbsNext.size 

121 aNrm_tiled = np.tile(np.reshape(aNrmNow, (aNrmCount, 1)), (1, ShkCount)) 

122 

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

124 PermShkVals_tiled = np.tile( 

125 np.reshape(PermShkValsNext, (1, ShkCount)), (aNrmCount, 1) 

126 ) 

127 TranShkVals_tiled = np.tile( 

128 np.reshape(TranShkValsNext, (1, ShkCount)), (aNrmCount, 1) 

129 ) 

130 ShkPrbs_tiled = np.tile(np.reshape(ShkPrbsNext, (1, ShkCount)), (aNrmCount, 1)) 

131 

132 # Calculate next period's capital-to-permanent-labor ratio under each combination 

133 # of end-of-period assets and shock realization 

134 kNrmNext = aNrm_tiled / (PermGroFac * PermShkVals_tiled) 

135 

136 # Calculate next period's market resources 

137 KtoLnext = kNrmNext / TranShkVals_tiled 

138 RfreeNext = 1.0 - DeprFac + CapShare * KtoLnext ** (CapShare - 1.0) 

139 wRteNext = (1.0 - CapShare) * KtoLnext**CapShare 

140 mNrmNext = RfreeNext * kNrmNext + wRteNext * TranShkVals_tiled 

141 

142 # Calculate end-of-period marginal value of assets for the RA 

143 vPnext = vPfuncNext(mNrmNext) 

144 EndOfPrdvP = DiscFac * np.sum( 

145 RfreeNext 

146 * (PermGroFac * PermShkVals_tiled) ** (-CRRA) 

147 * vPnext 

148 * ShkPrbs_tiled, 

149 axis=1, 

150 ) 

151 

152 # Invert the first order condition to get consumption, then find endogenous gridpoints 

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

154 mNrmNow = aNrmNow + cNrmNow 

155 

156 # Construct the consumption function and the marginal value function 

157 cFuncNow = LinearInterp(np.insert(mNrmNow, 0, 0.0), np.insert(cNrmNow, 0, 0.0)) 

158 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

159 

160 # Construct and return the solution for this period 

161 solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow) 

162 return solution_now 

163 

164 

165def solve_ConsRepAgentMarkov( 

166 solution_next, 

167 MrkvArray, 

168 DiscFac, 

169 CRRA, 

170 IncShkDstn, 

171 CapShare, 

172 DeprFac, 

173 PermGroFac, 

174 aXtraGrid, 

175): 

176 """ 

177 Solve one period of the simple representative agent consumption-saving model. 

178 This version supports a discrete Markov process. 

179 

180 Parameters 

181 ---------- 

182 solution_next : ConsumerSolution 

183 Solution to the next period's problem (i.e. previous iteration). 

184 MrkvArray : np.array 

185 Markov transition array between this period and next period. 

186 DiscFac : float 

187 Intertemporal discount factor for future utility. 

188 CRRA : float 

189 Coefficient of relative risk aversion. 

190 IncShkDstn : [distribution.Distribution] 

191 A list of discrete approximations to the income process between the 

192 period being solved and the one immediately following (in solution_next). 

193 Order: event probabilities, permanent shocks, transitory shocks. 

194 CapShare : float 

195 Capital's share of income in Cobb-Douglas production function. 

196 DeprFac : float 

197 Depreciation rate of capital. 

198 PermGroFac : [float] 

199 Expected permanent income growth factor for each state we could be in 

200 next period. 

201 aXtraGrid : np.array 

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

203 absolute minimum acceptable level. In this model, the minimum acceptable 

204 level is always zero. 

205 

206 Returns 

207 ------- 

208 solution_now : ConsumerSolution 

209 Solution to this period's problem (new iteration). 

210 """ 

211 # Define basic objects 

212 StateCount = MrkvArray.shape[0] 

213 aNrmNow = aXtraGrid 

214 aNrmCount = aNrmNow.size 

215 EndOfPrdvP_cond = np.zeros((StateCount, aNrmCount)) + np.nan 

216 

217 # Loop over *next period* states, calculating conditional EndOfPrdvP 

218 for j in range(StateCount): 

219 # Define next-period-state conditional objects 

220 vPfuncNext = solution_next.vPfunc[j] 

221 ShkPrbsNext = IncShkDstn[j].pmv 

222 PermShkValsNext = IncShkDstn[j].atoms[0] 

223 TranShkValsNext = IncShkDstn[j].atoms[1] 

224 

225 # Make tiled versions of end-of-period assets, shocks, and probabilities 

226 ShkCount = ShkPrbsNext.size 

227 aNrm_tiled = np.tile(np.reshape(aNrmNow, (aNrmCount, 1)), (1, ShkCount)) 

228 

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

230 PermShkVals_tiled = np.tile( 

231 np.reshape(PermShkValsNext, (1, ShkCount)), (aNrmCount, 1) 

232 ) 

233 TranShkVals_tiled = np.tile( 

234 np.reshape(TranShkValsNext, (1, ShkCount)), (aNrmCount, 1) 

235 ) 

236 ShkPrbs_tiled = np.tile(np.reshape(ShkPrbsNext, (1, ShkCount)), (aNrmCount, 1)) 

237 

238 # Calculate next period's capital-to-permanent-labor ratio under each combination 

239 # of end-of-period assets and shock realization 

240 kNrmNext = aNrm_tiled / (PermGroFac[j] * PermShkVals_tiled) 

241 

242 # Calculate next period's market resources 

243 KtoLnext = kNrmNext / TranShkVals_tiled 

244 RfreeNext = 1.0 - DeprFac + CapShare * KtoLnext ** (CapShare - 1.0) 

245 wRteNext = (1.0 - CapShare) * KtoLnext**CapShare 

246 mNrmNext = RfreeNext * kNrmNext + wRteNext * TranShkVals_tiled 

247 

248 # Calculate end-of-period marginal value of assets for the RA 

249 vPnext = vPfuncNext(mNrmNext) 

250 EndOfPrdvP_cond[j, :] = DiscFac * np.sum( 

251 RfreeNext 

252 * (PermGroFac[j] * PermShkVals_tiled) ** (-CRRA) 

253 * vPnext 

254 * ShkPrbs_tiled, 

255 axis=1, 

256 ) 

257 

258 # Apply the Markov transition matrix to get unconditional end-of-period marginal value 

259 EndOfPrdvP = np.dot(MrkvArray, EndOfPrdvP_cond) 

260 

261 # Construct the consumption function and marginal value function for each discrete state 

262 cFuncNow_list = [] 

263 vPfuncNow_list = [] 

264 for i in range(StateCount): 

265 # Invert the first order condition to get consumption, then find endogenous gridpoints 

266 cNrmNow = EndOfPrdvP[i, :] ** (-1.0 / CRRA) 

267 mNrmNow = aNrmNow + cNrmNow 

268 

269 # Construct the consumption function and the marginal value function 

270 cFuncNow_list.append( 

271 LinearInterp(np.insert(mNrmNow, 0, 0.0), np.insert(cNrmNow, 0, 0.0)) 

272 ) 

273 vPfuncNow_list.append(MargValueFuncCRRA(cFuncNow_list[-1], CRRA)) 

274 

275 # Construct and return the solution for this period 

276 solution_now = ConsumerSolution(cFunc=cFuncNow_list, vPfunc=vPfuncNow_list) 

277 return solution_now 

278 

279 

280############################################################################### 

281 

282# Make a dictionary of constructors for the representative agent model 

283repagent_constructor_dict = { 

284 "IncShkDstn": construct_lognormal_income_process_unemployment, 

285 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

286 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

287 "aXtraGrid": make_assets_grid, 

288 "solution_terminal": make_basic_CRRA_solution_terminal, 

289 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

290 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

291} 

292 

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

294default_kNrmInitDstn_params = { 

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

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

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

298} 

299 

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

301default_pLvlInitDstn_params = { 

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

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

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

305} 

306 

307 

308# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

309default_IncShkDstn_params = { 

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

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

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

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

314 "UnempPrb": 0.00, # Probability of unemployment while working 

315 "IncUnemp": 0.0, # Unemployment benefits replacement rate while working 

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

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

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

319} 

320 

321# Default parameters to make aXtraGrid using make_assets_grid 

322default_aXtraGrid_params = { 

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

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

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

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

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

328} 

329 

330# Make a dictionary to specify a representative agent consumer type 

331init_rep_agent = { 

332 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

333 "cycles": 0, # Finite, non-cyclic model 

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

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

336 "constructors": repagent_constructor_dict, # See dictionary above 

337 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

339 "Rfree": 1.03, # Interest factor on retained assets 

340 "DiscFac": 0.96, # Intertemporal discount factor 

341 "LivPrb": [1.0], # Survival probability after each period 

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

343 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

344 "DeprFac": 0.05, # Depreciation rate for capital 

345 "CapShare": 0.36, # Capital's share in Cobb-Douglas production function 

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

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

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

349 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

350 "AgentCount": 1, # Number of agents of this type 

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

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

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

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

355 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

359} 

360init_rep_agent.update(default_IncShkDstn_params) 

361init_rep_agent.update(default_aXtraGrid_params) 

362init_rep_agent.update(default_kNrmInitDstn_params) 

363init_rep_agent.update(default_pLvlInitDstn_params) 

364 

365 

366class RepAgentConsumerType(IndShockConsumerType): 

367 """ 

368 A class for representing representative agents with inelastic labor supply. 

369 

370 Parameters 

371 ---------- 

372 

373 """ 

374 

375 time_inv_ = ["CRRA", "DiscFac", "CapShare", "DeprFac", "aXtraGrid"] 

376 default_ = {"params": init_rep_agent, "solver": solve_ConsRepAgent} 

377 

378 def pre_solve(self): 

379 self.construct("solution_terminal") 

380 

381 def get_states(self): 

382 """ 

383 TODO: replace with call to transition 

384 

385 Calculates updated values of normalized market resources and permanent income level. 

386 Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow. 

387 

388 Parameters 

389 ---------- 

390 None 

391 

392 Returns 

393 ------- 

394 None 

395 """ 

396 pLvlPrev = self.state_prev["pLvl"] 

397 aNrmPrev = self.state_prev["aNrm"] 

398 

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

400 self.pLvlNow = pLvlPrev * self.shocks["PermShk"] # Same as in IndShockConsType 

401 self.kNrmNow = aNrmPrev / self.shocks["PermShk"] 

402 self.yNrmNow = self.kNrmNow**self.CapShare * self.shocks["TranShk"] ** ( 

403 1.0 - self.CapShare 

404 ) 

405 self.Rfree = ( 

406 1.0 

407 + self.CapShare 

408 * self.kNrmNow ** (self.CapShare - 1.0) 

409 * self.shocks["TranShk"] ** (1.0 - self.CapShare) 

410 - self.DeprFac 

411 ) 

412 self.wRte = ( 

413 (1.0 - self.CapShare) 

414 * self.kNrmNow**self.CapShare 

415 * self.shocks["TranShk"] ** (-self.CapShare) 

416 ) 

417 self.mNrmNow = self.Rfree * self.kNrmNow + self.wRte * self.shocks["TranShk"] 

418 

419 

420############################################################################### 

421 

422# Define the default dictionary for a markov representative agent type 

423markov_repagent_constructor_dict = repagent_constructor_dict.copy() 

424markov_repagent_constructor_dict["solution_terminal"] = ( 

425 make_repagent_markov_solution_terminal 

426) 

427markov_repagent_constructor_dict["MrkvArray"] = make_simple_binary_rep_markov 

428 

429init_markov_rep_agent = init_rep_agent.copy() 

430init_markov_rep_agent["PermGroFac"] = [[0.97, 1.03]] 

431init_markov_rep_agent["Mrkv_p11"] = 0.99 

432init_markov_rep_agent["Mrkv_p22"] = 0.99 

433init_markov_rep_agent["Mrkv"] = 0 

434init_markov_rep_agent["constructors"] = markov_repagent_constructor_dict 

435 

436 

437class RepAgentMarkovConsumerType(RepAgentConsumerType): 

438 """ 

439 A class for representing representative agents with inelastic labor supply 

440 and a discrete Markov state. 

441 """ 

442 

443 time_inv_ = RepAgentConsumerType.time_inv_ + ["MrkvArray"] 

444 default_ = {"params": init_markov_rep_agent, "solver": solve_ConsRepAgentMarkov} 

445 

446 def pre_solve(self): 

447 self.construct("solution_terminal") 

448 

449 def initialize_sim(self): 

450 RepAgentConsumerType.initialize_sim(self) 

451 self.shocks["Mrkv"] = self.Mrkv 

452 

453 def reset_rng(self): 

454 MarkovConsumerType.reset_rng(self) 

455 

456 def get_shocks(self): 

457 """ 

458 Draws a new Markov state and income shocks for the representative agent. 

459 """ 

460 self.shocks["Mrkv"] = MarkovProcess( 

461 self.MrkvArray, seed=self.RNG.integers(0, 2**31 - 1) 

462 ).draw(self.shocks["Mrkv"]) 

463 

464 t = self.t_cycle[0] 

465 i = self.shocks["Mrkv"] 

466 IncShkDstnNow = self.IncShkDstn[t - 1][i] # set current income distribution 

467 PermGroFacNow = self.PermGroFac[t - 1][i] # and permanent growth factor 

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

469 EventDraw = IncShkDstnNow.draw_events(1) 

470 PermShkNow = ( 

471 IncShkDstnNow.atoms[0][EventDraw] * PermGroFacNow 

472 ) # permanent "shock" includes expected growth 

473 TranShkNow = IncShkDstnNow.atoms[1][EventDraw] 

474 self.shocks["PermShk"] = np.array(PermShkNow) 

475 self.shocks["TranShk"] = np.array(TranShkNow) 

476 

477 def get_controls(self): 

478 """ 

479 Calculates consumption for the representative agent using the consumption functions. 

480 """ 

481 t = self.t_cycle[0] 

482 i = self.shocks["Mrkv"] 

483 self.controls["cNrm"] = self.solution[t].cFunc[i](self.mNrmNow)