Skip to content

BUG: lsq trf gives x=1e-10 if x0 is near a bound - #18896

Merged
tylerjereddy merged 2 commits into
scipy:mainfrom
ellieLitwack:trf_bounds
Jul 20, 2023
Merged

tylerjereddy merged 2 commits into
scipy:mainfrom
ellieLitwack:trf_bounds

Conversation

@ellieLitwack

@ellieLitwack ellieLitwack commented Jul 16, 2023 •

Copy link
Copy Markdown
Contributor

Reference issue

Closes #18793.

What does this implement/fix?

Previously, if x0 was within about 1e-10 of a bound and was a was close to the correct solution, least squares would gives x = [ 1e-10 ] if using trf. Now, it gives x = x0, which is the same behavior as dogbox.

Additional information

Previously, make_strictly_feasible was called before trf but after an initial bounds check. Now, make_strictly_feasible is not called prior to trf. As far as I could tell, that call of make_strictly_feasible was not doing anything useful. I am not sure why it was there in the first place.

Closes scipy#18793. Previously, if x0 was within about 1e-10 of a bound and
was a was close to the correct solution, least squares would gives x =
[ 1e-10 ] if using trf. Now, it gives x = x0, which is the same behavior
as dogbox.

Previously, make_strictly_feasible was called before trf but after an
initial bounds check. Now, make_strictly_feasible is not called prior to
trf.
@ellieLitwack
ellieLitwack marked this pull request as ready for review July 16, 2023 17:33
@j-bowhay j-bowhay added the defect A clear bug or issue that prevents SciPy from being installed or used as expected label Jul 16, 2023

@tylerjereddy tylerjereddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems straightforward enough, though not my area of expertise. One comment--any reason not to include the following regression test that fails on main and passes here in < 0.04s?

--- a/scipy/optimize/tests/test_least_squares.py
+++ b/scipy/optimize/tests/test_least_squares.py
@@ -809,3 +809,12 @@ def test_fp32_gh12991():
     # used a step size for FP64 when the working space was FP32.
     assert res.nfev > 2
     assert_allclose(res.x, np.array([0.4082241, 0.15530563]), atol=5e-5)
+
+
+def test_gh_18793():
+
+    def chi2(x):
+        return (x-1e-12)**2
+
+    res = least_squares(chi2, x0=1.1e-12, bounds=(0, np.inf))
+    assert_allclose(res.x, 1.1e-12)

@tylerjereddy tylerjereddy added the backport-candidate This fix should be ported by a maintainer to previous SciPy versions. label Jul 19, 2023
This is a regression test for scipy#18793. This checks that, if an initial
condition is already close to the answer, trf does not return a result
that is less accurate than the initial condition.
@ellieLitwack

Copy link
Copy Markdown
Contributor Author

That makes sense to add! I changed assert_allclose(res.x, 1.1e-12) because we would like res.x to be as close as possible to 1e-12, and no further away than 1.1e-12 is - the new assert statement checks for that specifically.

Thank you for your review!

@tylerjereddy
tylerjereddy merged commit ba761d8 into scipy:main Jul 20, 2023
@tylerjereddy

Copy link
Copy Markdown
Contributor

Thanks, I squash-merged to facilitate backporting. Folks seemed in agreement on the fix in the issue, the CI failure isn't related, and there's a new regression test, so seemed good to go.

@tylerjereddy tylerjereddy added this to the 1.12.0 milestone Jul 20, 2023
@tylerjereddy tylerjereddy modified the milestones: 1.12.0, 1.11.2 Aug 10, 2023
@tylerjereddy tylerjereddy mentioned this pull request Aug 10, 2023
4 of 5 tasks
@tylerjereddy tylerjereddy removed the backport-candidate This fix should be ported by a maintainer to previous SciPy versions. label Aug 17, 2023
@sweettyler

Copy link
Copy Markdown

got messages after upgrading Scipy from 1.11.1 to 1.11.2:

/mambaforge/lib/python3.10/site-packages/scipy/optimize/_lsq/trf.py:234: RuntimeWarning: divide by zero encountered in divide
Delta = norm(x0 * scale_inv / v**0.5)
/mambaforge/lib/python3.10/site-packages/scipy/optimize/_lsq/trf.py:363: RuntimeWarning: invalid value encountered in scalar divide
alpha *= Delta / Delta_new
/mambaforge/lib/python3.10/site-packages/scipy/optimize/_lsq/common.py:49: RuntimeWarning: invalid value encountered in scalar divide
t2 = c / q

Not sure if it is related.

@hwalinga

Copy link
Copy Markdown
Contributor

@sweettyler Yes this is noted by me as well. This should not have been merged, but a reduced step on make_strictly_feasible with rstep=0, would have already fixed this instead. You only have warnings, but see my related issue for a case where an optimization problem fails instead.

@tylerjereddy

Copy link
Copy Markdown
Contributor

Try to propose the desired behavior in a PR, pinging the folks involved, and add a regression test for the behavior you want preserved, since we obviously don't have such a test.

hwalinga added a commit to hwalinga/scipy that referenced this pull request Aug 22, 2023
PR scipy#18896 removed make_strictly_feasible because it makes solutions
worse if they are close to the boundary.
However, the downstream algorithm that eventually solves the
optimization problem has some trouble with starting points that
are on the boundaries of a bounded optimization problem.
This either results in division by zero warnings, see scipy#19102,
or in failure of finding the correct solution at all,
which is added as a regression test in this commit.
This PR brings back make_strictly_feasible but with the
smallest possible step from the boundary a floating point allows
by setting rstep=0 on the function make_strictly_feasible,
which uses np.nextafter to find such a point, hereby respecting
the strictly feasible point condition and minimizing boundary effects.
See strictly feasible points in interior-point methods for more context
https://nmayorov.wordpress.com/2015/06/19/trust-region-reflective-algorithm/
hwalinga added a commit to hwalinga/scipy that referenced this pull request Aug 23, 2023
PR scipy#18896 removed make_strictly_feasible because it makes solutions
worse if they are close to the boundary.
However, the downstream algorithm that eventually solves the
optimization problem has some trouble with starting points that
are on the boundaries of a bounded optimization problem.
This either results in division by zero warnings, see scipy#19102,
or in failure of finding the correct solution at all,
which is added as a regression test in this commit.
This PR brings back make_strictly_feasible but with the
smallest possible step from the boundary a floating point allows
by setting rstep=0 on the function make_strictly_feasible,
which uses np.nextafter to find such a point, hereby respecting
the strictly feasible point condition and minimizing boundary effects.
See strictly feasible points in interior-point methods for more context
https://nmayorov.wordpress.com/2015/06/19/trust-region-reflective-algorithm/
tylerjereddy pushed a commit to tylerjereddy/scipy that referenced this pull request Sep 21, 2023
PR scipy#18896 removed make_strictly_feasible because it makes solutions
worse if they are close to the boundary.
However, the downstream algorithm that eventually solves the
optimization problem has some trouble with starting points that
are on the boundaries of a bounded optimization problem.
This either results in division by zero warnings, see scipy#19102,
or in failure of finding the correct solution at all,
which is added as a regression test in this commit.
This PR brings back make_strictly_feasible but with the
smallest possible step from the boundary a floating point allows
by setting rstep=0 on the function make_strictly_feasible,
which uses np.nextafter to find such a point, hereby respecting
the strictly feasible point condition and minimizing boundary effects.
See strictly feasible points in interior-point methods for more context
https://nmayorov.wordpress.com/2015/06/19/trust-region-reflective-algorithm/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

defect A clear bug or issue that prevents SciPy from being installed or used as expected scipy.optimize

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: optimize.least_squares with method='trf' yields wrong result when initial guess is close to (lower) bound

5 participants