Skip to content

Conversation

@dangarciahe
Copy link

Description

This PR adds a fix to the AMPGO method for the minize() function. Previously, when AMPGO is aborted due to reaching max_nfev, it will return the last evaluated values, instead of the best value found so far, an incorrect behavior per AMPGO paper. This was discussed in #1017. The new behavior fixes this, and will return the best value found so far, even if the process is aborted due to max_nfev.

Additionally, a new test is added: It verifies against a known case to exhibit this troubled behavior in the current version, but returns a correct value under the newer version proposed by this PR.

This should improve AMPGO method, specially when minimizing computationally slow functions where an arbitrarily big max_nfev is not feasible, and a test to prevent future code to accidentally re-introduce this behavior.

Type of Changes
  • Bug fix
  • New feature
  • Refactoring / maintenance
  • Documentation / examples
Tested on

Python: 3.14.0 | packaged by Anaconda, Inc. | (main, Oct 8 2025, 17:15:28) [GCC 11.2.0]

lmfit: 1.3.4, scipy: 1.16.2, numpy: 2.3.4, asteval: 1.0.6, uncertainties: 3.2.3

Verification

Have you

  • included docstrings that follow PEP 257?
  • referenced existing Issue and/or provided relevant link to mailing list?
  • verified that existing tests pass locally?
  • verified that the documentation builds locally?
  • squashed/minimized your commits and written descriptive commit messages?
  • added or updated existing tests to cover the changes?
  • updated the documentation and/or added an entry to the release notes (doc/whatsnew.rst)?
  • added an example?
Minimal reproducible example

Here's a minimal example that allows to see the improvements:

import numpy as np
import lmfit

np.random.seed(10)

def parabolic(p):
    x = float(p["x"])
    y = x**2
    print(f"x: {x:.5e}, y:{y:.5e}")
    return y

fit_params = lmfit.Parameters()
fit_params.add('x', value=-2, min=-10, max=10, vary=True)
result = lmfit.minimize(parabolic, fit_params, method="ampgo", max_nfev=100)

print(lmfit.fit_report(result))

Under the older version, the fit will return x: 0.30768323 despite the existence of several evaluations of x in the order of x=-4.99985e-08, as evidenced by the prints; new version will correctly return x: -4.9999e-08

@reneeotten
Copy link
Contributor

thansk @dangarciahe for pointing out the issue and providing a solution! And, yes, we should certainly fix this.

I am just not sure whether that should be done in the actual _ampgo.py implementation or whether it can be done somehow in the ampgo finction minimizer.py. The latter would be preferable - I'll need to look.

@newville
Copy link
Member

@dangarciahe @reneeotten Thanks! I would probably have a slight preference for doing this in minimizer.py, but wouldn't be adamant about that.

@dangarciahe
Copy link
Author

Hi @newville @reneeotten. I would be happy to make those changes, but have no idea on how to, given that, from what I underestand, if _ampgo.py aborts, minizer.py has no idea of the current best value, so I don't see how could I catch those values. Hence why I added the try-except block to _ampgo.

Some guidance in this would be appreciated.

pass

if not result.aborted:
if 'ret' in locals():
Copy link
Member

Choose a reason for hiding this comment

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

please have AbortFitException set ret = None
and then make the test here be if ret is not None.

for i, par in enumerate(result.var_names):
result.params[par].value = float(result.ampgo_x0[i])

if not result.aborted:
Copy link
Member

Choose a reason for hiding this comment

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

result.nfev -= 2 if result.aborted else 1

might be worth considering here

res = minimize(tunnel, x0, args=tunnel_args, method=local,
bounds=bounds, tol=local_tol, options=options)
except Exception as e:
if e.__class__.__name__ == "AbortFitException":
Copy link
Member

Choose a reason for hiding this comment

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

        except Exception as exc:
            if isinstance(exc, AbortFitException):
              ...

Copy link
Member

@newville newville Nov 14, 2025

Choose a reason for hiding this comment

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

.... or bettter....

        except AbortFitException:
               do things....
        except Exception as exc: 
              do other things... 

@newville
Copy link
Member

@dangarciahe Thanks - I think a few small changes and this will be a great addition. Happy to hear others thoughts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants