scipy.optimize: fix signed bracket width in line search zoom - #40228
Open
VaggelisGian wants to merge 1 commit into
Open
scipy.optimize: fix signed bracket width in line search zoom#40228VaggelisGian wants to merge 1 commit into
VaggelisGian wants to merge 1 commit into
Conversation
The zoom stage of the Wolfe line search marked itself as failed when `a_hi - a_lo <= threshold`, where threshold was an absolute constant (1e-5 for dtypes narrower than float64, 1e-10 otherwise). Two problems: 1. dalpha is signed. zoom2 is always called as _zoom(a_i, ..., a_i1), i.e. with a_lo > a_hi whenever the outer loop doubled alpha, and zoom1 inverts its bracket after hi_to_lo swaps. On an inverted- bracket iteration `negative <= positive threshold` evaluated true and set failed after that iteration's trial point, cutting off any further refinement of a bracket that still contained Wolfe-satisfying steps. On problems where overshoot triggers zoom2 (Rosenbrock from [-1.2, 1] or from [0, 0], Beale), minimize reported a line search failure (status 3 for BFGS, 5 for l-bfgs-experimental-do-not-rely-on-this) for line searches that had in fact found a point satisfying the strong Wolfe conditions; scipy's line_search_wolfe2 returns the identical step for the same inputs. 2. The absolute thresholds were neither scale nor dtype aware. They are replaced by eps(dtype) * max(|a_lo|, |a_hi|): the width below which bisection can no longer produce a distinct midpoint. This lets deep but legitimate zooms finish (BFGS on sum((1e6*(x-1))^2) needs widths near 1e-12 that the old 1e-10 floor rejected) while still catching genuinely collapsed brackets. The zoom iteration cap moves from 30 to 60: when interpolation degenerates to pure bisection over a wide dynamic range (l-bfgs-experimental starts at alpha=1 and needs alpha ~ 5e-13 on the steep quadratic above), 30 halvings are not enough to reach such a step, so the old cap aborted searches that were still contracting. Deliberately unchanged: the Wolfe condition definitions, the outer line search loop, and the float32 alpha_k floor in line_search. Note one interaction worth knowing: the newly reachable deep zooms can end on an alpha below that floor (|alpha_k| < 1e-8 is floored to 1e-8 for narrow dtypes), so on problems with extreme curvature in float32 the L-BFGS path can now report convergence at a step whose stored alpha was floored; the returned fun/jac come from the true point while the saved step uses the floored one. This is pre-existing floor behavior on a previously unreachable path and is left to a separate change. Test Plan: python -m pytest tests/scipy_optimize_test.py -k "curved_valley or steep_quadratic" -q default flags: 2 failed before the fix, 2 passed + 3 skipped after. JAX_ENABLE_X64=1 python -m pytest tests/scipy_optimize_test.py -k "curved_valley or steep_quadratic" -q x64: 5 failed before the fix, 5 passed after. python -m pytest tests/scipy_optimize_test.py -q -n auto default flags: 15 passed, 4 skipped. JAX_ENABLE_X64=1 python -m pytest tests/scipy_optimize_test.py -q -n auto x64: 19 passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
jax.scipy.optimize.minimize(method="BFGS")reports a line-search failure (status 3) on the classical 2D Rosenbrock function from the standard start points, in both float32 and float64, wherescipy.optimize.minimizeconverges:Root cause is in the zoom stage of the Wolfe line search (
jax/_src/scipy/optimize/line_search.py). The failure guard compared a signed bracket widthdalpha <= threshold.zoom2is always entered witha_lo > a_hiwhenever the outer loop doubled alpha, andzoom1legitimately inverts its bracket after swaps, so on those iterations a negative width compared against a positive threshold set the stickyfailedflag even while the bracket still contained steps satisfying the strong Wolfe conditions - scipy'sline_search_wolfe2returns the identical step for the same inputs.Second, the absolute thresholds (1e-5 narrow dtypes, 1e-10 otherwise) were neither scale nor dtype aware and rejected legitimate deep bisection (widths near 1e-12 on steep quadratics).
The guard now compares
abs(dalpha)againsteps(dtype) * max(|a_lo|, |a_hi|), the width below which bisection cannot produce a distinct midpoint, and the zoom iteration cap moves from 30 to 60 so pure-bisection searches over wide dynamic ranges can finish.Deliberately unchanged: the Wolfe condition definitions, the outer loop, and the float32 alpha floor in
line_search. One interaction worth noting: the newly reachable deep zooms can end on an alpha below that pre-existing floor (|alpha_k| < 1e-8is floored to 1e-8 for narrow dtypes), so on problems with extreme curvature in float32 the L-BFGS path can report convergence at a step whose stored alpha was floored. That is pre-existing floor behavior on a previously unreachable path and is left to a separate change.AI assistance disclosure: this fix was developed with AI coding assistance under my direction; I verified the root cause analysis, ran all tests shown below, and take responsibility for the change.
Test Plan
New tests use the classical Rosenbrock valley (the file's existing helper squares plain differences and never forms it):
All previously-converging cases were checked for drift before/after: standard objectives converge to bit-identical results or shift by at most one iteration.
References
dalpha = a_hi - a_lo < 0at exactly these lines and prescribesjnp.abs(dalpha); this PR implements that prescription plus the dtype/scale-aware threshold the absolute-constant version still needed. BFGS line search behavior in steep loss landscapes departs from SciPy Minimize #4594 reports the same status=3 symptom from the user side. Thea_recslot correction in 88d98bd fixed an adjacent zoom-state bug only; the signed-threshold guard remained untouched on main.