Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Proposed constructed input framework #1408

Closed
mnwhite opened this issue Apr 9, 2024 · 11 comments
Closed

Proposed constructed input framework #1408

mnwhite opened this issue Apr 9, 2024 · 11 comments

Comments

@mnwhite
Copy link
Contributor

mnwhite commented Apr 9, 2024

HARK models as basic as ConsIndShock use some "constructed inputs" for their solver, by which I mean objects that are passed to the solve_one_period function but would not have been manually specified by the user. For example, IncShkDstn is a complicated (time-varying) object that could (in principle) be manually created by a user and assigned to an instance of IndShockConsumerType, but more reasonably would be constructed from "primitive parameters" like PermShkStd, TrankShkStd, UnempPrb, etc. As-is, PermGroFac is expected to be provided by the user, but it in principle it could be constructed on the AgentType instance given some parametric description of the income growth profile.

Each AgentType subclass has "baked in" constructors, which are called from (or simply part of) an update_X method. The update method itself is supposed to call all of those constructors, as a universal way to bring constructed inputs up-to-date with any changes made to the primitive parameters. It does so by explicitly calling each update_X method, sometimes from its parent class.

As is, if a user wants to have a different way of constructing such an input, they can either a) do it themselves and then drop in the result into their instances; or b) make a trivial subclass of the AgentType that overrides that constructor method. Neither of these are "nice", and there's no organized way for a new user to find out what the required "primitive inputs" are. I'm going to fix this, and this issue presents an overview of my proposal for doing so.


I propose that AgentType instances have a new constructors dictionary. The keys of this dictionary would be the names of inputs to the solve_one_period function for that class; the value for each key would be a constructor function whose inputs name the primitiver (or at least more primitive) parameters that are used by that constructor; those parameters would live as attributes on the instance (or in the parameters dictionary once that's fully implemented).

The top-level AgentType would have a universal method (e.g. called construct) that automatically runs some or all of the functions in the constructors dictionary. If the user passes one or more strings to this method, it runs the constructors for the named parameters. If the user passes nothing to the method, it runs all of the constructors.

When a constructor is run by construct, it tries to pull in the parameters named in its arguments (from the agents or its parameters dictionary), passes them to the function, and then assigns the output to that key (in the parameters dictionary or the agent itself, depending on what stage of development we're at).

If construct fails to find the necessary primitive parameters, it flags that constructor and records the names of the missing parameters. It then proceeds through the other constructors that have been requested. After it finishes all of them, if at least one was flagged as incomplete, it tries again, because new data might be available. If construct completes a "pass" with no constructors being successful, then it quits. The names of the "missing data" are stored or returned.

This structure makes it much easier and simpler for a user to change the construction method for a solver input, to make an input constructed, or to indicate that an input is not constructed (just delete its entry from constructors!). E.g. if a user decided that the income process they want to use has binary permanent shocks (characterized by the probability and value of one of the shocks), they can just change one function name in their IndShockConsumerType instances and provide parameters called (e.g.) LowPermShkVal and LowPermShkPrb rather than PermShkStd, PermShkCount, etc.

It also makes it easier for users to find out what primitive parameters / data are needed to successfully run construct. We can write a very simple query method that lists all of the inputs for each constructor, and which ones are currently missing (as well as related methods for non-constructed solver inputs).

There are two kinks in the design that I need to work out. First, how to handle allowing the user to specify that some input is time-varying vs not time-varying-- and how to indicate that the solver will not function properly if some inputs are improperly converted to time-varying. The simplest solution is to enforce at the class level which solver inputs can be time-varying, and require that the user pass a constant list if they want it to be time-invariant for their purposes (maybe with a method to handle that automatically).

Second, how to handle situations where it's most natural to construct two solver inputs simultaneously-- they're biproducts of the same process. I can think of a slightly clunky and inelegant workaround, but my "slightly clunky" is others' "hideously bad design".

@llorracc
Copy link
Collaborator

llorracc commented Apr 10, 2024 via email

@mnwhite
Copy link
Contributor Author

mnwhite commented Apr 10, 2024 via email

@sbenthall
Copy link
Contributor

sbenthall commented Apr 10, 2024 via email

@mnwhite
Copy link
Contributor Author

mnwhite commented Apr 10, 2024 via email

@sbenthall
Copy link
Contributor

sbenthall commented Apr 11, 2024 via email

@mnwhite
Copy link
Contributor Author

mnwhite commented Apr 11, 2024 via email

@sbenthall
Copy link
Contributor

Is there a restricted set of types-of-thing that 'constructed inputs' can be?

It sounds like all the examples listed so far are mathematical objects of some kind -- distributions, vectors, scalars.

I see now the different meanings of 'construct' in programming and mathematics; I wonder if there's a way to avoid ambiguity.

One way might be to name what inputs-to-solutions.

Seems to me that these are either parameterizations or definitions of exogenous processes, but maybe I'm missing some counterexample.

Clarity about this would also help with making clear the mathematically expressiveness of the library.

@mnwhite
Copy link
Contributor Author

mnwhite commented Apr 11, 2024 via email

@sbenthall
Copy link
Contributor

sbenthall commented Apr 11, 2024 via email

@Mv77
Copy link
Contributor

Mv77 commented Apr 12, 2024

@mnwhite this is very important work.

Another trivial example is that, under the current design, it is very cumbersome to solve a portfolio choice model where the return factor is not lognormal. With Matt's proposal, it would be easy.

I would like the Agent classes impose as little structure as needed. The ConsIndShk solver just needs the income distribution to be a bi-variate discrete distribution over positive numbers. We are currently forcing a lot of additional structure upon the user.

@Mv77
Copy link
Contributor

Mv77 commented Apr 12, 2024

I think that this is a worthwhile goal that is not that related to the time/age varying debate, I don't think the initiative should get bogged down there. This is an improvement in design that could be amended to accommodate the time/age architecture we land on.

@econ-ark econ-ark locked and limited conversation to collaborators Jun 3, 2024
@sbenthall sbenthall converted this issue into discussion #1446 Jun 3, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants