-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
BUG: Optimize: NewtonCG min crashes with xtol=0 #20299
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
Conversation
The minimize function crashes if `xtol` is set to `0`. This is because the initial value of a loop variable is `2 * xtol`, which is intended to ensure the loop runs at least once. However, if `xtol = 0` then the loop never runs, a variable isn't instantiated that is used outside of the loop, and an `UnboundLocalError` is raised. To fix this, the initial value of the loop variable is set to the max float value.
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.
LGTM!
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'll let the optimize regulars chime in on whether a regression test is sensible to add, but this slightly modified version of the original reproducer fails before/passes after and seems fast:
--- a/scipy/optimize/tests/test_optimize.py
+++ b/scipy/optimize/tests/test_optimize.py
@@ -3120,3 +3120,19 @@ def test_sparse_hessian(method, sparse_type):
assert res_dense.nfev == res_sparse.nfev
assert res_dense.njev == res_sparse.njev
assert res_dense.nhev == res_sparse.nhev
+
+
+def test_gh_20214():
+ def cosine(x):
+ f = np.cos(x[0])
+ return f
+
+ def jac(x):
+ ret = -np.sin(x)
+ return ret
+
+ result = optimize.minimize(cosine,
+ x0=[0.1],
+ jac=jac,
+ method="newton-cg",
+ options=dict(xtol=0, maxiter=2))
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.
At least one error from #20214 occurs elsewhere, namely update
is undefined in this branch:
scipy/scipy/optimize/_optimize.py
Lines 2118 to 2120 in cd60e81
else: | |
if np.isnan(old_fval) or np.isnan(update).any(): | |
return terminate(3, _status_message['nan']) |
Looking at c8c78d7, this commit failed to rename that instance of update
to update_l1norm
. CC @lorentzenchr
We need a test that exercises this else-branch in any case. I haven't checked if @tylerjereddy's case does that, but to reach the else:
clause of the while:
loop, we must achieve update_l1norm <= xtol
without having triggered any of the early return
statements in the while body.
Corrected error from previous rework when switching to `np.linalg.norm` that introduced the possibility of the unbound variable error.
Regression test for scipygh-20214 which addressed an issue in the Newton CG method which would raise an UnboundLocalVariable error if `xtol` was set to zero. It also ensures the method finds the solution in this case.
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 @TimButters lgtm but I will wait to see if others are happy before merging
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'm still unsure about the right initial value for update_l1norm
, though I can see the argument that it's being updated at the end of the loop. Still, the interplay with the shape of x
is a mystery to me. The code seems very brittle w.r.t. to putting in x0=[1, 2]
or something like that.
Unless we can figure this out quickly and comprehensively, I'd prefer an immediate fix for the unboundname error to go into 1.13, and the rest to be fixed for 1.14.
Is there a test for the „nan-path“? |
Comment added to explain the purpose of setting `update_l1norm` to the max float value - this is to ensure the preceding while loop executes at least once. Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com>
This is for the `xtol=0` test in the Newton CG minimize method.
@lorentzenchr yes, there is an existing test |
Not going to stand in the way here as I don't know the code well enough and I believe this is in good hands; as long as the UnboundNameError is fixed I'm happy. :)
@j-bowhay @lucascolley I think we have consensus now, would one of you be happy to merge this? |
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 @TimButters , I'll give it a few days for any more comments before merging.
As a note for the future, I'd recommend that you leave resolving conversations to maintainers - a thorough reviewer will probably want to check any conversations that were resolved by the author, so it is easier to leave them as is.
Thanks for the feedback @lucascolley, I thought I was doing the right thing there. I’ll leave them open in the future. |
Here we are, thanks @TimButters ! |
Thanks @TimButters a great couple of first contributions, keep them coming! |
* BUG: Optimize: NewtonCG min crashes with xtol=0 The minimize function crashes if `xtol` is set to `0`. This is because the initial value of a loop variable is `2 * xtol`, which is intended to ensure the loop runs at least once. However, if `xtol = 0` then the loop never runs, a variable isn't instantiated that is used outside of the loop, and an `UnboundLocalError` is raised. To fix this, the initial value of the loop variable is set to the max float value. * BUG: Optimize: UnboundLocalError for xtol=0 NCG Corrected error from previous rework when switching to `np.linalg.norm` that introduced the possibility of the unbound variable error. * TST: Optimize: NewtonCF Minimize when xtol=0 Regression test for scipygh-20214 which addressed an issue in the Newton CG method which would raise an UnboundLocalVariable error if `xtol` was set to zero. It also ensures the method finds the solution in this case. * MAINT: Add comment to explain initial value Comment added to explain the purpose of setting `update_l1norm` to the max float value - this is to ensure the preceding while loop executes at least once. Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com> * TST: Test solution value as well as success This is for the `xtol=0` test in the Newton CG minimize method. * TST: Explicitly extract val from array --------- Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com>
The minimize function crashes if
xtol
is set to0
. This is because the initial value of a loop variable is2 * xtol
, which is intended to ensure the loop runs at least once. However, ifxtol = 0
then the loop never runs, a variable isn't instantiated that is used outside of the loop, and anUnboundLocalError
is raised.To fix this, the initial value of the loop variable is set to the max float value.
Reference issue
Closes #20214
What does this implement/fix?
UnboundLocalError is thrown if
xtol
is set to 0 for thenewton-cg
method inminimize
.Additional information