-
Notifications
You must be signed in to change notification settings - Fork 9
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
Newton-Raphson root find for Helmholtz EOS #304
Conversation
Is there a possibility that the Newton method won't converge? If so, it might make sense to structure the root find option as use RF is NR fails or if the user specifically requests not to use NR. |
Thanks for the suggestion! I've added RF as fallback to the NR in cases the NR root find fails. As far as convergence is concerned, this a bit more difficult to answer. In my naive test program, RF was actually more likely to not converge, but I know that in astrophysical simulations the NR also has trouble converging in certain parameter spaces. The question is if the RF would actually converge better in those cases (e.g. there is nothing that can be done once the root find runs into the edges of the underlying table) and, more importantly, if the non-convergence actually matters in these cases (vs the increased computational cost). To my knowledge, most astrophysical simulations have 'ignored' this and just bound the result of the root find back to the tabulated values. I might be mistaken though, but I guess this is something that one would first need to investigate extensively. For now I have implemented RF as a fallback if NR does not converge, in my opinion this is a sensible default. |
Interesting! I suppose I'm used to using the RF method on problems that have an easy way to bracket the solution a priori via some known bounds. In those cases, the RF method is guaranteed to converge albeit slowly sometimes. I assume that when you say that RF didn't converge, it means that you were unable to bracket the solution? Or was it simply converging so slowly that it ran out of iterations? Another thing we could consider in the future is to improve the RF method to use either the Pegasus or Illinois algorithms (http://paulklein.se/newsite/teaching/rootfinding.pdf) to get superlinear convergence. |
Thanks for the interesting reference! I definitely want to take a deeper dive into this at some point. In case of the RF failures, as far as I can tell the root find reaches the maximum number of iterations. (Note Ultimately I think that this needs a thorough investigation to make an informed decision / analysis on the advantages of different root finding algorithms. The point of this MR is mainly to make the NR root find available for the Helmholtz EOS, as it is the tried and testend method for this EOS (not to speak of the significant speedup, which is important for the applications I'm working with) that so far has not been questioned. In the end it is probably an extremely efficient algorithm for this use case since F. Timmes fine tuned the underlying table to work with NR (i.e. taking care that the derivatives are accurate). |
Seems fair! I'll take a closer look when I get a chance but @Yurlungur should also review |
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.
Minor changes/questions
// solves for f(x,params) - ytarget = 0 | ||
template <typename T> | ||
PORTABLE_INLINE_FUNCTION Status newton_raphson(const T &f, const Real ytarget, | ||
const Real guess, const Real a, | ||
const Real b, const Real ytol, Real &xroot, | ||
const RootCounts *counts = nullptr, | ||
const bool &verbose = false) { |
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 it's okay to use the same function signature as the regula falsi method, but I think you should at least add a comment about the behavior of the callable f
being different (i.e. needing to return both function and derivative). Anybody using it now will quickly run into a compile time error if they try to just return the function, but why not save them the work of compiling to figure that out?
In the future I could see possibly wanting to develop an overload for this signature that uses the same behavior for f
as the regula falsi and then wraps that callable in a function to perform a finite difference of some sort when an analytic derivative isn't available. Regardless of whether that happens though, comments will always help in understanding the subtle differences in template behavior between the root finding methods.
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 agree with @jhp-lanl here
// check if we are out of bounds | ||
if (_x <= a && _xold <= a) { | ||
if (verbose) { | ||
printf("newton_raphson out of bounds! %.14e %.14e %.14e %.14e\n", ytarget, guess, | ||
a, b); | ||
} | ||
break; | ||
} | ||
if (_x >= b && _xold >= b) { | ||
if (verbose) { | ||
printf("newton_raphson out of bounds! %.14e %.14e %.14e %.14e\n", ytarget, guess, | ||
a, b); | ||
} | ||
break; | ||
} |
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.
Maybe I'm missing the difference, but it looks like these conditions could be combined to make this code more DRY
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.
Thanks for the catch! This is a bit of a tricky one. In the original reference implementation, in each condition the root value was set to the appropriate boundary value, hence the two conditions. This has been moved to the Helmholtz call itself (since it is also necessary for the RF roots). I'm not sure if this is the smart way to do, i.e. if this bounding should be handled by the root finding methods or by the function calling the root find. For now I have decided that this should be handled by the function calling the root find (since one might not always want to bound the value to the boundary values) and added a comment in the appropriate location.
This is also the reason that this out-of-bounds case does not return a FAILURE status. One could argue that this is not really a successful root, but at least in the context of the Helmholtz EOS a root bound to the edges of the table is (at least in the reference implementation) taken as a success. Most likely this is a peculiarity for the Helmholtz EOS due to the fine tuning and won't translate to other application. I am however hesitant to implement a FAILURE return here since this would trigger an additional root find using RF. The bound result should be good enough, at least good enough as to not trigger a new attempt.
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.
You could add a fail_on_bound_root
flag and default it to true
and then just set it to false
for the Helmholtz EOS. I'm a bit hesitant to have a root finder in the code whose default behavior could silently (if verbose
isn't on) fail to find a root.
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.
Fair point. Thanks for the suggestion! I've added the relevant option to the root find.
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 approve. A few minor nitpicks/comments below that match @jhp-lanl 's thoughts.
// solves for f(x,params) - ytarget = 0 | ||
template <typename T> | ||
PORTABLE_INLINE_FUNCTION Status newton_raphson(const T &f, const Real ytarget, | ||
const Real guess, const Real a, | ||
const Real b, const Real ytol, Real &xroot, | ||
const RootCounts *counts = nullptr, | ||
const bool &verbose = false) { |
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 agree with @jhp-lanl here
if (_x <= a && _xold <= a) { | ||
if (verbose) { | ||
printf("newton_raphson out of bounds! %.14e %.14e %.14e %.14e\n", ytarget, guess, | ||
a, b); | ||
} |
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 is fine for now, but I strongly suspect this can be made much more robust with, e.g., a line search, or even a simple bounds enforcement. NR is prone to overshooting, and simply forcing it back in the domain is often enough to get it to converge.
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 agree. There is most likely several ways to improve the convergence properties. For now the point is more to implement the NR as given by the reference implementation. I'm planning to conduct some more extensive benchmarking in the future to establish the performance vs. convergence in this context (since I suspect that this implementation is tuned for performance with just "good enough" convergence properties), but there is no timeline yet.
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 approve, but @Yurlungur should take a final look before this gets merged.
PR Summary
Adds a Newton-Raphson root find, specifically for use with the Helmholtz EOS. Although the current
regula falsi
produces on average more precise results, it is significantly more expensive. Moreover it is the method used in the originally published paper by Timmes.Plot comparing both root finds
This benchmark was generated evaluating the EOS at random input values. It is probably not super scientific/ accurate, but should suffice the advantage of the Newton-Raphson root find.
A few points may (or may not) need to be addressed still:
PR Checklist
make format
command after configuring withcmake
.