Closed sbenthall closed 4 years ago
Yes, I coded it this way to allow for time-varying solveOnePeriod functions. This was intentional.
Not all of our solution functions create a solver class. Some of them are just coded as a single function, which is a lot easier to read and write.
I don't particularly like our "object oriented solvers". The idea was to not rewrite code across solvers, but they made the code an absolute maze to navigate. Moreover, we never actually had a meeting to plan out what the scope of models that HARK should be able to handle (but not for lack of trying), so the "forward looking" structure of the solvers wasn't particularly forward looking.
As for redundant working being done by instantiating and then destroying solver objects: yes, 100%, and there's an old issue that I wrote years ago about this. I think you can find it if you search for "persistent solvers". The idea is to have a better partition between objects that can be re-used across periods of the model and those that need to be created every time.
On Fri, Apr 24, 2020, 7:17 PM Sebastian Benthall notifications@github.com wrote:
There is a strange coding pattern in the Model classes. I am tempted to try to turn it into something more standard, but want to check if there is a hidden purpose to it.
Typically, a ConsumerType will have an attribute, solveOnePeriod, that is set upon initialization to some stand-alone function in the module. E.g.:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L1658
This stand alone method then typically initalizes a Solver object, runs solve() on it, then returns the result (a solution). E.g.:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L590-L625
These methods are for the most part, except for the choice of Solver class, identical across the library.
This is not ideal because it makes it difficult to read the code: to follow the solution logic, you have to jump out of one class into the global scope, then into another.
It seems to me that either:
- (A) solveOnePeriod could be a method, which would just look like exactly the function that is currently assigned to it, or
- (B) solveOnePeriod could be a standardized method, which points to a particular Solver class, which is then parameterized in the model code.
The only motivation I can think of for the current implementation is that it's suggested by this code block that the idea was to make it possible to have time-varying solution functions:
However, this flexibility does not seem to be used anywhere in the HARK repository (I checked), nor does it have any tests associated with it. If this was still a desirable feature, it would be possible to build 'solveOnePeriod', as a standardized method, to optionally take a list of class names that could be indexed by time periods.
[Separately, and maybe to be addressed in a different issue: I'm skeptical about the performance of this method of firing up a new Solver object, complete with a lot of initialization, at every step of the backwards inductions. It seems to me there may be a lot of redundant code calls. I'll revisit this once I've got a profiler set up, but I wanted to mention it now to start circulating the thought.]
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPJRRIYWVBVYTJMUU3ROIMWZANCNFSM4MQPH6XQ .
Issue #97 is the "persistent solver" issue.
Thanks for acknowledging the maze--I'm glad it isn't just me.
It sounds like you are open to my doing some work on this. I'd be glad to.
Is the time-varying solver functionality something that must be preserved?
Must is a strong word. No, but it should exist.
On Fri, Apr 24, 2020, 9:39 PM Sebastian Benthall notifications@github.com wrote:
Issue #97 https://github.com/econ-ark/HARK/issues/97 is the "persistent solver" issue.
Thanks for acknowledging the maze--I'm glad it isn't just me.
It sounds like you are open to my doing some work on this. I'd be glad to.
Is the time-varying solver functionality something that must be preserved?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-619299790, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPWL5OELIJSEMNQNCTROI5MDANCNFSM4MQPH6XQ .
Honestly, I think the "some work" on this is to largely start over, in a similar vein to what you described in the other thread about integrating HARK with dolo.
On Fri, Apr 24, 2020, 10:14 PM Matthew White mnwhite@gmail.com wrote:
Must is a strong word. No, but it should exist.
On Fri, Apr 24, 2020, 9:39 PM Sebastian Benthall notifications@github.com wrote:
Issue #97 https://github.com/econ-ark/HARK/issues/97 is the "persistent solver" issue.
Thanks for acknowledging the maze--I'm glad it isn't just me.
It sounds like you are open to my doing some work on this. I'd be glad to.
Is the time-varying solver functionality something that must be preserved?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-619299790, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPWL5OELIJSEMNQNCTROI5MDANCNFSM4MQPH6XQ .
I agree with Matt. This is probably something for HARK 2.0.
I'm confused by your reaction here. The scope of this issue (#658) is quite narrow.
It also seems like in with one of the desiderata for HARK 1.0, which is correcting the class/method architecture to remove the use of standalone functions where they seem to have been left out in the open originally and never tucked in.
https://github.com/econ-ark/HARK/wiki/Towards-2.0#hark-10-refactoringfeature-targets
I could see how fixing #97 is a bigger lift. Maybe you meant that that should be a 2.0 target.
I don't think this issue should be solved. One use case for doing it this way is for teaching purposes when showing multiple methods for solving the same problem.
In my graduate numeric methods class, I walk the students through three different methods for solving a basic consumption-saving model (explicit maximization, first-order-condition solving, and endogenous grid method; I assign them to further vectorize the latter as an assignment). I write one (very simple) AgentType subclass and three solveOnePeriod functions. To demonstrate each one, I can create several instances of the type and substitute a different function into solveOnePeriod for each one.
That would take three different classes under your revision, if I understand you correctly.
On Mon, Apr 27, 2020 at 3:39 PM Sebastian Benthall notifications@github.com wrote:
I'm confused by your reaction here. The scope of this issue (#658 https://github.com/econ-ark/HARK/issues/658) is quite narrow.
It also seems like in with one of the desiderata for HARK 1.0, which is correcting the class/method architecture to remove the use of standalone functions where they seem to have been left out in the open originally and never tucked in.
https://github.com/econ-ark/HARK/wiki/Towards-2.0#hark-10-refactoringfeature-targets
I could see how fixing #97 https://github.com/econ-ark/HARK/issues/97 is a bigger lift. Maybe you meant that that should be a 2.0 target.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620192235, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFJF2DKXX2IJCVJJFHDROXNQNANCNFSM4MQPH6XQ .
Generically we should work on a syntax to direct an agent to solve their problem with a particular method: Value Function Iteration, FOC's, endogenous grid, polynomial methods, etc. At present, for most of our models, we have only one solution method that is hard-coded in. If we had a generic syntax, we could still make the "default" method be the one that is now hard-coded in, and if the user asks for any other solution method that is not available, they are told that their requested method is not yet available.
I like @llorracc 's point here a lot.
@mnwhite I think I see what you're saying about needing to swap in different solvers.
Are the other solveOnePeriod methods you use also in HARK?
The way Dolo works, the solver (say, Value Iteration), takes the model as an argument, and returns the solution (a decision rule). I like that design. https://github.com/EconForge/dolo/blob/master/dolo/algos/value_iteration.py#L17
What if as a workflow:
The solver could know that it only accepts certain classes of AgentType as inputs if it is not generic.
I don't like the idea of the solver "knowing that it only accepts certain classes of AgentType as inputs." Solvers should be specified as generically as possible, without reference to the AgentType that they are solving. It is the AgentType that should know which kinds of solvers work for it. This is because there is a potentially unlimited number of AgentTypes and it would be messy for each solver to have to be updated to keep track of the status of every kind of AgentType.
If you want to make solveByBlah a function rather than have a solve method, go ahead.
On Mon, Apr 27, 2020 at 4:26 PM Sebastian Benthall notifications@github.com wrote:
I like @llorracc https://github.com/llorracc 's point here a lot.
@mnwhite https://github.com/mnwhite I think I see what you're saying about needing to swap in different solvers.
Are the other solveOnePeriod methods you use also in HARK?
The way Dolo works, the solver (say, Value Iteration), takes the model as an argument, and returns the solution (a decision rule). I like that design.
https://github.com/EconForge/dolo/blob/master/dolo/algos/value_iteration.py#L17
What if as a workflow:
- you define an AgentType
- You instantiate a Solver (or 3)
- You pass the AgentType to the Solver?
The solver could know that it only accepts certain classes of AgentType as inputs if it is not generic.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620214898, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPIXZHQSPAI4VAWX43ROXS7DANCNFSM4MQPH6XQ .
Ok. I will put some thought into this and see if there's anything that can be done in the short term to make things a little cleaner.
I don't think there's any "small work" here, at least any that's meaningful. Changing the syntax from ThisType.solve() to solveByTheOnlyMethodWeProgrammed(ThisType) seems like a big structural change that doesn't actually do anything. We can do it, but it feels like something we should do when starting over from scratch. We can call that HARK 1.0 or HARK 2.0, but I don't think this is a place for incremental change.
On Mon, Apr 27, 2020 at 4:34 PM Sebastian Benthall notifications@github.com wrote:
Ok. I will put some thought into this and see if there's anything that can be done in the short term to make things a little cleaner.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620219372, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFLSAS7VKQWGZAFAL63ROXT7BANCNFSM4MQPH6XQ .
I thought you just said, "go ahead". Maybe you were being sarcastic.
I wonder if you've ever given much thought to what the term "refactor" means. https://refactoring.com/
My understanding is that a major deliverable of the grant I am working on is to refactor HARK towards a 1.0 version.
Refactoring normally means making structural changes to code that does not change the functionality in order to make it easier to read and extend.
My sense is that there are two good standards available for what HARK should be refactored towards:
There's also the usual considerations: performance, and making things a bit more concise when there are unnecessary lines of code.
One reason why it's a good idea to make these changes is that it makes it more likely that somebody else who comes along and reads the code isn't bewildered by it. You seem to be a bit ambivalent about the readability of the code here--sometimes you joke about it being hard to read, or call it "a maze". So I gather you do think that there are changes to make.
I'm not sure when the "rewrite" is planned, but I'm not sure it's wise to do that, to be honest. You already have many test cases built out and, I gather, a kind of commitment to preserving the old research code.
You might have the time or wherewithal to do the refactoring; I wouldn't expect you to as a professor of economics. But as I've been a software engineer, among other things, at various times in my career, I see this sort of thing as just par for the course. I think things would move more quickly if you let me do my job.
Sorry; I meant might not have the time or wherewithal to do the refactoring.
So, hmmm: there is already a function that takes an agent and solves it? core.solveAgent
https://github.com/econ-ark/HARK/blob/master/HARK/core.py#L681
But, once again, this is a little misleading. It's defined at the global module level, but really it's intended to be called like a method within AgentType:
https://github.com/econ-ark/HARK/blob/master/HARK/core.py#L314
I guess this has been like this for years and you are used to it.
But, and I'm sorry to have to say this, to me it reads like a mistake. It has strong "the person who wrote this didn't know what they were doing" energy. I gather that you understand that this is a bad design structure, because you've said as much in an analogous case here:
https://github.com/econ-ark/HARK/issues/538#issuecomment-589850515
So all I'm trying to do here is clean up these structural issues so that when HARK does a 1.0 release it doesn't have obviously bad structure to it. I think you think this is so heinously difficult to correct it's not worth doing it. It's on that point I disagree. I think it's wiser to do the refactoring with the existing functionality, robustly tested for as long as possible. It will get you to where you want to go faster. Trust me.
I think partly Matt's judgment of how difficult something would be to do is colored by the fact that until recently he was pretty much the only do-er. Now that you and Mridul are working with us, our capacity for do-ing is much greater than it was -- and Matt's role can be much smaller than his central nervous system feels it will be when he sees a proposal to do something. Basically, just heading off big mistakes.
whoops hit the wrong button. i guess that was my subconscious reacting with a 'case closed'
Wikipedia on Refactoring is actually quite good : https://en.wikipedia.org/wiki/Code_refactoring
I wasn't being sarcastic. I really did mean this is a change you can make. My subsequent comment was a reaction to your saying "in the short term", because this seems like if it's done and it has teeth, it is a big, big structural change.
To my understanding, you proposed having AgentType instances be passed as an argument to a solver function, where we might have multiple different solver functions, rather than invoking the solve() method. Ex ante, if we were just beginning (or starting over), this would be a fine choice, but it's not clear what's being gained in the short run by doing this. We don't/won't have multiple solver functions to use with it... and it's not clear to me that we ever will have a generic solveByVFI or solveByEGM function. So it feels like just a change in syntax that has a cost (adjusting downstream code) without an apparent upside.
Yes, the child functions of solve are not great, and are downright embarrassing in some cases. All of that can and should be simplified and moved into a single method of AgentType (or maybe two methods). This won't break anything because nothing interacts with the internal stuff in AgentType.
On Mon, Apr 27, 2020 at 8:25 PM Sebastian Benthall notifications@github.com wrote:
whoops hit the wrong button. i guess that was my subconscious reacting with a 'case closed'
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620305117, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPPE3Y2PWNSDCSLSR3ROYO6ZANCNFSM4MQPH6XQ .
Whoops, left off a sentence to explain the "change in syntax" bit. Without one of those "generic solvers", ultimately the new solve function will just end up calling our existing model-by-model code.
On Mon, Apr 27, 2020 at 8:53 PM Matthew White mnwhite@gmail.com wrote:
I wasn't being sarcastic. I really did mean this is a change you can make. My subsequent comment was a reaction to your saying "in the short term", because this seems like if it's done and it has teeth, it is a big, big structural change.
To my understanding, you proposed having AgentType instances be passed as an argument to a solver function, where we might have multiple different solver functions, rather than invoking the solve() method. Ex ante, if we were just beginning (or starting over), this would be a fine choice, but it's not clear what's being gained in the short run by doing this. We don't/won't have multiple solver functions to use with it... and it's not clear to me that we ever will have a generic solveByVFI or solveByEGM function. So it feels like just a change in syntax that has a cost (adjusting downstream code) without an apparent upside.
Yes, the child functions of solve are not great, and are downright embarrassing in some cases. All of that can and should be simplified and moved into a single method of AgentType (or maybe two methods). This won't break anything because nothing interacts with the internal stuff in AgentType.
On Mon, Apr 27, 2020 at 8:25 PM Sebastian Benthall < notifications@github.com> wrote:
whoops hit the wrong button. i guess that was my subconscious reacting with a 'case closed'
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620305117, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFPPE3Y2PWNSDCSLSR3ROYO6ZANCNFSM4MQPH6XQ .
"A journey of a thousand miles begins with a single step"
I think @sbenthall has shown good judgment about ways to move forward without breaking things more than is absolutely necessary.
Either the project will fail, or it will succeed. It definitely will not succeed if we are not ambitious. My sense is that @sbenthall has done a good job of identifying changes we need to make for it to be a good platform for the long haul, and charting a judicious path to getting there.
I want to flag this non-OO solver function as an example of a different coding pattern around 1-period solvers.
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsPortfolioModel.py#L228-L234
I see now it's a bit more complicated than I thought, because there's more than one pattern for 'one period solvers' around.
But I think that the OO-solver functions can be consolidated into a single function. Most of these have a redundant structure. E.g:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L623-L625
is similar, except for choice of Solver object, to this:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L623-L625
which has some additional logic for the interpolater choice, that could be smoothed over, but which is otherwise almost identical to this:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsPrefShockModel.py#L486-L491
And so on.
I would like ConsIndShockSolverSetup, ConsIndShockSolverBasic, and ConsIndShockSolver to be collapsed into one class. I lost that fight 4.5 years ago.
On Tue, Apr 28, 2020 at 9:17 AM Sebastian Benthall notifications@github.com wrote:
I want to flag this non-OO solver function as an example of a different coding pattern around 1-period solvers.
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsPortfolioModel.py#L228-L234
I see now it's a bit more complicated than I thought, because there's more than one pattern for 'one period solvers' around.
But I think that the OO-solver functions can be consolidated into a single function. Most of these have a redundant structure. E.g:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L623-L625
is similar, except for choice of Solver object, to this:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L623-L625
which has some additional logic for the interpolater choice, that could be smoothed over, but which is otherwise almost identical to this:
https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsPrefShockModel.py#L486-L491
And so on.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620600607, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFIHQGGC4SKIPYR23RTRO3JOBANCNFSM4MQPH6XQ .
@mnwhite Thanks for the clarification! I think we see eye to eye on a lot of design issues and I understand that part of what I've been seeing in the code is the result of uneasy truces.
Just FYI: even if a method is defined in a class, you can still override it with your own function in Python. I believe Python is mechanically indifferent to whether or not the value of a field is defined in the class/method syntax, or the value-assignment syntax.
So, the following will print "11"
from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType
example = IndShockConsumerType()
example.cycles = 0
example.solve()
example.update = lambda x : x + 1
print(example.update(10))
So if, as suggested originally in this issue, the solveOnePeriod functions were written in the class/method syntax, you could still do what you've been doing while teaching.
It would be a bit clearer from the code, in that case, that you were doing something you "shouldn't" be doing. Which may or may not bother you.
I thought that very specifically didn't work-- that once a particular name had been used as a method, it would expect the first argument to be self even if a function were assigned to that name later. I remember having difficulty with this.
On Tue, Apr 28, 2020 at 9:33 AM Sebastian Benthall notifications@github.com wrote:
@mnwhite https://github.com/mnwhite Thanks for the clarification! I think we see eye to eye on a lot of design issues and I understand that part of what I've been seeing in the code is the result of uneasy truces.
Just FYI: even if a method is defined in a class, you can still override it with your own function in Python. I believe Python is mechanically indifferent to whether or not the value of a field is defined in the class/method syntax, or the value-assignment syntax.
So, the following will print "11"
`` from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType
example = IndShockConsumerType() example.cycles = 0 example.solve()
example.update = lambda x : x + 1
print(example.update(10)) ``
So if, as suggested originally in this issue, the solveOnePeriod functions were written in the class/method syntax, you could still do what you've been doing while teaching.
It would be a bit clearer from the code, in that case, that you were doing something you "shouldn't" be doing. Which may or may not bother you.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620610760, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFOBPE7T7GECY7YXA4TRO3LKTANCNFSM4MQPH6XQ .
Hmm. Curious. I've tested the code in my last comment.
I think I have a good enough understanding of the terrain of concerns here to productively put energy into a PR.
I won't know the specific way that will look until I've done the work of the PR--this is a case where I'll really need to "think in code"--but certainly I'll ask you to review the work @mnwhite. You can tell me then if I've missed the mark.
Ok. To be clear, my ideal is for each of our solveOnePeriod functions to be just one function. The OO solvers were a decent idea, but their methods and submethods are incredibly tangled. Our efforts to "not duplicate code" resulted in fewer lines of code that are harder to read, and about 5x more docstrings.
On Tue, Apr 28, 2020 at 10:03 AM Sebastian Benthall < notifications@github.com> wrote:
Hmm. Curious. I've tested the code in my last comment.
I think I have a good enough understanding of the terrain of concerns here to productively put energy into a PR.
I won't know the specific way that will look until I've done the work of the PR--this is a case where I'll really need to "think in code"--but certainly I'll ask you to review the work @mnwhite https://github.com/mnwhite. You can tell me then if I've missed the mark.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/econ-ark/HARK/issues/658#issuecomment-620628078, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADKRAFOFXZ2AVT7NJ2V6IO3RO3O4VANCNFSM4MQPH6XQ .
Some progress on this, I hope. This proposed commit creates a generic onePeriodOOSolver
factory* and tests it out in the PerfectForesight case.
https://github.com/econ-ark/HARK/pull/665/commits/c5a2bc05efb245e79d3e92bae399720be18ff8a7
In a different commit on this PR, I show how that can be reused for the IndShockConsumerType case.
This implementation maintains solveOnePeriod's being set as a field, as opposed to as a method.
But rather than a different function defined for each OO solver, the function is created with the OO solver as an input. This requires a few modifications to the Solver objects, which don't seem sacred in their design based on our converation: they would take the Agent as an argument rather than having so many named parameters.
This is, again, an attempt at a minor refactoring. But the idea is to centralize more of the solution logic so it's a little easier to follow, and to see where there might be other extensions, improvements, or simplifications.
It may be a bit to digest so I won't work on it any more until we've had a chance to discuss it on our Thursday call.
There is a strange coding pattern in the Model classes. I am tempted to try to turn it into something more standard, but want to check if there is a hidden purpose to it.
Typically, a ConsumerType will have an attribute,
solveOnePeriod
, that is set upon initialization to some stand-alone function in the module. E.g.:https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L1658
This stand alone method then typically initalizes a Solver object, runs
solve()
on it, then returns the result (a solution). E.g.:https://github.com/econ-ark/HARK/blob/master/HARK/ConsumptionSaving/ConsIndShockModel.py#L590-L625
These methods are for the most part, except for the choice of Solver class, identical across the library.
This is not ideal because it makes it difficult to read the code: to follow the solution logic, you have to jump out of one class into the global scope, then into another.
It seems to me that either:
The only motivation I can think of for the current implementation is that it's suggested by this code block that the idea was to make it possible to have time-varying solution functions:
https://github.com/econ-ark/HARK/blob/78d1981233a6cda398d4e3007be03f558a7bc910/HARK/core.py#L807-L810
However, this flexibility does not seem to be used anywhere in the HARK repository (I checked), nor does it have any tests associated with it. If this was still a desirable feature, it would be possible to build 'solveOnePeriod', as a standardized method, to optionally take a list of class names that could be indexed by time periods.
[Separately, and maybe to be addressed in a different issue: I'm skeptical about the performance of this method of firing up a new Solver object, complete with a lot of initialization, at every step of the backwards inductions. It seems to me there may be a lot of redundant code calls. I'll revisit this once I've got a profiler set up, but I wanted to mention it now to start circulating the thought.]