2

I am optimising a function using scipy.optimize in the following manner:

yEst=minimize(myFunction, y0, method='L-BFGS-B', tol=1e-6).x

My problem is that I don't want to stop simply when the tolerance is less than a value (e.g. if on the nth iteration stop is |y_n - y_(n-1)|<tol). Instead I have a slightly more complex function of y_n and y_(n-1), say tolFun, and I want to stop when tolFun(y_n, y_(n-1))<tol.

To give more detail my tolerance function is the following. It partitions y into chunks and then checks if any of the individual partitions have a norm difference within tolerance and, if any do, then the minimisation should stop.

# Takes in current and previous iteration values and a pre-specified fixed scalar r.
def tolFun(yprev,ycurr,r):

  # The minimum norm so far (initialized to a big value)
  minnorm = 5000

  for i in np.arange(r):

    # Work out the norm of the ith partition/block of entries
    norm = np.linalg.norm(yprev[np.arange(r)+i*r],ycurr[np.arange(r)+i*r])

    # Update minimum norm
    minnorm = np.min(norm, minnorm)

  return(minnorm)

My question is similar to this question here but differs in the fact that this user needed only the current iterations value of y, whereas my custom tolerance function needs both the current iterations value of y and the previous value. Does anyone know how I could do this?

JDoe2
  • 267
  • 1
  • 12

1 Answers1

4

You cannot do directly what you want since the callback function receives only the current parameter vector. To solve your problem you can modify second solution from https://stackoverflow.com/a/30365576/8033585 (which I prefer to the first solution that uses global) in the following way or so:

class Callback:
    def __init__(self, tolfun, tol=1e-8):
        self._tolf = tolfun
        self._tol = tol
        self._xk_prev = None

    def __call__(self, xk):
        if self._xk_prev is not None and self._tolf(xk, self._xk_prev) < self._tol:
            return True

        self._xk_prev = xk
        return False

cb = Callback(tolfun=tolFun, tol=tol)  # set tol here to control convergence
yEst = minimize(myFunction, y0, method='L-BFGS-B', tol=0, callback=cb)

or

yEst = optimize.minimize(
    myFunction, y0, method='L-BFGS-B',
    callback=cb, options={'gtol': 0, 'ftol': 0}
)

You can find available options for a solver/method using:

optimize.show_options('minimize', 'L-BFGS-B')
AGN Gazer
  • 8,025
  • 2
  • 27
  • 45
  • Ah I see! Brilliant! Thank you! That's exactly what I was hoping for! Just to clarify though, why is it that you return `True`/`False` here. The example referenced raises an exception to stop the minimisation running... can returning `True`/`False` achieve the same thing here? – JDoe2 Aug 07 '19 at 13:51
  • 1
    @JDoe2 See documentation for `callback` in https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html for details. It says: _"If callback returns `True` the algorithm execution is terminated."_ hence no need to trow exceptions to end execution – AGN Gazer Aug 07 '19 at 14:33
  • Ah I see... going by that documentation I thought that is only for optimization with ‘trust-constr’ though? – JDoe2 Aug 07 '19 at 17:11
  • @JDoe2 No, it applies to any `callback`. The `'trust-constr'` thing affects only function signature – AGN Gazer Aug 07 '19 at 18:58