-
Notifications
You must be signed in to change notification settings - Fork 90
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
Implement an Ad parser class #1181
Comments
Will there be functionality reuse between |
Note to self: Operator evaluation will also be needed as part of the solution process (test case: the line search methods being introduced in #1208). We need to make sure that the |
NOTE: For debugging purposes, it should always be possible to switch off all caching. |
The current operator tree is generated by Python's processing system for arithmetic operations, and therefore automatically follows the expected rules of precedence for operators. There is however some leeway and room for improvement in ordering operations with equal priority, where Python will operate from left to right whereas we typically want to operate from right to left (reasons: 1. This is needed for the slicing approach to projections hinted at in #1182, 2., this may be faster for residual evaluations, where an expression of the type To exploit this flexibility, it is necessary to rearrange the computational graph to prioritize multiplication from right, though without violating the rules for operator precedence. Exactly how to do this is unclear, my instinct is to maximize the depth of nodes involving AdArrays and DenseArrays, but I am not sure about this. |
The introduction of an
The
This calls for the While it is formally okay make several instances of the @IvarStefansson could we have a discussion on this at some point? |
Sure! |
Implementation steps:
While it is tempting to split this into several PRs, there will be a lot of experimental coding here; moreover, the new system will not really be meaningful until step 4 is completed. Hence, it seems this will be a single effort. I hope to start working on this in earnest over the next few days. |
Update on relation between classes: Parsing of variables, thus general operator trees, requires access to the variable state, which is accessed through the
With option 2 (which is my strong preference), evaluation inside a model class (during equtaion construction or debugging) will go from some_operator.value(self.equation_system) to self.equation_system.operator_value(some_operator) Disclaimer: There may be caveats that I don't see yet, but hopefully such a solution can be implemented. @IvarStefansson If you have opinions on this, please let me know. We can surely negotiate on the method name |
Is the cyclic dependency really a problem? |
Yes, it will lead to import errors (and it generally signifies a poor code design). |
If I may attempt to be helpful, the descriptor protocol will help you with that.
|
Status after latest changes:
Remaining tasks before PR:
|
Summary of observations made during more thorough benchmarkingThis was much more complex than anticipated. Essentially, there are two ways of parsing an operator tree:
Caching, when implemented, can be done either individually among each operator tree, or jointly for a collection of trees (say, when the EquationSystem needs the Jacobian for multiple equations). I have not yet implemented functionality for keeping parts of the cache between Jacobian evaluations (this is possible, but not an immediate priority). Timings below are based on measurements using I have mainly considered a THM simulation with a varying number of cells, roughly estimated (guessed) to range from low hundreds to several thousands. Observations:
The best option right now seems to be the one mentioned in observation no 2 above, though with minimal caching, but I will need to experiment more before concluding. If anyone can explain what is going on, I would be most interested. |
Background
In the current code, the evaluation of a
pp.ad.Operator
is implemented in theOperator
class itself. The parsing of a tree of an operator (which is really a computational graph) is implemented as a depth-first traversal which identifies leaves in the tree, parses these to numerical values, and then propagates the values through the graph in a bottom-up fashion. While this works, there are shortcomings with the approach:Suggested changes
The following large-scale changes are suggested - specifications are needed before implementation:
def graph_traversal(list[pp.ad.Operator]) -> Iterator[pp.ad.Operator]
. The motivation for accepting a list of operators is given in the outline of the Ad parser class below.AdParser
, which takes over responsibility for parsing from theOperator
class. This will use the graph traversal function to iterate over the computational graph and convert it to numerical values. Moreover, the parser should contain functionality for chaching of operator values to reduce evaluation time.Thoughts on the
AdParser
classBelow is an outline of the class:
Access to the
AdParser
While it is tempting to make the
AdParser
a singleton accessible aspp.AdParser
or similar, there is no clear reason to do so. Instead, it is suggested that all models are equipped with anAdParser
, just as they have anEquationSystem
. It should also be possible to parse outside a model, by a callpp.ad.AdParser(mdg).value_and_jacobian([operator])
, though such operations will not be very common.The role of the
EquationSystem
While the functionality of the
AdParser
could have been integrated into theEquationSystem
, it is preferable not to add more functionality into this (arguably already overloaded) class. Instead, theEquationSystem
can be given the model-wideAdParser
as an attribute and call this for evaluation of equations.Dependencies
This should not be started until completion of #1179 and #1180
The text was updated successfully, but these errors were encountered: