Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Labeled income process #1189

Merged
merged 20 commits into from Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 13 additions & 6 deletions HARK/ConsumptionSaving/ConsIndShockModel.py
Expand Up @@ -33,6 +33,7 @@
from HARK.datasets.SCF.WealthIncomeDist.SCFDistTools import income_wealth_dists_from_scf
from HARK.distribution import (
DiscreteDistribution,
DiscreteDistributionLabeled,
IndexDistribution,
Lognormal,
MeanOneLogNormal,
Expand Down Expand Up @@ -866,7 +867,7 @@ def m_nrm_next(self, shocks, a_nrm, Rfree):
float
normalized market resources in the next period
"""
return Rfree / (self.PermGroFac * shocks[0]) * a_nrm + shocks[1]
return Rfree / (self.PermGroFac * shocks['PermShk']) * a_nrm + shocks['TranShk']

def calc_EndOfPrdvP(self):
"""
Expand All @@ -885,7 +886,7 @@ def calc_EndOfPrdvP(self):
"""

def vp_next(shocks, a_nrm, Rfree):
return shocks[0] ** (-self.CRRA) * self.vPfuncNext(
return shocks['PermShk'] ** (-self.CRRA) * self.vPfuncNext(
self.m_nrm_next(shocks, a_nrm, Rfree)
)

Expand Down Expand Up @@ -1131,7 +1132,7 @@ def make_cubic_cFunc(self, mNrm, cNrm):
"""

def vpp_next(shocks, a_nrm, Rfree):
return shocks[0] ** (-self.CRRA - 1.0) * self.vPPfuncNext(
return shocks['PermShk'] ** (-self.CRRA - 1.0) * self.vPPfuncNext(
self.m_nrm_next(shocks, a_nrm, Rfree)
)

Expand Down Expand Up @@ -1169,7 +1170,7 @@ def make_EndOfPrdvFunc(self, EndOfPrdvP):

def v_lvl_next(shocks, a_nrm, Rfree):
return (
shocks[0] ** (1.0 - self.CRRA) * self.PermGroFac ** (1.0 - self.CRRA)
shocks['PermShk'] ** (1.0 - self.CRRA) * self.PermGroFac ** (1.0 - self.CRRA)
) * self.vFuncNext(self.m_nrm_next(shocks, a_nrm, Rfree))

EndOfPrdv = self.DiscFacEff * expected(
Expand Down Expand Up @@ -3192,7 +3193,7 @@ def __init__(self, sigma, UnempPrb, IncUnemp, n_approx, seed=0):
super().__init__(pmv=dstn_approx.pmv, atoms=dstn_approx.atoms, seed=seed)


class BufferStockIncShkDstn(DiscreteDistribution):
class BufferStockIncShkDstn(DiscreteDistributionLabeled):
"""
A one-period distribution object for the joint distribution of income
shocks (permanent and transitory), as modeled in the Buffer Stock Theory
Expand Down Expand Up @@ -3251,7 +3252,13 @@ def __init__(

joint_dstn = combine_indep_dstns(perm_dstn, tran_dstn)

super().__init__(pmv=joint_dstn.pmv, atoms=joint_dstn.atoms, seed=seed)
super().__init__(
name='Joint distribution of permanent and transitory shocks to income',
var_names=['PermShk','TranShk'],
pmv=joint_dstn.pmv,
data=joint_dstn.atoms,
seed=seed
)


# Make a dictionary to specify a "kinked R" idiosyncratic shock consumer
Expand Down
4 changes: 2 additions & 2 deletions HARK/ConsumptionSaving/ConsMarkovModel.py
Expand Up @@ -323,7 +323,7 @@ def calc_EndOfPrdvPP(self):
"""

def vpp_next(shocks, a_nrm, Rfree):
return shocks[0] ** (-self.CRRA - 1.0) * self.vPPfuncNext(
return shocks['PermShk'] ** (-self.CRRA - 1.0) * self.vPPfuncNext(
self.m_nrm_next(shocks, a_nrm, Rfree)
)

Expand All @@ -332,7 +332,7 @@ def vpp_next(shocks, a_nrm, Rfree):
* self.Rfree
* self.Rfree
* self.PermGroFac ** (-self.CRRA - 1.0)
* calc_expectation(self.IncShkDstn, vpp_next, self.aNrmNow, self.Rfree)
* self.IncShkDstn.expected(vpp_next, self.aNrmNow, self.Rfree)
)
return EndOfPrdvPP

Expand Down
21 changes: 14 additions & 7 deletions HARK/ConsumptionSaving/tests/test_ConsMarkovModel.py
@@ -1,13 +1,16 @@
import numpy as np
from HARK.ConsumptionSaving.ConsIndShockModel import init_idiosyncratic_shocks
from HARK.ConsumptionSaving.ConsMarkovModel import MarkovConsumerType
from HARK.distribution import DiscreteDistribution, MeanOneLogNormal,combine_indep_dstns
from HARK.distribution import (
DiscreteDistribution,
MeanOneLogNormal,
combine_indep_dstns,
DiscreteDistributionLabeled,
)
from copy import copy
import unittest




class test_ConsMarkovSolver(unittest.TestCase):
def setUp(self):

Expand Down Expand Up @@ -61,11 +64,15 @@ def setUp(self):
self.model.vFuncBool = False # for easy toggling here

# Replace the default (lognormal) income distribution with a custom one
employed_income_dist = DiscreteDistribution(
np.ones(1), np.array([[1.0],[1.0]])
employed_income_dist = DiscreteDistributionLabeled(
pmv=np.ones(1),
data=np.array([[1.0], [1.0]]),
var_names=["PermShk", "TranShk"],
) # Definitely get income
unemployed_income_dist = DiscreteDistribution(
np.ones(1), np.array([[1.0],[0.0]])
unemployed_income_dist = DiscreteDistributionLabeled(
pmv=np.ones(1),
data=np.array([[1.0], [0.0]]),
var_names=["PermShk", "TranShk"],
) # Definitely don't
self.model.IncShkDstn = [
[
Expand Down
14 changes: 9 additions & 5 deletions HARK/ConsumptionSaving/tests/test_modelcomparisons.py
Expand Up @@ -18,7 +18,7 @@
)
from HARK.ConsumptionSaving.ConsMarkovModel import MarkovConsumerType
from HARK.ConsumptionSaving.TractableBufferStockModel import TractableConsumerType
from HARK.distribution import DiscreteDistribution
from HARK.distribution import DiscreteDistribution, DiscreteDistributionLabeled


class Compare_PerfectForesight_and_Infinite(unittest.TestCase):
Expand Down Expand Up @@ -158,11 +158,15 @@ def setUp(self):

MarkovType = MarkovConsumerType(**Markov_primitives)
MarkovType.cycles = 0
employed_income_dist = DiscreteDistribution(
np.ones(1), np.array([[1.0],[1.0]])
employed_income_dist = DiscreteDistributionLabeled(
pmv=np.ones(1),
data=np.array([[1.0], [1.0]]),
var_names=["PermShk", "TranShk"],
)
unemployed_income_dist = DiscreteDistribution(
np.ones(1), np.array([[1.0],[0.0]])
unemployed_income_dist = DiscreteDistributionLabeled(
pmv=np.ones(1),
data=np.array([[1.0], [0.0]]),
var_names=["PermShk", "TranShk"],
)
MarkovType.IncShkDstn = [[employed_income_dist, unemployed_income_dist]]

Expand Down
4 changes: 4 additions & 0 deletions HARK/distribution.py
Expand Up @@ -1192,6 +1192,10 @@ def RNG(self):
"""
return self.dataset.RNG

@RNG.setter
def RNG(self,value):
self.dataset.attrs['RNG'] = value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be np.random.RandomState(value)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure? What does this property do?

I wrote as it is because I thought that the get and set method should deal with exactly the same object. Since the getter is

HARK/HARK/distribution.py

Lines 1188 to 1193 in daf2afd

@property
def RNG(self):
"""
Returns the distribution's random number generator.
"""
return self.dataset.RNG

I wrote the setter to the RNG property directly. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is how we initialize a random state with a seed. When we initialize a DiscreteDsitribution object, we set RNG as follows

attrs["RNG"] = np.random.RandomState(seed)

so I guess we would have to do dist.RNG = np.random.RandomState(seed).

Actually I guess this isn't a problem, you just have to be more explicit that the value parameter should be of type RandomState. Maybe just a type check?

But also if we have a setter for dist.seed = value it should also generate a new RNG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the typecheck solution.

You did not add a setter for the seed property (I think?) Should I create one? Would that go in a different PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to have a seed setter. I think it would be fine to go in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alrighty, added a typecheck.

I could not remember why I had to add this setter in the first place, but just ran some tests and found out. The reason was that methods like initialize_sim call Distribution.reset() on things like the income distribution. That method is

def reset(self):
"""
Reset the random number generator of this distribution.
Parameters
----------
"""
self.RNG = np.random.RandomState(self.seed)

So without a setter, it failed


@property
def name(self):
"""
Expand Down
72 changes: 42 additions & 30 deletions examples/ConsumptionSaving/example_ConsMarkovModel.ipynb

Large diffs are not rendered by default.