Adding adaptive preconditioner functionality to Cantera#1010
Adding adaptive preconditioner functionality to Cantera#1010speth merged 3 commits intoCantera:mainfrom
Conversation
|
@bryanwweber and @ischoegl, I have updated the pull request to be from my "ap-dev" branch. I will use this in the future. |
|
Tagging #951. |
Codecov Report
@@ Coverage Diff @@
## main #1010 +/- ##
==========================================
+ Coverage 67.93% 68.01% +0.08%
==========================================
Files 316 327 +11
Lines 41948 42603 +655
Branches 16853 17143 +290
==========================================
+ Hits 28498 28978 +480
- Misses 11186 11345 +159
- Partials 2264 2280 +16
📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more |
|
@anthony-walker ... thanks for adding the tests. As a quick heads-up, please avoid |
65924db to
2d77873
Compare
speth
left a comment
There was a problem hiding this comment.
@anthony-walker, thanks for the work on this. I think a lot of people are looking forward to this feature. I have some (hopefully helpful) questions about the mathematical / algorithmic issues and suggestions about coding style.
First, as a point of clarification, at the end of AdaptivePreconditioner::setup, it looks like the m_matrix variable is meant to represent the Jacobian. Is this correct? Assuming that it is, I tried printing out the Jacobian for the initial timestep of the Python test problem, and what I saw didn't make a lot of sense. For instance, there are a lot of zeros on the diagonal, entire rows of zeros, and every value is positive. Just as a general sanity check, I would expect the diagonal elements to all be negative (except for a species with no reactions), and the matrix to have a mix of positive and negative values. I tried comparing this Jacobian to a simple finite difference approximation for just the species equation, and saw basically no correspondence.
Also, I don't see anywhere where the preconditioner matrix P = I - gamma * J gets formed. Are you just using the Jacobian directly as the preconditioner? There are places where the two seem to be conflated in the code, but the Jacobian itself presumably isn't what you want to use as the preconditioner.
As a more minor point, I see that the factorization step is being done as part of the "AdaptivePreconditioner::solve" function. To improve performance, the usual strategy is to factorize the preconditioner as part of the "setup" function, so that the repeated calls to "solve" can re-use the LU factors and solving is just a matter of backsubstitution. Later, once this is all working, you might also want to use one of the "incomplete" LU factorization algorithms that Eigen as a way of speeding things up even more, since the preconditioner doesn't need to be exact.
I also had a couple of suggestions on coding style and structure, which will hopefully make the code easier to read, review, and maintain:
- There's a lot of manual memory management going on, which I think should be avoided. This means using
std::vectorinstead ofnew[],std::unique_ptrorstd::shared_ptrin other cases where you can't allocate an object on the stack. Basically, you want to avoid ever having to directly usedelete. - Inside a member function, it is almost never necessary to prefix access to a member function or variable with
this->. - Please take a look at some of the other source code, e.g.
Reactor.cppfor typical style and try to write your code to be consistent with this. Take a look at things like indentation (4 spaces, but none for the initialnamespacedeclaration); spacing and placement of brackets inif / elseblocks,forloops, etc.; spacing around operators (one on each side of+,=and others).
ca2294a to
37ccaf5
Compare
8666869 to
9f28061
Compare
ischoegl
left a comment
There was a problem hiding this comment.
@anthony-walker ... great to see this moving along. I have one (tangential) comment at the moment.
There was a problem hiding this comment.
Hi @anthony-walker ... thank you for your continued work on this proof of concept! - this is an extremely promising addition that will have tremendous impact once it is fully implemented.
One thing that I wanted to bring up at this point in somewhat more detail is that I believe that the API would benefit from being better integrated with the existing Reactor and ReactorNet implementations (I have hinted at this earlier, see here and here). As you correctly imply, #1089 implemented derivatives directly within the Kinetics object which means that they are fully integrated. Similarly, I believe that the calculation of Jacobians should be fully integrated with zeroD objects and be accessible from their APIs.
Specifically, I think the following (proposed) methods are worth spending some thought on:
-
virtual Reactor::jacobian(C++) andReactor.jacobian(Python): while it is ok to just implement these methods forIdealGasMoleReactor/IdealGasConstPressureMoleReactorat the moment, it should be technically feasible to calculate them for otherReactorspecializations as well. This would be especially useful if Cantera/enhancements#91 gets tackled (which I believe is a worthwhile effort) and open the preconditioning up to other specializations without having to understand the implementation of the preconditioner (it suffices to implement the correct Jacobian for a givenReactorspecialization). It will also make it much easier to build preconditioners based on different versions of Jacobians (i.e. different approximation levels). -
ReactorNet::jacobian(C++) andReactorNet.jacobian(Python): same reasons as above.
I honestly believe that this approach will be tremendously helpful for unit testing: based on a numerical solution, the ODE terms at t + deltat should be close to the ODE term at time t plus Jacobian times difference in states. Imho, this is a much more rigorous test than comparing the results of a time integration.
In addition, this would allow for intuitive interactions with Jacobians, rather than working with the preconditioner directly as currently implemented in the Reactor::preconditionerSetup approach. From my perspective, it would make more sense if the preconditioner would be isolated in ReactorNet and not appear elsewhere.
|
@ischoegl, I agree that ultimately, it would be very useful to have a general interface to I'd suggest that for this PR, there are two main things to do to make such a future enhancement as simple as possible:
|
|
@speth This is already done.
Is there a specific way to mark methods as experimental?
|
|
@speth ... I respectfully disagree on the status, but am happy to engage in a discussion (which I've been trying to start for some time). @anthony-walker ... I honestly believe that some refactoring would make your work a lot stronger and easier to extend/maintain. Is this all the work you can do on this or will there be follow-up PR's? |
|
@ischoegl I intend to do follow up PRs, this one has just been drawn out for some time and is becoming massively cluttered and more complicated. I have some quick ideas for refactoring based on your suggestions that may not be too complicated. I will implement them quickly and we can discuss.
|
|
@anthony-walker ...
That is a relief! As long as this is the case, I'm overall not opposed to wrapping this PR up so we can get past this stage. Before doing this, could you post a new work-in-progress issue on Cantera/enhancements (or even in Cantera/enhancements#100)? When I did the refactoring of reaction rate evaluators in |
I'd suggest that documenting this plan an existing enhancement makes the most sense. Cantera/enhancements#86 (Provide Jacobian to Sundials solvers) is also very relevant.
Elsewhere, what we've done is to add the following to the Doxygen docstring: |
|
@speth ... the original issue is Cantera/enhancements#47 (I believe) |
|
@speth @ischoegl It was relatively straight forward to remove the |
speth
left a comment
There was a problem hiding this comment.
Thank you for the updates so far, @anthony-walker. I have another set of suggestions that I think will continue to simplify things and bring this into line with the other parts of the Cantera codebase. Beyond the various specific comments below, two general requests I have are:
- Please run
scons doxygen, look at the warnings issued, and fix the ones that are associated with your changes. There are quite a few places where there are discrepancies between the documented parameters and the actual function parameters. - Please go through your code and wrap lines that are longer than 88 characters. I would recommend setting a "ruler" in your IDE to this length so you can see where you are.
|
@speth Thank you for the feedback. Hopefully we are getting close to wrapping this up. I believe I have address all requested changes. I set my
I have also done this and corrected the docstrings. I did also refactor the |
speth
left a comment
There was a problem hiding this comment.
Thanks for making the previous round of updates, @anthony-walker. I think this is looking pretty good from a structural standpoint. Most (though not all) of the issues I've commented on here are just formatting and documentation concerns.
interfaces/cython/cantera/examples/reactors/preconditioned_integration.py
Outdated
Show resolved
Hide resolved
Adds PreconditionerBase and derives AdaptivePreconditioner from this base class. PreconditionerBase is intended to be an abstract class so other types of preconditioners can be extended. AdaptivePreconditioner is a specific type of preconditioning based on a tolerance. The preconditioned integrator works with all Sundials versions supported by Cantera. The ILUT algorithm is used to limit fill-in when factorizing the preconditioner. Adds access to CVODES integrator statistics in C++ and Python Adds reactor classes "IdealGasConstPressureMoleReactor" and "IdealGasMoleReactor" that use a mole-based state vector to increase the sparsity of the preconditioner.
|
@anthony-walker - Thanks for the updates. I think this has reached a state where it's ready to be merged, even though there are a few things that need to be done to improve robustness. I just pushed to your branch to squash some of the commits together because there was a lot of churn related to the several times that this PR has been rebased, and the intermediate commits had become impossible to even compile. @ischoegl - This is awaiting your approval before I can merge it. |
|
@speth I agree that it could still use some work to improve robustness. I think said work will be better suited to smaller pull requests now that the core functionality is present. |
|
@anthony-walker ... could you add what is left to do to Cantera/enhancements#47 (or whatever is most appropriate) so we have a road map for remaining work? |
|
@ischoegl yes I will update the most appropriate enhancement. |
- Adds a check for surfaces - adds updateState and updatePreconditioner in proper locations - Fixes documentation and makes other formatting changes - Adds utilities tests and expected failures tests for the preconditioner
|
@speth is there anything else that needs done before this is merged? |
|
@anthony-walker - Just the requested update to Cantera/enhancements#47. |
This code is adding preconditioning capabilities to Cantera/CVODEs solver interface for reactor networks. It is focused on a specific type of preconditioning but the code is being written with extensibility in mind. It adds Preconditioning capabilities to the numerics portion of cantera which contains a "PreconditionerBase" that is used to write future preconditioners. It also contains a class derived from this called "AdaptivePreconditioner" which is the aforementioned specific preconditioner.
There are also a couple of functions added to specific reactors. IdealGasReactor is one example. Currently, I added a function to evaluate the energy equation which is also done inside of evalEqs but I wanted to avoid touching core Cantera code as much as possible. In the future, I would like to collaborate with the appropriate people to stream line this.
Checklist
scons build&scons test) and unit tests address code coverage