scipy.optimize: report ftol termination in L-BFGS as converged - #40227
Open
VaggelisGian wants to merge 1 commit into
Open
scipy.optimize: report ftol termination in L-BFGS as converged#40227VaggelisGian wants to merge 1 commit into
VaggelisGian wants to merge 1 commit into
Conversation
Root cause: in jax/_src/scipy/optimize/_lbfgs.py an iteration whose
function decrease satisfied `(f_k - f_{k+1}) < ftol` set status=4 while
the converged mask tested only the gradient norm, so such a run ended
with converged=False, failed=True, and minimize() reported success=False
for a run that met its requested tolerance. For a clean stop this
matches the outcome SciPy L-BFGS-B reports for its ftol stopping rule;
no parity is claimed beyond that.
Change: the ftol condition now feeds `converged`, gated on line-search
success (`(~ls_results.failed) & (f_k - f_{k+1} < ftol)`), so an
ftol-stopped run reports converged=True, status=0, success=True while an
iteration whose line search failed never counts as converged via ftol.
The gate matters because a failed search can return a far-away point
whose spurious decrease satisfies the ftol test: on a scaled Rosenbrock
(float32, x0=[1.5, 1.5]) minimize() then reported success=True at
x=[-4.51e+10, 1.50e+10]; with the gate the run ends failed with status 5,
exactly as in the code before this change. The now-unreachable status=4
assignment is removed and the LBFGSResults docstring no longer lists
status 4; the ftol argument docstring states the failure exception.
Termination points are unchanged: every iteration that ended the loop
before this change still ends it at the same point (an ftol iteration
stopped via failed=True and now stops via converged=True), so iteration
and evaluation counts are identical for every input. The gtol criterion
itself is untouched.
Deliberate scope limits: the ftol criterion stays an absolute decrease,
unlike SciPy's relative reduction over max{|f_k|, |f_{k+1}|, 1}. The
absolute form is what this docstring has always documented, switching to
a relative form would move termination points for existing inputs, and
parity with SciPy holds only for clean stops, not for the stopping
criterion itself (minimize() already documents that results may differ
due to the line search). A relative criterion belongs to a separate
change. minimize() tol wiring is untouched; tests drive the solver
through options directly. A CHANGELOG entry under Unreleased documents
the behavior change.
Test Plan:
python -m pytest tests/scipy_optimize_test.py -k "ftol" -q with the
ftol term ungated, i.e. the state before the gate was added:
FAILED tests/scipy_optimize_test.py::TestLBFGS::test_ftol_not_reported_when_line_search_fails
AssertionError: Array(True, dtype=bool) is not false
1 failed, 2 passed, 16 deselected in 6.61s
python -m pytest tests/scipy_optimize_test.py -k "ftol" -q
3 passed, 16 deselected in 3.21s
Direct probe of the scaled-Rosenbrock repro (float32, x0=[1.5, 1.5]),
two deterministic runs per variant:
ungated mask:
success=True status=0 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
success=True status=0 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
gated mask (this change):
success=False status=5 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
success=False status=5 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
HEAD~1 mask swapped back in:
success=False status=5 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
success=False status=5 nit=1 nfev=20 x=[-4.51e+10 1.50e+10] fun=5.65e+09
python -m pytest tests/scipy_optimize_test.py -k "minimize or bfgs or lbfgs" -q
18 passed, 1 skipped in 10.42s
JAX_ENABLE_X64=1 python -m pytest tests/scipy_optimize_test.py -k "minimize or bfgs or lbfgs" -q
19 passed in 10.85s
python -m pytest tests/scipy_optimize_test.py -q
18 passed, 1 skipped in 10.52s
JAX_ENABLE_X64=1 python -m pytest tests/scipy_optimize_test.py -q
19 passed in 26.88s
python -m ruff check jax/_src/scipy/optimize/_lbfgs.py tests/scipy_optimize_test.py
All checks 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
In
jax.scipy.optimize.minimize(method="l-bfgs-experimental-do-not-rely-on-this"), an iteration whose function decrease satisfied(f_k - f_{k+1}) < ftolwas recorded as status 4 ("insufficient progress") while the converged mask tested only the gradient norm. Such runs ended withsuccess=Falseeven though they met the requested tolerance. SciPy's L-BFGS-B treats the same condition as convergence.The ftol condition now feeds
converged, so an ftol-stopped run reportsconverged=True, status 0,success=True, provided the line search at that iteration succeeded. If the line search failed at that iteration, the run still ends as a failure exactly as before (a failed search returns a finite objective decrease that would otherwise masquerade as ftol convergence). Status value 4 no longer occurs; termination points are unchanged for every input - such iterations previously stopped the loop viafailedand now stop it viaconverged.Deliberate scope limit: the ftol criterion stays an absolute decrease, unlike SciPy's relative reduction over
max{|f_k|, |f_{k+1}|, 1}. The absolute form is what the docstring has always documented, switching would move termination points for existing inputs, and JAX already disclaims result parity with SciPy for this method.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
Byte-level probes confirm identical nit/nfev/x/fun before and after the change for gtol-only and default runs; only the success/status reporting of ftol stops flips.
References