Coverage for HARK/ConsumptionSaving/TractableBufferStockModel.py: 83%
152 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-02 05:14 +0000
1"""
2Defines and solves the Tractable Buffer Stock model described in lecture notes
3for "A Tractable Model of Buffer Stock Saving" (henceforth, TBS) available at
4https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/consumption/TractableBufferStock
5The model concerns an agent with constant relative risk aversion utility making
6decisions over consumption and saving. He is subject to only a very particular
7sort of risk: the possibility that he will become permanently unemployed until
8the day he dies; barring this, his income is certain and grows at a constant rate.
10The model has an infinite horizon, but is not solved by backward iteration in a
11traditional sense. Because of the very specific assumptions about risk, it is
12possible to find the agent's steady state or target level of market resources
13when employed, as well as information about the optimal consumption rule at this
14target level. The full consumption function can then be constructed by "back-
15shooting", inverting the Euler equation to find what consumption *must have been*
16in the previous period. The consumption function is thus constructed by repeat-
17edly adding "stable arm" points to either end of a growing list until specified
18bounds are exceeded.
20Despite the non-standard solution method, the iterative process can be embedded
21in the HARK framework, as shown below.
22"""
24from copy import copy
26import numpy as np
27from scipy.optimize import brentq, newton
29from HARK import AgentType, NullFunc
30from HARK.distributions import Bernoulli, Lognormal
31from HARK.interpolation import LinearInterp, CubicInterp
33# Import the HARK library.
34from HARK.metric import MetricObject
35from HARK.rewards import (
36 CRRAutility,
37 CRRAutility_inv,
38 CRRAutility_invP,
39 CRRAutilityP,
40 CRRAutilityP_inv,
41 CRRAutilityPP,
42 CRRAutilityPPP,
43 CRRAutilityPPPP,
44)
46__all__ = ["TractableConsumerSolution", "TractableConsumerType"]
48# If you want to run the "tractable" version of cstwMPC, use cstwMPCagent from
49# cstwMPC REMARK and have TractableConsumerType inherit from cstwMPCagent rather than AgentType
51# Define utility function and its derivatives (plus inverses)
52utility = CRRAutility
53utilityP = CRRAutilityP
54utilityPP = CRRAutilityPP
55utilityPPP = CRRAutilityPPP
56utilityPPPP = CRRAutilityPPPP
57utilityP_inv = CRRAutilityP_inv
58utility_invP = CRRAutility_invP
59utility_inv = CRRAutility_inv
62class TractableConsumerSolution(MetricObject):
63 """
64 A class representing the solution to a tractable buffer saving problem.
65 Attributes include a list of money points mNrm_list, a list of consumption points
66 cNrm_list, a list of MPCs MPC_list, a perfect foresight consumption function
67 while employed, and a perfect foresight consumption function while unemployed.
68 The solution includes a consumption function constructed from the lists.
70 Parameters
71 ----------
72 mNrm_list : [float]
73 List of normalized market resources points on the stable arm.
74 cNrm_list : [float]
75 List of normalized consumption points on the stable arm.
76 MPC_list : [float]
77 List of marginal propensities to consume on the stable arm, corres-
78 ponding to the (mNrm,cNrm) points.
79 cFunc_U : function
80 The (linear) consumption function when permanently unemployed.
81 cFunc : function
82 The consumption function when employed.
83 """
85 def __init__(
86 self,
87 mNrm_list=None,
88 cNrm_list=None,
89 MPC_list=None,
90 cFunc_U=NullFunc,
91 cFunc=NullFunc,
92 ):
93 self.mNrm_list = mNrm_list if mNrm_list is not None else list()
94 self.cNrm_list = cNrm_list if cNrm_list is not None else list()
95 self.MPC_list = MPC_list if MPC_list is not None else list()
96 self.cFunc_U = cFunc_U
97 self.cFunc = cFunc
98 self.distance_criteria = ["PointCount"]
99 # The distance between two solutions is the difference in the number of
100 # stable arm points in each. This is a very crude measure of distance
101 # that captures the notion that the process is over when no points are added.
104def find_next_point(
105 DiscFac,
106 Rfree,
107 CRRA,
108 PermGroFacCmp,
109 UnempPrb,
110 Rnrm,
111 Beth,
112 cNext,
113 mNext,
114 MPCnext,
115 PFMPC,
116):
117 """
118 Calculates what consumption, market resources, and the marginal propensity
119 to consume must have been in the previous period given model parameters and
120 values of market resources, consumption, and MPC today.
122 Parameters
123 ----------
124 DiscFac : float
125 Intertemporal discount factor on future utility.
126 Rfree : float
127 Risk free interest factor on end-of-period assets.
128 PermGroFacCmp : float
129 Permanent income growth factor, compensated for the possibility of
130 permanent unemployment.
131 UnempPrb : float
132 Probability of becoming permanently unemployed.
133 Rnrm : float
134 Interest factor normalized by compensated permanent income growth factor.
135 Beth : float
136 Composite effective discount factor for reverse shooting solution; defined
137 in appendix "Numerical Solution/The Consumption Function" in TBS
138 lecture notes
139 cNext : float
140 Normalized consumption in the succeeding period.
141 mNext : float
142 Normalized market resources in the succeeding period.
143 MPCnext : float
144 The marginal propensity to consume in the succeeding period.
145 PFMPC : float
146 The perfect foresight MPC; also the MPC when permanently unemployed.
148 Returns
149 -------
150 mNow : float
151 Normalized market resources this period.
152 cNow : float
153 Normalized consumption this period.
154 MPCnow : float
155 Marginal propensity to consume this period.
156 """
158 def uPP(x):
159 return utilityPP(x, rho=CRRA)
161 cNow = (
162 PermGroFacCmp
163 * (DiscFac * Rfree) ** (-1.0 / CRRA)
164 * cNext
165 * (1 + UnempPrb * ((cNext / (PFMPC * (mNext - 1.0))) ** CRRA - 1.0))
166 ** (-1.0 / CRRA)
167 )
168 mNow = (PermGroFacCmp / Rfree) * (mNext - 1.0) + cNow
169 cUNext = PFMPC * (mNow - cNow) * Rnrm
170 # See TBS Appendix "E.1 The Consumption Function"
171 natural = (
172 Beth
173 * Rnrm
174 * (1.0 / uPP(cNow))
175 * ((1.0 - UnempPrb) * uPP(cNext) * MPCnext + UnempPrb * uPP(cUNext) * PFMPC)
176 ) # Convenience variable
177 MPCnow = natural / (natural + 1)
178 return mNow, cNow, MPCnow
181def add_to_stable_arm_points(
182 solution_next,
183 DiscFac,
184 Rfree,
185 CRRA,
186 PermGroFacCmp,
187 UnempPrb,
188 PFMPC,
189 Rnrm,
190 Beth,
191 mLowerBnd,
192 mUpperBnd,
193):
194 """
195 Adds a one point to the bottom and top of the list of stable arm points if
196 the bounding levels of mLowerBnd (lower) and mUpperBnd (upper) have not yet
197 been met by a stable arm point in mNrm_list. This acts as the "one period
198 solver" / solve_one_period in the tractable buffer stock model.
200 Parameters
201 ----------
202 solution_next : TractableConsumerSolution
203 The solution object from the previous iteration of the backshooting
204 procedure. Not the "next period" solution per se.
205 DiscFac : float
206 Intertemporal discount factor on future utility.
207 Rfree : float
208 Risk free interest factor on end-of-period assets.
209 CRRA : float
210 Coefficient of relative risk aversion.
211 PermGroFacCmp : float
212 Permanent income growth factor, compensated for the possibility of
213 permanent unemployment.
214 UnempPrb : float
215 Probability of becoming permanently unemployed.
216 PFMPC : float
217 The perfect foresight MPC; also the MPC when permanently unemployed.
218 Rnrm : float
219 Interest factor normalized by compensated permanent income growth factor.
220 Beth : float
221 Damned if I know.
222 mLowerBnd : float
223 Lower bound on market resources for the backshooting process. If
224 min(solution_next.mNrm_list) < mLowerBnd, no new bottom point is found.
225 mUpperBnd : float
226 Upper bound on market resources for the backshooting process. If
227 max(solution_next.mNrm_list) > mUpperBnd, no new top point is found.
229 Returns:
230 ---------
231 solution_now : TractableConsumerSolution
232 A new solution object with new points added to the top and bottom. If
233 no new points were added, then the backshooting process is about to end.
234 """
235 # Unpack the lists of Euler points
236 mNrm_list = copy(solution_next.mNrm_list)
237 cNrm_list = copy(solution_next.cNrm_list)
238 MPC_list = copy(solution_next.MPC_list)
240 # Check whether to add a stable arm point to the top
241 mNext = mNrm_list[-1]
242 if mNext < mUpperBnd:
243 # Get the rest of the data for the previous top point
244 cNext = solution_next.cNrm_list[-1]
245 MPCNext = solution_next.MPC_list[-1]
247 # Calculate employed levels of c, m, and MPC from next period's values
248 mNow, cNow, MPCnow = find_next_point(
249 DiscFac,
250 Rfree,
251 CRRA,
252 PermGroFacCmp,
253 UnempPrb,
254 Rnrm,
255 Beth,
256 cNext,
257 mNext,
258 MPCNext,
259 PFMPC,
260 )
262 # Add this point to the top of the stable arm list
263 mNrm_list.append(mNow)
264 cNrm_list.append(cNow)
265 MPC_list.append(MPCnow)
267 # Check whether to add a stable arm point to the bottom
268 mNext = mNrm_list[0]
269 if mNext > mLowerBnd:
270 # Get the rest of the data for the previous bottom point
271 cNext = solution_next.cNrm_list[0]
272 MPCNext = solution_next.MPC_list[0]
274 # Calculate employed levels of c, m, and MPC from next period's values
275 mNow, cNow, MPCnow = find_next_point(
276 DiscFac,
277 Rfree,
278 CRRA,
279 PermGroFacCmp,
280 UnempPrb,
281 Rnrm,
282 Beth,
283 cNext,
284 mNext,
285 MPCNext,
286 PFMPC,
287 )
289 # Add this point to the top of the stable arm list
290 mNrm_list.insert(0, mNow)
291 cNrm_list.insert(0, cNow)
292 MPC_list.insert(0, MPCnow)
294 # Construct and return this period's solution
295 solution_now = TractableConsumerSolution(
296 mNrm_list=mNrm_list, cNrm_list=cNrm_list, MPC_list=MPC_list
297 )
298 solution_now.PointCount = len(mNrm_list)
299 return solution_now
302###############################################################################
304# Define a dictionary for the tractable buffer stock model
305init_tractable = {
306 "cycles": 0, # infinite horizon
307 "UnempPrb": 0.00625, # Probability of becoming permanently unemployed
308 "DiscFac": 0.975, # Intertemporal discount factor
309 "Rfree": 1.01, # Risk-free interest factor on assets
310 "PermGroFac": 1.0025, # Permanent income growth factor (uncompensated)
311 "CRRA": 1.0, # Coefficient of relative risk aversion
312}
315class TractableConsumerType(AgentType):
316 """
317 Parameters
318 ----------
319 Same as AgentType
320 """
322 time_inv_ = [
323 "DiscFac",
324 "Rfree",
325 "CRRA",
326 "PermGroFacCmp",
327 "UnempPrb",
328 "PFMPC",
329 "Rnrm",
330 "Beth",
331 "mLowerBnd",
332 "mUpperBnd",
333 ]
334 shock_vars_ = ["eStateNow"]
335 state_vars = ["bLvl", "mLvl", "aLvl"]
336 poststate_vars = ["aLvl", "eStateNow"] # For simulation
337 default_ = {"params": init_tractable, "solver": add_to_stable_arm_points}
339 def pre_solve(self):
340 """
341 Calculates all of the solution objects that can be obtained before con-
342 ducting the backshooting routine, including the target levels, the per-
343 fect foresight solution, (marginal) consumption at m=0, and the small
344 perturbations around the steady state.
346 TODO: This should probably all be moved to a constructor function.
348 Parameters
349 ----------
350 none
352 Returns
353 -------
354 none
355 """
357 # Define utility functions
358 def uPP(x):
359 return utilityPP(x, rho=self.CRRA)
361 def uPPP(x):
362 return utilityPPP(x, rho=self.CRRA)
364 def uPPPP(x):
365 return utilityPPPP(x, rho=self.CRRA)
367 # Define some useful constants from model primitives
368 self.PermGroFacCmp = self.PermGroFac / (
369 1.0 - self.UnempPrb
370 ) # "uncertainty compensated" wage growth factor
371 self.Rnrm = (
372 self.Rfree / self.PermGroFacCmp
373 ) # net interest factor (Rfree normalized by wage growth)
374 self.PFMPC = 1.0 - (self.Rfree ** (-1.0)) * (self.Rfree * self.DiscFac) ** (
375 1.0 / self.CRRA
376 ) # MPC for a perfect forsight consumer
377 self.Beth = self.Rnrm * self.DiscFac * self.PermGroFacCmp ** (1.0 - self.CRRA)
379 # Verify that this consumer is impatient
380 PatFacGrowth = (self.Rfree * self.DiscFac) ** (
381 1.0 / self.CRRA
382 ) / self.PermGroFacCmp
383 PatFacReturn = (self.Rfree * self.DiscFac) ** (1.0 / self.CRRA) / self.Rfree
384 if PatFacReturn >= 1.0:
385 raise Exception("Employed consumer not return impatient, cannot solve!")
386 if PatFacGrowth >= 1.0:
387 raise Exception("Employed consumer not growth impatient, cannot solve!")
389 # Find target money and consumption
390 # See TBS Appendix "B.2 A Target Always Exists When Human Wealth Is Infinite"
391 Pi = (1 + (PatFacGrowth ** (-self.CRRA) - 1.0) / self.UnempPrb) ** (
392 1 / self.CRRA
393 )
394 self.h = 1.0 / (1.0 - self.PermGroFac / self.Rfree)
395 zeta = (
396 self.Rnrm * self.PFMPC * Pi
397 ) # See TBS Appendix "C The Exact Formula for target m"
398 self.mTarg = 1.0 + (
399 self.Rfree / (self.PermGroFacCmp + zeta * self.PermGroFacCmp - self.Rfree)
400 )
401 self.cTarg = (1.0 - self.Rnrm ** (-1.0)) * self.mTarg + self.Rnrm ** (-1.0)
402 mTargU = (self.mTarg - self.cTarg) * self.Rnrm
403 cTargU = mTargU * self.PFMPC
404 self.SSperturbance = self.mTarg * 0.1
406 # Find the MPC, MMPC, and MMMPC at the target
407 def mpcTargFixedPointFunc(k):
408 return k * uPP(self.cTarg) - self.Beth * (
409 (1.0 - self.UnempPrb) * (1.0 - k) * k * self.Rnrm * uPP(self.cTarg)
410 + self.PFMPC * self.UnempPrb * (1.0 - k) * self.Rnrm * uPP(cTargU)
411 )
413 self.MPCtarg = newton(mpcTargFixedPointFunc, 0)
415 def mmpcTargFixedPointFunc(kk):
416 return (
417 kk * uPP(self.cTarg)
418 + self.MPCtarg**2.0 * uPPP(self.cTarg)
419 - self.Beth
420 * (
421 -(1.0 - self.UnempPrb)
422 * self.MPCtarg
423 * kk
424 * self.Rnrm
425 * uPP(self.cTarg)
426 + (1.0 - self.UnempPrb)
427 * (1.0 - self.MPCtarg) ** 2.0
428 * kk
429 * self.Rnrm**2.0
430 * uPP(self.cTarg)
431 - self.PFMPC * self.UnempPrb * kk * self.Rnrm * uPP(cTargU)
432 + (1.0 - self.UnempPrb)
433 * (1.0 - self.MPCtarg) ** 2.0
434 * self.MPCtarg**2.0
435 * self.Rnrm**2.0
436 * uPPP(self.cTarg)
437 + self.PFMPC**2.0
438 * self.UnempPrb
439 * (1.0 - self.MPCtarg) ** 2.0
440 * self.Rnrm**2.0
441 * uPPP(cTargU)
442 )
443 )
445 self.MMPCtarg = newton(mmpcTargFixedPointFunc, 0)
447 def mmmpcTargFixedPointFunc(kkk):
448 return (
449 kkk * uPP(self.cTarg)
450 + 3 * self.MPCtarg * self.MMPCtarg * uPPP(self.cTarg)
451 + self.MPCtarg**3 * uPPPP(self.cTarg)
452 - self.Beth
453 * (
454 -(1 - self.UnempPrb)
455 * self.MPCtarg
456 * kkk
457 * self.Rnrm
458 * uPP(self.cTarg)
459 - 3
460 * (1 - self.UnempPrb)
461 * (1 - self.MPCtarg)
462 * self.MMPCtarg**2
463 * self.Rnrm**2
464 * uPP(self.cTarg)
465 + (1 - self.UnempPrb)
466 * (1 - self.MPCtarg) ** 3
467 * kkk
468 * self.Rnrm**3
469 * uPP(self.cTarg)
470 - self.PFMPC * self.UnempPrb * kkk * self.Rnrm * uPP(cTargU)
471 - 3
472 * (1 - self.UnempPrb)
473 * (1 - self.MPCtarg)
474 * self.MPCtarg**2
475 * self.MMPCtarg
476 * self.Rnrm**2
477 * uPPP(self.cTarg)
478 + 3
479 * (1 - self.UnempPrb)
480 * (1 - self.MPCtarg) ** 3
481 * self.MPCtarg
482 * self.MMPCtarg
483 * self.Rnrm**3
484 * uPPP(self.cTarg)
485 - 3
486 * self.PFMPC**2
487 * self.UnempPrb
488 * (1 - self.MPCtarg)
489 * self.MMPCtarg
490 * self.Rnrm**2
491 * uPPP(cTargU)
492 + (1 - self.UnempPrb)
493 * (1 - self.MPCtarg) ** 3
494 * self.MPCtarg**3
495 * self.Rnrm**3
496 * uPPPP(self.cTarg)
497 + self.PFMPC**3
498 * self.UnempPrb
499 * (1 - self.MPCtarg) ** 3
500 * self.Rnrm**3
501 * uPPPP(cTargU)
502 )
503 )
505 self.MMMPCtarg = newton(mmmpcTargFixedPointFunc, 0)
507 # Find the MPC at m=0
508 def f_temp(k):
509 return (
510 self.Beth
511 * self.Rnrm
512 * self.UnempPrb
513 * (self.PFMPC * self.Rnrm * ((1.0 - k) / k)) ** (-self.CRRA - 1.0)
514 * self.PFMPC
515 )
517 def mpcAtZeroFixedPointFunc(k):
518 return k - f_temp(k) / (1 + f_temp(k))
520 # self.MPCmax = newton(mpcAtZeroFixedPointFunc,0.5)
521 self.MPCmax = brentq(
522 mpcAtZeroFixedPointFunc, self.PFMPC, 0.99, xtol=0.00000001, rtol=0.00000001
523 )
525 # Make the initial list of Euler points: target and perturbation to either side
526 mNrm_list = [
527 self.mTarg - self.SSperturbance,
528 self.mTarg,
529 self.mTarg + self.SSperturbance,
530 ]
531 c_perturb_lo = (
532 self.cTarg
533 - self.SSperturbance * self.MPCtarg
534 + 0.5 * self.SSperturbance**2.0 * self.MMPCtarg
535 - (1.0 / 6.0) * self.SSperturbance**3.0 * self.MMMPCtarg
536 )
537 c_perturb_hi = (
538 self.cTarg
539 + self.SSperturbance * self.MPCtarg
540 + 0.5 * self.SSperturbance**2.0 * self.MMPCtarg
541 + (1.0 / 6.0) * self.SSperturbance**3.0 * self.MMMPCtarg
542 )
543 cNrm_list = [c_perturb_lo, self.cTarg, c_perturb_hi]
544 MPC_perturb_lo = (
545 self.MPCtarg
546 - self.SSperturbance * self.MMPCtarg
547 + 0.5 * self.SSperturbance**2.0 * self.MMMPCtarg
548 )
549 MPC_perturb_hi = (
550 self.MPCtarg
551 + self.SSperturbance * self.MMPCtarg
552 + 0.5 * self.SSperturbance**2.0 * self.MMMPCtarg
553 )
554 MPC_list = [MPC_perturb_lo, self.MPCtarg, MPC_perturb_hi]
556 # Set bounds for money (stable arm construction stops when these are exceeded)
557 self.mLowerBnd = 1.0
558 self.mUpperBnd = 2.0 * self.mTarg
560 # Make the terminal period solution
561 solution_terminal = TractableConsumerSolution(
562 mNrm_list=mNrm_list, cNrm_list=cNrm_list, MPC_list=MPC_list
563 )
564 self.solution_terminal = solution_terminal
566 # Make two linear steady state functions
567 self.cSSfunc = lambda m: m * (
568 (self.Rnrm * self.PFMPC * Pi) / (1.0 + self.Rnrm * self.PFMPC * Pi)
569 )
570 self.mSSfunc = (
571 lambda m: (self.PermGroFacCmp / self.Rfree)
572 + (1.0 - self.PermGroFacCmp / self.Rfree) * m
573 )
575 def post_solve(self):
576 """
577 This method adds consumption at m=0 to the list of stable arm points,
578 then constructs the consumption function as a cubic interpolation over
579 those points. Should be run after the backshooting routine is complete.
581 Parameters
582 ----------
583 none
585 Returns
586 -------
587 none
588 """
589 # Add bottom point to the stable arm points
590 self.solution[0].mNrm_list.insert(0, 0.0)
591 self.solution[0].cNrm_list.insert(0, 0.0)
592 self.solution[0].MPC_list.insert(0, self.MPCmax)
594 # Construct an interpolation of the consumption function from the stable arm points
595 self.solution[0].cFunc = CubicInterp(
596 self.solution[0].mNrm_list,
597 self.solution[0].cNrm_list,
598 self.solution[0].MPC_list,
599 self.PFMPC * (self.h - 1.0),
600 self.PFMPC,
601 )
602 self.solution[0].cFunc_U = LinearInterp([0.0, 1.0], [0.0, self.PFMPC])
604 def sim_birth(self, which_agents):
605 """
606 Makes new consumers for the given indices. Initialized variables include aNrm, as
607 well as time variables t_age and t_cycle. Normalized assets are drawn from a lognormal
608 distributions given by aLvlInitMean and aLvlInitStd.
610 Parameters
611 ----------
612 which_agents : np.array(Bool)
613 Boolean array of size self.AgentCount indicating which agents should be "born".
615 Returns
616 -------
617 None
618 """
619 # Get and store states for newly born agents
620 N = np.sum(which_agents) # Number of new consumers to make
621 self.state_now["aLvl"][which_agents] = Lognormal(
622 self.aLvlInitMean,
623 sigma=self.aLvlInitStd,
624 seed=self.RNG.integers(0, 2**31 - 1),
625 ).draw(N)
626 self.shocks["eStateNow"] = np.zeros(self.AgentCount) # Initialize shock array
627 # Agents are born employed
628 self.shocks["eStateNow"][which_agents] = 1.0
629 # How many periods since each agent was born
630 self.t_age[which_agents] = 0
631 self.t_cycle[which_agents] = (
632 0 # Which period of the cycle each agent is currently in
633 )
634 return None
636 def sim_death(self):
637 """
638 Trivial function that returns boolean array of all False, as there is no death.
640 Parameters
641 ----------
642 None
644 Returns
645 -------
646 which_agents : np.array(bool)
647 Boolean array of size AgentCount indicating which agents die.
648 """
649 # Nobody dies in this model
650 which_agents = np.zeros(self.AgentCount, dtype=bool)
651 return which_agents
653 def get_shocks(self):
654 """
655 Determine which agents switch from employment to unemployment. All unemployed agents remain
656 unemployed until death.
658 Parameters
659 ----------
660 None
662 Returns
663 -------
664 None
665 """
666 employed = self.shocks["eStateNow"] == 1.0
667 N = int(np.sum(employed))
668 newly_unemployed = Bernoulli(
669 self.UnempPrb, seed=self.RNG.integers(0, 2**31 - 1)
670 ).draw(N)
671 self.shocks["eStateNow"][employed] = 1.0 - newly_unemployed
673 def transition(self):
674 """
675 Calculate market resources for all agents this period.
677 Parameters
678 ----------
679 None
681 Returns
682 -------
683 None
684 """
685 bLvlNow = self.Rfree * self.state_prev["aLvl"]
686 mLvlNow = bLvlNow + self.shocks["eStateNow"]
688 return bLvlNow, mLvlNow
690 def get_controls(self):
691 """
692 Calculate consumption for each agent this period.
694 Parameters
695 ----------
696 None
698 Returns
699 -------
700 None
701 """
702 employed = self.shocks["eStateNow"] == 1.0
703 unemployed = np.logical_not(employed)
704 cLvlNow = np.zeros(self.AgentCount)
705 cLvlNow[employed] = self.solution[0].cFunc(self.state_now["mLvl"][employed])
706 cLvlNow[unemployed] = self.solution[0].cFunc_U(
707 self.state_now["mLvl"][unemployed]
708 )
709 self.controls["cLvlNow"] = cLvlNow
711 def get_poststates(self):
712 """
713 Calculates end-of-period assets for each consumer of this type.
715 Parameters
716 ----------
717 None
719 Returns
720 -------
721 None
722 """
723 self.state_now["aLvl"] = self.state_now["mLvl"] - self.controls["cLvlNow"]
724 return None