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

122 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-08 05:31 +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 _calc_end_of_prd_vP( 

78 vPfuncNext, IncShkDstn, aNrmNow, PermGroFac, DiscFac, CRRA, CapShare, DeprRte 

79): 

80 """ 

81 Compute end-of-period marginal value of assets given an income shock 

82 distribution and a single permanent growth factor. 

83 

84 Parameters 

85 ---------- 

86 vPfuncNext : function 

87 Marginal value function for next period's normalized market resources. 

88 IncShkDstn : distribution.Distribution 

89 Discrete approximation to the income process. Contains event 

90 probabilities in ``pmv`` and shock atoms in ``atoms[0]`` 

91 (permanent) and ``atoms[1]`` (transitory). 

92 aNrmNow : np.array 

93 End-of-period normalized asset grid. 

94 PermGroFac : float 

95 Expected permanent income growth factor for this state. 

96 DiscFac : float 

97 Intertemporal discount factor for future utility. 

98 CRRA : float 

99 Coefficient of relative risk aversion. 

100 CapShare : float 

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

102 DeprRte : float 

103 Depreciation rate for capital. 

104 

105 Returns 

106 ------- 

107 EndOfPrdvP : np.array 

108 End-of-period marginal value of assets, one value per point in 

109 ``aNrmNow``. 

110 """ 

111 # Unpack the shock distribution 

112 ShkPrbsNext = IncShkDstn.pmv 

113 PermShkValsNext = IncShkDstn.atoms[0] 

114 TranShkValsNext = IncShkDstn.atoms[1] 

115 

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

117 aNrmCount = aNrmNow.size 

118 ShkCount = ShkPrbsNext.size 

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

120 

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

122 PermShkVals_tiled = np.tile( 

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

124 ) 

125 TranShkVals_tiled = np.tile( 

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

127 ) 

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

129 

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

131 # combination of end-of-period assets and shock realization 

132 kNrmNext = aNrm_tiled / (PermGroFac * PermShkVals_tiled) 

133 

134 # Calculate next period's market resources 

135 KtoLnext = kNrmNext / TranShkVals_tiled 

136 RfreeNext = 1.0 - DeprRte + CapShare * KtoLnext ** (CapShare - 1.0) 

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

138 mNrmNext = RfreeNext * kNrmNext + wRteNext * TranShkVals_tiled 

139 

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

141 vPnext = vPfuncNext(mNrmNext) 

142 EndOfPrdvP = DiscFac * np.sum( 

143 RfreeNext 

144 * (PermGroFac * PermShkVals_tiled) ** (-CRRA) 

145 * vPnext 

146 * ShkPrbs_tiled, 

147 axis=1, 

148 ) 

149 return EndOfPrdvP 

150 

151 

152def solve_ConsRepAgent( 

153 solution_next, DiscFac, CRRA, IncShkDstn, CapShare, DeprRte, PermGroFac, aXtraGrid 

154): 

155 """ 

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

157 

158 Parameters 

159 ---------- 

160 solution_next : ConsumerSolution 

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

162 DiscFac : float 

163 Intertemporal discount factor for future utility. 

164 CRRA : float 

165 Coefficient of relative risk aversion. 

166 IncShkDstn : distribution.Distribution 

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

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

169 permanent shocks, transitory shocks. 

170 CapShare : float 

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

172 DeprRte : float 

173 Depreciation rate for capital. 

174 PermGroFac : float 

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

176 aXtraGrid : np.array 

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

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

179 level is always zero. 

180 

181 Returns 

182 ------- 

183 solution_now : ConsumerSolution 

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

185 """ 

186 # Unpack next period's solution 

187 vPfuncNext = solution_next.vPfunc 

188 aNrmNow = aXtraGrid 

189 

190 # Compute end-of-period marginal value of assets 

191 EndOfPrdvP = _calc_end_of_prd_vP( 

192 vPfuncNext, IncShkDstn, aNrmNow, PermGroFac, DiscFac, CRRA, CapShare, DeprRte 

193 ) 

194 

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

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

197 mNrmNow = aNrmNow + cNrmNow 

198 

199 # Construct the consumption function and the marginal value function 

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

201 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

202 

203 # Construct and return the solution for this period 

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

205 return solution_now 

206 

207 

208def solve_ConsRepAgentMarkov( 

209 solution_next, 

210 MrkvArray, 

211 DiscFac, 

212 CRRA, 

213 IncShkDstn, 

214 CapShare, 

215 DeprRte, 

216 PermGroFac, 

217 aXtraGrid, 

218): 

219 """ 

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

221 This version supports a discrete Markov process. 

222 

223 Parameters 

224 ---------- 

225 solution_next : ConsumerSolution 

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

227 MrkvArray : np.array 

228 Markov transition array between this period and next period. 

229 DiscFac : float 

230 Intertemporal discount factor for future utility. 

231 CRRA : float 

232 Coefficient of relative risk aversion. 

233 IncShkDstn : [distribution.Distribution] 

234 A list of Distribution objects for the income process, one per Markov 

235 state. Each has ``.pmv`` (event probabilities), ``.atoms[0]`` 

236 (permanent shocks), and ``.atoms[1]`` (transitory shocks). 

237 CapShare : float 

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

239 DeprRte : float 

240 Depreciation rate of capital. 

241 PermGroFac : [float] 

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

243 next period. 

244 aXtraGrid : np.array 

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

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

247 level is always zero. 

248 

249 Returns 

250 ------- 

251 solution_now : ConsumerSolution 

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

253 """ 

254 # Define basic objects 

255 StateCount = MrkvArray.shape[0] 

256 aNrmNow = aXtraGrid 

257 aNrmCount = aNrmNow.size 

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

259 

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

261 for j in range(StateCount): 

262 EndOfPrdvP_cond[j, :] = _calc_end_of_prd_vP( 

263 solution_next.vPfunc[j], 

264 IncShkDstn[j], 

265 aNrmNow, 

266 PermGroFac[j], 

267 DiscFac, 

268 CRRA, 

269 CapShare, 

270 DeprRte, 

271 ) 

272 

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

274 EndOfPrdvP = np.dot(MrkvArray, EndOfPrdvP_cond) 

275 

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

277 cFuncNow_list = [] 

278 vPfuncNow_list = [] 

279 for i in range(StateCount): 

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

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

282 mNrmNow = aNrmNow + cNrmNow 

283 

284 # Construct the consumption function and the marginal value function 

285 cFuncNow_list.append( 

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

287 ) 

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

289 

290 # Construct and return the solution for this period 

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

292 return solution_now 

293 

294 

295############################################################################### 

296 

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

298repagent_constructor_dict = { 

299 "IncShkDstn": construct_lognormal_income_process_unemployment, 

300 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

301 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

302 "aXtraGrid": make_assets_grid, 

303 "solution_terminal": make_basic_CRRA_solution_terminal, 

304 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

305 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

306} 

307 

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

309default_kNrmInitDstn_params = { 

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

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

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

313} 

314 

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

316default_pLvlInitDstn_params = { 

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

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

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

320} 

321 

322 

323# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

324default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

334} 

335 

336# Default parameters to make aXtraGrid using make_assets_grid 

337default_aXtraGrid_params = { 

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

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

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

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

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

343} 

344 

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

346init_rep_agent = { 

347 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

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

351 "constructors": repagent_constructor_dict, # See dictionary above 

352 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

355 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

358 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

359 "DeprRte": 0.05, # Depreciation rate for capital 

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

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

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

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

364 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

370 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

374} 

375init_rep_agent.update(default_IncShkDstn_params) 

376init_rep_agent.update(default_aXtraGrid_params) 

377init_rep_agent.update(default_kNrmInitDstn_params) 

378init_rep_agent.update(default_pLvlInitDstn_params) 

379 

380 

381class RepAgentConsumerType(IndShockConsumerType): 

382 """ 

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

384 

385 Parameters 

386 ---------- 

387 

388 """ 

389 

390 time_inv_ = ["CRRA", "DiscFac", "CapShare", "DeprRte", "aXtraGrid"] 

391 default_ = { 

392 "params": init_rep_agent, 

393 "solver": solve_ConsRepAgent, 

394 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

395 } 

396 

397 def pre_solve(self): 

398 self.construct("solution_terminal") 

399 

400 def get_states(self): 

401 """ 

402 TODO: replace with call to transition 

403 

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

405 Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow. 

406 

407 Parameters 

408 ---------- 

409 None 

410 

411 Returns 

412 ------- 

413 None 

414 """ 

415 pLvlPrev = self.state_prev["pLvl"] 

416 aNrmPrev = self.state_prev["aNrm"] 

417 

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

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

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

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

422 1.0 - self.CapShare 

423 ) 

424 self.Rfree = ( 

425 1.0 

426 + self.CapShare 

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

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

429 - self.DeprRte 

430 ) 

431 self.wRte = ( 

432 (1.0 - self.CapShare) 

433 * self.kNrmNow**self.CapShare 

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

435 ) 

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

437 

438 def check_conditions(self, verbose=None): 

439 raise NotImplementedError() # pragma: nocover 

440 

441 def calc_limiting_values(self): 

442 raise NotImplementedError() # pragma: nocover 

443 

444 

445############################################################################### 

446 

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

448markov_repagent_constructor_dict = repagent_constructor_dict.copy() 

449markov_repagent_constructor_dict["solution_terminal"] = ( 

450 make_repagent_markov_solution_terminal 

451) 

452markov_repagent_constructor_dict["MrkvArray"] = make_simple_binary_rep_markov 

453 

454init_markov_rep_agent = init_rep_agent.copy() 

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

456init_markov_rep_agent["Mrkv_p11"] = 0.99 

457init_markov_rep_agent["Mrkv_p22"] = 0.99 

458init_markov_rep_agent["Mrkv"] = 0 

459init_markov_rep_agent["constructors"] = markov_repagent_constructor_dict 

460 

461 

462class RepAgentMarkovConsumerType(RepAgentConsumerType): 

463 """ 

464 A class for representing representative agents with inelastic labor supply 

465 and a discrete Markov state. 

466 """ 

467 

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

469 default_ = { 

470 "params": init_markov_rep_agent, 

471 "solver": solve_ConsRepAgentMarkov, 

472 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl", "Mrkv"], 

473 } 

474 

475 def pre_solve(self): 

476 self.construct("solution_terminal") 

477 

478 def initialize_sim(self): 

479 RepAgentConsumerType.initialize_sim(self) 

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

481 

482 def reset_rng(self): 

483 MarkovConsumerType.reset_rng(self) 

484 

485 def get_shocks(self): 

486 """ 

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

488 """ 

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

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

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

492 

493 t = self.t_cycle[0] 

494 i = self.shocks["Mrkv"] 

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

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

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

498 EventDraw = IncShkDstnNow.draw_events(1) 

499 PermShkNow = ( 

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

501 ) # permanent "shock" includes expected growth 

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

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

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

505 

506 def get_controls(self): 

507 """ 

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

509 """ 

510 t = self.t_cycle[0] 

511 i = self.shocks["Mrkv"] 

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