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

Parameter typing (more support for time varying elements) #664

Closed
sbenthall opened this issue Apr 28, 2020 · 19 comments
Closed

Parameter typing (more support for time varying elements) #664

sbenthall opened this issue Apr 28, 2020 · 19 comments
Assignees
Labels
Design Tag: 1.0 About the v1 release of HARK.
Milestone

Comments

@sbenthall
Copy link
Contributor

In 2014, Python 3.5 got stronger support for static types, in the form of type hints and other functionality`. See PEP 484 for more information.

Previously, Python hadn't had the ability to do static typing, and so there were widely varying conventions around doing it. But since 3.5, the expectations of the Python community have converged and now Python code is encouraged to be in a more statically typed style.

[I'll say: I personally haven't had the opportunity to work in this way yet! But I think it's important to keep up with the times.]

Static typing makes programming more rigorous, much more like mathematics. I recommend we adopt it for HARK. Dolo has done this.

A first area where it would be impactful to introduce static typing would be in the model parameters setting. This is a very user-facing aspect of HARK, and also one connected to HARK's compatibility with Dolo. Some related issues: #640 #659 #660

The biggest type ambiguity in the model parameters right now is the optional use of a list for a time varying parameter. This design choice has implications throughout HARK: often, there will need to be two nearly duplicate code blocks, one for handling the time-varying case of a variable, one for handling the static case. If there is not support for both kinds of input, then a non-varying parameter needs to be entered as a list of identical values--which is suboptimal. For example:

https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L2912-L2913

The time-varying option could be better supported. I think Dolo supports this through the definition of Processes:

https://github.com/EconForge/dolo/blob/7a12eb7117f80d95bd36955328329d6573b0c23c/dolo/numeric/processes.py

So, what we've called a Distribution would, in Dolo, be a UnivariateIIDProcess. But a parameter might also be defined as an AR1 process, which would capture not only that it is time varying, but how it varies in time.

What I'm proposing is that we develop the Distribution/Process classes in HARK so that there are time-varying subclasses of them (or something like that). Then, when the Model needs to simulate or solve them, it can call Process class code to get at the time-appropriate value. This should reduce the amount of code in the solvers and simulators, making it more readable.

@project-bot project-bot bot added this to Needs Triage in Issues & PRs Apr 28, 2020
@mnwhite
Copy link
Contributor

mnwhite commented Apr 28, 2020 via email

@sbenthall
Copy link
Contributor Author

What is code like this doing?

https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsPortfolioModel.py#L228-L234

I thought it was testing to see if an optionally time-varying parameter was a list.

@mnwhite
Copy link
Contributor

mnwhite commented Apr 28, 2020 via email

@mnwhite
Copy link
Contributor

mnwhite commented Apr 28, 2020 via email

@llorracc
Copy link
Collaborator

I haven't been following this discussion closely but it sounds as though @sbenthall is proposing something that would make it harder for us to allow parameters to be time varying. I would strongly resist that; if we have to choose, we would be better off allowing ALL parameters to be time varying than we would be to make it more difficult to have time-varying parameters. Though we do not have much of the full-fledged business-cycle-frequency HA modeling stuff working yet, when we do we will need the capacity to experiment with anything. For example, some business cycle models are driven by shocks to "risk aversion". (I don't like those models, but we need to allow for them to be represented).

@sbenthall
Copy link
Contributor Author

I'm certainly not trying to make it harder for parameters to be time-varying.

I'm looking for a way to better support time-varying parameters through more explicit typing.

@mnwhite
Copy link
Contributor

mnwhite commented Apr 28, 2020 via email

@sbenthall
Copy link
Contributor Author

There is a similar construction, where a list is used optionally to signal that a value is time varying, here:

HARK/HARK/core.py

Lines 807 to 810 in 588dea3

if hasattr(agent.solveOnePeriod, "__getitem__"):
solveOnePeriod = agent.solveOnePeriod[t]
else:
solveOnePeriod = agent.solveOnePeriod

@mnwhite
Copy link
Contributor

mnwhite commented Apr 28, 2020 via email

@MridulS
Copy link
Member

MridulS commented Apr 28, 2020

not to be a party pooper, type checking is definitely fabulous, it's an easy way to find bugs and confirming code correctness but we would need to make a lot of changes underneath in the HARK machinery. It won't be as straight forward as just adding int/float/List in front of a variable.

Interop with Dolo will also require to exactly match the objects of HARK and Dolo (if native support between those two object is envisioned)

@sbenthall
Copy link
Contributor Author

sbenthall commented Apr 28, 2020

Hmm, well, that change was apparently an opinionated move by me:
3b2ace1

Whether or not this was checked for in time_vary (where would previously cause its list type to be asserted) or whether it is tested directly as a list, it is still an example of a part of model configuration that has different semantics depending on its representation.

As there appear to be no use cases for this functionality, it's unclear how you would configure a model to use this.

But I think it's something like this:

Old way:

model.solveOnePeriod = [method1, method2, method3]
model.addToTimeVary('solveOnePeriod')

The current way:

model.solveOnePeriod = [method1, method2, method3]

What I'm pushing for is something more like:

model.PermShockDstn = AR1(phi, sigma)
model.solveOnePeriod = TimeVaryingSolver([method1, method2])

Then later, in the code:

Solver.value(1) ## returns method2

@MridulS yep, I'm proposing this as a more thorough change.

@sbenthall
Copy link
Contributor Author

This proposed commit takes a baby step closer to what I'm getting at.
ac6b330

@albop
Copy link
Collaborator

albop commented Apr 28, 2020

If I may, here is some feedback about my progressive transition to python typing:

  • I've been progressing slowly and have not reached the point where I can catch any error by running mypy on the code (a static type checker). It's not really an objective either.
  • I find it most useful for the files containing many small functions, especially when their name does not clearly state what they do. I helps me keep my thinking organized. On the other hand for bigger objects and functions, they are mostly a consise way to help document what the code is doing. For instance if I write: solve(m: Model)->Decisionrule it's pretty clear it's not an inplace operation.
  • at the same time that I moved to static typing I tried moving to a way to think about objects as basic data structures that can be passed around as arguments instead that having big objects accumulate many methods. For the latter a bit of the advantages of static typing is lost. (it's clear what model.solve() does).
  • the ultimate goal was to be able to compile code operating on simple structures, not just ensure the sanity of the codebase. But the technology is still not there.
  • instead I have started to introduce a multiple dispatch approach way to code functions and I'm super happy with it. For instance, you could write:
@multimethod
def solve(m: Model):
    # do something

@multimethod
def solve(m: SecondOrderEquation):
    # do something else

and the correct version of the method would be called depending on the kind of object you are supplying. This depends on an additional libary to implement this behaviour and on the type annotation. Also, I really love this approach.

  • there is one limitation I found annoying with the type system: I haven't found a simple way to express parametric types. For instance I"d like to specify a model is of type Equilibrium[RationalAgent] so that the right solution method would be called for this model and another one for Equlibrium[NotSoRationalAgent]

@llorracc
Copy link
Collaborator

@sbenthall, I assume that

model.solveOnePeriod = TimeVaryingSolver([method1, method1])

in your post above should be

model.solveOnePeriod = TimeVaryingSolver([method1, method2])

@sbenthall
Copy link
Contributor Author

@llorracc yes that's right. thank you; i've corrected the comment.

@sbenthall
Copy link
Contributor Author

This has come up again in connection to SHARKFin:

sbenthall/SHARKFin#42

Looking again at the Python typing module, it looks relatively straightforward to create a new data type that is a wrapper around another data type, like a list:

https://docs.python.org/3/library/typing.html

@sbenthall sbenthall self-assigned this Dec 30, 2021
@sbenthall
Copy link
Contributor Author

So, in particular, it would be possible to define two data types, CyclicVarying and LifespanVarying, both of which wrap the basic Python list.

@llorracc
Copy link
Collaborator

llorracc commented Jan 3, 2022

These are not the only two categories, and I'm not sure exactly what would be accomplished by hard wiring them.

In particular, quite a popular thing to do is to have a "stochastic" life cycle: There is a "early career" phase, a "mid career" phase, a "late career" phase, a "partial retirement" phase, etc, and the transition between these does not happen deterministically at any particular age. In fact, there is no reason necessarily to keep track of the agent's age at all: What the agent would need to know in order to make all their choices is their life stage, their assets, and some other state variables, but not necessarily their chronological age. (Of course, if one wanted to capture details like "Social Security is available starting at chronological age 62" you need it; but not all models need to do that).

What we do need is the ability to nest problems within problems. So, we want to be able to nest a cyclic problem (four seasons of the year) inside either a "stochastic life cycle" or a "deterministic life cycle" model.

@sbenthall
Copy link
Contributor Author

I wonder if this nesting can also represented with a type system.
I suspect it can be.

@mnwhite mnwhite added Tag: 1.0 About the v1 release of HARK. and removed Expertise: Basic Python and Open Source labels Jul 3, 2024
@econ-ark econ-ark locked and limited conversation to collaborators Jul 3, 2024
@mnwhite mnwhite converted this issue into discussion #1468 Jul 3, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
Design Tag: 1.0 About the v1 release of HARK.
Projects
Issues & PRs
  
Needs Triage
Development

No branches or pull requests

5 participants