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

PortfolioFrameModel and FrameAgentType #865

Merged
merged 53 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c43ed7c
adding FrameAgent and Portfolio demo case
sbenthall Nov 6, 2020
115990a
set 'ShareNow' properly in simBirth
sbenthall Nov 6, 2020
da7a5e4
sample birth values of FrameAgents using methods
sbenthall Nov 7, 2020
f8538da
splitting PortfolioFrameModel controls into two frames
sbenthall Nov 7, 2020
e54f861
sketching functional transitions
sbenthall Nov 9, 2020
0caef0e
a Frame object for combining default values and transition functions
sbenthall Nov 19, 2020
cd3b516
adding transition inverse example
sbenthall Nov 24, 2020
e573b69
Add NARK.md and NARK.pdf to NARK root
llorracc Nov 20, 2020
40ad034
Delete NARK.md
llorracc Nov 21, 2020
f54109b
Merge branch 'master' of github.com:econ-ark/HARK
sbenthall Dec 15, 2020
0c705a8
adding FrameAgent and Portfolio demo case
sbenthall Nov 6, 2020
173e842
sample birth values of FrameAgents using methods
sbenthall Nov 7, 2020
fa6070c
splitting PortfolioFrameModel controls into two frames
sbenthall Nov 7, 2020
d04bd8b
changelog for FrameAgentType #865
sbenthall Dec 16, 2020
b817448
better documentation for the new Frame classes
sbenthall Dec 16, 2020
9d0795f
fix label on final frame
sbenthall Dec 21, 2020
0244022
Merge branch 'master' into i862
sbenthall Jun 14, 2021
5e65852
more reconciling FrameModel work with 0.11.0 changes
sbenthall Jun 14, 2021
6cd15c9
Merge branch 'master' into i862
sbenthall Jun 14, 2021
8c9e534
Merge branch 'i862b' into i862 -- adding functional Frames
sbenthall Jun 14, 2021
146f83e
small fixes
sbenthall Jun 14, 2021
9862373
bringing back in Frame class; adjusting for new RiskyAssetModel
sbenthall Jun 15, 2021
17ae983
Merge branch 'master' into i862
sbenthall Jun 18, 2021
37d8558
use IndexDistribution for Adjust shock in PortfolioFrameModel
sbenthall Jun 18, 2021
9108b57
using IndexDistribution and functional transition in Frame model
sbenthall Jun 21, 2021
220df3e
adding income shocks to PortfolioFrame model; needs IndexDistribution…
sbenthall Jun 21, 2021
e6941ce
Merge branch 'master' into i862
sbenthall Jul 22, 2021
b74f0f4
completing merge from master
sbenthall Jul 22, 2021
277b9ef
infinite models only for FramedAgentType
sbenthall Jul 22, 2021
804d619
IndexDistribution.approx now returns TimeVaryingDistribution
sbenthall Jul 22, 2021
705b8c4
Merge branch 'sb-distributionb' into i862
sbenthall Jul 22, 2021
11ff594
further work on the FrameAgentType and PortfolioFrameModel example
sbenthall Jul 29, 2021
872bcb3
fixing distribution based updates of shock values
sbenthall Jul 30, 2021
3b0286b
adding handling of aggregate values to FrameAgentModel
sbenthall Aug 2, 2021
d1b5133
small tweaks towards integrated aggregate values
sbenthall Aug 3, 2021
5d360b4
removing import pdb that should NOT be in master...
sbenthall Aug 3, 2021
b91bc72
new tests for ConsPortfolio*Model sim_one_period
sbenthall Aug 5, 2021
42a1c13
more tests for ConsPortfolio*Model
sbenthall Aug 5, 2021
7451637
using appropriate prev/now state
sbenthall Aug 9, 2021
0755994
use parameters['Rfree']
sbenthall Aug 9, 2021
9bc011d
updated tests
sbenthall Aug 9, 2021
0b5e8a2
Merge branch 'master' of github.com:econ-ark/HARK into i862
sbenthall Aug 9, 2021
70b7533
add time-varying discrete distribution to allowable types for time-va…
sbenthall Aug 9, 2021
95c58d6
add_discrete_out_constant_mean now distributions over TimeVaryingDisc…
sbenthall Aug 19, 2021
126a404
remove stray pdb
sbenthall Aug 19, 2021
b38de25
fixing PermShk mean in PortfolioFrameModel to take into account PermG…
sbenthall Aug 19, 2021
7aaad5a
Merge branch 'master' into i862
sbenthall Aug 19, 2021
06e74f4
make Risky an aggregate shock
sbenthall Aug 31, 2021
29c7d97
computing Rport in a state frame
sbenthall Aug 31, 2021
29d8a8c
cleanup
sbenthall Aug 31, 2021
e44a5fd
Merge branch 'master' of github.com:econ-ark/HARK into i862
sbenthall Aug 31, 2021
8776deb
adding FrameAgentType demo notebook comparing with the original Portf…
sbenthall Aug 31, 2021
4d4056c
Merge branch 'i862' of github.com:sbenthall/HARK into i862
sbenthall Aug 31, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Release Data: TBD

#### Major Changes

* FrameAgentType for modular definitions of agents [#865](https://github.com/econ-ark/HARK/pull/865)
* PortfolioConsumerFrameType, a port of PortfolioConsumerType to use Frames [#865](https://github.com/econ-ark/HARK/pull/865)
* A IndexDistribution class for representing time-indexed probability distributions [#1018](https://github.com/econ-ark/pull/1018/).
* Adds new consumption-savings-portfolio model `RiskyContrib`, which represents an agent who can save in risky and risk-free assets but faces
frictions to moving funds between them. To circumvent these frictions, he has access to an income-deduction scheme to accumulate risky assets.
Expand Down
3 changes: 0 additions & 3 deletions HARK/ConsumptionSaving/ConsAggShockModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,9 +1022,6 @@ def get_shocks(self):
# TODO: replace poststate_vars functionality with shocks here
EmpNow = self.state_now["EmpNow"]

if employed.sum() != emp_permute.size:
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not familiar with this bit. Not sure what this is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pdb is the 'python debugger'. It looks like somebody (probably me) was debugging and accidentally got this breakpoint pushed into the master branch by mistake.

I could remove it in a separate PR, which would be cleaner, but did so here so I didn't forget.

import pdb; pdb.set_trace()

# It's really this permutation that is the shock...
# This apparatus is trying to 'exact match' the 'internal' Markov process.
EmpNow[employed] = self.RNG.permutation(emp_permute)
Expand Down
286 changes: 286 additions & 0 deletions HARK/ConsumptionSaving/ConsPortfolioFrameModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
"""
This file contains classes and functions for representing,
solving, and simulating agents who must allocate their resources
among consumption, saving in a risk-free asset (with a low return),
and saving in a risky asset (with higher average return).

This file also demonstrates a "frame" model architecture.
"""
import numpy as np
from scipy.optimize import minimize_scalar
from copy import deepcopy
from HARK import NullFunc, Frame, FrameAgentType # Basic HARK features
from HARK.ConsumptionSaving.ConsIndShockModel import (
IndShockConsumerType, # PortfolioConsumerType inherits from it
utility, # CRRA utility function
utility_inv, # Inverse CRRA utility function
utilityP, # CRRA marginal utility function
utility_invP, # Derivative of inverse CRRA utility function
utilityP_inv, # Inverse CRRA marginal utility function
init_idiosyncratic_shocks, # Baseline dictionary to build on
)
from HARK.ConsumptionSaving.ConsRiskyAssetModel import (
RiskyAssetConsumerType
)
from HARK.ConsumptionSaving.ConsPortfolioModel import (
init_portfolio,
solveConsPortfolio,
PortfolioConsumerType,
PortfolioSolution
)

from HARK.distribution import combine_indep_dstns, add_discrete_outcome_constant_mean
from HARK.distribution import (
IndexDistribution,
Lognormal,
MeanOneLogNormal,
Bernoulli # Random draws for simulating agents
)
from HARK.interpolation import (

LinearInterp, # Piecewise linear interpolation
CubicInterp, # Piecewise cubic interpolation
LinearInterpOnInterp1D, # Interpolator over 1D interpolations
BilinearInterp, # 2D interpolator
ConstantFunction, # Interpolator-like class that returns constant value
IdentityFunction, # Interpolator-like class that returns one of its arguments
ValueFuncCRRA,
MargValueFuncCRRA,
MargMargValueFuncCRRA
)

class PortfolioConsumerFrameType(FrameAgentType, PortfolioConsumerType):
"""
A consumer type with a portfolio choice, using Frame architecture.

A subclass of PortfolioConsumerType for now.
This is mainly to keep the _solver_ logic intact.
"""

def __init__(self, **kwds):
params = init_portfolio.copy()
params.update(kwds)
kwds = params

# Initialize a basic consumer type
PortfolioConsumerType.__init__(
self, **kwds
)

# Set the solver for the portfolio model, and update various constructed attributes
self.solve_one_period = solveConsPortfolio
self.update()

## TODO: Should be defined in the configuration.
self.aggs = {'PermShkAggNow' : None, 'PlvlAgg' : None} # aggregate values
Copy link
Contributor

Choose a reason for hiding this comment

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

I like what you have done here, fully specifying states, shocks, controls and aggregates.

When creating new models, a frustration of mine has been the inconsistency with which that specification is done across different HARK agent types. Its sometimes done, sometimes not, sometimes inherited so one has to go hunting for the original class' attributes, sometimes fully specified.

It is further complicated by two different objects shock_vars and shock_vars_ (see e.g.) with no indication of what each of them does. I think I have seen this for states too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What I would like to do ultimately is move all of this information into the frame definitions.
Then have the constructor code build these dictionaries based on those frames.
Ultimately, I want all model information in the frames dictionary, and nothing baked into a model class.
This is sort of an intermediary implementation.

# -- handled differently because only one value each per AgentType
self.shocks = {'Adjust' : None, 'PermShk' : None, 'Risky': None, 'TranShk' : None}
self.controls = {'cNrm' : None, 'Share' : None}
self.state_now = {
Copy link
Contributor

Choose a reason for hiding this comment

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

was the _now not out of fashion now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are doing some logical work as per the current HARK simulation code.
There, there's both state_now and state_prev, because transition functions can depend on the previous period's state.

I can see how we could do an implementation where the state memory array was duplicated each period, and updated frame-by-frame.

That's an implementation detail, as far as I'm concerned, not a matter of model logic. I'm not opposed to making the implementation consistent for shocks, controls, and states, as another PR. But there may be benefits to keeping state_prev.

'aLvl' : None,
'aNrm' : None,
'bNrm' : None,
'mNrm' : None,
'pLvl' : None
}

# TODO: streamline this so it can draw the parameters from context
def birth_aNrmNow(self, N):
"""
Birth value for aNrmNow
"""
return Lognormal(
mu=self.aNrmInitMean,
sigma=self.aNrmInitStd,
seed=self.RNG.randint(0, 2 ** 31 - 1),
).draw(N)

# TODO: streamline this so it can draw the parameters from context
def birth_pLvlNow(self, N):
"""
Birth value for pLvlNow
"""
pLvlInitMeanNow = self.pLvlInitMean + np.log(
self.aggs["PlvlAgg"]
) # Account for newer cohorts having higher permanent income

return Lognormal(
pLvlInitMeanNow,
self.pLvlInitStd,
seed=self.RNG.randint(0, 2 ** 31 - 1)
).draw(N)


def transition(self, **context):
pLvlPrev = context['pLvl']
aNrmPrev = context['aNrm']

# This should be computed separately in its own transition
# Using IndShock get_Rfree instead of generic.
RfreeNow = self.parameters['Rfree'] * np.ones(self.AgentCount)

# Calculate new states: normalized market resources and permanent income level
pLvlNow = pLvlPrev * context['PermShk'] # Updated permanent income level

# Updated aggregate permanent productivity level
PlvlAggNow = context['PlvlAgg'] * context['PermShkAggNow']
# "Effective" interest factor on normalized assets
ReffNow = RfreeNow / context['PermShk']
bNrmNow = ReffNow * aNrmPrev # Bank balances before labor income
mNrmNow = bNrmNow + context['TranShk'] # Market resources after income

return pLvlNow, PlvlAggNow, bNrmNow, mNrmNow

def transition_ShareNow(self, **context):
"""
Transition method for ShareNow.
"""
## Changed from HARK. See #1049. Should be added to context.
ShareNow = self.controls['Share'].copy()

# Loop over each period of the cycle, getting controls separately depending on "age"
for t in range(self.T_cycle):
these = t == self.t_cycle

# Get controls for agents who *can* adjust their portfolio share
those = np.logical_and(these, context['Adjust'])

ShareNow[those] = self.solution[t].ShareFuncAdj(context['mNrm'][those])

# Get Controls for agents who *can't* adjust their portfolio share
those = np.logical_and(
these,
np.logical_not(context['Adjust']))
ShareNow[those] = self.solution[t].ShareFuncFxd(
context['mNrm'][those], ShareNow[those]
)

self.controls["Share"] = ShareNow

return ShareNow,

def transition_cNrmNow(self, **context):
"""
Transition method for cNrmNow.
"""
cNrmNow = np.zeros(self.AgentCount) + np.nan
ShareNow = context["Share"]

# Loop over each period of the cycle, getting controls separately depending on "age"
for t in range(self.T_cycle):
these = t == self.t_cycle

# Get controls for agents who *can* adjust their portfolio share
those = np.logical_and(these, context['Adjust'])
cNrmNow[those] = self.solution[t].cFuncAdj(context['mNrm'][those])

# Get Controls for agents who *can't* adjust their portfolio share
those = np.logical_and(
these,
np.logical_not(context['Adjust']))
cNrmNow[those] = self.solution[t].cFuncFxd(
context['mNrm'][those], ShareNow[those]
)

# Store controls as attributes of self
# redundant for now
self.controls["cNrm"] = cNrmNow

return cNrmNow,

def transition_poststates(self, **context):
"""
Calculates end-of-period assets for each consumer of this type.

Parameters
----------
None

Returns
-------
None
"""
# should this be "Now", or "Prev"?!?
self.state_now['aNrm'] = context['mNrm'] - context['cNrm']
# Useful in some cases to precalculate asset level
self.state_now['aLvl'] = context['aNrm'] * context['pLvl']

return (self.state_now['aNrm'], self.state_now['aLvl'])

# maybe replace reference to init_portfolio to self.parameters?
frames = [
Frame(('PermShkAggNow',), ('PermGroFacAgg',),
transition = lambda self, PermGroFacAgg : (PermGroFacAgg,)
),
Frame(
('PermShk'), None,
default = {'PermShk' : 1.0}, # maybe this is unnecessary because the shock gets sampled at t = 0
# this is discretized before it's sampled
transition = IndexDistribution(
MeanOneLogNormal,
{
'sigma' : init_portfolio['PermShkStd']
}
).approx(
init_portfolio['PermShkCount'], tail_N=0
),
),
Frame(
('TranShk'), None,
default = {'TranShk' : 1.0}, # maybe this is unnecessary because the shock gets sampled at t = 0
transition = IndexDistribution(
# Need to discretize this "first" to add unemployed in
# TODO: Encapsulate this widely used distribution form in the OO logic.
lambda sigma, seed : add_discrete_outcome_constant_mean(
MeanOneLogNormal(sigma = sigma, seed = seed).approx(
init_portfolio['TranShkCount'], tail_N=0
),
p = init_portfolio['UnempPrb'], x = init_portfolio['IncUnemp']
),
{
'sigma' : init_portfolio['PermShkStd']
}
),
),
Frame( ## TODO: Handle Risky as an Aggregate value
('Risky'),None,
transition = IndexDistribution(
Lognormal.from_mean_std,
{
'mean' : init_portfolio['RiskyAvg'],
'std' : init_portfolio['RiskyStd']
}
# seed=self.RNG.randint(0, 2 ** 31 - 1) : TODO: Seed logic
)
),
Frame(
('Adjust'),None,
default = {'Adjust' : False},
transition = IndexDistribution(
Bernoulli,
{'p' : init_portfolio['AdjustPrb']},
# seed=self.RNG.randint(0, 2 ** 31 - 1) : TODO: Seed logic
) # self.t_cycle input implied
),
## TODO risk free return rate
Frame(
('pLvl', 'PlvlAgg', 'bNrm', 'mNrm'),
('pLvl', 'aNrm', 'Rfree', 'PlvlAgg', 'PermShk', 'TranShk', 'PermShkAggNow'),
default = {'pLvl' : birth_pLvlNow, 'PlvlAgg' : 1.0},
transition = transition
),
Frame(
('Share'), ('Adjust', 'mNrm'),
default = {'Share' : 0},
transition = transition_ShareNow
),
Frame(
('cNrm'), ('Adjust','mNrm','Share'),
transition = transition_cNrmNow
),
Frame(
('aNrm', 'aLvl'), ('aNrm', 'cNrm', 'mNrm', 'pLvl'),
default = {'aNrm' : birth_aNrmNow},
transition = transition_poststates
)
]
1 change: 0 additions & 1 deletion HARK/ConsumptionSaving/ConsPortfolioModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import numpy as np
from scipy.optimize import minimize_scalar

from HARK import MetricObject, NullFunc, AgentType # Basic HARK features
from HARK.ConsumptionSaving.ConsIndShockModel import (
IndShockConsumerType, # PortfolioConsumerType inherits from it
Expand Down
Loading