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
« 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"""
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 _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.
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.
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]
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))
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))
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)
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
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
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.
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.
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
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 )
195 # Invert the first order condition to get consumption, then find endogenous gridpoints
196 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA)
197 mNrmNow = aNrmNow + cNrmNow
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)
203 # Construct and return the solution for this period
204 solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow)
205 return solution_now
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.
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.
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
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 )
273 # Apply the Markov transition matrix to get unconditional end-of-period marginal value
274 EndOfPrdvP = np.dot(MrkvArray, EndOfPrdvP_cond)
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
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))
290 # Construct and return the solution for this period
291 solution_now = ConsumerSolution(cFunc=cFuncNow_list, vPfunc=vPfuncNow_list)
292 return solution_now
295###############################################################################
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}
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}
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}
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}
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}
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)
381class RepAgentConsumerType(IndShockConsumerType):
382 """
383 A class for representing representative agents with inelastic labor supply.
385 Parameters
386 ----------
388 """
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 }
397 def pre_solve(self):
398 self.construct("solution_terminal")
400 def get_states(self):
401 """
402 TODO: replace with call to transition
404 Calculates updated values of normalized market resources and permanent income level.
405 Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow.
407 Parameters
408 ----------
409 None
411 Returns
412 -------
413 None
414 """
415 pLvlPrev = self.state_prev["pLvl"]
416 aNrmPrev = self.state_prev["aNrm"]
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"]
438 def check_conditions(self, verbose=None):
439 raise NotImplementedError() # pragma: nocover
441 def calc_limiting_values(self):
442 raise NotImplementedError() # pragma: nocover
445###############################################################################
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
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
462class RepAgentMarkovConsumerType(RepAgentConsumerType):
463 """
464 A class for representing representative agents with inelastic labor supply
465 and a discrete Markov state.
466 """
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 }
475 def pre_solve(self):
476 self.construct("solution_terminal")
478 def initialize_sim(self):
479 RepAgentConsumerType.initialize_sim(self)
480 self.shocks["Mrkv"] = self.Mrkv
482 def reset_rng(self):
483 MarkovConsumerType.reset_rng(self)
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"])
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)
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)