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
« 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"""
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
29__all__ = ["RepAgentConsumerType", "RepAgentMarkovConsumerType"]
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.
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.
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
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
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
74###############################################################################
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.
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.
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]
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))
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))
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)
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
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 )
152 # Invert the first order condition to get consumption, then find endogenous gridpoints
153 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA)
154 mNrmNow = aNrmNow + cNrmNow
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)
160 # Construct and return the solution for this period
161 solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow)
162 return solution_now
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.
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.
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
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]
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))
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))
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)
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
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 )
258 # Apply the Markov transition matrix to get unconditional end-of-period marginal value
259 EndOfPrdvP = np.dot(MrkvArray, EndOfPrdvP_cond)
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
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))
275 # Construct and return the solution for this period
276 solution_now = ConsumerSolution(cFunc=cFuncNow_list, vPfunc=vPfuncNow_list)
277 return solution_now
280###############################################################################
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}
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}
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}
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}
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}
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)
366class RepAgentConsumerType(IndShockConsumerType):
367 """
368 A class for representing representative agents with inelastic labor supply.
370 Parameters
371 ----------
373 """
375 time_inv_ = ["CRRA", "DiscFac", "CapShare", "DeprFac", "aXtraGrid"]
376 default_ = {"params": init_rep_agent, "solver": solve_ConsRepAgent}
378 def pre_solve(self):
379 self.construct("solution_terminal")
381 def get_states(self):
382 """
383 TODO: replace with call to transition
385 Calculates updated values of normalized market resources and permanent income level.
386 Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow.
388 Parameters
389 ----------
390 None
392 Returns
393 -------
394 None
395 """
396 pLvlPrev = self.state_prev["pLvl"]
397 aNrmPrev = self.state_prev["aNrm"]
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"]
420###############################################################################
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
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
437class RepAgentMarkovConsumerType(RepAgentConsumerType):
438 """
439 A class for representing representative agents with inelastic labor supply
440 and a discrete Markov state.
441 """
443 time_inv_ = RepAgentConsumerType.time_inv_ + ["MrkvArray"]
444 default_ = {"params": init_markov_rep_agent, "solver": solve_ConsRepAgentMarkov}
446 def pre_solve(self):
447 self.construct("solution_terminal")
449 def initialize_sim(self):
450 RepAgentConsumerType.initialize_sim(self)
451 self.shocks["Mrkv"] = self.Mrkv
453 def reset_rng(self):
454 MarkovConsumerType.reset_rng(self)
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"])
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)
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)