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

set aggregate property on frame #1064

Merged
merged 5 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Release Data: TBD

#### Major Changes

* FrameAgentType for modular definitions of agents [#865](https://github.com/econ-ark/HARK/pull/865)
* FrameAgentType for modular definitions of agents [#865](https://github.com/econ-ark/HARK/pull/865) [#1064](https://github.com/econ-ark/HARK/pull/1064)
* PortfolioConsumerFrameType, a port of PortfolioConsumerType to use Frames [#865](https://github.com/econ-ark/HARK/pull/865)
* Input parameters for cyclical models now indexed by t [#1039](https://github.com/econ-ark/HARK/pull/1039)
* A IndexDistribution class for representing time-indexed probability distributions [#1018](https://github.com/econ-ark/pull/1018/).
Expand Down
54 changes: 27 additions & 27 deletions HARK/ConsumptionSaving/ConsPortfolioFrameModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
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 import NullFunc # Basic HARK features
from HARK.distribution import Distribution
from HARK.frame import Frame, FrameAgentType
from HARK.ConsumptionSaving.ConsIndShockModel import (
IndShockConsumerType, # PortfolioConsumerType inherits from it
utility, # CRRA utility function
Expand Down Expand Up @@ -71,19 +73,9 @@ def __init__(self, **kwds):
self.solve_one_period = solveConsPortfolio
self.update()

## TODO: Should be defined in the configuration.
self.aggs = {'PermShkAggNow' : None, 'PlvlAgg' : None, 'Risky' : None} # aggregate values
# -- handled differently because only one value each per AgentType
self.shocks = {'Adjust' : None, 'PermShk' : None, 'TranShk' : None}
self.controls = {'cNrm' : None, 'Share' : None}
self.state_now = {
'Rport' : None,
'aLvl' : None,
'aNrm' : None,
'bNrm' : None,
'mNrm' : None,
'pLvl' : None
}
self.shocks = {}
self.controls = {}
self.state_now = {}
Comment on lines +76 to +78
Copy link
Member

Choose a reason for hiding this comment

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

where do these get defined now?

Copy link
Member

Choose a reason for hiding this comment

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

nvm i found it


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

return Lognormal(
Expand Down Expand Up @@ -130,14 +122,12 @@ def transition(self, **context):
# 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
return pLvlNow, bNrmNow, mNrmNow

def transition_ShareNow(self, **context):
"""
Expand Down Expand Up @@ -213,8 +203,9 @@ def transition_poststates(self, **context):
# maybe replace reference to init_portfolio to self.parameters?
frames = [
# todo : make an aggegrate value
Frame(('PermShkAggNow',), ('PermGroFacAgg',),
transition = lambda self, PermGroFacAgg : (PermGroFacAgg,)
Frame(('PermShkAgg',), ('PermGroFacAgg',),
transition = lambda self, PermGroFacAgg : (PermGroFacAgg,),
aggregate = True
),
Frame(
('PermShk'), None,
Expand Down Expand Up @@ -255,7 +246,8 @@ def transition_poststates(self, **context):
# seed=self.RNG.randint(0, 2 ** 31 - 1) : TODO: Seed logic
).approx(
init_portfolio['RiskyCount']
)
),
aggregate = True
),
Frame(
('Adjust'),None,
Expand All @@ -270,21 +262,29 @@ def transition_poststates(self, **context):
('Rport'), ('Share', 'Risky'),
transition = transition_Rport
),
## TODO risk free return rate
Frame(
('pLvl', 'PlvlAgg', 'bNrm', 'mNrm'),
('pLvl', 'aNrm', 'Rport', 'PlvlAgg', 'PermShk', 'TranShk', 'PermShkAggNow'),
default = {'pLvl' : birth_pLvlNow, 'PlvlAgg' : 1.0},
('PlvlAgg'), ('PlvlAgg', 'PermShkAgg'),
default = {'PlvlAgg' : 1.0},
transition = lambda self, PlvlAgg, PermShkAgg : PlvlAgg * PermShkAgg,
aggregate = True
),
Frame(
# TODO: PlvlAgg split out and handled as aggregate
('pLvl', 'bNrm', 'mNrm'),
('pLvl', 'aNrm', 'Rport', 'PlvlAgg', 'PermShk', 'TranShk'),
default = {'pLvl' : birth_pLvlNow},
transition = transition
),
Frame(
('Share'), ('Adjust', 'mNrm'),
default = {'Share' : 0},
transition = transition_ShareNow
transition = transition_ShareNow,
control = True
),
Frame(
('cNrm'), ('Adjust','mNrm','Share'),
transition = transition_cNrmNow
transition = transition_cNrmNow,
control = True
),
Frame(
('aNrm', 'aLvl'), ('aNrm', 'cNrm', 'mNrm', 'pLvl'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_simOnePeriod(self):
)

self.assertAlmostEqual(
self.pcct.aggs['Risky'][0],
self.pcct.shocks['Risky'][0],
0.96358739
)

Expand Down
239 changes: 0 additions & 239 deletions HARK/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,245 +866,6 @@ def clear_history(self):
self.history[var_name] = np.empty((self.T_sim, self.AgentCount)) + np.nan


class Frame():
"""
"""

def __init__(
self,
target,
scope,
default = None,
transition = None,
objective = None
):
"""
"""

self.target = target if isinstance(target, tuple) else (target,) # tuple of variables
self.scope = scope # tuple of variables
self.default = default # default value used in simBirth; a dict
self.transition = transition # for use in simulation
self.objective = objective # for use in solver


class FrameAgentType(AgentType):
"""
A variation of AgentType that uses Frames to organize
its simulation steps.

Frames allow for state, control, and shock resolutions
in a specified order, rather than assuming that they
are resolved as shocks -> states -> controls -> poststates.

Attributes
----------

frames : [Frame]
#Keys are tuples of strings corresponding to model variables.
#Values are methods.
#Each frame method should update the the variables
#named in the key.
#Frame order is significant here.
"""

cycles = 0 # for now, only infinite horizon models.

# frames property
frames = [
Frame(
('y'),('x'),
transition = lambda x: x^2
)
]

def initialize_sim(self):
for agg in self.aggs:
self.aggs[agg] = np.empty(1)

agg_default = [
frame.default[agg] for frame in self.frames
if agg in frame.target
and frame.default is not None
and agg in frame.default
]

if len(agg_default) > 0:
self.aggs[agg][:] = agg_default[0]

for shock in self.shocks:
# TODO: What about aggregate shocks?
self.shocks[shock] = np.empty(self.AgentCount)

for control in self.controls:
self.controls[control] = np.empty(self.AgentCount)

for state in self.state_now:
self.state_now[state] = np.empty(self.AgentCount)
super().initialize_sim()

def sim_one_period(self):
"""
Simulates one period for this type.
Calls each frame in order.
These should be defined for
AgentType subclasses, except getMortality (define
its components simDeath and simBirth instead)
and readShocks.

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

Returns
-------
None
"""
if not hasattr(self, "solution"):
raise Exception(
"Model instance does not have a solution stored. To simulate, it is necessary"
" to run the `solve()` method of the class first."
)

# Mortality adjusts the agent population
self.get_mortality() # Replace some agents with "newborns"

# state_{t-1}
for var in self.state_now:
self.state_prev[var] = self.state_now[var]
# note: this is not type checked for aggregate variables.
self.state_now[var] = np.empty(self.AgentCount)

# transition the variables in the frame
for frame in self.frames:
self.transition_frame(frame)

# Advance time for all agents
self.t_age = self.t_age + 1 # Age all consumers by one period
self.t_cycle = self.t_cycle + 1 # Age all consumers within their cycle
self.t_cycle[
self.t_cycle == self.T_cycle
] = 0 # Resetting to zero for those who have reached the end

def sim_birth(self, which_agents):
"""
Makes new agents for the simulation.
Takes a boolean array as an input, indicating which
agent indices are to be "born".

Populates model variable values with value from `init`
property

Parameters
----------
which_agents : np.array(Bool)
Boolean array of size self.AgentCount indicating which agents should be "born".

Returns
-------
None
"""
for frame in self.frames:
for var in frame.target:

N = np.sum(which_agents)

if frame.default is not None and var in frame.default:
if callable(frame.default[var]):
value = frame.default[var](self, N)
else:
value = frame.default[var]

if var in self.state_now:
## need to check in case of aggregate variables.. PlvlAgg
if hasattr(self.state_now[var],'__getitem__'):
self.state_now[var][which_agents] = value
elif var in self.controls:
self.controls[var][which_agents] = value
elif var in self.shocks:
## assuming no aggregate shocks...
self.shocks[var][which_agents] = value

# from ConsIndShockModel. Needed???
self.t_age[which_agents] = 0 # How many periods since each agent was born
self.t_cycle[
which_agents
] = 0 # Which period of the cycle each agent is currently in

## simplest version of this.
def transition_frame(self, frame):
"""
Updates the model variables in `target`
using the `transition` function.
The transition function will use current model
variable state as arguments.
"""
# build a context object based on model state variables
# and 'self' reference for 'global' variables
context = {} # 'self' : self}
context.update(self.aggs)
context.update(self.shocks)
context.update(self.controls)
context.update(self.state_prev)

# use the "now" version of variables that have already been targetted.
for pre_frame in self.frames[:self.frames.index(frame)]:
for var in pre_frame.target:
if var in self.state_now:
context.update({var : self.state_now[var]})

context.update(self.parameters)

# a method for indicating that a 'previous' version
# of a variable is intended.
# Perhaps store this in a separate notation.py module
#def decrement(var_name):
# return var_name + '_'

# use special notation for the 'previous state' variables
#context.update({
# decrement(var) : state_prev[var]
# for var
# in state_prev

#})

# limit context to scope of frame
local_context = {
var : context[var]
for var
in frame.scope
} if frame.scope is not None else context.copy()

if frame.transition is not None:
if isinstance(frame.transition, Distribution):
# assume this is an IndexDistribution keyed to age (t_cycle)
# for now
# later, t_cycle should be included in local context, etc.
if frame.target[0] in self.aggs: # very clunky, to fix when 'aggregate' is a frame property
new_values = (frame.transition.draw(1),)
else:
new_values = (frame.transition.draw(self.t_cycle),)

else: # transition is function of state variables not an exogenous shock
new_values = frame.transition(
self,
**local_context
)
else:
raise Exception(f"Frame has None for transition: {frame}")

# because we want to alter the 'now' not 'prev' table
context.update(self.state_now)

# because the context was a shallow update,
# the model values can be modified directly(?)
for i,t in enumerate(frame.target):
if t in context:
context[t][:] = new_values[i]
else:
raise Exception(f"From frame {frame.target}, target {t} is not in the context object.")

def solve_agent(agent, verbose):
"""
Solve the dynamic model for one agent type
Expand Down
Loading