From 446085d4bad035767a2e2d2bef16448cccd62e75 Mon Sep 17 00:00:00 2001 From: Decory Edwards Date: Sun, 13 Aug 2023 18:46:26 -0400 Subject: [PATCH] Added hyperlinks to referenced sections of the html rendered version of the BST paper Note, hyperlinks were only added to the sections of the code defining the solvers. Matt has a pull request which has already begun to make similar changes to the section of the code defining the agent types (specifically, the "check_conditions" and related methods which make direct references to the paper) --- HARK/ConsumptionSaving/ConsIndShockModel.py | 98 +++++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/HARK/ConsumptionSaving/ConsIndShockModel.py b/HARK/ConsumptionSaving/ConsIndShockModel.py index f8426cf99..2ea12dc3c 100644 --- a/HARK/ConsumptionSaving/ConsIndShockModel.py +++ b/HARK/ConsumptionSaving/ConsIndShockModel.py @@ -207,6 +207,10 @@ def append_solution(self, new_solution): # === Classes and functions that solve consumption-saving models === # ===================================================================== +# For theory, see hyperlink targets to expressions in +# url=https://econ-ark.github.io/BufferStockTheory/BufferStockTheory3.html +# For example, the hyperlink to the relevant section of the paper +# would be referenced as: [url]#Name-of-the-section class ConsPerfForesightSolver(MetricObject): """ @@ -309,6 +313,10 @@ def def_value_funcs(self): def make_cFunc_PF(self): """ Makes the (linear) consumption function for this period. + The conditions for which a solution to the unconstrained, perfect + foresight version of the problem are discussed at: + + [url]#PF-Unconstrained-Solution Parameters ---------- @@ -328,8 +336,8 @@ def make_cFunc_PF(self): self.hNrmNow = (self.PermGroFac / self.Rfree) * (self.solution_next.hNrm + 1.0) # Calculate the lower bound of the marginal propensity to consume - PatFac = ((self.Rfree * self.DiscFacEff) ** (1.0 / self.CRRA)) / self.Rfree - self.MPCmin = 1.0 / (1.0 + PatFac / self.solution_next.MPCmin) + RPFac = ((self.Rfree * self.DiscFacEff) ** (1.0 / self.CRRA)) / self.Rfree + self.MPCmin = 1.0 / (1.0 + RPFac / self.solution_next.MPCmin) # Extract the discrete kink points in next period's consumption function; # don't take the last one, as it only defines the extrapolation and is not a kink. @@ -403,10 +411,10 @@ def make_cFunc_PF(self): def add_mNrmTrg(self, solution): """ Finds value of (normalized) market resources m at which individual consumer - expects m not to change. - This will exist if the GICNrm holds. + expects m not to change. A *unique* normalized level of market resources will + exist for individuals when GICMod holds. Further discussed at: - https://econ-ark.github.io/BufferStockTheory#Unique-Stable-Points + [url]#Unique-Stable-Points Parameters ---------- @@ -466,9 +474,9 @@ def add_mNrmStE(self, solution): Finds market resources ratio at which 'balanced growth' is expected. This is the m ratio such that the expected growth rate of the M level matches the expected growth rate of permanent income. This value does - not exist if the Growth Impatience Condition does not hold. + not exist if the Growth Impatience Condition does not hold: - https://econ-ark.github.io/BufferStockTheory#Unique-Stable-Points + [url]#pseudo-steady-state Parameters ---------- @@ -543,8 +551,10 @@ def add_stable_points(self, solution): # 1. There is a non-degenerate SS for constrained PF model if GICRaw holds. # Therefore # Check if (GICRaw and BoroCnstArt) and if so compute them both - thorn = (self.Rfree * self.DiscFacEff) ** (1 / self.CRRA) - GICRaw = 1 > thorn / self.PermGroFac + + APFac = (self.Rfree * self.DiscFacEff) ** (1 / self.CRRA) + + GICRaw = 1 > APFac / self.PermGroFac if self.BoroCnstArt is not None and GICRaw: solution = self.add_mNrmStE(solution) solution = self.add_mNrmTrg(solution) @@ -666,6 +676,11 @@ def set_and_update_values(self, solution_next, IncShkDstn, LivPrb, DiscFac): (etc), the probability of getting the worst income shock next period, the patience factor, human wealth, and the bounding MPCs. + The exposition of the bounding MPC's for the problem incorporating uncertainty + can be found at: + + [url]#Bounds-for-the-Consumption-Functions + Parameters ---------- solution_next : ConsumerSolution @@ -706,8 +721,8 @@ def set_and_update_values(self, solution_next, IncShkDstn, LivPrb, DiscFac): self.vFuncNext = solution_next.vFunc # Update the bounding MPCs and PDV of human wealth: - self.PatFac = ((self.Rfree * self.DiscFacEff) ** (1.0 / self.CRRA)) / self.Rfree - self.MPCminNow = 1.0 / (1.0 + self.PatFac / solution_next.MPCmin) + self.RPFac = ((self.Rfree * self.DiscFacEff) ** (1.0 / self.CRRA)) / self.Rfree + self.MPCminNow = 1.0 / (1.0 + self.RPFac / solution_next.MPCmin) self.Ex_IncNext = np.dot( self.ShkPrbsNext, self.TranShkValsNext * self.PermShkValsNext ) @@ -717,7 +732,7 @@ def set_and_update_values(self, solution_next, IncShkDstn, LivPrb, DiscFac): self.MPCmaxNow = 1.0 / ( 1.0 + (self.WorstIncPrb ** (1.0 / self.CRRA)) - * self.PatFac + * self.RPFac / solution_next.MPCmax ) @@ -728,6 +743,10 @@ def def_BoroCnst(self, BoroCnstArt): """ Defines the constrained portion of the consumption function as cFuncNowCnst, an attribute of self. Uses the artificial and natural borrowing constraints. + The solution to the perfect foresight (artifically) constrained version of the + problem is discussed at: + + [url]#Constrained-Solution Parameters ---------- @@ -1013,17 +1032,17 @@ def add_stable_points(self, solution): """ # 0. Check if GICRaw holds. If so, then mNrmStE will exist. So, compute it. - # 1. Check if GICNrm holds. If so, then mNrmTrg will exist. So, compute it. + # 1. Check if GICMod holds. If so, then mNrmTrg will exist. So, compute it. - thorn = (self.Rfree * self.DiscFacEff) ** (1 / self.CRRA) + APFac = (self.Rfree * self.DiscFacEff) ** (1 / self.CRRA) - GPFRaw = thorn / self.PermGroFac + GPFRaw = APFac / self.PermGroFac self.GPFRaw = GPFRaw GPFNrm = ( - thorn / self.PermGroFac / np.dot(1 / self.PermShkValsNext, self.ShkPrbsNext) + APFac / self.PermGroFac / np.dot(1 / self.PermShkValsNext, self.ShkPrbsNext) ) self.GPFNrm = GPFNrm - GICRaw = 1 > thorn / self.PermGroFac + GICRaw = 1 > APFac / self.PermGroFac self.GICRaw = GICRaw GICNrm = 1 > GPFNrm self.GICNrm = GICNrm @@ -1507,10 +1526,10 @@ def prepare_to_calc_EndOfPrdvP(self): # Recalculate the minimum MPC and human wealth using the interest factor on saving. # This overwrites values from set_and_update_values, which were based on Rboro instead. if KinkBool: - PatFacTop = ( + RPFac_save = ( (self.Rsave * self.DiscFacEff) ** (1.0 / self.CRRA) ) / self.Rsave - self.MPCminNow = 1.0 / (1.0 + PatFacTop / self.solution_next.MPCmin) + self.MPCminNow = 1.0 / (1.0 + RPFac_save / self.solution_next.MPCmin) self.hNrmNow = ( self.PermGroFac / self.Rsave @@ -1943,11 +1962,11 @@ def check_AIC(self, verbose=None): name = "AIC" def test(agent): - return agent.thorn < 1 + return agent.APFac < 1 messages = { True: "The value of the Absolute Patience Factor (APF) for the supplied parameter values satisfies the Absolute Impatience Condition.", - False: "The given type violates the Absolute Impatience Condition with the supplied parameter values; the APF is {0.thorn}", + False: "The given type violates the Absolute Impatience Condition with the supplied parameter values; the APF is {0.APFac}", } verbose_messages = { True: " Because the APF < 1, the absolute amount of consumption is expected to fall over time.", @@ -1963,7 +1982,7 @@ def check_GICRaw(self, verbose=None): name = "GICRaw" # url = "https://econ-ark.github.io/BufferStockTheory/BufferStockTheory3.html#GICRaw" - self.GPFRaw = self.thorn / self.PermGroFac[0] + self.GPFRaw = self.APFac / self.PermGroFac[0] def test(agent): return agent.GPFRaw < 1 @@ -1985,7 +2004,7 @@ def check_RIC(self, verbose=None): Evaluate and report on the Return Impatience Condition """ - self.RPF = self.thorn / self.Rfree + self.RPF = self.APFac / self.Rfree name = "RIC" @@ -2010,7 +2029,7 @@ def check_FHWC(self, verbose=None): """ self.FHWF = self.PermGroFac[0] / self.Rfree - self.cNrmPDV = 1.0 / (1.0 - self.thorn / self.Rfree) + self.cNrmPDV = 1.0 / (1.0 - self.APFac / self.Rfree) name = "FHWC" @@ -2058,7 +2077,7 @@ def check_conditions(self, verbose=None): if self.cycles != 0 or self.T_cycle > 1: return - self.thorn = (self.Rfree * self.DiscFac * self.LivPrb[0]) ** (1 / self.CRRA) + self.APFac = (self.Rfree * self.DiscFac * self.LivPrb[0]) ** (1 / self.CRRA) verbose = self.verbose if verbose is None else verbose self.check_AIC(verbose) @@ -2332,14 +2351,14 @@ def calc_bounding_values(self): temp = self.PermGroFac[0] * PermShkMinNext / self.Rfree BoroCnstNat = -TranShkMinNext * temp / (1.0 - temp) - PatFac = (self.DiscFac * self.LivPrb[0] * self.Rfree) ** ( + RPFac = (self.DiscFac * self.LivPrb[0] * self.Rfree) ** ( 1.0 / self.CRRA ) / self.Rfree if BoroCnstNat < self.BoroCnstArt: MPCmax = 1.0 # if natural borrowing constraint is overridden by artificial one, MPCmax is 1 else: - MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * PatFac - MPCmin = 1.0 - PatFac + MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * RPFac + MPCmin = 1.0 - RPFac # Store the results as attributes of self self.hNrm = hNrm @@ -3081,12 +3100,13 @@ def pre_solve(self): def check_GICNrm(self, verbose=None): """ Check Individual Growth Patience Factor. + # [url]/#GPFacModDefn """ - self.GPFNrm = self.thorn / ( + self.GPFNrm = self.APFac / ( self.PermGroFac[0] * self.InvEx_PermShkInv - ) # [url]/#GICRawI + ) - name = "GICRaw" + name = "GICNrm" def test(agent): return agent.GPFNrm <= 1 @@ -3231,13 +3251,13 @@ def check_conditions(self, verbose=None): self.PermGroFac[0] * self.InvEx_PermShkInv ) # [url]/#PGroAdj - self.thorn = (self.Rfree * self.DiscFac) ** (1 / self.CRRA) + self.APFac = (self.Rfree * self.DiscFac) ** (1 / self.CRRA) # self.Ex_RNrm = self.Rfree*Ex_PermShkInv/(self.PermGroFac[0]*self.LivPrb[0]) - self.GPFRaw = self.thorn / (self.PermGroFac[0]) # [url]/#GPF + self.GPFRaw = self.APFac / (self.PermGroFac[0]) # [url]/#GPF # Lower bound of aggregate wealth growth if all inheritances squandered - self.GPFAggLivPrb = self.thorn * self.LivPrb[0] / self.PermGroFac[0] + self.GPFAggLivPrb = self.APFac * self.LivPrb[0] / self.PermGroFac[0] self.DiscFacGPFRawMax = ((self.PermGroFac[0]) ** (self.CRRA)) / ( self.Rfree @@ -3270,7 +3290,7 @@ def check_conditions(self, verbose=None): _log.warning("GPFRaw = %2.6f " % (self.GPFRaw)) _log.warning("GPFNrm = %2.6f " % (self.GPFNrm)) _log.warning("GPFAggLivPrb = %2.6f " % (self.GPFAggLivPrb)) - _log.warning("Thorn = APF = %2.6f " % (self.thorn)) + _log.warning("APFac = APF = %2.6f " % (self.APFac)) _log.warning("PermGroFacAdj = %2.6f " % (self.PermGroFacAdj)) _log.warning("uInvEpShkuInv = %2.6f " % (self.uInvEpShkuInv)) _log.warning("VAF = %2.6f " % (self.VAF)) @@ -3669,17 +3689,17 @@ def calc_bounding_values(self): temp = self.PermGroFac[0] * PermShkMinNext / self.Rboro BoroCnstNat = -TranShkMinNext * temp / (1.0 - temp) - PatFacTop = (self.DiscFac * self.LivPrb[0] * self.Rsave) ** ( + RPFac_save = (self.DiscFac * self.LivPrb[0] * self.Rsave) ** ( 1.0 / self.CRRA ) / self.Rsave - PatFacBot = (self.DiscFac * self.LivPrb[0] * self.Rboro) ** ( + RPFac_boro = (self.DiscFac * self.LivPrb[0] * self.Rboro) ** ( 1.0 / self.CRRA ) / self.Rboro if BoroCnstNat < self.BoroCnstArt: MPCmax = 1.0 # if natural borrowing constraint is overridden by artificial one, MPCmax is 1 else: - MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * PatFacBot - MPCmin = 1.0 - PatFacTop + MPCmax = 1.0 - WorstIncPrb ** (1.0 / self.CRRA) * RPFac_boro + MPCmin = 1.0 - RPFac_save # Store the results as attributes of self self.hNrm = hNrm