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
SHGO algorithm #545
SHGO algorithm #545
Conversation
17c9c6f
to
a4be43d
Compare
fixed the logic to skip tests when scipy <= 1.2 ; last failing test is unrelated to this PR and is caused by minimal differences in the uncertaintes (only for PY35, so probably related to scipy 1.1). We can probably change the number of decimals for that test to three and than it will not be as flaky anymore.. |
@reneeotten The behavior of SHGO for bounds or initial values near zero is pretty strange, and should probably be raised as an issue on scipy. Other than that, I think this looks great. I like the decorator for specifying the algorithms for testing, and thanks for the better solution for suppressing the flake8 message about unused imports. I think it's OK to leave the "tweak 0 to epsilon" code in, at least until it gets solved upstream, but maybe add a brief note about the issue... Anyone have concerns or objections to merging this in the next couple of days? |
@newville let's keep it open for a little bit; I'll take a look at the actual paper first to see if I am missing something obvious. I would like to get a small, self-contained example and submit this to the author his GitHub (or scipy) to see what they think. Something that I also don't fully understand yet is that while the solutions for the eggholder-test are the same when directly using scipy or through lmfit, the number of function evaluations in some steps are not. It's my understanding that the method should be deterministic, which means that this cannot be the case. Either way, I would prefer people to try out this PR for a bit and see how it behaves before merging. Of course, it's hard to tell if that ever happens or if it will only get used when merged into master... What about giving me (and others) till the end of the week or so to do a bit more testing? |
@reneeotten OK, that's fine with me! |
If you have a MWE that displays issues in the shgo implementation, then
scipy is always interested in fixing them.
… |
I know @andyfaff. But before saying that there are issues in the implementation I want to make sure it's not me who introduced these issue when adding the algorithm to lmfit ;) I will investigate a bit more this week before opening an issue with the author and/or scipy. |
@reneeotten is this merge-able? I'd like to start working on preparing 0.9.13 soon. Should this be included? |
@newville no, I am sorry but it's not ready to merge yet. I did look into it a bit, but haven't finished it yet... so feel free to prepare a release without this. I do think though that the weirdness I saw when bounds at zero is from lmfit; it appears that during the |
@reneeotten OK, that's fine. For the weird behaviour seen with bounds at 0, I'm not sure I've see that. But I am increasingly wary of auto-selecting initial values (including the lineshape models built into lmfit). If Max and Min are set but not an initial value, assuming that Initial Value is (Max-Min)/2 may cause serious problems. For sure, handling an initial value of 0 is a challenge. Like, did that mean ~1.e-3 or ~1.e-150? |
fac325a
to
ee32d7b
Compare
okay, I think this should be good to go now. It turned out that the issue with "bounds at zero" was actually "our" fault and not in the I have updated the built-in models so that the default bounds are set to "machine precision" instead of zero, and also included such check in the lineshape functions. Please double-check that I didn't miss anything or messed something up... Of course it will still fail is a user explicitly sets the minimum bound for |
Codecov Report
@@ Coverage Diff @@
## master #545 +/- ##
==========================================
+ Coverage 83.28% 84.86% +1.58%
==========================================
Files 11 11
Lines 3170 3198 +28
==========================================
+ Hits 2640 2714 +74
+ Misses 530 484 -46
Continue to review full report at Codecov.
|
@reneeotten Oh, that makes sense, and it's great that we can add SHGO. But a general comment, and maybe a suggestion for maybe thinking about how this could go differently. For division-by-zero, we may want to be more careful than just saying that any In my experience, working with tiny values (say, below 1.e-12) is definitely prone to several "close to zero" errors. But, floating point is supposed to handle that. Like, But it also seems to me that the problem isn't with I would guess that if |
Sure, I see your point and am open to suggestions for a more informed decision about what a "small value to replace zero" should be. I can look a bit more and see if, for example,
Yes, the problem is as you said with divide-by-zero. It was my intention to change all these occurences in
That's what I initially tried, but unfortunately that alone is not sufficient. The error occurs in the
The only way to circumvent this problem (as far as I can tell) is to actually set minimum bounds to something "slightly larger than zero" in the actual model class. Again, I am open to other suggestions. |
@reneeotten FWIW, I don't really have a very strong preference for what "TINY" to avoid divide-by-zero.
Oh, that seems kind of weird and seems like that might not be limited to one particular solver. Do you have a simple example for this? |
Yes, as I said earlier the same issue happens with
|
@reneeotten Thanks. That suggests to me that when
or whatever "smallest positive number" should be..... 2e-16 would be fine too. |
Make sure that lineshape functions give a finite output even when variables are at their bounds (e.g., sigma). Otherwise, methods that evaluate the residual function between (min, max)-values (i.e., brute and shgo) will throw a ZeroDivisionError. [use max(tiny, variable), where tiny = np.finfo(np.float).eps = 2.220446049250313e-16 ]
- rename file to better reflect its content
Guard against ZeroDivisionError in the evaluation of the expression for height and FWHM. Otherwise, methods that evaluate the residual function between (min, max)-values (i.e., brute and shgo) will throw a ZeroDivisionError in asteval. [use max(tiny, variable), where tiny = np.finfo(np.float).eps = 2.220446049250313e-16 ] * for MoffatModel: use 1e-3 instead of tiny to avoid overflow in power
- VoigtModel without bounds on sigma results now in NaNs This error started after guarding the lineshape functions against division-by-zero; not sure I understand this... Also, make "test_numdifftools_with_bound" less stringent for comparison of stderr for parameters (i.e., 3 decimals not 4); test is flaky...
okay, I have made changes to the lineshapes and calculation of |
@reneeotten yes, that's great! That should solve the problem for all solvers or anyone evaluating these models. Ready to merge? |
|
@reneeotten OK, merging -- this is really great!! |
Description
SciPy v1.2 introduced two new global optimization algorithms and these should be supported in lmfit as well (see #527). This PR adds the
shgo
(simplicial homology global optimization) method.Tests were added to check make sure the implementation in lmfit gives the same result as calling
scipy.optimize.shgo
directly, using the example in thes SciPy function docstring. It seems to me there might be an upstream issue when using bounds at0
orNone
, but I'll need to narrow that down a bit more to make sure that's indeed the case. The tests intest_covariance_matrix.py
and a few others I tried myself did not work when a boundary was exactly set to zero (throwing an error fromscipy/optimize/_shgo_lib/triangulation.py
). Replacing the0
with a very small value (I picked machine precision for a float64) seems to solve that problem. That required a bit of work before callingprepare_fit
, but I think the logic works there.Similarly, I think something looks weird when no bounds are specified (
shgo
will internally set the boundaries then to + and - 1e50), and it looks like the initial starting point for the minimization is then zero (at least when specifying finite bounds, it will take the the value in the middle betweenmin
andmax
), which throws the same error as when having bounds at zero. At least I cannot get it to pass thetest_numdifftools_no_bounds
intest_covariance_matrix.py
, so for now I did not add the shgo method there.I think this has nothing to do with the implementation in lmfit, but I would welcome a few more pairs of eyes to make sure... So, please take a look at the PR, try it on a few of your favorite minimization problems to see how it behaves and let me know.
Type of Changes
Tested on
Python: 2.7.16, 3.6.8, and 3.7.2
lmfit: 0.9.12+77.ga05de1a, scipy: 1.2.1, numpy: 1.16.2, asteval: 0.9.13, uncertainties: 3.0.3, six: 1.12.0
Verification
Have you