Coverage for HARK / ConsumptionSaving / ConsAggShockModel.py: 99%
897 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 06:00 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-26 06:00 +0000
1"""
2Consumption-saving models with aggregate productivity shocks as well as idiosyn-
3cratic income shocks. Currently only contains one microeconomic model with a
4basic solver. Also includes a subclass of Market called CobbDouglas economy,
5used for solving "macroeconomic" models with aggregate shocks.
6"""
8from copy import deepcopy
10import numpy as np
11import scipy.stats as stats
13from HARK import AgentType, Market
14from HARK.ConsumptionSaving.ConsAggIndMarkovModel import (
15 AggIndMarkovConsumerType,
16)
17from HARK.Calibration.Income.IncomeProcesses import (
18 construct_lognormal_income_process_unemployment,
19 construct_markov_lognormal_income_process_unemployment,
20 get_PermShkDstn_from_IncShkDstn,
21 get_TranShkDstn_from_IncShkDstn,
22 combine_ind_and_agg_income_shocks,
23 combine_markov_ind_and_agg_income_shocks,
24 get_PermShkDstn_from_IncShkDstn_markov,
25 get_TranShkDstn_from_IncShkDstn_markov,
26)
27from HARK.ConsumptionSaving.ConsIndShockModel import (
28 ConsumerSolution,
29 IndShockConsumerType,
30 make_lognormal_kNrm_init_dstn,
31 make_lognormal_pLvl_init_dstn,
32)
33from HARK.distributions import (
34 MarkovProcess,
35 MeanOneLogNormal,
36 Uniform,
37 expected,
38 combine_indep_dstns,
39)
40from HARK.interpolation import (
41 BilinearInterp,
42 ConstantFunction,
43 IdentityFunction,
44 LinearInterp,
45 LinearInterpOnInterp1D,
46 LowerEnvelope2D,
47 MargValueFuncCRRA,
48 UpperEnvelope,
49 VariableLowerBoundFunc2D,
50)
51from HARK.metric import MetricObject
52from HARK.rewards import (
53 CRRAutility,
54 CRRAutility_inv,
55 CRRAutility_invP,
56 CRRAutilityP,
57 CRRAutilityP_inv,
58 CRRAutilityPP,
59)
60from HARK.utilities import make_assets_grid, get_it_from, NullFunc
62__all__ = [
63 "AggShockConsumerType",
64 "AggShockMarkovConsumerType",
65 "CobbDouglasEconomy",
66 "SmallOpenEconomy",
67 "CobbDouglasMarkovEconomy",
68 "SmallOpenMarkovEconomy",
69 "AggregateSavingRule",
70 "AggShocksDynamicRule",
71 "init_agg_shocks",
72 "init_agg_mrkv_shocks",
73 "init_cobb_douglas",
74 "init_mrkv_cobb_douglas",
75]
77utility = CRRAutility
78utilityP = CRRAutilityP
79utilityPP = CRRAutilityPP
80utilityP_inv = CRRAutilityP_inv
81utility_invP = CRRAutility_invP
82utility_inv = CRRAutility_inv
85def make_aggshock_solution_terminal(CRRA):
86 """
87 Creates the terminal period solution for an aggregate shock consumer.
88 Only fills in the consumption function and marginal value function.
90 Parameters
91 ----------
92 CRRA : float
93 Coefficient of relative risk aversion.
95 Returns
96 -------
97 solution_terminal : ConsumerSolution
98 Solution to the terminal period problem.
99 """
100 cFunc_terminal = IdentityFunction(i_dim=0, n_dims=2)
101 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA)
102 mNrmMin_terminal = ConstantFunction(0)
103 solution_terminal = ConsumerSolution(
104 cFunc=cFunc_terminal, vPfunc=vPfunc_terminal, mNrmMin=mNrmMin_terminal
105 )
106 return solution_terminal
109def make_aggmrkv_solution_terminal(CRRA, MrkvArray):
110 """
111 Creates the terminal period solution for an aggregate shock consumer with
112 discrete Markov state. Only fills in the consumption function and marginal
113 value function.
115 Parameters
116 ----------
117 CRRA : float
118 Coefficient of relative risk aversion.
119 MrkvArray : np.array
120 Transition probability array.
122 Returns
123 -------
124 solution_terminal : ConsumerSolution
125 Solution to the terminal period problem.
126 """
127 solution_terminal = make_aggshock_solution_terminal(CRRA)
129 # Make replicated terminal period solution
130 StateCount = MrkvArray.shape[0]
131 solution_terminal.cFunc = StateCount * [solution_terminal.cFunc]
132 solution_terminal.vPfunc = StateCount * [solution_terminal.vPfunc]
133 solution_terminal.mNrmMin = StateCount * [solution_terminal.mNrmMin]
135 return solution_terminal
138def make_exponential_MgridBase(MaggCount, MaggPerturb, MaggExpFac):
139 """
140 Constructor function for MgridBase, the grid of aggregate market resources
141 relative to the steady state. This grid is always centered around 1.0.
143 Parameters
144 ----------
145 MaggCount : int
146 Number of gridpoints for aggregate market resources. Should be odd.
147 MaggPerturb : float
148 Small perturbation around the steady state; the grid will always include
149 1+perturb and 1-perturb.
150 MaggExpFac : float
151 Log growth factor for gridpoints beyond the two adjacent to the steady state.
153 Returns
154 -------
155 MgridBase : np.array
156 Grid of aggregate market resources relative to the steady state.
157 """
158 N = int((MaggCount - 1) / 2)
159 gridpoints = [1.0 - MaggPerturb, 1.0, 1.0 + MaggPerturb]
160 fac = np.exp(MaggExpFac)
161 for n in range(N - 1):
162 new_hi = gridpoints[-1] * fac
163 new_lo = gridpoints[0] / fac
164 gridpoints.append(new_hi)
165 gridpoints.insert(0, new_lo)
166 MgridBase = np.array(gridpoints)
167 return MgridBase
170def make_Mgrid(MgridBase, MSS):
171 """
172 Make the grid of aggregate market resources as the steady state level times the base grid.
173 """
174 return MSS * MgridBase
177###############################################################################
180def solveConsAggShock(
181 solution_next,
182 IncShkDstn,
183 LivPrb,
184 DiscFac,
185 CRRA,
186 PermGroFac,
187 PermGroFacAgg,
188 aXtraGrid,
189 BoroCnstArt,
190 Mgrid,
191 AFunc,
192 Rfunc,
193 wFunc,
194):
195 """
196 Solve one period of a consumption-saving problem with idiosyncratic and
197 aggregate shocks (transitory and permanent). This is a basic solver that
198 can't handle cubic splines, nor can it calculate a value function. This
199 version uses calc_expectation to reduce code clutter.
201 Parameters
202 ----------
203 solution_next : ConsumerSolution
204 The solution to the succeeding one period problem.
205 IncShkDstn : distribution.Distribution
206 A discrete
207 approximation to the income process between the period being solved
208 and the one immediately following (in solution_next). Order:
209 idiosyncratic permanent shocks, idiosyncratic transitory
210 shocks, aggregate permanent shocks, aggregate transitory shocks.
211 LivPrb : float
212 Survival probability; likelihood of being alive at the beginning of
213 the succeeding period.
214 DiscFac : float
215 Intertemporal discount factor for future utility.
216 CRRA : float
217 Coefficient of relative risk aversion.
218 PermGroFac : float
219 Expected permanent income growth factor at the end of this period.
220 PermGroFacAgg : float
221 Expected aggregate productivity growth factor.
222 aXtraGrid : np.array
223 Array of "extra" end-of-period asset values-- assets above the
224 absolute minimum acceptable level.
225 BoroCnstArt : float
226 Artificial borrowing constraint; minimum allowable end-of-period asset-to-
227 permanent-income ratio. Unlike other models, this *can't* be None.
228 Mgrid : np.array
229 A grid of aggregate market resourses to permanent income in the economy.
230 AFunc : function
231 Aggregate savings as a function of aggregate market resources.
232 Rfunc : function
233 The net interest factor on assets as a function of capital ratio k.
234 wFunc : function
235 The wage rate for labor as a function of capital-to-labor ratio k.
237 Returns
238 -------
239 solution_now : ConsumerSolution
240 The solution to the single period consumption-saving problem. Includes
241 a consumption function cFunc (linear interpolation over linear interpola-
242 tions) and marginal value function vPfunc.
243 """
244 # Unpack the income shocks and get grid sizes
245 PermShkValsNext = IncShkDstn.atoms[0]
246 TranShkValsNext = IncShkDstn.atoms[1]
247 PermShkAggValsNext = IncShkDstn.atoms[2]
248 TranShkAggValsNext = IncShkDstn.atoms[3]
249 aCount = aXtraGrid.size
250 Mcount = Mgrid.size
252 # Define a function that calculates M_{t+1} from M_t and the aggregate shocks;
253 # the function also returns the wage rate and effective interest factor
254 def calcAggObjects(M, Psi, Theta):
255 A = AFunc(M) # End-of-period aggregate assets (normalized)
256 # Next period's aggregate capital/labor ratio
257 kNext = A / (PermGroFacAgg * Psi)
258 kNextEff = kNext / Theta # Same thing, but account for *transitory* shock
259 R = Rfunc(kNextEff) # Interest factor on aggregate assets
260 wEff = (
261 wFunc(kNextEff) * Theta
262 ) # Effective wage rate (accounts for labor supply)
263 Reff = R / LivPrb # Account for redistribution of decedents' wealth
264 Mnext = kNext * R + wEff # Next period's aggregate market resources
265 return Mnext, Reff, wEff
267 # Define a function that evaluates R*v'(m_{t+1},M_{t+1}) from a_t, M_t, and the income shocks
268 def vPnextFunc(S, a, M):
269 psi = S[0]
270 theta = S[1]
271 Psi = S[2]
272 Theta = S[3]
274 Mnext, Reff, wEff = calcAggObjects(M, Psi, Theta)
275 PermShkTotal = (
276 PermGroFac * PermGroFacAgg * psi * Psi
277 ) # Total / combined permanent shock
278 mNext = Reff * a / PermShkTotal + theta * wEff # Idiosyncratic market resources
279 vPnext = Reff * PermShkTotal ** (-CRRA) * solution_next.vPfunc(mNext, Mnext)
280 return vPnext
282 # Make an array of a_t values at which to calculate end-of-period marginal value of assets
283 # Natural borrowing constraint at each M_t
284 BoroCnstNat_vec = np.zeros(Mcount)
285 aNrmNow = np.zeros((aCount, Mcount))
286 for j in range(Mcount):
287 Mnext, Reff, wEff = calcAggObjects(
288 Mgrid[j], PermShkAggValsNext, TranShkAggValsNext
289 )
290 aNrmMin_cand = (
291 PermGroFac * PermGroFacAgg * PermShkValsNext * PermShkAggValsNext / Reff
292 ) * (solution_next.mNrmMin(Mnext) - wEff * TranShkValsNext)
293 aNrmMin = np.max(aNrmMin_cand) # Lowest valid a_t value for this M_t
294 aNrmNow[:, j] = aNrmMin + aXtraGrid
295 BoroCnstNat_vec[j] = aNrmMin
297 # Compute end-of-period marginal value of assets
298 MaggNow = np.tile(np.reshape(Mgrid, (1, Mcount)), (aCount, 1)) # Tiled Mgrid
299 EndOfPrdvP = DiscFac * LivPrb * expected(vPnextFunc, IncShkDstn, (aNrmNow, MaggNow))
301 # Calculate optimal consumption from each asset gridpoint and endogenous m_t gridpoint
302 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA)
303 mNrmNow = aNrmNow + cNrmNow
305 # Loop through the values in Mgrid and make a linear consumption function for each
306 cFuncBaseByM_list = []
307 for j in range(Mcount):
308 c_temp = np.insert(cNrmNow[:, j], 0, 0.0) # Add point at bottom
309 m_temp = np.insert(mNrmNow[:, j] - BoroCnstNat_vec[j], 0, 0.0)
310 cFuncBaseByM_list.append(LinearInterp(m_temp, c_temp))
312 # Construct the overall unconstrained consumption function by combining the M-specific functions
313 BoroCnstNat = LinearInterp(
314 np.insert(Mgrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0)
315 )
316 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid)
317 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat)
319 # Make the constrained consumption function and combine it with the unconstrained component
320 cFuncCnst = BilinearInterp(
321 np.array([[0.0, 0.0], [1.0, 1.0]]),
322 np.array([BoroCnstArt, BoroCnstArt + 1.0]),
323 np.array([0.0, 1.0]),
324 )
325 cFuncNow = LowerEnvelope2D(cFuncUnc, cFuncCnst)
327 # Make the minimum m function as the greater of the natural and artificial constraints
328 mNrmMinNow = UpperEnvelope(BoroCnstNat, ConstantFunction(BoroCnstArt))
330 # Construct the marginal value function using the envelope condition
331 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA)
333 # Pack up and return the solution
334 solution_now = ConsumerSolution(
335 cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow
336 )
337 return solution_now
340###############################################################################
343def solve_ConsAggMarkov(
344 solution_next,
345 IncShkDstn,
346 LivPrb,
347 DiscFac,
348 CRRA,
349 MrkvArray,
350 PermGroFac,
351 PermGroFacAgg,
352 aXtraGrid,
353 BoroCnstArt,
354 Mgrid,
355 AFunc,
356 Rfunc,
357 wFunc,
358):
359 """
360 Solve one period of a consumption-saving problem with idiosyncratic and
361 aggregate shocks (transitory and permanent). Moreover, the macroeconomic
362 state follows a Markov process that determines the income distribution and
363 aggregate permanent growth factor. This is a basic solver that can't handle
364 cubic splines, nor can it calculate a value function.
366 Parameters
367 ----------
368 solution_next : ConsumerSolution
369 The solution to the succeeding one period problem.
370 IncShkDstn : [distribution.Distribution]
371 A list of
372 discrete approximations to the income process between the period being
373 solved and the one immediately following (in solution_next). Order:
374 idisyncratic permanent shocks, idiosyncratic transitory
375 shocks, aggregate permanent shocks, aggregate transitory shocks.
376 LivPrb : float
377 Survival probability; likelihood of being alive at the beginning of
378 the succeeding period.
379 DiscFac : float
380 Intertemporal discount factor for future utility.
381 CRRA : float
382 Coefficient of relative risk aversion.
383 MrkvArray : np.array
384 Markov transition matrix between discrete macroeconomic states.
385 MrkvArray[i,j] is probability of being in state j next period conditional
386 on being in state i this period.
387 PermGroFac : float
388 Expected permanent income growth factor at the end of this period,
389 for the *individual*'s productivity.
390 PermGroFacAgg : [float]
391 Expected aggregate productivity growth in each Markov macro state.
392 aXtraGrid : np.array
393 Array of "extra" end-of-period asset values-- assets above the
394 absolute minimum acceptable level.
395 BoroCnstArt : float
396 Artificial borrowing constraint; minimum allowable end-of-period asset-to-
397 permanent-income ratio. Unlike other models, this *can't* be None.
398 Mgrid : np.array
399 A grid of aggregate market resourses to permanent income in the economy.
400 AFunc : [function]
401 Aggregate savings as a function of aggregate market resources, for each
402 Markov macro state.
403 Rfunc : function
404 The net interest factor on assets as a function of capital ratio k.
405 wFunc : function
406 The wage rate for labor as a function of capital-to-labor ratio k.
408 Returns
409 -------
410 solution_now : ConsumerSolution
411 The solution to the single period consumption-saving problem. Includes
412 a consumption function cFunc (linear interpolation over linear interpola-
413 tions) and marginal value function vPfunc.
414 """
415 # Get sizes of grids
416 aCount = aXtraGrid.size
417 Mcount = Mgrid.size
418 StateCount = MrkvArray.shape[0]
420 # Loop through next period's states, assuming we reach each one at a time.
421 # Construct EndOfPrdvP_cond functions for each state.
422 EndOfPrdvPfunc_cond = []
423 BoroCnstNat_cond = []
424 for j in range(StateCount):
425 # Unpack next period's solution
426 vPfuncNext = solution_next.vPfunc[j]
427 mNrmMinNext = solution_next.mNrmMin[j]
429 # Unpack the income shocks
430 ShkPrbsNext = IncShkDstn[j].pmv
431 PermShkValsNext = IncShkDstn[j].atoms[0]
432 TranShkValsNext = IncShkDstn[j].atoms[1]
433 PermShkAggValsNext = IncShkDstn[j].atoms[2]
434 TranShkAggValsNext = IncShkDstn[j].atoms[3]
435 ShkCount = ShkPrbsNext.size
436 aXtra_tiled = np.tile(
437 np.reshape(aXtraGrid, (1, aCount, 1)), (Mcount, 1, ShkCount)
438 )
440 # Make tiled versions of the income shocks
441 # Dimension order: Mnow, aNow, Shk
442 ShkPrbsNext_tiled = np.tile(
443 np.reshape(ShkPrbsNext, (1, 1, ShkCount)), (Mcount, aCount, 1)
444 )
445 PermShkValsNext_tiled = np.tile(
446 np.reshape(PermShkValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1)
447 )
448 TranShkValsNext_tiled = np.tile(
449 np.reshape(TranShkValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1)
450 )
451 PermShkAggValsNext_tiled = np.tile(
452 np.reshape(PermShkAggValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1)
453 )
454 TranShkAggValsNext_tiled = np.tile(
455 np.reshape(TranShkAggValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1)
456 )
458 # Make a tiled grid of end-of-period aggregate assets. These lines use
459 # next prd state j's aggregate saving rule to get a relevant set of Aagg,
460 # which will be used to make an interpolated EndOfPrdvP_cond function.
461 # After constructing these functions, we will use the aggregate saving
462 # rule for *current* state i to get values of Aagg at which to evaluate
463 # these conditional marginal value functions. In the strange, maybe even
464 # impossible case where the aggregate saving rules differ wildly across
465 # macro states *and* there is "anti-persistence", so that the macro state
466 # is very likely to change each period, then this procedure will lead to
467 # an inaccurate solution because the grid of Aagg values on which the
468 # conditional marginal value functions are constructed is not relevant
469 # to the values at which it will actually be evaluated.
470 AaggGrid = AFunc[j](Mgrid)
471 AaggNow_tiled = np.tile(
472 np.reshape(AaggGrid, (Mcount, 1, 1)), (1, aCount, ShkCount)
473 )
475 # Calculate returns to capital and labor in the next period
476 kNext_array = AaggNow_tiled / (
477 PermGroFacAgg[j] * PermShkAggValsNext_tiled
478 ) # Next period's aggregate capital to labor ratio
479 kNextEff_array = (
480 kNext_array / TranShkAggValsNext_tiled
481 ) # Same thing, but account for *transitory* shock
482 R_array = Rfunc(kNextEff_array) # Interest factor on aggregate assets
483 Reff_array = (
484 R_array / LivPrb
485 ) # Effective interest factor on individual assets *for survivors*
486 wEff_array = (
487 wFunc(kNextEff_array) * TranShkAggValsNext_tiled
488 ) # Effective wage rate (accounts for labor supply)
489 PermShkTotal_array = (
490 PermGroFac
491 * PermGroFacAgg[j]
492 * PermShkValsNext_tiled
493 * PermShkAggValsNext_tiled
494 ) # total / combined permanent shock
495 Mnext_array = (
496 kNext_array * R_array + wEff_array
497 ) # next period's aggregate market resources
499 # Find the natural borrowing constraint for each value of M in the Mgrid.
500 # There is likely a faster way to do this, but someone needs to do the math:
501 # is aNrmMin determined by getting the worst shock of all four types?
502 aNrmMin_candidates = (
503 PermGroFac
504 * PermGroFacAgg[j]
505 * PermShkValsNext_tiled[:, 0, :]
506 * PermShkAggValsNext_tiled[:, 0, :]
507 / Reff_array[:, 0, :]
508 * (
509 mNrmMinNext(Mnext_array[:, 0, :])
510 - wEff_array[:, 0, :] * TranShkValsNext_tiled[:, 0, :]
511 )
512 )
513 aNrmMin_vec = np.max(aNrmMin_candidates, axis=1)
514 BoroCnstNat_vec = aNrmMin_vec
515 aNrmMin_tiled = np.tile(
516 np.reshape(aNrmMin_vec, (Mcount, 1, 1)), (1, aCount, ShkCount)
517 )
518 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled
520 # Calculate market resources next period (and a constant array of capital-to-labor ratio)
521 mNrmNext_array = (
522 Reff_array * aNrmNow_tiled / PermShkTotal_array
523 + TranShkValsNext_tiled * wEff_array
524 )
526 # Find marginal value next period at every income shock
527 # realization and every aggregate market resource gridpoint
528 vPnext_array = (
529 Reff_array
530 * PermShkTotal_array ** (-CRRA)
531 * vPfuncNext(mNrmNext_array, Mnext_array)
532 )
534 # Calculate expectated marginal value at the end of the period at every asset gridpoint
535 EndOfPrdvP = DiscFac * LivPrb * np.sum(vPnext_array * ShkPrbsNext_tiled, axis=2)
537 # Make the conditional end-of-period marginal value function
538 BoroCnstNat = LinearInterp(
539 np.insert(AaggGrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0)
540 )
541 EndOfPrdvPnvrs = np.concatenate(
542 (np.zeros((Mcount, 1)), EndOfPrdvP ** (-1.0 / CRRA)), axis=1
543 )
544 EndOfPrdvPnvrsFunc_base = BilinearInterp(
545 np.transpose(EndOfPrdvPnvrs), np.insert(aXtraGrid, 0, 0.0), AaggGrid
546 )
547 EndOfPrdvPnvrsFunc = VariableLowerBoundFunc2D(
548 EndOfPrdvPnvrsFunc_base, BoroCnstNat
549 )
550 EndOfPrdvPfunc_cond.append(MargValueFuncCRRA(EndOfPrdvPnvrsFunc, CRRA))
551 BoroCnstNat_cond.append(BoroCnstNat)
553 # Prepare some objects that are the same across all current states
554 aXtra_tiled = np.tile(np.reshape(aXtraGrid, (1, aCount)), (Mcount, 1))
555 cFuncCnst = BilinearInterp(
556 np.array([[0.0, 0.0], [1.0, 1.0]]),
557 np.array([BoroCnstArt, BoroCnstArt + 1.0]),
558 np.array([0.0, 1.0]),
559 )
561 # Now loop through *this* period's discrete states, calculating end-of-period
562 # marginal value (weighting across state transitions), then construct consumption
563 # and marginal value function for each state.
564 cFuncNow = []
565 vPfuncNow = []
566 mNrmMinNow = []
567 for i in range(StateCount):
568 # Find natural borrowing constraint for this state by Aagg
569 AaggNow = AFunc[i](Mgrid)
570 aNrmMin_candidates = np.zeros((StateCount, Mcount)) + np.nan
571 for j in range(StateCount):
572 if MrkvArray[i, j] > 0.0: # Irrelevant if transition is impossible
573 aNrmMin_candidates[j, :] = BoroCnstNat_cond[j](AaggNow)
574 aNrmMin_vec = np.nanmax(aNrmMin_candidates, axis=0)
575 BoroCnstNat_vec = aNrmMin_vec
577 # Make tiled grids of aNrm and Aagg
578 aNrmMin_tiled = np.tile(np.reshape(aNrmMin_vec, (Mcount, 1)), (1, aCount))
579 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled
580 AaggNow_tiled = np.tile(np.reshape(AaggNow, (Mcount, 1)), (1, aCount))
582 # Loop through feasible transitions and calculate end-of-period marginal value
583 EndOfPrdvP = np.zeros((Mcount, aCount))
584 for j in range(StateCount):
585 if MrkvArray[i, j] > 0.0:
586 temp = EndOfPrdvPfunc_cond[j](aNrmNow_tiled, AaggNow_tiled)
587 EndOfPrdvP += MrkvArray[i, j] * temp
589 # Calculate consumption and the endogenous mNrm gridpoints for this state
590 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA)
591 mNrmNow = aNrmNow_tiled + cNrmNow
593 # Loop through the values in Mgrid and make a piecewise linear consumption function for each
594 cFuncBaseByM_list = []
595 for n in range(Mcount):
596 c_temp = np.insert(cNrmNow[n, :], 0, 0.0) # Add point at bottom
597 m_temp = np.insert(mNrmNow[n, :] - BoroCnstNat_vec[n], 0, 0.0)
598 cFuncBaseByM_list.append(LinearInterp(m_temp, c_temp))
599 # Add the M-specific consumption function to the list
601 # Construct the unconstrained consumption function by combining the M-specific functions
602 BoroCnstNat = LinearInterp(
603 np.insert(Mgrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0)
604 )
605 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid)
606 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat)
608 # Combine the constrained consumption function with unconstrained component
609 cFuncNow.append(LowerEnvelope2D(cFuncUnc, cFuncCnst))
611 # Make the minimum m function as the greater of the natural and artificial constraints
612 mNrmMinNow.append(UpperEnvelope(BoroCnstNat, ConstantFunction(BoroCnstArt)))
614 # Construct the marginal value function using the envelope condition
615 vPfuncNow.append(MargValueFuncCRRA(cFuncNow[-1], CRRA))
617 # Pack up and return the solution
618 solution_now = ConsumerSolution(
619 cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow
620 )
621 return solution_now
624###############################################################################
627def solve_KrusellSmith(
628 solution_next,
629 DiscFac,
630 CRRA,
631 aGrid,
632 Mgrid,
633 mNextArray,
634 MnextArray,
635 ProbArray,
636 RnextArray,
637):
638 """
639 Solve the one period problem of an agent in Krusell & Smith's canonical 1998 model.
640 Because this model is so specialized and only intended to be used with a very narrow
641 case, many arrays can be precomputed, making the code here very short. See the
642 default constructor functions for details.
644 Parameters
645 ----------
646 solution_next : ConsumerSolution
647 Representation of the solution to next period's problem, including the
648 discrete-state-conditional consumption function and marginal value function.
649 DiscFac : float
650 Intertemporal discount factor.
651 CRRA : float
652 Coefficient of relative risk aversion.
653 aGrid : np.array
654 Array of end-of-period asset values.
655 Mgrid : np.array
656 A grid of aggregate market resources in the economy.
657 mNextArray : np.array
658 Precomputed array of next period's market resources attained from every
659 end-of-period state in the exogenous grid crossed with every shock that
660 might attain. Has shape [aCount, Mcount, 4, 4] ~ [a, M, s, s'].
661 MnextArray : np.array
662 Precomputed array of next period's aggregate market resources attained
663 from every end-of-period state in the exogenous grid crossed with every
664 shock that might attain. Corresponds to mNextArray.
665 ProbArray : np.array
666 Tiled array of transition probabilities among discrete states. Every
667 slice [i,j,:,:] is identical and translated from MrkvIndArray.
668 RnextArray : np.array
669 Tiled array of net interest factors next period, attained from every
670 end-of-period state crossed with every shock that might attain.
672 Returns
673 -------
674 solution_now : ConsumerSolution
675 Representation of this period's solution to the Krusell-Smith model.
676 """
677 # Loop over next period's state realizations, computing marginal value of market resources
678 vPnext = np.zeros_like(mNextArray)
679 for j in range(4):
680 vPnext[:, :, :, j] = solution_next.vPfunc[j](
681 mNextArray[:, :, :, j], MnextArray[:, :, :, j]
682 )
684 # Compute end-of-period marginal value of assets
685 EndOfPrdvP = DiscFac * np.sum(RnextArray * vPnext * ProbArray, axis=3)
687 # Invert the first order condition to find optimal consumption
688 cNow = EndOfPrdvP ** (-1.0 / CRRA)
690 # Find the endogenous gridpoints
691 aCount = aGrid.size
692 Mcount = Mgrid.size
693 aNow = np.tile(np.reshape(aGrid, [aCount, 1, 1]), [1, Mcount, 4])
694 mNow = aNow + cNow
696 # Insert zeros at the bottom of both cNow and mNow arrays (consume nothing)
697 cNow = np.concatenate([np.zeros([1, Mcount, 4]), cNow], axis=0)
698 mNow = np.concatenate([np.zeros([1, Mcount, 4]), mNow], axis=0)
700 # Construct the consumption and marginal value function for each discrete state
701 cFunc_by_state = []
702 vPfunc_by_state = []
703 for j in range(4):
704 cFunc_by_M = [LinearInterp(mNow[:, k, j], cNow[:, k, j]) for k in range(Mcount)]
705 cFunc_j = LinearInterpOnInterp1D(cFunc_by_M, Mgrid)
706 vPfunc_j = MargValueFuncCRRA(cFunc_j, CRRA)
707 cFunc_by_state.append(cFunc_j)
708 vPfunc_by_state.append(vPfunc_j)
710 # Package and return the solution
711 solution_now = ConsumerSolution(cFunc=cFunc_by_state, vPfunc=vPfunc_by_state)
712 return solution_now
715###############################################################################
717# Make a dictionary of constructors for the aggregate income shocks model
718aggshock_constructor_dict = {
719 "IncShkDstnInd": construct_lognormal_income_process_unemployment,
720 "IncShkDstn": combine_ind_and_agg_income_shocks,
721 "PermShkDstn": get_PermShkDstn_from_IncShkDstn,
722 "TranShkDstn": get_TranShkDstn_from_IncShkDstn,
723 "aXtraGrid": make_assets_grid,
724 "MgridBase": make_exponential_MgridBase,
725 "Mgrid": make_Mgrid,
726 "kNrmInitDstn": make_lognormal_kNrm_init_dstn,
727 "pLvlInitDstn": make_lognormal_pLvl_init_dstn,
728 "solution_terminal": make_aggshock_solution_terminal,
729 "T_sim": get_it_from("act_T"),
730}
732# Make a dictionary with parameters for the default constructor for kNrmInitDstn
733default_kNrmInitDstn_params = {
734 "kLogInitMean": 0.0, # Mean of log initial capital
735 "kLogInitStd": 0.0, # Stdev of log initial capital
736 "kNrmInitCount": 15, # Number of points in initial capital discretization
737}
739# Make a dictionary with parameters for the default constructor for pLvlInitDstn
740default_pLvlInitDstn_params = {
741 "pLogInitMean": 0.0, # Mean of log permanent income
742 "pLogInitStd": 0.0, # Stdev of log permanent income
743 "pLvlInitCount": 15, # Number of points in initial capital discretization
744}
747# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment
748default_IncShkDstn_params = {
749 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks
750 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks
751 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks
752 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks
753 "UnempPrb": 0.05, # Probability of unemployment while working
754 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working
755 "T_retire": 0, # Period of retirement (0 --> no retirement)
756 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired
757 "IncUnempRet": 0.0, # "Unemployment" benefits when retired
758}
760# Default parameters to make aXtraGrid using make_assets_grid
761default_aXtraGrid_params = {
762 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value
763 "aXtraMax": 20, # Maximum end-of-period "assets above minimum" value
764 "aXtraNestFac": 2, # Exponential nesting factor for aXtraGrid
765 "aXtraCount": 36, # Number of points in the grid of "assets above minimum"
766 "aXtraExtra": None, # Additional other values to add in grid (optional)
767}
769# Default parameters to make MgridBase using make_exponential_MgridBase
770default_MgridBase_params = {
771 "MaggCount": 17,
772 "MaggPerturb": 0.01,
773 "MaggExpFac": 0.15,
774}
776# Make a dictionary to specify an aggregate income shocks consumer type
777init_agg_shocks = {
778 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL
779 "cycles": 1, # Finite, non-cyclic model
780 "T_cycle": 1, # Number of periods in the cycle for this agent type
781 "constructors": aggshock_constructor_dict, # See dictionary above
782 "pseudo_terminal": False, # Terminal period is real
783 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL
784 "CRRA": 2.0, # Coefficient of relative risk aversion
785 "DiscFac": 0.96, # Intertemporal discount factor
786 "LivPrb": [0.98], # Survival probability after each period
787 "PermGroFac": [1.00], # Permanent income growth factor
788 "BoroCnstArt": 0.0, # Artificial borrowing constraint
789 # PARAMETERS REQUIRED TO SIMULATE THE MODEL
790 "AgentCount": 10000, # Number of agents of this type
791 "T_age": None, # Age after which simulated agents are automatically killed
792 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
793 # (The portion of PermGroFac attributable to aggregate productivity growth)
794 "NewbornTransShk": False, # Whether Newborns have transitory shock
795 # ADDITIONAL OPTIONAL PARAMETERS
796 "PerfMITShk": False, # Do Perfect Foresight MIT Shock
797 # (Forces Newborns to follow solution path of the agent they replaced if True)
798 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021)
799}
800init_agg_shocks.update(default_kNrmInitDstn_params)
801init_agg_shocks.update(default_pLvlInitDstn_params)
802init_agg_shocks.update(default_IncShkDstn_params)
803init_agg_shocks.update(default_aXtraGrid_params)
804init_agg_shocks.update(default_MgridBase_params)
807class AggShockConsumerType(IndShockConsumerType):
808 """
809 A class to represent consumers who face idiosyncratic (transitory and per-
810 manent) shocks to their income and live in an economy that has aggregate
811 (transitory and permanent) shocks to labor productivity. As the capital-
812 to-labor ratio varies in the economy, so does the wage rate and interest
813 rate. "Aggregate shock consumers" have beliefs about how the capital ratio
814 evolves over time and take aggregate shocks into account when making their
815 decision about how much to consume.
817 NB: Unlike most AgentType subclasses, AggShockConsumerType does not automatically
818 call its construct method as part of instantiation. In most cases, an instance of
819 this class cannot be meaningfully solved without being associated with a Market
820 instance, probably of the subclass CobbDouglasEconomy or SmallOpenEconomy.
822 To be able to fully build and solve an instance of this class, assign it as an
823 instance of the agents attribute of an appropriate Market, then run that Market's
824 give_agent_params method. This will distribute market-level parameters and
825 instruct the agents to run their construct method. You should then be able to
826 run the solve method on the Market or its agents.
827 """
829 default_ = {
830 "params": init_agg_shocks,
831 "solver": solveConsAggShock,
832 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"],
833 }
834 time_inv_ = IndShockConsumerType.time_inv_.copy()
835 time_inv_ += ["Mgrid", "AFunc", "Rfunc", "wFunc", "PermGroFacAgg"]
836 try:
837 time_inv_.remove("vFuncBool")
838 time_inv_.remove("CubicBool")
839 except: # pragma: nocover
840 pass
841 market_vars = [
842 "act_T",
843 "kSS",
844 "MSS",
845 "AFunc",
846 "Rfunc",
847 "wFunc",
848 "PermGroFacAgg",
849 "AggShkDstn",
850 ]
852 def __init__(self, **kwds):
853 AgentType.__init__(self, construct=False, **kwds)
855 def reset(self):
856 """
857 Initialize this type for a new simulated history of K/L ratio.
859 Parameters
860 ----------
861 None
863 Returns
864 -------
865 None
866 """
867 # Start simulation near SS
868 self.initialize_sim()
869 self.state_now["aLvlNow"] = self.kSS * np.ones(self.AgentCount)
870 self.state_now["aNrm"] = self.state_now["aLvlNow"] / self.state_now["pLvl"]
872 def pre_solve(self):
873 self.construct("solution_terminal")
875 def sim_birth(self, which_agents):
876 """
877 Makes new consumers for the given indices. Initialized variables include
878 aNrm and pLvl, as well as time variables t_age and t_cycle.
880 Parameters
881 ----------
882 which_agents : np.array(Bool)
883 Boolean array of size self.AgentCount indicating which agents should be "born".
885 Returns
886 -------
887 None
888 """
889 IndShockConsumerType.sim_birth(self, which_agents)
890 if "aLvl" in self.state_now and self.state_now["aLvl"] is not None:
891 self.state_now["aLvl"][which_agents] = (
892 self.state_now["aNrm"][which_agents]
893 * self.state_now["pLvl"][which_agents]
894 )
895 else:
896 self.state_now["aLvl"] = self.state_now["aNrm"] * self.state_now["pLvl"]
898 def sim_death(self):
899 """
900 Randomly determine which consumers die, and distribute their wealth among the survivors.
901 This method only works if there is only one period in the cycle.
903 Parameters
904 ----------
905 None
907 Returns
908 -------
909 who_dies : np.array(bool)
910 Boolean array of size AgentCount indicating which agents die.
911 """
912 # Just select a random set of agents to die
913 how_many_die = int(round(self.AgentCount * (1.0 - self.LivPrb[0])))
914 base_bool = np.zeros(self.AgentCount, dtype=bool)
915 base_bool[0:how_many_die] = True
916 who_dies = self.RNG.permutation(base_bool)
917 if self.T_age is not None:
918 who_dies[self.t_age >= self.T_age] = True
920 # Divide up the wealth of those who die, giving it to those who survive
921 who_lives = np.logical_not(who_dies)
922 wealth_living = np.sum(self.state_now["aLvl"][who_lives])
923 wealth_dead = np.sum(self.state_now["aLvl"][who_dies])
924 Ractuarial = 1.0 + wealth_dead / wealth_living
925 self.state_now["aNrm"][who_lives] = (
926 self.state_now["aNrm"][who_lives] * Ractuarial
927 )
928 self.state_now["aLvl"][who_lives] = (
929 self.state_now["aLvl"][who_lives] * Ractuarial
930 )
931 return who_dies
933 def get_Rport(self):
934 """
935 Returns an array of size self.AgentCount with self.RfreeNow in every entry.
936 This is the risk-free portfolio return in this model.
938 Parameters
939 ----------
940 None
942 Returns
943 -------
944 RfreeNow : np.array
945 Array of size self.AgentCount with risk free interest rate for each agent.
946 """
947 RfreeNow = self.RfreeNow * np.ones(self.AgentCount)
948 return RfreeNow
950 def get_shocks(self):
951 """
952 Finds the effective permanent and transitory shocks this period by combining the aggregate
953 and idiosyncratic shocks of each type.
955 Parameters
956 ----------
957 None
959 Returns
960 -------
961 None
962 """
963 IndShockConsumerType.get_shocks(self) # Update idiosyncratic shocks
964 self.shocks["TranShk"] *= self.TranShkAggNow * self.wRteNow
965 self.shocks["PermShk"] *= self.PermShkAggNow
967 def get_controls(self):
968 """
969 Calculates consumption for each consumer of this type using the consumption functions.
971 Parameters
972 ----------
973 None
975 Returns
976 -------
977 None
978 """
979 cNrmNow = np.zeros(self.AgentCount) + np.nan
980 MPCnow = np.zeros(self.AgentCount) + np.nan
981 MaggNow = self.get_MaggNow()
982 for t in range(self.T_cycle):
983 these = t == self.t_cycle
984 cNrmNow[these] = self.solution[t].cFunc(
985 self.state_now["mNrm"][these], MaggNow[these]
986 )
987 MPCnow[these] = self.solution[t].cFunc.derivativeX(
988 self.state_now["mNrm"][these], MaggNow[these]
989 ) # Marginal propensity to consume
991 self.controls["cNrm"] = cNrmNow
992 self.MPCnow = MPCnow
994 def get_MaggNow(self): # This function exists to be overwritten in StickyE model
995 return self.MaggNow * np.ones(self.AgentCount)
997 def market_action(self):
998 """
999 In the aggregate shocks model, the "market action" is to simulate one
1000 period of receiving income and choosing how much to consume.
1002 Parameters
1003 ----------
1004 None
1006 Returns
1007 -------
1008 None
1009 """
1010 self.simulate(1)
1012 def calc_bounding_values(self): # pragma: nocover
1013 """
1014 NOT YET IMPLEMENTED FOR THIS CLASS
1015 """
1016 raise NotImplementedError()
1018 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): # pragma: nocover
1019 """
1020 NOT YET IMPLEMENTED FOR THIS CLASS
1021 """
1022 raise NotImplementedError()
1024 def check_conditions(self, verbose=None): # pragma: nocover
1025 raise NotImplementedError()
1027 def calc_limiting_values(self): # pragma: nocover
1028 raise NotImplementedError()
1031###############################################################################
1034default_IncShkDstnInd_aggmrkv_params = {
1035 "PermShkStd": np.array(
1036 [[0.1, 0.1]]
1037 ), # Standard deviation of log permanent income shocks
1038 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks
1039 "TranShkStd": np.array(
1040 [[0.1, 0.1]]
1041 ), # Standard deviation of log transitory income shocks
1042 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks
1043 "UnempPrb": np.array([0.05, 0.05]), # Probability of unemployment while working
1044 "IncUnemp": np.array(
1045 [0.3, 0.3]
1046 ), # Unemployment benefits replacement rate while working
1047 "T_retire": 0, # Period of retirement (0 --> no retirement)
1048 "UnempPrbRet": None, # Probability of "unemployment" while retired
1049 "IncUnempRet": None, # "Unemployment" benefits when retired
1050}
1052# Make a dictionary to specify a Markov aggregate shocks consumer
1053init_agg_mrkv_shocks = init_agg_shocks.copy()
1054init_agg_mrkv_shocks.update(default_IncShkDstnInd_aggmrkv_params)
1055aggmrkv_constructor_dict = aggshock_constructor_dict.copy()
1056aggmrkv_constructor_dict["solution_terminal"] = make_aggmrkv_solution_terminal
1057aggmrkv_constructor_dict["IncShkDstnInd"] = (
1058 construct_markov_lognormal_income_process_unemployment
1059)
1060aggmrkv_constructor_dict["IncShkDstn"] = combine_markov_ind_and_agg_income_shocks
1061aggmrkv_constructor_dict["PermShkDstn"] = get_PermShkDstn_from_IncShkDstn_markov
1062aggmrkv_constructor_dict["TranShkDstn"] = get_TranShkDstn_from_IncShkDstn_markov
1063init_agg_mrkv_shocks["constructors"] = aggmrkv_constructor_dict
1066class AggShockMarkovConsumerType(AggShockConsumerType):
1067 """
1068 A class for representing ex ante heterogeneous "types" of consumers who
1069 experience both aggregate and idiosyncratic shocks to productivity (both
1070 permanent and transitory), who lives in an environment where the macroeconomic
1071 state is subject to Markov-style discrete state evolution.
1073 NB: Unlike most AgentType subclasses, AggMarkovShockConsumerType does not automatically
1074 call its construct method as part of instantiation. In most cases, an instance of
1075 this class cannot be meaningfully solved without being associated with a Market
1076 instance, probably of the subclass CobbDouglasMarkovEconomy or SmallOpenMarkovEconomy.
1078 To be able to fully build and solve an instance of this class, assign it as an
1079 instance of the agents attribute of an appropriate Market, then run that Market's
1080 give_agent_params method. This will distribute market-level parameters and
1081 instruct the agents to run their construct method. You should then be able to
1082 run the solve method on the Market or its agents.
1083 """
1085 time_inv_ = AggShockConsumerType.time_inv_ + ["MrkvArray"]
1086 shock_vars_ = AggShockConsumerType.shock_vars_ + ["Mrkv"]
1087 market_vars = AggShockConsumerType.market_vars + ["MrkvArray"]
1088 default_ = {
1089 "params": init_agg_mrkv_shocks,
1090 "solver": solve_ConsAggMarkov,
1091 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"],
1092 }
1094 def initialize_sim(self):
1095 self.shocks["Mrkv"] = 0
1096 AggShockConsumerType.initialize_sim(self)
1098 def get_shocks(self):
1099 """
1100 Gets permanent and transitory income shocks for this period. Samples from IncShkDstn for
1101 each period in the cycle. This is a copy-paste from IndShockConsumerType, with the
1102 addition of the Markov macroeconomic state. Unfortunately, the get_shocks method for
1103 MarkovConsumerType cannot be used, as that method assumes that MrkvNow is a vector
1104 with a value for each agent, not just a single int.
1106 Parameters
1107 ----------
1108 None
1110 Returns
1111 -------
1112 None
1113 """
1114 PermShkNow = np.zeros(self.AgentCount) # Initialize shock arrays
1115 TranShkNow = np.zeros(self.AgentCount)
1116 newborn = self.t_age == 0
1117 Mrkv = self.shocks["Mrkv"]
1118 for t in range(self.T_cycle):
1119 these = t == self.t_cycle
1120 if np.any(these):
1121 N = np.sum(these)
1122 # set current income distribution and permanent growth factor
1123 IncShkDstnNow = self.IncShkDstn[t - 1][Mrkv]
1124 PermGroFacNow = self.PermGroFac[t - 1]
1126 # Get random draws of income shocks from the discrete distribution
1127 ShockDraws = IncShkDstnNow.draw(N, shuffle=True)
1128 # Permanent "shock" includes expected growth
1129 PermShkNow[these] = ShockDraws[0] * PermGroFacNow
1130 TranShkNow[these] = ShockDraws[1]
1132 # That procedure used the *last* period in the sequence for newborns, but that's not right
1133 # Redraw shocks for newborns, using the *first* period in the sequence. Approximation.
1134 N = np.sum(newborn)
1135 if N > 0:
1136 these = newborn
1137 IncShkDstnNow = self.IncShkDstn[0][
1138 self.shocks["Mrkv"]
1139 ] # set current income distribution
1140 PermGroFacNow = self.PermGroFac[0] # and permanent growth factor
1142 # Get random draws of income shocks from the discrete distribution
1143 ShockDraws = IncShkDstnNow.draw(N, shuffle=True)
1145 # Permanent "shock" includes expected growth
1146 PermShkNow[these] = ShockDraws[0] * PermGroFacNow
1147 TranShkNow[these] = ShockDraws[1]
1149 # Store the shocks in self
1150 self.EmpNow = np.ones(self.AgentCount, dtype=bool)
1151 self.EmpNow[TranShkNow == self.IncUnemp[Mrkv]] = False
1152 self.shocks["TranShk"] = TranShkNow * self.TranShkAggNow * self.wRteNow
1153 self.shocks["PermShk"] = PermShkNow * self.PermShkAggNow
1155 def get_controls(self):
1156 """
1157 Calculates consumption for each consumer of this type using the consumption functions.
1158 For this AgentType class, MrkvNow is the same for all consumers. However, in an
1159 extension with "macroeconomic inattention", consumers might misperceive the state
1160 and thus act as if they are in different states.
1162 Parameters
1163 ----------
1164 None
1166 Returns
1167 -------
1168 None
1169 """
1170 cNrmNow = np.zeros(self.AgentCount) + np.nan
1171 MPCnow = np.zeros(self.AgentCount) + np.nan
1172 MaggNow = self.get_MaggNow()
1173 MrkvNow = self.getMrkvNow()
1175 StateCount = self.MrkvArray.shape[0]
1176 MrkvBoolArray = np.zeros((StateCount, self.AgentCount), dtype=bool)
1177 for i in range(StateCount):
1178 MrkvBoolArray[i, :] = i == MrkvNow
1180 for t in range(self.T_cycle):
1181 these = t == self.t_cycle
1182 for i in range(StateCount):
1183 those = np.logical_and(these, MrkvBoolArray[i, :])
1184 cNrmNow[those] = self.solution[t].cFunc[i](
1185 self.state_now["mNrm"][those], MaggNow[those]
1186 )
1187 # Marginal propensity to consume
1188 MPCnow[those] = (
1189 self.solution[t]
1190 .cFunc[i]
1191 .derivativeX(self.state_now["mNrm"][those], MaggNow[those])
1192 )
1193 self.controls["cNrm"] = cNrmNow
1194 self.MPCnow = MPCnow
1195 return None
1197 def getMrkvNow(self): # This function exists to be overwritten in StickyE model
1198 return self.shocks["Mrkv"] * np.ones(self.AgentCount, dtype=int)
1201###############################################################################
1203# Define some constructor functions for the basic Krusell-Smith model
1206def make_solution_terminal_KS(CRRA):
1207 cFunc_terminal = 4 * [IdentityFunction(n_dims=2)]
1208 vPfunc_terminal = [MargValueFuncCRRA(cFunc_terminal[j], CRRA) for j in range(4)]
1209 solution_terminal = ConsumerSolution(cFunc=cFunc_terminal, vPfunc=vPfunc_terminal)
1210 return solution_terminal
1213def make_assets_grid_KS(aMin, aMax, aCount, aNestFac):
1214 return make_assets_grid(aMin, aMax, aCount, None, aNestFac)
1217def make_KS_transition_arrays(
1218 aGrid,
1219 Mgrid,
1220 AFunc,
1221 LbrInd,
1222 UrateB,
1223 UrateG,
1224 ProdB,
1225 ProdG,
1226 MrkvIndArray,
1227 CapShare,
1228 DeprRte,
1229):
1230 """
1231 Construct the attributes ProbArray, mNextArray, MnextArray, and RnextArray,
1232 which will be used by the one period solver. The information for this method
1233 is usually obtained by the get_economy_data method. Output is returned as a
1234 *list* of four arrays, which are later assigned to their appropriate attributes.
1236 Parameters
1237 ----------
1238 aGrid : np.array
1239 Grid of end-of-period individual assets.
1240 MGrid : np.array
1241 Grid of aggregate market resources.
1242 AFunc : function
1243 End-of-period aggregate assets as a function of aggregate market resources.
1244 LbrInd : float
1245 Individual labor supply measure.
1246 UrateB : float
1247 Unemployment rate in the "bad" aggregate state.
1248 UrateG : float
1249 Unemployment rate in the "good" aggregate state.
1250 ProdB : float
1251 TFP in the "bad" aggregate state.
1252 ProdG : float
1253 TFP in the "good" aggregate state.
1254 MrkvIndArray : np.array
1255 Markov transition probabilities from the perspective of the individual.
1256 CapShare : float
1257 Capital's share of production.
1258 DeprRte : float
1259 Capital depreciation rate.
1261 Returns
1262 -------
1263 ProbArray : np.array
1264 Array of discrete future outcome probabilities.
1265 mNextArray : np.array
1266 Array of discrete realizations of next-period idiosyncratic market resources.
1267 MnextArray : np.array
1268 Array of discrete realizations of next-period aggregate market resources.
1269 RnextArray : np.array
1270 Array of discrete realizations of next-period rate of return.
1271 """
1272 # Get array sizes
1273 aCount = aGrid.size
1274 Mcount = Mgrid.size
1276 # Make tiled array of end-of-period idiosyncratic assets (order: a, M, s, s')
1277 aNow_tiled = np.tile(np.reshape(aGrid, [aCount, 1, 1, 1]), [1, Mcount, 4, 4])
1279 # Make arrays of end-of-period aggregate assets (capital next period)
1280 AnowB = AFunc[0](Mgrid)
1281 AnowG = AFunc[1](Mgrid)
1282 KnextB = np.tile(np.reshape(AnowB, [1, Mcount, 1, 1]), [1, 1, 1, 4])
1283 KnextG = np.tile(np.reshape(AnowG, [1, Mcount, 1, 1]), [1, 1, 1, 4])
1284 Knext = np.concatenate((KnextB, KnextB, KnextG, KnextG), axis=2)
1286 # Make arrays of aggregate labor and TFP next period
1287 Lnext = np.zeros((1, Mcount, 4, 4)) # shape (1,Mcount,4,4)
1288 Lnext[0, :, :, 0:2] = (1.0 - UrateB) * LbrInd
1289 Lnext[0, :, :, 2:4] = (1.0 - UrateG) * LbrInd
1290 Znext = np.zeros((1, Mcount, 4, 4))
1291 Znext[0, :, :, 0:2] = ProdB
1292 Znext[0, :, :, 2:4] = ProdG
1294 # Calculate (net) interest factor and wage rate next period
1295 KtoLnext = Knext / Lnext
1296 Rnext = 1.0 + Znext * CapShare * KtoLnext ** (CapShare - 1.0) - DeprRte
1297 Wnext = Znext * (1.0 - CapShare) * KtoLnext**CapShare
1299 # Calculate aggregate market resources next period
1300 Ynext = Znext * Knext**CapShare * Lnext ** (1.0 - CapShare)
1301 Mnext = (1.0 - DeprRte) * Knext + Ynext
1303 # Tile the interest, wage, and aggregate market resources arrays
1304 Rnext_tiled = np.tile(Rnext, [aCount, 1, 1, 1])
1305 Wnext_tiled = np.tile(Wnext, [aCount, 1, 1, 1])
1306 Mnext_tiled = np.tile(Mnext, [aCount, 1, 1, 1])
1308 # Make an array of idiosyncratic labor supply next period
1309 lNext_tiled = np.zeros([aCount, Mcount, 4, 4])
1310 lNext_tiled[:, :, :, 1] = LbrInd
1311 lNext_tiled[:, :, :, 3] = LbrInd
1313 # Calculate idiosyncratic market resources next period
1314 mNext = Rnext_tiled * aNow_tiled + Wnext_tiled * lNext_tiled
1316 # Make a tiled array of transition probabilities
1317 Probs_tiled = np.tile(
1318 np.reshape(MrkvIndArray, [1, 1, 4, 4]), [aCount, Mcount, 1, 1]
1319 )
1321 # Return the attributes that will be used by the solver
1322 transition_arrays = {
1323 "ProbArray": Probs_tiled,
1324 "mNextArray": mNext,
1325 "MnextArray": Mnext_tiled,
1326 "RnextArray": Rnext_tiled,
1327 }
1328 return transition_arrays
1331def make_emp_idx_arrays(UrateB, UrateG, MrkvIndArray, MrkvAggArray, AgentCount):
1332 """
1333 Construct the attributes emp_permute and unemp_permute, each of which is
1334 a 2x2 nested list of boolean arrays. The j,k-th element of emp_permute
1335 represents the employment states this period for agents who were employed
1336 last period when the macroeconomy is transitioning from state j to state k.
1337 Likewise, j,k-th element of unemp_permute represents the employment states
1338 this period for agents who were unemployed last period when the macro-
1339 economy is transitioning from state j to state k. These attributes are
1340 referenced during simulation, when they are randomly permuted in order to
1341 maintain exact unemployment rates in each period.
1343 Parameters
1344 ----------
1345 UrateB : float
1346 Unemployment rate in bad economic state.
1347 UrateG : float
1348 Unemployment rate in good economic state.
1349 MrkvIndArray : np.array
1350 4x4 array of transition probabilities among discrete states from the
1351 perspective of an individual consumer.
1352 MrkvAggArray : np.array
1353 2x2 array of transition probabilities among aggregate discrete states.
1354 AgentCount : int
1355 Number of agents to simulate.
1357 Returns
1358 -------
1359 emp_idx_arrays : dict
1360 Dictionary with two entries: unemp_permute and emp_permute.
1361 """
1362 # Get counts of employed and unemployed agents in each macroeconomic state
1363 B_unemp_N = int(np.round(UrateB * AgentCount))
1364 B_emp_N = AgentCount - B_unemp_N
1365 G_unemp_N = int(np.round(UrateG * AgentCount))
1366 G_emp_N = AgentCount - G_unemp_N
1368 # Bad-bad transition indices
1369 BB_stay_unemp_N = int(np.round(B_unemp_N * MrkvIndArray[0, 0] / MrkvAggArray[0, 0]))
1370 BB_become_unemp_N = B_unemp_N - BB_stay_unemp_N
1371 BB_stay_emp_N = int(np.round(B_emp_N * MrkvIndArray[1, 1] / MrkvAggArray[0, 0]))
1372 BB_become_emp_N = B_emp_N - BB_stay_emp_N
1373 BB_unemp_permute = np.concatenate(
1374 [
1375 np.ones(BB_become_emp_N, dtype=bool),
1376 np.zeros(BB_stay_unemp_N, dtype=bool),
1377 ]
1378 )
1379 BB_emp_permute = np.concatenate(
1380 [
1381 np.ones(BB_stay_emp_N, dtype=bool),
1382 np.zeros(BB_become_unemp_N, dtype=bool),
1383 ]
1384 )
1386 # Bad-good transition indices
1387 BG_stay_unemp_N = int(np.round(B_unemp_N * MrkvIndArray[0, 2] / MrkvAggArray[0, 1]))
1388 BG_become_unemp_N = G_unemp_N - BG_stay_unemp_N
1389 BG_stay_emp_N = int(np.round(B_emp_N * MrkvIndArray[1, 3] / MrkvAggArray[0, 1]))
1390 BG_become_emp_N = G_emp_N - BG_stay_emp_N
1391 BG_unemp_permute = np.concatenate(
1392 [
1393 np.ones(BG_become_emp_N, dtype=bool),
1394 np.zeros(BG_stay_unemp_N, dtype=bool),
1395 ]
1396 )
1397 BG_emp_permute = np.concatenate(
1398 [
1399 np.ones(BG_stay_emp_N, dtype=bool),
1400 np.zeros(BG_become_unemp_N, dtype=bool),
1401 ]
1402 )
1404 # Good-bad transition indices
1405 GB_stay_unemp_N = int(np.round(G_unemp_N * MrkvIndArray[2, 0] / MrkvAggArray[1, 0]))
1406 GB_become_unemp_N = B_unemp_N - GB_stay_unemp_N
1407 GB_stay_emp_N = int(np.round(G_emp_N * MrkvIndArray[3, 1] / MrkvAggArray[1, 0]))
1408 GB_become_emp_N = B_emp_N - GB_stay_emp_N
1409 GB_unemp_permute = np.concatenate(
1410 [
1411 np.ones(GB_become_emp_N, dtype=bool),
1412 np.zeros(GB_stay_unemp_N, dtype=bool),
1413 ]
1414 )
1415 GB_emp_permute = np.concatenate(
1416 [
1417 np.ones(GB_stay_emp_N, dtype=bool),
1418 np.zeros(GB_become_unemp_N, dtype=bool),
1419 ]
1420 )
1422 # Good-good transition indices
1423 GG_stay_unemp_N = int(np.round(G_unemp_N * MrkvIndArray[2, 2] / MrkvAggArray[1, 1]))
1424 GG_become_unemp_N = G_unemp_N - GG_stay_unemp_N
1425 GG_stay_emp_N = int(np.round(G_emp_N * MrkvIndArray[3, 3] / MrkvAggArray[1, 1]))
1426 GG_become_emp_N = G_emp_N - GG_stay_emp_N
1427 GG_unemp_permute = np.concatenate(
1428 [
1429 np.ones(GG_become_emp_N, dtype=bool),
1430 np.zeros(GG_stay_unemp_N, dtype=bool),
1431 ]
1432 )
1433 GG_emp_permute = np.concatenate(
1434 [
1435 np.ones(GG_stay_emp_N, dtype=bool),
1436 np.zeros(GG_become_unemp_N, dtype=bool),
1437 ]
1438 )
1440 # Package transition indices as a dictionary
1441 unemp_permute = [
1442 [BB_unemp_permute, BG_unemp_permute],
1443 [GB_unemp_permute, GG_unemp_permute],
1444 ]
1445 emp_permute = [
1446 [BB_emp_permute, BG_emp_permute],
1447 [GB_emp_permute, GG_emp_permute],
1448 ]
1449 emp_idx_arrays = {
1450 "unemp_permute": unemp_permute,
1451 "emp_permute": emp_permute,
1452 }
1453 return emp_idx_arrays
1456###############################################################################
1458# Make a dictionary for Krusell-Smith agents
1459KS_constructor_dict = {
1460 "solution_terminal": make_solution_terminal_KS,
1461 "aGrid": make_assets_grid_KS,
1462 "transition_arrays": make_KS_transition_arrays,
1463 "ProbArray": get_it_from("transition_arrays"),
1464 "mNextArray": get_it_from("transition_arrays"),
1465 "MnextArray": get_it_from("transition_arrays"),
1466 "RnextArray": get_it_from("transition_arrays"),
1467 "emp_idx_arrays": make_emp_idx_arrays,
1468 "unemp_permute": get_it_from("emp_idx_arrays"),
1469 "emp_permute": get_it_from("emp_idx_arrays"),
1470 "MgridBase": make_exponential_MgridBase,
1471 "T_sim": get_it_from("act_T"),
1472 "Mgrid": make_Mgrid,
1473}
1475init_KS_agents = {
1476 "T_cycle": 1,
1477 "cycles": 0,
1478 "pseudo_terminal": False,
1479 "constructors": KS_constructor_dict,
1480 "DiscFac": 0.99,
1481 "CRRA": 1.0,
1482 "aMin": 0.001,
1483 "aMax": 50.0,
1484 "aCount": 32,
1485 "aNestFac": 2,
1486 "MaggCount": 25,
1487 "MaggPerturb": 0.01,
1488 "MaggExpFac": 0.12,
1489 "AgentCount": 10000,
1490}
1493class KrusellSmithType(AggIndMarkovConsumerType):
1494 """
1495 A class for representing agents in the seminal Krusell-Smith (1998) model from
1496 the paper "Income and Wealth Heterogeneity in the Macroeconomy". All default
1497 parameters have been set to match those in the paper, but the equilibrium object
1498 is perceptions of aggregate assets as a function of aggregate market resources
1499 in each macroeconomic state (bad=0, good=1), rather than aggregate capital as
1500 a function of previous aggregate capital. This choice was made so that some
1501 of the code from HARK's other HA-macro models can be used.
1503 This class inherits from AggIndMarkovConsumerType, which provides the
1504 generic two-level hierarchical Markov state machinery:
1505 - 2 macro states: bad (0), good (1)
1506 - 2 micro states: unemployed (0), employed (1)
1507 - Combined index: 0=BU, 1=BE, 2=GU, 3=GE
1509 The micro-state transitions use exact-match permutation arrays to maintain
1510 precise unemployment rates each period (overrides the default stochastic
1511 draw in the base class).
1513 NB: Unlike most AgentType subclasses, KrusellSmithType does not automatically
1514 call its construct method as part of instantiation. In most cases, an instance of
1515 this class cannot be meaningfully solved without being associated with a Market
1516 instance, probably of the subclass KrusellSmithEconomy.
1518 To be able to fully build and solve an instance of this class, assign it as an
1519 instance of the agents attribute of an appropriate Market, then run that Market's
1520 give_agent_params method. This will distribute market-level parameters and
1521 instruct the agents to run their construct method. You should then be able to
1522 run the solve method on the Market or its agents.
1523 """
1525 time_inv_ = [
1526 "DiscFac",
1527 "CRRA",
1528 "aGrid",
1529 "ProbArray",
1530 "mNextArray",
1531 "MnextArray",
1532 "RnextArray",
1533 "Mgrid",
1534 ]
1535 time_vary_ = []
1536 shock_vars_ = ["Mrkv"]
1537 state_vars = ["aNow", "mNow", "EmpNow"]
1538 market_vars = [
1539 "act_T",
1540 "KSS",
1541 "MSS",
1542 "AFunc",
1543 "CapShare",
1544 "DeprRte",
1545 "LbrInd",
1546 "UrateB",
1547 "UrateG",
1548 "ProdB",
1549 "ProdG",
1550 "MrkvIndArray",
1551 "MrkvAggArray",
1552 "MrkvInit",
1553 ]
1554 default_ = {
1555 "params": init_KS_agents,
1556 "solver": solve_KrusellSmith,
1557 "track_vars": ["aNow", "cNow", "mNow", "EmpNow"],
1558 }
1560 def __init__(self, **kwds):
1561 temp = kwds.copy()
1562 temp["construct"] = False
1563 AggIndMarkovConsumerType.__init__(
1564 self, num_macro_states=2, num_micro_states=2, **temp
1565 )
1566 self.construct("MgridBase")
1568 # Special case: this type *must* be initialized with construct=False
1569 # because the data required to make its solution attributes is obtained
1570 # from the associated economy, not passed as part of its parameters.
1571 # To make it work properly, instantiate both this class and assign it
1572 # as an element of agents to a KrusellSmithEconomy instance, then call
1573 # that economy's give_agent_params method.
1575 def pre_solve(self):
1576 self.construct("solution_terminal")
1578 def reset(self):
1579 self.initialize_sim()
1581 def market_action(self):
1582 self.simulate(1)
1584 def initialize_sim(self):
1585 self.shocks["Mrkv"] = self.MrkvInit
1586 self.MacroMrkvNow = self.MrkvInit
1587 AgentType.initialize_sim(self)
1588 self.state_now["EmpNow"] = self.state_now["EmpNow"].astype(bool)
1589 self.MicroMrkvNow = self.state_now["EmpNow"].astype(int)
1591 def sim_birth(self, which):
1592 """
1593 Create newborn agents with randomly drawn employment states. This will
1594 only ever be called by initialize_sim() at the start of a new simulation
1595 history, as the Krusell-Smith model does not have death and replacement.
1596 The sim_death() method does not exist, as AgentType's default of "no death"
1597 is the correct behavior for the model.
1598 """
1599 N = np.sum(which)
1600 if N == 0:
1601 return
1603 S = self.shocks["Mrkv"]
1604 Urate = [self.UrateB, self.UrateG]
1605 unemp_N = int(np.round(Urate[S] * N))
1606 emp_N = self.AgentCount - unemp_N
1608 EmpNew = np.concatenate(
1609 [np.zeros(unemp_N, dtype=bool), np.ones(emp_N, dtype=bool)]
1610 )
1612 self.state_now["EmpNow"][which] = self.RNG.permutation(EmpNew)
1613 self.state_now["aNow"][which] = self.KSS
1614 self.MicroMrkvNow = self.state_now["EmpNow"].astype(int)
1616 def get_shocks(self):
1617 """
1618 Two-step hierarchical Markov draw, then sync employment states.
1620 Uses the AggIndMarkovConsumerType machinery:
1621 1. Read macro state from economy (via self.shocks["Mrkv"])
1622 2. Draw micro states via exact-match permutations
1623 3. Compute combined state index
1624 """
1625 self.get_markov_states()
1626 self.state_now["EmpNow"] = self.MicroMrkvNow.astype(bool)
1628 def get_micro_markov_states(self):
1629 """
1630 Exact-match permutation logic for idiosyncratic employment transitions.
1632 Instead of drawing stochastically from conditional probabilities, this
1633 method shuffles boolean arrays to maintain the exact unemployment rate
1634 implied by the macro state transition, matching Krusell & Smith (1998).
1635 """
1636 employed = self.state_prev["EmpNow"].copy().astype(bool)
1637 unemployed = np.logical_not(employed)
1639 mrkv_prev = int((unemployed.sum() / float(self.AgentCount)) != self.UrateB)
1640 MacroNow = self.MacroMrkvNow
1642 emp_permute = self.emp_permute[mrkv_prev][MacroNow]
1643 unemp_permute = self.unemp_permute[mrkv_prev][MacroNow]
1645 EmpNow = self.state_now["EmpNow"].copy()
1646 EmpNow[employed] = self.RNG.permutation(emp_permute)
1647 EmpNow[unemployed] = self.RNG.permutation(unemp_permute)
1649 self.state_now["EmpNow"] = EmpNow
1650 self.MicroMrkvNow = EmpNow.astype(int)
1652 def get_states(self):
1653 """
1654 Get each agent's idiosyncratic state, their household market resources.
1655 """
1656 self.state_now["mNow"] = (
1657 self.Rnow * self.state_prev["aNow"]
1658 + self.Wnow * self.LbrInd * self.state_now["EmpNow"]
1659 )
1661 def get_controls(self):
1662 """
1663 Get each agent's consumption using the combined Markov state index
1664 to look up the appropriate 2D consumption function.
1665 """
1666 employed = self.state_now["EmpNow"].copy().astype(bool)
1667 unemployed = np.logical_not(employed)
1669 N = self.num_micro_states
1670 unemp_idx = N * self.MacroMrkvNow + 0
1671 emp_idx = N * self.MacroMrkvNow + 1
1673 cNow = np.zeros(self.AgentCount)
1674 Mnow = self.Mnow * np.ones(self.AgentCount)
1675 cNow[unemployed] = self.solution[0].cFunc[unemp_idx](
1676 self.state_now["mNow"][unemployed], Mnow[unemployed]
1677 )
1678 cNow[employed] = self.solution[0].cFunc[emp_idx](
1679 self.state_now["mNow"][employed], Mnow[employed]
1680 )
1681 self.controls["cNow"] = cNow
1683 def get_poststates(self):
1684 """
1685 Gets each agent's retained assets after consumption.
1686 """
1687 self.state_now["aNow"] = self.state_now["mNow"] - self.controls["cNow"]
1690###############################################################################
1693# Make a dictionary to specify a Cobb-Douglas economy
1694init_cobb_douglas = {
1695 "PermShkAggCount": 3, # Number of points in discrete approximation to aggregate permanent shock dist
1696 "TranShkAggCount": 3, # Number of points in discrete approximation to aggregate transitory shock dist
1697 "PermShkAggStd": 0.0063, # Standard deviation of log aggregate permanent shocks
1698 "TranShkAggStd": 0.0031, # Standard deviation of log aggregate transitory shocks
1699 "DeprRte": 0.025, # Capital depreciation rate
1700 "CapShare": 0.36, # Capital's share of income
1701 "DiscFac": 0.96, # Discount factor of perfect foresight calibration
1702 "CRRA": 2.0, # Coefficient of relative risk aversion of perfect foresight calibration
1703 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
1704 "intercept_prev": 0.0, # Intercept of aggregate savings function
1705 "slope_prev": 1.0, # Slope of aggregate savings function,
1706 "verbose": False, # Whether to print solution progress to screen while solving
1707 "T_discard": 200, # Number of simulated "burn in" periods to discard when updating AFunc
1708 "DampingFac": 0.1, # Damping factor when updating AFunc
1709 "max_loops": 20, # Maximum number of AFunc updating loops to allow
1710}
1713class CobbDouglasEconomy(Market):
1714 """
1715 A class to represent an economy with a Cobb-Douglas aggregate production
1716 function over labor and capital, extending HARK.Market. The "aggregate
1717 market process" for this market combines all individuals' asset holdings
1718 into aggregate capital, yielding the interest factor on assets and the wage
1719 rate for the upcoming period.
1721 Note: The current implementation assumes a constant labor supply, but
1722 this will be generalized in the future.
1724 Parameters
1725 ----------
1726 agents : [ConsumerType]
1727 List of types of consumers that live in this economy.
1728 tolerance: float
1729 Minimum acceptable distance between "dynamic rules" to consider the
1730 solution process converged. Distance depends on intercept and slope
1731 of the log-linear "next capital ratio" function.
1732 act_T : int
1733 Number of periods to simulate when making a history of of the market.
1734 """
1736 def __init__(self, agents=None, tolerance=0.0001, act_T=1200, **kwds):
1737 agents = agents if agents is not None else list()
1738 params = init_cobb_douglas.copy()
1739 params["sow_vars"] = [
1740 "MaggNow",
1741 "AaggNow",
1742 "RfreeNow",
1743 "wRteNow",
1744 "PermShkAggNow",
1745 "TranShkAggNow",
1746 "KtoLnow",
1747 ]
1748 params["reap_vars"] = ["aLvl", "pLvl"]
1749 params["track_vars"] = ["MaggNow", "AaggNow"]
1750 params["dyn_vars"] = ["AFunc"]
1751 params["distributions"] = ["PermShkAggDstn", "TranShkAggDstn", "AggShkDstn"]
1752 params.update(kwds)
1754 Market.__init__(self, agents=agents, tolerance=tolerance, act_T=act_T, **params)
1755 self.update()
1757 def mill_rule(self, aLvl, pLvl):
1758 """
1759 Function to calculate the capital to labor ratio, interest factor, and
1760 wage rate based on each agent's current state. Just calls calc_R_and_W().
1762 See documentation for calc_R_and_W for more information.
1763 """
1764 return self.calc_R_and_W(aLvl, pLvl)
1766 def calc_dynamics(self, MaggNow, AaggNow):
1767 """
1768 Calculates a new dynamic rule for the economy: end of period savings as
1769 a function of aggregate market resources. Just calls calc_AFunc().
1771 See documentation for calc_AFunc for more information.
1772 """
1773 return self.calc_AFunc(MaggNow, AaggNow)
1775 def update(self):
1776 """
1777 Use primitive parameters (and perfect foresight calibrations) to make
1778 interest factor and wage rate functions (of capital to labor ratio),
1779 as well as discrete approximations to the aggregate shock distributions.
1781 Parameters
1782 ----------
1783 None
1785 Returns
1786 -------
1787 None
1788 """
1789 self.kSS = (
1790 (
1791 self.get_PermGroFacAggLR() ** (self.CRRA) / self.DiscFac
1792 - (1.0 - self.DeprRte)
1793 )
1794 / self.CapShare
1795 ) ** (1.0 / (self.CapShare - 1.0))
1796 self.KtoYSS = self.kSS ** (1.0 - self.CapShare)
1797 self.wRteSS = (1.0 - self.CapShare) * self.kSS ** (self.CapShare)
1798 self.RfreeSS = (
1799 1.0 + self.CapShare * self.kSS ** (self.CapShare - 1.0) - self.DeprRte
1800 )
1801 self.MSS = self.kSS * self.RfreeSS + self.wRteSS
1802 self.convertKtoY = lambda KtoY: KtoY ** (
1803 1.0 / (1.0 - self.CapShare)
1804 ) # converts K/Y to K/L
1805 self.Rfunc = lambda k: (
1806 1.0 + self.CapShare * k ** (self.CapShare - 1.0) - self.DeprRte
1807 )
1808 self.wFunc = lambda k: ((1.0 - self.CapShare) * k ** (self.CapShare))
1810 self.sow_init["KtoLnow"] = self.kSS
1811 self.sow_init["MaggNow"] = self.kSS
1812 self.sow_init["AaggNow"] = self.kSS
1813 self.sow_init["RfreeNow"] = self.Rfunc(self.kSS)
1814 self.sow_init["wRteNow"] = self.wFunc(self.kSS)
1815 self.sow_init["PermShkAggNow"] = 1.0
1816 self.sow_init["TranShkAggNow"] = 1.0
1817 self.make_AggShkDstn()
1818 self.AFunc = AggregateSavingRule(self.intercept_prev, self.slope_prev)
1820 def get_PermGroFacAggLR(self):
1821 """
1822 A trivial function that returns self.PermGroFacAgg. Exists to be overwritten
1823 and extended by ConsAggShockMarkov model.
1825 Parameters
1826 ----------
1827 None
1829 Returns
1830 -------
1831 PermGroFacAggLR : float
1832 Long run aggregate permanent income growth, which is the same thing
1833 as aggregate permanent income growth.
1834 """
1835 return self.PermGroFacAgg
1837 def make_AggShkDstn(self):
1838 """
1839 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn.
1840 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount.
1842 Parameters
1843 ----------
1844 None
1846 Returns
1847 -------
1848 None
1849 """
1850 RNG = self.RNG
1851 TranShkAggDstn = MeanOneLogNormal(
1852 sigma=self.TranShkAggStd, seed=RNG.integers(0, 2**31 - 1)
1853 )
1854 self.TranShkAggDstn = TranShkAggDstn.discretize(N=self.TranShkAggCount)
1855 PermShkAggDstn = MeanOneLogNormal(
1856 sigma=self.PermShkAggStd, seed=RNG.integers(0, 2**31 - 1)
1857 )
1858 self.PermShkAggDstn = PermShkAggDstn.discretize(N=self.PermShkAggCount)
1859 self.AggShkDstn = combine_indep_dstns(
1860 self.PermShkAggDstn, self.TranShkAggDstn, seed=RNG.integers(0, 2**31 - 1)
1861 )
1863 def reset(self):
1864 """
1865 Reset the economy to prepare for a new simulation. Sets the time index
1866 of aggregate shocks to zero and runs Market.reset().
1868 Parameters
1869 ----------
1870 None
1872 Returns
1873 -------
1874 None
1875 """
1876 self.Shk_idx = 0
1877 Market.reset(self)
1879 def make_AggShkHist(self):
1880 """
1881 Make simulated histories of aggregate transitory and permanent shocks.
1882 Histories are of length self.act_T, for use in the general equilibrium
1883 simulation.
1885 Parameters
1886 ----------
1887 None
1889 Returns
1890 -------
1891 None
1892 """
1893 sim_periods = self.act_T
1894 Events = np.arange(self.AggShkDstn.pmv.size) # just a list of integers
1895 EventDraws = self.AggShkDstn.draw(N=sim_periods, atoms=Events)
1896 PermShkAggHist = self.AggShkDstn.atoms[0][EventDraws]
1897 TranShkAggHist = self.AggShkDstn.atoms[1][EventDraws]
1899 # Store the histories
1900 self.PermShkAggHist = PermShkAggHist * self.PermGroFacAgg
1901 self.TranShkAggHist = TranShkAggHist
1903 def calc_R_and_W(self, aLvlNow, pLvlNow):
1904 """
1905 Calculates the interest factor and wage rate this period using each agent's
1906 capital stock to get the aggregate capital ratio.
1908 Parameters
1909 ----------
1910 aLvlNow : [np.array]
1911 Agents' current end-of-period assets. Elements of the list correspond
1912 to types in the economy, entries within arrays to agents of that type.
1914 Returns
1915 -------
1916 MaggNow : float
1917 Aggregate market resources for this period normalized by mean permanent income
1918 AaggNow : float
1919 Aggregate savings for this period normalized by mean permanent income
1920 RfreeNow : float
1921 Interest factor on assets in the economy this period.
1922 wRteNow : float
1923 Wage rate for labor in the economy this period.
1924 PermShkAggNow : float
1925 Permanent shock to aggregate labor productivity this period.
1926 TranShkAggNow : float
1927 Transitory shock to aggregate labor productivity this period.
1928 KtoLnow : float
1929 Capital-to-labor ratio in the economy this period.
1930 """
1931 # Calculate aggregate savings
1932 AaggPrev = np.mean(np.array(aLvlNow)) / np.mean(
1933 pLvlNow
1934 ) # End-of-period savings from last period
1935 # Calculate aggregate capital this period
1936 AggregateK = np.mean(np.array(aLvlNow)) # ...becomes capital today
1937 # This version uses end-of-period assets and
1938 # permanent income to calculate aggregate capital, unlike the Mathematica
1939 # version, which first applies the idiosyncratic permanent income shocks
1940 # and then aggregates. Obviously this is mathematically equivalent.
1942 # Get this period's aggregate shocks
1943 PermShkAggNow = self.PermShkAggHist[self.Shk_idx]
1944 TranShkAggNow = self.TranShkAggHist[self.Shk_idx]
1945 self.Shk_idx += 1
1947 AggregateL = np.mean(pLvlNow) * PermShkAggNow
1949 # Calculate the interest factor and wage rate this period
1950 KtoLnow = AggregateK / AggregateL
1951 self.KtoYnow = KtoLnow ** (1.0 - self.CapShare)
1952 RfreeNow = self.Rfunc(KtoLnow / TranShkAggNow)
1953 wRteNow = self.wFunc(KtoLnow / TranShkAggNow)
1954 MaggNow = KtoLnow * RfreeNow + wRteNow * TranShkAggNow
1955 self.KtoLnow = KtoLnow # Need to store this as it is a sow variable
1957 # Package the results into an object and return it
1958 return (
1959 MaggNow,
1960 AaggPrev,
1961 RfreeNow,
1962 wRteNow,
1963 PermShkAggNow,
1964 TranShkAggNow,
1965 KtoLnow,
1966 )
1968 def calc_AFunc(self, MaggNow, AaggNow):
1969 """
1970 Calculate a new aggregate savings rule based on the history
1971 of the aggregate savings and aggregate market resources from a simulation.
1973 Parameters
1974 ----------
1975 MaggNow : [float]
1976 List of the history of the simulated aggregate market resources for an economy.
1977 AaggNow : [float]
1978 List of the history of the simulated aggregate savings for an economy.
1980 Returns
1981 -------
1982 (unnamed) : CapDynamicRule
1983 Object containing a new savings rule
1984 """
1985 verbose = self.verbose
1986 discard_periods = (
1987 self.T_discard
1988 ) # Throw out the first T periods to allow the simulation to approach the SS
1989 update_weight = (
1990 1.0 - self.DampingFac
1991 ) # Proportional weight to put on new function vs old function parameters
1992 total_periods = len(MaggNow)
1994 # Regress the log savings against log market resources
1995 logAagg = np.log(AaggNow[discard_periods:total_periods])
1996 logMagg = np.log(MaggNow[discard_periods - 1 : total_periods - 1])
1997 slope, intercept, r_value, p_value, std_err = stats.linregress(logMagg, logAagg)
1999 # Make a new aggregate savings rule by combining the new regression parameters
2000 # with the previous guess
2001 intercept = (
2002 update_weight * intercept + (1.0 - update_weight) * self.intercept_prev
2003 )
2004 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev
2005 AFunc = AggregateSavingRule(
2006 intercept, slope
2007 ) # Make a new next-period capital function
2009 # Save the new values as "previous" values for the next iteration
2010 self.intercept_prev = intercept
2011 self.slope_prev = slope
2013 # Print the new parameters
2014 if verbose:
2015 print(
2016 "intercept="
2017 + str(intercept)
2018 + ", slope="
2019 + str(slope)
2020 + ", r-sq="
2021 + str(r_value**2)
2022 )
2024 return AggShocksDynamicRule(AFunc)
2027# Make a dictionary to specify a small open economy
2028init_small_open_economy = {
2029 "PermShkAggCount": 3, # Number of points in discrete approximation to aggregate permanent shock dist
2030 "TranShkAggCount": 3, # Number of points in discrete approximation to aggregate transitory shock dist
2031 "PermShkAggStd": 0.0063, # Standard deviation of log aggregate permanent shocks
2032 "TranShkAggStd": 0.0031, # Standard deviation of log aggregate transitory shocks
2033 "Rfree": 1.02, # exogenous and fixed return factor on assets
2034 "wRte": 1.0, # exogenous and fixed wage rate
2035 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor
2036 "intercept_prev": 0.0, # Intercept of aggregate savings function
2037 "slope_prev": 1.0, # Slope of aggregate savings function,
2038 "verbose": False, # Whether to print solution progress to screen while solving
2039 "max_loops": 1, # Maximum number of AFunc updating loops to allow, should always be 1
2040}
2043class SmallOpenEconomy(Market):
2044 """
2045 A class for representing a small open economy, where the wage rate and interest rate are
2046 exogenously determined by some "global" rate. However, the economy is still subject to
2047 aggregate productivity shocks.
2049 Parameters
2050 ----------
2051 agents : [ConsumerType]
2052 List of types of consumers that live in this economy.
2053 tolerance: float
2054 Minimum acceptable distance between "dynamic rules" to consider the
2055 solution process converged. Distance depends on intercept and slope
2056 of the log-linear "next capital ratio" function.
2057 act_T : int
2058 Number of periods to simulate when making a history of of the market.
2059 """
2061 def __init__(self, agents=None, tolerance=0.0001, act_T=1000, **kwds):
2062 agents = agents if agents is not None else list()
2063 params = init_small_open_economy.copy()
2064 params.update(**kwds)
2065 Market.__init__(
2066 self,
2067 agents=agents,
2068 sow_vars=[
2069 "MaggNow",
2070 "AaggNow",
2071 "RfreeNow",
2072 "wRteNow",
2073 "PermShkAggNow",
2074 "TranShkAggNow",
2075 "KtoLnow",
2076 ],
2077 reap_vars=[],
2078 track_vars=["MaggNow", "AaggNow", "KtoLnow"],
2079 dyn_vars=[],
2080 distributions=["PermShkAggDstn", "TranShkAggDstn", "AggShkDstn"],
2081 tolerance=tolerance,
2082 act_T=act_T,
2083 **params,
2084 )
2085 self.assign_parameters(**kwds)
2086 self.update()
2088 def update(self):
2089 """
2090 Use primitive parameters to set basic objects.
2091 This is an extremely stripped-down version
2092 of update for CobbDouglasEconomy.
2094 Parameters
2095 ----------
2096 none
2098 Returns
2099 -------
2100 none
2101 """
2102 self.kSS = 1.0
2103 self.MSS = 1.0
2104 self.sow_init["KtoLnow_init"] = self.kSS
2105 self.Rfunc = ConstantFunction(self.Rfree)
2106 self.wFunc = ConstantFunction(self.wRte)
2107 self.sow_init["RfreeNow"] = self.Rfunc(self.kSS)
2108 self.sow_init["wRteNow"] = self.wFunc(self.kSS)
2109 self.sow_init["MaggNow"] = self.kSS
2110 self.sow_init["AaggNow"] = self.kSS
2111 self.sow_init["PermShkAggNow"] = 1.0
2112 self.sow_init["TranShkAggNow"] = 1.0
2113 self.make_AggShkDstn()
2114 self.AFunc = ConstantFunction(1.0)
2116 def make_AggShkDstn(self):
2117 """
2118 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn.
2119 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount.
2121 Parameters
2122 ----------
2123 None
2125 Returns
2126 -------
2127 None
2128 """
2129 RNG = self.RNG
2130 TranShkAggDstn = MeanOneLogNormal(
2131 sigma=self.TranShkAggStd, seed=RNG.integers(0, 2**31 - 1)
2132 )
2133 self.TranShkAggDstn = TranShkAggDstn.discretize(N=self.TranShkAggCount)
2134 PermShkAggDstn = MeanOneLogNormal(
2135 sigma=self.PermShkAggStd, seed=RNG.integers(0, 2**31 - 1)
2136 )
2137 self.PermShkAggDstn = PermShkAggDstn.discretize(N=self.PermShkAggCount)
2138 self.AggShkDstn = combine_indep_dstns(
2139 self.PermShkAggDstn, self.TranShkAggDstn, seed=RNG.integers(0, 2**31 - 1)
2140 )
2142 def mill_rule(self):
2143 """
2144 No aggregation occurs for a small open economy, because the wage and interest rates are
2145 exogenously determined. However, aggregate shocks may occur.
2147 See documentation for get_AggShocks() for more information.
2148 """
2149 return self.get_AggShocks()
2151 def calc_dynamics(self, KtoLnow):
2152 """
2153 Calculates a new dynamic rule for the economy, which is just an empty object.
2154 There is no "dynamic rule" for a small open economy, because K/L does not generate w and R.
2155 """
2156 return MetricObject()
2158 def reset(self):
2159 """
2160 Reset the economy to prepare for a new simulation. Sets the time index of aggregate shocks
2161 to zero and runs Market.reset(). This replicates the reset method for CobbDouglasEconomy;
2162 future version should create parent class of that class and this one.
2164 Parameters
2165 ----------
2166 None
2168 Returns
2169 -------
2170 None
2171 """
2172 self.Shk_idx = 0
2173 Market.reset(self)
2175 def make_AggShkHist(self):
2176 """
2177 Make simulated histories of aggregate transitory and permanent shocks. Histories are of
2178 length self.act_T, for use in the general equilibrium simulation. This replicates the same
2179 method for CobbDouglasEconomy; future version should create parent class.
2181 Parameters
2182 ----------
2183 None
2185 Returns
2186 -------
2187 None
2188 """
2189 sim_periods = self.act_T
2190 Events = np.arange(self.AggShkDstn.pmv.size) # just a list of integers
2191 EventDraws = self.AggShkDstn.draw(N=sim_periods, atoms=Events)
2192 PermShkAggHist = self.AggShkDstn.atoms[0][EventDraws]
2193 TranShkAggHist = self.AggShkDstn.atoms[1][EventDraws]
2195 # Store the histories
2196 self.PermShkAggHist = PermShkAggHist
2197 self.TranShkAggHist = TranShkAggHist
2199 def get_AggShocks(self):
2200 """
2201 Returns aggregate state variables and shocks for this period. The capital-to-labor ratio
2202 is irrelevant and thus treated as constant, and the wage and interest rates are also
2203 constant. However, aggregate shocks are assigned from a prespecified history.
2205 Parameters
2206 ----------
2207 None
2209 Returns
2210 -------
2211 MaggNow : float
2212 Aggregate market resources for this period normalized by mean permanent income
2213 AaggNow : float
2214 Aggregate savings for this period normalized by mean permanent income
2215 RfreeNow : float
2216 Interest factor on assets in the economy this period.
2217 wRteNow : float
2218 Wage rate for labor in the economy this period.
2219 PermShkAggNow : float
2220 Permanent shock to aggregate labor productivity this period.
2221 TranShkAggNow : float
2222 Transitory shock to aggregate labor productivity this period.
2223 KtoLnow : float
2224 Capital-to-labor ratio in the economy this period.
2226 """
2227 # Get this period's aggregate shocks
2228 PermShkAggNow = self.PermShkAggHist[self.Shk_idx]
2229 TranShkAggNow = self.TranShkAggHist[self.Shk_idx]
2230 self.Shk_idx += 1
2232 # Factor prices are constant
2233 RfreeNow = self.Rfunc(1.0 / TranShkAggNow)
2234 wRteNow = self.wFunc(1.0 / TranShkAggNow)
2236 # Aggregates are irrelavent
2237 AaggNow = 1.0
2238 MaggNow = 1.0
2239 KtoLnow = 1.0 / PermShkAggNow
2241 return (
2242 MaggNow,
2243 AaggNow,
2244 RfreeNow,
2245 wRteNow,
2246 PermShkAggNow,
2247 TranShkAggNow,
2248 KtoLnow,
2249 )
2252# This example makes a high risk, low growth state and a low risk, high growth state
2253MrkvArray = np.array([[0.90, 0.10], [0.04, 0.96]])
2255# Make a dictionary to specify a Markov Cobb-Douglas economy
2256init_mrkv_cobb_douglas = init_cobb_douglas.copy()
2257init_mrkv_cobb_douglas["PermShkAggStd"] = [0.012, 0.006]
2258init_mrkv_cobb_douglas["TranShkAggStd"] = [0.006, 0.003]
2259init_mrkv_cobb_douglas["PermGroFacAgg"] = [0.98, 1.02]
2260init_mrkv_cobb_douglas["MrkvArray"] = MrkvArray
2261init_mrkv_cobb_douglas["MrkvInit"] = 0
2262init_mrkv_cobb_douglas["slope_prev"] = 2 * [1.0]
2263init_mrkv_cobb_douglas["intercept_prev"] = 2 * [0.0]
2266class CobbDouglasMarkovEconomy(CobbDouglasEconomy):
2267 """
2268 A class to represent an economy with a Cobb-Douglas aggregate production
2269 function over labor and capital, extending HARK.Market. The "aggregate
2270 market process" for this market combines all individuals' asset holdings
2271 into aggregate capital, yielding the interest factor on assets and the wage
2272 rate for the upcoming period. This small extension incorporates a Markov
2273 state for the "macroeconomy", so that the shock distribution and aggregate
2274 productivity growth factor can vary over time.
2276 Parameters
2277 ----------
2278 agents : [ConsumerType]
2279 List of types of consumers that live in this economy.
2280 tolerance: float
2281 Minimum acceptable distance between "dynamic rules" to consider the
2282 solution process converged. Distance depends on intercept and slope
2283 of the log-linear "next capital ratio" function.
2284 act_T : int
2285 Number of periods to simulate when making a history of of the market.
2286 """
2288 def __init__(
2289 self,
2290 agents=None,
2291 tolerance=0.0001,
2292 act_T=1200,
2293 **kwds,
2294 ):
2295 agents = agents if agents is not None else list()
2296 params = init_mrkv_cobb_douglas.copy()
2297 params.update(kwds)
2299 CobbDouglasEconomy.__init__(
2300 self,
2301 agents=agents,
2302 tolerance=tolerance,
2303 act_T=act_T,
2304 sow_vars=[
2305 "MaggNow",
2306 "AaggNow",
2307 "RfreeNow",
2308 "wRteNow",
2309 "PermShkAggNow",
2310 "TranShkAggNow",
2311 "KtoLnow",
2312 "Mrkv", # This one is new
2313 ],
2314 **params,
2315 )
2317 self.sow_init["Mrkv"] = params["MrkvInit"]
2319 def update(self):
2320 """
2321 Use primitive parameters (and perfect foresight calibrations) to make
2322 interest factor and wage rate functions (of capital to labor ratio),
2323 as well as discrete approximations to the aggregate shock distributions.
2325 Parameters
2326 ----------
2327 None
2329 Returns
2330 -------
2331 None
2332 """
2333 CobbDouglasEconomy.update(self)
2334 StateCount = self.MrkvArray.shape[0]
2335 AFunc_all = []
2336 for i in range(StateCount):
2337 AFunc_all.append(
2338 AggregateSavingRule(self.intercept_prev[i], self.slope_prev[i])
2339 )
2340 self.AFunc = AFunc_all
2342 def get_PermGroFacAggLR(self):
2343 """
2344 Calculates and returns the long run permanent income growth factor. This
2345 is the average growth factor in self.PermGroFacAgg, weighted by the long
2346 run distribution of Markov states (as determined by self.MrkvArray).
2348 Parameters
2349 ----------
2350 None
2352 Returns
2353 -------
2354 PermGroFacAggLR : float
2355 Long run aggregate permanent income growth factor
2356 """
2357 # Find the long run distribution of Markov states
2358 w, v = np.linalg.eig(np.transpose(self.MrkvArray))
2359 idx = (np.abs(w - 1.0)).argmin()
2360 x = v[:, idx].astype(float)
2361 LR_dstn = x / np.sum(x)
2363 # Return the weighted average of aggregate permanent income growth factors
2364 PermGroFacAggLR = np.dot(LR_dstn, np.array(self.PermGroFacAgg))
2365 return PermGroFacAggLR
2367 def make_AggShkDstn(self):
2368 """
2369 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn.
2370 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount.
2371 This version accounts for the Markov macroeconomic state.
2373 Parameters
2374 ----------
2375 None
2377 Returns
2378 -------
2379 None
2380 """
2381 TranShkAggDstn = []
2382 PermShkAggDstn = []
2383 AggShkDstn = []
2384 StateCount = self.MrkvArray.shape[0]
2385 RNG = self.RNG
2387 for i in range(StateCount):
2388 TranShkAggDstn.append(
2389 MeanOneLogNormal(
2390 sigma=self.TranShkAggStd[i], seed=RNG.integers(0, 2**31 - 1)
2391 ).discretize(
2392 N=self.TranShkAggCount,
2393 )
2394 )
2395 PermShkAggDstn.append(
2396 MeanOneLogNormal(
2397 sigma=self.PermShkAggStd[i], seed=RNG.integers(0, 2**31 - 1)
2398 ).discretize(
2399 N=self.PermShkAggCount,
2400 )
2401 )
2402 AggShkDstn.append(
2403 combine_indep_dstns(
2404 PermShkAggDstn[-1],
2405 TranShkAggDstn[-1],
2406 seed=RNG.integers(0, 2**31 - 1),
2407 )
2408 )
2410 self.TranShkAggDstn = TranShkAggDstn
2411 self.PermShkAggDstn = PermShkAggDstn
2412 self.AggShkDstn = AggShkDstn
2414 def make_AggShkHist(self):
2415 """
2416 Make simulated histories of aggregate transitory and permanent shocks.
2417 Histories are of length self.act_T, for use in the general equilibrium
2418 simulation. Draws on history of aggregate Markov states generated by
2419 internal call to make_Mrkv_history().
2421 Parameters
2422 ----------
2423 None
2425 Returns
2426 -------
2427 None
2428 """
2429 self.make_Mrkv_history() # Make a (pseudo)random sequence of Markov states
2430 sim_periods = self.act_T
2432 # For each Markov state in each simulated period, draw the aggregate shocks
2433 # that would occur in that state in that period
2434 StateCount = self.MrkvArray.shape[0]
2435 PermShkAggHistAll = np.zeros((StateCount, sim_periods))
2436 TranShkAggHistAll = np.zeros((StateCount, sim_periods))
2437 for i in range(StateCount):
2438 AggShockDraws = self.AggShkDstn[i].draw(N=sim_periods)
2439 PermShkAggHistAll[i, :] = AggShockDraws[0, :]
2440 TranShkAggHistAll[i, :] = AggShockDraws[1, :]
2442 # Select the actual history of aggregate shocks based on the sequence
2443 # of Markov states that the economy experiences
2444 PermShkAggHist = np.zeros(sim_periods)
2445 TranShkAggHist = np.zeros(sim_periods)
2446 for i in range(StateCount):
2447 these = i == self.MrkvNow_hist
2448 PermShkAggHist[these] = PermShkAggHistAll[i, these] * self.PermGroFacAgg[i]
2449 TranShkAggHist[these] = TranShkAggHistAll[i, these]
2451 # Store the histories
2452 self.PermShkAggHist = PermShkAggHist
2453 self.TranShkAggHist = TranShkAggHist
2455 def make_Mrkv_history(self):
2456 """
2457 Makes a history of macroeconomic Markov states, stored in the attribute
2458 MrkvNow_hist. This version ensures that each state is reached a sufficient
2459 number of times to have a valid sample for calc_dynamics to produce a good
2460 dynamic rule. It will sometimes cause act_T to be increased beyond its
2461 initially specified level.
2463 Parameters
2464 ----------
2465 None
2467 Returns
2468 -------
2469 None
2470 """
2471 loops_max = getattr(self, "loops_max", 10)
2472 state_T_min = 50 # Choose minimum number of periods in each state for a valid Markov sequence
2473 logit_scale = (
2474 0.2 # Scaling factor on logit choice shocks when jumping to a new state
2475 )
2476 # Values close to zero make the most underrepresented states very likely to visit, while
2477 # large values of logit_scale make any state very likely to be jumped to.
2479 # Reset act_T to the level actually specified by the user
2480 if hasattr(self, "act_T_orig"):
2481 act_T = self.act_T_orig
2482 else: # Or store it for the first time
2483 self.act_T_orig = self.act_T
2484 act_T = self.act_T
2486 # Find the long run distribution of Markov states
2487 w, v = np.linalg.eig(np.transpose(self.MrkvArray))
2488 idx = (np.abs(w - 1.0)).argmin()
2489 x = v[:, idx].astype(float)
2490 LR_dstn = x / np.sum(x)
2492 # Initialize the Markov history and set up transitions
2493 MrkvNow_hist = np.zeros(self.act_T_orig, dtype=int)
2494 loops = 0
2495 go = True
2496 MrkvNow = self.sow_init["Mrkv"]
2497 t = 0
2498 StateCount = self.MrkvArray.shape[0]
2500 # Add histories until each state has been visited at least state_T_min times
2501 rare_dstn = Uniform(seed=0)
2502 while go:
2503 markov_process = MarkovProcess(self.MrkvArray, seed=loops)
2504 for s in range(self.act_T_orig): # Add act_T_orig more periods
2505 MrkvNow_hist[t] = MrkvNow
2506 MrkvNow = markov_process.draw(MrkvNow)
2507 t += 1
2509 # Calculate the empirical distribution
2510 state_T = np.zeros(StateCount)
2511 for i in range(StateCount):
2512 state_T[i] = np.sum(MrkvNow_hist == i)
2514 # Check whether each state has been visited state_T_min times
2515 if np.all(state_T >= state_T_min):
2516 go = False # If so, terminate the loop
2517 continue
2519 # Choose an underrepresented state to "jump" to
2520 if np.any(
2521 state_T == 0
2522 ): # If any states have *never* been visited, randomly choose one of those
2523 never_visited = np.where(np.array(state_T == 0))[0]
2524 MrkvNow = np.random.choice(never_visited)
2525 else: # Otherwise, use logit choice probabilities to visit an underrepresented state
2526 emp_dstn = state_T / act_T
2527 ratios = LR_dstn / emp_dstn
2528 ratios_adj = ratios - np.max(ratios)
2529 ratios_exp = np.exp(ratios_adj / logit_scale)
2530 ratios_sum = np.sum(ratios_exp)
2531 jump_probs = ratios_exp / ratios_sum
2532 cum_probs = np.cumsum(jump_probs)
2533 MrkvNow = np.searchsorted(cum_probs, rare_dstn.draw(1)[0])
2535 loops += 1
2536 # Make the Markov state history longer by act_T_orig periods
2537 if loops >= loops_max:
2538 go = False
2539 raise (
2540 ValueError(
2541 "make_Mrkv_history reached maximum number of loops without generating a valid sequence!"
2542 )
2543 )
2544 else:
2545 MrkvNow_new = np.zeros(self.act_T_orig, dtype=int)
2546 MrkvNow_hist = np.concatenate((MrkvNow_hist, MrkvNow_new))
2547 act_T += self.act_T_orig
2549 # Store the results as attributes of self
2550 self.MrkvNow_hist = MrkvNow_hist
2551 self.act_T = act_T
2553 def mill_rule(self, aLvl, pLvl):
2554 """
2555 Function to calculate the capital to labor ratio, interest factor, and
2556 wage rate based on each agent's current state. Just calls calc_R_and_W()
2557 and adds the Markov state index.
2559 See documentation for calc_R_and_W for more information.
2561 Params
2562 -------
2563 aLvl : float
2564 pLvl : float
2566 Returns
2567 -------
2568 Mnow : float
2569 Aggregate market resources for this period.
2570 Aprev : float
2571 Aggregate savings for the prior period.
2572 KtoLnow : float
2573 Capital-to-labor ratio in the economy this period.
2574 Rnow : float
2575 Interest factor on assets in the economy this period.
2576 Wnow : float
2577 Wage rate for labor in the economy this period.
2578 MrkvNow : int
2579 Binary indicator for bad (0) or good (1) macroeconomic state.
2580 """
2581 MrkvNow = self.MrkvNow_hist[self.Shk_idx]
2582 temp = self.calc_R_and_W(aLvl, pLvl)
2584 return temp + (MrkvNow,)
2586 def calc_AFunc(self, MaggNow, AaggNow):
2587 """
2588 Calculate a new aggregate savings rule based on the history of the
2589 aggregate savings and aggregate market resources from a simulation.
2590 Calculates an aggregate saving rule for each macroeconomic Markov state.
2592 Parameters
2593 ----------
2594 MaggNow : [float]
2595 List of the history of the simulated aggregate market resources for an economy.
2596 AaggNow : [float]
2597 List of the history of the simulated aggregate savings for an economy.
2599 Returns
2600 -------
2601 (unnamed) : CapDynamicRule
2602 Object containing new saving rules for each Markov state.
2603 """
2604 verbose = self.verbose
2605 discard_periods = (
2606 self.T_discard
2607 ) # Throw out the first T periods to allow the simulation to approach the SS
2608 update_weight = (
2609 1.0 - self.DampingFac
2610 ) # Proportional weight to put on new function vs old function parameters
2611 total_periods = len(MaggNow)
2613 # Trim the histories of M_t and A_t and convert them to logs
2614 logAagg = np.log(AaggNow[discard_periods:total_periods])
2615 logMagg = np.log(MaggNow[discard_periods - 1 : total_periods - 1])
2616 MrkvHist = self.MrkvNow_hist[discard_periods - 1 : total_periods - 1]
2618 # For each Markov state, regress A_t on M_t and update the saving rule
2619 AFunc_list = []
2620 rSq_list = []
2621 for i in range(self.MrkvArray.shape[0]):
2622 these = i == MrkvHist
2623 slope, intercept, r_value, p_value, std_err = stats.linregress(
2624 logMagg[these], logAagg[these]
2625 )
2627 # Make a new aggregate savings rule by combining the new regression parameters
2628 # with the previous guess
2629 intercept = (
2630 update_weight * intercept
2631 + (1.0 - update_weight) * self.intercept_prev[i]
2632 )
2633 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev[i]
2634 AFunc_list.append(
2635 AggregateSavingRule(intercept, slope)
2636 ) # Make a new next-period capital function
2637 rSq_list.append(r_value**2)
2639 # Save the new values as "previous" values for the next iteration
2640 self.intercept_prev[i] = intercept
2641 self.slope_prev[i] = slope
2643 # Print the new parameters
2644 if verbose: # pragma: nocover
2645 print(
2646 "intercept="
2647 + str(self.intercept_prev)
2648 + ", slope="
2649 + str(self.slope_prev)
2650 + ", r-sq="
2651 + str(rSq_list)
2652 )
2654 return AggShocksDynamicRule(AFunc_list)
2657# Make a dictionary to specify a small open Markov economy
2658init_mrkv_small_open_economy = init_small_open_economy.copy()
2659init_mrkv_small_open_economy["PermShkAggStd"] = [0.012, 0.006]
2660init_mrkv_small_open_economy["TranShkAggStd"] = [0.006, 0.003]
2661init_mrkv_small_open_economy["PermGroFacAgg"] = [0.98, 1.02]
2662init_mrkv_small_open_economy["MrkvArray"] = MrkvArray
2663init_mrkv_small_open_economy["MrkvInit"] = 0
2664init_mrkv_small_open_economy["slope_prev"] = 2 * [1.0]
2665init_mrkv_small_open_economy["intercept_prev"] = 2 * [0.0]
2666init_mrkv_small_open_economy["Rfree"] = 1.02
2667init_mrkv_small_open_economy["wRte"] = 1.0
2670class SmallOpenMarkovEconomy(CobbDouglasMarkovEconomy, SmallOpenEconomy):
2671 """
2672 A class for representing a small open economy, where the wage rate and interest rate are
2673 exogenously determined by some "global" rate. However, the economy is still subject to
2674 aggregate productivity shocks. This version supports a discrete Markov state. All
2675 methods in this class inherit from the two parent classes.
2676 """
2678 def __init__(self, agents=None, tolerance=0.0001, act_T=1000, **kwds):
2679 agents = agents if agents is not None else list()
2680 temp_dict = init_mrkv_small_open_economy.copy()
2681 temp_dict.update(kwds)
2682 CobbDouglasMarkovEconomy.__init__(
2683 self,
2684 agents=agents,
2685 tolerance=tolerance,
2686 act_T=act_T,
2687 reap_vars=[],
2688 dyn_vars=[],
2689 **temp_dict,
2690 )
2692 def update(self):
2693 SmallOpenEconomy.update(self)
2694 StateCount = self.MrkvArray.shape[0]
2695 self.AFunc = StateCount * [IdentityFunction()]
2697 def make_AggShkDstn(self):
2698 CobbDouglasMarkovEconomy.make_AggShkDstn(self)
2700 def mill_rule(self):
2701 MrkvNow = self.MrkvNow_hist[self.Shk_idx]
2702 temp = SmallOpenEconomy.get_AggShocks(self)
2703 temp += (MrkvNow,)
2704 return temp
2706 def calc_dynamics(self):
2707 return NullFunc()
2709 def make_AggShkHist(self):
2710 CobbDouglasMarkovEconomy.make_AggShkHist(self)
2713init_KS_economy = {
2714 "verbose": False,
2715 "act_T": 11000,
2716 "T_discard": 1000,
2717 "DampingFac": 0.1,
2718 "intercept_prev": [0.0, 0.0],
2719 "slope_prev": [1.0, 1.0],
2720 "DiscFac": 0.99,
2721 "CRRA": 1.0,
2722 # Not listed in KS (1998), but Alan Lujan got this number indirectly from KS
2723 "LbrInd": 0.3271,
2724 "ProdB": 0.99,
2725 "ProdG": 1.01,
2726 "CapShare": 0.36,
2727 "DeprRte": 0.025,
2728 "DurMeanB": 8.0,
2729 "DurMeanG": 8.0,
2730 "SpellMeanB": 2.5,
2731 "SpellMeanG": 1.5,
2732 "UrateB": 0.10,
2733 "UrateG": 0.04,
2734 "RelProbBG": 0.75,
2735 "RelProbGB": 1.25,
2736 "MrkvInit": 0,
2737}
2740class KrusellSmithEconomy(Market):
2741 """
2742 A class to represent an economy in the special Krusell-Smith (1998) model.
2743 This model replicates the one presented in the JPE article "Income and Wealth
2744 Heterogeneity in the Macroeconomy", with its default parameters set to match
2745 those in the paper.
2747 Parameters
2748 ----------
2749 agents : [KrusellSmithType]
2750 List of types of consumers that live in this economy.
2751 tolerance: float
2752 Minimum acceptable distance between "dynamic rules" to consider the
2753 solution process converged. Distance depends on intercept and slope
2754 of the log-linear "next capital ratio" function.
2755 act_T : int
2756 Number of periods to simulate when making a history of of the market.
2757 """
2759 def __init__(self, agents=None, tolerance=0.0001, **kwds):
2760 agents = agents if agents is not None else list()
2761 params = deepcopy(init_KS_economy)
2762 params.update(kwds)
2764 Market.__init__(
2765 self,
2766 agents=agents,
2767 tolerance=tolerance,
2768 sow_vars=["Mnow", "Aprev", "Mrkv", "Rnow", "Wnow"],
2769 reap_vars=["aNow", "EmpNow"],
2770 track_vars=["Mrkv", "Aprev", "Mnow", "Urate"],
2771 dyn_vars=["AFunc"],
2772 **params,
2773 )
2774 self.update()
2776 def update(self):
2777 """
2778 Construct trivial initial guesses of the aggregate saving rules, as well
2779 as the perfect foresight steady state and associated objects.
2780 """
2781 StateCount = 2
2782 AFunc_all = [
2783 AggregateSavingRule(self.intercept_prev[j], self.slope_prev[j])
2784 for j in range(StateCount)
2785 ]
2786 self.AFunc = AFunc_all
2787 self.KtoLSS = (
2788 (1.0**self.CRRA / self.DiscFac - (1.0 - self.DeprRte)) / self.CapShare
2789 ) ** (1.0 / (self.CapShare - 1.0))
2790 self.KSS = self.KtoLSS * self.LbrInd
2791 self.KtoYSS = self.KtoLSS ** (1.0 - self.CapShare)
2792 self.WSS = (1.0 - self.CapShare) * self.KtoLSS ** (self.CapShare)
2793 self.RSS = (
2794 1.0 + self.CapShare * self.KtoLSS ** (self.CapShare - 1.0) - self.DeprRte
2795 )
2796 self.MSS = self.KSS * self.RSS + self.WSS * self.LbrInd
2797 self.convertKtoY = lambda KtoY: KtoY ** (
2798 1.0 / (1.0 - self.CapShare)
2799 ) # converts K/Y to K/L
2800 self.rFunc = lambda k: self.CapShare * k ** (self.CapShare - 1.0)
2801 self.Wfunc = lambda k: ((1.0 - self.CapShare) * k ** (self.CapShare))
2802 self.sow_init["KtoLnow"] = self.KtoLSS
2803 self.sow_init["Mnow"] = self.MSS
2804 self.sow_init["Aprev"] = self.KSS
2805 self.sow_init["Rnow"] = self.RSS
2806 self.sow_init["Wnow"] = self.WSS
2807 self.PermShkAggNow_init = 1.0
2808 self.TranShkAggNow_init = 1.0
2809 self.sow_init["Mrkv"] = 0
2810 self.make_MrkvArray()
2812 def reset(self):
2813 """
2814 Reset the economy to prepare for a new simulation. Sets the time index
2815 of aggregate shocks to zero and runs Market.reset().
2816 """
2817 self.Shk_idx = 0
2818 Market.reset(self)
2820 def make_MrkvArray(self):
2821 """
2822 Construct the attributes MrkvAggArray and MrkvIndArray from the primitive
2823 attributes DurMeanB, DurMeanG, SpellMeanB, SpellMeanG, UrateB, UrateG,
2824 RelProbGB, and RelProbBG.
2825 """
2826 # Construct aggregate Markov transition probabilities
2827 ProbBG = 1.0 / self.DurMeanB
2828 ProbGB = 1.0 / self.DurMeanG
2829 ProbBB = 1.0 - ProbBG
2830 ProbGG = 1.0 - ProbGB
2831 MrkvAggArray = np.array([[ProbBB, ProbBG], [ProbGB, ProbGG]])
2833 # Construct idiosyncratic Markov transition probabilities
2834 # ORDER: BU, BE, GU, GE
2835 MrkvIndArray = np.zeros((4, 4))
2837 # BAD-BAD QUADRANT
2838 MrkvIndArray[0, 1] = ProbBB / self.SpellMeanB
2839 MrkvIndArray[0, 0] = ProbBB * (1 - 1.0 / self.SpellMeanB)
2840 MrkvIndArray[1, 0] = self.UrateB / (1.0 - self.UrateB) * MrkvIndArray[0, 1]
2841 MrkvIndArray[1, 1] = ProbBB - MrkvIndArray[1, 0]
2843 # GOOD-GOOD QUADRANT
2844 MrkvIndArray[2, 3] = ProbGG / self.SpellMeanG
2845 MrkvIndArray[2, 2] = ProbGG * (1 - 1.0 / self.SpellMeanG)
2846 MrkvIndArray[3, 2] = self.UrateG / (1.0 - self.UrateG) * MrkvIndArray[2, 3]
2847 MrkvIndArray[3, 3] = ProbGG - MrkvIndArray[3, 2]
2849 # BAD-GOOD QUADRANT
2850 MrkvIndArray[0, 2] = self.RelProbBG * MrkvIndArray[2, 2] / ProbGG * ProbBG
2851 MrkvIndArray[0, 3] = ProbBG - MrkvIndArray[0, 2]
2852 MrkvIndArray[1, 2] = (
2853 ProbBG * self.UrateG - self.UrateB * MrkvIndArray[0, 2]
2854 ) / (1.0 - self.UrateB)
2855 MrkvIndArray[1, 3] = ProbBG - MrkvIndArray[1, 2]
2857 # GOOD-BAD QUADRANT
2858 MrkvIndArray[2, 0] = self.RelProbGB * MrkvIndArray[0, 0] / ProbBB * ProbGB
2859 MrkvIndArray[2, 1] = ProbGB - MrkvIndArray[2, 0]
2860 MrkvIndArray[3, 0] = (
2861 ProbGB * self.UrateB - self.UrateG * MrkvIndArray[2, 0]
2862 ) / (1.0 - self.UrateG)
2863 MrkvIndArray[3, 1] = ProbGB - MrkvIndArray[3, 0]
2865 # Test for valid idiosyncratic transition probabilities
2866 assert np.all(MrkvIndArray >= 0.0), (
2867 "Invalid idiosyncratic transition probabilities!"
2868 )
2869 self.MrkvAggArray = MrkvAggArray
2870 self.MrkvIndArray = MrkvIndArray
2872 def make_Mrkv_history(self):
2873 """
2874 Makes a history of macroeconomic Markov states, stored in the attribute
2875 MrkvNow_hist. This variable is binary (0 bad, 1 good) in the KS model.
2876 """
2877 # Initialize the Markov history and set up transitions
2878 self.MrkvNow_hist = np.zeros(self.act_T, dtype=int)
2879 MrkvNow = self.MrkvInit
2881 markov_process = MarkovProcess(self.MrkvAggArray, seed=0)
2882 for s in range(self.act_T): # Add act_T_orig more periods
2883 self.MrkvNow_hist[s] = MrkvNow
2884 MrkvNow = markov_process.draw(MrkvNow)
2886 def mill_rule(self, aNow, EmpNow):
2887 """
2888 Method to calculate the capital to labor ratio, interest factor, and
2889 wage rate based on each agent's current state. Just calls calc_R_and_W().
2891 See documentation for calc_R_and_W for more information.
2893 Returns
2894 -------
2895 Mnow : float
2896 Aggregate market resources for this period.
2897 Aprev : float
2898 Aggregate savings for the prior period.
2899 MrkvNow : int
2900 Binary indicator for bad (0) or good (1) macroeconomic state.
2901 Rnow : float
2902 Interest factor on assets in the economy this period.
2903 Wnow : float
2904 Wage rate for labor in the economy this period.
2905 """
2906 return self.calc_R_and_W(aNow, EmpNow)
2908 def calc_dynamics(self, Mnow, Aprev):
2909 """
2910 Method to update perceptions of the aggregate saving rule in each
2911 macroeconomic state; just calls calc_AFunc.
2912 """
2913 return self.calc_AFunc(Mnow, Aprev)
2915 def calc_R_and_W(self, aNow, EmpNow):
2916 """
2917 Calculates the interest factor and wage rate this period using each agent's
2918 capital stock to get the aggregate capital ratio.
2920 Parameters
2921 ----------
2922 aNow : [np.array]
2923 Agents' current end-of-period assets. Elements of the list correspond
2924 to types in the economy, entries within arrays to agents of that type.
2925 EmpNow [np.array]
2926 Agents' binary employment states. Not actually used in computation of
2927 interest and wage rates, but stored in the history to verify that the
2928 idiosyncratic unemployment probabilities are behaving as expected.
2930 Returns
2931 -------
2932 Mnow : float
2933 Aggregate market resources for this period.
2934 Aprev : float
2935 Aggregate savings for the prior period.
2936 MrkvNow : int
2937 Binary indicator for bad (0) or good (1) macroeconomic state.
2938 Rnow : float
2939 Interest factor on assets in the economy this period.
2940 Wnow : float
2941 Wage rate for labor in the economy this period.
2942 """
2943 # Calculate aggregate savings
2944 # End-of-period savings from last period
2945 Aprev = np.mean(np.array(aNow))
2946 # Calculate aggregate capital this period
2947 AggK = Aprev # ...becomes capital today
2949 # Calculate unemployment rate
2950 Urate = 1.0 - np.mean(np.array(EmpNow))
2951 self.Urate = Urate # This is the unemployment rate for the *prior* period
2953 # Get this period's TFP and labor supply
2954 MrkvNow = self.MrkvNow_hist[self.Shk_idx]
2955 if MrkvNow == 0:
2956 Prod = self.ProdB
2957 AggL = (1.0 - self.UrateB) * self.LbrInd
2958 elif MrkvNow == 1:
2959 Prod = self.ProdG
2960 AggL = (1.0 - self.UrateG) * self.LbrInd
2961 self.Shk_idx += 1
2963 # Calculate the interest factor and wage rate this period
2964 KtoLnow = AggK / AggL
2965 Rnow = 1.0 + Prod * self.rFunc(KtoLnow) - self.DeprRte
2966 Wnow = Prod * self.Wfunc(KtoLnow)
2967 Mnow = Rnow * AggK + Wnow * AggL
2968 self.KtoLnow = KtoLnow # Need to store this as it is a sow variable
2970 # Returns a tuple of these values
2971 return Mnow, Aprev, MrkvNow, Rnow, Wnow
2973 def calc_AFunc(self, Mnow, Aprev):
2974 """
2975 Calculate a new aggregate savings rule based on the history of the
2976 aggregate savings and aggregate market resources from a simulation.
2977 Calculates an aggregate saving rule for each macroeconomic Markov state.
2979 Parameters
2980 ----------
2981 Mnow : [float]
2982 List of the history of the simulated aggregate market resources for an economy.
2983 Anow : [float]
2984 List of the history of the simulated aggregate savings for an economy.
2986 Returns
2987 -------
2988 (unnamed) : AggShocksDynamicRule
2989 Object containing new saving rules for each Markov state.
2990 """
2991 verbose = self.verbose
2992 # Throw out the first T periods to allow the simulation to approach the SS
2993 discard_periods = self.T_discard
2994 # Proportional weight to put on new function vs old function parameters
2995 update_weight = 1.0 - self.DampingFac
2996 total_periods = len(Mnow)
2998 # Trim the histories of M_t and A_t and convert them to logs
2999 logAagg = np.log(Aprev[discard_periods:total_periods])
3000 logMagg = np.log(Mnow[discard_periods - 1 : total_periods - 1])
3001 MrkvHist = self.MrkvNow_hist[discard_periods - 1 : total_periods - 1]
3003 # For each Markov state, regress A_t on M_t and update the saving rule
3004 AFunc_list = []
3005 rSq_list = []
3006 for i in range(self.MrkvAggArray.shape[0]):
3007 these = i == MrkvHist
3008 slope, intercept, r_value, p_value, std_err = stats.linregress(
3009 logMagg[these], logAagg[these]
3010 )
3012 # Make a new aggregate savings rule by combining the new regression parameters
3013 # with the previous guess
3014 intercept = (
3015 update_weight * intercept
3016 + (1.0 - update_weight) * self.intercept_prev[i]
3017 )
3018 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev[i]
3019 AFunc_list.append(
3020 AggregateSavingRule(intercept, slope)
3021 ) # Make a new next-period capital function
3022 rSq_list.append(r_value**2)
3024 # Save the new values as "previous" values for the next iteration
3025 self.intercept_prev[i] = intercept
3026 self.slope_prev[i] = slope
3028 # Print the new parameters
3029 if verbose: # pragma: nocover
3030 print(
3031 "intercept="
3032 + str(self.intercept_prev)
3033 + ", slope="
3034 + str(self.slope_prev)
3035 + ", r-sq="
3036 + str(rSq_list)
3037 )
3039 return AggShocksDynamicRule(AFunc_list)
3042class AggregateSavingRule(MetricObject):
3043 """
3044 A class to represent agent beliefs about aggregate saving at the end of this period (AaggNow) as
3045 a function of (normalized) aggregate market resources at the beginning of the period (MaggNow).
3047 Parameters
3048 ----------
3049 intercept : float
3050 Intercept of the log-linear capital evolution rule.
3051 slope : float
3052 Slope of the log-linear capital evolution rule.
3053 """
3055 def __init__(self, intercept, slope):
3056 self.intercept = intercept
3057 self.slope = slope
3058 self.distance_criteria = ["slope", "intercept"]
3060 def __call__(self, Mnow):
3061 """
3062 Evaluates aggregate savings as a function of the aggregate market resources this period.
3064 Parameters
3065 ----------
3066 Mnow : float
3067 Aggregate market resources this period.
3069 Returns
3070 -------
3071 Aagg : Expected aggregate savings this period.
3072 """
3073 Aagg = np.exp(self.intercept + self.slope * np.log(Mnow))
3074 return Aagg
3077class AggShocksDynamicRule(MetricObject):
3078 """
3079 Just a container class for passing the dynamic rule in the aggregate shocks model to agents.
3081 Parameters
3082 ----------
3083 AFunc : CapitalEvoRule
3084 Aggregate savings as a function of aggregate market resources.
3085 """
3087 def __init__(self, AFunc):
3088 self.AFunc = AFunc
3089 self.distance_criteria = ["AFunc"]