econ-ark / HARK

Heterogenous Agents Resources & toolKit
Apache License 2.0
312 stars 195 forks source link

Proposed constructed input framework #1408

Open mnwhite opened 3 weeks ago

mnwhite commented 3 weeks ago

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 commented 3 weeks ago

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI . You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll
mnwhite commented 3 weeks ago

I do understand your views, and I want to build in those capabilities. They're useful both for teaching concepts and for the beginning stages of model development. But a lot of the use cases of HARK are "production stage", where you can specify the whole problem structure in advance. We want to be able to accommodate that and make it useful.

The "construction step" is usually (but not always) a very small portion of the total solve time. We can have options to only construct solver inputs for the next-back period ("JIT construction"), but it won't speed things up, and will often slow them down. In some cases, it's not meaningfully possible or a pure waste of CPU cycles to attempt it.

Right now, we have a disorganized and hardwired system for making constructed inputs. I'm just proposing a way to make it more organized for the typical use case where the user can specify the problem in advance. This doesn't rule out other use cases in which new periods are constructed wholly on the fly.

The issue I was raising with time-varying inputs vs time-invariant ones is that some solver math relies on an assumption of time invariance (e.g. CRRA). That solver math can be changed or generalized, but some solution properties will be lost. If we want to make a decision now that we never want to make any assumptions about future values / concepts, we should talk about that.

On Tue, Apr 9, 2024, 8:19 PM Christopher Llorracc Carroll < @.***> wrote:

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

  • their [image: t-1] solver requires some subset (probably nonstrict) of the period-[image: t] beginning-of-period states and functions (like, k and vFunc_{t}).
  • they define a method of simulation that can be invoked to apply the shocks and decision rules to get from the beginning of [image: t-1] to the end

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe < https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046236249, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFKDK7535IOETIM3MN3Y4SASXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGIZTMMRUHE . You are receiving this because you authored the thread.Message ID: @.***>

sbenthall commented 2 weeks ago

It might be instructive to look at Dolo for guidance about this, especially with respect to one thing, which is that Dolo separates the endogenous and exogenous aspects of a problem.

The exogenous structure can be more complex than IID shocks. AR(1) processes for example.

I know that income shocks are one thing that will go on the 'constructors' dictionary with your new plan. What else will? Are they all exogenous?

On Tue, Apr 9, 2024, 9:15 PM Matthew N. White @.***> wrote:

I do understand your views, and I want to build in those capabilities. They're useful both for teaching concepts and for the beginning stages of model development. But a lot of the use cases of HARK are "production stage", where you can specify the whole problem structure in advance. We want to be able to accommodate that and make it useful.

The "construction step" is usually (but not always) a very small portion of the total solve time. We can have options to only construct solver inputs for the next-back period ("JIT construction"), but it won't speed things up, and will often slow them down. In some cases, it's not meaningfully possible or a pure waste of CPU cycles to attempt it.

Right now, we have a disorganized and hardwired system for making constructed inputs. I'm just proposing a way to make it more organized for the typical use case where the user can specify the problem in advance. This doesn't rule out other use cases in which new periods are constructed wholly on the fly.

The issue I was raising with time-varying inputs vs time-invariant ones is that some solver math relies on an assumption of time invariance (e.g. CRRA). That solver math can be changed or generalized, but some solution properties will be lost. If we want to make a decision now that we never want to make any assumptions about future values / concepts, we should talk about that.

On Tue, Apr 9, 2024, 8:19 PM Christopher Llorracc Carroll < @.***> wrote:

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

  • their [image: t-1] solver requires some subset (probably nonstrict) of the period-[image: t] beginning-of-period states and functions (like, k and vFunc_{t}).
  • they define a method of simulation that can be invoked to apply the shocks and decision rules to get from the beginning of [image: t-1] to the end

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046236249, or unsubscribe < https://github.com/notifications/unsubscribe-auth/ADKRAFKDK7535IOETIM3MN3Y4SASXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGIZTMMRUHE>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046283240, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQZEC745GPURN4WEZ2Z2TY4SHDXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGI4DGMRUGA . You are receiving this because you are subscribed to this thread.Message ID: @.***>

mnwhite commented 2 weeks ago

Everything that would be made by functions constructors is "exogenous" in the sense that it's inputs to the solver: data structures needed to characterize and solve the one period problem. They're not all "exogenous" in the sense of "exogenous shocks". Any of the solver inputs could be designated as being constructed. You could make the lifecycle sequence of LivPrb be constructed by some function that takes in polynomial coefficients on age and returns survival probabilities as determined by a probit or logit formula. I have constructors that make functions for insurance contracts, based on parameters that characterize the contract.

On Wed, Apr 10, 2024 at 8:09 AM Sebastian Benthall @.***> wrote:

It might be instructive to look at Dolo for guidance about this, especially with respect to one thing, which is that Dolo separates the endogenous and exogenous aspects of a problem.

The exogenous structure can be more complex than IID shocks. AR(1) processes for example.

I know that income shocks are one thing that will go on the 'constructors' dictionary with your new plan. What else will? Are they all exogenous?

On Tue, Apr 9, 2024, 9:15 PM Matthew N. White @.***> wrote:

I do understand your views, and I want to build in those capabilities. They're useful both for teaching concepts and for the beginning stages of model development. But a lot of the use cases of HARK are "production stage", where you can specify the whole problem structure in advance. We want to be able to accommodate that and make it useful.

The "construction step" is usually (but not always) a very small portion of the total solve time. We can have options to only construct solver inputs for the next-back period ("JIT construction"), but it won't speed things up, and will often slow them down. In some cases, it's not meaningfully possible or a pure waste of CPU cycles to attempt it.

Right now, we have a disorganized and hardwired system for making constructed inputs. I'm just proposing a way to make it more organized for the typical use case where the user can specify the problem in advance. This doesn't rule out other use cases in which new periods are constructed wholly on the fly.

The issue I was raising with time-varying inputs vs time-invariant ones is that some solver math relies on an assumption of time invariance (e.g. CRRA). That solver math can be changed or generalized, but some solution properties will be lost. If we want to make a decision now that we never want to make any assumptions about future values / concepts, we should talk about that.

On Tue, Apr 9, 2024, 8:19 PM Christopher Llorracc Carroll < @.***> wrote:

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

  • their [image: t-1] solver requires some subset (probably nonstrict) of the period-[image: t] beginning-of-period states and functions (like, k and vFunc_{t}).
  • they define a method of simulation that can be invoked to apply the shocks and decision rules to get from the beginning of [image: t-1] to the end

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046236249,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/ADKRAFKDK7535IOETIM3MN3Y4SASXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGIZTMMRUHE>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046283240, or unsubscribe < https://github.com/notifications/unsubscribe-auth/AAAQZEC745GPURN4WEZ2Z2TY4SHDXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGI4DGMRUGA>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2047376303, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFMYSGKKS7IIRBRWDO3Y4UTV5AVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGM3TMMZQGM . You are receiving this because you authored the thread.Message ID: @.***>

sbenthall commented 2 weeks ago

That's interesting.

If you don't mind, there's still an ambiguity and I'm trying to understand -- maybe I can ask the question a different way.

'constructor' is a concept in object oriented programming. It's a special kind of function that creates an object.

'exogeneity' is a concept in social scientific modeling. In our context, a variable is exogenous if it does not depend on a control variable. This is when viewing the problem from the perspective of the agent's problem/solution. With a different frame of reference in a dynamic system, endogeneity can have some other meanings.

It sounds like some of what you intend for this new design is the ability to programmatically create (data) structures that are part of the environment the agents act in. But that these may not be probability distributions (i.e, of shocks) but rather are fixed. It sounds like you want something that handles both the construction of probability distributions, for shocks, and these other environmental factors.

There are many ways to implement exogenous processes, and only some of them are object oriented. There may be many ways to implement these other factors, only some of which are object oriented.

Is it very important for your new design for these to be object-oriented programming constructs?

Or is it more important that they are playing a consistent role in the modeling logic, a role that comes prior to solving an agents' problem?

On Wed, Apr 10, 2024 at 9:54 AM Matthew N. White @.***> wrote:

Everything that would be made by functions constructors is "exogenous" in the sense that it's inputs to the solver: data structures needed to characterize and solve the one period problem. They're not all "exogenous" in the sense of "exogenous shocks". Any of the solver inputs could be designated as being constructed. You could make the lifecycle sequence of LivPrb be constructed by some function that takes in polynomial coefficients on age and returns survival probabilities as determined by a probit or logit formula. I have constructors that make functions for insurance contracts, based on parameters that characterize the contract.

On Wed, Apr 10, 2024 at 8:09 AM Sebastian Benthall @.***> wrote:

It might be instructive to look at Dolo for guidance about this, especially with respect to one thing, which is that Dolo separates the endogenous and exogenous aspects of a problem.

The exogenous structure can be more complex than IID shocks. AR(1) processes for example.

I know that income shocks are one thing that will go on the 'constructors' dictionary with your new plan. What else will? Are they all exogenous?

On Tue, Apr 9, 2024, 9:15 PM Matthew N. White @.***> wrote:

I do understand your views, and I want to build in those capabilities. They're useful both for teaching concepts and for the beginning stages of model development. But a lot of the use cases of HARK are "production stage", where you can specify the whole problem structure in advance. We want to be able to accommodate that and make it useful.

The "construction step" is usually (but not always) a very small portion of the total solve time. We can have options to only construct solver inputs for the next-back period ("JIT construction"), but it won't speed things up, and will often slow them down. In some cases, it's not meaningfully possible or a pure waste of CPU cycles to attempt it.

Right now, we have a disorganized and hardwired system for making constructed inputs. I'm just proposing a way to make it more organized for the typical use case where the user can specify the problem in advance. This doesn't rule out other use cases in which new periods are constructed wholly on the fly.

The issue I was raising with time-varying inputs vs time-invariant ones is that some solver math relies on an assumption of time invariance (e.g. CRRA). That solver math can be changed or generalized, but some solution properties will be lost. If we want to make a decision now that we never want to make any assumptions about future values / concepts, we should talk about that.

On Tue, Apr 9, 2024, 8:19 PM Christopher Llorracc Carroll < @.***> wrote:

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

  • their [image: t-1] solver requires some subset (probably nonstrict) of the period-[image: t] beginning-of-period states and functions (like, k and vFunc_{t}).
  • they define a method of simulation that can be invoked to apply the shocks and decision rules to get from the beginning of [image: t-1] to the end

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll

— Reply to this email directly, view it on GitHub < https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046236249>,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/ADKRAFKDK7535IOETIM3MN3Y4SASXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGIZTMMRUHE>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046283240,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAAQZEC745GPURN4WEZ2Z2TY4SHDXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGI4DGMRUGA>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2047376303, or unsubscribe < https://github.com/notifications/unsubscribe-auth/ADKRAFMYSGKKS7IIRBRWDO3Y4UTV5AVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGM3TMMZQGM>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2047615779, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQZEF3GJWJW3P6N4GGVKDY4VABHAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGYYTKNZXHE . You are receiving this because you commented.Message ID: @.***>

mnwhite commented 2 weeks ago

It's not at all important that the constructed inputs be object-oriented. Some of them will be, and some won't be. The thing that's constructed could be as simple as a list of numbers, or even just one number. Consider the following constructor function:

make_LivPrb_as_polynomial_probit(LivPrb_probit_coeffs, age_0, age_count, age_incr): ....j_vec = np.arange(age_count)*age_incr + age_0 ....K = len(coeffs) ....LivPrb_base = np.zeros(age_count) ....for k in range(K): ........coeff = LivPrb_probit_coeffs[k] ........LivPrb_base += coeff * j_vec**k ....LivPrb = norm.cdf(LivPrb_base) ....return LivPrb.tolist()

That returns a list of floats, with each entry representing the survival probability at some age. For even more simple constructed output, consider the limiting share in the risky asset as wealth goes to infinity. It can be calculated from RiskyDstn and CRRA, but it's just one number. That case is a little bit special, because there's a right answer, but plausibly different ways to run the calculation to get to the same right answer.

On Thu, Apr 11, 2024 at 10:03 AM Sebastian Benthall < @.***> wrote:

That's interesting.

If you don't mind, there's still an ambiguity and I'm trying to understand -- maybe I can ask the question a different way.

'constructor' is a concept in object oriented programming. It's a special kind of function that creates an object.

'exogeneity' is a concept in social scientific modeling. In our context, a variable is exogenous if it does not depend on a control variable. This is when viewing the problem from the perspective of the agent's problem/solution. With a different frame of reference in a dynamic system, endogeneity can have some other meanings.

It sounds like some of what you intend for this new design is the ability to programmatically create (data) structures that are part of the environment the agents act in. But that these may not be probability distributions (i.e, of shocks) but rather are fixed. It sounds like you want something that handles both the construction of probability distributions, for shocks, and these other environmental factors.

There are many ways to implement exogenous processes, and only some of them are object oriented. There may be many ways to implement these other factors, only some of which are object oriented.

Is it very important for your new design for these to be object-oriented programming constructs?

Or is it more important that they are playing a consistent role in the modeling logic, a role that comes prior to solving an agents' problem?

On Wed, Apr 10, 2024 at 9:54 AM Matthew N. White @.***> wrote:

Everything that would be made by functions constructors is "exogenous" in the sense that it's inputs to the solver: data structures needed to characterize and solve the one period problem. They're not all "exogenous" in the sense of "exogenous shocks". Any of the solver inputs could be designated as being constructed. You could make the lifecycle sequence of LivPrb be constructed by some function that takes in polynomial coefficients on age and returns survival probabilities as determined by a probit or logit formula. I have constructors that make functions for insurance contracts, based on parameters that characterize the contract.

On Wed, Apr 10, 2024 at 8:09 AM Sebastian Benthall @.***> wrote:

It might be instructive to look at Dolo for guidance about this, especially with respect to one thing, which is that Dolo separates the endogenous and exogenous aspects of a problem.

The exogenous structure can be more complex than IID shocks. AR(1) processes for example.

I know that income shocks are one thing that will go on the 'constructors' dictionary with your new plan. What else will? Are they all exogenous?

On Tue, Apr 9, 2024, 9:15 PM Matthew N. White @.***> wrote:

I do understand your views, and I want to build in those capabilities. They're useful both for teaching concepts and for the beginning stages of model development. But a lot of the use cases of HARK are "production stage", where you can specify the whole problem structure in advance. We want to be able to accommodate that and make it useful.

The "construction step" is usually (but not always) a very small portion of the total solve time. We can have options to only construct solver inputs for the next-back period ("JIT construction"), but it won't speed things up, and will often slow them down. In some cases, it's not meaningfully possible or a pure waste of CPU cycles to attempt it.

Right now, we have a disorganized and hardwired system for making constructed inputs. I'm just proposing a way to make it more organized for the typical use case where the user can specify the problem in advance. This doesn't rule out other use cases in which new periods are constructed wholly on the fly.

The issue I was raising with time-varying inputs vs time-invariant ones is that some solver math relies on an assumption of time invariance (e.g. CRRA). That solver math can be changed or generalized, but some solution properties will be lost. If we want to make a decision now that we never want to make any assumptions about future values / concepts, we should talk about that.

On Tue, Apr 9, 2024, 8:19 PM Christopher Llorracc Carroll < @.***> wrote:

Replies below targeted mainly to persons in the set @.***) as I think MNW understands my views already.

On Tue, Apr 9, 2024 at 4:11 PM Matthew N. White @.***> wrote:

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.

Yes, like “flat permanent income growth if you’re older than 65, prior permanent income is current permanent income / 0.7 for a 65 year old” (to capture the fact that if you’re going forward in time “permanent income” falls to 0.7 of its prior value when you retire” etc.

This should be our fundamental “raw” way of creating models — with every single aspect of the model (not just income growth) up for grabs and available to change as you move earlier, with the only restriction being that the end-of-state variables from one state must mesh with the beginning-of-state variables from its successor.

Each AgentType subclass has "baked in" constructors, which are called from

(or simply part of) an update_X method.

So, these are “backwards” constructors? Like, if you’ve just solved for age 77, the constructor will make the stuff needed to solve the age 76 problem?

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.

Seems like the way to do all of this is to allow users to specify “default” values of everything (say, RRA), and unless their constructor explicitly constructs RRA the default applies.

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.

Or c) do what we do now in which you construct everything in advance of solving anything.

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;

This is too rigid - as best I can understand it requires the user to already know, when they solve the problem of the 120 year old, what the names of the inputs will be for the problem of the 18 year old who is deciding whether to go to college.

My (strong) preference is that, in principle, for any age [image: t] we allow the user to make up anything they want for the [image: t-1] problem, so long as:

  • their [image: t-1] solver requires some subset (probably nonstrict) of the period-[image: t] beginning-of-period states and functions (like, k and vFunc_{t}).
  • they define a method of simulation that can be invoked to apply the shocks and decision rules to get from the beginning of [image: t-1] to the end

I’m very much opposed to any restriction that requires the user to specify anything about the problem that is not necessary for the period in question (like, parameters for the 18 year old in the problem of the 120 year old).

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).

  1. I think “solve_one_period” should change to “solve_this_stage” to accommodate the fact that there may be several stages inside a period, and that the only difference between the last stage of a period and other stages is that what it needs as an input is a soln_beg_next_prd object which has in it whatever info is needed to construct the solution.

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).

This seems like a layer on top of where I think we should start. We should start with the user being asked to build the solution for every period backwards from a terminal period. Once we have that working, we can build “helper tools” like the ones you describe that winnow down the protean possibilities to ones users are likely to want to entertain.

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--

It’s time- (or age-) varying if they build it that way. EVERY SINGLE VARIABLE should be allowed to START being time-varying at any age. So, you might have constant variance of income shocks after age 65, but age-varying for earlier ages, AND THE USER NEED NOT DO ANYTHING to accomplish this until they get to the point of having to solve the problem of the 64 year old. The variable will be age-varying as a consequence of the fact that the user builds it backwards to be age-varying for solutions for people younger than 65.

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".

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408, or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAKCK75JTKGY64G253KM3PLY4RDOJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43ASLTON2WKOZSGIZTIMRSGA3DQMI>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

--

  • Chris Carroll

— Reply to this email directly, view it on GitHub < https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046236249>,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/ADKRAFKDK7535IOETIM3MN3Y4SASXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGIZTMMRUHE>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub < https://github.com/econ-ark/HARK/issues/1408#issuecomment-2046283240>,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/AAAQZEC745GPURN4WEZ2Z2TY4SHDXAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBWGI4DGMRUGA>

. You are receiving this because you are subscribed to this thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2047376303,

or unsubscribe <

https://github.com/notifications/unsubscribe-auth/ADKRAFMYSGKKS7IIRBRWDO3Y4UTV5AVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGM3TMMZQGM>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2047615779, or unsubscribe < https://github.com/notifications/unsubscribe-auth/AAAQZEF3GJWJW3P6N4GGVKDY4VABHAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGYYTKNZXHE>

. You are receiving this because you commented.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2049767145, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFKB2XCPWNQQZEGRHT3Y42JZVAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBZG43DOMJUGU . You are receiving this because you authored the thread.Message ID: @.***>

sbenthall commented 2 weeks ago

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 commented 2 weeks ago

I'm not sure what else it could be except a "mathematical object of some kind". Constructed inputs can also be representations of functions that are used directly or indirectly by the solver. Here's a somewhat weird example. Consider someone with the following utility function:

u(c,m; eta) = c^{1-rho} / (1-rho) + (m/eta)^{1-nu} / (1-nu)

Where c and m are choice variables and eta is a shock (that is observed by the agent when they're making their decision). Consider the static optimization problem in which this person has money x to spend, consumption c costs 1 per unit and medical care m costs pi per unit. In a dynamic model with this utility function, this static problem needs to be solved millions (or billions) of times, for reasons that aren't important here. In one of my projects, I have a constructed solver input that returns solutions to this static problem instantly. As in:

(c,m) = solve_static_allocation_problem(x, eta, pi)

The function solve_static_allocation_problem is constructed in advance (under a different name, obviously) based on rho and nu-- once you tell me the agent's preferences, I can characterize the solution to the static optimization problem for any inputs, accurate to an arbitrary degree. That numeric function is a "mathematical object", but it's not a parameterization or definition of an exogenous process.

And yes, we want to be able to describe these things in the modeling language outside of HARK.

On Thu, Apr 11, 2024 at 10:40 AM Sebastian Benthall < @.***> wrote:

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.

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2049851928, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFMFVQNUBZGYTCHXP7TY42OHJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBZHA2TCOJSHA . You are receiving this because you authored the thread.Message ID: @.***>

sbenthall commented 2 weeks ago

Ah, thank you for that example. I see now that this is something quite different. It also more directly relates to algorithmic performance because of how the object is used in the solver.

On Thu, Apr 11, 2024 at 11:03 AM Matthew N. White @.***> wrote:

I'm not sure what else it could be except a "mathematical object of some kind". Constructed inputs can also be representations of functions that are used directly or indirectly by the solver. Here's a somewhat weird example. Consider someone with the following utility function:

u(c,m; eta) = c^{1-rho} / (1-rho) + (m/eta)^{1-nu} / (1-nu)

Where c and m are choice variables and eta is a shock (that is observed by the agent when they're making their decision). Consider the static optimization problem in which this person has money x to spend, consumption c costs 1 per unit and medical care m costs pi per unit. In a dynamic model with this utility function, this static problem needs to be solved millions (or billions) of times, for reasons that aren't important here. In one of my projects, I have a constructed solver input that returns solutions to this static problem instantly. As in:

(c,m) = solve_static_allocation_problem(x, eta, pi)

The function solve_static_allocation_problem is constructed in advance (under a different name, obviously) based on rho and nu-- once you tell me the agent's preferences, I can characterize the solution to the static optimization problem for any inputs, accurate to an arbitrary degree. That numeric function is a "mathematical object", but it's not a parameterization or definition of an exogenous process.

And yes, we want to be able to describe these things in the modeling language outside of HARK.

On Thu, Apr 11, 2024 at 10:40 AM Sebastian Benthall < @.***> wrote:

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.

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2049851928, or unsubscribe < https://github.com/notifications/unsubscribe-auth/ADKRAFMFVQNUBZGYTCHXP7TY42OHJAVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBZHA2TCOJSHA>

. You are receiving this because you authored the thread.Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/1408#issuecomment-2049907452, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAQZEGM7XWGMYIQEF4SKLLY42Q57AVCNFSM6AAAAABF7GUBCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBZHEYDONBVGI . You are receiving this because you commented.Message ID: @.***>

Mv77 commented 2 weeks ago

@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 commented 2 weeks ago

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.