libprima / prima

PRIMA is a package for solving general nonlinear optimization problems without using derivatives. It provides the reference implementation for Powell's derivative-free optimization methods, i.e., COBYLA, UOBYQA, NEWUOA, BOBYQA, and LINCOA. PRIMA means Reference Implementation for Powell's methods with Modernization and Amelioration, P for Powell.
http://libprima.net
BSD 3-Clause "New" or "Revised" License
292 stars 36 forks source link

[RFC] add a callback function #65

Closed jschueller closed 6 months ago

jschueller commented 10 months ago

for scipy integration we should be able to pass an optional callback function to report at least the current best x/f values

subroutine CALLBACK(x, f)
    use consts_mod, only : RP
    implicit none
    real(RP), intent(in) :: bestx(:)
    real(RP), intent(in) :: bestf
    integer(RK), intent(out) :: stop
end subroutine CALLBACK

I propose it could also to cancel the minimization (or via the objective function) according to a "stop" argument (and add a new return code for this):

I dont know if there are other useful stuff we want to track, like the number of evaluations ...

in C it is usual to pass an additional pointer argument to hook back to struct/data:

typedef int (*cbfunc)(unsigned n, const double *bestx, conda double bestf, void *func_data);

see the "callback" argument of minimize: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html

could also be useful for nlopt: https://github.com/stevengj/nlopt/issues/37

zaikunzhang commented 10 months ago

Thank you @jschueller . Could we ask the SciPy people what is the standard / preferred signature of this callback function, e.g., at https://github.com/scipy/scipy/issues/18118#issuecomment-1715536813 ? Thanks.

jschueller commented 10 months ago

seems they only report x: https://github.com/scipy/scipy/blob/main/scipy/optimize/cobyla/cobyla.pyf#L11C9-L15C32

zaikunzhang commented 10 months ago

I am copying from https://github.com/libprima/prima/pull/69#issuecomment-1722190222

The first question that we have to discuss is as follows. In callback(x, f, terminate, [, other inputs]), what do we want x and f to be? There are two possibilities.

  • x is the current best point and f is its function value
  • x is the most recently evaluated point and f is its function value

Another possibility is to change the signature of the callback function to callback(xnew, fnew, xbest, fbest, terminate, ...) to include both of them.

In addition,

  • for lincona, we must include cstrv (constraint violatio) as an input to callback,
  • for cobyla, we must include both cstrv and constr.

Insights from SciPy maintainers are greatly needed.

Thanks.

zaikunzhang commented 10 months ago

Comments by @andyfaff copied from the SciPy issue.

The design of the callback is made easier if the Fortran code can be stepped through as an iterator. i.e. Python takes control of the solve, possibly supplying things (e.g. func/grad evalutions) to the solver. It's best to have as much logic in Python as possible, Fortran as a whole is difficult to maintain.

For example, the core of differential_evolution is something akin to the following:

def solve(self):
    for i in range(numiters):
        if converged:
            # return final result
        else:
            result = next(self)

def __next__(self):
    # call the external code to take one solve step, fortran returns interesting things.
    x, fun, etc = make_fortran_step()

    # make intermediate result
    im_result = OptimizeResult(x=x, fun=fun)

    # call callback
    callback(intermediate_result=im_result)
    return im_result

L-BFGS-B/SLSQP kind of follow this pattern, and could be made cleaner in that regard.

Modern callbacks are being designed to return an intermediate OptimizeResult. See the docstring for minimize. The intermediate result should be filled out with as much information that makes sense to offer to the user.

jschueller commented 10 months ago

with the new callback we can do something similar as you get the new optimum, and if the point is good enough you can decide to continue or to stop

nbelakovski commented 6 months ago

This was resolved in https://github.com/libprima/prima/pull/113, recommend we close this issue.