Search code examples
pythontimelimitthresholddifferential-evolution

Stop Scipy differential_evolution() after a time threshold


My question has already been partially answered here. I just need to extend the answer to another Scipy function. (Scipy 1.4.0, Python 3.7 on Windows 10)

Referring to the answer given by @ali_m, I tried to apply the same idea to the differential_evolution() Scipy function which also has the callback argument.

I would like to make sure that my Scipy differential_evolution() function stops running after a certain time limit. In this case I chose the Rosenbrock function with 40 input parameters and a threshold of 0.3 seconds to highlight what happens.

import numpy as np
from scipy.optimize import differential_evolution, rosen
import time
import warnings

class TookTooLong(Warning):
    pass

class MinimizeStopper(object):
    def __init__(self, max_sec=0.3):
        self.max_sec = max_sec
        self.start = time.time()
    def __call__(self, xk=None, convergence=None):
        elapsed = time.time() - self.start
        if elapsed > self.max_sec:
            warnings.warn("Terminating optimization: time limit reached",
                          TookTooLong)
        else:
            print("Elapsed: %.3f sec" % elapsed)

n_var = 40 

upper_bound_array = np.ones(n_var) * 5
lower_bound_array = np.ones(n_var) * -5
bounds = Bounds(lower_bound_array, upper_bound_array)

# function call
res = differential_evolution(Rosen, bounds, strategy='best1bin',disp=False,
                                    callback=MinimizeStopper(), 
                                    maxiter=1000000) 

As a result I get no errors, but it seems that the same logic used in Scipy minimize() doesn't work here. To be more specific, when I run the program, even after the Warning is raised, the program silently continues to calculate all the necessary iterations until the optimization problem converges.

Does anyone know why in this case it doesn't work like in minimize() case? I would really appreciate your help.

Thanks in advance


Solution

  • The issue is that the callback must return True or False as whether the optimisation must be halted or not (respectively).

    In your case, MinimizeStopper does not return anything and it basically just raises a warning. So you must hardcode True/False returns as well.

    Try this one

    class MinimizeStopper(object):
        def __init__(self, max_sec=0.3):
            self.max_sec = max_sec
            self.start = time.time()
    
        def __call__(self, xk=None, convergence=None):
            elapsed = time.time() - self.start
            if elapsed > self.max_sec:
                print("Terminating optimization: time limit reached")
                return True
            else:
                # you might want to report other stuff here
                # print("Elapsed: %.3f sec" % elapsed)
                return False
    

    There are a couple of things that should be considered:

    1. unless you specify polish=False as input to differential_evolution(), the polishing operation is still carried out after halting the evolution: this adds additional time
    2. the callback is triggered after finishing the evaluation of each generation: this can exceed the limit when an iteration takes substantial time.