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
Add constructor architecture #1410
Conversation
Completely untested, will adapt construct_income_process on next commit as test example.
Holy crap, construct() worked on the first try! Only IncShkDstn has been tested, and none of the "error catching" features have been looked at. But hey, it works!
Should pass these tests now, but formatting is probably still wrong.
As noted in the issue where I proposed this, there are two significant design points to work out:
|
Can now collapse a DiscreteDistribution to a single dimension; PermShkDstn and TranShkDstn are made this way now. Moved bespoke constructor dictionary lines out of init. Future plan: each AgentType subclass should have default constructors dictionary in its parameter dictionary.
One test was failing due to lack of seed.
Can someone tell me how to run the pre-commit routine locally? I can run black, but I don't know how to make lint work. |
|
more info in the developer guide here: https://docs.econ-ark.org/guides/contributing.html#pre-commit-hooks |
|
you probably don't want to run all files, only your changes |
All tests pass except for calc_jacobian, which needs a separate fix.
Missed this one. Jacobian test will still be failing.
All test should pass now. Added new optional input for AgentType.solve() that allows the user to decline to run the presolve routine. For most of our classes, presolve might update the terminal solution and/or run update/constructor methods, but this is fatal to what calc_jacobian wants to do.
I think something about pytest just changed while I was working on this branch, because three tests needed to have a [0] added to them (to reference the only element in the array) in order to be correct. Nothing changed, but the result of these tests did. I'm confused.
One assertEqual test was off by 1e-16 on Python 3.9, but not 3.10. No idea why.
Also added retro support for using [None] rather than None for aXtraExtra.
A bunch of tests needed syntax changes, but everything passes now. The other things that I'm moving to constructors will be much easier from here on out, and should be completed on Monday. For this PR, I'm going to set the goal at getting everything in constructor functions that should be (plus some alternates that can be swapped in). After this is merged, in a separate branch/PR, I'm going to tackle undoing/simplifying the custom init methods for each AgentType subclass, and get them all just using AgentType.init ... That also means that the default dictionary should be a class attribute. |
We should be doing super().init everywhere |
Even where |
pLvlGrid and pLvlNextFunc are now built with the constructors framework. This means that the *only* difference between the AgentType subclasses in ConsGenIncProcessModel is their default dictionary. GenInc tests pass locally, committing to see what else I broke.
If tests pass on this commit, the next commit will change exactly one line of code, and revise tests to fit. As is, the "construct pLvlGrid by simulation" method draws on AgentCount, which is fine as long as AgentCount is actually large. The number of eventual simulated agents shouldn't be used in the solution of the problem, even extremely indirectly. So I'm just going to have it use a large number of agents no matter what. This is also why the hardcoded seeds don't matter in this function/method. |
Never make last second changes before committing, kids. You will never get it right and only embarrass yourself through the commit history.
Minor errors in code and dictionaries due to changes in prior commit.
Still behind schedule, as there were more terminal solvers than I remembered. Still to do the following small tasks:
|
for j in range(len(args_needed)): | ||
this_arg = args_needed[j] | ||
if hasattr(self, this_arg): | ||
temp_dict[this_arg] = getattr(self, this_arg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to do this without hasattr/getattr/setattr, using manipulations of a data structure rather than an object's attributes?
if not any_missing: | ||
temp = constructor(**temp_dict) | ||
setattr(self, key, temp) | ||
self.parameters[key] = temp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these parameters, or model constructs, or both?
what if there was a self.constructs dictionary that was separate from parameters?
including their names, the function that constructs them, the names of | ||
those functions inputs, and whether those inputs are present. | ||
|
||
Parameters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python conventions for printed representations of things is to create a __repr__
method, or to display docstrings.
HARK/core.py
Outdated
|
||
out = "" | ||
for i in range(N_keys): | ||
key = keys[i] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could try for key in keys:
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do use i
below, because I track which work items have been completed in a boolean vector.
HARK/core.py
Outdated
out = "" | ||
for i in range(N_keys): | ||
key = keys[i] | ||
has_val = hasattr(self, key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be looking up in parameters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, construct()
does this but I forgot to put that line in here. Will fix.
Returns | ||
------- | ||
None. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method create functionality that operates on arbitrary functions, since any arbitrary function is a constructor.
Is a constructor a type?
Or a constructor-list a type/data structure with extra functionality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to leave it general/simple that any function can be a constructor, to keep barriers to entry low.
Also had to change dictionary import in tests. Notebooks might break.
Too much copy-pasta, something ended up zero that should have been one.
One is an extremely basic binary state specification, the other is a "ratchet" specification in which transitions only go one direction, and only one step at a time.
Same issue as in earlier commit, but in an example notebook. Just imported and used correct dictionary.
Construct method is now more robust, with an option to force through errors and record them in a separate dictionary. Also made other small changes/fixes.
This commit adds one missing (trivial) constructor and adds a few "counterexample" constructors that *could* be used for other parameter specifications. There are many, many more that could be written.
Once the current tests finish, I'll be pushing a commit that only has documentation / changelog updates. I've added a small number of "alternate" constructors that can be substituted in, but there's an arbitrarily large number of other ones that could be written as well. Following yesterday's discussion with Seb and Alan, I added an additional features to construct: the option to This PR is ready for others to look at. No external behavior has changed. The only changes in this PR are how complex objects get created. |
Also added one missing line of documentation.
if param_name in self.parameters: | ||
del self.parameters[param_name] | ||
if hasattr(self, param_name): | ||
delattr(self, param_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any way to avoid this redundant storage of the constructs?
Maybe constructs should not be stored in parameters, but in self.constructs (or something)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's for deleting things. Right now, I've written things to be intentionally redundant, because we're "between two worlds". The medium run goal is to remove the top level namespace at all, only putting them in parameters
.
I don't want a separate constructs
dictionary, because there's no meaningful distinction between parameters
and constructs
. It might be that parameters
was the wrong name for that dictionary.
This all looks very good to me. The one thing which I think could be improved is the confusing way in which constructed objects are stored, both (!) as an entry in What about a |
Ok, I understand that removing object attribute storage entirely is a goal, but something to be implemented in a different PR. I'll leave further notes on #889 |
There is a conflicting file with CHANGELOG Otherwise, I believe this is ready to merge? |
Fixed CHANGELOG |
Beginning work on the
construct
method ofAgentType
. As of this moment, a basic prototype works for income distributions. Still a lot more to develop and test.